4.1.6 操作符之间的优先顺序

在表达一些比较复杂的条件判断时,在同一个表达式中,有时可能会存在多个操作符。比如,我们在判断要不要买某个西瓜时,不仅要判断它的总价(单价8.2元/斤,一共10.3斤)是否小于100块钱(因为兜里只有这么多钱),同时还要判断这个西瓜是否有坏掉的地方。要表达这个复杂的条件判断,我们不得不把前面学过的算术操作符、关系操作符和逻辑操作符全都派上场:

bool bBad = false;   // 是否有坏掉的地方
float fPrice = 8.2; // 单价
float fWeight = 10.3; // 重量
// 判断总价是否小于100且是否坏掉
if(fPrice * fWeight < && !bBad)
{
cout<<"买西瓜"<<endl;
}
else
{
cout<<"算了,不买了"<<endl;
}

在“fPrice * fWeight < 100 && !bBad”这个表达式中,有算术操作符“*”,有关系操作符“<”,同时也还有逻辑操作符“!”和“&&”。那么,这么多操作符在同一个表达式中,到底该从哪一个操作开始呢?这个表达式的最终结果又是什么呢?

要想搞清楚一个表达式是按照什么顺序计算的,就得先搞清楚各个操作符之间的计算优先级。按照正确的计算顺序进行计算,才可以得出正确的结果。在C++中,各个操作符的优先级如表4-1所示。

表4-1  操作符的优先级

级别

操  作  符

说    明

1

( )

括号是所有操作符中的领导,具有最高的优先级。如果括号内部还有括号,内部括号的优先级更高

2

!、+(正号)、-(负号)、++、--

它们都是一元操作符,往往是对操作数进行计算得到结果后继续参与下一个计算

注意,这里的+、-指的是改变数值正负属性的符号,而不是加减操作的符号

3

*、/、%

乘、除、取余运算

4

+、-

加、减运算

5

>、>=、<、<=、==、!=

关系运算

6

&&

逻辑与运算,它会首先计算其左侧表达式的值,当其值为true时,才会继续计算右侧表达式的值,最后计算两个值的逻辑与

7

||

逻辑或运算,它同样会首先计算其左侧表达式的值,若其值为false,则继续计算右侧表达式的值,最后计算两个值的逻辑或

8

=、+=、*=、/=、%=

赋值操作

表达式的计算顺序规则是:总是优先计算优先级较高的操作符;同一优先级的操作符,则按照从左到右的顺序进行计算;如果有特殊规则的操作符(比如逻辑与“&&”),则按照特殊规则进行计算。在清楚了各操作符的优先级及表达式的计算规则后,那就可以计算上面这个复杂表达式的结果了。在这个表达式中,有一个拥有特殊规则的操作符“&&”,按照它的计算规则,这个表达式会首先计算其左侧表达式的值:

fPrice * fWeight <   // 首先得到计算的左侧表达式

在这个表达式中,没有特殊规则的操作符,那就按照操作符的优先级进行计算。其中,优先级最高的是计算总价的乘法算术操作符“*”,对其进行计算后得到一个中间结果:

84.46 <    // fPrice*fWeight的结果是84.46

这个中间结果表达式只有一个操作符,直接计算得到其结果值为true。按照“&&”操作符的计算规则,如果左侧表达式的值为true,则继续计算其右侧表达式的值。所以,接下来要计算的表达式变为:

true && !bBad     // 左侧表达式计算完成后的中间结果

在“&&”的右侧只有一个操作符“!”,直接计算得到的中间结果是:

true && true

现在,剩下唯一的逻辑与操作符“&&”,最终结果一目了然,对两个true值进行逻辑与运算,表达式的最终结果是true。计算机在对表达式进行计算时,是按照各个操作符的优先级确定的计算顺序进行的。反过来,这也就要求我们在设计表达式的时候,也同样必须遵守操作符的优先顺序,按照这个顺序来设计表达式。否则,实际的计算顺序跟我们设想的计算顺序不同,得到的计算结果自然也就跟我们的设想大相径庭了。从这个意义上讲,熟悉和掌握操作符的优先级十分必要。

最佳实践:合理使用括号标示表达式的计算顺序

从上面这个例子我们可以看到,过于复杂的表达式计算起来非常麻烦。虽然表达式是由计算机负责计算,我们不用担心计算机怕麻烦。但是,表达式却是由程序员进行设计,并且也是要提供给他人阅读的。设计过于复杂的表达式很容易出错,且代码的可读性非常差。所以我们应当尽量避免在同一表达式中混合使用多个操作符,尽量保持表达式的短小精悍。必要的时候,可以将复杂的表达式拆分成多个较小的表达式分别计算得到中间结果,最后再将中间结果组合起来得到最终结果。例如,我们可以把上面的复杂表达式拆分成两个较小的表达式,分别判断是否有坏掉的地方以及总价是否小于100块,然后再将这两个中间结果进行“与”运算,得到最终结果:

