wordpress网站会员太多产品单页设计模板
一 概念明晰:“解引用”与“解引”
1. 什么是“解引用”?
定义:通过指针访问其指向的内存地址中存储的实际数据。
操作符:使用 * 运算符(如 *ptr)。
核心作用:将指针从“地址”转换为“该地址存储的值”。
示例
int num = 42;
int *ptr = # // ptr 存储 num 的地址// 解引用 ptr 获取 num 的值
printf("%d\n", *ptr); // 输出 42(解引用后得到 num 的值)
2. “解引”是“解引用”的简化表达
3. 为什么需要解引用?
指针的本质是存储内存地址的变量,而程序通常需要操作地址中的实际数据。解引用是连接“地址”和“数据”的关键步骤。
4. 解引用的常见用途
(1)访问动态内存
(2)修改函数外部的变量
(3)遍历数组
int arr[3] = {1, 2, 3};
int *ptr = arr;
for (int i = 0; i < 3; i++) {printf("%d ", *(ptr + i)); // 解引用访问每个元素
}
// 输出:1 2 3
二 概念明晰:隐式转换
在C语言中,隐式转换是指编译器在表达式求值、赋值或函数调用时,自动进行的类型转换,无需程序员显式指定。
1. 隐式转换的常见场景
(1)数组到指针的转换
-
二维数组
a的隐式转换:当使用数组名a时(如传递给函数或参与指针运算),它会被隐式转换为指向其首元素的指针:
int a[2][3]; // a 的类型本是 int [2][3],但在此处隐式转换为 int (*)[3](指向第一行的指针) func(a); // 等价于 func(&a[0])
三 与二维数组有关的指针
现已定义一个名为’a’的二维数组

