表达式左值右值

本文详细解析了C++中的左值、右值及其属性valuecategory的概念,包括lvalue、xvalue、prvalue的区别与联系。文章还介绍了如何通过引用类型来区分左值与右值,并提供了丰富的示例代码帮助理解。

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

  左值右值是表达式的属性,该属性称为 value category。按该属性分类,每一个表达式属于下列之一:

lvalue

left value,传统意义上的左值

xvalue

expiring value, x值,指通过“右值引用”产生的对象

prvalue

pure rvalue,纯右值,传统意义上的右值(?)

  而 xvalue 和其他两个类型分别复合,构成:

lvalue + xvalue = glvalue

general lvalue,泛左值

xvalue + prvalue = rvalue

右值

1.区分

  ++x 与 x++ 假定x的定义为 int x=0;,那么前者是 lvalue,后者是rvalue。前者修改自身值,并返回自身;后者先创建一个临时对像,为其赋值,而后修改x的值,最后返回临时对像。区分表达式的左右值属性有一个简便方法:若可对表达式用 & 符取址,则为左值,否则为右值。比如

&obj , &*ptr , &ptr[index] , &++x

有效

&1729 , &(x + y) , &std::string("meow"), &x++

无效

  对于函数调用,根绝返回值类型不同,可以是lvalue、xvalue、prvalue:

  • The result of calling a function whose return type is an lvalue reference is an lvalue

  • The result of calling a function whose return type is an rvalue reference is an xvalue.

  • The result of calling a function whose return type is not a reference is a prvalue.

2.const vs non-const

  左值右值表达式都可以是constnon-const。比如,变量和函数的定义为:

1 string one("lvalue");
2 const string two("clvalue");
3 string three() { return "rvalue"; }
4 const string four() { return "crvalue"; }
View Code

  那么表达式:

表达式

分类

one

modifiable lvalue

two

const lvalue

three()

modifiable rvalue

four()

const rvalue

  引用

Type&

只能绑定到可修改的左值表达式

const Type&

可以绑定到任何表达式

Type&&

可绑定到可修改的左值或右值表达式

const Type&&

可以绑定到任何表达式

3.重载函数

 1 #include <iostream>
 2 #include <string>
 3 using namespace std;
 4 
 5 string one("lvalue");
 6 const string two("clvalue");
 7 string three() { return "rvalue"; }
 8 const string four() { return "crvalue"; }
 9 
10 void func(string& s)
11 {
12     cout << "func(string& s): " << s << endl;
13 }
14 
15 void func(const string& s)
16 {
17     cout << "func(const string& s): " << s << endl;
18 }
19 
20 void func(string&& s)
21 {
22     cout << "func(string&& s): " << s << endl;
23 }
24 
25 void func(const string&& s)
26 {
27     cout << "func(const string&& s): " << s << endl;
28 }
29 
30 int main()
31 {
32     func(one);
33     func(two);
34     func(three());
35     func(four());
36     return 0;
37 }
View Code

  结果:

func(string& s): lvalue
func(const string& s): clvalue
func(string&& s): rvalue
func(const string&& s): crvalue

  如果只保留const string& 和 string&& 两个重载函数,结果为:

func(const string& s): lvalue
func(const string& s): clvalue
func(string&& s): rvalue
func(const string& s): crvalue

4.右值引用

  C++0x第5章的第6段:

Named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues; rvalue references to functions are treated as lvalues whether named or not.
  • 具名右值引用被视为左值
  • 无名对对象的右值引用被视为x值
  • 对函数的右值引用无论具名与否都将被视为左值
 1 #include <iostream>
 2 #include <string>
 3 
 4 void F1(int&& a)
 5 {
 6     std::cout<<"F1(int&&) "<<a<<std::endl;
 7 }
 8 
 9 void F1(const int& a)
10 {
11     std::cout<<"F1(const int&) "<<a<<std::endl;
12 }
13 
14 void F2(int&& a)
15 {
16     F1(a);
17 }
18 
19 int main()
20 {
21     int && a=1;
22     F2(a);
23     F1(a);
24     F2(2);
25     F1(2);
26     return 0;
27 }
View Code

  结果

F1(const int&) 1
F1(const int&) 1
F1(const int&) 2
F1(int&&) 2

5.移动语义

  在这之前,如果写一个交换两个值的swap函数:

1 template <class T> swap(T& a, T& b)
2 {
3     T tmp(a);   // now we have two copies of a
4     a = b;      // now we have two copies of b
5     b = tmp;    // now we have two copies of tmp     
6 }
View Code

  之后

1 template <class T> swap(T& a, T& b)
2 {
3     T tmp(std::move(a));
4     a = std::move(b);   
5     b = std::move(tmp);
6 }
View Code

  std::move 接受左值或右值参数,并返回一个右值(其所作工作很简单)

1 template <class T>
2 typename remove_reference<T>::type&&
3 move(T&& a)
4 {
5     return a;
6 }
View Code

  要是的swap真正发挥作用,需要重载:

