第四章 表达式
4.1左值和右值
当一个对象被用作右值的时候,用的是对象的值(内容),当对象被用作左值的时候,用的对象的身份(在内存中的位置)
4.2 求值顺序
有4种运算符明确规定了运算对象的求值顺序:
逻辑与(&&)和逻辑或(II):都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧对象的值。这种策略称为短路求值
条件运算符(?😃:
cond ? expr1 : expr2
先求cond的值,条件为真返回expr1,条件为假返回expr2
逗号运算符:按照从左向右的顺序依次求值;
逗号运算符首先对左侧的表达式求值,然后将求值结果丢弃掉。逗号运算符真正的结果是右侧表达式的值,如:
vector<int>:size_type cnt = ivec.size;
for(vector<int>::size_type ix = 0;ix != ivec.size();++ix,--cnt)
ivec[ix] =cnt
4.3 算术运算符
运算符 | 功能 | 用法 |
---|---|---|
+ | 一元正号 | + expr |
- | 一元负号 | - expr |
* | 乘法 | expr * expr |
/ | 除法 | expr / expr |
% | 求余 | expr % expr |
+ | 加法 | expr + expr |
- | 减法 | expr - expr |
**优先级:**一元运算符的优先级最高,接下来是乘法和除法,优先级最低的是加法和减法;
布尔对象值取负:对于大多数运算符对象来说,布尔类型的运算对象将被提升为int类型。
bool b = true;
bool b2 = -b;//b2是true
如上所示,布尔变量b的值为真,参与运算时将被提升成整数值1,对它取负后的结果是-1。将-1再转换回布尔值并将其作为b2的初始值,显然值不等于0,转换成布尔值后应该为1.所以b2的值是真。
4.4 递增和递减运算符
递增和递减运算符有两种形式:前置版本和后置版本。
前置版本首先将运算对象加1(或减1),然后将改变后的对象作为求值结果;
后置版本也会将运算对象加1(或减1),但是求值结果是运算对象改变之前那个值的副本;
int i = 0 ,j = 0;
j = ++i; //j=1,i=1:前置版本得到递增之后的值;
j = i++; //j=1,i=2:后置版本得到递增之前的值;
Tips:除非必须,否则不用递增递减运算符的后置版本
前置版本的递增运算符避免了不必要的工作,它将值加1后直接返回改变了的运算对象;后置版本需要将原始值存储下来以便于返回这个未修改的内容。如果不需要修改前的值,那么后置版本的操作就是一种浪费
4.5条件运算符
条件运算符(? :)的优先级非常低,因此当一条长表达式中嵌套了条件运算子表达式时,通常需要在它两端加上括号
cout << ((value < 60) ? "fail" : "pass") << endl;//输出pass或者fail
cout << (value < 60) ? "fail" : "pass";//输出1或者0
cout << value < 60 ? "fail" : "pass";//错误
4.6 sizeof运算符
sizeof返回一个size_t类型的常量表达式。运算符的运算方式有两种形式:
sizeof(type) 和sizeof expr
第二种形式中,sizeof返回的是表达式结果类型的大小,sizeof并不计算其运算对象的值
struct Sales_data
{
int sales_num;
char sales_name[10];
};
Sales_data data,*p;
sizeof(Sales_data);//存储Sales_data类型的对象所占空间的大小
sizeof(data);//data的类型的大小,即sizeof(Sales_data)
sizeof p;//指针所占空间大小,结果为8(指针存放的是32位地址)
上面这个例子中,sizeof data输出结果为16,这是因为字节对齐,sales_name后面填充两个字节使sales_num与整个结构体对齐
用win10+vscode测试发现,32位系统,使用的是4对齐,即sales_name分配空间为4的倍数
对引用类型执行sizeof运算得到被引用对象所占空间的大小;
对数组执行sizeof运算得到整个数组所占空间大小
对于string或者vector对象,sizeof会返回一个固定值而不会去计算对象中元素占用了多少空间。(win10+vscode输出为32)
4.7命名的强制类型转换
一个命名的强制类型转换具有如下形式:
cast-name<type>(expression)
cast-name是static_cast、dynamic_cast、const_cast和reinterpret_cast中的一种
4.7.1 static_cast
任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。
void *p = &d;
double *dp = static_cast<double*>(p);
当把指针存放在void *中,并且使用static_cast将其强制转换回原来的类型时,应该确保指针的值保持不变,即强制转换的结果将与原始的地址值相等,必须确保转换后得到的类型就是指针所指的类型
4.7.2 const_cast
const_cast只能改变运算对象的底层const(底层const:所指对象是一个常量不可改变)
const char *pc;
char *p = const_cast<char *>(pc);
对于常量对象转换成非常量对象的行为,称为去掉const性质
const char *cp;
char *q = static_cast<char *>(cp);//错误:static_cast不能转换const性质
static_cast<string>(cp);//正确:字符串字面值转换成string
const_cast<string>(cp);//错误,const_cast只改变常量属性
只有const_cast能改变表达式的常量熟悉,使用其他形式的命名强制转换类型改变表达式的常量属性都将引发编译器错误。同样的,也不能用const_cast改变表达式的类型
建议:避免强制类型转换