C++ Primer 第四章 表达式
第四章 表达式
4.1 基础
-
一元运算符和二元运算符
- 一元运算符:作用于一个运算对象的运算符,例如取地址符(&),解引用符(*)
- 二元运算符:作用于两个运算对象的运算符,例如相等运算符(==)
- 函数调用:特殊的运算符,对运算对象数量没有限制
-
左值和右值
- 左值可以位于赋值语句的左侧,右值不能
- 当对象被用作右值的时候,用的是对象的值(内容),当对象被用作左值的时候,用的是对象的身份(在内存中的位置)
- 在需要右值的时候可以用左值来代替,但是不能把右值当成左值(也就是位置)使用.当一个左值被当成右值使用时,实际使用的是它的内容(值)
- 使用左值对象的例子
- 赋值运算符的需要一个左值作为其左侧运算对象,其返回也是一个左值
- 取地址符作用于一个左值运算对象,返回一个指针,这个指针是右值
- 下标运算符的求值结果是左值
-
求值顺序
- 运算符之间有优先级,但是没有说明运算符的运算对象按照什么顺序求值,例如
int i = f1() * f(2)
,我们可以确定f1和f2会在乘法之前被调用,但是无法确定他们之间哪个会被更早的调用 - 例如 << 运算符,他的表达式没有明确规定何时以及如何对运算对象求值,因此形如
cout << i << " " << ++i << endl;
这样的语句结果是不可预知的 - 有4种运算符明确规定了求值顺序,(&&),(||),(?:)和(,)
- 因此,形如
f() + g() * h()
这样的表达式中,如果函数之间是无关函数,那么他们的调用顺序不受限制,否则如果其中一个函数会影响其他函数的结果,则它是一条错误表达式
- 运算符之间有优先级,但是没有说明运算符的运算对象按照什么顺序求值,例如
4.2 递增和递减运算符
- 前置版本得到递增之后的值 ++i
- 后置版本得到递增之前的值 i++
- 这两种运算符必须作用于左值运算对象,前置版本将对象本身作为左值返回,后置版本将对象原始值的副本作为右值返回
- tips:优先使用前置运算符
- 前置版本的递增运算符避免了不必要的操作,相比较而言,后置版本需要将原始值存下来以便于返回这个未修改的内容,如果我们不需要修改前的值,那么后置版本的操作就是一种浪费
- 对于整数和指针类型而言,编译器可以对这种操作进行优化,但是对于复杂的迭代器而言,这种额外的工作就消耗巨大了,因此建议使用前置的运算符
4.3 成员访问运算符
- 点运算符:如果成员所属的对象是左值,那么结果是左值,反之结果是右值
- 箭头运算符:作用于一个指针类型的运算对象,结果是一个左值
4.4 命名的强制类型转换
- 一个命名的强制类型转换具有以下形式
cast-name<type>(expression)
- type:转换的目标类型,如果type是引用类型,则结果是左值
- expression:要转换的值
- cast-name:
static_cast
,dynamic_cast
,const_cast
和reinterpret_cast
中的一种
4.4.1 static_cast
- 任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast.(底层const:指针所指向的对象是个常量)
- e.g.
double s = static_cast<double>(j) / i;
- e.g.
- 当需要把一个较大的算术类型给较小的类型时,static_cast非常拥有,这个转换相当于告诉编译器和读者:我们知道并且不在乎精度损失
- static_cast对于编译器无法自动执行的类型转换也非常有用,例如可以用来找回存在于void*指针的值
- e.g.
void *p = &d; double *s = static_cast<double*>(p);
- e.g.
4.4.2 const_cast
- const_cast只能改变对象的底层const
const char *pc;
char *p = const_cast<char*>(pc); //正确,但是通过p写值是未定义的行为
- 对于将常量对象转换为非常量对象的行为,我们一般称为"去掉const性质",但是如果对象是一个常量,再使用const_cast执行写操作就会产生未定义的后果
- 只有const_cast能改变表达式的常量属性,使用其它形式的命名强制类型转换改变表达式的常量属性都将引发编译器错误
- 同样的,也不能使用const_cast改变表达式的类型
const char *cp;
char *q = static_cast<char*>(cp); //错误:static_cast不能转换掉const性质
static_cast<string>(cp); //正确:字符串字面值转换成string类型
const_cast<string>(cp); //错误:const_cast只能改变常量属性
4.4.3 reinterpret_cast
- reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释
- e.g.
int *ip; char* pc = reinterpret_cast<char*>(ip);
- pc所指的真实对象是一个int而非char,如果把pc当成普通的字符指针就会在运行时发生错误,例如
string str(pc);
可能导致异常
- pc所指的真实对象是一个int而非char,如果把pc当成普通的字符指针就会在运行时发生错误,例如
- 使用reinterpret_cast是非常危险的,例如上面的例子,编译器不会发出任何警告,使用的时候就会认定他是char*类型
4.4.4 强制类型转换
- 强制类型转换干扰了正常的类型检查,因此建议避免使用强制类型转换,尤其是reinterpret_cast,在有重载函数的上下文之外的情况下,使用const_cast意味着程序存在着设计缺陷,static_cast和dynamic_cast不应频繁使用
- 对于旧式的强制类型转换 type(expr)和(type) expr,我们在执行的时候如果换成const_cast和static_cast也合法,则其行为会被对应的替换,如果不合法,则执行与reinterpret_cast类似的功能
- 旧式的强制类型转换从表现形式上来看不那么清晰明了,容易被看漏,出现了问题追踪起来也更困难