PART III 表达式
“花が好きですか?いいえ,花は嫌いです.”
“花が好きですか?いいえ,でも嫌いじゃありません.”
“花が好きですか?いいえ,好きです.”
——剣鬼恋歌です
1.左值和右值
C++的表达式要不然是右值 (rvalue,读作“are-value”),要不然就是左值 (Ivalue,读作“ell-value”)。
左值(L-value): 左值是指表达式所代表的对象在内存中有明确的存储位置。换句话说,左值是可以用地址取得的表达式。典型的例子是变量、数组元素以及可以被引用的对象。左值可以出现在赋值的左边或右边,可以进行修改操作
Eg1:
- int x = 5; // x 是左值
- int arr[5] = {1, 2, 3, 4, 5}; // arr[2] 是左值
右值(R-value): 右值是指表达式所代表的对象在内存中没有明确的存储位置,或者说它们是临时的值。右值不能出现在赋值操作的左边,因为它们不能被修改。右值可以是字面值、临时对象、表达式的结果等。
Eg2:
- int y = 10; // 10 是右值
- int result = x + y; // x + y 是右值
一个重要的原则,需要右值的地方可以用左值来代替,但是不能把右值当成左值(也就是位置)使用。当一个左值被当成右值使用时,实际使用的是它的内容(值)。
右值引用:
右值引用(R-value reference)是 C++11 引入的一个重要特性,它允许我们标识并操作右值。右值引用使得在某些情况下可以高效地将资源从一个对象转移到另一个对象,从而避免不必要的数据拷贝。
右值引用的语法是通过在类型名称后面加上&&来定义的。例如:int&& 表示一个右值引用类型。右值引用的用途如下:
- 移动语义: 右值引用使得我们可以实现移动语义,将资源(如内存、文件句柄等)从一个对象转移到另一个对象,而不需要进行深拷贝操作,从而提高性能。
- 避免不必要的拷贝: 在某些情况下,通过引入右值引用,我们可以避免创建临时对象,从而避免不必要的拷贝操作
举例如下:
- 绑定到右值: 右值引用可以绑定到右值。一个右值引用可以看作是一个右值的标识符,允许我们操作它所绑定的对象。
Eg3:
- int x = 5; // x 是左值
- int&& rref = 10; // rref 是右值引用,绑定到右值 10
- 转移语义: 通过使用 std::move() 函数,我们可以将一个左值强制转换为右值引用,从而允许移动语义的使用。这通常用于实现资源的有效转移。
Eg4:
- int x = 5; // x 是左值
- int&& rref = 10; // rref 是右值引用,绑定到右值 10
- 右值引用参数: 在函数参数中,使用右值引用参数可以接受右值作为输入,并在函数内部进行移动语义操作。
Eg5:
- void processValue(int&& value) {
- // 对 value 进行操作,可能会移动资源
- }
Decltype:
使用关键字 decltype的时候,左值和右值也有所不同。如果表达式的求值结果是左值,decltype 作用于该表达式(不是变量)得到一个引用类型。举个例子,假定p的类型是 int*,因为解引用运算符生成左值,所以 decltype (*p)的结果是 int&。另一方面,因为取地址运算符生成右值,所以 decltype(&p)的结果是int**,也就是说,结果是一个指向整型指针的指针。
2.优先级
普通运算符的优先级我们都已经了解的非常清楚了,但是有一些优先级还是需要加以说明,首先我们看两组例子,请大家输出答案是什么并与我的编译器答案作比较:
Eg6:
- int main()
- {
- int i = 0;
- std::cout << i << " " << i++ << std::endl;
- }
Eg7:
- int main()
- {
- int i = 0;
- std::cout << i << " " << ++i << std::endl;
- }
我的编译器输出结果为1 0和1 1,你可能会问为什么,但是这也正是我想知道的,这和我的预期严重不符合,我问只好请教了最好的导师:GPT先生来回答,以下是他的答案:
“这可能是与编译器、优化设置、以及编译器对于表达式求值顺序的实现相关。C++ 并没有明确规定递增操作的执行顺序,所以在不同的编译器和不同的情况下,可能会产生不同的结果。
这种情况下,编译器可能进行了一些优化,导致表达式求值的顺序与你预期的不同。C++ 中的表达式求值顺序是未定义行为,编译器可以根据其实现进行优化,只要不违反 C++ 的语法和语义规则。
为了避免这种不确定性,最好是不在同一个表达式中混合使用递增操作和其他操作,或者使用多个独立的语句来确保求值顺序。”
因此,非必要我们还是尽量避免这种程序的写法,对于逻辑求值,如果必要还是加上括号这一运算符来保证输出结果。
短路求值:
逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值。这种策略称为短路求值(short-circuit evaluation)。
- 对于逻辑与运算符来说,当且仅当左侧运算对象为真时才对右侧运算对象求值.
- 对于逻辑或运算符来说,当且仅当左侧运算对象为假时才对右侧运算对象求值。
3.混用解引用和递增运算符
如果我们想在一条复合表达式中既将变量加1或减1又能使用它原来的值,这时就可以使用递增和递减运算符的后置版本。
举个例子,可以使用后置的递增运算符来控制循环输出一个 vector 对象内容直至遇到(但不包括)第一个负值为止:
Eg8:
- auto pbeg = v.begin():// 输出元素直至遇到第一个负值为止
- while (pbeg != v.end() && *beg >= 0)
- cout <<*pbeg++ << endl; // 输出当前值并将 pbeg 向前移动一个元素
对于刚接触 C++和 C 的程序员来说,*pbeg++不太容易理解。其实这种写法非常普遍所以程序员一定要理解其含义
4.移位运算符
移位运算符 << 是 C++ 中的位操作运算符之一,用于对二进制表示的整数进行左移操作。它可以用于将一个数的二进制位向左移动指定的位数,从而实现数值的乘以2的幂操作。
移位操作的效果:
当使用 value << n 进行左移操作时,value 的二进制表示中的所有位都向左移动 n 位,低位补0。这相当于将 value 乘以 2^n。
Eg9:
- int x = 5; // 二进制表示:00000101
- int shifted = x << 2; // 左移2位,结果为 00010100,即 20
在这个示例中,x 的二进制表示是 00000101,左移2位后得到结果 00010100,相当于将原始值5乘以2^2,即得到结果20。
需要注意的是,如果左移导致最高位超出了数据类型的表示范围,会发生溢出。此外,移位运算对于带符号类型来说,可能会产生未定义行为。在使用移位运算时,应该确保不会发生溢出或未定义行为。
- |= 按位或赋值操作符:
|= 用于按位或运算,并将运算结果赋值给变量。
Eg10:
- int a = 5; // 二进制表示:00000101
- int b = 3; // 二进制表示:00000011
- a |= b; // a 的值变为 00000111,即 7
- &= 按位与赋值操作符:
&= 用于按位与运算,并将运算结果赋值给变量。
Eg11:
- int x = 12; // 二进制表示:00001100
- int y = 7; // 二进制表示:00000111
- x &= y; // x 的值变为 00000100,即 4
同样的,我们还会有与或运算与移位运算结合起来的情况:
Eg12:
- int flags = 0b0010; // 二进制表示:00000010
- // 使用 |= 和 << 进行位运算和赋值
- flags |= (1 << 3); // 将第 3 位设置为 1
- // flags 变为 00001010,即 10
Eg13:
- int mask = 0b1100; // 二进制表示:00001100
- // 使用 &= 和 << 进行位运算和赋值
- mask &= ~(1 << 2); // 将第 2 位设置为 0
- // mask 变为 00001000,即 8
在这些示例中,<< 用于创建一个在指定位置上为 1 的位模式,然后与原始值进行 |= 或 &= 操作,从而实现对指定位的设置或清除。这种结合使用在位掩码和位操作中非常常见,用于设置或清除特定位的状态。
5.逗号运算符
逗号运算符 , 是 C++ 中的一个运算符,它用于在表达式中执行多个操作,并返回最后一个操作的值。逗号运算符允许将多个表达式放在一起,按顺序依次执行,并将最后一个表达式的结果作为整个逗号表达式的结果。
特点:
- 从左到右依次计算每个表达式,但只保留最后一个表达式的结果。
- 所有表达式都会被依次执行,但只有最后一个表达式的结果会成为整个逗号表达式的结果。
Eg14:
- int a = 5, b = 10, c = 15;
- int result = (a += 2, b += 3, c += 4); // 依次执行 a += 2, b += 3, c += 4,并将 c += 4 的结果作为整个逗号表达式的结果
在这个示例中,逗号运算符在 result 赋值语句中用于在多个表达式之间执行,但只有最后一个表达式 c += 4 的结果(即 19)会赋值给 result。
逗号运算符在某些情况下可以用于简化代码,但也可能会使代码更难理解。因此,在使用逗号运算符时,应谨慎考虑其影响,并确保代码的可读性不受影响。
2023年8月31日
NHG :+
文章评论