楼层: 首页/ 软件技术/ C 语言/ 指针:C 的灵魂,也是新手的鬼门关
4

指针:C 的灵魂,也是新手的鬼门关

Pointers — The Soul of C

别怕,指针没你想的那么玄。变量住在内存的某个房间里,每个房间有个门牌号(地址)。指针就是存门牌号的变量。你拿着门牌号,就能直接找到那个房间、读写里面的东西。这一节慢一点读,它值回整个 C 教程的票价。

论两个符号:& 是门牌号,* 是开门进去

&变量:"这个变量住几号房间?"——取地址。

*指针:"按这个门牌号,进去拿里面的东西。"——解引用。

一句话:& 和 * 是一对反操作。*(&x) 又变回了 x。

第一个指针:看它怎么指来指去

#include <stdio.h> int main() { int x = 42; int *p = &x; // p 是个指针,存的是 x 的地址(门牌号) printf("x 的值 = %d\n", x); // 42 printf("x 的地址 = %p\n", (void*)&x); // 类似 0x7ff...,一堆十六进制 printf("p 存的地址 = %p\n", (void*)p); // 和上面一样,因为 p 就是 x 的地址 printf("按 p 找进去 = %d\n", *p); // 42 —— 解引用,进去读到 x 的值 *p = 100; // 通过指针改 x!外面的 x 也变了 printf("改完后 x = %d\n", x); // 100 return 0; }

运行结果(地址值每次不同,别纠结具体数字)

x 的值 = 42 x 的地址 = 0x7ffd2a3b p 存的地址 = 0x7ffd2a3b 按 p 找进去 = 42 改完后 x = 100

指针与数组:数组名就是首元素的地址

这是 C 里最省墨的一个设计:数组名 arr 等于 &arr[0]。于是 arr[i] 和 *(arr + i) 是一模一样的东西。指针做加减法,会自动按"所指类型的大小"跳格——p++ 对 int 指针就是往前走 4 字节。

用指针遍历数组

int arr[] = {10, 20, 30}; int *p = arr; // p 指向第一个元素,等价于 &arr[0] printf("%d\n", p[1]); // 20 —— p[1] 就是 *(p+1) printf("%d\n", *(p + 2)); // 30 —— 两种写法完全等价 p++; // p 往后跳一格(一个 int 的大小),指向 20 printf("%d\n", *p); // 20

指针与函数:为什么交换两个数要用指针

C 的函数参数全是"值传递"——传进去的只是副本。你在函数里改副本,外面的原件纹丝不动。想让函数真的改外面的变量,就得传它的地址进去。

经典例子:交换两个数

// 错误版:传值,改的是副本,外面不变 void swap_bad(int a, int b) { int t = a; a = b; b = t; // 只交换了两个复印件 } // 正确版:传地址(指针),进去直接改原件 void swap(int *a, int *b) { int t = *a; // 取出 a 指向的原值 *a = *b; // 把 b 指向的值写进 a 指向的地方 *b = t; } // 调用: int x = 1, y = 2; swap(&x, &y); // 把 x、y 的地址传进去,外面的 x、y 真的被换了

指针家族:指针数组、多级指针、函数指针

写法是什么
int *p一个指向 int 的指针。
int *arr[5]指针数组:5 个指针组成的数组。
int **p二级指针:指向"指针"的指针。常用于函数里想改外面那个指针本身。
char *s = "hi"指向字符串常量的指针。注意:指向的内容只读,别去写它。
int (*fp)(int,int)函数指针:指向一个"收两个 int、返回 int"的函数。回调函数的基础。
NULL空指针,表示"哪儿也不指"。拿到指针先判 NULL 再用。

函数指针:把函数当参数传来传去(回调的雏形)

#include <stdio.h> int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; } // 这个函数收一个函数指针当参数,通用! int calc(int x, int y, int (*op)(int,int)) { return op(x, y); // 调用传进来的那个函数 } int main() { printf("%d\n", calc(3, 4, add)); // 7 printf("%d\n", calc(3, 4, mul)); // 12 return 0; }

运行结果

7 12
三种必死的指针

① 空指针 NULL:还没让它指到有效内存,就直接 *p,程序当场段错误。
② 野指针:指针没初始化(值是随机垃圾),你不知道它指向哪。
③ 悬空指针:它指向的内存已经被 free 释放了,你还拿着旧地址去用。
保命口诀:指针声明时顺手置 NULL;free 之后马上置 NULL;用之前先判 if (p != NULL)。

void 指针与 const 指针:两种容易搞混的修饰

void *(无类型指针)是"通用门牌号":不知道(也不关心)后面指的是什么类型。malloc 返回的就是 void *,赋值时自动转成对应类型。你不能直接 *p 解引用 void*(编译器不知道要读几个字节),得先强转。

void*:通用指针,传参时不知道具体类型也能收

void *p; int a = 10; p = &a; // void* 可以指向任意类型 printf("%d\n", *(int *)p); // 10:用之前先强转回 int*,再解引用 // malloc 返回 void*,赋给具体类型指针时自动转 int *arr = malloc(10 * sizeof(int)); if (arr == NULL) { /* 分配失败 */ }

const 指针三兄弟是面试高频,区分诀窍:看 const 离谁近、读的时候从右往左读。

写法含义(读从右往左)
const int *p 或 int const *p指向"const int"的指针:不能通过 p 改所指的值,但 p 本身可以改指向别处。
int * const pp 是个"const 指针":指针本身不能改指向,但可以通过它改所指的值。
const int * const p两头 const:既不能改所指的值,p 也不能改指向。只读死路一条。

指针运算别忘了跨元素:p + 1 不是地址加 1 字节,而是跳下一个元素——实际字节数是 1 * sizeof(元素类型)。int *p; p+1 实际加了 4 字节(int 占 4)。这就是为什么指针和数组那么合拍:p[i] 等价于 *(p+i)。