指针基础:指针的概念、定义、初始化,取地址符&与解引用符*

指针,是C语言的灵魂,也是很多人学C时第一个"劝退"点。说实话,我当年学指针时也绕了好一阵子。但一旦你理解了它背后的逻辑,你会发现——指针其实没那么玄乎,它就是一个装着地址的变量而已。

今天我们就来彻底搞懂指针的基础:它是什么、怎么定义、怎么初始化,以及那两个最关键的符号——&和*。

指针到底是什么?

先问一个问题:你写了一个变量 int a = 10;,这个a存在哪里?

答案是——内存里。每个变量在内存中都有一个地址,就像你家的门牌号。指针,就是用来存这个"门牌号"的变量。

说白了,普通变量存的是"值",指针变量存的是"地址"。

核心理解:

  • 普通变量:存数据(比如10、'A'、3.14)
  • 指针变量:存地址(比如0x7ffd1234)

我刚开始学的时候,总把指针和它指向的数据搞混。后来我告诉自己:指针就是一张纸条,上面写着一个地址。你顺着地址去找,才能拿到真正的数据。

指针的定义与初始化

定义指针的语法很简单:

类型 *指针变量名;

这里的*告诉编译器:我要定义一个指针变量。前面的类型表示这个指针将来要指向什么类型的数据。

举个例子:

int *p;      // p是一个指向int类型数据的指针
char *cp;    // cp是一个指向char类型数据的指针
double *dp;  // dp是一个指向double类型数据的指针

定义之后,指针是"野"的——它里面存的是什么地址,没人知道。所以一定要初始化。

初始化有两种方式:

  1. 指向一个已有的变量
  2. 赋值为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图来展示指针、地址、数据之间的关系。这张图我画了很多次,每次给学生讲指针时都会用类似的图。

指针、地址、数据关系图 变量 a 10 地址: 0x7ffd1000 指针 p 0x7ffd1000 地址: 0x7ffd2000 p = &a *p → 顺着地址找到变量a,得到值 10 p存地址,*p取数据;&a取地址,a存数据

指针的常见误区

学指针时,有几个坑我几乎每次都会看到有人踩。这里列出来,你注意一下。

误区一:混淆定义时的*和使用时的*

定义时: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