C++11右值引用(一看即懂)

本文介绍了C++11中的右值引用,作为C++的重要新特性,它解决了C++98/03标准中无法直接引用右值的问题。右值引用用'&&'表示,用于实现移动语义和完美转发。文章对比了左值引用和右值引用,并强调了右值引用可以修改右值,而常量右值引用的实际用途有限。此外,还提供了区分左值和右值的方法,并给出了简化的记忆表格。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++11右值引用(一看即懂)

前一节中提到,在 C++98/03 标准的基础上,C++11 标准对 C++ 语言增添了约 140 个新特性。本节要讲的右值引用就是众多新特性中的一个,同时也是最重要的特性之一。

很多初学者都感觉右值引用晦涩难懂,其实不然。右值引用只不过是一种新的 C++ 语法,真正理解起来有难度的是基于右值引用引申出的 2 种 C++ 编程技巧,分别为移动语义和完美转发。本节先给读者讲解什么是右值引用以及它的基本用法,至于移动语义和完美转发则放到后续章节讲解。

C++左值和右值

右值引用可以从字面意思上理解,指的是以引用传递(而非值传递)的方式使用 C++ 右值。

在 C++ 或者 C 语言中,一个表达式(可以是字面量、变量、对象、函数的返回值等)根据其使用场景不同,分为左值表达式和右值表达式。确切的说 C++ 中左值和右值的概念是从 C 语言继承过来的。
值得一提的是,左值的英文简写为“lvalue”,右值的英文简写为“rvalue”。很多人认为它们分别是"left value"、“right value” 的缩写,其实不然。lvalue 是“loactor value”的缩写,可意为存储在内存中、有明确存储地址(可寻址)的数据,而 rvalue 译为 “read value”,指的是那些可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据)。

通常情况下,判断某个表达式是左值还是右值,最常用的有以下 2 种方法。

  1. 可位于赋值号(=)左侧的表达式就是左值;反之,只能位于赋值号右侧的表达式就是右值。举个例子:
int a = 5;
5 = a; //错误,5 不能为左值

其中,变量 a 就是一个左值,而字面量 5 就是一个右值。值得一提的是,C++ 中的左值也可以当做右值使用,例如:

int b = 10; // b 是一个左值
a = b; // a、b 都是左值,只不过将 b 可以当做右值使用
  1. 有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。

以上面定义的变量 a、b 为例,a 和 b 是变量名,且通过 &a 和 &b 可以获得他们的存储地址,因此 a 和 b 都是左值;反之,字面量 5、10,它们既没有名称,也无法获取其存储地址(字面量通常存储在寄存器中,或者和代码存储在一起),因此 5、10 都是右值。

注意,以上 2 种判定方法只适用于大部分场景。由于本节主要讲解右值引用,因此这里适可而止,不再对 C++ 左值和右值做深度剖析,感兴趣的读者可自行研究。

C++右值引用

前面提到,其实 C++98/03 标准中就有引用,使用 “&” 表示。但此种引用方式有一个缺陷,即正常情况下只能操作 C++ 中的左值,无法对右值添加引用。举个例子:

int num = 10;
int &b = num; //正确
int &c = 10; //错误

如上所示,编译器允许我们为 num 左值建立一个引用,但不可以为 10 这个右值建立引用。因此,C++98/03 标准中的引用又称为左值引用。

注意,虽然 C++98/03 标准不支持为右值建立非常量左值引用,但允许使用常量左值引用操作右值。也就是说,常量左值引用既可以操作左值,也可以操作右值,例如:

int num = 10;
const int &b = num;
const int &c = 10;

我们知道,右值往往是没有名称的,因此要使用它只能借助引用的方式。这就产生一个问题,实际开发中我们可能需要对右值进行修改(实现移动语义时就需要),显然左值引用的方式是行不通的。

为此,C++11 标准新引入了另一种引用方式,称为右值引用,用 “&&” 表示。

话说,C++标准委员会在选定右值引用符号时,既希望能选用现有 C++ 内部已有的符号,还不能与 C++ 98 /03 标准产生冲突,最终选定了 2 个 ‘&’ 表示右值引用。

需要注意的,和声明左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化,比如:

int num = 10;
//int && a = num;  //右值引用不能初始化为左值
int && a = 10;

和常量左值引用不同的是,右值引用还可以对右值进行修改。例如:

int && a = 10;
a = 100;
cout << a << endl;
程序输出结果为 100。

另外值得一提的是,C++ 语法上是支持定义常量右值引用的,例如:

const int&& a = 10;//编译器不会报错

