跳转至

Reference & Value Category

我们要来到我们目前能接触到的 C++ 中最困难的话题了。

引用

引用的概念本身很简单——就是给一个变量起个别名:

1
2
3
4
int x = 42;
int &rx = x;
rx = 100; // makes x = 100
assert(&x == &rx); // true

rxx 的引用,对 rx 的任何操作都会直接作用于 x。引用的一个重要特性是它必须在定义时被初始化(我们称它被绑定到一个对象),并且引用不能被重新绑定到另一个对象。rx 被初始化时绑定到 x 之后,没有任何方法可以让它绑定到其他对象——这与指针不同。

rx 的类型是 int &,而不是 int。我们称它为左值引用lvalue reference,因为它只能绑定到左值lvalue。我们稍晚一点会讨论左值和右值的概念。目前可以认为这代表下面的写法无法通过编译:

1
2
3
/* error: non-const lvalue reference to type 'int'
   cannot bind to a temporary of type 'int' */
int &rx = 42;

任何类型都有其对应的引用类型;类型 T 的引用类型是 T &,只要 T 本身不是引用类型。引用的底层实现可能仍然是指针,但我们不需要关心这个细节。

引用的一个重要用途是作为函数参数。我们都知道参数按值传递。考虑在 C 中实现一个 swap 函数,我们会用指针来实现。在 C++ 中当然也可以这么做,但可以使用引用来简化这个过程。使用引用我们可以避免一些容易出错的语法(例如交换了指针本身的值而不是它们指向的对象)。

C
1
2
3
4
5
void swap(int *a, int *b) {
  int tmp = *a;
  *a = *b;
  *b = tmp;
}
C++
1
2
3
4
5
void swap(int &a, int &b) {
  int tmp = a;
  a = b;
  b = tmp;
}

实际上 C++ 提供了标准库函数 std::swap 来实现这个功能。

std::swap 的实现要比我们上面给出的更复杂,它需要处理各种我们目前尚未讨论的特殊情况。

引用的另一个重要用途是作为函数返回值。一个极其常用的例子是为自定义类型实现流插入运算符(<<):

1
2
3
4
5
6
7
8
9
struct rectangle {
  int width;
  int height;
};
std::ostream &operator<<(std::ostream &os, const rectangle &r) {
  os << "rectangle[width=" << r.width
     << ", height=" << r.height << "]";
  return os;
}

你可能会疑惑为什么我们要将 operator<< 的参数原样返回,我们会在 Operator & Cast 中讨论这个问题。目前我们可以认为,是这样的写法让我们能够将 operator<< 作为链式调用的一部分:

std::cout << "The rectangle is: " << r << std::endl;

TBD

值类别

临时量实质化

引用折叠

std::move

评论