第13讲:深入理解指针(3)——数组与指针的“深度绑定”
🧭 第13讲:深入理解指针(3)——数组与指针的“深度绑定” 🔗📊
指针进阶:揭开数组名的神秘面纱,掌握传参本质与指针数组魔法!
📚 目录
- 数组名的理解:是地址,但不止是地址 🧩
- 使用指针访问数组:指针即数组,数组即指针 ✨
- 一维数组传参的本质:形参写数组,实为指针 🔁
- 冒泡排序:指针视角下的“相邻较量” 🔄
- 二级指针:指针的“地址管理员” 🏢
- 指针数组:存放指针的“容器” 🧰
- 指针数组模拟二维数组:非连续的“矩阵” 📐
- 学习收获总结 ✅
数组名的理解:是地址,但不止是地址 🧩
🔍 数组名 = 首元素地址?
在指针访问数组时,我们常写:
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0]; // 获取首元素地址
但其实,数组名本身就是首元素的地址!
#include <stdio.h>
int main() {int arr[10] = {1,2,3,4,5,6,7,8,9,10};printf("&arr[0] = %p\n", &arr[0]); // 输出地址printf("arr = %p\n", arr); // 输出相同地址return 0;
}
✅ 输出结果一致 → 证明 arr == &arr[0]
⚠️ 矛盾出现:sizeof(arr)
为何是 40?
printf("%d\n", sizeof(arr)); // 输出 40(int占4字节 × 10)
如果 arr
是地址,应输出 4 或 8(指针大小),为何是 40?
🎯 关键突破:
数组名是首元素地址 ✅
但有两个例外!
📌 两大例外:sizeof
与 &
中的数组名
场景 | 数组名含义 | 说明 |
---|---|---|
sizeof(arr) | 表示整个数组 | 计算数组总大小(字节) |
&arr | 表示整个数组的地址 | 类型为 int(*)[10] ,指向整个数组 |
🌟 重要区别:
&arr
和&arr[0]
虽值相同,但类型不同,导致指针运算结果不同!
printf("&arr[0] = %p\n", &arr[0]); // 首元素地址
printf("&arr[0]+1 = %p\n", &arr[0]+1); // +1 → 跳过1个int(+4字节)
printf("arr = %p\n", arr); // 首元素地址
printf("arr+1 = %p\n", arr+1); // +1 → 跳过1个int(+4字节)
printf("&arr = %p\n", &arr); // 整个数组地址
printf("&arr+1 = %p\n", &arr+1); // +1 → 跳过整个数组(+40字节)
📌 结论:
&arr[0]
和arr
:类型为int*
,+1 跳 4 字节&arr
:类型为int(*)[10]
,+1 跳 40 字节(整个数组)
✅ 记忆口诀:
“普通使用是首址,sizeof
和&
是整体”
使用指针访问数组:指针即数组,数组即指针 ✨
🔄 指针遍历数组
#include <stdio.h>
int main() {int arr[10] = {0};int sz = sizeof(arr)/sizeof(arr[0]);int *p = arr; // p 指向首元素// 输入for(int i = 0; i < sz; i++) {scanf("%d", p + i); // 等价于 &arr[i]}// 输出for(int i = 0; i < sz; i++) {printf("%d ", *(p + i)); // 等价于 arr[i]}return 0;
}
📌
p + i
:指针偏移i
个元素,指向arr[i]
🎯 p[i]
真的能访问数组吗?
// 将 *(p+i) 改为 p[i]
printf("%d ", p[i]); // ✅ 完全合法!
🌟 核心原理:
p[i]
在编译器中被解释为*(p + i)
- 同理,
arr[i]
等价于*(arr + i)
✅ 结论:
数组下标访问本质上是指针运算!
arr[i]
和p[i]
在语法上完全等价。
一维数组传参的本质:形参写数组,实为指针 🔁
❓ 问题:函数内能否求出数组长度?
void test(int arr[]) {int sz2 = sizeof(arr) / sizeof(arr[0]);printf("sz2 = %d\n", sz2); // ❌ 输出 1 或 2(指针大小 / int大小)
}int main() {int arr[10] = {1,2,3,4,5,6,7,8,9,10};int sz1 = sizeof(arr)/sizeof(arr[0]); // ✅ 输出 10test(arr);return 0;
}
🤔 为何
sz2
不是 10?
🔍 数组传参的本质
- 传递的是数组名 → 即首元素地址
- 函数形参接收的是指针,不是整个数组
void test(int arr[]) // 等价于 int* arr
{printf("%d\n", sizeof(arr)); // 输出指针大小(4或8)
}void test(int* arr) // 完全等价
{printf("%d\n", sizeof(arr)); // 输出指针大小
}
📌 本质:
一维数组传参时,形参无论写成int arr[]
还是int* arr
,都退化为指针。
✅ 正确传参方式
void func(int* arr, int sz) {// 使用 arr 和 sz 操作数组for(int i = 0; i < sz; i++) {printf("%d ", arr[i]);}
}
🎯 总结:
一维数组传参,必须额外传递数组长度!
冒泡排序:指针视角下的“相邻较量” 🔄
🔄 基础版本
void bubble_sort(int arr[], int sz) {for(int i = 0; i < sz - 1; i++) {for(int j = 0; j < sz - i - 1; j++) {if(arr[j] > arr[j+1]) {int tmp = arr[j];arr[j] = arr[j+1];arr[j+1] = tmp;}}}
}
📌 核心思想:两两相邻元素比较,大者后移。
🚀 优化版本:提前终止
void bubble_sort(int arr[], int sz) {for(int i = 0; i < sz - 1; i++) {int flag = 1; // 假设已有序for(int j = 0; j < sz - i - 1; j++) {if(arr[j] > arr[j+1]) {flag = 0; // 发生交换,说明无序int tmp = arr[j];arr[j] = arr[j+1];arr[j+1] = tmp;}}if(flag) break; // 未发生交换,提前结束}
}
✅ 优势:已有序时提前退出,提升效率。
二级指针:指针的“地址管理员” 🏢
🧩 什么是二级指针?
指针变量也有地址,存放指针变量地址的指针,就是二级指针。
int a = 10;
int *pa = &a; // pa 存放 a 的地址
int **ppa = &pa; // ppa 存放 pa 的地址
📌 类型:
pa
类型:int*
ppa
类型:int**
🔄 二级指针的运算
表达式 | 含义 |
---|---|
*ppa | 解引用 → 得到 pa (等价于 pa ) |
**ppa | 两次解引用 → 得到 a 的值 |
**ppa = 20; // 等价于 a = 20
*ppa = &b; // 等价于 pa = &b
🎯 应用场景:修改指针本身(如动态内存分配、链表操作)。
指针数组:存放指针的“容器” 🧰
🧩 指针数组 vs 整型数组
数组类型 | 存放内容 | 示例 |
---|---|---|
整型数组 int arr[3] | 整数 | {1, 2, 3} |
指针数组 int* parr[3] | 指针(地址) | {&a, &b, &c} |
int a = 1, b = 2, c = 3;
int* parr[3] = {&a, &b, &c}; // 每个元素是一个 int* 指针
📌 本质:
指针数组是一个数组,其每个元素都是指针类型。
指针数组模拟二维数组:非连续的“矩阵” 📐
🔄 模拟代码
#include <stdio.h>
int main() {int arr1[] = {1,2,3,4,5};int arr2[] = {2,3,4,5,6};int arr3[] = {3,4,5,6,7};int* parr[3] = {arr1, arr2, arr3}; // 指针数组,指向三个一维数组for(int i = 0; i < 3; i++) {for(int j = 0; j < 5; j++) {printf("%d ", parr[i][j]); // ✅ 可以像二维数组一样访问}printf("\n");}return 0;
}
✅ 输出:
1 2 3 4 5 2 3 4 5 6 3 4 5 6 7
⚠️ 与真正二维数组的区别
特性 | 真正二维数组 int arr[3][5] | 指针数组模拟 |
---|---|---|
内存布局 | 连续存储 | 每行可能不连续 |
初始化 | 静态分配 | 需先定义每行数组 |
灵活性 | 固定大小 | 可动态调整每行大小 |
📌 优势:
指针数组可实现不规则二维数组(如每行长度不同)。
✅ 学习收获总结
技能 | 掌握情况 |
---|---|
✅ 理解数组名的本质及两大例外 | ✔️ |
✅ 掌握指针与数组的等价访问方式 | ✔️ |
✅ 理解一维数组传参的指针本质 | ✔️ |
✅ 能实现并优化冒泡排序算法 | ✔️ |
✅ 理解二级指针的概念与应用 | ✔️ |
✅ 区分指针数组与数组指针 | ✔️ |
✅ 会使用指针数组模拟二维数组 | ✔️ |
🎯 数组与指针的深度绑定,是C语言高效与灵活的基石。
你已掌握其核心逻辑,继续前行,迈向指针的更高境界!💪🔥
💬 需要本讲的 指针数组练习题 / 冒泡排序动画解析 / 二级指针实战案例?欢迎继续提问,我为你准备!