c语言表达式和选择语句知识点总结

计算器不能让我们学会算术,只会使我们忘记算术。
C语言的一个特点就是它更多地强调表达式而不是语句,表达式是表示如何计算值的公式。最简单的表达式是变量和常量。变量表示程序运行时需要计算的值,常量表示不变的值,更加复杂的表达式把运算符用于操作数(操作数自身就是表达式)。在表达式a+(b*c) 中,运算符+ 用于操作数a 和(b*c) ,而这两者自身又都是表达式。
运算符是构建表达式的基本工具,C语言拥有异常丰富的运算符。首先,C提供了基本运算符,这类运算符在大多数编程语言中都有。
算术运算符 ,包括加、减、乘和除。
关系运算符 进行诸如“i 比0大”这样的比较运算。
逻辑运算符 实现诸如“i 比0大并且i 比10小”这样的关系运算。
但是C语言不只包括这些运算符,还提供了许多其他运算符。事实上,运算符非常多,我们需要在本书的前20章中逐步进行介绍。虽然掌握如此众多的运算符可能是一件非常繁琐的事,但这对于成为C语言专家是特别重要的。

1. 算术运算符
算术运算符 是包括C语言在内的许多编程语言中都广泛应用的一种运算符,这类运算符可以执行加法、减法、乘法和除法。下表展示了C语言的算术运算符。

加法类运算符和乘法类运算符都属于二元 运算符,因为它们需要两个 操作数。一元 运算符只需要一个 操作数:

一元运算符+ 什么都不做;实际上,经典C中甚至不存在这种运算符。它主要用于强调某数值常量是正的。
二元运算符或许看上去很熟悉,只有求余运算符% 可能例外。i%j 的值是i 除以j 后的余数。例如,10%3 的值是1,而12%4 的值是0。
[插图] 除% 运算符以外,表4-1中的二元运算符既允许操作数是整数也允许操作数是浮点数,两者混合也是可以的。当把int 型操作数和float 型操作数混合在一起时,运算结果是float 型的。因此,9+2.5f 的值为11.5,而6.7f/2 的值为3.35。
运算符/ 和运算符% 需要特别注意以下几点。
运算符/ 可能产生意外的结果。当两个操作数都是整数时,运算符/ 会丢掉分数部分来“截取”结果。因此,1 / 2 的结果是0而不是0.5。
运算符% 要求操作数是整数。如果两个操作数中有一个不是整数,程序将无法编译通过。
把零用作/ 或% 的右操作数会导致未定义的行为)。
[插图] 当运算符/ 和运算符% 用于负操作数时,其结果难以确定。根据C89标准,如果两个操作数中有一个为负数,那么除法的结果既可以向上取整也可以向下取整。(例如,-9/7 的结果既可以是-1 也可以是-2 。)在C89中,如果i 或者j 是负数,i%j 的符号与具体实现有关。(例如,-9%7 的值可能是-2 或者5 。)[插图] 但是在C99中,除法的结果总是向零截取的(因此-9/7 的结果是-1 ),i%j 的值的符号与i 的相同(因此-9%7 的值是-2 )。
“由实现定义”的行为 
术语由实现定义 (implementation-defined)出现频率很高,因此值得花些时间讨论一下。C标准故意对C语言的部分内容未加指定,并认为其细节可以由“实现”来具体定义。所谓实现是指程序在特定的平台上编译、链接和执行所需要的软件。因此,根据实现的不同,程序的行为可能会稍有差异。C89中运算符/和运算符%对负操作数的行为就是一个由实现定义行为的例子。
留下语言的一部分内容未加指定看起来可能有点奇怪,甚至很危险,但这正反映了C语言的基本理念。C语言的目标之一是高效,这常常意味着要与硬件行为相匹配。-9 除以7 时,有些CPU产生的结果是-1 ,有些为-2 。C89标准简单地反映了这一现实。
最好避免编写依赖于由实现定义的行为的程序。如果不可能做到,起码要仔细查阅手册——C标准要求在文档中说明由实现定义的行为。
运算符的优先级和结合性
当表达式包含多个运算符时,其含义可能不是一目了然的。例如,表达式i+j*k 是“i 加上j ,然后结果再乘以k ”还是“j 乘以k ,然后加上i ”呢?解决这个问题的一种方法就是添加圆括号,写为(i+j)*k 或者i+(j*k) 。作为通用规则,C语言允许在所有表达式中用圆括号进行分组。
可是,如果不使用圆括号结果会如何呢?编译器是把表达式i+j*k 解释为(i+j)*k 还是i+(j*k) ?和其他许多语言一样,C语言采用运算符优先级 (operator precedence)规则来解决这种隐含的二义性问题。算术运算符的相对优先级如下:
最高优先级:+   - (一元运算符)
      *   /   % 
