C++ - 引用

529 阅读5分钟

一. 引用的定义

引用 (reference) 不是定义一个新的变量, 而是给已存在的变量取一个别名, 对引用的操作与对变量直接操作完全一样.

类型& 引用变量名(对象名) = 引用实体

#include <iostream>
using namespace std;

int main()
{
    int a = 1;
    int& ra = a;

    a = 2;  // 对变量操作
    cout << a << endl;  // 输出结果为 2
    ra = 3;  // 对引用操作
    cout << a << endl;  // 输出结果为 3
    return 0;
}

image.png

Tip: 一般情况下, 引用的类型要和与之绑定的对象严格匹配.

二. 引用的特性

1. 引用在定义时必须被初始化

int main()
{
    int b = 1;
    int& rb = b;  // rb指向 b (rb是 b的别名)
    int& rrb;  // 报错: 必须初始化引用
    return 0;
}

image.png

一般在初始化变量时, 初始值会被拷贝到新建的对象中. 然而定义引用时, 程序把引用和它的初始值绑定 (bind) 在一起, 而不是将初始值拷贝给引用. 一旦初始化完成, 引用将和它的初始值对象一直绑定在一起.

2. 一个变量可以有多个引用

#include <iostream>
using namespace std;

int main()
{
    int a = 1;
    int& ra = a;
    int& rra = a;

    // 输出结果为 1 1 1
    cout << a << ' ' << ra << ' ' << rra << endl;
    return 0;
}

image.png

3. 不能定义引用的引用

int main()
{
    int b = 1;
    int& (&rb) = b;  // 报错: 无法创建对引用的引用
    return 0;
}

1685414174797.png

因为引用本身不是一个对象, 所以不能定义引用的引用.

4. 引用一旦绑定一个对象, 就不能再绑定其他对象

int main()
{
    int a = 1;
    int b = 2;
    int& ra = a;
    int& ra = b;  // 报错: 引用重定义(多次初始化)
    return 0;
}

image.png

5. 定义引用变量时不会开辟新的内存空间

#include <iostream>
using namespace std;

int main()
{
    int a = 1;
    int& ra = a;

    // a和 ra的地址是相同的
    cout << &a << endl << &ra << endl;
    return 0;
}

image.png

编译器不会为引用变量开辟新的内存空间, 它和它所引用的变量共用同一块内存空间.

三. 对 const 的引用

把引用绑定到 const 对象上, 称之为 const 的引用, 与普通引用不同的是, 不能通过对 const 的引用去改变它所绑定的对象.

1. 对 const 对象进行 const 引用

int main()
{
    const int c = 10;
    const int& r1 = c;  // 正确: 引用及其对应的对象都是常量(权限可以平移)
    r1 = 20;  // 错误: r1是对常量的引用, 不能通过 r1去修改 c
    int& r2 = c;  // 错误: 一个非常量引用无法指向一个常量对象(权限不能放大)
    return 0;
}

2. 对非 const 对象进行 const 引用

const 引用仅对引用可参与的操作权限做出了限定 ( const 引用只有读权限, 没有写权限; 非 const 的一般引用既有读权限又有写权限 ), 对于引用的对象本身是不是一个常量未作限定.

int main()
{
    int i = 23;
    int& r1 = i;  // r1绑定对象 i, 允许通过 r1修改 i(权限可以平移)
    r1 = 0;  // 正确: r1并非对常量的引用, i的值被修改为 0

    const int& r2 = i;  // r2绑定对象 i, 但是不允许通过 r2修改 i(权限可以缩小)
    r2 = 0;  // 错误: r2是对常量的引用, 不能通过 r2去修改 i
    return 0;
}

r2 绑定 (非常量) 整数 i 是合法的行为. 然而, 不允许通过 r2 修改 i 的值. 尽管如此, i 的值仍然允许通过其他途径修改: 1. 直接给 i 赋值; 2. 通过像 r1 一样绑定到 i 的其他非 const 引用来修改.

3. 对 const 引用的初始化

前面提到, 引用的类型必须与其所引用对象的类型一致, 但在此处存在例外: 在初始化 const 引用时允许用任意表达式作为初始值, 只要该表达式的结果能转换成引用的类型即可. 尤其, 允许为一个 const 引用绑定非常量的对象, 字面值, 甚至是一个字面表达式.

int main() 
{
    int i = 24;
    const int& r1 = i;  // 正确: 允许将 const int& 绑定到一个普通 int对象上
    const int& r2 = 24;  // 正确: r2是一个 const 引用, 绑定到一个字面值上
    const int& r3 = i * 2;  // 正确: r3是一个 const 引用, 绑定到一个一般表达式上

    int& r4 = i * 2;  // 错误: r4是一个普通的非 const 引用, 只能绑定到一个普通 int对象上
    return 0;
}

当一个 const 引用被绑定到另外一种类型上到底发生了什么?

对如下程序进行探索:

#include <iostream>
using namespace std;

int main() 
{
    double val = 3.14;
    const int& r = val;
    cout << val << ' ' << r << endl;
    return 0;
}

image.png

此处 r 引用了一个 int 型的数. 对 r 的操作应该是整数运算, 但 val 却是一个双精度浮点数. 因此为了确保让 r 绑定一个整数, 编译器把上述代码变成了如下形式.

const int tmp = val;  // 由双精度浮点数生成一个临时的整型常量
const int& r = tmp;  // 让 r绑定 tmp这个临时量对象

image.png

在这种情况下, r 绑定了一个临时量 (temporary) 对象. 所谓临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名对象. 通常把临时量对象简称为临时量, 临时量具有 const 属性.