LearnCpp 9.3 — 左值引用

左值引用

在 C++ 中,引用是现有对象的别名。一旦定义了引用,对引用的任何操作都将应用于被引用的对象。

关键见解
引用本质上与被引用的对象相同。


这意味着我们可以使用引用来读取或修改被引用的对象。尽管一开始引用可能看起来很愚蠢、无用或多余,但在 C++ 中到处都在使用引用(我们将在几节课中看到这方面的示例)。
您也可以创建对函数的引用,尽管很少这样做。
现代 C++ 包含两种类型的引用:左值引用(lvalue references)和右值引用(rvalue references). 在本章中,我们将讨论左值引用。

左值引用类型

左值引用(通常称为 引用,因为在 C++11 之前只有一种reference引用类型)充当现有左值(例如变量)的别名。
要声明左值引用类型,我们在类型声明中使用与号 (&):

int      // 普通类型
int&     // 对int对象的左值引用
double&  // 对double对象的左值引用

左值引用变量

左值引用变量是充当对左值(通常是另一个变量)的引用的变量。
要创建左值引用变量,我们只需定义一个具有左值引用类型的变量:

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

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

    return 0;
}

在上面的示例中,类型int&定义ref为对 int 的左值引用,我们用x对左值表达式初始化。此后,ref和x可以当作相同使用。该程序因此打印:

在这里插入图片描述
从编译器的角度来看,与号在类型名称旁边( int& ref) 还是变量名称旁边 ( int &ref) 无关紧要,您选择哪一个是风格问题。现代 C++ 程序员倾向于将 & 符号附加到类型上,因为它更清楚地表明引用是类型信息的一部分,而不是标识符。

最佳实践
定义引用时, 将 & 符号放在类型旁边(不是引用变量的名称)。
对于高级读者

对于那些已经熟悉指针的人来说,这里的 & 符号并不意味着“地址”,它意味着“左值引用”。

通过左值引用修改值

在上面的例子中,我们展示了我们可以使用引用来读取被引用对象的值。我们还可以使用引用来修改被引用对象的值:

#include <iostream>

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

    std::cout << x << ref; // print 5

    x = 6; // x值现在是6

    std::cout << x << ref; // prints 6

    ref = 7; // x现在是7

    std::cout << x << ref; // prints 7

    return 0;
}

此代码打印:

556677


在上面的例子中,ref是 的别名x,所以我们可以通过x或ref来改变 x变量的值。

左值引用的初始化

就像常量一样,所有引用都必须初始化。

int main()
{
    int& invalidRef;   // error: 引用必须初始化

    int x { 5 };
    int& ref { x }; // okay: 引用绑定了变量x

    return 0;
}

当使用对象(或函数)初始化引用时,我们说它绑定到该对象(或函数)。绑定此类引用的过程称为引用绑定(reference binding)。被引用的对象(或函数)有时称为被引用对象(referent.)。

左值引用必须绑定到可修改的左值。

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

    const int y { 5 };
    int& invalidRef { y };  // 不可以绑定不可以修改的左值
    int& invalidRef2 { 0 }; // 不可以绑定右值

    return 0;
}

左值引用不能绑定到不可修改的左值或右值(否则您可以通过引用更改这些值,这将违反它们的不变性 const-ness)。出于这个原因,左值引用有时被称为对非常量的左值引用(有时简称为非常量引用)。
在大多数情况下,引用的类型必须与被引用的对象类型相匹配(这条规则有一些例外,我们将在继承时讨论):

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

    double y { 6.0 };
    int& invalidRef { y }; //无效的
    double& invalidRef2 { x }; //无效的

    return 0;
}

不允许左值引用void(这有什么意义?)。


无法重新定位引用(更改为引用另一个对象)

一旦初始化,C++ 中的引用就不能重复引用,这意味着它不能被更改为引用另一个对象。新的C++程序员经常试图通过使用赋值为引用提供另一个变量来重新定位一个引用。这将编译并运行——但不能按预期运行。考虑以下程序:


#include <iostream>

int main()
{
    int x { 5 };
    int y { 6 };

    int& ref { x }; 

    ref = y; // 将6(y的值)分配给x(被引用的对象)
    // 以上行不会将REF更改为对y的引用!

    std::cout << x; // 用户期望这将打印5

    return 0;
}

也许令人惊讶的是,这会打印:
在这里插入图片描述
在表达式中计算引用时,它会解析为它所引用的对象。所以ref = y不会更改ref为 新的引用 y。相反,因为ref是 的别名x,所以表达式的计算结果就像它是写的一样x = y——并且因为y计算结果为值6,x所以被赋值为6。


左值引用范围和持续时间

引用变量遵循与普通变量相同的范围和持续时间规则:

#include <iostream>

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

     return 0;
} // x and ref 消亡

引用和被引用对象具有独立的生命周期

除了一个例外(我们将在下一课中介绍),引用的生命周期和它的所指对象的生命周期是独立的。换句话说,以下两个都是正确的:

  • 引用可以在被引用的对象之前被销毁。
  • 被引用的对象可以在引用之前被销毁。

当引用在引用之前被销毁时,引用不会受到影响。以下程序演示了这一点:

#include <iostream>

int main()
{
    int x { 5 };

    {
        int& ref { x };   // ref is a reference to x
        std::cout << ref; // prints value of ref (5)
    } // ref is destroyed here -- x is unaware of this

    std::cout << x; // prints value of x (5)

    return 0;
} // x destroyed here

以上打印:

55

当ref消亡时,变量x照常进行,没有意识到对它的引用已被销毁。


悬空引用

当被引用的对象在对它的引用之前被销毁时,该引用将引用一个不再存在的对象。这样的引用称为悬空引用。访问悬空引用会导致未定义的行为。
悬空引用很容易避免,但我们将在第9.5 课中展示这种情况在实践中可能发生的情况——通过左值引用传递。

引用不是对象

如果可能,编译器将通过用所指对象替换所有出现的引用来优化引用。但是,这并不总是可行的,在这种情况下,引用可能需要存储。
这也意味着术语“引用变量”有点用词不当,因为变量是有名字的对象,而引用不是对象。
因为引用不是对象,所以不能在需要对象的任何地方使用它们(例如,不能有对引用进行引用,因为左值引用必须引用可识别的对象)。在你需要一个作为对象的引用或一个可以被重新定位的引用的情况下,std::reference_wrapper(我们在第16.3课–聚合中讨论)提供了一个解决方案。

作为旁白…

考虑以下变量:

int var{};
int& ref1{ var };  // an lvalue reference bound to var
int& ref2{ ref1 }; // an lvalue reference bound to var

因为ref2是用引用 初始化ref1的,所以您可能会得出结论,这ref2是对引用的引用。它不是。因为ref1是对
的引用var,当在表达式(例如初始化程序)中使用时,ref1计算结果为var。所以ref2只是一个普通的左值引用(它的类型表示int&),绑定到var.
对引用的引用将具有语法int&&——但由于 C++ 不支持对引用的引用,因此在 C++11中重新使用该语法来指示右值引用(我们在第M.2 课中介绍过—— R 值参考)。

小测验时间

问题 #1

自己确定以下程序打印的值(不要编译程序)。

#include <iostream>

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

    std::cout << x << ref;

    int y{ 2 };
    ref = y;
    y = 3;

    std::cout << x << ref;

    x = 4;

    std::cout << x << ref;

    return 0;
}

答案:

112244

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值