最低优先级:+   - (二元运算符)
当两个或更多个运算符出现在同一个表达式中时,可以通过按运算符优先级从高到低的次序重复给子表达式添加圆括号来确定编译器解释表达式的方法。下面的例子说明了这种结果:
i + j \* k  等价于 i + ( j \* k ) 
-i \* -j   等价于 (-i) \* (-j) 
+i + j / k  等价于 (+i) + ( j / k ) 当表达式包含两个或更多个相同优先级的运算符时,仅有运算符优先级规则是不够用的。这种情况下,运算符的结合性 (associativity)开始发挥作用。如果运算符是从左向右结合的,那么称这种运算符是左结合的 (left associative)。二元算术运算符(即* 、/ 、% 、+ 和- )都是左结合的,所以
i - j - k   等价于 (i - j ) - k 
i \* j / k  等价于 (i \* j ) / k 
如果运算符是从右向左结合的,那么称这种运算符是右结合的 (right associative)。一元算术运算符(+和-)都是右结合的,所以
- + i   等价于  -(+i) 
在许多语言(特别是C语言)中,优先级和结合性规则都是非常重要的。然而,C语言的运算符太多了(差不多50种),很少有程序员愿意记住这么多优先级和结合性规则。程序员在有疑问时会参考运算符表(附录A),或者加上足够多的圆括号。

2.赋值运算符
求出表达式的值以后常常需要将其存储到变量中,以便将来使用。C语言的= (简单赋值 (simple assignment))运算符可以用于此目的。为了更新已经存储在变量中的值,C语言还提供了一种复合赋值 (compound assignment)运算符。
3.自增运算符和自减运算符
最常用于变量的两种运算是“自增”(加1)和“自减”(减1)。当然,也可以通过下列方式完成这类操作:

C语言允许用++ (自增 )和-- (自减 )运算符将这些语句缩得更短些。
乍一看,简化的原因仅仅是使用了自增和自减运算符:++ 表示操作数加1,而-- 表示操作数减1。但是,这是一种误导,实际上自增和自减运算符的使用是很复杂的。复杂的原因之一就是,++ 和-- 运算符既可以作为前缀 (prefix)运算符(如++i 和--i )使用也可以作为后缀 (postfix)运算符(如i++ 和i-- )使用。程序的正确性可能和选取适合的运算符形式紧密相关。
复杂的另一个原因是,和赋值运算符一样,++ 和-- 也有副作用:它们会改变操作数的值。计算表达式++i (“前缀自增”)的结果是i+1 ,而副作用的效果是自增i :

计算表达式i++ (“后缀自增”)的结果是i ,但是会引发i 随后进行自增:

第一个printf 函数显示了i 自增前的原始值,第二个printf 函数显示了i 变化后的新值。正如这些例子说明的那样,++i 意味着“立即自增i ”,而i++ 则意味着“现在先用i 的原始值,稍后再自增i ”。这个“稍后”有多久呢?[插图] C语言标准没有给出精确的时间,但是可以放心地假设i 将在下一条语句执行前进行自增。

