第5章 指针与数组
一、核心概念关系
指针与数组是C语言最密切相关的两个概念,本章揭示了它们的本质联系:
- 数组名本质上是数组首元素的常量指针
- 指针运算与数组下标访问具有等价性(
a[i]
≡*(a+i)
) - 指针提供了比数组下标更灵活的操作方式
二、指针基础深入
1. 指针声明与初始化
int *p; // 声明指向int的指针
int x = 10;
p = &x; // p指向x
*p = 20; // 通过指针修改x的值
2. 指针运算特性
- 算术运算:
p ± n
的实际地址为p ± n*sizeof(*p)
- 关系运算:比较指针在内存中的位置关系
- 减法运算:得到两指针间的元素个数(非字节数)
三、指针与数组的等价性
1. 数组访问的两种形式
int a[10];
a[3] = 5; // 数组下标形式
*(a + 3) = 5; // 指针运算形式(编译器实际处理方式)
2. 关键区别
特性 | 数组名 | 指针变量 |
---|---|---|
存储位置 | 编译时确定 | 运行时动态分配 |
可修改性 | 不可修改 | 可修改 |
sizeof结果 | 数组总字节数 | 指针大小 |
四、高级指针应用
1. 指针数组 vs 数组指针
int *p[10]; // 指针数组:10个int指针组成的数组
int (*p)[10]; // 数组指针:指向含10个int的数组的指针
2. 多维数组处理
二维数组作为参数传递的三种等价形式:
void func(int a[][10]);
void func(int (*a)[10]);
void func(int **a); // 仅当动态分配时适用
3. 命令行参数处理
int main(int argc, char *argv[]) {
// argv[0] = 程序名
// argv[1...] = 命令行参数
}
五、复杂声明解析
使用"右左法则"解析复杂指针声明:
- 从标识符开始
- 先向右看,再向左看
- 遇到括号先解析括号内
示例:
int (*(*fp)(int))[10];
// fp是指向函数的指针,该函数接受int参数,返回指向含10个int的数组的指针
六、内存管理函数
1. 动态分配函数族
void *malloc(size_t size); // 未初始化
void *calloc(size_t n, size_t size); // 初始化为0
void *realloc(void *ptr, size_t size); // 调整大小
void free(void *ptr); // 释放内存
2. 使用规范
- 检查返回值是否为NULL
- 释放后立即将指针置NULL
- 避免"悬挂指针"和"内存泄漏"
七、典型编程模式
1. 字符串处理
// 字符串复制实现
void strcpy(char *s, char *t) {
while (*s++ = *t++);
}
2. 高效数组遍历
for (p = arr; p < arr + len; p++) {
// 使用*p访问元素
}
3. 函数指针应用
int (*compare)(void *, void *); // 比较函数指针
qsort(base, n, size, compare); // 标准库排序函数
八、重要注意事项
- 指针初始化:未初始化的指针是"野指针"
- 边界检查:指针运算可能越界
- 类型匹配:避免不兼容指针类型的强制转换
- const修饰:
const int *p; // 指向常量的指针
int *const p; // 指针本身为常量
九、本章核心要点图示
+-------------------+ +-------------------+
| 指针系统 |<----->| 数组系统 |
+-------------------+ +-------------------+
^ | ^ |
| v | v
+-------------------+ +-------------------+
| 内存直接访问能力 | | 连续存储结构 |
+-------------------+ +-------------------+
本章奠定了C语言高效系统编程的基础,理解指针与数组的关系是掌握C语言内存管理和高效算法的关键。后续章节的复杂数据结构(如链表、树等)都建立在这些概念之上。