// 将复杂表达式拆分成两个较小的表达式
bool bFresh = !bBad; // 表示是否新鲜
float fTotal = fPrice * fWeight; // 计算总价
bool bMoney = fTotal < ; // 判断总价是否小于100块
// 对中间结果进行比较
if( bFresh && bMoney)
// …

经过这样的拆分,每个表达式的计算都清楚明了,减少了出错的可能,可读性也得到了提升。但是它同时也带来一个不便之处,那就是代码变的过于繁琐。既想得到拆分表达式带来的清楚明了的好处,又想避免代码繁琐的不便,那就只有使用“()”了。

“()”的优先级是所有操作符中最高的,使用它,可以人为地按照设计者的意图标示表达式中的计算顺序。比如,可以改写上面的表达式,用括号来表达我们希望的计算顺序,让其表达的意义更加清晰:

// …
if(((fPrice * fWeight) < ) && (!bBad))
// …

使用括号后,整个表达式的计算顺序变得一目了然:按照括号确定的计算顺序,首先计算最里层的(fPrice * fWeight) 得到中间结果84.46,然后计算(84.46 < 100)得到中间结果true,接着计算(!bBad)得到中间结果true,最后计算“true && true”得到最终结果true。使用括号后,计算顺序跟默认顺序相同,但是却增加了代码的可读性,让我们对计算顺序一目了然,同时也避免了让代码变得过于繁琐。另外,在某些特殊情况下需要改变表达式的默认计算顺序时,括号成为一种必须。

总结起来,使用“()”后,我们想让表达式按照什么顺序计算就按照什么顺序计算,妈妈再也不用担心我记不住各个操作符的优先级。

4.1.7  将表达式组织成语句

学习C++编程,实际上也就是学习如何使用这门特殊的语言来描述和表达现实世界,就如同我们学习英语是为了用它来描述和表达现实世界一样。在前面的章节中,我们学习了操作符,学习了由操作符连接操作数而构成的各种表达式,而这些只能算是这门语言中的“短语”,它们可以表达一定的意义,但却是不完整的:

// 短语式的表达式
a // 一个单独的变量,什么都不做
+ // 用算术操作符“+”计算3和2的和

这些表达式可以被执行,但它们并不改变程序的状态,也没有计算结果保留下来,所以没有任何实际的意义。就像在英语中我们需要给短语加上主谓宾才能构成一个完整的句子一样,在C++中,我们也同样需要把一些表达零散意义的表达式组合起来,最后再加一个英文分号表示结束,以此来形成一个语句,用以完成某个相对独立而完整的功能。例如,把上面两个表达式通过赋值操作符组合起来,就形成了一条完整的赋值语句:

// 赋值语句
a = + ;

形成语句后,它表达了一个完整的意义:用算术操作符“+”计算3和2的和,然后将其赋值给变量a。

在C++中,语句和表达式并没有严格的区分。很多时候,一个表达式加上一个分号就可以直接形成一条语句。语句强调它所完成的功能,而表达式关注它所描述的运算和最终的结果。在此之前,我们已经接触过两种最常见的语句类型:变量定义语句和赋值语句。

知道更多:使用“{}”表示的语句块

当连续的多条语句属于同一个控制结构时,可以用一对花括号“{}”将这些语句括起来,从而形成一个语句块,共同表达一个相对独立的意义。在使用上,语句块与单独的语句并无太大区别,但是它的意义在于,它可以将多条语句打包成一个语句块,从而可以在for循环等控制结构中执行多条语句。例如,在for循环结构中,我们可以这样来统计从1到100间所有整数的和:

int nTotal = ;

for(int i = ; i <= ; ++i)
 nTotal += i;

这个统计只需要一条语句就可以完成,自然可以把这条语句直接放在for循环结构之后完成,可是如果我们只需要统计这个区间中所有偶数的和,那么就需要加上条件判断,这就不是单独一条语句可以完成的了。我们必须用“{}”将所有判断偶数、统计偶数的语句打包成一个语句块,然后放在for循环结构之后才能完成统计:

for(int i = ; i <= ; ++i)
{ // for循环语句块开始
if( == i%) // 判断语句
nTotal += i; // 统计语句
} // for循环语句块结束

除了打包语句之外,语句块的另外一个意义是,它代表了C++中的作用域的起讫位置。关于作用域的具体介绍可以参考后继的7.3.3小节。

