一、指针就是地址:理解内存地址、指针变量的本质、取地址符&与解引用符*、指针的声明与初始化

好,咱们正式开始。第一章,我打算从最根本的地方讲起——指针到底是什么

很多初学者一听到「指针」两个字就头大。其实你把它想简单点:指针就是地址。就这么回事。

你想想看,我们写程序时定义的每个变量,最终都要放到内存里。内存就像一长排带编号的柜子,每个柜子有一个唯一的编号——那就是内存地址。指针,就是用来存放这个编号的变量。

核心一句话:指针变量存的是地址,不是普通的值。

1.1 内存地址——柜子编号

我刚开始学C语言时,总觉得「地址」这东西很抽象。后来我换了个思路:把内存想象成一个大旅馆,每个房间都有门牌号。这个门牌号,就是地址。

举个例子:

int a = 10;

这行代码做了两件事:

  • 在内存中找一块空闲的「房间」(4字节,因为int通常占4字节)
  • 把数字10放进去

这个房间的门牌号是多少?我们可以用 &a 把它取出来。

printf("变量a的地址是:%p\n", &a);

输出可能是 0x7ffd5a3b4c2c 这样的十六进制数。这就是a在内存中的「门牌号」。

小提示:每次运行程序,地址都可能不同。这是现代操作系统的地址空间随机化机制,正常现象。

1.2 指针变量——专门存地址的变量

普通变量存数字、存字符。指针变量呢?它专门存地址

声明一个指针变量很简单:

int *p;

这里的 * 不是解引用,而是告诉编译器:我要声明一个指针变量,它将来要存放一个int类型变量的地址。

我个人习惯把 * 紧贴着变量名写:int *p;。也有人喜欢贴着类型写:int* p;。两种写法都对,但要注意一个坑:

int* p, q;   // p是指针,q是普通int变量!

嗯,这里要小心。如果你想让两个都是指针,得这样写:

int *p, *q;

1.3 取地址符&——拿到门牌号

& 运算符的作用很简单:取出变量的地址

int a = 10;
int *p = &a;   // 把a的地址存到指针p中

现在,p里面存的就是a的门牌号。你可以说:p指向a

我在项目中遇到过一个问题:有人对数组名取地址,结果把自己绕晕了。数组名本身就是一个地址,但 &数组名 取出来的是整个数组的地址,类型不一样。这个后面会细讲,先留个印象。

1.4 解引用符*——通过门牌号进房间

有了地址,怎么拿到里面的值?用 * 运算符。

这叫解引用,说白了就是:拿着地址,去那个房间看看里面放了什么

int a = 10;
int *p = &a;

printf("%d\n", *p);   // 输出 10
*p = 20;              // 通过指针修改a的值
printf("%d\n", a);    // 输出 20

看到了吗?*p 就相当于 a。你通过指针,间接地操作了a这个变量。

警告:解引用一个未初始化的指针,后果很严重!

int *p;   // 野指针!
*p = 10;  // 崩溃,或者更糟——静默破坏内存

我曾经在调试一个嵌入式程序时,花了整整两天才找到bug——就是一个野指针在偷偷改写关键数据。从那以后,我声明指针时一定会初始化。

1.5 指针的声明与初始化——规矩要立好

指针的声明格式:

类型 *变量名;

类型决定了:当你解引用这个指针时,从哪个地址开始读多少个字节

指针类型 解引用读取的字节数
char * 1 字节
int * 4 字节(通常)
double * 8 字节

初始化有三种常见方式:

  1. 用已有变量的地址初始化
int a = 5;
int *p = &a;
  1. 用动态分配的内存初始化
int *p = (int *)malloc(sizeof(int));
if (p != NULL) {
    *p = 100;
}
  1. 初始化为NULL
int *p = NULL;   // 指向空,表示哪儿也不指

我的习惯:声明指针时立刻初始化。如果暂时没有合适的地址,就赋NULL。这样至少解引用时能快速崩溃,而不是静默地破坏数据。

1.6 一张图理清关系

下面这张SVG图,把变量、地址、指针的关系画清楚了。

指针与地址关系示意图 变量 a 值: 10 地址: 0x7ffd5a3b4c2c 指针变量 p 存的是地址 内容: 0x7ffd5a3b4c2c 指向 *p → 得到 10 p = &a; // p存的是a的地址 *p = 20; // 通过p修改a的值

1.7 避坑指南——我踩过的雷

讲几个我亲身经历的教训:

  • 野指针:声明了指针没初始化,就直接解引用。程序可能当时没事,但某个深夜突然崩溃。我曾经在调试一个电机控制程序时,就是被野指针坑了三天。
  • 类型不匹配int *p = &a; 没问题,但 double *p = &a; 编译器会警告。别忽略这个警告!解引用时会读错字节数。
  • 对NULL解引用int *p = NULL; *p = 10; 必崩。所以解引用前养成检查的习惯:if (p != NULL) { ... }

记住:指针本身也是一个变量,它也有自己的地址。你可以对指针变量取地址:&p。但别搞混了——p 存的是别人的地址,&p 是它自己的门牌号。

1.8 小结

这一章的核心就三句话:

  • 指针就是地址,指针变量就是用来存地址的变量。
  • & 取地址,* 解引用——一个拿门牌号,一个进门看东西。
  • 声明指针时一定要初始化,别让它成为野指针。

嗯,第一章就到这儿。这些东西看着简单,但基础打牢了,后面讲数组、函数指针、多级指针时你才不会晕。下一章我们聊聊指针的运算——说白了就是地址怎么加减,以及数组和指针之间那点「暧昧」的关系。


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