大家好,我是 同学小张,持续学习C++进阶知识和AI大模型应用实战案例,持续分享,欢迎大家点赞+关注,共同学习和进步。
重学C++系列文章,在会用的基础上深入探讨底层原理和实现,适合有一定C++基础,想在C++方向上持续学习和进阶的同学。争取让你每天用5-10分钟,了解一些以前没有注意到的细节。
是否你也和我一样,到现在也分不清指针数组和数组指针?这篇文档带你重新认识它们,彻底认清它们。 1. 什么是指针数组和数组指针?1.1 字面意思理解看到一个很简单的从字面意思理解 指针数组 和 数组指针 的方式 ---> 在中间加个“的”: 是不是瞬间觉得很好区分了? 但是光从概念上区分是没用的,要从表达式区分出它们才行。 1.2 表达式判断是指针数组还是数组指针int *ptr1[10]; int (*ptr2)[10];
如上面两种声明方式,你能区分出哪种是数组指针,哪种是指针数组吗? 区分方式如下: (1)首先得了解运算符的优先级,多了不说,只需要知道 () > [] 即可。 (2)知道了运算符优先级,那我们来看: int *ptr1[10] 中,优先级高的是[10] ,说明这整个表达式ptr1 代表的是个数组,其它的都是修饰这个数组的,所以这是个指针的数组,指针数组。
int (*ptr2)[10] 中,优先级高的是(*ptr2) ,说明整个表达式代表的是个指针,其它的都是修饰这个指针的,所以这是个数组的指针,数组指针。
2. 指针数组2.1 原理图指针数组,指针的数组,数组里面都是指针: 使用时将里面的每一个元素都当作普通指针去使用就可以了。 2.2 测试代码char *str[3] = {"lirendada","C语言","C Language"}; std::printf("str+1的值:%s\n", *(str+1)); std::printf("str+1的值:%s\n", str[1]); std::printf("str+1的地址:%p\n", str+1); std::printf("str+1的地址:%p\n", &str[1]); std::printf("str+1指向的地址:%p\n", str[1]); std::printf("str+1指向的地址:%p\n", *(str+1));
运行结果: 2.3 运行结果详解(1)*(str+1) 与 str[1] 等价,都是取第1个字符串的值 (2)整体的结构可以用下图表示:数组的每个元素都是个char*指针,指向一个字符串,字符串存储在全局区,数组的元素存放在栈区。 (3)每个char*指针指向的是字符串的首地址。 2.4 补充细节参考:https://blog.csdn.net/lirendada/article/details/122931987
(1)字符串指针数组赋值时,每个元素必须是char*类型,也就是必须也是指针类型或能退化成指针的数组类型才可以,否则报错: (2)二维数组与指针数组的区别 char *p1[]={"lirendada","C","C++"}; char p2[][8]={"liren","C","C++"};
3. 数组指针3.1 原理图数组指针,数组的指针,指向数组的一个指针: 数组指针ptr2指向一个数组的首地址。下面以一段测试代码来看下数组指针怎么使用。 3.2 测试代码int a[5] = {0,1,2,3,4}; int (*ptr2)[5] = &a;
std::printf("a的地址:%p\n", &a); std::printf("ptr2指向的地址:%p\n", ptr2); std::printf("ptr2自身的地址:%p\n", &ptr2); std::printf("a[1]的值: %d\n", a[1]); std::printf("使用ptr2访问a[1]的值:%d\n", (*ptr2)[1]); std::printf("使用ptr2访问a[1]的值:%d\n", *((*ptr2)+1));
运行结果: 3.3 运行结果详解(1)首先看数组指针的赋值语句:第二句 &a ,必须带"& "号 int a[5] = {0,1,2,3,4}; int (*ptr2)[5] = &a;
(2)a的地址和ptr2指向的地址相同,这就是数组指针的本质(也和我们上面的原理图一致),指向一个数组的首地址。 (3)ptr2自身有个地址,如果再仔细一点看,它的地址与数组首地址差了8个字节,一个地址的距离,也就是说,数组a和ptr2在栈内存空间中是挨着的。 (4)最后三行代码提供了三种访问a[1]元素的方式,这三种方式等价。读者可以先思考下为什么这三种方式是等价的。后面一起解答。 3.4 升级难度:二维数组的数组指针以二维数组:int b[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; 为例 3.4.1 二维数组(1)内存形式 二维数组虽然我们认为有行有列,但实际在内存中,是一块连续的内存区域,并没有行和列的概念。 (2)一些测试 std::printf("数组名b的地址:%p\n", b); std::printf("b[0]的地址:%p\n", &(b[0])); std::printf("b+1的地址:%p\n", b+1); std::printf("b[1]的地址:%p\n", &(b[1])); std::printf("b+1的大小:%llu\n", sizeof(*(b+1))); std::printf("第一行第二列 b[1][2]值的访问:%d", *(*(b+1)+2));
// 输出: // 数组名b的地址:00000000005ffe00 // b[0]的地址:00000000005ffe00 // b+1的地址:00000000005ffe10 // b[1]的地址:00000000005ffe10 // b+1的大小:16 // 第一行第二列 b[1][2]值的访问:7
· 数组名b代表的是整个数组的首地址,也是第0行的首地址。 · b+1 代表的是第1行的首地址 · *(b+1) 的大小为 16,4个int值,也就是代表第一行的所有数据 · *(b+1)+2 ,当*(b+1) 作为表达式中的一项时,会作为这一行的首地址使用,所以 *(b+1) 表示第1行的首地址,再+2 表示这一行的第2列。
3.4.2 二维数组指针定义方法: int (*ptr3)[4] = b;
注意在二维数组时,b前面没有了& 符号,因为b本身就代表一个int[4] 的数组了。
从直观上理解,*ptr3是不是就相当于代替了原来的 b[0], b[1] 和 b[2] ?所以,ptr3应该与b[0]指向相同的地址,ptr3+1与b[1]指向相同的地址。写如下代码测试上面的结论: std::printf("b[0]的地址:%p\n", &(b[0])); std::printf("ptr3指向的地址:%p\n", ptr3); std::printf("b[1]的地址:%p\n", &(b[1])); std::printf("ptr3+1指向的地址:%p\n", ptr3+1); std::printf("第一行第二列 b[1][2]值的访问:%d\n", *(*(ptr3+1)+2));
// 输出结果 // b[0]的地址:00000000005ffe00 // ptr3指向的地址:00000000005ffe00 // b[1]的地址:00000000005ffe10 // ptr3+1指向的地址:00000000005ffe10 // 第一行第二列 b[1][2]值的访问:7
3.5 数组名与数组指针的等价关系通过上面的测试代码不难发现,数组名与数组指针之间具有以下等价关系: b+i == ptr3+i b[i] == ptr3[i] == *(b+i) == *(ptr3+i) b[i][j] == ptr3[i][j] == *(b[i]+j) == *(ptr3[i]+j) == *(*(b+i)+j) == *(*(ptr3+i)+j)
4. 总结总结一下数组指针与指针数组的区别: (1)数组指针是指向数组的指针,本质是一个指针;指针数组是元素全都是指针的数组,本质是一个数组。 (2)基于两者的本质区别,数组指针大小就是4字节(32位平台)或8字节(64位平台),而指针数组的大小不止取决于平台的位数,还取决于数组的大小。 (3)最后再上一个区别的图,都以一个二维数组为例:数组指针指向这个二维数组首行首元素的地址。指针数组首先是包含3个指针,每个指针指向一行的首元素地址。 5. 补充问题a 为一维数组,为什么下面的打印a 与&a 地址是相同的?欢迎讨论。
std::printf("a的地址:%p\n", a); // 输出:00000000005ffe40 std::printf("a的地址:%p\n", &a); // 输出:00000000005ffe40
提醒一句:一定要动手去实践一下!没有任何一篇文章看了之后就能彻底搞懂指针,必须亲身体验才能加深印象!
如果觉得本文对你有帮助,麻烦点个赞和关注呗 ~~~点击上方公众号,关注↑↑↑
· 大家好,我是 同学小张,日常分享AI知识和实战案例 · 欢迎 点赞 + 关注 👏,持续学习,持续干货输出。 · +v: jasper_8017 一起交流💬,一起进步💪。
公众号内文章一览
|