一、数组与指针的基本概念
1. 数组的三大特点
- 连续性:内存空间连续分配
- 单一性:元素类型相同
- 有序性:元素有固定顺序
2. 二维数组的本质
二维数组 = 一维数组的数组
int a[3][4];
// 可以理解为:有3个元素,每个元素都是int[4]类型
3. 核心类型辨析
// 整型二维数组
int a[3][4];
int (*a)[4]; // ❌ 错误理解
// ✅ 正确:a是数组名,不需要*
// 数组指针 vs 指针数组
char (*s)[4]; // 数组指针:指向char[4]类型数组的指针
char* s[4]; // 指针数组:有4个元素,每个元素都是char*类型
二、二维数组首元素的理解
1. 首元素的双重含义
int a[3][4];
首元素:a[0]
// 1. 作为二维数组的首元素
// 2. 相当于是内部一维数组的数组名
// a. 类型 --- int[4]类型
// b. 值 --- 内部一维数组的首元素地址 &a[0][0]
2. 地址的层次关系
&a[0] // 1.行地址
a[0][0] // 内部一维数组的首元素
&a[0][0] // 内部一维数组的首元素地址
&a[0][0] // <=地址值等价=> a[0]
关键理解:
a[0]的值等价于&a[0][0]a[0]的类型是int[4]&a[0]的类型是int[4]*(C语言写作int(*)[4])
三、数组指针的定义与使用
1. 定义方式
int a[3][4];
// 首元素是 a[0]
// &a[0] --- 行地址
// --- 确定地址类型
// --- a[0]的类型是 int[4]
// --- &a[0]的数据类型 --- int[4]*
int (*p)[4] = &a[0]; // 正确定义
int (*p)[4] = a; // 也正确,a会退化为&a[0]
2. 指针运算详解
int a[3][4];
int (*p)[4] = a;
访问元素:
p // 代表一行地址
p+i // 第i行 行地址
*(p+i) // 第i行 第0列(首元素)的地址 ---列地址
*(p+i)+j // 第i行 第j列 元素的地址 ---列地址
*(*(p+i)+j) // 第i行 第j列 元素
| 表达式 | 含义 | 类型 |
|---|---|---|
p | 代表一行地址 | int(*)[4] 行指针 |
p+i | 第i行的行地址 | int(*)[4] 行指针 |
*(p+i) | 第i行首元素地址(降维) | int* 列指针 |
*(p+i)+j | 第i行第j列元素地址 | int* 列指针 |
*(*(p+i)+j) | 第i行第j列元素值 | int |
3. 等价表达式
(1). a[i][j]
(2). *(*(a+i)+j)
(3). *(a+i)[j] // a[i] <=> *(a+i)
(4). *(a[i] + j)
四、字符型二维数组
1. 基本使用
char s[3][10] = {"hello", "world", "china"};
char (*p)[10] = s;
// 访问方式
*(p+i) // 第i行首字符地址
*(p+i)+j // 第i行第j列字符地址 (i=0, j=4)
2. 内存布局
char s[3][10] = {"hello", "world", "china"};
内存示意:
p -> |'h'|'e'|'l'|'l'|'o'|'\0'|...| (10字节)
p+1 -> |'w'|'o'|'r'|'l'|'d'|'\0'|...| (10字节)
p+2 -> |'c'|'h'|'i'|'n'|'a'|'\0'|...| (10字节)
3. 关注点---字符串
printf("%s\n", p+i); // ❌ 不行
// p+i是行地址 char(*)[10]
// %s需要 char* 类型
printf("%s\n", *(p+i)); // ✅ 正确
// *(p+i)是列地址 char*
// %s --- char* 整个字符串的首地址
关键理解:
- 不行:
p+i是行地址char(*)[10] - 正确:
*(p+i)是首字符地址char* %s需要的是char*类型
五、访问二维数组的两种方式
方式1:使用数组指针(推荐)
int a[3][4];
int (*p)[4] = &a[0]; // 或 = a
int (*p)[4] = a;
方式2:使用二维数组名直接访问
int a[3][4];
// 直接用 a 访问,a 等价于 &a[0]
六、指针数组(一维指针数组)
1. 定义与特点
char *s1 = "hello";
char *s2 = "world";
char *s3 = "china";
char *s[3] = {"hello", "world", "china"};
// s 是指针数组:数组中存放的都是地址
2. 二级指针操作
char *s[3] = {"hello", "world", "china"};
char **p = s; // p指向指针数组的首元素
// 类型分析
s[0] // 首元素,类型:char*
&s[0] // 首元素地址,类型:char**
// 使用
printf("%s\n", p+i); // ❌ 错误,p+i是char**
printf("%s\n", *(p+i)); // ✅ 正确,*(p+i)是char*
七、完整对比表
1. 类型对比
| 声明方式 | 名称 | 存储内容 | 指针类型 | 适用场景 |
|---|---|---|---|---|
int a[3][4] | 二维数组 | 12个int | int(*)[4] | 固定大小的矩阵 |
char s[3][10] | 字符二维数组 | 3×10个字符 | char(*)[10] | 固定长度的字符串集合 |
char *s[3] | 指针数组 | 3个char* | char** | 不同长度的字符串集合 |
2. 内存布局对比
// 二维数组
char s[3][10] = {"hello", "world", "china"};
[h][e][l][l][o][\0][ ][ ][ ][ ] // 连续的30字节
[w][o][r][l][d][\0][ ][ ][ ][ ]
[c][h][i][n][a][\0][ ][ ][ ][ ]
// 指针数组
char *s[3] = {"hello", "world", "china"};
s[0] -> "hello" (在只读数据区)
s[1] -> "world" (在只读数据区)
s[2] -> "china" (在只读数据区)
数组本身只存储3个指针
3. 操作对比
| 操作 | 二维数组 | 指针数组 |
|---|---|---|
| 定义指针 | char (*p)[10] = s; | char **p = s; |
| 输出字符串 | printf("%s", *(p+i)); | printf("%s", *(p+i)); |
| 修改字符 | ✅ 可以:s[0][0] = 'H'; | ❌ 不可以(字符串常量) |
| 改变指向 | ❌ 数组名不可改变 | ✅ 可以:s[0] = "new"; |
八、重点难点解析
1. 降维操作的理解
int (*p)[4]; // 行指针
p // 行指针(int(*)[4])
*(p) // 降维 -> 列指针(int*)
*(p)+j // 仍是列指针
*(*(p)+j) // 降维 -> 元素值(int)
记忆口诀:
- 每次
*操作就是一次降维 - 行指针 → 列指针 → 元素值
2. 类型判断技巧
// 看到 [] 在变量名右边 → 数组
char s[10]; // 数组
char *s[10]; // 指针数组(数组优先级高)
// 看到 () 包裹变量名 → 指针
char (*s)[10]; // 数组指针(指针优先级被()提高)
char (*s); // 普通指针
3. 字符串输出的注意事项
char s[3][10] = {"hello", "world", "china"};
char (*p)[10] = s;
// ❌ 错误写法
printf("%s", p); // p是行指针,不是字符指针
printf("%s", p+1); // p+1还是行指针
// ✅ 正确写法
printf("%s", *p); // *p是字符指针 char*
printf("%s", *(p+1)); // *(p+1)是字符指针 char*
printf("%s", p[1]); // p[1]等价于*(p+1)
九、课堂练习汇总
练习1:找出二维整型数组最大值
int findMax(int (*p)[4], int row, int col);
练习2:字符串输入函数
void inputStr(char (*p)[10], int row);
练习3:找出最大字符串
int findMaxStr(char (*p)[10], int row);
练习4:字符串逆序
void reverseStrings(char (*p)[10], int row);
练习5:字符串排序
// 选择排序
void selectSort(char (*p)[10], int row);
// 冒泡排序
void bubbleSort(char (*p)[10], int row);
十、记忆要点总结
1. 核心概念
- 二维数组的首元素是一个一维数组
a[0] - 数组名的值是地址,类型是数组类型
&a[0]地址值等于a[0],但类型不同
2. 指针类型
- 数组指针:
int (*p)[4]← 指向数组 - 指针数组:
int *p[4]← 数组存指针
3. 访问元素
// 二维数组
*(*(p+i)+j) <==> p[i][j] <==> a[i][j]
// 记忆:两次*降维,两次[]索引
4. 字符串操作
- 二维数组:
printf("%s", *(p+i)); - 指针数组:
printf("%s", *(p+i)); - 关键:确保传给
%s的是char*类型
5. 类型判断口诀
- 优先级:
()>[]>* - 从变量名开始,向右看
[]是数组,向左看*是指针 - 遇到
()就先处理括号内的
终极理解:指针的本质是类型系统,不同的指针类型决定了:
- 指针运算时的步长(
+1移动多少字节) - 解引用时得到什么类型
- 可以执行什么操作
掌握了类型,就掌握了指针!

1 #include <stdio.h>
2 int main(void)
3 {
4 int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
5 int (*p)[4] = a;
6 int i = 0;
7 int j = 0;
8 for(i = 0;i <3;++i)
9 {
10 for(j = 0;j<4;++j)
11 {
12 printf("%d ",*(*(p+i)+j));
13 }
14 putchar('\n');
15
16 }
17 return 0;
18 }


960

被折叠的 条评论
为什么被折叠?



