在C++中,数组与指针有密切的关系。任何通过数组和下标实现的表达式可等价地通过指针和偏移量实现。下面首先介绍一维数组与指针的关系,之后介绍二维数组的行指针和列指针。
1.一维数组和指针定义一个长度为3的一维数组a:
int a[3] = {1, 2, 3};
假设其首元素地址为0x9F36FAE0,则数组元素在内存中的示意图如下图所示:
指向数组元素的指针声明和初始化方式如下:
int* p = a; // 等价于p = &a[0]
这里声明了一个指向数组a的首元素的指针p。
由于在C++中数组名就是数组首元素的地址,因此可以直接使用数组名来初始化指针,也可以显式地取数组元素的地址(如&a[0])。
对于数组a和指向其首元素的指针p,有以下等价形式:
| 含义 | 等价形式 | 类型 | 值 |
|---|---|---|---|
| 首元素的值 | a[0]、*a、p[0]、*p | int | 1 |
| 首元素的地址 | &a[0]、a、&p[0]、p | int* | 0x9F36FAE0 |
| 第i个元素的值 | a[i]、*(a+i)、p[i]、*(p+i) | int | 3 (i = 2) |
| 第i个元素的地址 | &a[i]、a+i、&p[i]、p+i | int* | 0x9F36FAE8 (i = 2) |
★根据C++地址算术运算的定义,无论a是数组名还是指针,都有
- a[i]等价于*(a+i)
- &a[i]等价于a+i
指向数组的指针声明和初始化方式如下:
int (*q)[3] = &a;
这里声明了一个指向数组a 本身的指针q,因此*q等价于a。
注意:数组a的地址&a在数值上等于其首元素的地址a,但二者的类型不同。
对于数组a和指向其本身的指针q,有以下等价形式:
| 含义 | 等价形式 | 类型 | 值 |
|---|---|---|---|
| 首元素的值 | a[0]、(*q)[0]、**q | int | 1 |
| 首元素的地址 | &a[0]、&(*q)[0]、*q | int* | 0x9F36FAE0 |
| 第i个元素的值 | a[i]、(*q)[i]、*(*q+i) | int | 3 (i = 2) |
| 第i个元素的地址 | &a[i]、&(*q)[i]、*q+i | int* | 0x9F36FAE8 (i = 2) |
| 数组的地址 | &a、q | int(*)[3] | 0x9F36FAE8 |
#include2.二维数组和指针using namespace std; int main() { int a[3] = {1, 2, 3}; int* p = a; int (*q)[3] = &a; printf("首元素的值n"); printf("a[0]t*atp[0]t*pt(*q)[0]t**qn"); printf("%dt%dt%dt%dt%dt%dn", a[0], *a, p[0], *p, (*q)[0], **q); putchar('n'); printf("首元素的地址n"); printf("&a[0]ttatt&p[0]ttptt&(*q)[0]tt*qn"); printf("%pt%pt%pt%pt%pt%pn", &a[0], a, &p[0], p, &(*q)[0], *q); putchar('n'); printf("第i个元素的值n"); printf("ita[i]t*(a+i)tp[i]t*(p+i)t(*q)[i]t*(*q+i)n"); for (int i = 0; i < 3; ++i) printf("%dt%dt%dt%dt%dt%dt%dn", i, a[i], *(a + i), p[i], *(p + i), (*q)[i], *(*q + i)); putchar('n'); printf("第i个元素的地址n"); printf("it&a[i]tta+itt&p[i]ttp+itt&(*q)[i]tt*q+in"); for (int i = 0; i < 3; ++i) printf("%dt%pt%pt%pt%pt%pt%pn", i, &a[i], a + i, &p[i], p + i, &(*q)[i], *q + i); putchar('n'); printf("数组的地址n"); printf("&attqn"); printf("%pt%pn", &a, q); return 0; }
定义一个大小为2×3的二维数组a:
int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
假设其首元素地址为0x19AFFC40。
二维数组在概念上可理解为矩阵:
但二维数组在内存中的实际存储形式是按行的顺序依次存储,如下图所示:
可以看出,二维数组在内存中是以类似于一维数组的形式存储的,元素a[i][j]的偏移量可使用公式i*列数+j计算。
虽然二维数组并不是以矩阵的形式存储的,但二维数组的每一行就是一个一维数组,在这一点上与矩阵的概念是一致的。
2.1 地址运算由于二维数组就是一维数组的数组,元素类型是一维数组,数组名仍然是首元素的地址。在这里“首元素”就是二维数组的首行,因此a是首行的地址。使用数组名a可进行如下的地址运算。
2.1.1 行| 含义 | 等价形式 | 类型 | 值 |
|---|---|---|---|
| 首行 | a[0]、*a | int[3] | {1, 2, 3} |
| 首行的地址 | &a[0]、a | int(*)[3] | 0x19AFFC40 |
| 第i行 | a[i]、*(a+i) | int[3] | {4, 5, 6} (i = 1) |
| 第i行的地址 | &a[i]、a+i | int(*)[3] | 0x19AFFC4C (i = 1) |
由此可以看出,即使数组元素的类型变成了一维数组,1.1节最后的结论仍然成立。
2.1.2 数组元素| 含义 | 等价形式 | 类型 | 值 |
|---|---|---|---|
| 首行首列元素的值 | a[0][0]、*a[0]、(*a)[0]、**a | int | 1 |
| 首行首列元素的地址 | &a[0][0]、a[0]、*a | int* | 0x19AFFC40 |
| 第i行首列元素的值 | a[i][0]、*a[i]、(*(a+i))[0]、**(a+i) | int | 4 (i = 1) |
| 第i行首列元素的地址 | &a[i][0]、a[i]、*(a+i) | int* | 0x19AFFC4C (i = 1) |
| 第i行第j列元素的值 | a[i][j]、*(a[i]+j)、(*(a+i))[j]、*(*(a+i)+j) | int | 6 (i = 1, j = 2) |
| 第i行第j列元素的地址 | &a[i][j]、a[i]+j、*(a+i)+j | int* | 0x19AFFC54 (i = 1, j = 2) |
二维数组行指针的声明和初始化方式如下:
int (*q)[3] = a; // 等价于q = &a[0]
这里声明了一个指向二维数组a首行的指针q,因此*q等价于a[0]。
从语法上可以看出,行指针本质上就是指向数组的指针。二维数组a的首行a[0]就是一个长度为3的一维数组,因此可以使用其地址&a[0]来初始化行指针q。另一方面,因为数组名就是首元素的地址,因此a等价于&a[0]。
对于二维数组a和指向其首行的指针q,有以下等价形式:
| 含义 | 等价形式 | 类型 | 值 |
|---|---|---|---|
| 首行 | a[0]、q[0]、*q | int[3] | {1, 2, 3} |
| 首行的地址 | &a[0]、a、&q[0]、q | int(*)[3] | 0x19AFFC40 |
| 第i行 | a[i]、q[i]、*(q+i) | int[3] | {4, 5, 6} (i = 1) |
| 第i行的地址 | &a[i]、&q[i]、q+i | int(*)[3] | 0x19AFFC4C (i = 1) |
与数组元素相关的表达形式这里不再列举,直接将a替换为q即可。
2.3 列指针二维数组列指针的声明和初始化方式如下:
int *p = &a[0][0];
这里声明了一个指向二维数组a首行首列元素的指针p。
可以看出,列指针就是指向数组元素的普通指针。通过列指针可以将二维数组当作一维数组、利用偏移量计算公式来访问数组元素。
对于二维数组a和指向其首行首列元素的指针p,有以下等价形式:
| 含义 | 等价形式 | 类型 | 值 |
|---|---|---|---|
| 首行首列元素的值 | a[0][0]、p[0]、*p | int | 1 |
| 首行首列元素的地址 | &a[0][0]、&p[0]、p | int* | 0x19AFFC40 |
| 第i行首列元素的值 | a[i][0]、p[i*3]、*(p+i*3) | int | 4 (i = 1) |
| 第i行首列元素的地址 | &a[i][0]、&p[i*3]、p+i*3 | int* | 0x19AFFC4C (i = 1) |
| 第i行第j列元素的值 | a[i][j]、p[i*3+j]、*(p+i*3+j) | int | 6 (i = 1, j = 2) |
| 第i行第j列元素的地址 | &a[i][j]、&p[i*3+j]、*p+i*3+j | int* | 0x19AFFC54 (i = 1, j = 2) |
注:通过列指针访问元素时用到了列数3,而通过行指针或者数组名+下标访问元素时不需要,能够“自动”找到对应的地址,这就是在声明中必须指定列数的原因。
2.4 验证代码#include3.应用using namespace std; int main() { int a[2][3] = {{1, 2, 3}, {4, 5, 6}}; int (*q)[3] = a; // 行指针 int *p = &a[0][0]; // 列指针 printf("首行的地址n"); printf("&a[0]ttatt&q[0]ttqn"); printf("%pt%pt%pt%pn", &a[0], a, &q[0], q); putchar('n'); printf("第i行的地址n"); printf("it&a[i]tta+itt&q[i]ttq+in"); for (int i = 0; i < 2; ++i) printf("%dt%pt%pt%pt%pn", i, &a[i], a + i, &q[i], q + i); putchar('n'); printf("首行首列元素的值n"); printf("a[0][0]t**atp[0]t*pn"); printf("%dt%dt%dt%dn", a[0][0], **a, p[0], *p); putchar('n'); printf("首行首列元素的地址n"); printf("&a[0][0]t*att&p[0]ttpn"); printf("%pt%pt%pt%pn", &a[0][0], *a, &p[0], p); putchar('n'); printf("第i行首列元素的值n"); printf("ita[i][0]t**(a+i)tp[i*3]t*(p+i*3)n"); for (int i = 0; i < 2; ++i) printf("%dt%dt%dt%dt%dn", i, a[i][0], **(a + i), p[i * 3], *(p + i * 3)); putchar('n'); printf("第i行首列元素的地址n"); printf("it&a[i][0]tt*(a+i)tt&p[i*3]ttp+i*3n"); for (int i = 0; i < 2; ++i) printf("%dt%pt%pt%pt%pn", i, &a[i][0], *(a + i), &p[i * 3], p + i * 3); putchar('n'); printf("第i行第j列元素的值n"); printf("itjta[i][j]t*(*(a+i)+j)tp[i*3+j]t*(p+i*3+j)n"); for (int i = 0; i < 2; ++i) for (int j = 0; j < 3; ++j) printf("%dt%dt%dt%dtt%dtt%dn", i, j, a[i][j], *(*(a + i) + j), p[i * 3 + j], *(p + i * 3 + j)); putchar('n'); printf("第i行第j列元素的地址n"); printf("itjt&a[i][j]t*(a+i)+jt&p[i*3+j]tp+i*3+jn"); for (int i = 0; i < 2; ++i) for (int j = 0; j < 3; ++j) printf("%dt%dt%pt%pt%pt%pn", i, j, &a[i][j], *(a + i) + j, &p[i * 3 + j], p + i * 3 + j); putchar('n'); return 0; }
下面的程序分别使用行指针和列指针遍历二维数组,并展示了如何将行指针和列指针传递给函数:
#include4.参考using namespace std; void print_2d_array(int a[][3], int m, int n); void print_2d_array_row_pointer(int (*q)[3], int m, int n); void print_2d_array_col_pointer(int* p, int m, int n); int main() { int a[2][3] = {{1, 2, 3}, {4, 5, 6}}; int (*q)[3] = a; int* p = &a[0][0]; print_2d_array(a, 2, 3); print_2d_array_row_pointer(q, 2, 3); print_2d_array_col_pointer(p, 2, 3); return 0; } void print_2d_array(int a[][3], int m, int n) { cout << "直接打印二维数组:" << endl; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) cout << a[i][j] << ' '; cout << endl; } } void print_2d_array_row_pointer(int (*q)[3], int m, int n) { cout << "使用行指针打印二维数组:" << endl; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) cout << q[i][j] << ' '; cout << endl; } } void print_2d_array_col_pointer(int* p, int m, int n) { cout << "使用列指针打印二维数组:" << endl; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) cout << p[i * n + j] << ' '; cout << endl; } }
二维数组与指针(详解)



