漫谈C语言指针
Posted 易水南风
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了漫谈C语言指针相关的知识,希望对你有一定的参考价值。
上一篇漫谈C语言指针(二) 主要讲了指针和数组的关系、字符串指针、指针变量作为函数参数,就像电影最后进入高潮一样,接下来,指针的内容将进入深水区,大家坐稳扶好,马上出发~
今天要讲的内容有:二级指针、二维数组、指针数组、函数指针、函数指针数组、结构体指针。为什么说的深水区呢?因为前面的2篇只要看懂概念基本就能学会,但是这一章,概念开始变绕,不再平坦,甚至有点花俏,所以需要消费一些脑细胞才可以理解,不过不用担心,我对我的表达有自信(看不懂也勿喷。。)
二级指针
二级指针,顾名思义,就是指针的指针,一个指针指向另一个指针,即指针存放的数值是另一个指针的地址。
假设有一个 int 类型的变量 a,p1是指向 a 的指针变量,p2 又是指向 p1 的指针变量,它们的关系如下图所示:
用代码定义就是:
int a =5;
int *p1 = &a;
int **p2 = &p1;
以此类推还有多级指针,但一般用到二级指针,更多级的指针很少用到。
那二级指针的作用是什么呢?
在函数内部修改函数外部一个指针的指向:
在漫谈C语言指针(一) 中的“通过指针在函数内部修改函数外部变量值”章节讲过,要在函数内改变函数外定义的变量的数值,要通过指针来传参,那以此类推,二级指针的一个作用就是通过二级指针传参,在函数内部修改函数外部一个指针的指向。
比如:
void secondPointer(int **a) {
int *p = static_cast<int *>(malloc(sizeof(int)));//用malloc保证离开函数后内存不会释放
*p = 5;
*a = p;//改变a函数外传入的二级指针指向的指针的指向
printf("secondPointer *a %#X\\n", *a);
}
int main() {
int i = 10;
int *a = &i;
printf("before secondPointer a %#X\\n", a);//改变a之前的指向之前a的指向地址
secondPointer(&a);
printf("after secondPointer a %#X\\n", a);//改变a之后的指向之前a的指向地址
printf("after secondPointer *a %d\\n", *a);//改变a之前的指向之前a的指向地址
}
运行结果:
before secondPointer a 0X62FE1C
secondPointer *a 0XDB1C40
after secondPointer a 0XDB1C40
after secondPointer *a 5
可以看到通过secondPointer函数的处理,a成功指向了5(即函数中p指向的变量),地址也发生了改变。
二维数组
二级指针的另一个常用的作用,就是对二维数组的操作。
先说下二维数组,简单来说就是数组的数组,每个数组元素都是一个数组,在概念上是二维的,有行和列,但在内存中所有的数组元素都是连续排列的,它们之间没有“缝隙”。以下面的二维数组 a 为例:
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
从概念上理解,a 的分布像一个矩阵:
0 1 2 3
4 5 6 7
8 9 10 11
但在内存中,a 的分布是一维线性的,整个数组占用一块连续的内存:
C语言中的二维数组是按行排列的,也就是先存放 a[0] 行,再存放 a[1] 行,最后存放 a[2] 行;每行中的 4 个元素也是依次存放。数组 a 为 int 类型,每个元素占用 4 个字节(假设当前环境int占用4字节),整个数组共占用 4×(3×4) = 48 个字节。
首先要引入数组指针的概念,即指向数组的指针,定义如下:
int (*p)[4] = &a;//数组指针p指向数组a
与数组名不同的是,数组名在表达式中表示的是数组第一个元素的首地址,所以对数组名算术运算的步长是数组元素的长度,而数组指针的步长是真整个数组的大小(如果对步长不理解,请看:漫谈C语言指针(一) 指针算术运算一节):
int a[] = {1,2,3,4,5,6};
int (*p)[6] = &a;
printf("secondPointer sizeof(int) %d\\n", sizeof(int));
printf("secondPointer p %#X\\n", p);
printf("secondPointer p+1 %#X\\n", p+1);
运行结果:
secondPointer sizeof(int) 4
secondPointer p 0X62FE00
secondPointer p+1 0X62FE18
注意到 p+1比p大了24字节(10进制),刚好是6个int类型元素的大小。
C语言允许把一个二维数组分解成多个一维数组来处理。对于数组 a,它可以分解成三个一维数组,即 a[0]、a[1]、a[2]。每一个一维数组又包含了 4 个元素,例如 a[0] 包含 a[0][0]、a[0][1]、a[0][2]、a[0][3]。
假设数组 a 中第 0 个元素的地址为 1000,那么每个一维数组的首地址如下图所示:
在漫谈C语言指针(二) 中说过,一维数组a的数组名在表达式中指向数组首元素首地址,指向的类型是数组元素类型。以此类推,上图的二维数组a,a指向第一个元素(即数组a[0],这里a[n]表示第n个数组(第n行)),因为a作为指针指向的类型为数组,所以步长为数组的长度,在这里即为4*sizeof(int)。而因为a[n]表示第n个数组,所以a[n]本身可以看作第n个数组的数组名,即a[n]本身是一个指向第n个数组的首元素首地址的指针,指向的类型为第n个数组的元素类型,所以步长为数组元素类型大小,在这里即为sizeof(int)。
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
printf("sizeof(*(a)) %d\\n", sizeof(*a));//求出a指向的数据的大小
printf("a %d\\n", a);
printf("a+1 %d\\n", a+1);//求a的步长(其实和sizeof(*a)一样大)
printf("sizeof(*a[0]) %d\\n", sizeof(*a[0]));//求出a[0]指向的数据的大小
printf("a[0] %d\\n", a[0]);
printf("a[0]+1 %d\\n", a[0]+1);//求a[0]的步长(其实和sizeof(*a[0])一样大)
运行结果:
sizeof(*(a)) 16
a 6487536
a+1 6487552
sizeof(*a[0]) 4
a[0] 6487536
a[0]+1 6487540
可以看出,a和a[0]指向的地址都是整个二维数组第一个数组的第一个元素的首地址,但是a步长是16,即指向的类型是int[4],而a[0]步长为4,即指向类型为int。根据结果可以进一步可以推导出,a+n指向的是第n+1个数组的首元素首地址,a[x]+y指向的是第x+1数组第y+1个元素首地址。
(未完待续…)
以上是关于漫谈C语言指针的主要内容,如果未能解决你的问题,请参考以下文章