(一) a与*a
a
1. a 的本质
a是一个二维数组的名称。a的本质是一个指向一维数组的指针(即指向第一行的指针)。
2. a 的基本含义
a表示二维数组的首地址,即第 0 行的地址。它的值是&a[0]。a的指向:a指向的是整个第一行(一个一维数组),而不是某个具体的元素。
3. a 退化的指针类型
a退化为int (*)[4](假设a是int a[3][4])。- 例外情况:
sizeof(a):此时a不会退化,结果是整个数组的大小(如3 * 4 * sizeof(int))。&a:此时a不会退化,结果是整个二维数组的地址(类型是int (*)[3][4])。
4. a 的用法
- 作为指向行的指针:
a + i:指向第i行的指针(即&a[i])。*(a + i):访问第i行(即a[i],是一个一维数组)。
- 遍历二维数组:
- 可以通过
a和行索引访问每一行,例如a[i][j]或*(*(a + i) + j)。
- 可以通过
*a
1. *a 的本质
*a是对a的解引用。由于a是指向第一行的指针,*a就是第一行本身(即a[0])。a[0]是一个一维数组的名称,因此*a的本质是一个指向第一行第一个元素的指针(即&a[0][0])。
2. *a 的基本含义
*a表示二维数组第一行的首地址(即第 0 行第 0 列元素的地址)。*a的指向:*a指向的是a[0][0],即具体的整数元素。
3. *a 退化的指针类型
*a是一维数组a[0]的名称,因此会退化为指向其首元素的指针,即int *(指向a[0][0])。- 例外情况:
sizeof(*a):此时*a不会退化,结果是第一行的大小(如4 * sizeof(int))。&*a:等价于a,类型是int (*)[4]。
4. *a 的用法
- 作为指向列元素的指针:
*a + j:指向第 0 行第j列元素的指针(即&a[0][j])。*(*a + j):访问第 0 行第j列元素(即a[0][j])。
- 遍历一维数组:
- 可以通过
*a访问第一行的元素,例如(*a)[j]或*(*a + j)
- 可以通过
总结对比
| 项目 | a | *a |
|---|---|---|
| 本质 | 指向一维数组的指针 (int (*)[4]) | 指向整数的指针 (int *) |
| 基本含义 | 第 0 行的地址 (&a[0]) | 第 0 行第 0 列的地址 (&a[0][0]) |
| 退化类型 | int (*)[4](通常不退化) | int *(退化为一维数组首元素指针) |
| 用法 | 访问行(如 a + i) | 访问列(如 *a + j) |
(二) a[i]与&a[i]
a[i]
1. a[i] 的本质
a[i]是二维数组a的第i行。在 C 语言中,a[i]等价于*(a + i),即对指向第i行的指针解引用,得到第i行本身(一个一维数组)。
2. a[i] 的基本含义
a[i]表示第i行的首地址(即第i行第 0 列元素的地址),等价于&a[i][0]。a[i]的指向:a[i]指向的是第i行的第一个元素(a[i][0])。
3. a[i] 退化的指针类型
- 在大多数表达式中,
a[i]会退化为指向其首元素的指针,即int *(指向a[i][0])。 - 例外情况:
sizeof(a[i]):此时a[i]不会退化,结果是第i行的大小(如4 * sizeof(int))。&a[i]:此时a[i]不会退化,结果是第i行的地址(类型是int (*)[4])。
4. a[i] 的用法
-
作为指向列元素的指针:
a[i] + j:指向第i行第j列元素的指针(即&a[i][j])。*(a[i] + j):访问第i行第j列元素(即a[i][j])。
-
遍历第
i行:- 可以通过
a[i]访问第i行的元素,例如a[i][j]或*(a[i] + j)。
- 可以通过
&a[i]
1. &a[i] 的本质
&a[i]是取第i行的地址。由于a[i]是一个一维数组,&a[i]的类型是指向一维数组的指针(即int (*)[4])。&a[i]等价于a + i,即指向第i行的指针。
2. &a[i] 的基本含义
&a[i]表示第i行的地址,即&a[i][0]的“行视角”地址。&a[i]的指向:&a[i]指向的是整个第i行(一个一维数组),而不是某个具体的元素。
3. &a[i] 退化的指针类型
&a[i]不会退化,因为它本身就是取地址操作,类型固定为int (*)[4](假设a是int a[3][4])。
4. &a[i] 的用法
- 作为指向行的指针:
&a[i] + 1:指向第i+1行的指针(即a + i + 1)。*(&a[i] + 1):访问第i+1行(即a[i + 1])。
- 遍历二维数组:
- 可以通过
&a[i]访问行,例如*(&a[i])[j]或*(*(&a[i] + 0) + j)。
- 可以通过
总结对比
| 项目 | a[i] | &a[i] |
|---|---|---|
| 本质 | 第 i 行的一维数组 (int [4]) | 指向第 i 行的指针 (int (*)[4]) |
| 基本含义 | 第 i 行首元素的地址 (&a[i][0]) | 第 i 行的地址 (a + i) |
| 退化类型 | int *(退化为一维数组首元素指针) | 不退化(固定为 int (*)[4]) |
| 用法 | 访问第 i 行的元素(如 a[i][j]) | 访问行(如 &a[i] + 1) |
(三) a+1与a[i]+1
a+1
1.. a + 1 的本质
a + 1是二维数组a的指针运算,表示跳过一行(即指向下一行的指针)。- 如果
a是int a[3][4],则a的类型是int (*)[4](指向一维数组的指针),a + 1会移动sizeof(int[4])(即4 * sizeof(int))字节。
2. a + 1 的基本含义
a + 1表示第 1 行的地址(即&a[1])。- 它仍然是一个指向行的指针(
int (*)[4]),而不是指向单个元素的指针。
3. a + 1 的退化情况
a + 1不会退化,因为它本身就是指针运算,类型仍然是int (*)[4]。- 例外情况:
- **如果解引用
*(a + 1),则退化为int *(**指向a[1][0]的指针)。
- **如果解引用
4. a + 1 的用法
-
用于遍历二维数组的行:
for (int i = 0; i < 3; i++) {int *row = *(a + i); // 获取第 i 行的首地址for (int j = 0; j < 4; j++) {printf("%d ", row[j]); // 访问 a[i][j]} } -
直接访问下一行:
int (*next_row)[4] = a + 1; // 指向第 1 行的指针 printf("%d\n", (*next_row)[2]); // 输出 a[1][2]
a[i]+1
1. a[i] + 1 的本质
a[i]是第i行的首地址(&a[i][0]),a[i] + 1是指向第i行第 1 个元素的指针(即&a[i][1])。- 如果
a是int a[3][4],a[i]的类型是int *(指向int的指针),a[i] + 1会移动sizeof(int)字节。
2. a[i] + 1 的基本含义
a[i] + 1表示第i行第 1 列元素的地址(即&a[i][1])。- 它是一个指向单个元素的指针(
int *),而不是指向整行的指针。
3. a[i] + 1 的退化情况
a[i]已经退化成了int *,所以a[i] + 1仍然是int *,没有额外的退化。- 例外情况:
sizeof(a[i] + 1)仍然是指针大小(通常是 4 或 8 字节),不会退化。
4. a[i] + 1 的用法
-
用于遍历第
i行的元素:for (int j = 0; j < 4; j++) {printf("%d ", *(a[i] + j)); // 等价于 a[i][j] } -
直接访问第
i行的下一个元素:int *next_element = a[i] + 1; printf("%d\n", *next_element); // 输出 a[i][1]
总结对比
| 项目 | a + 1 | a[i] + 1 |
|---|---|---|
| 本质 | 指向下一行的指针 (int (*)[4]) | 指向当前行下一元素的指针 (int *) |
| 基本含义 | 第 1 行的地址 (&a[1]) | 第 i 行第 1 列元素的地址 (&a[i][1]) |
| 退化情况 | 不退化(仍然是 int (*)[4]) | 不退化(仍然是 int *) |
| 用法 | 遍历行(如 *(a + 1)[j]) | 遍历列(如 *(a[i] + j)) |
(四) a[1][2]与&a[1][2]
a[1][2]
1. a[1][2] 的本质
a[1][2]是二维数组a的第1行第2列的元素(即a的第2行第3列,因为索引从0开始)。- 它是一个具体的整数值(假设
a是int类型数组),而不是指针。
2. a[1][2] 的基本含义
a[1][2]表示直接访问二维数组a的第1行第2列的元素。- 它是数组元素的直接取值,不涉及指针运算(但底层仍然通过指针访问)。
3. a[1][2] 的退化情况
-
a[1][2]不会退化,因为它已经是具体的值(int类型),而不是指针。 -
它可以直接用于赋值、计算或打印:
int val = a[1][2]; // val = 7 printf("%d\n", a[1][2]); // 输出 7
4. a[1][2] 的用法
-
直接访问数组元素:
printf("%d\n", a[1][2]); // 输出 7 -
修改数组元素:
a[1][2] = 100; // 修改 a[1][2] 为 100 -
作为函数参数传递:
void print_val(int x) {printf("%d\n", x); } print_val(a[1][2]); // 输出 7
&a[1][2]
1. &a[1][2] 的本质
&a[1][2]是取a[1][2]的地址,即指向a[1][2]的指针。- 它的类型是
int *(指向int的指针)。 - 例如,如果
a[1][2]的地址是0x1040,则&a[1][2]的值是0x1040。
2. &a[1][2] 的基本含义
&a[1][2]表示第1行第2列元素的地址。- 它是一个指向单个
int的指针,可以用于指针运算或间接访问。
3. &a[1][2] 的退化情况
-
&a[1][2]不会退化,因为它本身就是取地址操作,类型固定为int *。 -
它可以用于指针运算:
int *ptr = &a[1][2]; printf("%d\n", *ptr); // 输出 7 printf("%d\n", *(ptr + 1)); // 输出 a[1][3](8)
4. &a[1][2] 的用法
-
获取元素的地址:
int *p = &a[1][2]; // p 指向 a[1][2] -
通过指针修改数组:
C*(&a[1][2]) = 100; // 等价于 a[1][2] = 100 -
遍历后续元素:
int *p = &a[1][2]; for (int i = 0; i < 2; i++) {printf("%d ", *(p + i)); // 输出 a[1][2] 和 a[1][3] }
总结对比
| 项目 | a[1][2] | &a[1][2] |
|---|---|---|
| 本质 | 具体的 int 值(如 7) | 指向 a[1][2] 的指针 (int *) |
| 基本含义 | 直接访问元素值 | 获取元素地址 |
| 退化情况 | 不退化(已经是 int) | 不退化(固定为 int *) |
| 用法 | 取值、赋值、计算 | 指针运算、间接访问 |
(五)*(a+1)+2、*(*(a+1)+2)以及*(a[1]+2)
1. *(a + 1) + 2
本质
a + 1是指向第1行的指针(类型int (*)[4])。*(a + 1)解引用后得到第1行(类型int [4]),但在表达式中会退化为int *(指向a[1][0]的指针)。*(a + 1) + 2是在a[1]的基础上偏移2个int,得到&a[1][2](类型int *)。
基本含义
- 表示第
1行第2列元素的地址(即&a[1][2])。 - 等价于
a[1] + 2或&a[1][2]。
退化情况
*(a + 1)退化为int *,*(a + 1) + 2仍然是int *(不退化)。
用法
- 用于获取
a[1][2]的地址:int *p = *(a + 1) + 2; // p = &a[1][2] printf("%d\n", *p); // 输出 a[1][2]
2. *(*(a + 1) + 2)
本质
*(a + 1) + 2是&a[1][2](见上)。*(*(a + 1) + 2)是对&a[1][2]解引用,得到a[1][2]的值(类型int)。
基本含义
- 表示第
1行第2列元素的值(即a[1][2])。 - 等价于
a[1][2]或*(a[1] + 2)。
退化情况
- 已经是具体的
int值,不涉及退化。
用法
- 直接访问
a[1][2]:int val = *(*(a + 1) + 2); // val = a[1][2] printf("%d\n", val);
3. *(a[1] + 2)
本质
a[1]是第1行的首地址(类型int [4],退化为int *)。a[1] + 2是&a[1][2](类型int *)。*(a[1] + 2)是对&a[1][2]解引用,得到a[1][2](类型int)。
基本含义
- 表示第
1行第2列元素的值(即a[1][2])。 - 等价于
*(*(a + 1) + 2)和a[1][2]。
退化情况
- 已经是具体的
int值,不涉及退化。
用法
- 直接访问
a[1][2]:int val = *(a[1] + 2); // val = a[1][2] printf("%d\n", val);
总结对比
| 表达式 | 类型 | 等价形式 | 含义 |
|---|---|---|---|
*(a + 1) + 2 | int * | a[1] + 2 | 第 1 行第 2 列地址 |
*(*(a + 1) + 2) | int | a[1][2] | 第 1 行第 2 列的值 |
*(a[1] + 2) | int | *(*(a + 1) + 2) | 第 1 行第 2 列的值 |
(六) 大总结与对比
1. 核心概念分层
| 层级 | 典型表达式 | 本质 | 类型 | 退化规则 |
|---|---|---|---|---|
| 数组层 | a | 二维数组首地址 | int [3][4] | →int (*)[4] |
| 行指针层 | a+i, &a[i] | 第i行地址 | int (*)[4] | 不退化 |
| 列指针层 | *(a+i), a[i] | 第i行首元素地址 | int * | 已退化 |
| 元素层 | a[i][j] | 具体元素值 | int | 不适用 |
2. 关键表达式全解
| 表达式 | 等价形式 | 作用 | 内存操作说明 |
|---|---|---|---|
a | &a[0] | 获取第0行地址 | 步长=一行大小(16字节) |
*a | a[0] | 获取第0行首元素地址 | 步长=1元素大小(4字节) |
a+1 | &a[1] | 移动到第1行地址 | 地址+16字节 |
*(a+1)+2 | &a[1][2] | 获取第1行第2列地址 | 基础地址+8字节 |
*(*(a+1)+2) | a[1][2] | 获取第1行第2列的值 | 直接内存访问 |
&a[1][2] | *(a+1)+2 | 获取元素地址 | 可用于指针运算 |
3. 典型场景对照表
| 访问需求 | 推荐写法 | 指针写法 | 底层解释 |
|---|---|---|---|
| 遍历所有行 | for(i=0;i<3;i++) | for(p=a;p<a+3;p++) | p每次+16字节 |
| 遍历某行元素 | for(j=0;j<4;j++) | for(q=a[i];q<a[i]+4;q++) | q每次+4字节 |
| 随机访问元素 | a[i][j] | *(*(a+i)+j) | 编译器优化后效率相同 |
4. 内存模型可视化
TEXT地址示例 | 值 | 表达式表示
0x1000 | a[0][0]=1 | *(*a)
0x1004 | a[0][1]=2 | *(*a+1)
...
0x1010 | a[1][0]=5 | *(*(a+1))
0x1014 | a[1][1]=6 | *(*(a+1)+1)
0x1018 | a[1][2]=7 | *(*(a+1)+2)
...
0x1020 | a[2][0]=9 | *(*(a+2))
5. 易错点警示
-
维度混淆:
a+1移动一行,a[0]+1移动一列 -
解引用陷阱:
*a得到的是列指针,不是元素值**a才是首个元素值
-
sizeof差异:
sizeof(a) // 48字节(整个数组) sizeof(a[0]) // 16字节(一行) sizeof(*a) // 16字节(同上一行)
6. 终极理解技巧
- 类型观察法:
- 看到
int (*)[4]→行指针 - 看到
int *→列指针
- 看到
- 星号(*)法则:
- 每出现一个
*抵消一个[] a[i][j]≡*(*(a+i)+j)
- 每出现一个
- 地址算术口诀:
- “行跳行大小,列跳元素大小”
