左值引用

在 C++ 中,引用(reference) 是某个已存在对象的别名。一旦定义了引用,对该引用的任何操作都作用在被引用的对象上;因此,我们可以通过引用读取或修改原对象。

初看之下,引用似乎多余,但在 C++ 中它们无处不在,稍后几课将展示其强大用途。

关键要点
引用在语义上与其所引用的对象完全一致。

(进阶提示)
也可以创建对函数的引用,只是这种做法较少见。

现代 C++ 提供两种引用:

  • 左值引用(lvalue reference)
  • 右值引用(rvalue reference)

本章仅讨论左值引用;右值引用将在第 22 章“移动语义”中介绍。

左值引用类型

左值引用(常简称“引用”)是某个左值对象(如变量)的别名。
其类型由被引用类型后加一个 & 标识:

int        // 普通 int,非引用
int&       // 指向 int 的左值引用
double&    // 指向 double 的左值引用
const int& // 指向 const int 的左值引用
  • int& 表示“指向 int 的左值引用”;
  • const int& 表示“指向 const int 的左值引用”。

术语

  • 引用类型:带 & 的完整类型(如 int&)。
  • 被引用类型:去掉 & 后的类型(如 int)。

左值引用分为两种:

  1. 非常量左值引用:可修改原对象。
  2. 常量左值引用:只读原对象(下一课 12.4 讨论)。

左值引用变量

定义语法:在变量名前加 &

int x{ 5 };   // 普通整型
int& ref{ x }; // ref 是 x 的左值引用

示例程序:

#include <iostream>

int main()
{
    int x{ 5 };
    int& ref{ x };

    std::cout << x << '\n';   // 5
    std::cout << ref << '\n'; // 5
}

输出:

5
5

风格建议
现代 C++ 社区倾向把 & 写在类型侧:int& ref,以突出“引用”是类型的一部分,而非标识符的一部分。

通过非常量左值引用修改原对象

#include <iostream>

int main()
{
    int x{ 5 };
    int& ref{ x };

    std::cout << x << ref << '\n'; // 55

    x = 6;       // 通过 x 修改
    std::cout << x << ref << '\n'; // 66

    ref = 7;     // 通过 ref 修改
    std::cout << x << ref << '\n'; // 77
}

输出:

55
66
77

引用初始化

  • 必须初始化;未初始化即报错。
  • 初始化过程称为引用绑定(reference binding),被引用对象称referent
int& invalidRef; // 错误:未初始化
int x{ 5 };
int& ref{ x };   // 合法:绑定到 x

非常量左值引用只能绑定到可修改左值

int x{ 5 };
const int y{ 5 };

int& ref1{ x };       // OK
int& ref2{ y };       // 错误:不能绑定到 const 左值
int& ref3{ 0 };       // 错误:不能绑定到右值

(如果允许绑定到 const 或右值,则可通过引用修改其值,违反 const 语义。)

  • 不允许 void&(无意义)。
  • 类型不匹配时,编译器尝试隐式转换;若转换结果为右值,则非常量左值引用无法绑定,导致编译错误。

引用不可重定向

引用一经绑定,终身不可改指

int x{ 5 }, y{ 6 };
int& ref{ x };
ref = y; // 把 y 的值赋给 x,而非让 ref 指向 y

作用域与生命周期

引用变量遵循与普通变量相同的作用域与生命周期规则;引用与其 referent 的生命周期相互独立

int main()
{
    int x{ 5 };
    {
        int& ref{ x };
        std::cout << ref << '\n'; // 5
    } // ref 销毁,x 不受影响
    std::cout << x << '\n';      // 5
}

悬垂引用(dangling reference)

若 referent 先于引用被销毁,则引用悬垂,访问它产生未定义行为。我们将在 12.12 课“返回引用与返回地址”中展示实际场景。

引用不是对象

  • 引用本身不是对象,不保证占用存储;编译器常将其优化掉。
  • 因此“引用变量”一词并不严谨——变量是具名对象,而引用不是。
  • 不能声明“引用的引用”,也不能把引用当作对象使用;若需可重定向的引用对象,可用 std::reference_wrapper(第 23.3 课)。

示例:

int var{};
int& ref1{ var };   // ref1 绑定 var
int& ref2{ ref1 };  // ref2 同样绑定 var(而非“引用的引用”)

int&& 在 C++11 起表示右值引用,而非“引用的引用”。

测验

问题 1
不编译运行,推断下面程序输出:

#include <iostream>

int main()
{
    int x{ 1 };
    int& ref{ x };

    std::cout << x << ref << '\n'; // 11

    int y{ 2 };
    ref = y; // 把 2 赋给 x
    y = 3;   // 仅改 y

    std::cout << x << ref << '\n'; // 22

    x = 4;   // 同时改 x 与 ref

    std::cout << x << ref << '\n'; // 44
}

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"