本文知识点导图
表达式
基础
基本概念
作用于一个对象的运算符称为一元运算符,如取地址&,解引 *。
作用于两个对象的运算符称为二元运算符,如==,乘法运算 * 。
组合运算符和运算对象
对于含有多个运算符的复杂表达式来说,要想理解它的含义首先要理解运算符的优先级,结合律以及运算对象的求值顺序
运算对象的转换
在表达式求值过程中,运算对象常常由一种类型转换成另一种类型。
重载运算符
C++定义了运算符作用于内置类型和复合类型的运算对象时所执行的操作。
当运算符作用于类类型的运算对象时,用户可以自定义其含义。
这种自定义的过程事实上是为已存在的运算符赋予了另外一层含义,所以称之为重载运算符。
重载运算符可以定义运算对象的类型和返回值类型,但是运算对象的个数,运算符的优先级和结合律都是无法改变的。
左值和右值
C++的表达式要不然是右值,要不是就是左值。
c语言中,左值位于赋值语句左侧,右值则不能。
在C++中,当一个对象被用作右值的时候,用的是对象的值(内容),当对象被用作左值当时候,用的是对象的身份(内存中的位置)。
- 赋值运算符需要一个(非常量)左值为其运算对象,得到一个左值。
- 取地址符作用域一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值
- 内置解引运算符,下标运算符,迭代器解引运算符,string和vector的下标运算符的求值结果都是左值。
- 内置类型和迭代器的递减运算符作用于左值,所得结果也是左值。
优先级与结合律
高优先级运算符要比低优先级运算符的运算对象更为紧密的结合在一起。
括号无视优先级与结合律
求值顺序
求值顺序、优先级、结合律
运算对象的求值顺序与优先级和结合律无关。
如在一条形如f()+g()*h()+j()的表达式中:
- 优先级规定,g()的返回值和h()的返回值相乘;
- 结合律规定,f()的返回值和g()h()的乘积相加,所得结果再与j()相加。
- 对于这些函数的调用顺序,没有明确的规定。
如果这些函数既不会改变同一个对象的状态也不执行IO认为,那么函数调用的顺序不受限。反之,如果其中某几个函数影响同一对象,则它是一条错误的表达式。
建议
1.拿不准的时候最好用括号来强制让表达式的组合关系符合程序逻辑的要求
2.如果改变了某个运算对象的值,在表达式的其他地方不要再使用这个运算对象。
算术运算符
运算符 | 功能 | 用法 |
---|---|---|
+ | 一元正数 | +expr |
- | 一元负数 | -expr |
* | 乘法 | expr*expr |
/ | 除法 | expr/expr |
% | 求余 | expr%expr |
+ | 加法 | expr+expr |
- | 减法 | expr/expr |
一元运算符优先级最高,然后是乘除求余,然后是加减。
算术运算符的运算对象和求值结果都是右值。
整数相除的结果还是整数,商的小数部分直接弃除。
%运算俗称“取余”或“取模”,计算两数相除的余数,运算对象必须是整数。
在除法运算中,如果两个运算对象符号相同则商为正,否者为负。
在求余运算中,如果m%n不等于0,则它的符号和m相同。
逻辑和关系运算符
结合律 | 运算符 | 功能 | 用法 |
---|---|---|---|
右 | ! | 逻辑非 | !expr |
左 | < | 小于 | expr < expr |
左 | <= | 小于等于 | expr <= expr |
左 | > | 大于 | expr > expr |
左 | >= | 大于等于 | expr >= expr |
左 | == | 相等 | expr == expr |
左 | != | 不相等 | expr != expr |
左 | && | 逻辑与 | expr && expr |
左 | || | 逻辑或 | expr || expr |
逻辑与和逻辑或都是先求左侧运算符的值再求右侧运算对象想的值,当且仅当右侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值,这种策略称为短路求值
当几个关系运算符连在一起使用时,会有意想不到的结果,如:
if(i < j < k) //这个条件是吧i<j的布尔值和k进行比较。
如果想测试一个算术对象或指针对象的真值,最直接的方法如下
if(val){ /*...........*/ } //如果val是任意非0的值,条件为真
if(!val){ /*...........*/ } //如果val是0,条件为真
赋值运算符
赋值运算符的左侧运算对象必须是一个可修改的左值。
赋值运算的结果是它左侧的运算对象,且是一个左值。
如果左右两个运算对象类型不同,右侧运算对象将转换成左侧运算对象的类型。
赋值运算满足右结合律。
int ival, jval;
ival = jval = 0;//正确,都被赋值为0
对于多重赋值,运算对象的类型或与右边的对象相同,或者可以由右边的对象类型转换得到。
int ival, *pval;
ival = pval = 0; //错误,不能把指针赋值给int
string s1,s2;
s1 = s2 = "OK"; //正确,字面值“OK”转换成了string对象。
赋值运算的优先级较低
复合赋值运算符
+= , -= , *= , /= , %= , <<= , >>= , &= , ^= , |=
这运算符都完全等价于:
a = a op b;
递增和递减运算符
递增运算符++和递减预算符- - 有前置版本和后置版本两种。
前置版本将运算对象+1,然后将改变后的对象作为求值结果。
后置版本将运算对象+1,但求值结果是运算对象改变之前那个值的副本。
int i = 0, j;
j = ++i; // j = 1, i =2
j = i ++; // j = 1, i = 2
建议:除非必须,不要使用递增递减运算符的后置版本。
auto pbeg = v.begin();
while (pbeg != v.end() && *beg >= 0)
cout << *pbeg++ << endl; // 输出当前值并将pbeg向强移动一个元素。
由于递增运算符优先级高于解引运算符,上例中*pbeg++等价与 *(pbeg++)
成员访问运算符
点运算和箭头运算都可以用于访问成员。
点运算获取类对象的一个成员。
箭头运算与点运算有关,表达式ptr -> mem 等价于 (*ptr).mem
因为解引运算符的优先级低于点运算符,所以执行解引运算的表达式必须加上括号
*p.size(); //错误,p是一个指针,他没有名为size的成员
箭头运算作用于指针类型,结果是一个左值。
点运算,如果成员所属对象是左值,结果是左值,反之,结果是右值。
条件运算符
条件运算符?:允许我们把简单的if-else语句逻辑嵌套到单个表达式当中
comd ? expr1 : expr2
执行过程是:首先求cond的值,如果条件为真对expr1求值并返回该值,否值对expr2求值并返回该值。
如下例子:
string finalgrade = (grade < 60) ? "fail" : "pass";
允许在条件运算符中嵌套条件运算符
finalgrade = (grade > 90) ? "high pass": (grade < 60) ? "fail" : "pass";
条件运算符满足右结合律。
条件运算符的优先级非常低,当一条长表达式嵌套了条件运算子表达式时,通常需要在两端加上括号。
位运算符
位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。
运算符 | 功能 | 用法 |
---|---|---|
~ | 位求反 | ~expr |
<< | 左移 | expr1 << expr2 |
>> | 右移 | expr1 >> expr2 |
& | 位与 | expr & expr |
^ | 位异或 | expr ^ expr |
| | 位或 | expr | expr |
IO库的<<和>>运算符,是标准库对左移右移运算符的重载
左移运算符<<在右侧插入值为0的二进制位。
右移运算符>>如果是无符号类型,在左侧插入值为0的二进制数,如果是带符号数,在左侧插入符号位的副本或值为0的二进值,具体情况视环境而定。
位求反运算符~ 将运算对象逐位求反后生成一个新值,将1置为0,将0置为1。
& | ^ 在两个对象上逐位执行相应的逻辑操作:
位与运算符 & :如果两个对应位置都是1则为1,否则为0;
位或运算符|:如果两个对应位置至少有一个为1则为1,否则为0;
位异运算符 ^ :如果两个对应位置有且只有一个为1则为1,否则为0;
移位运算符满足左结合律。
移位运算符优先级不高不低,比算术运算符低,比关系运算符,赋值运算符和条件运算符高。
sizeof运算符
sizeof 运算符返回一条表达式或一个类型名字所占的字节数。
sizeof运算符满足右结合律,得到的是一个size_t类型的常量表达式。
两种形式:
sizeof(type)
sizeof expr
- 对char或者类型为char的表达式执行sizeof运算,结果为1
- 对引用执行sizeof得到被引用对象所占的空间大小
- 对指针执行sizeof运算得到指针本身所占空间大小
- 对解引执行sizeof运算得到指针指向的对象所占的空间大小,指针不需要有效。
- 对数组执行sizeof运算得到整个数组所占空间的大小。
- 对string对象或vector对象执行sizeof值返回该类型固定部分的大小,不会计算该对象的元素占用了多少空间。
逗号运算符
逗号运算符含有两个运算对象,从左到右的顺序依次求值。
对于逗号运算符来说,首先对左侧的表达式求值,然后丢弃掉。逗号运算符右边的表达式的值才是真正的结果。
如果右侧运算对象是左值,那么最终的求值结果也是左值。
类型转换
何时发生隐式类型转换
- 在大多数表达式中,比int类型小的整型值首先提升为较大的整数类型
- 在条件中,非布尔值转换成布尔值
- 初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧运算对象的类型。
- 如果算术运算或关系运算的运算对象有多种类型,需要转换成同一种类型。
- 函数调用时也会发生类型转换。
显示转换
命名的强制类型转换
命名的强制类型转换具有如下形式:
cast-name<type>(expression);
type是转换的目标类型;
expression是要转换的值;
cast-name是static_cast、dynamic_cast、 const_cast、 reinterpret_cast中的一个。
static_cast
任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast
double slope = static_cast<double>(j) /i ;
const_cast
const_cast只能改变运算对象的底层const。
reinterpret_cast
通常为运算对象的位模式提供较低层次上的重新解释。