opoojkk

C++ 学习笔记:指针与引用

lxx
目次

指针是什么 #

在 Java 和 Kotlin 这些语言里,你不能直接操作内存地址,只需要关心生命周期,避免被生命周期更长的对象引用,进而导致内存不能释放。这是一种内存管理方案。而 C++ 则允许显式地操作内存——更具体一点,是允许直接处理指针或你手动分配的内存。Rust 又走了另一条路:归属模型。一块内存只能被一个变量拥有,其他变量想要用它必须“借”,目的是规避 C/C++ 中指针带来的野指针、悬空指针之类的问题。

废话有点多,回到核心问题:指针到底是什么?

维基百科给出的定义非常标准,但也非常“教科书”。简单来说,指针就是一个变量,它的值不是普通数据,而是另一个对象的内存地址。也就是说,它保存的是“对象在哪里”,而不是“对象是什么”。

举个最正常不过的例子:

1
int a = 1;

所有语言都能理解这句话:定义一个 int 类型的变量 a,它的值是 1。只不过在底层,这意味着分配了一块用来放 int 的内存空间,这块空间里存着数字 1。而 a 其实就是“访问这块内存时的入口”。

如果我们能直接拿到这块内存的地址(假设是这块内存的起始位置),那还需要知道它是什么类型,才能知道要从这个地址往后读多少字节,才能正确还原数据的值。这就是为什么指针需要类型:int* 不仅意味着“存的是地址”,还意味着“当解引用时,把那段内存按 int 解释”。

所以,指针是什么?一句话:

指针是一个保存某个对象内存地址的变量。它的类型决定你用它解引用时如何解析那段内存。

在 C++ 中,指针由 * 声明:

1
2
int a = 1;
int* p = &a;

不要想太复杂,int* 就是“指向 int 的指针类型”,和你写 intfloat 一样,是类型的一部分。它可以在定义时就指向某个变量,也可以先定义再让它指向别的地方。

既然说指针保存的是地址,那它就不能随便接受一个非地址的值,比如:

1
int* p = 1; // 不行

不是因为“指针不能赋值”,而是 1 并不是一个合法的地址,不能拿来解引用。

接下来,既然地址存进去了,怎么获得地址里存放的数据呢?还是用 *

1
int a = *p;

你注意到没有,这次 *= 右边,表示“解引用”。总之如果 * 没出现在类型声明里(int* p 这种),它就是“从地址取值”。

引用 #

前面说了,&a 的含义是“取 a 的地址”。而如果你写:

1
int b = a;

这表示的是:开辟一块新的内存,把 a 的值复制过去给 b。两个变量各占一块地方。

那有没有一种方式,不开辟第二块内存,只是给 a 再起一个名字,让另一个变量直接访问 a 的那块内存呢?有,这就是引用:

1
int& b = a;

ba 是同一个对象的两个名字,修改 b 就是在修改 a。这也是为什么引用一旦绑定之后不能再改。

注意,这里的 & 和“取地址”的那个 & 完全不是一个含义。现在它出现在 = 左侧,也就是类型声明中,和 int* p 里的 * 类似,只是用来描述类型而已。

总结 #

*& 都很“善变”,放在不同位置含义完全不同。

更准确地说,它们分为两种情况: 一种是出现在类型声明中,比如 int*int&; 另一种是出现在表达式中,比如 *p(解引用)、&a(取地址)。

这两类含义完全不同:

符号出现在“类型声明”中出现在“表达式”中
*声明“指针类型”解引用(通过地址访问对象)
&声明“引用类型”取地址(得到指针)
标签:
Categories: