5.2 二维数组与指针
5.2.1 使用数组名访问
设有一个二维数组 a 定义为:
int a[3][4] = {{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}};
二维数组 a,可视为三个一维数组:a[0]、a[1]、a[2];而每个一维数组又是一维数组,分别由 4 个元素组成。首先,理解如下的操作:
printf("%d\n",a[0][0]); //二维数组中元素a[0][0]的值
printf("%p\n",&a[0][0]); //二维数组中元素a[0][0]的值对应的地址
printf("%p\n",a[0]); //二维数组中a[0][0]的地址
printf("%p\n",a); //二维数组中a[0]的地址
printf("%p\n",&a); //二维数组a的地址
对应图示
举例:
| 表示形式 | 含义 | 地址 |
|---|---|---|
a | 二维数组名,指向一维数组a[0], 即0行起始地址 | 2000 |
&a[0][0],a[0],*a | 0行0列元素地址 | 2000 |
a[0][0],*(a[0]),**a | 0行0列元素a[0][0]的值 | 1 |
&a[1],a+1 | 指向索引为1行的起始地址 | 2016 |
&a[1][0],a[1],*(a+1) | 1行0列元素a[1][0]的地址 | 2016 |
a[1][0],*(a[1]),*(*(a+1)) | 1行0列元素a[1][0]的值 | 5 |
&a[1][2],a[1]+2,*(a+1)+2 | 1行2列元素a[1][2]的地址 | 2024 |
a[1][2],*(a[1]+2),*(*(a+1)+2) | 1行2列元素a[1][2]的值 | 是元素值,7 |
总结:
&a:二维数组a的地址
a: 二维数组中a[0]的地址
a[0]:二维数组中a[0][0]的地址
讨论:a[0][0]相关的
a[0][0]的地址:&a[0][0],a[0],*a,
a[0][0]的值: a[0][0],*(a[0]),**a,
讨论:a[1]相关的
a[1]的地址:&a[1],a + 1
讨论:a[1][0]相关的
a[1][0]的地址:&a[1][0],a[1],*(a+1)
a[1][0]的值:a[1][0],*a[1],*(*(a+1))
讨论:a[1][2]相关的
a[1][2]的地址:&a[1][2],a[1]+2,*(a+1)+2
a[1][2]的值:a[1][2],*(a[1]+2),*(*(a+1)+2)
注意:
如果 a 是二维数组,则 a[i]代表一个数组名, a[i]并不占内存单元,也不能存放a 数组元素值。它只是一个地址。所以:a、a+i、a[i]、(a+i)、(a+i)+j、a[i]+j 都是地址。
获取数组元素值的三种表示形式:
1) a[i][j] 下标法
2) *(a[i]+j) 用一维数组名
3) *(*(a+i)+j) 用二维数组名
5.2.2 使用指针变量访问
设 p 是指针变量,若p 指向数组首元素,即p = a[0];,那a[i][j]的指针如何表示?
先看一个代码:
int main() {
int a[3][2] = {{10, 20},
{30, 40},
{50, 60}};
int *p;
p = &a[0][0];
printf("%p\n", p); //000000f2f49ff7b0
printf("%p\n", p + 1); //000000f2f49ff7b4
printf("%p\n", p + 2); //000000f2f49ff7b8
int *q;
q = a[0];
printf("%p\n", q); //000000f2f49ff7b0
printf("%p\n", q + 1); //000000f2f49ff7b4
printf("%p\n", q + 2); //000000f2f49ff7b8
int *r;
r = a; //代码片段1
printf("%p\n", r); //000000f2f49ff7b0
printf("%p\n", r + 1); //000000f2f49ff7b4
printf("%p\n", r + 2); //000000f2f49ff7b8
return 0;
}
进而:
-
p+j 将指向 a[0] 数组中的元素
a[0][j]。 -
对于二维数组
a[M][N]来讲,由于 a[0]、a[1]、... 、a[M-1]等各行数组在内存中是依次连续存储,则对于 a 数组中的任一元素a[i][j]:- 地址表示:
p+i*N+j - 值表示:
*(p+i*N+j)、p[i*N+j]
- 地址表示:
注意:上述代码中,代码片段1中的赋值操作会存在类型不匹配的情况,我们在5.6节中展开说明。
举例1:
int b[4][3] = {{10, 20, 30},
{40, 50, 60},
{70, 80, 90},
{100, 110, 120}};
int *p = b[0];
则:元素 b[1][2]对应的地址/指针、元素值为:
printf("b[1][2]对应的地址/指针为:%p\n",p+1*3+2);
printf("b[1][2]对应的值为:%d\n",*(p+1*3+2));
printf("b[1][2]对应的值为:%d\n",p[1*3+2]);
举例2:用指针访问二维数组,求二维数组元素的最大值。
#include <stdio.h>
#define ROWS 3
#define COLS 4
int main() {
int a[ROWS][COLS] = {{10, 20, 30, 40},
{50, 60, 70, 80},
{120, 110, 100, 90}};
//方式1:
// int max = a[0][0];
// for (int i = 0; i < ROWS; i++) {
// for (int j = 0; j < COLS; j++) {
// if (max < a[i][j]) {
// max = a[i][j];
// }
// }
// }
//方式2:
int *p = a[0];
int max;
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
if (max < *(p + i * COLS + j)) {
max = *(p + i * COLS + j);
}
}
}
printf("max=%d\n", max);
//方式3:
int *q, max1;
for (q = a[0], max1 = *q; q < a[0] + ROWS * COLS; q++)
if (max1 < *q)
max1 = *q;
printf("Max=%d\n", max1);
return 0;
}
5.3 指针数组
5.3.1 数组指针 vs 指针数组
数组指针:
当指针变量里存放一个数组的首地址时,此指针变量称为指向数组的指针变量,简称数组指针。
数组指针是指针?还是数组?
答案是:指针。
整型指针: int * pint; 能够指向整型数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
数组指针:能够指向数组的指针。
指针数组:
数组是用来存放一系列相同类型的数据,当然数组也可以用来存放指针,这种用来存放指针的数组被称为指针数组,它要求存放在数组中指针的数据类型必须一致。
问题:指针数组是指针还是数组?
答案:是数组。是存放指针的数组。
5.3.2 指针数组的使用
格式:
数据类型 *指针数组名[大小];
举例1:
int *arr[5];
arr是一个数组,有5个元素,每个元素是一个整型指针,需要使用下标来区分。
举例2:
int main() {
int a, b, c, d, e;
a = 1;
b = 2;
c = 3;
d = 4;
e = 5;
int *arr[] = {&a, &b, &c, &d, &e};//定义一个int类型的指针数组
for(int i = 0;i < 5;i++){
printf("%d ",*arr[i]);
}
return 0;
}
举例3:
#include <stdio.h>
int main() {
int line1[] = {1, 2, 3, 4}; //声明数组,矩阵的第一行
int line2[] = {5, 6, 7, 8}; //声明数组,矩阵的第二行
int line3[] = {9, 10, 11, 12}; //声明数组,矩阵的第三行
int *p_line[3]; //声明整型指针数组
p_line[0] = line1; //初始化指针数组元素
p_line[1] = line2;
p_line[2] = line3;
printf("矩阵:\n");
for (int i = 0; i < 3; i++) { //对指针数组元素循环
for (int j = 0; j < 4; j++) { //对矩阵每一列循环
printf("%d\t", p_line[i][j]); //或改为printf("%d\t", *(p_line[i] + j));
}
printf("\n");
}
return 0;
}
运行结果:
5.4 字符数组 vs 字符指针变量
一个字符串,可以使用一维字符数组表示,也可以使用字符指针来表示。
- 字符数组由若干个元素组成,每个元素放一个字符
- 字符指针变量中存放的是地址(字符串/字符数组的首地址),绝不是将字符串放到字符指针变量中。
举例1:
char str[] = "hello tom"; //使用字符数组
char * pStr = "hello tom"; //使用字符指针
图示:
两种方式的对比:
对已声明好的字符数组,只能一一对各个元素赋值,不能用以下错误方法对字符数组赋值
char str[14];
str[0] = 'i'; //正确
str = "hello Tom"; //错误
对字符指针变量,采用如下方式赋值是可以的。
char * pStr = "hel";
pStr = "hello tom"; //正确
图示:
一个字符数组,因为它有确定的内存地址,所以字符数组名是一个
常量。而定义一个字符指针变量时,它在指向某个确定的字符串数据的情况下,也可以多次重新赋值。
举例2:体会字符串字面量的不可变性
情况1:针对于整型数组、整型指针变量
int main() {
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *p = arr;
printf("%d\n", p[1]); //2
p[1] = 50;
printf("%d\n", p[1]); //50
printf("%d\n", arr[1]); //50
//
int num = 30;
p = #
printf("%d\n",*p); //30
printf("%d\n",p[0]); //30
return 0;
}
情况2:针对于字符数组、字符指针变量
int main() {
char arr[] = "hello";
arr[1] = "m"; //运行时错误
printf("%s\n",arr);
return 0;
}
因为字符串字面量存储在只读内存区域,是不可变的,不能修改其值。
拓展:
int main() {
char *pStr = "hello";
pStr = "hello tom"; //正确
pStr[1] = 'm'; //运行时错误
printf("%s\n",pStr);
return 0;
}
5.5 字符串数组的表示
字符串可以使用一维字符数组或字符指针变量等两种方式表示,那么字符串数组如何表示呢?
如果一个数组的每个成员都是一个字符串,则构成了字符串数组。字符串数组有两种表示方式:① 二维字符数组 ;②字符指针数组。
举例1:
方式1:使用二维字符数组
char fruit[][7]={"Apple","Orange","Grape","Pear","Peach"}; //上一章5.6节举例4
char weekdays[7][10] = { //行数7也可以省略
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
};
字符串数组,一共包含7个字符串,所以第一维的长度是7。其中,最长的字符串的长度是10(含结尾的终止符 \0 ),所以第二维的长度统一设为10。
思考: 数组的第二维,长度统一定为10,有点浪费空间,因为大多数成员的长度都小于10。解决方法就是把数组的第二维,从字符数组改成字符指针。
方式2:使用字符指针数组
char* weekdays[7] = { //7也可以省略
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
};
上面的字符串数组,其实是一个一维数组,成员就是7个字符指针,每个指针指向一个字符串(字符数组)。
类似的:定义表示颜色的指针数组 colors,存储“red”、“yellow”、“blue”、“white”、“black” 5 种颜色。
char *colors[5] = {"red", "yellow", "blue", "white", "black"};
遍历字符指针数组:
for (int i = 0; i < 7; i++) {
printf("%s\n", weekdays[i]);
}
举例2:
请编写程序,定义一个字符指针数组,用来存储四大名著的书名, 并通过遍历该指针数组,显示字符串信息 , (即:定义一个指针数组,该数组的每个元素,指向的是一个字符串)
方式1:二维字符数组
int main() {
char books[4][13] = {"三国演义", "西游记", "红楼梦", "水浒传"};
int len = 4;
for(int i = 0;i < len;i++){
printf("books[%d] : %s\n",i,books[i]);
}
return 0;
}
方式2:字符指针数组
int main() {
//定义一个指针数组,该数组的每个元素,指向的是一个字符串
char *books[] = {
"三国演义",
"西游记",
"红楼梦",
"水浒传",
};
//遍历
int len = 4;
for (int i = 0; i < len; i++) {
printf("books[%d] : %s\n", i, books[i]);
}
return 0;
}
5.6 拓展:指向固定长度数组的指针变量
定义一个整型指针变量指向一维数组,一维数组的每个元素包含 m 个元素。
定义格式:
(*标识符)[一维数组元素个数];
例如:定义一个指针变量 p,它指向包含有 4 个元素的一维数组。
int (*p)[4];
说明:p先和*结合,说明p是一个指针变量,指向一个大小为4的整型数组。
注意:此时定义的是一个指针变量,并非是一个指针数组。(*p 必须放在括弧内,否则就变成了定义指针数组。)
由于 p 是指向有 4 个整型元素的一维数组的指针变量,因此,p+1 是将地址值加上 4*4,即指向下一个一维数组。
举例:
int a[3][4] = {{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}};
int (*q)[4];
q = a;
则:
q 为二维数组第 0 行首地址,与 a 相同;
q+1 为二维数组第 1 行首地址,与 a+1相同;
q+2 为二维数组第 2 行首地址,与 a+2相同;
*(q+i)为二维数组第 i 行第 0 列元素的地址,与*(a+i)相同;
*(q+i)+j 为二维数组第 i 行第 j 列元素的地址,与*(a+i)+j 相同;
*(*(q+i)+j) 为二维数组第 i 行第 j 列元素值,与*(*(a+i)+j)相同,即 a[i][j]。
举例:
int main() {
int arr[3][4] = {{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}};
int (*q)[4];
q = arr;
printf("arr[0]的地址为:%p\n", arr); //0000006460dffb40
printf("arr[0]的地址为:%p\n", q); //0000006460dffb40
printf("arr[0][1]的地址为:%p\n", *arr + 1); //0000006460dffb44
printf("arr[0][1]的地址为:%p\n", *q + 1); //0000006460dffb44
printf("arr[0][1]的地址为:%p\n", arr[0] + 1); //0000006460dffb44
printf("arr[0][1]的地址为:%p\n", q[0] + 1); //0000006460dffb44
printf("arr[1]的地址为:%p\n", arr + 1); //0000006460dffb50
printf("arr[1]的地址为:%p\n", q + 1); //0000006460dffb50
printf("arr[1][0]的值为:%d\n", *(*(arr + 1))); //5
printf("arr[1][0]的值为:%d\n", *(*(q + 1))); //5
printf("arr[1][1]的值为:%d ", *(*(q + 1) + 1));//6
//遍历arr[0]中的几个元素
for (int i = 0; i < 4; i++) {
printf("%d ", *(*q + i)); //输出1 2 3 4
}
printf("\n");
return 0;
}
【华南理工大学2018研】若有以下说明:
int w[3][4]={{0,1},{2,4},{5,8}}; int (*p)[4]=w;则数值为4的表达式是( )。
A.w[1]+1
B.p++,(p+1)
C.w[2][2]
D.p[1][1]【答案】D
【解析】A中w[1]表示的是数值2,则表达式的值为3,错误;B中p++表示指向二维数组第二行w[1]的地址,而(p+1)代表的是第三行w[2][0]元素的地址,B错误;C中表示的是数值0,答案选D。