• 为什么C/C++等少数编程语言要区分左右值?
  1. 历史原因: C语言作为一门古老的编程语言,其设计初衷是为了在硬件资源有限的系统上进行高效的编程,因此其语法和语义设计相对较简单。左值和右值的概念最初是由C语言引入的,而C++则在此基础上进行了扩展。这种设计历史原因导致了C/C++等语言中需要区分左值和右值。

  2. 内存管理: C/C++是一种比较底层的编程语言,直接操作内存。左值通常对应于具名变量,它们在内存中有固定的存储位置,可以被多次引用。而右值通常对应于临时的表达式或者字面值,它们在内存中没有固定的存储位置,只是临时的值。区分左值和右值有助于编译器在内存管理方面做出合适的优化,例如对右值进行临时对象的优化,避免不必要的内存拷贝。

  3. 语法规则: C/C++等语言中有许多操作只能对左值或只能对右值进行,例如赋值语句中等号左边必须是左值,而函数调用中实参必须是右值。如果没有左值和右值的区分,这些语法规则就无法定义和解释。这种语义上的区别使得编程语言可以在语法层面对不同类型的表达式和变量进行限制和处理。

  4. 性能优化: 区分左值和右值有助于编译器进行性能优化。例如,C++11引入的移动语义(Move Semantics)就是利用了右值的特性,通过将临时对象的资源(如内存)直接转移给目标对象,避免了不必要的拷贝操作,从而提高了性能。

总的来说,C/C++等语言区分左值和右值是为了在内存管理、语义和性能优化等方面提供更多的灵活性和效率。然而,对于初学者来说,理解和使用左值和右值可能会有一定的难度,因此在编写代码时需要仔细考虑它们的语法和语义规则。

  • 对于方法来说,传递左值或右值作为参数各有不同的用途和行为:

    • 传递左值:将左值传递给方法,方法可以对其进行修改,并且对传递的左值的修改会在函数调用后保持。这对于需要在方法内部修改传递的对象,并且希望在函数调用后对原对象产生影响的情况非常有用。
    • 传递右值:将右值传递给方法,方法可以从其获取值或者资源,并在方法内部进行处理,例如移动语义(move semantics)的情况下,可以将右值的资源转移到方法内部,从而避免不必要的复制操作。传递右值还可以用于实现完美转发(perfect forwarding),将右值引用传递给下游函数。

在一般情况下,对于方法的入参:
如果方法需要修改传递的对象或者希望在函数调用后对原对象产生影响,则传递左值作为参数。
如果方法只需要获取传递的值或者资源,并且不需要修改传递的对象,或者希望实现移动语义或完美转发,则传递右值作为参数。这可以根据方法的具体需求和语义来进行选择。

需要注意的是,在 C++11 及之后的版本中,通过使用引用折叠规则(reference collapsing rules)和右值引用(rvalue reference),可以实现在方法中同时接收左值和右值的参数,从而在函数调用时不需要显式地区分左值和右值。这可以提供更加灵活和高效的参数传递方式。

表达式: 由运算符和运算对象构成的计算式。字面量、变量、函数返回值都是表达式。 表达式返回的结果,有两个属性:类型和值类别。

左值(lvalue)

  • 可以出现在赋值运算符左边的表达式。可以取地址的表达式
  • 可以通过 ‘&’ 取到左值的地址
  • 可修改的左值可用作 ‘=’ 的左操作数
  • 可用于初始化左值引用
void modifyLeftValue(int& x) {x = x + 1; // 修改传递的左值
}int main(){int num = 10;modifyLeftValue(num); // 传递左值给方法std::cout << num << std::endl; // 输出 11,因为方法内部修改了传递的左值return 0;
}
//变量:包括普通变量、引用(包括左值引用和常量引用)以及指针。
int a = 10;        // 普通变量
int& b = a;       // 左值引用
const int& c = a; // 常量引用
int* ptr = &a;    // 指针//数组元素:数组的元素可以通过索引访问,并且可以用作左值。
int arr[5] = {1, 2, 3, 4, 5};
arr[0] = 10; // 数组元素作为左值//类成员:类的成员变量可以作为左值,包括普通成员变量和引用类型的成员变量。
class MyClass {public:int x;int& y;MyClass(int a, int b) : x(a), y(b) {}
};MyClass obj(1, 2);
obj.x = 10; // 类成员变量作为左值
obj.y = 20; // 类引用成员变量作为左值//解引用指针:解引用指针可以得到一个左值。
int a = 10;
int* ptr = &a;
*ptr = 20; // 解引用指针作为左值//函数返回的引用:如果一个函数返回一个引用类型的值,那么该返回值可以作为左值。
int& getReference() {static int x = 10;return x;
}
getReference() = 30; // 函数返回的引用作为左值

