C++基础教程面向对象(学习笔记(83))

R值参考

我们需要了解l值和r值,然后告诉你不要太担心它们。这是C ++ 11之前的建议。但是理解C ++ 11中的移动语义需要重新审视该主题。所以我们现在就这样做。

L值和r值

尽管名称中包含“值”一词,但l值和r值实际上不是值的属性,而是表达式的属性。

C ++中的每个表达式都有两个属性:一个类型(用于类型检查)和一个值类别(用于某些类型的语法检查,例如是否可以将表达式的结果赋值)。在C ++ 03及更早版本中,l值和r值是唯一可用的两个值类别。

这些表达式是l值和r值的实际定义令人惊讶地复杂,因此我们将简单地对主题进行简化,这对于我们的目的来说基本上是足够的。

将l值(也称为定位器值)视为函数或对象(或计算为函数或对象的表达式)是最简单的。所有l值都分配了内存地址。

当最初定义l值时,它们被定义为“适合在赋值表达式的左侧的值”。但是,稍后,const关键字被添加到语言中,并且l值被分成两个子类:可修改的l值(non-const)和不可修改的l值(const)。

想到一个r值是最简单的作为“一切不是l值”。这尤其包括文字(例如5),临时值(例如x + 1)和匿名对象(例如Fraction(5,2))。r值通常是针对它们的值进行评估,具有表达式范围(它们在它们所在的表达式的末尾处死亡),并且不能分配给它们。这种非赋值规则是有意义的,因为赋值会对对象应用副作用。由于r值具有表达式范围,如果我们要为r值赋值,那么在我们有机会在下一个表达式中使用指定值之前,r值将超出范围(这使得赋值无用)或者我们必须使用在表达式中多次应用副作用的变量(现在你应该知道它会导致未定义的行为!)。

为了支持移动语义,C ++ 11引入了3个新的值类别:pr值,x值和gl值。我们将在很大程度上忽略这些,因为理解它们对于有效地学习或使用移动语义是没有必要的。如果您有兴趣,cppreference.com有大量的表达式列表,这些表达式符合各种值类别的要求,以及有关它们的更多详细信息。

L值参考

在C ++ 11之前,C ++中只存在一种类型的引用,因此它被称为“引用”。但是,在C ++ 11中,它有时被称为l值引用。L值引用只能用可修改的l值初始化。

扫描二维码关注公众号,回复: 4431411 查看本文章
L值参考 可以初始化 可以修改
可修改的l值
不可修改的l值 没有 没有
R值 没有 没有
L值参考 可以初始化 可以修改
L值引用const 没有
不可修改的l值 没有
R值 没有

对const对象的L值引用特别有用,因为它们允许我们将任何类型的参数(l值或r值)传递给函数,而无需复制参数。

R值参考

C ++ 11添加了一种称为r值引用的新类型引用。r值引用是一个用r值(仅)初始化的引用。使用单个&符创建l值引用时,使用双符号创建r值引用:

int x = 5;
int &lref = x; // l值引用用l值x初始化
int &&rref = 5; // 用r值5初始化的r值参考

无法使用l值初始化R值引用。

R值参考 可以初始化 可以修改
可修改的l值 没有 没有
不可修改的l值 没有 没有
R值
对const的R值引用 可以初始化 可以修改
可修改的l值 没有 没有
不可修改的l值 没有 没有
R值 没有

R值引用有两个有用的属性。首先,r值引用将它们初始化的对象的生命周期延长到r值引用的生命周期(对const对象的l值引用也可以这样做)。其次,非常量r值引用允许您修改r值!

我们来看看一些例子:

#include <iostream>
 
class Fraction
{
private:
	int m_numerator;
	int m_denominator;
 
public:
	Fraction(int numerator = 0, int denominator = 1) :
		m_numerator(numerator), m_denominator(denominator)
	{
	}
 
	friend std::ostream& operator<<(std::ostream& out, const Fraction &f1)
	{
		out << f1.m_numerator << "/" << f1.m_denominator;
		return out;
	}
};
 
int main()
{
	Fraction &&rref = Fraction(3, 5); // r值引用临时分数
	std::cout << rref << '\n';
 
	return 0;
} //rref(和临时分数)超出了范围

该程序打印:

3/5
作为一个匿名对象,Fraction(3,5)通常会在定义它的表达式的末尾超出范围。但是,由于我们用它初始化一个r值引用,它的持续时间会延长到块的结尾。然后我们可以使用该r值引用来打印Fraction的值。

现在让我们来看一个不太直观的例子:

#include <iostream>
 
int main()
{
    int &&rref = 5; // 因为我们用文字初始化r值引用,所以在这里创建一个值为5的临时值
    rref = 10;
    std::cout << rref;
 
    return 0;
}

该程序打印:

10
虽然使用文字值初始化r值引用然后能够更改该值可能看起来很奇怪,但在使用文字初始化r值时,会从文字构造临时值,以便引用引用临时值对象,而不是文字值。

R值参考不经常以上述任何一种方式使用。

R值引用作为函数参数

R值引用更常用作函数参数。当您希望对l值和r值参数具有不同的行为时,这对于函数重载最有用。

void fun(const int &lref) // l-value arguments will select this function
{
	std::cout << "l-value reference to const\n";
}
 
void fun(int &&rref) // r-value argument will select this function
{
	std::cout << "r-value reference\n";
}
 
int main()
{
	int x = 5;
	fun(x); // l-value argument calls l-value version of function
	fun(5); // r-value argument calls r-value version of function
 
	return 0;
}

这打印:

l-value reference to const
r-value reference

如您所见,当传递l值时,重载函数已解析为具有l值引用的版本。当传递r值时,重载函数解析为具有r值引用的版本(这被认为是比对const的l值引用更好的匹配)。

你为什么要这样做?我们将在下一课中更详细地讨论这个问题。不用说,它是移动语义的重要组成部分。

返回r值引用

你应该几乎永远不会返回一个r值引用,因为你几乎永远不会返回一个l值引用。在大多数情况下,当引用的对象超出函数末尾的范围时,您最终将返回挂起引用。

Quiz Time:

1)说明以下哪个字母语句不能编译:

int main()
{
	int x;
 
	// l值参考
	int &ref1 = x; // A
	int &ref2 = 5; // B
 
	const int &ref3 = x; // C
	const int &ref4 = 5; // D
 
	//r值参考
	int &&ref5 = x; // E
	int &&ref6 = 5; // F
 
	const int &&ref7 = x; // G
	const int &&ref8 = 5; // H
	
	return 0;
}

解决方案

B,E和G不会编译。

猜你喜欢

转载自blog.csdn.net/qq_41879485/article/details/84778599