基本概念
如果一個指針變量存放的又是另一個指針變量的地址,則稱這個指針變量爲指向指針的指針變量。也稱爲“二級指針”。
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> int main(void) { int a = 10; int *p = &a; int **pp = &p; printf("&a : %p\n", &a); printf("p : %p\n", p); printf("&p: %p\n", &p); printf("pp: %p\n", pp); printf("&pp: %p\n", &pp); printf("*p: %d\n", *p); printf("*pp:%p\n", *pp); printf("**pp:%d\n", **pp); return 0; }
用處
我們知道,指針往往作爲函數參數才能發揮最大作用。而指針作爲函數參數具有輸入輸出特性。
二級指針也是指針,同樣具有上述屬性。二級指針作爲輸出參數
最典型的例子就是跨函數開闢堆內存空間。(分配,初始化,賦值,釋放)#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> //二級指針作爲輸出參數 //在函數內部 在堆上開闢空間 傳出去。 int get_mem(/*out */char **mem1, int *mem_len1, char **mem2, int *mem_len2) { char *temp_p1 = NULL; char *temp_p2 = NULL; int len1 = 0; int len2 = 0; if (mem1 == NULL || mem2 == NULL || mem_len1 == NULL || mem_len2 == NULL) { fprintf(stderr, " (mem1 == NULL || mem2 == NULL || mem_len1 == NULL || mem_len2 == NULL) \n"); return -1; } temp_p1 = (char *)malloc(4096); if (temp_p1 == NULL) { return -1; } memset(temp_p1, 0, 4096);//在堆上開闢4096個BYTE的內存空間,然後判斷指針是否爲空,最後初始化 strcpy(temp_p1, "12345678"); len1 = strlen(temp_p1); temp_p2 = (char*)malloc(4096); if (temp_p2 == NULL) { return -1; } memset(temp_p2, 0, 4096); strcpy(temp_p2, "abcdefg"); len2 = strlen(temp_p2); //以上開闢完空間 *mem1 = temp_p1; *mem2 = temp_p2; *mem_len1 = len1; *mem_len2 = len2; return 0; } void free_mem(char **mem1, char **mem2) { char *temp_mem1 = *mem1; char *temp_mem2 = *mem2; if (mem1 != NULL) { free(temp_mem1); } if (mem2 != NULL) { free(temp_mem2); } *mem1 = NULL; *mem2 = NULL; } int main(void) { char *buf1 = NULL; char *buf2 = NULL; int len1 = 0; int len2 = 0; if (get_mem(&buf1, &len1, &buf2, &len2) < 0) { return -1; } printf("buf1: %s, buf2:%s\n", buf1, buf2); free_mem(&buf1, &buf2); return 0; }
二級指針做輸入參數
二級指針做輸出參數,本質上操作的還是一級指針,只是披着二級指針的外衣。
而二級指針做輸入參數則是真正的對二級指針進行操作。二級指針做輸入參數的三個模型
- char * Array[num]
- char Array[][num]或者 char (*Array)[num]
- char ** Array 或者 char Array[num1][num2]
首先我們來看一看這三種輸入參數的區別。
對char * Array[num]。我們可以理解爲char * Array和[num]的組合。對前者我們再熟悉不過了,是一個字符指針,對後者我們可以理解爲字符指針的個數。那麼我們就可以這樣理解char * Array[num]。定義了一個字符指針的數組,簡稱爲指針數組。數組中每一個元素存放的都是各指針指向字符串首元素的地址。所以每個元素佔4個字節,指針的步長爲4。
任務:畫出下列程序的內存四區圖#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> //等價於int print_array(char* array[], int len) int print_array(char* *array, int len) { int i = 0; for (i = 0; i < len; i++) { //操作字符串的兩種方法printf("%s\n", array[i]); printf("%s\n", *(array + i)); } return 0; } int sort_array(char *array[], int len) { int i = 0; int j = 0; char *temp = NULL; for (i = 0; i < len; i++) { for (j = i; j < len; j++) { if (strcmp(array[i], array[j]) > 0) { temp = array[i]; array[i] = array[j]; array[j] = temp; } } } return 0; } int main(void) { char * myArray[] = { "aaaaaa", "ccccc", "bbbbbb", "111111" }; int len = 0; len = sizeof(myArray) / sizeof(myArray[0]); // 16 / 4 = 4個 printf("排序之前\n"); print_array(myArray, len); //排序 sort_array(myArray, len); printf("排序之後\n"); print_array(myArray, len); return 0; }
對char Array[][num]或者 char (*Array)[num]來說,二級指針的傳參就又有不同了。
我們在上一程序中,訪問指針數組時使用的是array[i],但是這是怎麼實現的呢?我們知道指針變量在內存中佔4個字節。指針數組中存放的元素是指向字符的指針。所以指向指針數組的指針的步長是4個字節。在上述程序中myArray+1實際上移動了4個字節。
而對於數組myArray[5][6]來說,myArray+1移動的是4個字節嗎?答案很明顯不是。這是一個5行6列的數組,也就是說一行有6個元素,如果數組是char 類型,那麼一行就佔有6個字節。我們知道,字符數組中一行存儲一個字符串,那麼我如果從一個字符串移動到下一個字符串,按照以上的設定,就會移動6個字節。
所以,對於二維數組的參數傳遞,不能使用上述程序的方法。下述程序解決了這一問題:#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> //int print_array(char * *array, int num) //int print_array(char array[5][6], int num) //int print_array(char[6]* array, int num) 這三種指針傳遞方法都是錯誤的 int print_array(char array[][6], int num) //int print_array(char (*array)[6], int num)這兩種方法是等價的 { int i = 0; for (i = 0; i < num; i++) { //printf("%s\n", array[i]); //my_array[0]; my_array[0]--->"aaa" printf(%s, myArray[0]); printf("%s\n", *(array + i)); /// ===>array 應該是一個指向 char[6]的指針 } return 0; } int sort_array(char array[][6], int num) { char buf[6] = { 0 }; int i = 0; int j = 0; for (i = 0; i < num; i++) { for (j = i; j< num; j++) { if (strcmp(array[i], array[j]) > 0) { strcpy(buf, array[i]); strcpy(array[i], array[j]); strcpy(array[j], buf); } } } return 0; } int main(void) { char my_array[5][6] = { "aaa", "ccc", "bbb", "111" }; int num = 0; int i = 0; for (i = 0; i < 5; i++) { if (strlen(my_array[i]) != 0) { num++; } } printf("num : %d\n", num); printf("排序之前\n"); print_array(my_array, num); sort_array(my_array, num); printf("排序之後\n"); print_array(my_array, num); return 0; }
我們再來思考一下以上兩種二級指針做輸入參數傳遞指針的不同。第一種二級指針,我們稱之爲指針數組。第二種二級指針,我們稱之爲數組指針。
指針數組存放的元素是指針,即地址,用4個字節就能表示所有的地址。所以對char Myarray[5][6],char **Myarray, char * Myarray[]
來說,他們在編譯器中都會被翻譯成char **Myarray
,他們的步長均爲4個Byte。指針數組的數據存放在全局區中的常量區。Myarray[i]的值可以改變(因爲其爲指針變量)
而數組指針中的元素卻是字符,不是地址。字符的長度不固定,所以我們不能確定步長到底是多少。既然不能確定,那麼傳參數的時候就應該告訴編譯器,這裏的步長是多少。那問題來了,怎麼“告訴”編譯器呢?那就是char Myarray[][6]
或者char (*Myarray)[6]
。這個6就代表了列數就是一行有多少個元素。數組指針的值存放在棧區。Myarray[i]的值不能改變(因爲是數組名,內存塊的別名)。我們最後再來看看char **Array的應用。
現在我們有一個需求,主函數中有一個二級指針p。現在需要跨函數在堆內存中開闢一個指針數組,指針數組中每個字符指針都指向一個長爲64字節的內存空間。我們應該怎麼做?
該需求有兩種方法實現。第一種方法是採用三級指針做輸出參數,在子函數中動態分配內存空間。該方法的問題是使用了三級指針;一般來說程序使用二級指針基本可以完成各種需求了,沒有必要使用更高級的指針了。第二種方法使用函數的返回值來傳遞二級指針。下面使用第一種方法來實現,第二種方法讀者感興趣可以自己編寫相關代碼。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int get_mem(char ***array_p, int num)
{
char **array = NULL;
int i = 0;
array = (char**)malloc(sizeof(char*)* num);//在堆上開闢num個 char*指針
if (array == NULL) {
fprintf(stderr, "malloc char **array error\n");
return -1;
}
memset(array, 0, sizeof(char*)*num);
for (i = 0; i < num; i++) {
array[i] = (char*)malloc(64);
if (array[i] == NULL) {
fprintf(stderr, "maloc array[%d] error\n", i);
return -1;
}
memset(array[i], 0, 64);
//賦值
sprintf(array[i], "%d%d%d%d", 9 - i, 9 - i, 9 - i, 9 - i);
}
*array_p = array;
return 0;
}
void free_mem(char ***array_p, int num)
{
int i = 0;
if (array_p == NULL) {
return;
}
char **array = *array_p;
for (i = 0; i < num; i++) {
if (array[i] != NULL) {
free(array[i]);
array[i] = NULL;
}
}
free(array);
*array_p = NULL;
}
int main(void)
{
char **my_array = NULL;
int num = 4;
get_mem(&my_array, num);
printf("-----\n");
free_mem(&my_array, num);
if (my_array == NULL) {
printf("kong\n");
}
return 0;
}