C语言篇:指针全面讲解
指针是C语言的灵魂,也是最容易让人困惑的部分。
掌握指针,就等于掌握了C语言的“内存操控权”和“地址级编程能力”。
下面从零到深入,系统地把指针讲透。
1. 指针到底是什么?
最核心一句话:
指针是一个变量,它的值是另一个变量的内存地址。
int a = 10; // a 是一个普通整型变量
int *p; // p 是一个指针变量,专门用来存地址
p = &a; // 把 a 的地址赋给 p
画图理解:
内存地址 内容
0x7ffee4a0 ←─ a 的地址
│
10 ←─ a 的值
↑
p ←─ p 存的是 0x7ffee4a0
所以:
&a→ 取a的地址*p→ 解引用(通过地址找到里面的值)
printf("%d\n", a); // 10
printf("%d\n", *p); // 10
printf("%p\n", &a); // 0x7ffee4a0(地址)
printf("%p\n", p); // 0x7ffee4a0(地址)
printf("%p\n", &p); // p 自己的地址(指针变量也占内存)
2. 指针的声明方式(非常重要!)
int *p; // p 是一个指向 int 的指针
double *q; // q 指向 double
char *str; // str 指向 char(最常见用于字符串)
int **pp; // pp 是一个指向指针的指针(二级指针)
int *arr[10]; // 指针数组:10个指向int的指针
int (*ptr)[10]; // 数组指针:指向一个有10个int的数组
最容易写错的几种写法对比:
int *p1, p2; // p1是指针,p2是普通int ← 陷阱!
int* p1, p2; // 同上,还是只有p1是指针
int *p1, *p2; // 正确,两个都是指针
推荐写法(清晰):
int* p;
int* q;
3. 指针最常见的六大使用场景
| 序号 | 场景 | 典型代码示例 | 核心目的 |
|---|---|---|---|
| 1 | 修改函数外部变量 | void swap(int *a, int *b) | 传址调用,实现“真交换” |
| 2 | 动态内存分配 | int *p = malloc(n * sizeof(int)); | 运行时决定大小的数组 |
| 3 | 操作数组(最经典用法) | *(arr + i) 等价于 arr[i] | 数组名就是首元素地址 |
| 4 | 字符串处理 | char *s = "hello"; 或 char str[]="hi"; | 字符串本质是char指针 |
| 5 | 多级指针(链表、树等) | struct Node **head | 修改链表头指针 |
| 6 | 函数指针 | int (*func)(int, int); | 回调、策略模式、qsort等 |
4. 指针与数组(最容易混淆的部分)
核心口诀:
- 数组名在多数情况下会隐式转换为指向首元素的指针
- 但数组名本身不是指针,它是一个常量地址
int arr[5] = {10,20,30,40,50};
int *p = arr; // 正确,等价于 &arr[0]
int *q = &arr; // 错误!类型不匹配
int (*r)[5] = &arr; // 正确,数组指针
printf("%p\n", arr); // 首元素地址
printf("%p\n", &arr); // 整个数组的地址(数值相同,但类型不同)
printf("%zu\n", sizeof(arr)); // 20(5×4)
printf("%zu\n", sizeof(p)); // 4或8(指针大小)
指针运算规则(非常重要):
int arr[5];
int *p = arr;
p++; // 指向 arr[1],地址 + sizeof(int)
p += 2; // 指向 arr[3]
*(p + 3) = 99; // 等价于 arr[6] = 99(越界!危险!)
5. 指针与字符串(经典用法)
// 三种常见写法对比
char str1[] = "hello"; // 数组,内容可改,大小固定
char *str2 = "hello"; // 指针,指向常量区,不可修改内容(C标准中未定义行为)
const char *str3 = "hello"; // 推荐写法:指向字符串常量
str1[0] = 'H'; // 可以
// str2[0] = 'H'; // 不安全!可能段错误
动态字符串推荐做法:
char *s = malloc(100);
strcpy(s, "hello world");
...
free(s); // 千万别忘!
6. const 与指针(面试高频)
const 修饰的位置不同,含义完全不同:
| 写法 | 含义 | 谁不能改 |
|---|---|---|
| const int *p | 指向的内容不能改(指针常量) | *p |
| int * const p | 指针本身不能改(常量指针) | p |
| const int * const p | 指针和内容都不能改 | p 和 *p |
| int const *p | 同 const int *p | *p |
记忆口诀:const 离谁近,谁就不能改
7. 多级指针(链表修改头节点经典案例)
void insert_head(struct Node **head, int val) {
struct Node *new_node = malloc(sizeof(struct Node));
new_node->data = val;
new_node->next = *head;
*head = new_node; // 修改主函数中的头指针
}
调用:
struct Node *list = NULL;
insert_head(&list, 100); // 要传 &list(地址的地址)
8. 函数指针(高级用法)
// 定义一个函数指针类型
typedef int (*Operation)(int, int);
// 函数实现
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
// 使用
Operation op = add;
printf("%d\n", op(10, 3)); // 13
op = sub;
printf("%d\n", op(10, 3)); // 7
经典应用:qsort、信号处理、回调函数、状态机等。
9. 指针常见错误与防御性写法
| 错误类型 | 典型代码 | 后果 | 防御写法 |
|---|---|---|---|
| 野指针 | int *p; *p = 100; | 段错误 | 初始化为 NULL |
| 悬空指针 | free(p); *p = 10; | 未定义行为 | free后立刻 p = NULL |
| 越界访问 | int a[5]; a[10] = 1; | 内存破坏 | 严格控制下标 |
| 返回局部变量地址 | return &local_var; | 悬空指针 | 不要返回栈上地址 |
| 重复free | free(p); free(p); | 崩溃 | free后置NULL,检查是否NULL再free |
总结:指针核心思维导图
指针
├── 概念:存地址的变量
├── 声明:类型 *变量名
├── 运算:& 取地址 * 解引用 ++ -- + -(按类型大小移动)
├── 与数组:数组名→首元素地址 指针可当数组用
├── const位置决定谁不可改
├── 多级指针:修改指针本身(链表头、函数参数)
├── 函数指针:回调、动态行为
└── 安全原则:
• 初始化为NULL
• 使用前判空
• free后置空
• 避免返回局部变量地址
一句话总结:
指针就是让你可以直接“隔着地址操作内存”的能力。
它强大、灵活,但也危险。写C程序时,对指针的每一次操作都要心怀敬畏。
有哪部分还想再深入(比如链表实现、函数指针数组、void*用法、const正确用法等),可以继续问,我继续给你拆得更细!