选择语句
不应该以聪明才智和逻辑分析能力来评判程序员,而要看其分析问题的全面性。
尽管C语言有许多运算符,但是它所拥有的语句却相对较少。到目前为止,我们只见过两种语句:return 语句和表达式语句。根据对语句执行顺序的影响,C语言的其余语句大多属于以下3大类。
选择语句 (selection statement)。if 语句和switch 语句允许程序在一组可选项中选择一条特定的执行路径。
重复语句 (iteration statement)。while 语句、do 语句和for 语句支持重复(循环)操作。
跳转语句 (jump statement)。break 语句、continue 语句和goto 语句导致无条件地跳转到程序中的某个位置。(return 语句也属于此类。)
C语言还有其他两类语句,一类是复合语句(把几条语句组合成一条语句),一类是空语句(不执行任何操作)。 

[if 语句]   if (表达式) 语句 
注意,表达式两边的圆括号是必需的,它们是if 语句的组成部分,而不是表达式的内容。还要注意的是,与在其他一些语言中的用法不同,单词then 没有出现在圆括号的后边。
执行if 语句时,先计算圆括号内表达式的值。如果表达式的值非零(C语言把非零值解释为真值),那么接着执行圆括号后边的语句。下面是一个示例:

如果条件line_num == MAX_LINES 为真(有非零值),那么执行语句line_num = 0; 。
[插图]  不要混淆== (判等)运算符和= (赋值)运算符。语句if (i == 0)... 测试i 是否等于0,而语句if (i = 0)... 则是先把0赋值给i ,然后测试赋值表达式的结果 是否是非零值。在这种情况下,测试总是会失败的。
把== 运算符与=运算符相混淆是最常见的C语言编程错误,这也许是因为=在数学(和其他许多编程语言)中意味着“等于”。[插图] 如果注意到应该正常出现运算符== 的地方出现的是运算符= ,有些编译器会给出警告。
通常,if 语句中的表达式能判定变量是否落在某个数值范围内。例如,为了判定0 ≤i <n 是否成立,可以写成
[惯用法] if (0 <= i && i < n)... 
5.2.1 复合语句
要注意,在if 语句模板中,语句是一条语句 而不是多条语句:如果想用if 语句处理两条 或更多条语句,该怎么办呢?可以引入复合语句 (compound statement)。复合语句格式如下:[复合语句]   { 多条语句 } 
通过在一组语句周围放置花括号,可以强制编译器将其作为一条语句来处理。
下面是一个复合语句的示例:

为了表示清楚,通常将一条复合语句放在多行内,每行有一条语句,如下所示:

注意,每条内部语句仍然是以分号结尾的,但复合语句本身却不是。
下面是在if 语句内部使用复合语句的形式:

5.2.2 else 子句
if 语句可以有else 子句:
[带有else 子句的if 语句]   if (表达式) 语句 else 语句 
如果圆括号内的表达式的值为0,那么就执行else 后边的语句。
下面是一个含有else 子句的if 语句的示例:

注意,两条“内部”语句都是以分号结尾的。
if 语句包含else 子句时,出现了布局问题:应该把else 放置在哪里呢?和前面的例子一样,许多C程序员把它和if 对齐排列在语句的起始位置。内部语句通常采用缩进格式;但是,如果内部语句很短,可以把它们与if 和else 放置在同一行中:

C语言对可以出现在if 语句内部的语句的类型没有限制。事实上,在if 语句内部嵌套其他if 语句是非常普遍的。考虑下面的if 语句,其功能是找出i 、j 和k 中所存储的最大值并将其保存到max 中:

if 语句可以嵌套任意层。注意,把每个else 同与它匹配的if 对齐排列,这样做很容易辨别嵌套层次。如果发现嵌套仍然很混乱,那么不要犹豫,直接增加花括号就可以了:

 5.2.3 级联式if 语句
编程时常常需要判定一系列的条件,一旦其中某一个条件为真就立刻停止。“级联式”if 语句常常是编写这类系列判定的最好方法。例如,下面这个级联式if 语句用来判定n 是小于0,等于0,还是大于0:

虽然第二个if 语句是嵌套在第一个if 语句内部的,但是C语言程序员通常不会对它进行缩进,而是把每个else 都与最初的if 对齐:

5.2.4 “悬空else ”的问题

