我的C++primer长征之路:表达式

表达式

基本概念

左值右值

当一个对象被用作右值的时候,用到的是对象的值(也就是内容)。而当对象被用作左值时,用到的是对象的本身(也就是内存)。
一般情况下,用到右值的地方可以用左值来代替,但不能用右值来代替左值。

求值顺序

对于一个运算表达式,通常情况下不会明确其求值顺序,例如

int i = f1() * f2(); //不确定是先计算f1还是f2

如果表达式指向并修改了同一个对象,会产生未定义错误。

int i = 0;
cout<< i << " "<< ++i << endl; //未定义

只有四种运算符明确了求值顺序:

逻辑与 && 先求左侧对象的值,只有左侧对象的值为真时才继续求右侧对象的值
逻辑或|| 先求左侧运算对象的值,只有左侧运算对象的值为假时才会继续求右侧运算对象的值,即短路求值
条件运算符(cond? expr1: expr2) 先求cond的值,如果条件为真,对expr1求值并返回该值,否则对expr2求值并返回该值。
逗号运算符 首先对左侧表达式求值,将该结果丢掉,运算符真正结果是右侧表达式的值。

取余运算%的运算对象必须是整数类型。

递增递减运算符

前置版本和后置版本
前置版本将运算对象+1(-1),将改变后的对象作为求值结果,对象本身作为左值返回。
后置版本也会将运算对象+1(-1),但求值结果是运算对象改变之前的那个值的副本,副本作为右值返回。
除非必须,否则尽量使用前置版本的递增递减运算符,因为对于复杂的类型迭代器来说,后置++(–)会创建副本,消耗资源。

解引用运算符与递增运算符

auto pbeg = v.begin();
while(pbeg != v.end() && *pbeg >= 0){
    cout << *pbeg++ << endl; //*pbeg++ == *(pbeg++)
}

因为解引用运算符优先级较低,所以结果是pbeg的值+1,然后返回pbeg初始值的副本作为解引用的结果。

成员访问运算符

ptr->mem 等价于 (*p).mem

string s = "a string", *p = &s;
auto n = s.size();
n = (*p).size(); //解引用运算符优先级低于.运算符,所以要加上括号。
n = p->size();

这种情况是错误的,因为指针p没有size()成员函数,是它所指向的对象有size()成员函数。

*p.size(); //错误
p->size(); //正确

条件运算符 cond ? expr1 : expr2
条件运算符优先级非常低,因此当一条长表达式中嵌套条件运算子表达式时,通常要在其两端加上括号。

cout << ((grade > 60) ? "pass" : "fail") << endl; //正确,输出pass或者fail
cout << (grade > 60) ? "pass" : "fail" <<endl; //输出1或者0!
cout << grade > 60 ? "pass" : "fail" << endl; //错误,试图比较cout和60, 因为<< 返回值是cout

位运算符

~, <<, >>, &, |, ^

注意区分位与&和逻辑与&&,位或|和逻辑或||,位求反~和逻辑非!。

sizeof运算符
返回一条表达式或者一个类型名字所占用的字节数。满足右结合律,其值是一个size_t类型。
sizeof(type)、sizeof expr。其中第二种表达式只返回表达式结果类型的大小,而不计算其运算的值。

Sales_data data, *p;
sizeof(Sales_data); //存储Sales_data类型对象所占的空间的大小
sizeof data; //data的类型的大小
sizeof p; //指针所占空间的大小
sizeof *p; //p所指向的类型的空间大小,即sizeof(Sales_data)

sizeof与*运算符的优先级一样,因为sizeof满足右结合律,所以sizeof *p等价于sizeof (*p)。sizeof的运算对象中解引用一个无效的指针仍然是一种安全的行为,因为该指针并没有被用到。

此外还允许使用作用域运算符来获取类成员的大小。

sizeof data.revenue; //Sales_data的revenue成员的类型的大小
sizeof Sales_data::revenue; //等价形式

对数组进行sizeof等价于对数组的每个元素分别进行sizeof之后取和,也就是说sizeof不会把数组转换成指针来运算。
对string或者vector对象执行sizeof只会返回该类型固定部分的大小,不会计算对象中元素占用了多少空间。

类型转换

算数转换

运算符的运算对象将转换成最宽的类型,如一个运算对象的类型是long double,则无论另一个类型是什么都会转换成long double。当表达式中既有浮点数也有整数时,整数值将转换成相应的浮点数类型。

整型提升
把小整数类型转换成较大的整数类型。多实践才能记住。

数组转换成指针
在大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针。

int ia[10];
int *p = ia; //ia转换成指向数组首元素的指针

指针的转换
常量整数值0或者nullptr能转换成任意类型的指针。指向任意非常量的指针都能转换成void*。指向任意对象的指针都能转换成const void*。
转换成常量:允许将非常量类型的指针转换成指向相应的常量类型的指针,对于引用也是如此。

int i;
const int &j = i; //非常量转换成const int 的引用
const int *p = &i; //非常量的地址转换成const的地址
int &r = j, *q = p; //错误,不允许const转换成非常量

显式转换

static_cast, dynamic_cast, const_cast, reinterpret_cast。
dynamic_cast:支持运行时类型识别。
static_cast:只要不包含底层const,都可以使用static_cast。运用static_cast可以将较大的类型赋值给较小的类型,不会出现警告信息。static_cast对于编译器无法自动执行的类型转换也非常有用。如static_cast可以找回存在于void*指针中的值:

double d = 9;
void* p = &d; //对于任意非常量对象的地址都能存入void*
double *dp = static_cast<double*>(p);//正确,将void*转换回初始指针类型

const_cast:只能改变运算对象的底层const,不能改变表达式的类型。将常量对象转换成非常量对象。常用于函数重载的上下文中。

const char *pc = "abc";
char *p = const_cast<char*>(pc);//正确,但是通过p来写入值的话是未定义的行为。
const_cast<string>(pc); //错误,const_cast只能改变常量属性。

reinterpret_cast:为运算对象的位模式提供较低层次上的重新解释。很危险,尽量避免使用。

旧式的强制类型转换

type(expr);
(type)expr;

猜你喜欢

转载自blog.csdn.net/weixin_40313940/article/details/106086762