第三章 表达式

一、基础

使用关键字decltype的时候,左值和右值也有所不同。
如果表达式的求值结果是左值,decltype作用于该表达式(不是变量)得到一个引用类型。如,假定p的类型是int*,因为解引用运算符生成左值,所以decltype(p)的结果是int&。另一方面,因为取地址运算符生成右值,所以decltype(&p)都结果是int*,也就是说,结果是一个指向整型指针的指针。

求值顺序

优先级规定了运算对象的组合方式,但没有说明运算对象按照什么顺序求值。在大多数情况下,不会明确指定求值的顺序。
对于如下的表达式:
在这里插入图片描述
我们知道f1和f2一定会在执行乘法之前被调用,因为毕竟相乘的是这两个函数的返回值。但是我们无法知道到底是f1在f2之前调用还是f2在f1之前调用。
对于那些没有指定执行顺序的运算符来说,如果表达式指向并修改了同一个对象,将会引发错误并产生未定义的行为。
如:<<运算符没有明确规定何时以及如何对运算对象求值,因此下面的输出表达式是未定义的:
在这里插入图片描述
编译器可能先求++i的值再求i的值,此时输出结果是1 1;也可能先求 i 的值再求++i的值,输出结果0 1;甚至编译器还可能做完全不同的操作。因为此表达式的行为不可预知,因为不论编译器生成什么样的代码程序都是错误的。

运算对象的求值顺序与优先级和结合律无关,在一条形如f()+g()*h()+j()的表达式中:
优先级规定,g()的返回值和h()的返回值想乘。
结合律规定,f()的返回值先于g()和h()的乘积相加,所得结果再与j()的返回值相加。
对于这些函数的调用顺序没有明确规定。
如果f、g、h和 j 是无关函数,它们既不会改变同一个对象的状态也不执行 IO 任务,那么函数的调用顺序不受限制。反之,如果其中某几个函数影响同一对象,则它是一条错误的表达式,将产生未定义的行为。

建议:处理复合表达式

  1. 拿不准的时候最好用括号来强制让表达式的组合关系符合程序逻辑的要求。
  2. 如果改变了某个运算对象的值,在表达式的其他地方不要再使用这个运算对象。

二、算数运算符

在这里插入图片描述
对大多数运算符来说,布尔类型的运算对象将被提升为int类型,-1不为0,所以b2的值是真。
除法
在除法运算中,如果两个运算对象的符号相同则商为正(如果不为0的话),否则商为负。新标准规定商一律向0取整。
m%(-n)等于m%n,(-m)%n等于-(m%n),即符号与m相同。
如:
在这里插入图片描述

三、赋值运算符

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
新标准允许使用花括号括起来的初始值列表作为赋值语句的右侧运算对象。若左侧运算对象是内置类型,那么初始值列表最多只能包含一个值,而且该值即使转化的话其所占空间也不应该大于目标类型的空间。

四、递增和递减运算符

除非必须,否则不用递增递减运算符的后置版本。
前置版本的递增运算符避免了不必要的工作,它把值加1后直接返回改变了的运算对象。与之相比,后置版本需要将原始值存储下来以便于返回这个未修改的内容。

五、条件运算符

在输出表达式中使用条件运算符

条件运算符的优先级非常低。
在这里插入图片描述
第二条表达式等价于:
在这里插入图片描述
第三条表达式等价于:
在这里插入图片描述

六、位运算符

位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。
在这里插入图片描述
运算对象可以是带符号的,也可以是无符号的。如果运算对象是带符号的且它的值为负,那么为运算符如何处理运算对象的“符号位”依赖于机器。而且,此时的左移操作可能会改变符号位的值,因此是一种未定义的行为。
关于符号位如何处理没有明确的规定,所以强烈建议仅将位运算符用于处理无符号类型。

移位运算符

右侧的运算对象一定不能为负,且值必须严格小于结果位数,否则会产生未定义的行为。
在这里插入图片描述
移位运算符的优先级比算术运算符优先级低,但比关系运算符、赋值运算符和条件运算符的优先级高。

位求反运算符

在这里插入图片描述

七、sizeof运算符

两种形式:
在这里插入图片描述
在第二种形式中,sizeof返回的是表达式结果类型的大小。sizeof并不实际计算其运算对象的值。
在这里插入图片描述
sizeof *p :因为sizeof不会实际求运算对象的值,所以即使p是一个无效(未初始化)的指针也不会有什么影响。

对数组执行sizeof运算得到整个数组所占空间的大小。注意:sizeof运算不会把数组转换成指针来处理。

对string对象或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间。

八、类型转换

如果一个运算对象是无符号类型、另外一个运算对象是带符号类型,而且其中的无符号类型不小于带符号类型,那么带符号的运算对象转换成无符号的。如果带符号类型恰好为负值,结果等于这个负数加上无符号数的模。
若带符号类型大于无符号类型,此时转换的结果依赖于机器。如果无符号类型的所有值都能存在该带符号类型中,则无符号类型的运算对象转换成带符号类型。如果不能,那么带符号类型都运算对象转换成无符号类型。
在这里插入图片描述
在这里插入图片描述

显式转换

命名的强制类型转换

在这里插入图片描述
type:转换的目标类型。
expression :要转换的值。
cast-name:static_cast,dynamic_cast,const_cast,reinterpret_cast中的一种。指定了执行的是哪种转换。

static_cast

任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。
eg:通过将一个运算对象强制转换成double类型就能使表达式执行浮点数除法:
在这里插入图片描述
1、当需要把一个较大的算术类型赋值给较小的类型时,static_cast 非常有用。
如果编译器发现一个较大的算术类型试图赋值给较小的类型,就会给出警告信息;但是当我们执行了显示的类型转换后,警告信息就会被关闭了。
2、static_cast对于编译器无法自动执行的类型转换也非常有用。
eg:我们可以使用static_cast找回存在于void指针中的值:
在这里插入图片描述
当我们把指针存放在void
中,并且使用static_cast将其强制转换回原来的类型时,应该确保指针的值保持不变。也就是说,强制转换的结果将与原始的地址值相等,因此我们必须确保转换后所得的类型就是指针所指的类型。类型一旦不符,将产生未定义的后果。

const_cast

const_cast只能改变运算对象的底层const。
在这里插入图片描述
对于将常量对象转换成非常量对象的行为,我们一般称其为:去掉const性质。一旦我们去掉了某个对象的const性质,编译器就不再阻止我们对该对象进行写操作了。如果对象本身不是一个常量,使用强制类型转换获得写权限是合法的行为。然而如果对象是一个常量,再使用const_cast执行写操作就会产生未定义的后果。
只有const_cast能改变表达式的常量属性,使用其他形式的命名强制类型转换改变表达式的常量属性都将引发编译器错误。同样的,也不能用const_cast改变表达式的类型:
在这里插入图片描述
const_cast常常用于有函数重载的上下文中。

reinterpret_cast

reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。
假设有如下转换:
在这里插入图片描述
我们必须牢记pc所指的真实对象是一个int而非字符,如果把pc当成普通的字符指针使用就可能在运行时发生错误。
在这里插入图片描述
可能导致异常的运行时行为。
建议:避免强制类型转换。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值