第一章:指针是什么
说实话,指针这个概念,当年我学的时候也绕了好一阵子。
很多人一听到「指针」两个字就头大,觉得是什么高深莫测的东西。其实说白了,指针就是一个普通变量——只不过它存的是地址,不是数字。
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