在C++中,数组与指针有密切的关系。任何通过数组和下标实现的表达式可等价地通过指针和偏移量实现。下面首先介绍一维数组与指针的关系,之后介绍二维数组的行指针和列指针。
1.一维数组和指针
定义一个长度为3的一维数组a :
int a[3] = {1, 2, 3};
假设其首元素地址为0x9F36FAE0,则数组元素在内存中的示意图如下图所示:
1.1 指向数组元素的指针
指向数组元素的指针声明和初始化方式如下:
int* p = a;
这里声明了一个指向数组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 是数组名还是指针,都有
1.2 指向数组的指针
指向数组的指针声明和初始化方式如下:
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 |
1.3 验证代码
#include <cstdio>
using namespace std;
int main() {
int a[3] = {1, 2, 3};
int* p = a;
int (*q)[3] = &a;
printf("首元素的值\n");
printf("a[0]\t*a\tp[0]\t*p\t(*q)[0]\t**q\n");
printf("%d\t%d\t%d\t%d\t%d\t%d\n", a[0], *a, p[0], *p, (*q)[0], **q);
putchar('\n');
printf("首元素的地址\n");
printf("&a[0]\t\ta\t\t&p[0]\t\tp\t\t&(*q)[0]\t\t*q\n");
printf("%p\t%p\t%p\t%p\t%p\t%p\n", &a[0], a, &p[0], p, &(*q)[0], *q);
putchar('\n');
printf("第i个元素的值\n");
printf("i\ta[i]\t*(a+i)\tp[i]\t*(p+i)\t(*q)[i]\t*(*q+i)\n");
for (int i = 0; i < 3; ++i)
printf("%d\t%d\t%d\t%d\t%d\t%d\t%d\n", i, a[i], *(a + i), p[i], *(p + i), (*q)[i], *(*q + i));
putchar('\n');
printf("第i个元素的地址\n");
printf("i\t&a[i]\t\ta+i\t\t&p[i]\t\tp+i\t\t&(*q)[i]\t\t*q+i\n");
for (int i = 0; i < 3; ++i)
printf("%d\t%p\t%p\t%p\t%p\t%p\t%p\n", i, &a[i], a + i, &p[i], p + i, &(*q)[i], *q + i);
putchar('\n');
printf("数组的地址\n");
printf("&a\t\tq\n");
printf("%p\t%p\n", &a, q);
return 0;
}
2.二维数组和指针
定义一个大小为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) |
2.2 行指针
二维数组行指针的声明和初始化方式如下:
int (*q)[3] = a;
这里声明了一个指向二维数组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 验证代码
#include <cstdio>
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]\t\ta\t\t&q[0]\t\tq\n");
printf("%p\t%p\t%p\t%p\n", &a[0], a, &q[0], q);
putchar('\n');
printf("第i行的地址\n");
printf("i\t&a[i]\t\ta+i\t\t&q[i]\t\tq+i\n");
for (int i = 0; i < 2; ++i)
printf("%d\t%p\t%p\t%p\t%p\n", i, &a[i], a + i, &q[i], q + i);
putchar('\n');
printf("首行首列元素的值\n");
printf("a[0][0]\t**a\tp[0]\t*p\n");
printf("%d\t%d\t%d\t%d\n", a[0][0], **a, p[0], *p);
putchar('\n');
printf("首行首列元素的地址\n");
printf("&a[0][0]\t*a\t\t&p[0]\t\tp\n");
printf("%p\t%p\t%p\t%p\n", &a[0][0], *a, &p[0], p);
putchar('\n');
printf("第i行首列元素的值\n");
printf("i\ta[i][0]\t**(a+i)\tp[i*3]\t*(p+i*3)\n");
for (int i = 0; i < 2; ++i)
printf("%d\t%d\t%d\t%d\t%d\n", i, a[i][0], **(a + i), p[i * 3], *(p + i * 3));
putchar('\n');
printf("第i行首列元素的地址\n");
printf("i\t&a[i][0]\t\t*(a+i)\t\t&p[i*3]\t\tp+i*3\n");
for (int i = 0; i < 2; ++i)
printf("%d\t%p\t%p\t%p\t%p\n", i, &a[i][0], *(a + i), &p[i * 3], p + i * 3);
putchar('\n');
printf("第i行第j列元素的值\n");
printf("i\tj\ta[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("%d\t%d\t%d\t%d\t\t%d\t\t%d\n", i, j, a[i][j], *(*(a + i) + j), p[i * 3 + j], *(p + i * 3 + j));
putchar('\n');
printf("第i行第j列元素的地址\n");
printf("i\tj\t&a[i][j]\t*(a+i)+j\t&p[i*3+j]\tp+i*3+j\n");
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 3; ++j)
printf("%d\t%d\t%p\t%p\t%p\t%p\n", i, j, &a[i][j], *(a + i) + j, &p[i * 3 + j], p + i * 3 + j);
putchar('\n');
return 0;
}
3.应用
下面的程序分别使用行指针和列指针遍历二维数组,并展示了如何将行指针和列指针传递给函数:
#include <iostream>
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;
}
}
4.参考
二维数组与指针(详解)
|