Chapter 4 表达式基础与详述
4-1 表达式基础:引入
int main()
{
int x;
x = 3 // 表达式,可求值
3 + 2.3 // 传入两个不同类型-->隐式产生类型转换
}
表达式由一到多个操作数组成,可以求值,并通常会返回求值结果
-
最基本的表达式:变量、字面值
-
表达式可嵌套
-
通常来说,表达式会包含操作符(运算符)
-
操作符的几个特性
- 接收几个操作数:一元、二元、三院
- 操作数的类型——类型转换
- 操作数是左值还是右值
- 有些情况下只能接收左值; // 3 = 5
- 结果的类型
- 结果是左值还是右值
- 优先级和结合性(2 + 3 * 5),可以通过小括号来改变运算顺序
- 操作符的重载——不改变接收操作数的个数、优先级和结合性
-
操作数求值顺序的不确定性
// 函数重载 ---> 读写依赖关系避免乱序 void fun(int p1, int p2) { std::cout << p1 << '\n' << p2 << '\n'; } int main() { int x = 0; fun(x = x + 1, x = x + 1); }
4-2 表达式基础:左值与右值
// C语言
int main()
{
int x;
x = 3; // x可以放在等号左边,称为左值;3不能放在等号左边,称为右值
}
// 在C++当中,左值也不一定能放在等号左边,而右值也可能放在等号左边
// 所有的划分都是针对表达式的,不是针对对象或数值
glvalue (generalized left-value):泛左值是一个表达式—>获取到x所关联的内存,确定一个对象、位域或者函数
prvalue (Pure right-value):用于初始化某对象,不开辟内存
struct Str
{
}
int main()
{
int{}
Str{}
}
xvalue:代表其资源能够被重新使用
# include <iostream>
# include <vector>
void fun(std::vector<int>&& par)
{
}
int main()
{
std::vector<int> x;
fun(std::move(x)); // 把x变为将亡值
}
// 左值不一定能放在等号左边
const int x = 3;
x = 4;// 会报错
// 右值可能可以放在等号左边
struct Str {};
int main()
{
Str() = Str(); // 编译可通过
}
- 左值可以转换为右值(lvalue to rvalue conversion);
- 临时具体化(Temporary Materialization)
// 右值可能可以放在等号左边
struct Str {
int x;
};
int main()
{
Str().x // 从Str中取出特定部分,从纯右值转换为将亡值
}
-
再谈decltype
int x; decltype(x); // 将亡值--->type&& // 左值--->type& // 纯右值--->type
4-3 表达式基础:类型转换
-
隐式类型转换
例:3 + 0.5
- 自动发生
- 实际上是一个有限长度的转型序列
- (cpp-reference)数值提升:整型提升/浮点提升
- 数值转换:如int—>double,可能会更改值,有 潜在的精度损失
-
显式类型转换
例:static_cast(3) + 0.5,程序以可见形式进行转换
int x = 3; int y = 4; std::cout << (x / static_cast<double>(y)) << std::endl;
-
static_cast<新类型> (表达式) // 静态期转换,不影响运行期
-
dynamic_cast<新类型>(表达式)
-
const_cast<新类型> (表达式)
const int* ptr; const_cast<int*>(ptr);// 去除常量
// 需要小心 int x = 3; const int& ref = x; int& ref2 = const_cast<int&>(ref); ref2 = 4; std::cout << x << std::endl;// 输出4 // 但如果定义const int x = 3,上述代码可能输出3而不是4,与编译器有关,转换指针时,确保是关联到一个变量而不是常量
-
reinterpret_cast
int main() { int x = 3; double y = reinterpret_cast<double>(x); // 对内存重新解释,但针对指针,该行代码报错 int* ptr = &x; float* ptr2 = reinterpret_cast<float*>(ptr);// 该行代码正常 }
-
C类型转换(在程序中避免大量使用)
(double)3; // 不建议
-
4-4 表达式详述:算数操作符
+,- // 优先级1 一元
*,/,% // 优先级2
+,- // 优先级3
- 均为左结合的
- 通常来说,操作数与结果均为算数类型的右值;但加减法与一元+可接收指针
int main()
{
int a[3] = {1,2,3};
int* ptr = a;
ptr = ptr + 1;
ptr = ptr - 1;
std::cend(a) - std::cbegin(a);
}
- 一元+操作符会产生integral promotion
- 整数相除会产生整数,向0取整;
- 求余只能接受整数类型操作数,结果符号与第一个操作数相同
- (m / n) * n + m % n = m
4-5 表达式详述:逻辑与关系操作符
逻辑操作符:!, ||, &&
关系操作符:<=>, <, <=, >, >=, ==, !=
都可以转换为布尔值的操作数
true && true;
3 && true; // 字面值可以转换为bool
int x = 3;
3 && x; // 只要可以转换为布尔值,逻辑操作符就是合法的
// 指针只要指向不是nullptr,转换为bool就是true
-
操作数与结果均为右值
-
除逻辑非之外,其他操作符都是左结合
-
逻辑与和逻辑或具有短路特性
a && b; // 执行过程:先判断a转换为bool是否为真,若为假,直接返回0 int* ptr = nullptr; if (ptr && (*ptr == 3)) { }
-
逻辑与的优先级高于逻辑或
a || (b && c) // 计算加括号会好很多,不会乱
-
关系操作符不要串联
std::cout << (c > b > a) << std::endl; // 计算会先计算出c > b--->true 1 > a---> false // 正确的实现应该是 std::cout << ((c > b) && (b > a)) << std::endl;
-
不要写出 val == true的代码
int main() { int a = 3; if (a) // 不要写( a == true --->会转换为a == 1) { } }
-
Spaceship operator:<=>(C++20支持)
if (a > b) { } else if (a < b) { } else { }
a <= > b—>会返回a与b的关系
上式可转换为
auto res = (a <=> b); if (res > 0) { } else if (res < 0) { } else { }
4-6 表达式详述:位操作符
~、&、|、^
-
接收右值,进行位运算,返回右值
signed char x = 3; // 00000011 std::cout << ~x << std::endl; // 补码 // 00000011------>11111100补码形式进行分析,最高位为1,代表为负数,最终结果为-4 signed char y = 5; // 00000101 std::cout << (x & y) << std::endl; // 两个只要有一个位对应为0,就是0,否则为1 std::cout << (x | y) << std::endl; // 两个有一个位对应为1则为1,否则为0 std::cout << (x ^ y) << std::endl; // 取值不同,返回为1,取值相同,返回为0
-
除取反外,其他运算符均为左结合的
-
注意计算过程中可能会涉及到integral promotion
-
位操作符没有短路逻辑
-
移位操作在一定程度上等价于乘(除)2的幂
int x = 3; constexpr int y = 2; std::cout << (x * y) << std::endl; //std::cout << (x << 1) << std::endl;
-
注意整数的符号与位操作符的相关影响
- integral promotion 会根据整数的符号影响其结果
unsigned char x = 0xff; //11111111 // 0000..00011111111(24个0+8个1) // 按位取反:1111..11100000000--->-256 auto y = ~x; std::cout << y << std::endl; signed char x = 0xff; // 上面y会输出为0 1(符号位)1111111 // 1111...1111111111--->0000...0000000000
- 右移保持符号,但左移不能保证
int x = 0x80000000; // 10...0 std::cout << x << std::endl; std::cout << (x << 1) << std::endl; // 数据溢出,输出为0 std::cout << (x >> 1) << std::endl; // x/2
4-7 表达式详述:赋值操作符
-
左操作数为可修改左值,右操作数为右值,可转换为左操作数的类型
-
赋值操作是右结合的,求值结果为左操作数
int x; int y; x = y = 3; // 合法,且先计算y = 3
-
可以引用大括号(初始化列表)防止收缩转换
short x; x = {0x80000000};
-
小心区分= 与==
-
复合赋值运算符
4-8 表达式详述:自增自减运算符
x += 1 == ++x;
-
a++/a-- // 后缀自增/自减运算符 --> y = x++/x–返回x的原始值
-
++a/–a // 前缀自增/自减运算符
-
分前缀与后缀两种情况
-
操作数为左值;前缀时返回左值;后缀时返回右值
-
建议使用前缀形式
4-9 表达式详述:其他操作符
struct Str
{
int x;
}
int main()
{
a = Str;
a.x;
}
成员访问操作符:.与->
-
-> 等价于 (*).
-
. 的左操作数是左值(或右值),返回左值(或右值xvalue )
-
-> 的左操作数指针,返回左值
条件操作符
-
唯一的三元操作符
true ? 3 : 5
-
接收一个可转换为bool 的表达式与两个类型相同的表达式,只有一个表达式会被求值
-
如果表达式均是左值,那么就返回左值,否则返回右值
-
右结合
逗号操作符
-
保证操作数会被从左到右求值
-
求值结果为右操作数
-
左结合
sizeof操作符
-
产生表达式的类型对象所表示的字符数
int x; sizeof x; // 不建议使用