上面的else 子句究竟属于哪一个if 语句呢?缩进格式暗示它属于最外层的if 语句。然而,C语言遵循的规则是else 子句应该属于离它最近的且还未和其他else 匹配的if 语句。在此例中,else 子句实际上属于最内层的if 语句,所以正确的缩进格式应该如下所示:

为了使else 子句属于外层的if 语句,可以把内层的if 语句用花括号括起来:

这个示例表明了花括号的作用。如果把花括号用在本节第一个示例的if 语句上,那么就不会有这样的问题了。
5.2.5 条件表达式
C语言的if 语句允许程序根据条件的值来执行两个操作中的一个。C语言还提供了一种特殊的运算符 ,这种运算符允许表达式依据条件的值产生两个值 中的一个。
条件运算符 (conditional operator)由符号? 和符号: 组成,两个符号必须按如下格式一起使用:
[条件表达式]   表达式1 ? 表达式2 : 表达式3 
表达式 1、表达式 2和表达式 3可以是任何类型的表达式,按上述方式组合成的表达式称为条件表达式 (conditional expression)。条件运算符是C运算符中唯一一个要求3个操作数的运算符。因此,经常把它称为三元 (ternary)运算符。应该把条件表达式表达式 1? 表达式 2:表达式 3读作“如果表达式 1成立,那么表达式 2,否则表达式 3。”条件表达式求值的步骤是:首先计算出表达式 1的值,如果此值不为零,那么计算表达式 2的值,并且计算出来的值就是整个条件表达式的值;如果表达式 1的值为零,那么表达式 3的值是整个条件表达式的值。
下面的示例对条件运算符进行了演示:在第一个对k 赋值的语句中,条件表达式i > j ? i : j 根据i 和j 的大小关系返回其中一个的值。由于i 的值为1而j 的值为2,表达式i > j 比较的结果为假,所以条件表达式的值2被赋给k 。在第二个对k 赋值的语句中,表达式i >= 0 比较的结果为真,所以条件表达式(i >= 0 ? i : 0) 的值为1,然后把这个值和j 相加得到结果3。顺便说一下,这里的圆括号是非常必要的,因为除了赋值运算符,条件运算符的优先级低于先前介绍过的所有运算符。

5.2.7 C99中的布尔值
长期缺乏布尔类型的问题在C99中得到了解决。 C99提供了_Bool 型,所以在C语言的这一版本中,布尔变量可以声明为:

_Bool 是整数类型(更准确地说是无符号 整型),所以_Bool 变量实际上就是整型变量;但是和一般的整型不同,_Bool 只能赋值为0或1。一般来说,往_Bool 变量中存储非零值会导致变量赋值为1:

对于_Bool 变量来说,算术运算是合法的(不过不建议这样做),它的值也可以进行打印(显示0或者1)。当然,_Bool 变量也可以在if 语句中测试:

除了_Bool 类型的定义,C99还提供了一个新的头<stdbool.h> ,这使得操作布尔值更加容易。该头提供了bool 宏,用来代表_Bool 。如果程序中包含了<stdbool.h> ,可以这样写:

<stdbool.h> 头还提供了true 和false 两个宏,分别代表1和0。于是可以写

5.3 switch 语句
在日常编程中,常常需要把表达式和一系列值进行比较,从中找出当前匹配的值。在5.2节已经看到,级联式if 语句可以达到这个目的。例如,下面的级联式if 语句根据成绩的等级显示出相应的英语单词:

C语言提供了switch 语句作为这类级联式if 语句的替换。下面的switch 语句等价于前面的级联式if 语句:

执行这条语句时,变量grade 的值与4、3、2、1和0进行比较。例如,如果值和4相匹配,那么显示信息Excellent ,然后break 语句把控制传递给switch 后边的语句。如果grade 的值和列出的任何选择都不匹配,那么执行default 分支的语句,显示消息Illegal grade 。
switch 语句往往比级联式if 语句更容易阅读。此外,switch 语句往往比if 语句执行速度快,特别是在有许多情况要判定的时候。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值