【C++】二维数组的行指针和列指针
在C++中,数组与指针有密切的关系。任何通过数组和下标实现的表达式可等价地通过指针和偏移量实现。下面首先介绍一维数组与指针的关系,之后介绍二维数组的行指针和列指针。
1.一维数组和指针
定义一个长度为3的一维数组a
:
1
int a[3] = {1, 2, 3};
假设其首元素地址为0x9F36FAE0,则数组元素在内存中的示意图如下图所示:
1.1 指向数组元素的指针
指向数组元素的指针声明和初始化方式如下:
1
int* p = a; // 等价于int* 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
1.2 指向数组的指针
指向数组的指针声明和初始化方式如下:
1
int (*q)[3] = &a;
这里声明了一个指向数组a
本身的指针q
,因此*q
等价于a
。
注意:这里的圆括号不能省略,否则int *q[3]
声明的是“有3个元素的整型指针数组”,而不是“指向有3个元素的整型数组的指针”。
对于数组a
和指向其本身的指针q
,有以下等价形式:
含义 | 等价形式 | 类型 | 值 |
---|---|---|---|
首元素的值 | a[0] 、*a 、(*q)[0] 、**q | int | 1 |
首元素的地址 | &a[0] 、a 、&(*q)[0] 、*q | int* | 0x9F36FAE0 |
第i个元素的值 | a[i] 、*(a+i) 、(*q)[i] 、*(*q+i) | int | 3 (i = 2) |
第i个元素的地址 | &a[i] 、a+i 、&(*q)[i] 、*q+i | int* | 0x9F36FAE8 (i = 2) |
数组的地址 | &a 、q | int(*)[3] | 0x9F36FAE0 |
注意:&a
在数值上等于a
,但二者的类型不同,如下表所示:
指针 | 指针类型 | 元素类型 | sizeof元素 | 指针值 |
---|---|---|---|---|
a | int[3] | int | 4 | 0x9F36FAE0 |
a+1 | int[3] | int | 4 | 0x9F36FAE4 |
p | int* | int | 4 | 0x9F36FAE0 |
p+1 | int* | int | 4 | 0x9F36FAE4 |
&a | int(*)[3] | int[3] | 12 | 0x9F36FAE0 |
&a+1 | int(*)[3] | int[3] | 12 | 0x9F36FAEC |
q | int(*)[3] | int[3] | 12 | 0x9F36FAE0 |
q+1 | int(*)[3] | int[3] | 12 | 0x9F36FAEC |
&a
和q
是指向数组的指针,因此&a+1
和q+1
直接到达数组末尾的下一个位置。
1.3 验证代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <cstdio>
using namespace std;
int main() {
int a[3] = {1, 2, 3};
int* p = a;
int (*q)[3] = &a;
printf("第i个元素的值:\n");
printf("%s %10s %10s %10s %10s %10s %10s\n", "i", "a[i]", "*(a+i)", "p[i]", "*(p+i)", "(*q)[i]", "*(*q+i)");
for (int i = 0; i < 3; ++i)
printf("%d %10d %10d %10d %10d %10d %10d\n", i, a[i], *(a + i), p[i], *(p + i), (*q)[i], *(*q + i));
putchar('\n');
printf("第i个元素的地址:\n");
printf("%s %10s %10s %10s %10s %10s %10s\n", "i", "&a[i]", "a+i", "&p[i]", "p+i", "&(*q)[i]", "*q+i");
for (int i = 0; i < 3; ++i)
printf("%d %10p %10p %10p %10p %10p %10p\n", i, &a[i], a + i, &p[i], p + i, &(*q)[i], *q + i);
putchar('\n');
printf("指针的值:\n");
printf("%10s %10s %10s\n", "指针", "类型", "值");
printf("%10s %10s %10p\n", "a", "int[3]", a);
printf("%10s %10s %10p\n", "a+1", "int[3]", a + 1);
printf("%10s %10s %10p\n", "p", "int*", p);
printf("%10s %10s %10p\n", "p+1", "int*", p + 1);
printf("%10s %10s %10p\n", "&a", "int(*)[3]", &a);
printf("%10s %10s %10p\n", "&a+1", "int(*)[3]", &a + 1);
printf("%10s %10s %10p\n", "q", "int(*)[3]", q);
printf("%10s %10s %10p\n", "q+1", "int(*)[3]", q + 1);
return 0;
}
输出如下(指针的具体值会随每次运行变化):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
第i个元素的值:
i a[i] *(a+i) p[i] *(p+i) (*q)[i] *(*q+i)
0 1 1 1 1 1 1
1 2 2 2 2 2 2
2 3 3 3 3 3 3
第i个元素的地址:
i &a[i] a+i &p[i] p+i &(*q)[i] *q+i
0 9F36FAE0 9F36FAE0 9F36FAE0 9F36FAE0 9F36FAE0 9F36FAE0
1 9F36FAE4 9F36FAE4 9F36FAE4 9F36FAE4 9F36FAE4 9F36FAE4
2 9F36FAE8 9F36FAE8 9F36FAE8 9F36FAE8 9F36FAE8 9F36FAE8
指针的值:
指针 类型 值
a int[3] 9F36FAE0
a+1 int[3] 9F36FAE4
p int* 9F36FAE0
p+1 int* 9F36FAE4
&a int(*)[3] 9F36FAE0
&a+1 int(*)[3] 9F36FAEC
q int(*)[3] 9F36FAE0
q+1 int(*)[3] 9F36FAEC
2.二维数组和指针
定义一个大小为2×3的二维数组a
:
1
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} | 0x19AFFC40 |
首行的地址 | &a[0] 、a | int(*)[3] | 0x19AFFC40 | |
第i行 | a[i] 、*(a+i) | int[3] | {4, 5, 6} (i = 1) | 0x19AFFC4C (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) |
注意:&a[0][0]
、a[0]
、&a[0]
、a
和&a
(上面未出现)在数值上均相等,但类型不同,如下表所示:
指针 | 指针类型 | 元素类型 | sizeof元素 | 指针值 |
---|---|---|---|---|
&a[0][0] | int* | int | 4 | 0x19AFFC40 |
&a[0][0]+1 | int* | int | 4 | 0x19AFFC44 |
a[0] | int[3] | int | 4 | 0x19AFFC40 |
a[0]+1 | int[3] | int | 4 | 0x19AFFC44 |
&a[0] | int(*)[3] | int | 12 | 0x19AFFC40 |
&a[0]+1 | int(*)[3] | int | 12 | 0x19AFFC4C |
a | int[2][3] | int[3] | 12 | 0x19AFFC40 |
a+1 | int[2][3] | int[3] | 12 | 0x19AFFC4C |
&a | int(*)[2][3] | int[2][3] | 24 | 0x19AFFC40 |
&a+1 | int(*)[2][3] | int[2][3] | 24 | 0x19AFFC58 |
2.2 行指针
二维数组行指针的声明和初始化方式如下:
1
int (*q)[3] = a; // 等价于int (*q)[3] = &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} | 0x19AFFC40 |
首行的地址 | &a[0] 、a 、&q[0] 、q | int(*)[3] | 0x19AFFC40 | |
第i行 | a[i] 、q[i] 、*(q+i) | int[3] | {4, 5, 6} (i = 1) | 0x19AFFC4C (i = 1) |
第i行的地址 | &a[i] 、&q[i] 、q+i | int(*)[3] | 0x19AFFC4C (i = 1) |
与数组元素相关的表达形式这里不再列举,直接将a
替换为q
即可。
2.3 列指针
二维数组列指针的声明和初始化方式如下:
1
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) |
注:
- 使用列指针时不存在“行”的概念,因为对于列指针来说,二维数组
a
相当于长度为6的一维数组。 - 通过列指针访问元素时(
p[i*3+j]
)用到了列数3,而通过行指针或者数组名+下标访问元素时(a[i][j]
)却不需要,好像编译器能够“自动”知道列数。这是因为数组的声明类型int a[2][3]
中包含了列数信息——这就是为什么在定义二维数组或者声明函数参数时可以省略行数,但必须指定列数的原因,即可以声明为int a[][3]
,但不能是int a[][]
。
2.4 验证代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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("第i行的地址\n");
printf("%s %10s %10s %10s %10s\n", "i", "&a[i]", "a+i", "&q[i]", "q+i");
for (int i = 0; i < 2; ++i)
printf("%d %10p %10p %10p %10p\n", i, &a[i], a + i, &q[i], q + i);
putchar('\n');
printf("第i行第j列元素的值\n");
printf("%s %s %10s %10s %10s %10s\n", "i", "j", "a[i][j]", "*(*(a+i)+j)", "p[i*3+j]", "*(p+i*3+j)");
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 3; ++j)
printf("%d %d %10d %10d %10d %10d\n", i, j, a[i][j], *(*(a + i) + j), p[i * 3 + j], *(p + i * 3 + j));
putchar('\n');
printf("第i行第j列元素的地址\n");
printf("%s %s %10s %10s %10s %10s\n", "i", "j", "&a[i][j]", "*(a+i)+j", "&p[i*3+j]", "p+i*3+j");
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 3; ++j)
printf("%d %d %10p %10p %10p %10p\n", i, j, &a[i][j], *(a + i) + j, &p[i * 3 + j], p + i * 3 + j);
putchar('\n');
printf("指针的值:\n");
printf("%10s %15s %10s\n", "指针", "类型", "值");
printf("%10s %15s %10p\n", "&a[0][0]", "int*", &a[0][0]);
printf("%10s %15s %10p\n", "&a[0][0]+1", "int*", &a[0][0] + 1);
printf("%10s %15s %10p\n", "a[0]", "int[3]", a[0]);
printf("%10s %15s %10p\n", "a[0]+1", "int[3]", a[0] + 1);
printf("%10s %15s %10p\n", "&a[0]", "int(*)[3]", &a[0]);
printf("%10s %15s %10p\n", "&a[0]+1", "int(*)[3]", &a[0] + 1);
printf("%10s %15s %10p\n", "a", "int[2][3]", a);
printf("%10s %15s %10p\n", "a+1", "int[2][3]", a + 1);
printf("%10s %15s %10p\n", "&a", "int(*)[2][3]", &a);
printf("%10s %15s %10p\n", "&a+1", "int(*)[2][3]", &a + 1);
return 0;
}
输出如下(指针的具体值会随每次运行变化):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
第i行的地址
i &a[i] a+i &q[i] q+i
0 19AFFC40 19AFFC40 19AFFC40 19AFFC40
1 19AFFC4C 19AFFC4C 19AFFC4C 19AFFC4C
第i行第j列元素的值
i j a[i][j] *(*(a+i)+j) p[i*3+j] *(p+i*3+j)
0 0 1 1 1 1
0 1 2 2 2 2
0 2 3 3 3 3
1 0 4 4 4 4
1 1 5 5 5 5
1 2 6 6 6 6
第i行第j列元素的地址
i j &a[i][j] *(a+i)+j &p[i*3+j] p+i*3+j
0 0 19AFFC40 19AFFC40 19AFFC40 19AFFC40
0 1 19AFFC44 19AFFC44 19AFFC44 19AFFC44
0 2 19AFFC48 19AFFC48 19AFFC48 19AFFC48
1 0 19AFFC4C 19AFFC4C 19AFFC4C 19AFFC4C
1 1 19AFFC50 19AFFC50 19AFFC50 19AFFC50
1 2 19AFFC54 19AFFC54 19AFFC54 19AFFC54
指针的值:
指针 类型 值
&a[0][0] int* 19AFFC40
&a[0][0]+1 int* 19AFFC44
a[0] int[3] 19AFFC40
a[0]+1 int[3] 19AFFC44
&a[0] int(*)[3] 19AFFC40
&a[0]+1 int(*)[3] 19AFFC4C
a int[2][3] 19AFFC40
a+1 int[2][3] 19AFFC4C
&a int(*)[2][3] 19AFFC40
&a+1 int(*)[2][3] 19AFFC58
3.n维数组和指针
行指针和列指针的概念可以推广到n维数组:
- n维数组的行指针就是指向n-1维数组的指针。
- n维数组的列指针就是指向数组元素的普通指针。
例如:
1
2
3
4
5
6
int a[2][3][4] = {
{ {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} },
{ {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} }
};
int (*q)[3][4] = a;
int* p = &a[0][0][0];
这段代码声明了一个三维数组a
以及指向其“首行”的行指针q
和指向其首元素的列指针p
,则a[i][j][k]
分别等价于q[i][j][k]
和p[((i*3)+j)*4+k]
。
4.应用
下面的程序分别使用行指针和列指针遍历二维数组,并展示了如何将行指针和列指针传递给函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#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;
}
}