基本概念
如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。也称为“二级指针”。
#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;
}