指针与变量:从基础到进阶

指针这东西,说白了就是地址。我刚开始学C语言时,总觉得指针很神秘,像是某种黑魔法。后来做嵌入式开发,调试过几次野指针导致的死机后,才真正理解了它。嗯,今天我们就从最基础的开始,一步步把指针讲透。

一、指针指向普通变量

每个变量在内存中都有个地址。指针变量就是用来存这个地址的。你可以把它想象成门牌号——变量是房子,指针就是写着地址的小纸条。

int a = 10;      // 普通变量
int *p = &a;     // 指针p指向变量a的地址

这里有个关键点:&是取地址运算符,*是解引用运算符。我个人习惯把int *p读作「p是一个指向int的指针」。声明时写int* p还是int *p?其实都可以,我更喜欢后者,因为*p本身就是一个int,语义更清晰。

核心理解:

  • p 存的是地址(比如 0x7fff1234)
  • *p 是取这个地址里的值(也就是 10)
  • &a 是变量a的地址

二、通过指针修改变量值

指针最大的用处之一,就是间接修改变量。你想想看,函数传参时如果传的是值,改的是副本;传指针,才能改原件。

void change_value(int *p) {
    *p = 100;  // 通过指针修改原变量
}

int main() {
    int a = 10;
    change_value(&a);
    printf("%d\n", a);  // 输出 100
    return 0;
}

我在项目中遇到过一个问题:有个同事在中断服务函数里直接修改全局变量,结果数据总是不对。后来发现是编译器优化导致的——变量被优化到了寄存器里。解决办法就是传指针,用volatile修饰。嗯,这是题外话,但说明指针在底层开发中真的很重要。

避坑指南:我曾经在写链表操作时,忘记对空指针做判断,结果程序跑飞了。记住:使用指针前,一定要检查是否为NULL。

三、指针的指针(二级指针)

二级指针,就是指向指针的指针。为什么要用二级指针?最常见的场景是:你想在函数里修改传入的指针本身,而不仅仅是指针指向的值。

void alloc_memory(int **pp) {
    *pp = (int *)malloc(sizeof(int));
    **pp = 42;
}

int main() {
    int *p = NULL;
    alloc_memory(&p);  // 传的是指针的地址
    printf("%d\n", *p); // 输出 42
    free(p);
    return 0;
}

你看,int **pp 读作「pp是一个指向int指针的指针」。*pp 得到的是p本身(一个int*),**pp 才是最终的值。说白了,就是多了一层间接引用。

我个人习惯在链表、树等数据结构中大量使用二级指针。比如删除链表节点时,用二级指针可以避免处理头节点的特殊情况,代码更简洁。

注意:二级指针容易让人晕。我建议你画个内存图,把变量、指针、二级指针的关系画清楚。纸上画一遍,比看十遍代码都管用。

四、void* 泛型指针

void* 是C语言里的「万能指针」。它可以指向任何类型的数据,但不能直接解引用——因为编译器不知道它指向的是什么类型。

int a = 10;
double b = 3.14;
void *vp;

vp = &a;      // 指向int
// printf("%d\n", *vp); // 错误!不能直接解引用
printf("%d\n", *(int*)vp); // 先转成int*再解引用

vp = &b;      // 指向double
printf("%f\n", *(double*)vp); // 先转成double*

void* 最常见的用途是实现泛型函数。比如标准库里的memcpyqsort,参数都是void*。我在写通用链表、通用队列时也经常用。

关键点:

  • void* 可以接收任何类型的指针
  • 使用前必须强制转换成具体类型
  • 不能对void*做指针运算(比如 p++)

为什么会这样?因为指针运算依赖于类型大小。void* 不知道指向的对象有多大,自然没法做加减。你想想看,如果不知道每个元素占几个字节,p++该跳多远?

五、知识体系总览

下面这张图总结了本章的核心内容。我建议你把它保存下来,复习时看一眼就能回忆起全貌。

指针与变量知识体系 指针基础 指向普通变量 int *p = &a; 通过指针修改值 *p = 100; 二级指针 int **pp = &p; void* 泛型指针 void *vp = &a; 核心:指针存地址,解引用取值。二级指针多一层间接,void* 万能但需强转。 使用前检查NULL,避免野指针。

六、总结与建议

指针这东西,光看是学不会的。我建议你打开编译器,把上面的代码都敲一遍。改改值,打印地址,看看内存里到底发生了什么。

记住几个要点:

  • 指针变量本身也有地址(可以用&取)
  • 声明时的*和使用的*含义不同——声明时表示「这是一个指针」,使用时表示「解引用」
  • 二级指针在需要修改指针本身时用,比如动态分配内存
  • void* 是工具,用好了能写出通用代码,用不好容易出bug

嗯,今天就到这里。下一章我们会聊指针和数组的关系——这两个东西在C语言里纠缠不清,搞懂了它们,你的C语言水平会上一个大台阶。


公众号:蓝海资料掘金营,微信 deep3321