C++ 学习笔记:指针与引用
目次
指针是什么 #
在 Java 和 Kotlin 这些语言里,你不能直接操作内存地址,只需要关心生命周期,避免被生命周期更长的对象引用,进而导致内存不能释放。这是一种内存管理方案。而 C++ 则允许显式地操作内存——更具体一点,是允许直接处理指针或你手动分配的内存。Rust 又走了另一条路:归属模型。一块内存只能被一个变量拥有,其他变量想要用它必须“借”,目的是规避 C/C++ 中指针带来的野指针、悬空指针之类的问题。
废话有点多,回到核心问题:指针到底是什么?
维基百科给出的定义非常标准,但也非常“教科书”。简单来说,指针就是一个变量,它的值不是普通数据,而是另一个对象的内存地址。也就是说,它保存的是“对象在哪里”,而不是“对象是什么”。
举个最正常不过的例子:
| |
所有语言都能理解这句话:定义一个 int 类型的变量 a,它的值是 1。只不过在底层,这意味着分配了一块用来放 int 的内存空间,这块空间里存着数字 1。而 a 其实就是“访问这块内存时的入口”。
如果我们能直接拿到这块内存的地址(假设是这块内存的起始位置),那还需要知道它是什么类型,才能知道要从这个地址往后读多少字节,才能正确还原数据的值。这就是为什么指针需要类型:int* 不仅意味着“存的是地址”,还意味着“当解引用时,把那段内存按 int 解释”。
所以,指针是什么?一句话:
指针是一个保存某个对象内存地址的变量。它的类型决定你用它解引用时如何解析那段内存。
在 C++ 中,指针由 * 声明:
| |
不要想太复杂,int* 就是“指向 int 的指针类型”,和你写 int、float 一样,是类型的一部分。它可以在定义时就指向某个变量,也可以先定义再让它指向别的地方。
既然说指针保存的是地址,那它就不能随便接受一个非地址的值,比如:
| |
不是因为“指针不能赋值”,而是 1 并不是一个合法的地址,不能拿来解引用。
接下来,既然地址存进去了,怎么获得地址里存放的数据呢?还是用 *:
| |
你注意到没有,这次 * 在 = 右边,表示“解引用”。总之如果 * 没出现在类型声明里(int* p 这种),它就是“从地址取值”。
引用 #
前面说了,&a 的含义是“取 a 的地址”。而如果你写:
| |
这表示的是:开辟一块新的内存,把 a 的值复制过去给 b。两个变量各占一块地方。
那有没有一种方式,不开辟第二块内存,只是给 a 再起一个名字,让另一个变量直接访问 a 的那块内存呢?有,这就是引用:
| |
b 和 a 是同一个对象的两个名字,修改 b 就是在修改 a。这也是为什么引用一旦绑定之后不能再改。
注意,这里的 & 和“取地址”的那个 & 完全不是一个含义。现在它出现在 = 左侧,也就是类型声明中,和 int* p 里的 * 类似,只是用来描述类型而已。
总结 #
* 和 & 都很“善变”,放在不同位置含义完全不同。
更准确地说,它们分为两种情况:
一种是出现在类型声明中,比如 int*、int&;
另一种是出现在表达式中,比如 *p(解引用)、&a(取地址)。
这两类含义完全不同:
| 符号 | 出现在“类型声明”中 | 出现在“表达式”中 |
|---|---|---|
* | 声明“指针类型” | 解引用(通过地址访问对象) |
& | 声明“引用类型” | 取地址(得到指针) |