1 class T
2 {
3 public:
4     T(T&& );
5     T& operator = (T&& );
6 ...
View Code

6.模板参数类型

  为了对比左值引用和右值引用,一开始误打误撞,写了这样一个函数

1 template <typename Type> void Swap(Type&& sb1, Type&& sb2)
2 {
3     Type sb(sb1);
4     sb1 = sb2;
5     sb2 = sb;
6 }
View Code

  然后

1 int main()
2 {
3     int a=1, b=2;
4     Swap(a, b);
5     std::cout<<a<<" "<<b<<std::endl;
6     return 0;
7 }
View Code

  结果却是

2 2

  不用整数,换用一个自定义的类型试试看:

 1 class A 
 2 {
 3 public:
 4     A() {
 5         std::cout << "Default constructor." << std::endl;
 6         m_p = NULL;
 7     }
 8 
 9     ~A() {
10         std::cout << "Destructor." << std::endl;
11         delete m_p;
12     }
13 
14     explicit A(const int n) {
15         std::cout << "Unary constructor." << std::endl;
16         m_p = new int(n);
17     }
18 
19     A(const A& other) {
20         std::cout << "Copy constructor." << std::endl;
21         if (other.m_p) {
22             m_p = new int(*other.m_p);
23         } else {
24             m_p = NULL;
25         }
26     }
27 
28     A(A&& other) {
29         std::cout << "Move constructor." << std::endl;
30         m_p = other.m_p;
31         other.m_p = NULL;
32     }
33 
34     A& operator=(const A& other) {
35         std::cout << "Copy assignment operator." << std::endl;
36         if (this != &other) {
37             delete m_p;
38             if (other.m_p) {
39                 m_p = new int(*other.m_p);
40             } else {
41                 m_p = NULL;
42             }
43         }
44         return *this;
45     }
46 
47     A& operator=(A&& other) {
48         std::cout << "Move assignment operator." << std::endl;
49         if (this != &other) {
50             delete m_p;
51             m_p = other.m_p;
52             other.m_p = NULL;
53         }
54         return *this;
55     }
56 
57     int get() const {
58         return m_p ? *m_p : 0;
59     }
60 
61 private:
62     int * m_p;
63 };
64 
65 int main()
66 {
67     A a(1);
68     A b(2);
69     Swap2(a, b);
70     std::cout<<a.get()<<" "<<b.get()<<std::endl;
71     return 0;
72 }
View Code

  结果

Unary constructor.
Unary constructor.
Copy assignment operator.
Copy assignment operator.
2 2
Destructor. Destructor.

  只出现了两个对象,那么Swap中的临时对象去哪儿了?

C++0x 14.8.2.1

If P is a cv-qualified type, the top level cv-qualifiers of P’s type are ignored for type deduction. If P is a reference type, the type referred to by P is used for type deduction. If P is an rvalue reference to a cv unqualified template parameter and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction. 
1 template <class T> int f(T&&);
2 template <class T> int g(const T&&);
3 int i;
4 int n1 = f(i); // calls f<int&>(int&)
5 int n2 = f(0); // calls f<int>(int&&)
6 int n3 = g(i); // error: would call g<int>(const int&&), which
7 // would bind an rvalue reference to an lvalue
View Code
 也就是前面提到的
1 template <typename Type> void Swap(Type&& sb1, Type&& sb2)

  参数推导后

1 void Swap<int&>(int& sb1, int& sb1)

7.参考

转载于:https://www.cnblogs.com/blueoverflow/p/4817395.html

<think>好的,我现在需要准备关于左值的讲解。首先,我得回想一下自己对这些概念的理解,确保准确无误。左值是C++中的概念,对吗?我记得左值指的是可以取地址的、有持久性的表达式,而则是临时的、即将被销毁的。不过,具体的区别和应用场景可能需要进一步理清。 首先,我应该从基本定义入手。左值(lvalue)通常指的是可以出现在赋语句侧的表达式,比如变量、对象等,它们有明确的内存地址。而(rvalue)则通常出现在赋语句的侧,是临时生成的、无法取地址的,比如字面量、临时对象或者表达式计算结果。 接下来,我需要考虑C++11引入的引用和移动语义,这可能和左值的现代用法有关。引用(用&&表示)允许我们绑定到,从而实现资源的转移而非复制,提升效率。例如,移动构造函数和移动赋运算符就是利用引用来避免不必要的深拷贝。 用户可能需要了解左值的区别,以及如何在实际编程中应用这些概念。比如,函数参数传递、返回优化、完美转发等场景。这时候应该举一些例子,说明哪些表达式左值,哪些是,以及如何通过类型转换(如std::move)将左值转换为引用。 需要注意的是,有时候左值也可以被当作使用,特别是在使用std::move时。这时候原本的左值会被标记为,允许其资源被移动而非复制。不过,之后访问该左值可能会有未定义行为,需要谨慎处理。 可能还需要提到左值引用和引用的区别。左值引用用&声明,只能绑定到左值引用用&&声明,只能绑定到。但如果有const左值引用,也可以绑定到,这是例外情况。例如,const int& a = 5;是合法的,因为常量左值引用可以绑定到。 另外,模板推导中的引用折叠规则和完美转发(std::forward)也是基于左值的概念。当使用通用引用(T&&)时,根据传入的实参是左值还是,T会被推导为不同的类型,从而实现完美转发,保持参数的类别。 不过,在讲解的时候需要避免过于深入复杂的模板元编程,保持基础而清晰的解释。应该用简单的代码示例来说明,比如: int a = 5; // a是左值 int b = a; // a是左值,作为使用 int c = std::move(a); // std::move将左值a转换为引用,允许移动 另外,函数返回的例子:当函数返回一个临时对象时,该返回。如果返回的是局部变量,在C++11之后可能会被当作,从而触发移动语义而非拷贝。 可能存在的误区是认为引用就是,实际上引用本身是左值,因为它有名字,可以取地址。例如: void f(int&& x) { // x在这里是左值,因为它有名字 int y = x; // 这里是拷贝,不是移动 int z = std::move(x); // 需要再次使用std::move转换为 } 这里需要强调,即使参数是引用,但在函数内部使用时,它被视为左值,因此需要显式使用std::move来启用移动语义。 总结一下,讲解的结构应该是: 1. 左值的定义与区别 2. 左值引用和引用的语法与用法 3. 移动语义和std::move的作用 4. 实际应用示例和注意事项 5. 相关的高级主题(如完美转发)的简要提及 在写作过程中,要确保例子正确,术语准确,并且语言通俗易懂,适合不同层次的读者理解。需要检查是否有错误,比如混淆了引用和本身,或者对std::move的作用描述不准确。另外,要注意中文表达流畅,避免直译英文术语导致的生硬。</think>### 左值(Lvalue)与(Rvalue)讲解 #### 1. **基本定义** - **左值(Lvalue)** 指有明确内存地址、可被标识的表达式,通常可出现在赋语句的侧。 **特点**:持久性,可多次使用。 **示例**:变量、对象、返回左值引用的函数调用。 ```cpp int a = 10; // a 是左值 int* p = &a; // 可对 a 取地址 a = 20; // 可赋 ``` - **(Rvalue)** 指临时的、无持久性的表达式,通常只能出现在赋语句的侧。 **特点**:短暂性,用完即销毁。 **示例**:字面量、临时对象、表达式计算结果。 ```cpp int b = 5; // 5(字面量) int c = a + b; // a + b 的结果是 ``` #### 2. **左值引用与引用** - **左值引用(`&`)** 只能绑定到左值,用于修改或延长左值的生命周期。 ```cpp int x = 10; int& ref = x; // 正确:左值引用绑定到左值 // int& ref = 5; // 错误:左值引用不能绑定到 ``` - **引用(`&&`)** 只能绑定到,用于实现移动语义和完美转发。 ```cpp int&& rref = 10; // 正确:引用绑定到 // int&& rref = x; // 错误:引用不能绑定到左值 ``` **例外**:`const` 左值引用可绑定到。 ```cpp const int& cref = 5; // 合法 ``` #### 3. **移动语义与 `std::move`** - **移动语义** 通过引用“窃取”临时对象的资源,避免深拷贝,提升效率。 ```cpp class String { public: // 移动构造函数 String(String&& other) { data = other.data; // 直接转移资源 other.data = nullptr; // 置空原对象 } }; ``` - **`std::move` 的作用** 将左值强制转换为引用,触发移动语义。 ```cpp std::vector<int> v1 = {1, 2, 3}; std::vector<int> v2 = std::move(v1); // 移动构造,v1 变为空 ``` **注意**:使用 `std::move` 后,原对象状态不确定,需避免再次访问。 #### 4. **实际应用示例** - **场景1:函数参数传递** ```cpp void process_copy(const std::string& s); // 接受左值(拷贝) void process_move(std::string&& s); // 仅接受(移动) std::string str = "Hello"; process_copy(str); // 传递左值,触发拷贝 process_move(std::move(str)); // 传递,触发移动 ``` - **场景2:返回优化(RVO)** ```cpp std::string create_string() { return "Temp"; // 返回,可能触发移动或直接构造 } std::string s = create_string(); // 无需拷贝 ``` #### 5. **注意事项** - **引用本身是左值** ```cpp void f(int&& x) { int y = x; // x 是左值(有名字),触发拷贝 int z = std::move(x); // 需显式转换为 } ``` - **避免滥用 `std::move`** 仅在需要转移资源时使用,否则可能导致意外行为。 #### 6. **高级主题(简要提及)** - **完美转发(`std::forward`)** 保留参数的类别(左值/),常用于泛型编程。 ```cpp template<typename T> void wrapper(T&& arg) { // 保持 arg 的原始类别(左值) func(std::forward<T>(arg)); } ``` --- ### 总结 - **左值**:有地址、可重复使用的表达式。 - ****:临时、一次性使用的表达式。 - **引用**:实现移动语义,提升性能。 - **关键操作**:`std::move` 转换左值,`std::forward` 保持类别。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值