1. 指针是什么:内存地址、指针变量、取地址符&、解引用符*、指针的声明与初始化
好,咱们正式开始。指针这玩意儿,我当年学的时候也绕了好一阵子。说白了,它就是C语言的灵魂。你如果搞不懂指针,写出来的代码就像没装引擎的车——看着挺完整,跑不起来。
今天这一章,咱们先把最基础的概念掰扯清楚。别急,一步一步来。
1.1 内存地址:你的数据住在哪
先问个问题:你定义一个变量 int a = 10;,这个 a 到底存在哪?
答案是——内存里。内存就像一栋巨大的公寓楼,每个房间都有一个门牌号。这个门牌号,就是内存地址。
每个变量住进这栋楼,都会分到一个房间。比如 a 住在 0x7ffeefbff5a8 这个房间。你通过变量名 a 能找到它,但更底层的方式,是通过地址直接找到它。
核心概念:内存地址是内存中每个字节的唯一编号。它是数据在物理内存中的“坐标”。
我记得刚入行时,有个同事调试一个野指针问题,查了三天没查出来。最后发现是地址计算错了,偏移了一个字节。嗯,从那以后,我对地址的敏感性就刻进骨子里了。
1.2 指针变量:专门存地址的变量
既然地址是个数字,那能不能用一个变量来存它?当然可以。这种专门用来存放地址的变量,就叫指针变量。
你想想看,普通变量存的是值(比如10、3.14、'A'),指针变量存的是地址(比如0x7ffeefbff5a8)。
int a = 10; // 普通变量,存值
int *p = &a; // 指针变量,存地址
这里 p 就是一个指针变量,它里面装的是 a 的地址。换句话说,p 指向了 a。
个人习惯:我写指针声明时,喜欢把 * 紧贴变量名,比如 int *p;。这样读起来更自然——"p是一个指针,指向int类型"。当然也有人喜欢 int* p;,风格问题,不纠结。
1.3 取地址符 &:找到变量的门牌号
怎么拿到一个变量的地址?用 & 运算符。它叫取地址符,作用就是获取变量在内存中的起始地址。
int b = 20;
printf("b 的地址是:%p\n", &b); // 输出类似 0x7ffeefbff5ac
%p 是专门打印地址的格式符。输出的是一个十六进制数,看起来有点吓人,其实就是一个数字而已。
我在项目中遇到过一种情况:有人把 & 用在了常量上,比如 &10,编译直接报错。为什么?因为常量没有固定的内存地址,它可能直接编码在指令里。记住:只有变量才有地址。
1.4 解引用符 *:通过地址找到值
有了地址,怎么拿到地址里存的值?用 * 运算符。它叫解引用符,作用是通过指针变量中存储的地址,访问该地址上的数据。
int c = 30;
int *p = &c; // p 存了 c 的地址
printf("%d\n", *p); // 输出 30,通过 p 找到了 c 的值
这里 *p 就是解引用。你可以把它理解成:拿着门牌号(地址),打开门,看看里面住着谁(值)。
注意:声明时的 * 和使用时的 * 是两码事。声明时 int *p; 表示 p 是一个指针变量;使用时 *p 表示解引用,访问 p 指向的值。初学者最容易搞混的就是这个。
1.5 指针的声明与初始化
指针的声明格式很简单:类型 *变量名;。类型决定了指针指向的数据类型。
int *p1; // 指向 int 的指针
char *p2; // 指向 char 的指针
double *p3; // 指向 double 的指针
void *p4; // 无类型指针(万能指针)
声明之后,指针变量里存的是什么?嗯,是随机值。如果你直接解引用一个未初始化的指针,后果很严重——轻则段错误,重则把系统搞崩。
所以,指针一定要初始化。有三种方式:
- 指向一个已有变量:
int *p = &a; - 指向动态分配的内存:
int *p = malloc(sizeof(int)); - 初始化为 NULL:
int *p = NULL;,表示指针不指向任何有效地址
int d = 100;
int *p1 = &d; // 方式1:指向已有变量
int *p2 = (int *)malloc(sizeof(int)); // 方式2:动态分配
*p2 = 200;
int *p3 = NULL; // 方式3:空指针
避坑指南:我曾经见过一个新手,声明了指针没初始化就直接用,结果程序跑起来时好时坏。查了半天,发现是指针指向了栈上的某个临时变量,那个变量被释放后,指针就成了野指针。所以我的习惯是:声明指针的同时就初始化,要么给地址,要么给 NULL。
1.6 知识体系总览
下面这张图,把本章的核心概念串起来了。你看一遍,应该就能理解指针的全貌。
1.7 一个完整的例子
说了这么多,咱们写个完整的程序看看。
#include <stdio.h>
int main() {
int num = 42;
int *ptr = # // 声明并初始化指针
printf("num 的值:%d\n", num); // 42
printf("num 的地址:%p\n", &num); // 0x...
printf("ptr 的值:%p\n", ptr); // 0x...(和上面一样)
printf("ptr 指向的值:%d\n", *ptr); // 42(解引用)
// 通过指针修改值
*ptr = 100;
printf("修改后 num 的值:%d\n", num); // 100
return 0;
}
运行结果:
num 的值:42
num 的地址:0x7ffeefbff5a8
ptr 的值:0x7ffeefbff5a8
ptr 指向的值:42
修改后 num 的值:100
看到了吗?ptr 里存的就是 num 的地址。通过 *ptr 不仅能读,还能写。这就是指针的威力——间接访问。
总结一下:
- 内存地址是数据的门牌号
- 指针变量是存门牌号的变量
&是查门牌号*是通过门牌号进门- 指针声明必须带类型,使用前必须初始化
嗯,这一章就到这。概念虽然基础,但后面的所有高级用法都建立在这上面。你把它吃透了,后面的路就好走了。
公众号:蓝海资料掘金营,微信deep3321