你好,C++(18) 到底要不要买这个西瓜?4.1.6 操作符之间的优先顺序的更多相关文章

  1. async/await到底该怎么用?如何理解多线程与异步之间的关系?

    前言 如标题所诉,本文主要是解决是什么,怎么用的问题,然后会说明为什么这么用.因为我发现很多萌新都会对之类的问题产生疑惑,包括我最初的我,网络上的博客大多知识零散,刚开始看相关博文的时候,就这样.然后 ...

  2. [Effective JavaScript 笔记]第18条:理解函数调用、方法调用及构造函数调用之间的不同

    面向对象编程中,函数.方法.类的构造函数是三种不同的概念. JS中,它们只是单个构造对象的三种不同的使用模式. 三种不同的使用模式 函数调用 function hello(username){ ret ...

  3. 超级详细的iptable教程文档

    Iptabels是与Linux内核集成的包过滤防火墙系统,几乎所有的linux发行版本都会包含Iptables的功能.如果 Linux 系统连接到因特网或 LAN.服务器或连接 LAN 和因特网的代理 ...

  4. iptables学习笔记

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/SJQ. http://www.cnblogs.com/shijiaqi1066/p/3812510.html ...

  5. Iptabels详解

    http://www.07net01.com/2016/02/1291283.html Iptabels是与linux内核集成的包过滤防火墙系统,几乎所有的linux发行版本都会包含Iptables的 ...

  6. Linux 学习之防火墙配置

    1.安装iptables防火墙 yum install iptables  2. 清除已有的iptables规则  iptables -F  iptables -X  iptables -Z  3.显 ...

  7. Iptables详解+实例

    Iptabels是与Linux内核集成的包过滤防火墙系统,几乎所有的linux发行版本都会包含Iptables的功能.如果 Linux 系统连接到因特网或 LAN.服务器或连接 LAN 和因特网的代理 ...

  8. iptables之centos6版本详解

    1 Linux防火墙概述 Linux防火墙实际指的是Linux下的Netfilter/Iptables.Netfilter/Iptables是2.4.x/2.6.x版本Linux内核集成的IP信息包过 ...

  9. leetcode 学习心得 (2) (301~516)

    源代码地址:https://github.com/hopebo/hopelee 语言:C++ 301. Remove Invalid Parentheses Remove the minimum nu ...

随机推荐

  1. VS2010安装项目的系统必备中添加.NET 2.0

    把DotNetFX.rar解压后的DotNetFX文件夹,放置于安装了 VS2010 的 C:\Program Files\Microsoft SDKs\Windows\v7.0A\Bootstrap ...

  2. BZOJ3564 信号增幅仪

    http://www.lydsy.com/JudgeOnline/problem.php?id=3564 思路:先旋转坐标系,再缩进x坐标,把椭圆变成圆,然后做最小圆覆盖. 还有,为什么用srand( ...

  3. ASM-51汇编出错信息表

    1  Address Out of Range 一个被计值的目标地址超出了当前语句的范围.2 Badly Formed Argument 数字规定的类型中有非法数字存在.3 Illefal Equal ...

  4. elasticsearch 重启后,需要的操作

    如果elasticsearch 集群挂了,请勿开启Logstash 同步数据,需等待elasticsearch集群恢复后,在继续写入

  5. [LeetCode] 28. Implement strStr() 解题思路

    Implement strStr(). Returns the index of the first occurrence of needle in haystack, or -1 if needle ...

  6. java与数据结构(6)---java实现链栈

    栈之链式存储结构链栈 链栈 栈的链式存储结构成为链栈.链栈是没有头结点,头结点就是栈顶指针top. 代码结构 package list; public interface Stackable;公共接口 ...

  7. Mac OS X Shell 脚本和终端命令

    系统 重启 Mac OS X: 1 shutdown - r now 关闭 Mac OS X: 1 shutdown now 电源管理/省电 获取当前电源管理设置的信息 1 pmset -g 设置显示 ...

  8. MYSQL触发器学习笔记

    课程学至金色晨曦科技公司技术总监沙利穆 触发器 1.       什么是触发器 触发器是一种特殊类型的存储过程,不由用户直接调用.创建触发器时会对其进行定义,以便在对特定表或列作特定类型的数据修改时执 ...

  9. Linux网络配置相关

    路由相关 #添加到主机的路由 route add -host 192.168.1.2 dev eth0 route add -host 192.168.1.2 gw 192.168.1.1 注1:添加 ...

  10. Android Sensor Test

    魅蓝note可用 [{Sensor name="MPL Gyroscope", vendor="Invensense", version=1, type=4, ...