【重学C++】【指针】详解让人迷茫的指针数组和数组指针

简介: 【重学C++】【指针】详解让人迷茫的指针数组和数组指针

大家好,我是 同学小张,持续学习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++"};
  • *p1*(p1+1)*(p1+2):所指向的字符串常量是不规则长度的,且sizeof(p1)=12
  • p2[0]p2[1]p2[2]所指向的字符串都是一定长度的,且sizeof(p2)=24

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

提醒一句:一定要动手去实践一下!没有任何一篇文章看了之后就能彻底搞懂指针,必须亲身体验才能加深印象!

如果觉得本文对你有帮助,麻烦点个赞和关注呗 ~~~


  • 大家好,我是 同学小张,持续学习C++进阶知识AI大模型应用实战案例
  • 欢迎 点赞 + 关注 ?,持续学习持续干货输出
  • +v: jasper_8017 一起交流?,一起进步?。
  • 微信公众号也可搜【同学小张】 ?

本站文章一览:

相关文章
|
4天前
|
C++ 数据格式
LabVIEW传递接收C/C++DLL指针
LabVIEW传递接收C/C++DLL指针
14 1
|
4天前
|
存储 人工智能
指针的应用练习(数组与指针的关系)
指针的应用练习(数组与指针的关系)
13 0
|
4天前
|
存储 人工智能
字符指针变量和字符数组注意事项(区别)
字符指针变量和字符数组注意事项(区别)
7 0
|
4天前
指针的基础应用(数组的颠倒和排序,二维数组的表示)
指针的基础应用(数组的颠倒和排序,二维数组的表示)
7 0
|
4天前
|
编译器 C++
C/C++杂谈——指针常量、常量指针
C/C++杂谈——指针常量、常量指针
9 0
|
4天前
|
C++ 编译器
数组指针、函数指针、指针数组、函数 指针数组、指针函数详细总结
数组指针、函数指针、指针数组、函数 指针数组、指针函数详细总结
|
4天前
|
存储 安全 程序员
C++:智能指针
C++:智能指针
21 5
|
4天前
|
存储 安全 C++
深入理解C++中的指针与引用
深入理解C++中的指针与引用
12 0
|
4天前
|
算法 C++
【C++入门到精通】智能指针 shared_ptr循环引用 | weak_ptr 简介及C++模拟实现 [ C++入门 ]
【C++入门到精通】智能指针 shared_ptr循环引用 | weak_ptr 简介及C++模拟实现 [ C++入门 ]
16 0
http://www.vxiaotou.com