【重学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 一起交流💬,一起进步💪。
  • 微信公众号也可搜【同学小张】 🙏

本站文章一览:

相关文章
|
13天前
使用指针访问数组元素
【10月更文挑战第30天】使用指针访问数组元素。
29 3
|
8天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
27 4
|
12天前
使用指针访问数组元素
【10月更文挑战第31天】使用指针访问数组元素。
26 2
|
24天前
|
存储 安全 编译器
在 C++中,引用和指针的区别
在C++中,引用和指针都是用于间接访问对象的工具,但它们有显著区别。引用是对象的别名,必须在定义时初始化且不可重新绑定;指针是一个变量,可以指向不同对象,也可为空。引用更安全,指针更灵活。
|
21天前
|
算法 索引
单链表题+数组题(快慢指针和左右指针)
单链表题+数组题(快慢指针和左右指针)
26 1
|
30天前
|
存储
如何使用指针数组来实现动态二维数组
指针数组可以用来实现动态二维数组。首先,定义一个指向指针的指针变量,并使用 `malloc` 为它分配内存,然后为每个子数组分配内存。通过这种方式,可以灵活地创建和管理不同大小的二维数组。
|
30天前
|
存储
如何通过指针数组来实现二维数组?
介绍了二维数组和指针数组的概念及其区别,详细讲解了如何使用指针数组模拟二维数组,包括定义与分配内存、访问和赋值元素、以及正确释放内存的步骤,适用于需要动态处理二维数据的场景。
|
30天前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
1月前
魔法指针 之 二级指针 指针数组
魔法指针 之 二级指针 指针数组
19 1
|
1月前
|
存储 C++
c++的指针完整教程
本文提供了一个全面的C++指针教程,包括指针的声明与初始化、访问指针指向的值、指针运算、指针与函数的关系、动态内存分配,以及不同类型指针(如一级指针、二级指针、整型指针、字符指针、数组指针、函数指针、成员指针、void指针)的介绍,还提到了不同位数机器上指针大小的差异。
38 1