Menu

C++ 引用与指针的区别:各自何时使用

实用地对比 C++ 中的引用和指针:它们的共同点,它们的不同之处(重新绑定、空值、指针运算),以及日常代码中该选哪一个的清晰规则。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

引用某样东西的两种方式

你已经认识了指针:存放地址的变量,让你能访问住在那里的对象。引用是 C++ 提供的另一种间接操作已有对象的工具。两者重叠的地方很多,初学者常常分不清该选哪个,所以本页把它们放在一起逐项对比。

简单说:引用是别名。int& r = x; 执行之后,r 就是 x:同一个对象,不同的名字。指针是一个独立的对象,只不过它恰好存着另一个对象的地址。其他所有差异都源于这一点。

引用就是别名

引用在创建的那一刻就必须绑定到一个对象上,从此以后,对引用的每次使用都作用在原对象上。

注意,使用时既没有用来解引用的 *,也没有用来“取地址”的 &:你读写 alias 的方式和普通的 int 完全一样。int& alias 中的 & 是类型的一部分,不是取地址运算符。

它们的不同之处

下面这些行为差异,正是两种工具同时存在的全部理由。这张表值得记住。

//                      reference            pointer
// must be initialized? yes                  no (but should be)
// can be null?         no                   yes (nullptr)
// can be reseated?     no                   yes
// pointer arithmetic?  no                   yes
// syntax to use it     just the name        *p  or  p->member
// taking address       &ref == &original    &p is the pointer's own address

其中有两点最容易让人犯错。第一,引用永远不能重新绑定:给它赋值是把一个值拷贝进它所引用的对象,而不是让引用指向新的东西。

相比之下,指针随时都可以改为指向别处:

第二,引用永远不可能合法地为空,而指针可以。这意味着“没有值”可以用指针表示,却无法用引用表示,这个特性你会经常用到。

在函数参数中如何选择

这种选择最常出现在函数参数上。当函数需要读取或修改调用者的对象时,两者都能用,但传达的意图不同。

引用版本(addTax(cart))不可能用“什么都没有”来调用,所以函数内部永远不用检查空值,对象一定存在。指针版本(applyDiscount(&cart))则在调用处通过 & 表明参数可能会被修改,还允许调用者传入 nullptr 来表示“不适用”。选那个保证与你的函数相符的。

对于大类型的只读参数,惯用的选择是 const T&:它避免了拷贝,同时承诺不做修改。按值传递和按引用传递的更多内容,请看函数参数一节。

一条简单的经验法则

拿不准的时候,默认用引用,只有在需要引用做不到的能力时才换成指针:

  • 用引用:当对象一定存在,而且身份永远不变时。这是函数参数和别名的常见情况。
  • 用指针:当下面任意一条成立时:
    • “什么都没有”是合法状态(可选参数、可能找不到匹配项的搜索):指针可以是 nullptr。
    • 你需要随时间指向不同的对象:指针可以重新指向。
    • 你在管理之后要 delete 的堆内存(或者更好的做法,用智能指针),或者在用指针运算遍历数组。

如果以上都不适用,引用就是更简洁、更安全的选择,因为编译器会替你保证“始终有效,绝不重新绑定”。

要避开的常见错误

  • 以为 ref = other 会重新绑定。 它其实是把一个值赋给所引用的对象。引用一旦绑定就终身不变;需要重新指向时,用指针。
  • 返回局部变量的引用(或指针)。 int& f() { int x = 5; return x; } 返回的是一个悬空引用:f 返回时 x 就销毁了,使用这个结果是未定义行为。指针也有同样的陷阱(return &x;)。
  • 伪造一个“空引用”。 在 p 是 nullptr 时写 int& r = *p;,解引用的那一刻就是未定义行为,而不是一个安全的“空”引用。请改用指针或 std::optional 来表达可选性。
  • 出于习惯就用指针。 如果参数一定存在、你也永远不会重新指向它,引用就能免去一整类空值检查和崩溃。用不到的能力,就别为它付出代价。

下一节:动态内存

到目前为止,你引用或指向的每个对象都是在栈上自动创建的。下一页动态内存会讲解 new 和 delete:如何在运行时向操作系统申请内存,为什么拥有这块内存的是指针(而不是引用),以及忘记释放它为什么会导致内存泄漏。

常见问题

C++ 中引用和指针有什么区别?

引用是一个已有对象的别名:它必须初始化,永远不能为空,之后也不能再改为引用另一个对象。指针是一个存放地址的独立变量:它可以为空,可以重新指向别处,还支持指针运算。引用使用 & 语法,指针使用 */->。

C++ 中什么时候该用指针而不是引用?

当“什么都没有”是一种合法状态时(可选参数、未找到的结果),当你需要随时间让它指向不同对象时,或者当你持有之后要 delete 的堆内存时,用指针。当那个东西一定存在、身份也从不改变时,用引用,大多数函数参数都属于这种情况。

C++ 中的引用可以为空吗?

不可以。合法的引用总是引用一个真实的对象,所以你从不需要检查它是否为空。如果你通过解引用一个空指针来创建引用(int& r = *p;,其中 p 为空),得到的是未定义行为,而不是一个空引用。需要表达“可能什么都没有”时,请用指针或 std::optional。

Coddy 编程语言插图

和 Coddy 一起学编程

开始