常见的左值表达式包括:

  • 变量,对象。例如, int i
  • 返回类型为左值引用的函数调用或重载运算符表达式,例如 str1 = str2、++it
  • 所有内建的赋值及复合赋值表达式,例如 a = b、a += b
  • 内建的前置自增、前置自减,例如 ++i、–i
  • 内建的间接寻址表达式,例如 *p
  • 字符串字面量,例如 “hello world”
  • 内建的下标表达式, 例如 a[n]
  • 迭代器是左值,例如 vecotr::iterator iter
//常量(Constants):常量是无法改变的值,例如整数、浮点数等。尽管它们在语法上可以被视为左值,但它们不能作为左值引用的目标,因此不是广义左值。
const int a = 10;
a = 20; // 错误,a 是左值但不是广义左值
//数组(Arrays):数组在语法上可以被视为左值,但不能作为左值引用的目标,因此不是广义左值。
int arr[5];
arr = nullptr; // 错误,arr 是左值但不是广义左值

广义左值(Generalized lvalue)

  • 指可以出现在赋值运算符左边的表达式,包括左值(lvalue)和一些具有类似于左值性质的表达式。C++11 标准引入了广义左值的概念,扩展了左值的范围,使得一些原本不能作为左值的表达式也可以用于赋值操作。
  • 广义左值并不是所有可以出现在赋值运算符左边的表达式都可以作为左值使用,因为它们仍然受到语言规则和类型系统的限制。广义左值的引入主要是为了支持更灵活的赋值和修改操作,提供更方便的编程方式。
//临时对象(Temporary objects):临时对象是由表达式生成的临时对象,例如函数返回的临时对象。虽然临时对象在语法上可以被视为左值,但它们不能作为左值引用的目标,因此不是真正的左值。
int foo() { return 42; }
foo() = 10; // 错误,foo() 是广义左值但不是左值//表达式的结果(Result of an expression):一些表达式的结果可以被视为广义左值,例如赋值操作符(=)的返回值。尽管它们在语法上可以被视为左值,但它们不能作为左值引用的目标,因此不是真正的左值。
int a = 10;
int b = 20;
(a + b) = 30; // 错误,(a + b) 是广义左值但不是左值

右值(rvalue)

  • 指临时的、不可取地址的表达式;可以通过使用双 ampersand (&&) 作为引用类型来实现。
  • 右值引用允许将临时对象(右值)绑定到引用上,并在方法中修改它们。C++ 的语言规范,对于右值引用绑定的临时对象,其生命周期通常在方法退出后结束,因此在方法退出后,对这个临时对象的修改可能会导致未定义行为。
  • 右值引用的设计初衷是用于优化性能,例如通过避免不必要的对象复制
  • 要避免对右值引用绑定的临时对象进行修改后导致未定义行为,可以使用 std::move 函数将右值引用转换为左值引用,从而使得在方法中修改的是原始对象而不是临时对象。
void processRightValue(std::string&& str) {// Processing: Hello , str : 0x308c32280std::cout << "Processing: " << str <<" , str : "<<&str<< std::endl; // 使用传递的右值
}int main(){processRightValue("Hello"); // 传递右值给方法// 注意:传递的右值字符串在方法调用后会被销毁,因为它是临时的,不可取地址的表达式return 0;
}//void processRightValue(std::string&& str) {std::cout << "Processing: " << str <<" , str : "<<&str<< std::endl;str = "xxx";
}int main(){std::string val = "hello";//std::move(val) 和 std::forward<std::string>(val) 传递进去的值被修改的调用后 都能保留processRightValue(std::move(val));std::cout<<"--val : "<<val<<std::endl;//xxxreturn 0;
}

纯右值(Pure Right-hand-side Value)

  • 是一种表达式类型,通常可以作为右值引用(Rvalue Reference)的绑定目标。纯右值是指在表达式求值后不再被使用的临时对象,它可以被移动语义(Move Semantics)优化,从而避免不必要的拷贝操作,提高代码效率。
