最近在学习C++Primer Pointer and Multdimensional Arrays 这一章节的时候遇到了这么一个例子,作用是用Pointer 遍历整个 Array, 看完感觉特别绕,于是就详细的研究了一下这段代码.

Code如下:

int ia[3][4]; 
int (*ip)[4] = ia; 
//print the value of each element in ia, with each inner array on its own line. 
// p point to an array of 4 ints 
for (auto p = ia; p != ia+3; ++p) { 
//q points to the first element of an array of four ints 
for (auto q = *p; q != *p + 4; ++q) 
cout << *q << ' '; 
cout << endl; 
} 

C++ 中的 Pointer to Multidimensional array
反复看了好几遍, 发现不太理解 p 做加法到底是在行中改变指向还是在列中改变指向. 同时, 对q = *p 这个表达式我也有很大的疑问:

往顶层 loop 看, p 是个 pointer, 往底层 loop 看, q 在作地址位移, 也特么的是个pointer 啊! 那么刚说到的表达式从字面上来理解就是: 把一个pointer derefernce 以后在赋值给另外一个 pointer . WTF??? 当然程序是没错的, 那只能是自己太蠢了理解不能了啊!

既然意识到了这个问题, 那就再研究一下吧. 为了搞明白这个 *p 到底是什么鬼, 我们就从开头开始看这个例子.

pointer p 作加法, 位移是行方向还是列方向?

首先 p 是指向了一个2D array. 对于这个2D array, 包含 p 的外层 loop:

for (auto p = ia; p != ia+3; ++p)

到底是做什么的呢?

按照 pointer 的定义, pointer 指向的是一个地址. 对于 array ia 来说, ia 这个表达式表达的就是 整个array 的第一个元素的地址. 所以第一步, 我们的:

auto p = ia;

实际上说的就是定义了一个指向 array ia 的 pointer.

其实到这里都没有问题, 我是从第二个表达式:

p != ia+3;

开始迷糊的. 这里的 ia+3 到底是指的什么? 大家都应该了解 pointer 加上一个 Int N 等于改变pointer的指向到接下来的第 N 个单元. 那么问题来了, 这里的 ia+3 到底是先往列位移呢?还是先往行位移呢?

我在这里起始就受到了 Dimension 这个词的困扰, 理所当然的按照 2D array 的数学概念去理解这个问题, 结果是不怎么好理解的. 为此我再去回顾了一下C++ Primer 的解释.

multidimensinal array is really an array of arrays.

定义在C++/ C里的 2D array, 从本质上来说是 array of arrays. 那么也就是说, 对于这个所谓的 2D array, 其实存储的顺序可以理解成线性的. 用一张图来解释就是:

1763382596.png

由图可知, 我们看到的最外层的 loop, 遍历的元素 都是 array . 那么 loop 中 对于 p != ia+3 的操作很显然就是遍历里层 array 的判断条件了. 所以最外层的 loop 是对 行 / 里层的 单位 array 的遍历.

*p 到底是什么?

然后我们接着来看里层的 loop.

首先按 p 的定义来说, p 指向的元素是array. 这里提到的 array 是包含在顶层 array 里的 array , 我们且称之为里层 array .

所以对于 p 来说, pia[0] 其实是等价的, 都是指向了 array 的首个array元素的地址.

那么问题又来了, p 是 pointer, 那么 *p 操作就是读取 p 指向 ia[0] 的内容了, 这里的内容是一个包含4个 Int 的 array. 那为什么在里层 loop 里可以把 *p 赋值给另外一个 pointer q ?

书上给出的解释是:

When we use an array, it is converted automatically to a pointer to its first element.

这个同时也是C标准里的东西.

6.3.2.1 Lvalues, arrays, and function designators
...
3.Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal >used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression >with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue.

按照标准的定义, 表达式 *p 在这个地方表示的是 array 的内容, 是 array type, 同时也显然不是左值( array_tpye 是不能被复制的). 所以, *p 在这里已经被编译器从array 转化成了 pointer . 这个 pointer 指向当前 里层 array 的第一个元素 (int type) . 所以可以顺利赋值给 q , 然后让 q 做位移运算, 从而达到遍历里层 array 的功能了.

总结: pointer 和 array 的概念还需要进一步的强化, 而文中提到的array 和 pointer 之间的自动类型转换, 在C++ 中很多地方是需要避免的, 比如 在用 range for 对 2D array 的遍历中, 就需要用reference 来避免 array 和 pointer 中的自动类型转换.