第一章:指针是什么

说实话,指针这个概念,当年我学的时候也绕了好一阵子。

很多人一听到「指针」两个字就头大,觉得是什么高深莫测的东西。其实说白了,指针就是一个普通变量——只不过它存的是地址,不是数字。

1.1 内存地址的概念

先聊聊内存。你想想看,计算机的内存就像一栋巨大的宿舍楼。

每个房间都有一个门牌号。这个门牌号,就是地址。

程序运行时,变量就住在这些房间里。比如你定义了一个 int a = 10,系统就会给 a 分配一个房间(内存单元),然后把 10 放进去。

关键点:每个内存单元都有唯一的地址。地址从 0 开始编号,用十六进制表示。

我在项目中遇到过一个问题:一个结构体变量莫名其妙被改写了。查了半天,发现是另一个地方的指针越界写到了这个变量的地址上。从那以后,我对内存地址的敏感度就高了很多。

1.2 指针变量的定义

指针变量,就是用来存地址的变量。

定义方式很简单:

int *p;      // p 是一个指针,指向 int 类型的数据
char *cp;    // cp 是一个指针,指向 char 类型的数据
float *fp;   // fp 是一个指针,指向 float 类型的数据

这里的 * 不是解引用,而是告诉编译器:我要定义一个指针变量。

个人习惯:我写指针定义时,喜欢把 * 紧挨着变量名,比如 int *p。这样读起来更自然——「p 是一个指针,指向 int」。

你可能会问:为什么要区分类型?

嗯,这个问题问得好。指针本身只存地址,地址的长度都一样(32位系统是4字节,64位系统是8字节)。但编译器需要知道指针指向的数据是什么类型,这样才能正确读写。

1.3 取地址符 &

& 的作用很简单:获取变量的地址。

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

打个比方:a 是房间里的人,&a 就是房间的门牌号。

注意:取地址符只能用于变量,不能用于常量或表达式。比如 &10 是错的,&(a + 1) 也是错的。

我曾经见过一个新手这样写:

int *p = &10;  // 编译报错

他以为这样就能让指针指向数值 10 所在的地址。但 10 是常量,没有固定的内存地址,所以不行。

1.4 解引用符 *

* 的作用正好相反:通过地址访问变量。

int a = 10;
int *p = &a;
printf("%d\n", *p);  // 输出 10
*p = 20;             // 等价于 a = 20
printf("%d\n", a);   // 输出 20

这里的 *p 就是「去 p 存的地址那里,取出里面的值」。

核心理解:

  • &a → 拿到 a 的地址
  • *p → 通过 p 里的地址,访问那个变量

说白了,&* 是互逆操作。&(*p) 等于 p,* (&a) 等于 a。

1.5 指针的声明与初始化

声明指针时,最好同时初始化。不初始化的指针,里面存的是垃圾值,用起来非常危险。

int *p = NULL;   // 安全做法:初始化为空指针
int a = 10;
p = &a;          // 再赋有效地址

避坑指南:我曾经调试过一个 bug,程序运行到一半莫名其妙崩溃。查了两天,发现是一个指针没有初始化,指向了随机地址,然后对它做了写操作。从那以后,我每个指针定义都会顺手初始化。

1.6 指针的大小

指针本身占多少字节?这取决于系统架构。

系统架构 指针大小
32 位 4 字节
64 位 8 字节

不管指针指向什么类型,它的大小都一样。因为地址的长度是固定的。

printf("%zu\n", sizeof(int *));   // 4 或 8
printf("%zu\n", sizeof(char *));  // 4 或 8
printf("%zu\n", sizeof(float *)); // 4 或 8

1.7 知识体系图

下面这张图帮你理清本章的核心逻辑:

指针是什么 内存地址 每个变量有唯一地址 十六进制表示 指针变量 存地址的变量 需指定指向类型 取地址符 & 获取变量地址 不能用于常量 解引用符 * 通过地址访问变量 可读可写 核心:& 和 * 是互逆操作

1.8 本章小结

  • 指针就是存地址的变量,没什么神秘的
  • & 取地址,* 解引用,两者互为逆操作
  • 指针定义时要初始化,避免野指针
  • 指针大小由系统架构决定,32位4字节,64位8字节

我的建议:刚开始学指针,别急着背语法。先在纸上画一画——变量在哪个房间,指针存的是哪个门牌号。画明白了,代码自然就写对了。


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