1. 指针的本质:内存地址与指针变量的关系

指针这东西,说白了就是地址的“快递员”。

我刚开始学C语言那会儿,总觉得指针特别玄乎。后来在项目里调试一个内存泄漏问题,折腾了三天,才真正搞明白——指针就是个变量,只不过它存的是地址,不是普通数值。

1.1 内存地址是什么?

你想想看,计算机的内存就像一栋巨大的公寓楼。每个房间都有唯一的门牌号,这个门牌号就是内存地址。

  • 每个字节(8个比特)都有一个地址
  • 地址通常用十六进制表示,比如 0x7ffeefbff5a8
  • 变量就住在这个“房间”里,地址就是它的门牌号

核心概念:内存地址是数据在内存中的位置编号。指针变量就是用来存这个编号的变量。

1.2 指针变量的声明与初始化

声明指针,其实就是在告诉编译器:“嘿,我要一个变量,它专门用来存地址。”

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

嗯,这里要注意:int *p 里的 * 只是声明符号,不是解引用。我见过不少新手在这里犯迷糊。

初始化呢?就是给这个指针变量赋一个具体的地址值:

int a = 42;
int *p = &a;  // p现在存的是变量a的地址

个人习惯:我每次声明指针都会立即初始化,要么给有效地址,要么给 NULL。野指针这东西,坑过我好几次。

1.3 取地址符 & 与解引用符 *

这两个符号,是操作指针的左右手。

符号 名称 作用 示例
& 取地址符 获取变量的内存地址 &a → 得到a的地址
* 解引用符 访问指针指向的内存中的数据 *p → 得到p指向的值
int a = 10;
int *p = &a;   // & 取a的地址,赋给p

printf("a的值: %d\n", a);    // 输出 10
printf("a的地址: %p\n", &a); // 输出类似 0x7ffeefbff5a8
printf("p的值: %p\n", p);    // 输出和上面一样
printf("*p的值: %d\n", *p);  // 输出 10,解引用得到a的值

*p = 20;  // 通过指针修改a的值
printf("a的新值: %d\n", a);  // 输出 20

我曾经犯过一个低级错误:声明了指针却没初始化,直接解引用。结果程序崩溃,查了半天才发现是野指针在作怪。记住:未初始化的指针,解引用就是未定义行为。

1.4 指针的图解

光说可能不够直观,我画个图帮你理解:

指针与内存地址关系图 变量 a 值: 42 地址: 0x1000 指针变量 p 值: 0x1000 地址: 0x2000 存储地址 解引用操作 *p 通过 p 中存储的地址 (0x1000) 找到变量 a 所在的内存位置 *p = 42 (读取a的值)

1.5 常见误区与避坑指南

  1. 混淆声明和解引用:int *p; 里的 * 是声明,*p = 10; 里的 * 才是解引用。
  2. 忘记初始化:声明指针后一定要赋值,否则就是野指针。
  3. 类型不匹配:int *p = &a; 要求 a 是 int 类型,不能把 double 的地址赋给 int 指针。

我建议:写代码时养成习惯——声明指针的同时初始化。如果暂时没有目标地址,就赋 NULL。这样至少解引用时会立刻崩溃,而不是偷偷摸摸地破坏数据。

1.6 小结

指针就是个存地址的变量。& 取地址,* 解引用。就这么简单。

但简单不代表容易。我在项目里见过太多因为指针没初始化、类型不匹配导致的诡异bug。记住:指针是工具,不是玩具。用好了如虎添翼,用不好就是自掘坟墓。

一句话总结:指针变量存地址,取地址用 &,拿数据用 *,初始化别偷懒。