但这种定义出来的右值引用并无实际用处。一方面,右值引用主要用于移动语义和完美转发,其中前者需要有修改右值的权限;其次,常量右值引用的作用就是引用一个不可修改的右值,这项工作完全可以交给常量左值引用完成。

学到这里,一些读者可能无法记清楚左值引用和右值引用各自可以引用左值还是右值,这里给大家一张表格,方便大家记忆:

表 1 C++左值引用和右值引用
引用类型	|可以引用的值类型	|使用场景|--|--|--

其实,C++11 标准中对右值做了更细致的划分,分别称为纯右值(Pure value,简称 pvalue)和将亡值(eXpiring value,简称 xvalue )。其中纯右值就是 C++98/03 标准中的右值(本节中已经做了大篇幅的讲解),而将亡值则指的是和右值引用相关的表达式(比如某函数返回的 T && 类型的表达式)。对于纯右值和将亡值,都属于右值,读者知道即可,不必深究。

<think>嗯,用户问的是如何理解移动构造函数和引用的概念。首先,我需要确保自己对这个概念有清晰的理解。引用C++11引入的特性,用于实现移动语义,避免不必要的拷贝,提高性能。移动构造函数则是利用引用,将资源从个临时对象转移到新对象,而不是复制。 用户给出的那段话里提到了几个关键点:移动而非复制、引用作为参数、源对象资源被移动后不再指向原资源、源对象可重新赋或销毁。我需要把这些点逐解释清楚。 可能用户是C++初学者,或者对移动语义不太熟悉,所以需要从基础讲起。先解释什么是引用,然后说明移动构造函数的作用,和复制构造函数的区别。还要举例说明,比如使用std::vector或字符串这样的资源密集型类,移动如何提升性能。 需要注意的是,用户可能对“”和“左”的区别不太清楚,可能需要简单解释下。左是有持久状态的变量,通常是临时对象。移动构造函数接受引用参数,意味着它只能绑定到临时对象或显式转换为的对象。 另外,要强调移动后的源对象处于有效但未定义的状态,可以被重新赋或销毁,但不能再次使用其原有资源。比如,移动个动态数组指针后,源对象的指针应该置空,避免双重释放。 可能用户在实际编程中遇到过性能问题,想知道如何优化,所以需要结合应用场景,说明移动语义的优势。例如,在返回大型对象或容器时,使用移动语义可以避免深拷贝,减少内存分配和复制的时间。 还要注意代码示例的正确性,用中文注释,确保用户能看。比如展示移动构造函数的具体实现,如何转移资源,并将源对象的指针置空。 最后,总结移动构造函数的要点,帮助用户形成清晰的理解框架。确保回答结构清晰,分点说明,用词准确,避免术语混淆。</think>我们可以通过以下结构逐步理解这段话: (以C++语言为例,用中文场景拆解) 1. **基础概念铺垫** ```cpp // 传统复制构造函数(性能低) class String { public: char* data; // 复制构造函数:深拷贝 String(const String& other) { data = new char[strlen(other.data)+1]; strcpy(data, other.data); // 完全复制内容 } }; ``` 2. **问题引入** 当处理临时对象时: ```cpp String createString() { String temp("hello"); return temp; // 触发复制构造:但temp马上就要被销毁了... } ``` 3. **移动构造函数原理** ```cpp // 移动构造函数(C++11新增) class String { public: // 参数是引用(&&) String(String&& other) noexcept { data = other.data; // 直接接管资源 other.data = nullptr; // 置空源对象指针 } }; ``` 4. **执行过程对比** ```text 传统方式: 源对象:[h][e][l][l][o][\0] ← 复制 → 新对象:[h][e][l][l][o][\0] (两次内存分配,两次数据拷贝) 移动语义: 源对象:[h][e][l][l][o][\0] → 移动 → 新对象:[h][e][l][l][o][\0] 源对象变为:nullptr (仅指针赋,零数据拷贝) ``` 5. **关键特征总结** - ✅ 只转移所有权(资源指针交接) - ✅ 源对象进入有效但未定义状态(可安全析构) - ✅ 适用于即将消亡的对象() - ✅ 性能提升:时间复杂度从O(n)降为O(1) 6. **应用场景示例** ```cpp std::vector<String> v; v.push_back(String("test")); // 自动调用移动构造函数 String s1 = std::move(s2); // 显式转移资源 ``` 7. **注意事项** - 移动后源对象不应再访问原资源 - 需确保类具有有效的移动构造函数 - 对于简单类型(如int/double)移动=复制 - 需要配合`std::move()`显式标记可移动对象 这种机制显著优化了包含动态内存、文件句柄等资源的对象操作效率,是现代C++高性能编程的重要基础。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

睿科知识云

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值