第四章 表达式
前言
本章主要介绍:语言本身定义、并用于内置类型运算对象的运算符。简单介绍:几种标准库定义的运算符。
表达式本身由一个或多个运算对象组成,其目的是得到一个结果:
value operator n*(operation object)
通过运算符将一个或多个运算对象结合起来便组成了表达式!
4.1 基础
基本概念
一元运算符(unary operator)、二元运算符(binary operator)和三元运算符(ternary operator)
解释:
- 一元运算符:作用于一个运算对象的运算符,例如:取地址符(&)和解引用符(*)
- 二元运算符:作用于两个运算对象的运算符,例如:相等运算符(==)和乘法运算符(*)
- 三元运算符:作用于三个运算对象的运算符,格式为:condition ? expression1 : expression2
补充
函数调用也是一种特殊的运算符,只不过它对运算对象数量没有限制。
对于能够充当多种元运算符的运算符,根据上下文判断其属于那种元运算符,例如:*。
重载运算符
在这里简单介绍一下重载运算符,重载运算符其实就是对已存在的运算符赋予另一层含义,例如:IO库中的“<<”和“>>”运算符。
重载运算符会改变运算对象的类型和返回值的类型,但是无法改变运算对象的个数、运算符的优先级和结合律。
左值和右值
具体查看代码
优先级和结合律
关键点:
- 优先级和结合律决定了运算对象组合的方式
- 括号无视该规则
- 表达式依赖于子表达式的结合方式
- 高优先级的运算对象比低优先级的运算对象更为紧密的组合在一起
- 算术运算对象满足左结合律
示例:
int a=1+2*4/2+6; /* 11 */
int b=(1+2)*4/2+6; /* 12 */
求值顺序
对于函数的调用来说,求值顺序的不同很有可能会产生不同的结果,甚至是错误!
例如:
int test_number = 1;
int test_evaluationOrder_test_1() { return test_number++; }
int test_evaluationOrder_test_2() { return test_number += 3; }
/**
* 求值顺序的探讨
*/
void test_evaluationOrder() {
int i = 0;
/**
* <<运算符并未明确规定何时以及如何对运算对象进行求值,没有明确的执行顺序,那么该表达式就是错误的。因为您无法确定是i先执行还是++i先执行,得到的答案不论如何都是错误的
* */
std::cout << i << " " << ++i << "\n";
/**
* 同理:这下面一句也是错误的。
* 你无法判断是test_1()先执行还是test_2()先执行。
*/
int number = test_evaluationOrder_test_1() * test_evaluationOrder_test_2();
std::cout << number << "\n";
}
事实上,除了以下四种运算符明确规定运算对象的求值顺序,其他运算符并没有明确说明:
- 逻辑与(&&)运算符
- 逻辑或(||)运算符
- 条件(?:)运算符
- 逗号(,)运算符
这就导致一个问题:这些函数的调用顺序没有明确规定,那么如果某几个函数会影响到同一个对象,那么必然导致结果的唯一性。
建议
对于复合表达式而言,有两条经验之谈:
- 拿不准最好用括号括起来。
- 如果已经改变某个对象的值,那么表达式中就不能再调用能够影响该值的函数。
4.2 算术运算符
算术运算符分别有:
- + - ,一元正号(负号)
- * / % ,乘法、除法、取余
- + -,加号、减号
算术运算有时候也会产生未定义的结果,例如除数是0、数据溢出等。
在取余这里,C++ 11定义有:
- m%(-n)=m%n
- (-m)%n=-(m%n)
具体示例:
21%6=3; 21/6=3;
21%7=0; 21/7=3;
-21%-8=-5; -21/-8=2;
21%-5=1; 21/-5=-4;
4.3 逻辑和关系运算符
关系运算符作用于算术类型或指针类型,逻辑运算符作用于任意能转换成布尔值的类型。二者返回值皆为布尔类型。
其含有:
- !,逻辑非
- < <= > >=,小于、小于等于、大于、大于等于
- == !=,等于、不等于
- &&,逻辑与
- ||,逻辑或
注意
在相等性测试和布尔字面值上,如果想要测试一个算术对象或指针对象的真值,最后的方式是用if语句:
if(value)
,在这种条件下,编译器会自动将算术对象转换为布尔值并进行判断。注意:不要使用
if(value==true)
的方式进行判断,原因:①、该写法不直观;②、如果value不是布尔值则失去比较意义:如果value不是布尔值,那么首先将true转换为value的类型,如果value不是布尔值,则转换为if(value==1)
,这样的比较是没有意义的。综述,如果进行比较运算,如果比较对象不是布尔类型,请不要使用布尔字面值作为运算对象参与比较。
4.4 赋值运算符
赋值运算符的左侧运算对象必须是一个可修改的左值。
赋值运算满足右结合律,例如:
int rval,lval;
rval=lval=0; /* rval和lval的值均为0 */
lval作为靠左赋值运算符的右侧运算对象,赋值运算返回其左侧运算对象,所以靠右的赋值运算的结果被赋值给rval。
4.5 递增和递减运算符
递增和递减运算符有前置和后置两种方式,其原理:
前置:将对象本身加/减1后作为左值返回;
后置:将对象原始值的副本作为右值返回,后再进行对象本身的加/减1;
建议
除非必要,在使用递增和递减运算符时一律使用前置方式,使用后置方式会导致不必要的资源浪费,同时前置方式更符合编程初衷。