//临时对象:例如通过调用函数返回值、执行类型转换、进行算术运算等产生的临时对象都可以是纯右值。
int x = 1 + 2; // 表达式 1 + 2 是纯右值//字面量:例如整型、浮点型、字符型等字面量常量都可以是纯右值。
字面量:例如整型、浮点型、字符型等字面量常量都可以是纯右值。//强制类型转换产生的临时对象:例如使用 static_cast、dynamic_cast、const_cast、reinterpret_cast 等强制类型转换操作时,产生的临时对象可以是纯右值。
int z = static_cast<int>(3.14); // 强制类型转换产生的临时对象是纯右值//在 C++11 标准引入右值引用之后,纯右值的概念扩展了,包括了具有右值引用类型的表达式也可以被视为纯右值。
int a = 1;
int&& rvalue_ref = std::move(a); // 右值引用是纯右值

纯右值和右值在实际使用中可能有一些细微的区别和限制,具体取决于编译器和语言标准的实现。

  • 纯右值和右值之间存在以下几个区别:

    • 定义:纯右值是指在表达式求值后不再被使用的临时对象,可以作为右值引用(Rvalue Reference)的绑定目标。而右值是指在表达式求值后,其值可以被移动(Move)但不可以被复制(Copy)的表达式,包括纯右值和具有右值引用类型的表达式。
    • 生命周期:纯右值的生命周期较短,通常仅在表达式求值期间有效,并且无法被持久化。而右值则可能具有更长的生命周期,例如通过右值引用延长其生命周期。
    • 可用性:纯右值通常在表达式求值后不再可用,不能再进行其他操作,因为它们是临时对象。而右值可能具有更长的可用性,例如通过右值引用继续使用它们。
    • 移动语义:纯右值在进行赋值或传递时,可以通过移动语义(Move Semantics)进行高效的资源转移,避免不必要的拷贝操作。而右值也可以通过移动语义进行资源转移,但具有更广泛的适用性,包括纯右值和具有右值引用类型的表达式。
    • 使用场景:纯右值主要用于临时对象的创建和传递,例如通过函数返回值、执行类型转换、进行算术运算等产生的临时对象。而右值则可以更广泛地用于各种场景,包括作为函数参数、返回值、成员变量、局部变量等。右值引用还可以用于实现移动语义、完美转发等高效的 C++ 特性。

将亡值(Expiring Value)

  • 亡值实际上是 C++ 11 引入了右值引用的概念而引入的,在C++ 11 之前,右值可以等价于纯右值。 亡值与右值引用息息相关。
  • 将亡值指即将被销毁的临时对象或右值引用。C++11引入了将亡值的概念,它是一种介于左值和纯右值之间的临时对象。
    *
  • 需要注意的是,将亡值在转移资源所有权的同时,会让原始对象变为无效状态。因此,在使用将亡值时需要小心,确保不会在对象变为无效状态后再次访问它。同时,也需要了解不同编译器对将亡值的优化行为可能有所不同,因此在编写跨平台或可移植性较强的代码时需要谨慎使用将亡值。

将亡值主要用于在C++11中引入的移动语义,允许将资源(如内存或文件句柄)从一个对象转移到另一个对象,而不进行昂贵的资源拷贝操作。将亡值通常出现在以下两种情况下:

  1. 将亡值作为函数返回值:当函数返回一个临时对象时,C++编译器可以将其优化为将亡值,从而避免不必要的拷贝操作,提高性能。
std::string get_string() {// 返回一个临时对象,将亡值return std::string("Hello");
}std::string str = get_string(); // 将亡值转移到str中
  1. 将亡值作为函数参数:当将一个临时对象传递给函数时,C++编译器可以将其优化为将亡值,从而避免不必要的拷贝操作,提高性能。
void process_string(std::string&& str) {// 处理str,这里str是将亡值
}std::string str = "Hello";
process_string(std::move(str)); // 将亡值转移到process_string函数中

