很多初学者学C语言学到指针这块,直接懵了。*p、&a、int *p,一个星号一个and符号,位置变一下意思全变。今天一次性把它说清楚,看完你也能写出一手流畅的指针代码。

先记住一句话:指针就是地址。

你不是在操作什么神秘的东西,就是在操作内存地址。变量存数据,指针存地址。就这么简单。

&:取地址符——“你家门牌号是多少”

& 的作用只有一个:获取一个变量在内存中的地址

int a = 42;
printf("%p", &a);  // 打印变量a的内存地址,比如 0x7ffee4d2a8bc

一个变量有3个属性:名字类型。a这个名字是给人看的,CPU根本不认识,CPU只认地址。&a就是把变量a的“门牌号”取出来。

什么时候用&?当你需要把一个变量的地址交给某个函数或者存到指针变量里的时候。

int a = 10;
int *p = &a;  // p里面存的就是a的地址

这时p就是“存储地址的变量”,它指向a。

*:解引用符——“按门牌号找到那户人家”

* 的作用是:从指针里读出地址,然后去那个地址找数据。

int a = 42;
int *p = &a;
printf("%d", *p);  // 打印42

*p的意思是:“p里面存着一个地址,你帮我到那个地址去,把里面存的数据读出来。”

*也可以往地址里写入数据:

*p = 100;  // 把a的值改成了100
printf("%d", a);  // 打印100

没用到a这个名字,直接通过地址把a给改了。

星号出现在哪,意思都不一样

初学指针最晕的,就是*在三个地方出现,意思完全不同:

1. 声明指针变量:int *p;

int *p;  // 声明一个指向int的指针变量p

声明时的*是告诉编译器“我要的是一个指针”,不是用来读数据的。int *p和int* p和int * p都对,但推荐写成int *p,强调“p是一个指针,指向int”。

2. 解引用读/写数据:*p = 100;

*p = 100;   // 往p指向的地址写数据
int b = *p; // 从p指向的地址读数据

执行时的*是操作符,意思是“去那个地址干活”。

3. 声明并初始化:int *p = &a;

int a = 10;
int *p = &a;  // p指向a

这里的*是声明,&a是把a的地址取出来放进去。

指针到底有什么用?

场景1:函数想修改外部变量

在C语言里,函数参数是传值的,复制的副本,外面动不了。

void swap_bad(int a, int b) {
    int temp = a;
    a = b;
    b = temp;  // 外面毫无变化
}

void swap_good(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;  // 直接修改外面变量的值
}

调用时:swap_good(&x, &y);,把x和y的地址传进去,函数就能动手改了。

场景2:函数要“返回”多个值

C语言函数只能返回一个值。想返回多个,就得靠指针。

void get_stats(int *sum, float *avg) {
    *sum = 100;
    *avg = 25.5f;
}

int main() {
    int s;
    float a;
    get_stats(&s, &a);
    // s=100, a=25.5
}

场景3:操作动态内存

malloc出来的内存没有名字,只有地址,只能用指针去操作。

int *arr = (int*)malloc(10 * sizeof(int));
arr[0] = 100;  // 等价于 *(arr + 0) = 100
free(arr);

野指针:新手最容易翻的坑

指针只声明没赋值,里面存的是个随机的地址,不能直接用

int *p;   // 野指针,不知道指向哪
*p = 10;  // 大概率段错误(Segmentation Fault)

正确的做法:要么给它赋一个合法的地址,要么赋NULL。

int a;
int *p = &a;  // 指向a
// 或者
int *p = NULL;  // 指向空,解引用前先判空

总结:一张图搞定

写法

含义

int *p;

声明一个指向int的指针变量p(编译器语境)

p = &a;

取变量a的地址,存进p里

*p = 100;

到p指向的地址去,把100写进去

int b = *p;

到p指向的地址去,把数据读出来给b

&p

取指针变量p自己的地址(它在内存中也有自己的位置)

核心就这三条:

  1. &是取地址,把一个变量的门牌号拿出来。
  2. *在声明里(类型后面)是指针的标志。
  3. *在执行里(变量名前)是解引用,拿着门牌号去敲门。

以后看到*,先问自己一句:“这里是声明还是操作?” 瞬间就能分清。