第四章 表达式🚀
- 算术运算符,包括加、减、乘和除。
- 关系运算符进行诸如“i比0大”这样的比较运算。
- 逻辑运算符实现诸如“i比0大并且i比10小”这样的关系运算。
4.1 算术运算符🚀
一元运算符 + 什么都不做;实际上,经典 C中甚至不存在这种运算符。它主要用于强调某数值常量是正的。
除%运算符以外,表4-1中的二元运算符既允许操作数是整数也允许操作数是浮点数,两者混合也是可以的。 当把int 型操作数和 float 型操作数混合在一起时,运算结果是 float型的。因此,9+2.5f 的值为 11.5 ,而 6.7f/2 的值为 3.35 。
运算符/和运算符%需要特别注意以下几点。
1.运算符 / 可能产生意外的结果。 当两个操作数都是整数时 ,运算符 / 会丢掉分数部分来“截
取”结果。因此, 1 / 2 的结果是 0 而不是 0.5 。
2.运算符%要求操作数是整数。 如果两个操作数中有一个不是整数,程序将无法编译通过。
3. 把零用作/或%的右操作数会导致未定义的行为 ( 4.4 节)。
4. 当运算符/和运算符%用于负操作数时,其结果难以确定。 根据 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标准要求在文档中说明由实现定义的行为。
运算符的优先级和结合性
4.2 赋值运算符🚀
4.2.1 简单赋值🚀
表达式v = e的赋值效果是求出表达式e的值,并把此值复制给v。
如果v和e的类型不同,那么赋值运算发生时会把e的值转化为v的类型:
注意:
注意由于存在类型转换,串在一起的赋值运算的最终结果可能不是预期的结果:
int i;
float f;
f = i = 33.3f;
首先把数值 33 赋值给变量 i ,然后把 33.0 (而不是预期的 33.3 )赋值给变量 f 。
4.2.2 左值🚀
4.3 自增运算符和自减运算符🚀
++i意味着“立即自增i”,而i++则意味着“现在先用i的原始值,稍后 再自增i”。这个“稍后”有多久呢? C语言标准没有给出精确的时间,但是可以放心地假 设i将在下一条语句执行前进行自增。
需要记住的是,后缀 ++ 和后缀 -- 比一元的正号、负号优先级高,而且都是左结合的。前缀
++ 和前缀 -- 与一元的正号、负号优先级相同,而且都是右结合的。
4.4 表达式求值
(最高优先级为1,最低优先级为5),最后一列显示了每种运算符的结合性。
根据C 标准,类似 c = (b = a + 2) – (a = 1); 和 j = i * i++; 这样的语句都会导致“未定义的行为”(undefined behavior ),这跟 4.1节中讲的由实现定义的行为是不同的。当程序中出现未定义的行为时,后果是不可预料的。不同的编译器给出的编译结果可能是不同的,但这还不是唯一可能发生的事情:首先程序可能无法通过编译,就算通过了编译也可能无法运行,就
算可以运行也有可能崩溃、不稳定或者产生无意义的结果。换句话说,应该像躲避瘟疫一样避免未定义的行为。
4.5 表达式语句🚀
C语言有一条不同寻常的规则,那就是任何表达式都可以用作语句。换句话说,不论表达
式是什么类型,计算什么结果,我们都可以通过在后面添加分号的方式将其转换成语句。例如,可以把表达式++i转换成语句
++i;
键盘上的误操作很容易造成“什么也不做”的表达式语句。例如,本想输入
i = j;
但是却错误地输入
i + j;
(因为 = 和 + 两个字符通常在键盘的同一个键上,所以这种错误发生的频率可能会超出
想象。)某些编译器 可能会检查出无意义的表达式语句 ,会显示类似“ statement with no
effect ”的警告。
问与答🚀
问:我注意到C语言没有指数运算符。如何求一个数的幂呢?
答:通过重复乘法运算的方法可以进行整数的较低的整数次幂运算( i * i * i 是 i的立方运算)。如果想计算 非 整数次幂,可以调用pow函数( 23.3 节)。(abs()绝对值)
问:我想把%运算符用于浮点数,但程序无法通过编译。该怎么办?(p.37)
答:%运算符要求操作数是整数,可以试试fmod函数( 23.3节)。
C99 出现的时候,大多数 CPU 都对除法的结果向零取整,所以这也被写入这一标准作为唯一允许的结果。
问:如果C语言有左值,那它也有右值吗?(p.41)
答:是的,当然。左值是可以出现在赋值 左侧 的表达式,而右值则是可以出现在赋值 右侧 的表达式。因此,右值可以是变量、常量或更加复杂的表达式。本书和C 标准一样,采用“表达式”这一术语来代替“右值”。(左值->空间 右值->值)
*问:前面提到:如果v有副作用,那么v += e 不等价于 v = v + e。可以解释一下吗?(p.41)
答: 计算v += e只会求一次v的值,而计算v = v + e则会求两次v的值 。在后一种情况下,对 v 求值可能引起的任何副作用也都会出现两次。在下面的例子中,i 只自增一次:
a[i++] += 2;
如果用 = 代替 += ,语句变成:
a[i++] = a[i++] + 2;
*
i 的值在别处被修改和使用了,因此上述语句的结果是未定义的。 i 的值可能会自增两次,但我们无法确定到底会发生什么。
问: C 语言为什么提供 ++ 和 -- 运算符?它们是比其他的自增、自减方法执行得快,还是仅仅更便捷? (p.42 )
对于现代编译器而言,使用 ++ 和 -- 不会使编译后的程序变得更短小或更快,继续普及这些运算符主要是由于它们的简洁和便利。
问:++和--是否可以处理float型变量?
答:可以。自增和自减运算也可以用于浮点数,但实际应用中极少采用自增和自减运算符处理float型变量。
*问:在使用后缀形式的++或--时,何时执行自增或自减操作?(p.42)
答:这是一个非常好的问题,也是一个非常难回答的问题。 C语言标准引入了“顺序点”的概念,并且指出“应该在前一个顺序点和下一个顺序点之间对存储的操作数的值进行更新”。在C语言中有多种不同类型的顺序点,表达式语句的末尾是其中一种。在表达式语句的末尾,该语句中的所有自增和自减操作都必须执行完毕,否则不能执行下一条语句。
在后面章节中会遇到的一些运算符(逻辑与 、逻辑 或 、条件和逗号)对顺序点也有影响。函数调用也是如此:在函数调用执行之前,所有的实际参数必须全部计算出来。如果实际参数恰巧是含有++ 或 -- 运算符的表达式,那么必须在调用前进行自增或自减操作。
问:丢掉表达式语句的值意味着什么?(p.45)
答:根据定义,一个表达式表示一个值。例如,如果 i 的值为 5 ,那么计算 i + 1 产生的值为 6 。在末尾添加分号,把i+1 变成语句:
i + 1;
执行这条语句时,我们计算出了i + 1的值;但是我们没有保存此值(也没有以某种方式使用这个值),因此这个值就丢失了。
问:但是类似i = 1;这样的语句会如何呢?我没发现有什么东西被丢掉了。
答:不要忘记在 C 语言中 = 是一种运算符,它可以像其他任何运算符一样产生值。赋值语句
i = 1;
把1赋值给i。整个表达式的值是1,这个值被丢掉了。编写语句的首要目的是改变i的值,因此丢掉表达式的值不算什么大的损失。
最后的最后,创作不易,希望读者三连支持💖
赠人玫瑰,手有余香💖