本节书摘来自异步社区出版社《C和C++代码精粹》一书中的第2章,第2.10节,作者: 【美】Chuck Allison,更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.10 指针和多维数组
C和C++代码精粹
实际上,在C++中没有多维数组!至少对多维数组没有直接的支持。人们通常把一个一维数组看作一个向量,把一个二维数组看作一个表或者矩阵,把一个三维数组看作一个长方体。然而,数组的几何模型使明智地使用高维数组变得很困难,取而代之的是C++支持“数组的数组”的概念。例如,如果一个一维的整型数组为
int a[4]={0,1,2,3};
它是一个有索引的整数集合:
我们通常把它描述成一个向量:
对一个二维整型数组,如:
int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,0,1}}
是像下面这样的向量集合:
或者,如果喜欢的话可写成:
这就是数组的集合。因此,a就是一个“3个具有4个整型元素的数组的数组”,a[0]就是这些具有4个整型元素的数组之一。由于在表达式中使用数组名时,它总是被解释为指向它的第一个元素的指针,所以一个诸如 a+1 之类的表达式就是“指向一个具有4个整型元素的数组的指针”,在这种情况下,该表达式将指向第二行(即a[1])。程序清单2.15中的程序说明了如何声明指向一个数组的指针,这样的指针可以在不改变下标语法的情况下,替换数组名。初学者容易错误地假设:指向整型数据的指针能代替一个整型数组名,就像以下程序:
int a[]={0,1,2,3},*p=a;
/*...*/
p[i]=...
那么,这样指向整型数据的指针的指针将会对二维数组进行同样的操作,就像下面:
int a[][4]={ {0,2,3,4},{4,5,6,7},{8,9,0,1}};
int **p=a; /*受怀疑的指针转换*/
/*...*/
p[i][j]=...
要弄清以上做法为何不对,考虑一下表达式pi,根据“重要的指针原则2”,这就相当于:
*(p[i]+j)
也相当于:
*(*(p+i)+j)
程序清单2.15 说明在二维数组中指向一维数组的指针
// array8.cpp: 使用一个一维数组指针
#include <iostream>
main()
{
int a[][4] = {{0,1,2,3},{4,5,6,7},{8,9,0,1}};
int (*p)[4] = a; //指向包含4个整型成员的数组
size_t nrows = sizeof a / sizeof a[0];
size_t ncols = sizeof a[0] / sizeof a[0][0];
cout << "sizeof(*p) == " << sizeof *p << endl;
for (int i = 0; i < nrows; ++i)
{
for (int j = 0; j < ncols; ++j)
cout << p[i][j] << ' ';
cout << endl;
}
}
//输出:
sizeof(*p) == 16
0 1 2 3
4 5 6 7
8 9 0 1
由于p是一个指向整型数据的指针,所以表达式 p+i 往 p后面移动的距离等于i个指针的大小,而不是 i 行(我们需要移动 i 行)。显然,我们需要的指针类型是 p 所指向的指针具备一个行的大小,因此 p 必须是一个行指针,也就是说它指向一个大小合适的数组。因此,有趣但又有逻辑性的语法为:
int (*p)[4]=a;
根据p的这个定义,编译器依据下面的步骤来求表达式((p+i)+j)的值:
1.p+i
这一步计算行指针,该行超出行 p 当前所指向的 i 行。
2.*(p+i)
这是具体的行(是一个数组)。
3.由于数组 *(p+i)不是 sizeof 或者 & 运算的操作数,所以它被指向它第一个元素的指针所替代,即 &ai ,这是一个指向整型(int)的指针。
4.&ai+j
因为&ai是一个指向整型(int)的指针,加上j就把这个指针向前移动了j个整型数据单位,结果是&ai。
5.*&ai
这就是整型a[i] [j]。
表2.1总结了指针和二维数组的这种关系,注意,不要因为a+1和a[1]有同样的值(0×8)而得出以下的结论:
a[i]==a+i // 错误!
它们仅仅在数值上相等,而类型却不一样,因为sizeof(a+1)==2(一个指针),而 sizeof(a[1])= =8(一个有4个整型数的数组)。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。