1. 基础
表达式由一个或多个操作数组成,计算时会产生一个值。最简单的表达式是单个变量或字面值(literal)。复杂表达式包含一个或多个操作符。
操作符有一元操作符,如:取地址&,解引用* ;有二元操作符,如:相等==,相乘* ;有一个三元操作符,条件判断?: ;函数调用也是操作符,其操作数个数没有限制。
计算含有多个操作符的表达式,需要了解操作符的优先级 和结合性 以及操作数的计算顺序。
1.1 左值和右值
每个表达式要么是左值(lvalue),要么是右值(rvalue)。[区别]:左值可以出现在赋值语句的等号左边(即被赋值对象),而右值不能出现在左边。
在C++中,左值表达式会产生一个对象或函数。例外的是,一些左值,比如const对象,不能出现在赋值语句左边。不严格的讲,当作为右值使用对象时,使用的是对象的值(它的内容);当作为左值使用对象时,使用的是对象的IDentity(它在内存中的位置)。
操作符的不同就在于是否需要的是左值操作数还是右值操作数,以及返回的是左值还是右值。左值可以作为右值使用(使用的是它的内容),但是相反则不行。
- 赋值操作符需要非const左值作为左操作数,返回的左操作数是左值。
- 取地址操作符需要左值操作数,返回指向操作数的指针也是右值。
- 内置的解引用和下标操作符,迭代器的解引用和string,vector类型的下标操作符都产生左值。
- 内置的和迭代器的自增,自减操作需要左操作数,前置版本返回左值。
int a = 10;
int *pa = &a;
decltype(*pa) ra = a; // *pa产生左值,decltype返回的类型为int &
decltype(&pa) ppa; // &pa产生右值,decltype返回的类型为int **
1.2 优先级和结合性
以表达式 f() + g() * h() + j() 为例:- 优先级保证了先计算g()和h()的乘积。
- 结合性使得f() 与 g()*h()的结果相加,然后相加的结果与j()相加。
- 而这些函数的调用顺序则没有保证。
如果这几个函数是相互独立的,则函数的调用顺序不会影响到结果。如果他们之间有相互联系,比如操作了相同的对象,则该表达式是错误的,其行为没有定义。
2 取模运算符
对于整数m和n(n非0),取模操作定义为 使等式(m / n) * n + m % n = m成立。如果m%n非0 ,则结果的符号与m的符号相同。早期版本的C++允许m%n的结果和n的符号相同,是基于负值结果的m/n的舍入远离0的规则,但是现在这种实现方法已经禁用。在-m不溢出的情况下,(-m)/n和m/(-n)总是等于-(m/n),m%(-n)等于m%n,(-m)%n等于-(m%n)。
cout << 7%3 << endl; // 1
cout << 7%-3 << endl; // 1
cout << -7%3 << endl; // -1
cout << -7%-3 << endl;// -1
3 逻辑运算符
所谓“短路效应”,就是对于AND和OR操作符,会先计算左操作数的值,如果根据左操作数的值可以判断表达式的值,则计算结束,否则继续计算右操作数的值。
例如,a && b ,如果a为假,则不必计算b的值就知道该表达式为假;a || b,如果a为真,则不必计算b 的值就知道该表达式为真。
4 自增自减操作
以自增操作为例。前缀版本++i,操作数+1后返回改变会的对象。后缀版本i++,操作数+1后返回原对象的一个副本。
我们应该养成前缀版本的习惯,尽量避免额外开销(需要额外保存原对象的副本),只在必要的时候使用后缀版本。对于int和指针类型,编译器会优化掉后缀版本产生的额外开销,但是对复杂的迭代类型,会有潜在的额外开销。
使用后缀版本的情况:*p++。自增操作的优先级高于解引用,后缀版本使得返回的对象为原对象的副本,然后解引用作用于指针p。该操作广泛应用于序列的遍历。
[注意]:大多数操作符没有规定操作数的计算顺序(逻辑AND和OR规定先计算左操作数),一般情况下计算顺序是不要紧的。但是,当表达式的一部分使用了一个对象,而该表达式的另一部分改变了该对象时,则会产生歧义。例如:
*p = togo(*p++);
该表达式可以解释为:
*p = togo(*p);
*(p+1) = togo(*p);
也可能是其他的。
5 条件操作符
条件操作符 cond ?expr1 :expr2可以看成是if-else逻辑的缩略版。支持嵌套:cond ? expr1 : (cond ? expr2 : expr3 )
6 位操作
对于符号位的处理方法与机器有关,所以使用位操作时最好使用unsigned类型。
7 类型转换
在下列情况下,编译器会进行自动的隐式类型转换,对于算术类型会尽可能保留精度:
- 在大多数表达式中,小于int的整型会首先转换成一个更大的整型。
- 在条件表达式中,非bool的表达式会转换成bool。
- 在初始化中,初始化表达式会转换成变量的类型;在赋值语句中,右操作数会转换成左操作数的类型。
- 在算术和关系表达书中,有多种混合类型时,会转换成一个统一类型。比如,同时含有整型和浮点型时,整型会转换成一个合适的浮点型。
- 在算术表达式中,bool, char, signed char, unsigned char, short 和 unsigned short会转换成int,如果int不足以保存数值,则转换成unsigned int
- unsigned 类型的转换与机器有关。因为不同的机器整型的长度不同。比如,unsigned int和long,如果int和long的字节数相同,则long转换成unsigned int;如果long的字节数大于int,则unsigned int 转换成long。值得注意的是,负值转换成无符号类型,其值可能会改变。
unsigned int a = 0; int b = -1; cout << a + b << endl; //输出:4294967295
- [数组指针转换]一般情况下,当我们引用数组名时,会自动转换为指向数组首元的指针。例外是:当用于decltype,取地址符&,sizeof,或typeid时,不进行转换,按数组对待。
- [指针转换]0和nullptr可以转换成任意指针类型;一个非const类型的指针可以转换成void*,任何类型的指针可以转换成const void*。
- [转换成bool]在条件语句中,算术类型或指针类型会自动转换成bool类型,0值转换成false,其他值转换成true。
- [const转换]可以将指向类型T的指针或引用,转换成指向const T的指针或引用。
- [class类型转换]可以将C-风格字符串转换成string,如:string s = "Hello";将istream类型转换成bool,如:while (cin >> a)。
- static_cast:“A static_cast is often useful when a larger arithmetic type is assigned to a smaller type”
- dynamic_cast:“Used in combination with inheritance and run-time type identification”
- const_cast:“A cast that converts a low-level const object to the corresponding nonconst type or vice versa”
- reinterpret_cast:“Interprets the contents of the operand as a different type. Inherently machine dependent and dangerous.”
8 操作符优先级表
Associativity and Operator | Function | Use |
---|---|---|
L :: | global scope | ::name |
L :: | class scope | class::name |
L :: | namespace scope | namespace::name |
L . | member selectors | object.member |
L -> | member selectors | pointer->member |
L [ ] | subscript | expr[expr] |
L ( ) | function call | name ( expr_list ) |
L ( ) | type construction | type ( expr_list ) |
R ++ | postfix increment | lvalue++ |
R -- | postfix decrement | lvalue-- |
R typeid | type ID | typeid(type) |
R typeid | run-time type ID | typeid(expr) |
R explicit cast | type conversion | cast_name<type>(expr) |
R ++ | prefix increment | ++lvalue |
R -- | prefix decrement | --lvalue |
R ~ | bitwise NOT | ~expr |
R ! | logical NOT | !expr |
R - | unary minus | -expr |
R + | unary plus | +expr |
R * | dereference | *expr |
R & | address-of | &lvalue |
R ( ) | type conversion | (type) expr |
R sizeof | size of object | sizeof expr |
R sizeof | size of type | sizeof( type ) |
R sizeof ... | size of parameter pack | sizeof ... ( name ) |
R new | allocate object | new type |
R new [ ] | allocate array | new type [size] |
R delete | deallocate object | delete expr |
R delete [ ] | deallocate array | delete [ ] expr |
R noexcept | can expr throw | noexcept ( expr ) |
L ->* | ptr to member select | ptr->*ptr_to_member |
L .* | ptr to member select | obj.*ptr_to_member |
L * | multiply | expr * expr |
L / | divide | expr / expr |
L % | modulo(remainder) | expr % expr |
L + | add | expr + expr |
L - | subtract | expr - expr |
L << | bitwise shift left | expr << expr |
L >> | bitwise shift right | expr >> expr |
L < | less than | expr < expr |
L <= | less than or equal | expr <= expr |
L > | greater than | expr > expr |
L >= | greater than or equal | expr >= expr |
L == | equality | expr == expr |
L != | inequality | expr != expr |
L & | bitwise AND | expr & expr |
L ^ | bitwise XOR | expr ^ expr |
L | | bitwise OR | expr | expr |
L && | logical AND | expr && expr |
L || | logical OR | expr || expr |
R ?: | conditional | expr ? expr : expr |
R = | assignment | lvalue = expr |
R *=, /=, %=, R +=, -=, R <<=, >>=, R &=, |=, ^= | compound assign | lvalue += expr, etc. |
R throw | throw exception | throw expr |
L , | comma | expr, expr |