指针基础:指针的概念、定义、初始化,取地址符&与解引用符*
指针,是C语言的灵魂,也是很多人学C时第一个"劝退"点。说实话,我当年学指针时也绕了好一阵子。但一旦你理解了它背后的逻辑,你会发现——指针其实没那么玄乎,它就是一个装着地址的变量而已。
今天我们就来彻底搞懂指针的基础:它是什么、怎么定义、怎么初始化,以及那两个最关键的符号——&和*。
指针到底是什么?
先问一个问题:你写了一个变量 int a = 10;,这个a存在哪里?
答案是——内存里。每个变量在内存中都有一个地址,就像你家的门牌号。指针,就是用来存这个"门牌号"的变量。
说白了,普通变量存的是"值",指针变量存的是"地址"。
核心理解:
- 普通变量:存数据(比如10、'A'、3.14)
- 指针变量:存地址(比如0x7ffd1234)
我刚开始学的时候,总把指针和它指向的数据搞混。后来我告诉自己:指针就是一张纸条,上面写着一个地址。你顺着地址去找,才能拿到真正的数据。
指针的定义与初始化
定义指针的语法很简单:
类型 *指针变量名;
这里的*告诉编译器:我要定义一个指针变量。前面的类型表示这个指针将来要指向什么类型的数据。
举个例子:
int *p; // p是一个指向int类型数据的指针
char *cp; // cp是一个指向char类型数据的指针
double *dp; // dp是一个指向double类型数据的指针
定义之后,指针是"野"的——它里面存的是什么地址,没人知道。所以一定要初始化。
初始化有两种方式:
- 指向一个已有的变量
- 赋值为NULL(空指针)
int a = 10;
int *p = &a; // 方式1:p指向a的地址
int *q = NULL; // 方式2:q不指向任何有效地址
注意:未初始化的指针非常危险。我曾经在项目中见过一个同事,定义了一个指针没初始化就直接用,结果程序跑起来后随机崩溃,查了两天才找到原因。嗯,从那以后我每次定义指针都会顺手初始化。
取地址符&:拿到变量的门牌号
&是取地址运算符。它的作用很简单:获取一个变量在内存中的地址。
int x = 100;
printf("x的地址是:%p\n", &x); // 输出类似 0x7ffd1234
你想想看,&x得到的是一个地址,这个地址的类型是"指向int的指针",也就是int *。所以它可以直接赋值给int *类型的指针变量。
这里有个细节要注意:
&只能用于变量或数组元素,不能用于常量或表达式- 比如
&100是错的,&(a+b)也是错的
小技巧:用%p格式化输出地址时,地址通常以十六进制显示。不同系统下地址长度不同,32位系统是4字节,64位系统是8字节。
解引用符*:顺着地址找数据
*是解引用运算符(也叫间接访问运算符)。它的作用正好和&相反——给你一个地址,你去找到那个地址里存的数据。
int a = 10;
int *p = &a;
printf("%d\n", *p); // 输出 10
*p = 20; // 通过指针修改a的值
printf("%d\n", a); // 输出 20
看到了吗?*p就相当于a。你通过指针拿到了a的地址,然后用*去操作那个地址里的数据。
我个人习惯把*p理解为"p指向的那个变量"。这样想,代码读起来就顺多了。
记住这个对应关系:
| 表达式 | 含义 |
|---|---|
p |
指针变量,存的是地址 |
&a |
变量a的地址 |
*p |
p指向的那个变量(即a) |
&*p |
还是p本身(因为*p就是a,再取地址又回去了) |
指针的图解:一张图看懂
下面我用一张SVG图来展示指针、地址、数据之间的关系。这张图我画了很多次,每次给学生讲指针时都会用类似的图。
指针的常见误区
学指针时,有几个坑我几乎每次都会看到有人踩。这里列出来,你注意一下。
误区一:混淆定义时的*和使用时的*
定义时:int *p; 这里的*表示"p是一个指针"
使用时:*p = 10; 这里的*表示"解引用"
说白了,同一个符号在不同位置含义不同。我刚开始也经常搞混,后来记住一句话:定义时看类型,使用时看操作。
误区二:对NULL指针解引用
如果你写了 int *p = NULL; *p = 10;,程序会直接崩溃。NULL指针不指向任何有效内存,解引用它是未定义行为。
我曾经在调试一个嵌入式程序时,就是因为某个指针在异常路径下没被赋值,仍然是NULL,结果一解引用就hard fault。查了整整一个下午。
误区三:指针类型不匹配
int *p = &a; 没问题,但 int *p = &c;(c是char类型)编译器会报警告。指针的类型决定了它一次能读取多少字节。int*读4字节,char*读1字节,搞错了数据就全乱了。
指针的尺寸:它占多少内存?
一个指针变量本身占多少字节?答案是:取决于系统。
| 系统 | 指针大小 |
|---|---|
| 32位系统 | 4字节(32位地址) |
| 64位系统 | 8字节(64位地址) |
| 嵌入式16位系统 | 2字节(少见) |
不管指针指向什么类型(int、char、double),在同一个系统上,所有指针的大小都是一样的。因为地址的长度是固定的。
printf("int* 大小: %zu 字节\n", sizeof(int*));
printf("char* 大小: %zu 字节\n", sizeof(char*));
printf("double* 大小: %zu 字节\n", sizeof(double*));
// 在64位系统上,输出都是8
提示:sizeof返回的是size_t类型,用%zu格式化输出。这个细节很多人不注意,但写标准C代码时最好用%zu。
一个完整的例子
把今天讲的内容串起来,写一个完整的程序:
#include <stdio.h>
int main() {
int a = 42;
int *p = &a; // 定义指针并初始化
printf("a 的值: %d\n", a);
printf("a 的地址: %p\n", &a);
printf("p 的值: %p\n", p);
printf("p 指向的值: %d\n", *p);
*p = 100; // 通过指针修改a
printf("修改后 a 的值: %d\n", a);
printf("指针 p 本身的大小: %zu 字节\n", sizeof(p));
printf("p 指向的数据大小: %zu 字节\n", sizeof(*p));
return 0;
}
输出结果(64位系统示例):
a 的值: 42
a 的地址: 0x7ffd1234
p 的值: 0x7ffd1234
p 指向的值: 42
修改后 a 的值: 100
指针 p 本身的大小: 8 字节
p 指向的数据大小: 4 字节
看到没?p和&a的值一模一样,说明p确实存的是a的地址。*p就是a本身,改*p就是改a。
小结
指针基础就这些内容。说白了就是三件事:
- &:取地址,拿到变量的门牌号
- *:解引用,顺着门牌号进去拿数据
- 指针变量:存门牌号的变量
我个人的经验是,学指针不要死记硬背,多画图、多写代码。你亲手写几个例子,把地址打印出来看看,慢慢就理解了。下一节我们会继续深入,看看指针和数组之间那些"剪不断理还乱"的关系。
公众号:蓝海资料掘金营,微信deep3321