C++ for-range遍历多维数组时auto变量及mat[0]的准确类型
首先明确核心前提:C/C++中没有原生的多维数组结构,二维数组本质是元素为一维数组的一维数组。以你定义的int mat[2][3] = { {10, 20, 30}, {40, 50, 60} };为例,mat的完整类型是「长度为2的数组,每个元素的类型是长度为3的int数组(即int[3])」。
一、mat[0]的准确类型
先对齐你总结的一维数组三类情形的类型逻辑:
- 对一维数组
int arr[3],arr是int[3]数组类型(对应情形1),arr[0]是int类型元素(对应情形2),&arr是指向整个int[3]数组的指针(对应情形3,步长为整个数组长度)。
套用到二维数组上:mat[0]的准确类型是int[3]——也就是mat数组的首个元素,本身是一个长度为3的一维int数组,完全对应你列的情形1,并非你猜测的情形3。
你之前测试代码输出不符合预期,问题出在指针运算和解引用的逻辑错误,不是类型判断错了:
你写的代码是cout << *((&mat[0]) + 1);,按照指针运算规则:
mat[0]是int[3]类型的数组,因此&mat[0]是指向整个int[3]数组的指针(类型为int (*)[3]),步长为3个int的长度(即12字节,按常规int占4字节计算)。(&mat[0]) + 1会跨过整个第一行(10、20、30三个元素),指向第二行mat[1]的起始位置。- 对这个指针做一次解引用
*((&mat[0]) + 1),得到的是mat[1]——也就是第二行的int[3]类型数组名,它在输出时会自动退化为指向mat[1][0]的指针,因此你看到的输出是一个地址值,而非int类型的元素40。
要拿到40的正确写法是做两次解引用:
cout << *(*((&mat[0]) + 1));,第一次解引用拿到第二行的数组名,第二次解引用才能拿到第二行的首元素40,和mat[1][0]的写法完全等价。你可以用一维数组做对照验证:对
int arr[3]={1,2,3}写cout << *(&arr +1);,输出的同样是跨过整个数组的内存地址,不会得到某个int元素值,和你现在遇到的现象完全一致,这反而能证明mat[0]就是和arr性质相同的一维数组,对应情形1。
二、范围for循环中it的准确类型
先明确C范围for遍历原生数组的底层逻辑:对于类型为T[N]的数组,范围for会自动生成从数组首地址到尾后地址的指针迭代,每次解引用迭代器拿到一个类型为T的元素。
对int mat[2][3]来说,数组的元素类型T是int[3](长度为3的int数组)。当你用auto it按值接收迭代元素时,由于C不允许数组直接作为值拷贝,数组类型会自动退化为指向其首元素的指针,因此:
it的准确类型是int*,本质是当前遍历行的一维数组退化得到的、指向该行首元素的指针。它不属于你列的三类情形中的任何一种:既不是完整的数组类型(情形1),也不是int类型的数组元素(情形2),更不是指向整个数组的指针(情形3)。你可以通过it[j]的形式访问当前行的第j个元素,比如循环内写cout << it[0];就会依次输出10、40。
如果你把循环写法改成引用形式for (auto& it : mat),那it的类型就是int (&)[3](对长度为3的int数组的引用),这时候it不会发生退化,性质和你列的情形1里的一维数组名完全一致,可以直接把it当作一维数组名使用。
内容的提问来源于stack exchange,提问作者Eric

