C语言指针详解(最清晰、最完整的入门到进阶指南)
指针是 C 语言中最强大、最重要、也最容易出错的核心特性。
一句话总结:指针就是“地址的变量”,它存储的是另一个变量(或内存块)的内存地址,通过指针可以“间接访问”和“操作”那块内存。
下面从零基础到高级,一步步把指针讲透。
1. 指针最基础的概念(必须死记)
int a = 10; // 普通变量,值是 10
int *p = &a; // p 是一个指针变量,存的是 a 的地址
// 三行最核心的理解:
printf("%d\n", a); // 输出 10 (变量的值)
printf("%p\n", &a); // 输出地址,如 0x7ffdf1234568
printf("%p\n", p); // 输出地址,和 &a 相同
printf("%d\n", *p); // 输出 10 (通过指针“解引用”得到的值)
最关键的三个符号(每天默写 10 遍,直到刻进 DNA)
| 符号 | 含义 | 读法(中文) | 英文说法 |
|---|---|---|---|
| & | 取地址运算符 | “取地址” | address-of operator |
| * | 1. 定义指针类型 2. 解引用(取值) | “指针类型” / “解引用” | pointer declaration / dereference |
| *p | 通过指针 p 访问它指向的内存的值 | “p 指向的值” | value pointed by p |
一句话记忆口诀:
& 是“去哪找”,* 是“找来干什么”
2. 指针变量的定义方式(最容易混淆的地方)
int a = 10;
int *p1 = &a; // 推荐写法:* 紧跟变量名
int* p2 = &a; // 也可以,但容易误导
int *p3, q; // p3 是指针,q 是普通 int(最常出错!)
int *p4, *p5; // p4 和 p5 都是指针(正确)
正确记忆:* 是跟变量走的,不是跟类型走的
所以永远写成 int *p 而不是 int* p,这样定义多个变量时才不会出错。
3. 指针的各种常见类型(必须掌握)
| 指针类型 | 定义写法 | 指向的内容大小(步长) | 典型用途 |
|---|---|---|---|
| 指向基本类型 | int *p | sizeof(int) 通常 4 | 操作单个 int 变量 |
| 指向数组 | int arr[10]; int *p = arr; | 同上 | 遍历数组(最常见) |
| 指针数组 | int *arr[10]; | — | 每个元素都是指针 |
| 数组指针 | int (*p)[10]; | 整个数组大小 | 指向整个数组(多维数组常用) |
| 指向函数的指针 | int (*func)(int,int); | — | 回调函数、函数表 |
| void 指针 | void *p; | 无步长(不能 ++) | 通用指针(malloc 返回的就是 void*) |
4. 指针的经典用法(面试 + 实战必会)
4.1 指针遍历数组(最常见写法)
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // 数组名就是首元素地址
for (int i = 0; i < 5; i++) {
printf("%d ", *(p + i)); // 方式1:指针 + 偏移
// 或
printf("%d ", p[i]); // 方式2:像数组一样用 [](本质相同)
// 或
printf("%d ", *p++); // 方式3:指针自增(最简洁,但注意顺序)
}
4.2 交换两个变量(不用第三变量)
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 调用
int x = 5, y = 10;
swap(&x, &y); // 必须传地址
4.3 动态内存分配(malloc / calloc / realloc / free)
#include <stdlib.h>
// 分配一个 int 大小的内存
int *p = (int *)malloc(sizeof(int));
if (p == NULL) { /* 分配失败处理 */ }
*p = 100;
// 分配 10 个 int 的数组
int *arr = (int *)malloc(10 * sizeof(int));
// 分配并清零
int *arr2 = (int *)calloc(10, sizeof(int)); // 全初始化为 0
// 扩容(很重要!)
int *new_arr = (int *)realloc(arr, 20 * sizeof(int));
if (new_arr) arr = new_arr;
// 释放(必须成对!)
free(p);
free(arr);
p = arr = NULL; // 防止野指针
现代推荐(C99+):用 sizeof(*p) 而不是 sizeof(int)
int *p = malloc(sizeof(*p)); // 更安全,类型改了也不用改代码
5. 指针最容易犯的致命错误(面试 + 生产必考)
- 解引用空指针(Segmentation fault)
int *p = NULL;
*p = 10; // 崩溃!
- 使用后释放(Use After Free)
int *p = malloc(sizeof(int));
free(p);
*p = 20; // 未定义行为!
- 重复释放(Double Free)
free(p);
free(p); // 崩溃或内存损坏
- 野指针
int *p; // 未初始化
*p = 10; // 随机地址,极度危险
- 数组越界
int arr[5];
*(arr + 10) = 999; // 越界写,破坏内存
防御式编程黄金准则:
- 所有指针初始化为 NULL
- free 后立刻置 NULL
- 使用前永远判断 !NULL
- 永远用
sizeof(*p)而不是硬编码类型
6. 面试最常考的指针笔试题(背熟这些基本能过指针关)
- 指针 + 1 到底加了多少?
int *p; // 加 4(int 4 字节)
char *q; // 加 1
double *r; // 加 8
*p++、(*p)++、++*p、p++区别- 写一个函数返回两个数的最大值和最小值(用指针)
- 实现字符串复制(不调用 strcpy)
- 用指针实现二维数组动态分配
- const 指针 vs 指针 const 的四种写法
总结:指针的本质一句话
指针就是地址的变量,通过它可以“隔空操控”内存
C 语言把内存管理权完全交给程序员,所以指针强大,但也最容易出错。
现代 C 项目中,“指针不离手,但别裸奔” —— 能用数组用数组,能用结构体用结构体,指针主要用来做参数传递、动态内存、遍历和底层操作。
想继续深入哪个指针专题?
A. 指针面试经典 20 题详解
B. const 与指针的四种组合(const int、int const 等)
C. 指针数组 vs 数组指针完整对比 + 多维数组写法
D. 动态内存分配的完整案例(链表、哈希表)
E. 野指针、悬垂指针、内存泄漏的真实案例分析
告诉我字母,我们继续深挖!