鋤禾日當午,學C真TM苦。編程
指針是C語言中功能最強大的部分,可是也是最難理解的部分。數組
本文主要介紹二級指針的使用,包括與數組和函數結合的使用,一級指針簡單帶過。ide
1、一級指針
函數
一級指針的簡單描述學習
①一級指針是一種以存儲其餘變量地址爲目的的變量。一個T類型的指針變量(一級指針)就是一個存儲了某T類型值變量的內存地址的引用。spa
②對指針的解引用(也就是*),是獲取指針指向的內存空間中存儲的值。指針
③當指針解引用作左值的時候,能夠改變指針指向內存空間中存儲的值。code
④指針指向哪一個變量,就將該變量的內存地址賦給該指針(用於指針變量作左值的時候)。對象
⑤改變指針變量的值(指針變量作左值時),就改變了該指針的指向。blog
2、二級指針的相關介紹
多級指針(pointer to pointer to)是指向指針的指針,二級指針是指向一級指針的指針。
一級指針指向的是某個變量,也就是一級指針中存儲的是某個變量的內存地址;二級指針指向一級指針,也就是二級指針中存儲的是一級指針的內存地址。
代碼
int main(void) { int a = 10; //聲明一個變量a int *p = &a; //聲明指針p,指向變量a int **q = &p; //聲明二級指針q,指向一級指針p printf("a = %d\n",a); //打印變量a的值 printf("a的地址&a=%p\n",&a); //打印變量a的地址 printf("p = %p\n",p); //打印p的值 printf("p的地址&p=%p\n",&p); //打印p的地址 printf("p的解引用*p=%d\n",*p); //打印p的解引用 printf("q = %p\n",q); //打印q的值 printf("q的地址&q=%p\n",&q); //打印q的地址 printf("q的解引用*q=%p\n",*q); //打印q的解引用 printf("q的雙重解引用**q=%d\n",**q); //打印q的雙重解引用 return 0; }
執行結果
a = 10
a的地址&a=0x7fff5fbff838
p = 0x7fff5fbff838
p的地址&p=0x7fff5fbff830
p的解引用*p=10
q = 0x7fff5fbff830
q的地址&q=0x7fff5fbff828
q的解引用*q=0x7fff5fbff838
q的雙重解引用**q=10
內存結構示意圖
如上圖的代碼和執行後的結果,從結果中就能夠看出變量a,一級指針p和二級指針q的關係。
在學習的過程當中主要是對二級指針不是很理解,因此這裏特別對二級指針說明一下。
變量q是一個二級指針,裏面存放的是一級指針p的地址,對q的解引用就獲得了q所指向的一級指針的值,也就是變量a的地址,對q雙重解引用就獲得了變量a的值,因此也能夠經過二級指針來修改變量a的值。
3、指針與二位數組
首先一點的是,雖然二維數組的數組名能夠看作是一個指針,可是並不能將二維數組的數組名賦值給一個二級指針,也就是以下的代碼
int main(void) { int arr[3][3] = {{1,2,3},{4,5,6},{7,8,9}}; int **p = arr; return 0; }
上面的代碼在執行的時候會報錯,這是由於二者數據類型是不相同的,因此不能賦值。
二維數組的數組名指向的是一維數組,也就是指向數組類型,可是二級指針指向的是一級指針,也就是指針類型,因此二者不能互相賦值。
下面詳細介紹指針與二維數組的關係
聲明一個二維數組
int arr[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
要想理解指針和二維數組之間的關係,就要首先弄清楚各類數組名所表明的元素。
對於二維數組的存儲,是按照先行後列的形式排列的,把每一行看作是一個一位數組,那二維數組就是由多個一位數組組成的數組,即二維數組是數組的數組。
對於二維數組名,能夠將它看作是指向行數組的指針,也就是說二維數組的元素是行數組,因此對於二維數組加減的變化是以行數組的大小爲單位的,即arr指向arr[0]這個行數組,arr+1指向的是arr[1]這個行數組。對其進行解引用,獲得的是每一行數組的首地址,即*arr表示的是第0行數組的首地址,和arr[0]相等,*(arr+1)表示的是第1行數組的首地址,和arr[1]是相等的。假如要取第1行第2個元素的地址,就是arr[1]+2,由於此時arr[1]表明的是一維數組,因此它的元素就是一個數字,在加減的時候移動的就是元素的大小,例如+1就表示該數組中第1個元素,+3就表示數組中的3個元素(以0開始)。由於
*(arr+1)和arr[1]相等,因此第1行第2個元素的地址也能夠表示爲*(arr+1)+2,對這個地址進行解引用,獲得的就是數組元素的具體值,也就是*(*(arr+1)+2)。
因此有以下公式,假如一個二維數組每行有N個元素,二維數組名是arr,那第i行第j個元素的地址是
*(arr+i*N)+j,也能夠表示爲arr[i]+j。
元素的值能夠表示爲*(*(arr+i)+j),或者能夠表示爲arr[i][j]。
還用數組的形式來表示元素的地址和值,能夠更方便編程人員的閱讀,可是使用指針的方式更方便與C語言編譯器閱讀。
驗證代碼以下
int main(void) { int arr[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}}; //分別使用不一樣的形式打印第2行,第3個元素的值和地址 //根據推到出來的規律以下 //該元素的地址 printf("%p\n",*(arr+2*4)+3); printf("%p\n",arr[2]+3); printf("%p\n",&arr[2][3]); //該元素的值是12 printf("%d\n",*(*(arr+2)+3)); printf("%d\n",arr[2][3]); return 0; }
執行結果以下
0x7fff5fbff88c
0x7fff5fbff82c
0x7fff5fbff82c
12
12
對上述二維數組arr,雖然arr[0]、arr都是數組首地址,但兩者指向的對象不一樣,arr[0]是一維數組的名字,它指向的是arr[0]數組的首元素,對其進行「*」運算,獲得的是一個數組元素值,即arr[0]數組首元素值,所以,*arr[0]與arr[0][0]是同一個值;而a是一個二維數組的名字,它指向的是它所屬元素的首元素,它的每個元素都是一個行數組,所以,它的指針移動單位是「行」,因此arr+i指向的是第i個行數組,即指向arr[i]。對arr進行「*」運算,獲得的是一維數組arr[0]的首地址,即*arr與arr[0]是同一個值。當用int *p;定義指針p時,p的指向是一個int型數據,而不是一個地址,所以,用arr[0]對p賦值是正確的,而用arr對p賦值是錯誤的。
4、數組指針
既然不能將二位數組的數組名賦值給二位指針,那該用什麼來表示二位數組呢。答案就是數組指針。數組指針就是指向數組的指針。
int arr[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (*p)[4] = arr;
如上圖所示的聲明方式,能夠認爲指針p就是指向數組的指針,這個數組中有4個int類型的元素,此時p的增量以它所指向的一維數組長度爲單位。同時要想使用指針p來表示數組arr中的元素,上面總結的規律也是可使用的。
以下圖的代碼,就是將上面的arr換成了指針p,一樣可使用。
int main(void)
{
int arr[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (*p)[4] = arr;
//分別使用不一樣的形式打印第2行,第3個元素的值和地址
//根據推到出來的規律以下
//該元素的地址
printf("%p\n",*(p+2*4)+3);
printf("%p\n",p[2]+3);
printf("%p\n",&p[2][3]);
//該元素的值是12
printf("%d\n",*(*(p+2)+3));
printf("%d\n",p[2][3]);
return 0;
}
使用數組指針指向一維數組
int a[3]={1,2,3}; 那麼p就是指向這個數組a的指針。
int(*p)[3]=&a; // 這裏賦值必定要用取地址符號。也就是取數組a的地址。
不能夠這樣賦值: int(*p)[3]=a; // error :類型不兼容。a原本是數組類型,是不能夠賦值給int(*)[3]這個類型的。
可是這樣是能夠的int *p1=a; // ok 由於a能夠隱式轉換爲int*類型,其值實際上就是數組第一個元素的地址,也就是&a[0]。
數組指針練習題
//打印數組 void print_Array(char (*p)[30],int *len) { for(int i=0;i<*len;i++) { printf("%s\n",p[i]); } } //數組排序 int sort(char (*p)[30],int *len) { int ret = 0; char tmp[100]; if(p==NULL||len==NULL) { printf("function sort error"); ret = -1; } for(int i=0;i<*len;i++) { for(int j=i+1;j<*len;j++) { if(strcmp(p[i],p[j])>0) { strcpy(tmp,p[i]); strcpy(p[i],p[j]); strcpy(p[j],tmp); } } } return ret; } int main(void) { char array[10][30] = {"sdecs","codeq","owxja","qwer","bsdws"}; int count = 5; print_Array(array, &count); sort(array, &count); printf("排序以後的數組\n"); print_Array(array, &count); return 0; }
執行結果
sdecs codeq owxja qwer bsdws 排序以後的數組 bsdws codeq owxja qwer sdecs
上面的代碼是使用數組指針作函數參數,接受主調函數傳遞過來的二維數組的地址,而後利用數組指針對二位數組進行相關的操做。
5、指針數組
說完了數組指針,如今接着要說一下指針數組,這二者並無什麼聯繫,放在一塊兒是由於二者的聲明比較像,有時候容易弄混,放在一塊兒能夠比較一下。
指針數組就是數組裏面存放的是指針這種數據類型。
int main(void) { int a = 1; int b = 2; int c = 3; int *d = &a; int *e = &b; int *f = &c; int *p[3] = { d, e, f }; printf("%d\n",*p[0]); printf("%d\n", *d); printf("%p\n", p[0]); printf("%p\n", &a); return 0; }
執行結果
1
1
0x7fff5fbff818
0x7fff5fbff818
如上圖所示分別是指針數組的代碼和相應的執行結果。
上面的p就是一個數組,這個數組有3個元素,每一個元素都是int *類型的,也就是每一個元素都是指向int類型的指針。
下面是指針數組的練習題
//數組打印 void print_array(const char **p,const int *len) { for(int i=0;i<*len;i++) { printf("%s\n",p[i]); } } //元素排序 int sort(char **p,int *len) { char **str = p; int *count = len; char *tmp = NULL; //函數返回值 int ret = 0; if(str==NULL||len==NULL) { printf("function sort error"); return ret; } for(int i=0;i<*count;i++) { for(int j=i+1;j<*count;j++) { if(strcmp(str[i],str[j])>0) { tmp = str[i]; str[i] = str[j]; str[j] = tmp; } } } return ret; } int main(void) { char *array[] = {"sdjd","asdjf","peroa","asoeq"}; int count = sizeof(array)/sizeof(array[0]); //排序前打印 print_array(array, &count); //調用排序函數 sort(array, &count); //排序後打印 printf("排序後打印\n"); print_array(array, &count); return 0; }
執行結果
sdjd asdjf peroa asoeq 排序後打印 asdjf asoeq peroa sdjd
在這裏須要補充的一點是,C語言中沒有字符串這種數據類型,字符串在C語言中使用字符數組來表示。
注意點:①在調用函數的時候,使用了二級指針接受main函數中的指針數組,同時能夠看到在傳遞數組個數的時候仍然使用了實參傳遞地址,形參使用指針來接受參數的這種形式,這是指針在作函數參數的時候很是重要的應用。
在函數傳遞參數的時候,經過指針給主調函數中的變量間接複製,是指針存在最大的意義。例如想經過在被調函數中改變主調函數中變量的值,則能夠在函數調用的時候將變量的地址傳遞過去,在被調函數中使用一級指針來接受,這樣在被調函數中就能夠經過指針來間接改變變量的值;同理,若是在被調函數中要改變主調函數中一級指針的值,則能夠在被調函數中經過二級指針接受一級指針的地址,從而實如今被調函數中改變一級指針的值。同時這樣作還有一個好處就是,由於被調函數只能有一個返回值,當想返回多個值的時候就很難實現,可是經過上面的那種方式,即在被調函數中經過地址來間接改變變量(指針也是一種變量)值的方式,就能夠在被調函數中改變多個變量的值。而只是讓函數返回函數執行狀態的值,這樣就能夠判斷出現了哪一種狀況。
注意上面這段代碼(1代碼)和以前的數組指針練習題代碼(2代碼)的比較
①在計算數組元素個數的時候,2代碼若是也使用和1代碼相同的方式求元素個數,獲得的結果永遠都是10,但實際元素個數並非10,出現這種現象的緣由是由於2代碼中固定了數組元素的個數,可是1代碼中並無固定。
②在排序交換數組元素的時候,使用的方式也不相同,1代碼使用的是交換指針指向的方法,也就是說,指針中存儲的數組的地址改變了,指針的指向變了;2代碼使用的是交換交換數組的值,也就是指針指向不變,可是指針所指向的內存空間中的值變了。
6、三級指針形參接受二級指針地址進行相關操做
//排序 int sort(char ***p,int *len) { char *tmp = NULL; int ret = 0; if(p==NULL||len==NULL) { printf("function sort error"); ret = -1; } for(int i=0;i<*len;i++) { for(int j=i+1;j<*len;j++) { if((*p)[i]<(*p)[j]) { tmp = (*p)[i]; (*p)[i] = (*p)[j]; (*p)[j] = tmp; } } } return ret; } //給二級指針分配內存 int getMem(char ***p,int count) { int ret = 0; if(p==NULL) { printf("function getMem error"); ret = -1; } *p = (char **)malloc(sizeof(char*)*(count)); for(int i=0;i<count;i++) { (*p)[i] = (char *)malloc(sizeof(char)*100); sprintf((*p)[i],"%d%d%d",i,i,i); } return ret; } //打印數組 void printArray(char ***p,int count) { for(int i=0;i<count;i++) { printf("%s\n",(*p)[i]); } } //釋放內存空間 int freePoint(char ***p,int count) { if(p==NULL) { return -1; } for(int i=0;i<count;i++) { free((*p)[i]); } free(*p); return 0; } int main(void) { char **p = NULL; int count = 5; //分配內存 getMem(&p, count); //打印數組 printArray(&p, count); //排序 sort(&p, &count); //打印數組 printArray(&p, count); //釋放內存 freePoint(&p, count); return 0; }
如上圖所示的代碼,在main方法中聲明瞭一個二級指針,而後先講二級指針的地址作實參傳遞給甘薯getMem,在函數getMem中使用三級指針作形參接受二級指針,在函數中分配相應的內存空間,而後賦值。
代碼注意點①在分配內存的時候,判斷的是三級指針p是否爲NULL,而不是二級指針
②在(*p)[i]的時候注意符號的優先級,上面的代碼若是改爲*p[i]就會出錯,由於[]的優先級大於*,因此這樣寫會形成越界。
剛學C語言,能力有限,以上的內容有不對或者寫的很差的地方請提出來,我會盡力改正。謝謝!