表达式
REF
An expression is a sequence of operators and their operands, that specifies a computation.
Expression evaluation may produce a result (e.g., evaluation of 2+2 produces the result 4) and may generate side-effects (e.g. evaluation of std::printf("%d",4) prints the character '4' on the standard output).
基本概念
分类
根据运算对象数量可分为一元运算符 unary operator、二元运算符binary operator等,函数调用也是一种特殊的运算符。
类型转换
在不同类型对象进行运算时,常常会发生类型转换。如小整数类型一般会被 提升 promote 成较大整数类型。
重载运算符 overloaded operator
当用户对类类型的对象进行运算时,可以自定运算符及其含义,称为 重载运算符 overloaded operator 。IO库zhongde'>>'和'<<',以及vector和string对象中的许多运算符都是重载的运算符。
左右值
C++表达式可以分为:
- lvalue and rvalue
- glvalue and prvalue
其中:
lvalue+xvalue=glvalue;prvalue+xvlaue=rvalue
简单来说,左值用对象的身份,右值用对象的数值。
详见 https://en.cppreference.com/w/cpp/language/value_category
优先级 precedence、结合律 associativity、求值顺序 order of evaluation
优先级和结合律
https://en.cppreference.com/w/cpp/language/operator_precedence
求值顺序
参见:https://en.cppreference.com/w/cpp/language/eval_order
若某个语句没有指定执行顺序,但表达式指向并修改了同一个对象,则无法明确何时、如何对该对象求值,会引发错误产生未定义的行为 undefined behavior。
- If a side effect on a scalar object is unsequenced relative to another side effect on the same scalar object, the behavior is undefined.
i = ++i + 2; // undefined behavior until C++11 i = i++ + 2; // undefined behavior until C++17 f(i = -2, i = -2); // undefined behavior until C++17 f(++i, ++i); // undefined behavior until C++17, unspecified after C++17 i = ++i + i++; // undefined behavior
- If a side effect on a scalar object is unsequenced relative to a value computation using the value of the same scalar object, the behavior is undefined.
cout << i << i++; // undefined behavior until C++17 a[i] = i++; // undefined behavior until C++17 n = ++i + i; // undefined behavior
算术运算符 arithmetic operators
+a
-a
a + b
a - b
a * b
a / b
a % b
~a
a & b
a | b
a ^ b
a << b
a >> b
逻辑logical 和 比较comparison 运算符
//logical operator
!a
a && b
a || b
//comparison operator
a == b
a != b
a < b
a > b
a <= b
a >= b
a <=> b
短路求值 short-circuit evaluation
逻辑运算符都是从左往右求值,直到无法确定后再从右开始。
-
||
当且仅当左侧为假才向右计算 -
&&
当且仅当左侧为真才向右计算 - 使用以上规则可以规避某些溢出,如:
该式若改变index != s.size() && !isspace(s[index])
&&
两端顺序,则isspace
首先因下标越界而出错。
赋值运算符 assignment operator
赋值运算符左侧对象必须是一个可修改的左值。结果是左侧运算对象,且是一个左值。
a = b
a += b
a -= b
a *= b
a /= b
a %= b
a &= b
a |= b
a ^= b
a <<= b
a >>= b
右结合律
int a1, a2;
a1 = a2 = 0; //正确,从右往左
int a3, *a4;
a3 = a4 = 0; //错误,类型不同且无法转换
复合赋值运算符
假设算术运算符形如 X
,则:
a = a X b;
等价于 a X= b;
,而左边求值两次,右边一次。
递增/递减运算符 increment/decrement operator
- 前置版本
++i
:首先将运算对象加1,然后将改变后的对象作为结果继续运算。更广泛使用,能避免不必要的语句,如for循环中会少进行一次判断。 - 后置版本
i++
: 将运算对象加1,但求值结果是运算对象改变之前的值的副本。较少使用,需要先储存副本,增加了不必要的运算。
后置自增和解引用混用
*p++
是一种常用的避免无法输出首字母的表达式。由于递增递减的优先级高于解引用,该式等价于*(p++)
。
auto* p = v.begin();
while (p != v.end())
cout<< *p++ <
用前置自增改写会增加代码而变得不简洁:
auto* p = v.begin();
while (p != v.end()) {
cout<< *p <
使用递增递减应避免未定义行为
成员访问运算符 member access operator
a[b]
*a
&a
a->b
a.b
a->*b
a.*b
https://en.cppreference.com/w/cpp/language/operator_precedence
a->b
等价于(*a).b
而不是*a.b
。后者相当于a本身是一个指针,试图访问该指针的b成员,自然出错。
条件运算符
cond ? expr1 : expr2
:先求cond的值,若真则对expr1求值并返回,否则对expr2求值并返回。
条件运算符嵌套
const int A = 90;
const int B = 80;
const int C = 70;
const int D = 60;
int main(){
int grade = 0;
cin>>grade;
string str_grade = (grade>=A)? "A" :
(grade>=B)? "B" :
(grade>=C)? "C" :
(grade>=D)? "D" : "F";
cout<
相较于switch和if等语句,由于会逐渐向后计算,使用该方式可减少判断条件的长度;但由于前式都需要计算,可能会略微减慢。
位运算符
用于整数类型的运算对象并将其视为二进制位的集合,并提供检查和设置二进制位的功能。位运算符同样可用于bitset
的标准库类型。
如果运算对象类型是小整型,会被自动提升promote成较大整型。此外,由于不同编译器位运算对最前面符号位的处理方式不同,可能出现未定义undefined结果,因此避免为有符号数进行位运算。
~a
a & b
a | b
a ^ b
a << b
a >> b
-
若是短类型,先进行提升:短类型前补满0转为长类型;
- 无论是否带符号,提升后默认是有符号值。
-
若超限,则:
- 超上限:减去容量(char: 256=0400);
- 超下限:加上容量。
无符号数按位取反:容量减去当前值;
-
有符号数按位取反(提升并使容量合法时):
- 所有位按位取反;
- 若取反后符号位是1,显示的值是再(对绝对值)按位取反并+1。相当于 ~正->负:绝对值 +1 变符号;
- 若取反后符号位是0,显示的值是再(对绝对值)按位取反并-1。相当于 ~负->正:绝对值 -1 变符号。
//char: int8_t;
//sizeof(unsigned char): 1byte=8bits; 3bit/num;
//char-max = 0B11'111'111 = 0377 = 255 = 0xFF = unsigned char -1;
unsigned char c1o = 0227;
//unsigned char 0b10'010;111 = 151
//to unsigned int 0b00000000'00000000'00000000'10010111
unsigned int i1o = 0227;
//unsigned int 0b00000000'00000000'00000000'10010111
auto c1o_(~c1o);
//int 0b11111111'11111111'11111111'01101000 =>
//int -(0b00000000'00000000'00000000'10010111+1) => int -152
auto i1o_(~i1o);
//unsigned int 0b11111111'11111111'11111111'01101000
//to unsigned int 4294967144
cout<
不超限的短类型负数:
signed char c1o = -027;
//注意char可能为unsigned也可能为signed
int x = -0b00000000'00000000'00000000'00010111;
int xx = 0b11111111'11111111'11111111'11101001;
cout<<~c1o<<'\t'<<~xx<<'\t'<<~x<
负号和按位取反的区别:
int main(){
int a = ~0b00000000'00000000'00000000'00000100;
//int a = ~5;
int b = 0b11111111'11111111'11111111'11111011;
//int b = -5;
int c = -0b00000000'00000000'00000000'00000101;
//int c = -5;
cout<
负号返回值增加了一个取补码的过程,在表达式中的结果同样也是提符号-取补码作为绝对值-进行输出。而按位操作单纯对位操作,不取补码。二者单独使用均可逆,但混用时注意流向。
移位运算符(io运算符)满足左结合律。
sizeof运算符
返回的是某表达式或类型名所占的字节数。该运算符满足右结合律,所得的值是一个size_t类型的常量表达式。sizeof
并不实际计算表达式的值而是直接返回字节数。运算符的运算对象形式如下:
sizeof (type);
sizeof expr;
sizeof *p
比较特殊。首先,由于sizeof()
满足右结合律并且优先级等于*
运算符,按照从右向左的顺序组合,等价于sizeof(*p)
。此外,由于不会实际求运算对象的值,即使是无效指针也可以安全获取结果。
一些规则:
-
sizeof(char)
: 1 - 对string或vector执行sizeof只返回该类型固定部分的大小而不是对象中元素占了多少空间。
- 若
ia
是个数组,可以使用sizeof(ia)/sizeof(ia[0])
或者sizeof(ia)/sizeof(*ia)
得到数组大小。- 注意不要使用指向数组的指针:
sizeof(p)/sizeof(*p)
- 注意不要使用指向数组的指针:
-
sizeof
的返回值是一个常量表达式,因此可以用该结果声明数组维度。
逗号运算符 comma operator
- 用在for循环中,实现多个变量的自增/自减
- 用在同一基本类型的连续赋值中
类型转换
如果两种类型有关联,则当程序需要其中一种类型的运算对象时,可以用另一种关联类型的对象或值来替代:若两种类型可以相互转换conversion,则二者就是关联的。
隐式转换 implicit conversion:自动执行,无需人为介入。如下列情况:
- 比int小的整型提升时;
- 非布尔转为布尔;
- 初始化的初始值转为变量的类型;赋值中右侧类型转为左侧类型
- 多种类型进行混合算术运算;
- 函数调用时的类型转换。
算数转换 arithmetic conversion
把一种算术类型转换成另一种算术类型。运算符的运算对象将转换成最宽的类型。例如有整型和浮点就都换为浮点;含有long double,则都换为long double。
整型提升integral promotion
- 对于
bool, char, signed char, unsigned char, short, unsigned short
,只要它们所有可能的值都能存在int里,它们就会提升成int类型,否则提升成unsigned int类型。 - 对于
wchar_t, char16_t, char32_t
,提升为能够容纳它们的int, unsigned int, long, unsigned long, long long, unsigned long long
中最小的一种类型。
无符号类型
在进行整型提升后,再决定是否是带符号的类型。
具体规则如下:
- 若只同时存在有符号或同时存在无符号,则转为容量较大的类型(无论正负)。
- 若无符号类型 >= 有符号类型,均转为无符号类型:
signed char a = -10; unsigned int b = 1; unsigned long c = 3.0; auto d = a+b+c; //unsigned long
- 若有符号类型 > 无符号类型:
- 若无符号类型的所有值都可以存在有符号类型中,则转为带符号类型
- 若不能,则转为无符号类型
- 主要依赖于系统或编译器对类型大小的定义。
其他隐式类型转换
https://en.cppreference.com/w/cpp/language/implicit_conversion
特殊类型
某些特殊类型如size_t, size_type, ptrdiff_t, wchar_t
都可以隐式转换为相应的合理类型(主要由操作系统和编译器决定)。为了程序的可移植性,应多使用这些类型。
数组转为指针
int ia[10];
int* ip = ia; //右边隐式转换为指向首元素的指针
int* ip = std::begin(ia);
上述转换不会在decltype, &, sizeof, typeid
中发生。用引用初始化数组也不会发生。
指针的转换
- 常量整数值的指针或者nullptr能转为任意指针类型;
- 指向任意非常量的指针能转换为void*;
- 指向任意对象的指针能转换成const void*。
- 再有继承关系的类中还有其他转换方式。
指向常量的指针
允许将指向非常量类型的指针转换成指向相应常量类型的指针(增加底层const),反之由于试图删除底层const,无法实现。
int a = 1;
const int& ra = a;
const int* pa = &a;
int& rr = ra; //错误
int* pp = pa; //错误
int* const ppc = pa; //错误,没有底层,顶层自然不一致
类类型定义的转换
显式转换
强制类型转换 cast
具体参见:
https://en.cppreference.com/w/cpp/language/explicit_cast
命名的强制类型转换
形式如:
cast-name
- type:目标类型。若是引用类型,则结果是左值。
- expression:要转换的表达式
- cast_name:包括
static_cast, dynamic_cast, const_cast, reinterpret_cast
指定了转换方式。
注意:不可用于左值,使用cast后的值进行初始化等操作时可与auto配合使用以减少类型名的重复编写。
static_cast
任何具有明确定义的类型转换,只要不包含底层const就可以使用。
static_cast不能转换掉expression的const、volatile、或者__unaligned属性。
int a = 1;
float b = static_cast(a);
//两种旧式转换方式
float c = float(a);
float d = (float)a;
int* const pc = &a;
int* p = static_cast(pc);
int* pp = pc; //而且非底层const不必static_cast可以直接转换
const_cast
只能改变运算对象的底层const,这种行为称为 去掉const性质 | cast away the const。注意必须具有底层const。若对象是个常量(具有顶层const)则
int a = 1;
const int* pa = &a;
int* p = const_cast(pa);
const int& ra = a;
int& r = const_cast(ra);
reinterpret_cast
reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释,即以二进制存在形式的重新解释
可将int*
转为char*
等。使用此强制转换较为危险。
int* const pp = &b;
unsigned int* qq = reinterpret_cast(pp);
unsigned int* qq = reinterpret_cast(pp);
https://en.cppreference.com/w/cpp/language/reinterpret_cast
dynamic_cast
https://en.cppreference.com/w/cpp/language/dynamic_cast
pointer_cast
https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast
std::static_pointer_cast, std::dynamic_pointer_cast, std::const_pointer_cast, std::reinterpret_pointer_cast
旧型强制转换
type(expr) //函数形式
(type)expr //C语言风格
根据涉及类型不同,可以具有与 const_cast, static_cast, reinterpret_cast
任意一种相似的行为。若替换为const_cast
和static_cast
合法,则行为与命名一致,否则执行的是reinterpret_cast
。