指针:C 的灵魂,也是新手的鬼门关
别怕,指针没你想的那么玄。变量住在内存的某个房间里,每个房间有个门牌号(地址)。指针就是存门牌号的变量。你拿着门牌号,就能直接找到那个房间、读写里面的东西。这一节慢一点读,它值回整个 C 教程的票价。
论两个符号:& 是门牌号,* 是开门进去
&变量:"这个变量住几号房间?"——取地址。
*指针:"按这个门牌号,进去拿里面的东西。"——解引用。
一句话:& 和 * 是一对反操作。*(&x) 又变回了 x。
第一个指针:看它怎么指来指去
运行结果(地址值每次不同,别纠结具体数字)
指针与数组:数组名就是首元素的地址
这是 C 里最省墨的一个设计:数组名 arr 等于 &arr[0]。于是 arr[i] 和 *(arr + i) 是一模一样的东西。指针做加减法,会自动按"所指类型的大小"跳格——p++ 对 int 指针就是往前走 4 字节。
用指针遍历数组
指针与函数:为什么交换两个数要用指针
C 的函数参数全是"值传递"——传进去的只是副本。你在函数里改副本,外面的原件纹丝不动。想让函数真的改外面的变量,就得传它的地址进去。
经典例子:交换两个数
指针家族:指针数组、多级指针、函数指针
| 写法 | 是什么 |
|---|---|
int *p | 一个指向 int 的指针。 |
int *arr[5] | 指针数组:5 个指针组成的数组。 |
int **p | 二级指针:指向"指针"的指针。常用于函数里想改外面那个指针本身。 |
char *s = "hi" | 指向字符串常量的指针。注意:指向的内容只读,别去写它。 |
int (*fp)(int,int) | 函数指针:指向一个"收两个 int、返回 int"的函数。回调函数的基础。 |
NULL | 空指针,表示"哪儿也不指"。拿到指针先判 NULL 再用。 |
函数指针:把函数当参数传来传去(回调的雏形)
运行结果
① 空指针 NULL:还没让它指到有效内存,就直接 *p,程序当场段错误。
② 野指针:指针没初始化(值是随机垃圾),你不知道它指向哪。
③ 悬空指针:它指向的内存已经被 free 释放了,你还拿着旧地址去用。
保命口诀:指针声明时顺手置 NULL;free 之后马上置 NULL;用之前先判 if (p != NULL)。
void 指针与 const 指针:两种容易搞混的修饰
void *(无类型指针)是"通用门牌号":不知道(也不关心)后面指的是什么类型。malloc 返回的就是 void *,赋值时自动转成对应类型。你不能直接 *p 解引用 void*(编译器不知道要读几个字节),得先强转。
void*:通用指针,传参时不知道具体类型也能收
const 指针三兄弟是面试高频,区分诀窍:看 const 离谁近、读的时候从右往左读。
| 写法 | 含义(读从右往左) |
|---|---|
const int *p 或 int const *p | 指向"const int"的指针:不能通过 p 改所指的值,但 p 本身可以改指向别处。 |
int * const p | p 是个"const 指针":指针本身不能改指向,但可以通过它改所指的值。 |
const int * const p | 两头 const:既不能改所指的值,p 也不能改指向。只读死路一条。 |
指针运算别忘了跨元素:p + 1 不是地址加 1 字节,而是跳下一个元素——实际字节数是 1 * sizeof(元素类型)。int *p; p+1 实际加了 4 字节(int 占 4)。这就是为什么指针和数组那么合拍:p[i] 等价于 *(p+i)。