指針
指針是什麼
指針 是一種數據變量類型,它儲存了一個寄存器地址,這個地址的值指向儲存在該地址的對象的值。
名爲 a 的指針,指向一個存儲器地址,當中的值爲 b。
圖片來自Wikipedia.org
指針的優點
指針相對於值傳遞的速度更快(在相應變量佔用儲存空間較多的情況下),值傳遞在C/C++中是將已有對象複製一份到一個新的內存空間中;而指針則是給出儲存數據的位置和數據類型,對數據直接進行操作。
定義指針變量
定義指針變量比較簡單,如下
int *a; //定義整型指針變量a
char *b; //定義char類型指針變量
int **c; //定義(int *)類型指針變量,所謂的二級指針
char ***d; //定義(char **)類型的指針變量,所謂的三級指針
//特殊的指針,void型指針,可以指向任何數據類型
void* ptr = a; //OK 指向int類型的數據
ptr = b; //OK 指向char類型的數據
指針變量的值中有一個非常特殊的值: NULL,它不指向系統中的任何變量或者函數。一般,我們使用它作爲一個標誌(返回指針的函數沒有正確執行、到達鏈表末尾等等)
指針的運算符
指針的常用運算符有兩個,一個是 &,一個是 *
首先說說 &,它是取地址運算符(在這裏它是單目運算符,區別於按位與),使用它可以得到相應變量的儲存地址。
其次說說 *,它是指針運算符(或稱間接訪問運算符,區別於乘號),使用它可以得到指針指向空間的實際值,也可以修改指針指向空間的值。(需要區別於定義變量時,在變量名前寫的星號)
舉幾個例子
int a = 0;
int *b = &a;
int **c = &b; //多級指針賦值
*b = 1; //a == 1, 完全等價於 a = 1;
*(&a) = 2; //a == 2, 完全等價於 a = 2;
&(*b) == b; //先間接訪問a,再取出其地址, true
*(&a) == a; //先取a的地址,在通過指針間接訪問a, true
"&*(接地址)"連用表示什麼都沒幹
"*&(接變量名)"連用也表示什麼都沒幹
const修飾
在定義指針的時候,可以使用const對其進行修飾,const修飾有兩種寫法
int a[2] = {1, 2};
const int *b = a; //不能修改指針指向內容的值
b++; //合法的
*b = 2; //非法的
int *const c = a; //不能修改指針變量的值
*c = 4; //合法的
c++; //非法的
兩種const修飾也可以一起用
int a[2] = {1, 2};
const int *const b = a; //既不能修改指針變量的值,也不能修改指針指向內容的值
b++; //非法的
*b = 3; //非法的
coding環節
例一
觀察下面兩種方法的輸出結果有何不同
例一值傳遞
#include <stdio.h>
void swap(int a, int b) {
int c = a;
a = b;
b = c;
}
int main() {
int a = 2, b = 3;
swap(a, b);
printf("a=%d\tb=%d\n", a, b);
return 0;
}
例一指針傳遞
#include <stdio.h>
void swap(int* pa, int* pb) {
int c = *pa;
*pa = *pb;
*pb = c;
}
int main() {
int a = 2, b = 3;
swap(&a, &b);
printf("a=%d\tb=%d\n", a, b);
return 0;
}
例一總結
值傳遞只是拷貝了一份變量的值到新的內存空間,對新內存空間中的變量進行操作,而不會對原有變量造成任何影響。指針操作則直接對原有變量內存空間中的數據進行了修改,從而正確地完成了交換數據的功能。
例二
對比兩種方法的運行時間
例二值傳遞
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//定義一個佔用空間比較多的變量類型
typedef struct {
int a;
float b;
double c;
double d;
double e;
double f;
double g;
double h;
} comp;
int main() {
comp a, b = {};
getchar(); //排除執行過程對運行時間產生的影響
clock_t start = clock(); //記錄開始時間
int i = 0;
for (; i < 100000000; i++) {
b.a = i;
a = b; //每次執行此條語句都是將comp中的所有變量一個一個的從b賦值到a
}
clock_t end = clock(); //記錄結束時間
printf("Used time=%dms\n", end - start);
return 0;
}
例二指針傳遞
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//定義一個佔用空間比較多的變量類型
typedef struct {
int a;
float b;
double c;
double d;
double e;
double f;
double g;
double h;
} comp;
int main() {
comp *a, b = {};
getchar(); //排除編譯過程對運行時間產生的影響
clock_t start = clock(); //記錄開始時間
int i = 0;
for (; i < 100000000; i++) {
b.a = i;
a = &b; //只取出b的地址
}
clock_t end = clock(); //記錄結束時間
printf("Used time=%dms\n", end - start);
return 0;
}
例二總結
在變量佔用空間較多的情況下,採用指針傳值會比普通的值傳遞快很多
總結1
上面的兩個例子說明了在一些情況下,使用指針賦值來代替傳統的賦值操作會讓程序運行得更快
而且我們對指針的調用也能夠實現對兩個變量的值進行交換,或是讀取相應變量的值。
而這時有人或許會對指針爲什麼還要有不同的類型有所疑問。下面就讓我們嘗試着運行一下下面
的這串代碼。
#include <stdio.h>
int main() {
int array1[10];
char array2[10];
printf("數組元素中,第0個元素的地址 %d\n", &array1[0]);
printf("整型數組的首地址是 %d\n數組中第一個元素的地址是 %d\n", array1, &array1[1]);
//使用指針加1的效果:
printf("int型指針加1: %d\n", array1 + 1);
printf("字符數組的首地址是 %d\n數組中第一個元素的地址是 %d\n", array2, &array2[1]);
//使用指針加1的效果:
printf("char型指針加1:%d\n", array2 + 1);
return 0;
}
C\C++使用指針的類型來確定一次指針變量 +1 需要在內存空間向後移動多少個字節,同時
指針的類型也使得程序能夠正確地完成指針–>值的轉換(這裏的"值"指的是指針指向的內存
空間中儲存的值)。
其次,上面的程序也證明了數組的首地址與數組名中儲存的地址相同!
下圖是一個字符數組在內存空間內的存儲結構圖,對於字符數組,其指針每加“1”在內存空間內就是將指向的地址向後移動了一個字節(8位)的長度。
下圖是一個整型數組在內存空間內的儲存結構圖,對於整型數組,其指針每加“1”在內存空間內就是將指向的地址向後移動了四個字節(32位)的長度。
二維數組的指針
運行一下試試
#include <stdio.h>
int main() {
int array[3][10];
printf("二維數組首地址 %d\n", array);
printf("第一行一維數組首地址 %d\n", *array);
printf("二維數組第二行地址 %d\n", array + 1);
printf("第二行一維數組首地址 %d\n", *(array + 1));
return 0;
}
是不是覺得二維數組的指針還是挺好理解的
那下面來看看提高部分吧
Error code
#include <stdio.h>
int main() {
int array[3][10] = {};
int **p = array;
printf("第1行第0列的值 %d\n", **(p1+1));
return 0;
}
好吧,這樣居然就報錯!我強制轉換總行了吧!
#include <stdio.h>
int main() {
int array[3][10] = {};
int **p = (void**)array;
return 0;
}
還錯,那怎樣才正確嘛!
Correct code
#include <stdio.h>
int main() {
int array[10][10] = {};
int(*p1)[10] = array;
printf("第1行第0列的值 %d\n", **(p1+1));
return 0;
}
二維數組指針總結
二維數組指針和二級指針的差異來自於內存中儲存數組方式的不同
理想的情況下,二維數組儲存的方式如下圖所示
而在計算機的內存中,考慮到內存空間的總量有限,二維數組是以下圖的形式儲存的
也就是說,在C\C++中,二維數組是一個僞二維數組,它本質上就是一個一維數組
函數指針
函數指針,函數指針,看名字就能看出來,它是一個指向函數的指針。定義方法爲:
<Typename1> (*funcPointer)(<Typename2>, …)
Typename1 爲函數返回值類型
Typename2… 爲函數的形參表
下面提供一個使用函數指針的例子
例三
#include <stdio.h>
int func1(int a) {
return a / 2;
}
int func2(int a) {
return a * 2;
}
int main() {
int (*funcPointer)(int); //定義函數指針
funcPointer = func1;
printf("func1: %d\n", funcPointer(10)); //函數指針的調用
funcPointer = func2;
printf("func1: %d\n", funcPointer(10)); //函數指針的調用
return 0;
}
例三總結
函數指針是一個非常非常有用的工具,下面我們就舉一個例子。
例四
在下面的這個例子中,我們將使用函數指針來指定數組的排序方法(升序 or 降序)
//請如果現在打開的是C語言的項目,請先新建一個C++的項目
#include <iostream>
#include <algorithm>
using namespace std;
int array[1000];
bool cmp(int front, int behind) { //比較函數(現在是升序)
return front < behind;
}
int main() {
int n;
//*****輸入數組 Start******
cin >> n;
int i = 0;
for (; i < n; i++) {
cin >> array[i];
}
//****輸入數組 End*********
sort(array, array+n, cmp); //傳入比較函數,告訴sort這個數組從小到大排
for (i = 1; i < n; i++) { //測試數組排序是否正確
if (array[i] < array[i - 1]) {
cout << "Error!\n";
return -1;
}
}
return 0;
}
例五
使用指針進行數組元素交換
啥,怎麼又是使用指針交換數組元素????
這次是數組元素萬能交換~~~
#include <stdio.h>
void swp(void*, unsigned);
int main() {
int a[2] = {1, 2};
double b[2] = {2.4, 3.4};
swp(a, sizeof(int));
printf("int數組 %d %d\n", a[0], a[1]);
swp(b, sizeof(double));
printf("double數組 %f %f", b[0], b[1]);
return 0;
}
/*******萬能數組元素交換器************
* 傳入參數ptr(任意類型指針),size指針對應的變量所佔用的字節數(可使用sizeof(類型名))
*/
void swp(void* ptr, unsigned size) {
char a;
unsigned i;
char *first, *second;
first = (char *)ptr;
second = first + size;
for (i = 0; i < size; i++) {
a = first[i];
first[i] = second[i];
second[i] = a;
}
}
//**************end*****************
例六
使用qsort對結構體進行排序
#include <stdlib.h>
#include <stdio.h>
int array[1000];
/**
* 比較函數類型 int funcName(const void *, const void *)
* 使用const修飾以避免對數組元素值的意外修改
*/
int cmp(const void *front, const void *behind) {
return *(const int *)front - *(const int *)behind; //將void *指針轉換爲int *(使用const修飾以避免對數組元素值的意外修改)
}
int main() {
int n;
scanf("%d", &n);
int i = 0;
for (; i < n; i++) {
scanf("%d", array + i);
}
qsort(array, n, sizeof(int), cmp); //傳入四個參數,第一個是數組首地址,第二個是數組長度,第三個是數組中一個元素所佔用的字節數,第四個是比較函數
for (i = 1; i < n; i++) {
if (array[i] < array[i-1]) {
printf("Error!\n"); //未正確排序,輸出Error,並退出程序
return -1;
}
}
return 0;
}
總結2
指針作爲一種高效的傳遞參數的變量類型,正確地使用可以加快程序運行速度。在C/C++中也有許多用到了指針的地方。例如:qsort,sort函數 cout操作符等。