c++ 左值 广义左值 右值 纯右值 将亡值相关推荐

  1. 左值右值将亡值泛左值

    左右值概念 简单理解 左值:赋值运算符左边的变量,可以接受右边值,例如 int a = 10; a就是一个左值右值:赋值运算符右边的值,这个值可以是一个变量也可以是一个常量,例如 int a = 10 ...

  2. 左值、右值、将亡值 | 左值引用、右值引用

    左值.右值.将亡值 左值.右值 左值(lvalue)中l指的是location,是有明确地址的,可寻址的值.右值(rvalue)中r指的是read,是可读的值,不一定可以寻址.左值可以当右值使用,但右 ...

  3. java左值与右值问题_[C++11]左值、右值、左值引用、右值引用小结

    左值和右值 左值:指表达式结束后依然存在的持久对象,可以取地址,具名变量或对象 右值:表达式结束后就不再存在的临时对象,不可以取地址,没有名字. 比如 int a = b + c;,a 就是一个左值, ...

  4. [C++11]左值、右值、左值引用、右值引用小结

    左值和右值 左值:指表达式结束后依然存在的持久对象,可以取地址,具名变量或对象 右值:表达式结束后就不再存在的临时对象,不可以取地址,没有名字. 比如 int a = b + c;,a 就是一个左值, ...

  5. Cpp / 右值、纯右值、将亡值

    一.左值与右值 左值(lvalue)和右值(rvalue)是 C++ 类型系统之中的基础概念,我们不需要了解这些基础概念,同样也能写出代码.但是如果没有弄清左右值的概念,对于许多 C++ 高级特性的探 ...

  6. java左右值_为什么C/C++等少数编程语言要区分左右值?

    我个人认为的区分左值和右值的原因: 赋值的意义(基本上)是把右边的值放进左边的位置,替换掉左边位置原有的值.从而赋值对左边期望的是位置与其中的值的结合(相当于 C/C++ 的对象),对右边期望的是单纯 ...

  7. C++/C++11中左值、左值引用、右值、右值引用的使用

    C++的表达式要不然是右值(rvalue),要不然就是左值(lvalue).这两个名词是从C语言继承过来的,原本是为了帮助记忆:左值可以位于赋值语句的左侧,右值则不能. 在C++语言中,二者的区别就没 ...

  8. 39.左值、左值引用、右值、右值引用

    1.左值和右值的概念 左值是可以放在赋值号左边可以被赋值的值:左值必须要在内存中有实体:          右值当在赋值号右边取出值赋给其他变量的值:右值可以在内存也可以在CPU寄存器.       ...

  9. c语言 变量的左值和右值,C++雾中风景10:聊聊左值,纯右值与将亡值

    C++11的版本在类型系统上下了很大的功夫,添加了诸如auto,decltype,move等新的关键词来简化代码的编写与降低阅读代码的难度.为了更好的理解这些新的语义,笔者确定通过几篇文章来简单窥探一 ...

最新文章

  1. 云计算赋能人工智能,未来的红利在哪?
  2. matlab学习200316
  3. Android 混淆打包
  4. nginx虚拟主机概念和类型介绍
  5. oracle更改文件,Oracle修改数据文件名以及移动数据文件
  6. 基于TCP协议的进程间通信
  7. (48)FPGA面试技能提升篇(数字采样、射频存储、频谱搬移)
  8. 移动互联网“去哪儿”?
  9. oracle主机自增,Oracle中实现ID自增
  10. 【数据结构】十字链表
  11. Python爬虫【一】爬取移动版“微博辟谣”账号内容(API接口)
  12. c语言中isupper用法,C 库函数 isupper() 使用方法及示例
  13. php 队列创建,php 队列的实现方法
  14. Java实验—四子棋进阶
  15. 锂电池升压-SX1308
  16. 三维扫描、三维建模在数字展厅中的应用
  17. C语言switch语句用法总结
  18. appium连接vivo手机,启动APP后就不动了--其它手机正常
  19. PHP:【商城后台管理系统】admin超级管理员后台操作界面部署{无限级菜单}
  20. 我的物联网项目(五)下单渠道

热门文章

  1. 赛灵思 PL 和 PS IBIS 模型解码器
  2. 搭建ntp时间同步服务器,解决cm时间问题
  3. 轻量级容器主机 Photon OS
  4. 此CA证书不受信任的解决办法
  5. 查询oracle数据库表名和中文名
  6. Unity的AB包系统使用概论
  7. “我应为王”,比尔盖茨名言--author :邵京国
  8. 如何为自己制作出一份优秀的简历
  9. 蓝桥杯练习系统答案-数的读法-Python
  10. 没得选这件事是人生常态