漫谈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语言指针的主要内容,如果未能解决你的问题,请参考以下文章

漫谈C语言指针

漫谈C语言指针

漫谈C语言内存管理

漫谈C语言指针

漫谈C语言指针

C语言编程漫谈——main函数