当前位置: 首页 > news >正文

C46-二维数组与指针的总结

一 概念明晰:“解引用”与“解引”

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’的二维数组

image-20250519095000509

(一) a与*a

a

1. a 的本质
  • a 是一个二维数组的名称a 的本质是一个指向一维数组的指针(即指向第一行的指针)。
2. a 的基本含义
  • a 表示二维数组的首地址,即第 0 行的地址。它的值是 &a[0]
  • a 的指向:a 指向的是整个第一行(一个一维数组),而不是某个具体的元素。
3. a 退化的指针类型
  • a 退化为 int (*)[4](假设 aint 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](假设 aint 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 的指针运算,表示跳过一行(即指向下一行的指针)。
  • 如果 aint 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])。
  • 如果 aint 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 + 1a[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 开始)。
  • 它是一个具体的整数值(假设 aint 类型数组),而不是指针。
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] 的基础上偏移 2int,得到 &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) + 2int *a[1] + 21 行第 2 列地址
*(*(a + 1) + 2)inta[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字节)
*aa[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. 易错点警示
  1. 维度混淆a+1移动一行,a[0]+1移动一列

  2. 解引用陷阱:

    • *a得到的是列指针,不是元素值
    • **a才是首个元素值
  3. sizeof差异:

    sizeof(a)     // 48字节(整个数组)
    sizeof(a[0])  // 16字节(一行)
    sizeof(*a)    // 16字节(同上一行)
    
6. 终极理解技巧
  1. 类型观察法:
    • 看到int (*)[4]→行指针
    • 看到int *→列指针
  2. 星号(*)法则:
    • 每出现一个*抵消一个[]
    • a[i][j]*(*(a+i)+j)
  3. 地址算术口诀:
    • “行跳行大小,列跳元素大小”
http://www.xdnf.cn/news/549235.html

相关文章:

  • VUE3 中的 ResizeObserver 警告彻底解决方案
  • C#:多线程Task使用
  • c++使用protocol buffers
  • JS实现古诗竖排从右至左
  • 谈谈jvm的调优思路
  • c++学习方向选择说明
  • [软件工程]第二章题目汇总
  • MySQL 8.0窗口函数详解
  • 48、c# 中 IList 接⼝与List的区别是什么?
  • Gin--Blog项目-flags文件解析
  • RK3576 Android 14.0 SDK开发指南(第一集)
  • 丝杆升降机在锂电行业的自动化应用有什么?
  • Unity-编辑器扩展
  • 2025年护网行动蓝队防御全解析:构建智能动态防御体系
  • Raft算法学习(1)博士论文大纲
  • Go学习教程(附电子书资料)
  • 桥梁凝冰在线监测装置:科技守护道路安全的新防线
  • Python入门手册:Python简介,什么是Python
  • C++之fmt库介绍和使用(2)
  • GPS模块_亿佰特E108-GN04D_u-center2调试
  • Linux:面试题
  • CAU数据库class3 关系型数据库基础
  • WebSocket心跳机制
  • 85.评论日记
  • 【C++算法】69.栈_验证栈序列
  • C++类与对象--7 特性三:多态
  • # YOLOv5:目标检测的新里程碑
  • 日语学习-日语知识点小记-构建基础-JLPT-N4阶段(25):受身形(3)
  • GitHub 自动认证教程
  • cv2.VideoWriter_fourcc(*‘mp4v‘)生成的视频无法在浏览器展