数组指针、指针数组、二维数组
数组指针、指针数组、二维数组
蛋总常说“凡事必有初”,写下这篇博客的初衷在于:
今天尝试用指针的方式访问一个大二维数组,而又不想用一维数组人工计算偏移量,于是有了指针数组与数组指针的探索。结论是数组指针。
二维数组
我们在开辟一个二位数组的时候似乎很简单。
比如要开辟一个第一二维度分别是5和8的int数组,可以定义为 int a[5][8]
。遍历这个数组的方式可以写为:
int a[5][8];
for (int i=0; i<5; i++) {
for (int j=0; j<8; j++) {
// visit a[i][j]
}
}
注意,这个二维数组的第二维是固定的,第一维通常也是固定的,例外情况是 C99 支持的 VLA,可以在函数内声明第一维不定长的数组,如:
int main() {
int x = 5;
int a[x][8];
return a[2][0];
}
但是通常我们为了兼容性考虑,不会使用VLA,因为C++不支持VLA。而且VLA只能在函数内声明,无法声明为全局变量,比如以下写法就无法通过编译:
int x = 5;
int a[x][8];
int main() {
return a[2][0];
}
数组指针
数组指针本质还是一个指针,和多维数组搭配使用很方便。
比如对于上面的二维数组a,可以声明一个对应类型的数组指针 b 为 int (*b)[8]
, 这里b就是一个数组指针,指向包含8个int元素的一维数组,p++
会一次跨越8个int元素的步长。若要借助b来访问a的数据,操作如下:
int a[5][8];
int (*b)[8];
b = a;
for (int i=0; i<5; i++) {
for (int j=0; j<8; j++) {
// visit b[i][j]
}
}
上面这种方式看起来似乎没有必要,那么当我们需要开辟一个第一维不定长的数组,尤其是全局数组的时候,数组指针就很有必要了。还记得之前说过的 VLA 的局限吗?
// ...
int (*b)[8];
int main() {
int w = 5;
b = (int(*)[8])malloc(sizeof(int) * 8 * w);
for (int i=0; i<5; i++) {
for (int j=0; j<8; j++) {
// visit b[i][j]
}
}
}
另外一种使用场景则是在函数传参的时候,使用 int (*p)[8]
作为形参和使用 int p[][8]
作为形参是等价的,做的都是数组指针的工作:
void foo(int p[][8]) {
// visit p[i][j]
}
void bar(int (*p)[8]) {
// visit p[i][j]
}
int main() {
int a[5][8];
foo(a);
bar(a);
}
指针数组
指针数组顾名思义是一个元素类型为指针的数组,本质是个数组。
指针数组也可以和多维数组搭配使用,通常用于底层维度不确定的情况。当然用于底层维度确定的情况也可以,但是没必要,还是上面的例子:
int a[5][8];
int* b[5];
for (int i=0; i<5; i++) {
b[i] = a[i];
}
for (int i=0; i<5; i++) {
for (int j=0; j<8; j++) {
// visit b[i][j]
}
}
或者更干脆,当高维长度不知道的时候,我们需要写成如下形式:
int** b;
int main() {
int w = 5;
b = (int**)malloc(sizeof(int*) * w);
for (int i=0; i<w; i++) {
b[i] = (int*)malloc(sizeof(int) * 8);
}
for (int i=0; i<w; i++) {
for (int j=0; j<8; j++) {
// visit b[i][j]
}
}
}
可以看到,同样是访问数组a,借助数组指针只需要一个指针的空间开销,而借助指针数组则需要5个指针开销。 int (*p)[8]
vs. int* p[5]
前者完胜后者。