1 指向指针的指针
- 指针的本质是变量,会占用一定的内存空间
- 可以定义指针的指针来保存指针变量的地址
指针的指针没有什么了不起的,它还是一个指针,只不过这个指针所指向的内存空间的元素又是一个地址。可以这么理解,你的狗狗丢了,要去找狗狗,现在你有一个门牌号,你拿着这个门牌号去找对应的房子,到了对应的屋子以后,没找到狗狗,找到的还是一个门牌号,然后你拿着这个新找到的门牌号再找对应的房子,在房子里面找到了狗狗。
看下面的代码:
上面的代码,pp 就是指向指针的指针,pp 指向指针 p,也就是说 pp 对应的内存空间的元素是指针 p 的地址,p 对应的内存空间的元素才是变量 i 的地址。
*pp = &i 相当于 p = &i,也就是让 p 指向 i。
问题:那为什么需要指向指针的指针呢?
- 指针本质上也是变量
- 对于指针也同样存在传值调用与传址调用
将一个变量通过传址调用,我们操作的是变量的地址,也就是指针;那么将一个指针通过传址调用,我们操作的不就是指向指针的指针了吗。
实例分析:重置动态空间大小
// 34-1.c
#include<stdio.h>
#include<malloc.h>
int reset(char**p, int size, int new_size)
{
int ret = 1;
int len = 0;
int i = 0;
char* pt = NULL;
char* tmp = NULL;
char* pp = *p;
if (p != NULL && new_size > 0)
{
pt = (char*)malloc(new_size);
tmp = pt;
len = (size < new_size) ? size : new_size;
for (i = 0; i < len; i++)
{
*tmp++ = *pp++;
}
free(*p);
*p = pt;
}
else
{
ret = 0;
}
return ret;
}
int main()
{
char *p = (char*)malloc(5);
printf("%p\n", p);
if (reset(&p, 5, 3))
{
printf("%p\n", p);
}
free(p);
return 0;
}
函数 reset(char**p, int size, int new_size) 的作用是将原来申请的长度为 size 的数组变成长度为 new_size 的数组,并将原来的数据复制过来。由于传递的是指针,还是传址调用调用,所以函数第一个参数为指向指针的指针。
编译运行结果如下:
$ gcc 34-1.c -o 34-1
$ ./34-1
0x55f11c4c6260
0x55f11c4c6690
2 二级数组与二级指针
- 二维数组在内存中以一维的方式排列
- 二维数组中的第一维是一维数组,第二维才是具体的值
- 二维数组的数组名可看作常量指针
二维数组在内存中保存的是一维数组,如下图所示:
实例分析:遍历二维数组
// 32-4.c
#include<stdio.h>
void printArray(int a[], int size)
{
int i = 0;
printf("printArray: %ld\n", sizeof(a));
for (i = 0; i < size; i++)
{
printf("%d\n", a[i]);
}
}
int main()
{
int a[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
int* p =&a[0][0];
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
printf("%d, ", *(*(a + i) + j));
}
printf("\n");
}
printf("\n");
printArray(p, 9);
return 0;
}
程序第 22 行,*(*(a + i) + j) ==> *(a[i] + j) ==> a[i][j],为了程序的可读性,写成 a[i][j] 更好
printArray 函数将二维数组作为一维数组,打印从数组 a 开始的 size 个元素,结果是什么呢,我们编译运行一下:
$ gcc 34-2.c -o 34-2
$ ./34-2
0, 1, 2,
3, 4, 5,
6, 7, 8,
printArray: 8
0
1
2
3
4
5
6
7
8
结果显示主函数和 printArray 函数都将二维数组中的元素打印出来了,这更加验证了二维数组在内存中其实是以一维数组的方式保存的。
3 数组名
- 一维数组名代表数组首元素的地址
对于 int a[5],a 的数组名为 int* - 二维数组名同样代表数组首元素的地址
对于 int m[2][5],m 的类型为 int(*)[5]
结论:
- 二维数组名可以看作是指向数组的常量指针
- 二维数组可以看作一维数组
- 二维数组中的每个元素都是同类型的一维数组
实例分析:动态申请二维数组
#include<stdio.h>
#include<malloc.h>
int** malloc2d(int row, int col)
{
int** ret = NULL;
if (row >0 && col > 0)
{
int* p = NULL;
ret = (int**)malloc(row * sizeof(int*));
p = (int*)malloc(row * col * sizeof(int));
if (ret != NULL && p != NULL)
{
int i = 0;
for (i = 0; i < row; i++)
{
ret[i] = p + i*col;
}
}
else
{
free(ret);
free(p);
ret = NULL;
}
}
return ret;
}
void free2d(int** p)
{
if(*p != NULL)
{
free(*p);
}
free(p);
}
int main()
{
int** a = malloc2d(3, 3);
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
printf("%d, ", a[i][j]);
}
printf("\n");
}
free2d(a);
return 0;
}
ret 是指针数组,p 是真正存放元素的数组,ret[0] 指向 p[0],ret[1] 指向 p[col],ret[2] 指向 p[2*col]……以此类推,ret[row-1] 指向 p[(row-1)*col],二维数组在内存中实际是以一维方式存储的。
$ gcc 34-3.c -o 34-3
$ ./34-3
0, 0, 0,
0, 0, 0,
0, 0, 0,
4 小结
1、C 语言中只支持一维数组
2、C 语言中的数组大小必须在编译期就作为常数确定
3、C 语言中的数组元素可以是任何类型的数据
4、C 语言中的数组的元素可以是另外一个数组