前言
前面一篇说到了复杂指针的第一种指针——二重指针, 本篇博文接着来聊聊更加复杂的指针。
指针数组和数组指针
首先需要明确的是指针数组是数组,而数组指针是指针。 之所以把它们放在一起, 是因为它们在形式上非常相似, 但实际上它们的用法和意义完全不同。 譬如定义一个指针数组: int *a[10], 表示一个有 10 个 int* 类型指针的数组; 定义一个数组指针: int (*pa)[10], 可以表示一个指向 int 类型、长度为 10 的一维数组的指针。
注意: 上面提到的都是我们比较常见的指针数组和数组指针, 实际上还有许多指针数组和数组指针, 譬如: int **a[10]、 int (*pa)[10][10] 等等, 但是这些出现的频率实在太低, 所以一般说到指针数组和数组指针时都特指上述两类比较常见的。
指针数组和数组指针在内存中的表示
1.指针数组
从图上看到内存中开辟了两块空间, 一块用于存储二重指针 pa, 一块空间用于存储指针数组。 并且二重指针 pa 指向了指针数组名 a。指针数组本质上和一维数组没有区别, 唯一区别的是它们存储的东西不同, 指针数组存储的元素是指针。 图中, 我特意在指针数组的下面标明了数组元素的假定地址, 这里使用的模型是 LP64, 所以该指针数组里面的元素都是 8 byte 大小的指针。 注意指针的大小与指针类型无关。
在指针篇(3)一文中, 我们提到了二重指针可以指向一重指针。 那么为什么二重指针可以指向指针数组名呢? 我们知道一维数组的数组名在作右值时可以隐式转换为指针类型, 等效于数组首元素首地址。 同理指针数组名在作右值时也可以隐式转换为指针类型, 等效于数组首元素首地址。所以「pa = a 」等效于「pa = &a[0] 」。 指针数组的元素都是指针, 所以「pa = a 」是成立的。
2. 数组指针
![](https://i-blog.csdnimg.cn/blog_migrate/3156edb735cdbc0055813b5e41712fdf.png)
int a[2][10] =
{
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,},
{10, 11, 12, 13, 14, 15, 16, 17, 18, 19,},
};
int *pa = NULL;
pa = &a[0][0];
int a[2][10] =
{
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,},
{10, 11, 12, 13, 14, 15, 16, 17, 18, 19,},
};
int (*pa)[10] = NULL;
pa = a;
区分指针数组和数组指针的技巧
很多人弄不清楚指针数组和数组指针这两者到底哪个是指针, 哪个是数组。 所以我们首先说说如何在字面上区分这两者。 其实在中文表达当中, 通常会把名词(主语)放在后面, 修饰词(定语)放在前面。 所以指针数组是数组在后, 指针在前, 重点表示的是数组。 反之, 数组指针它重点要表达的是一个指针。
那么又如何区分具体的 C 语言代码呢?这里涉及到一个运算符优先级的问题。 C 语言中有一个运算符优先级表, 大多数情况下不需要用到这个优先级表。 不过作为 C 语言的一个知识点, 也有必要会看懂优先级表和记住几个比较重要运算符的优先级。 关于这一点会在下一篇博文中讲解。 这里我们只涉及到「*」(解引用运算符)、「()」、「[]」这三个运算符。
在优先级表中, 上述的几个运算符的优先级顺序为:「[]」> 「()」 > 「*」。 所以当「*」和「[]」同时作用于 a 的时候, a 先与「[]」结合构成一个数组, 然后再与「*」结合, 结果构成一个指针数组, 其实指针数组也可以写成 int *(a[10]),不过这里的括号没有起到任何作用, 因为「[]」的优先级高于「*」; 同理数组指针中「()」就起到了一个隔离的作用, 这时候「[]」就不直接作用于 a, 所以 a 要先与「*」结合构成指针,然后再与「[]」结合。
2017 年 7 月 13 日
Kilento