跳转至

第四章 表达式

表达式是显示如何计算值的公式。最简单的表达式是变量和常量。变量表示程序运行时计算出的值;常量表示不变的值。

运算符是构建表达式的基本工具。C语言提供了基本的运算符:

  • 算术运算符。
  • 关系运算符。
  • 逻辑运算符。

算术运算符

算术运算符有:

一元运算符 二元运算符
+ - + - * / %

二元运算符要求有两个操作数,而一元运算符只要有一个操作数。

一元运算符+无任何操作,它主要是为了强调某数值常量是正的。

%被称之为 mod (求模)或 rem (取余)。 i % j 的数值是i除以j后的余数。

除了%,二元运算符既允许操作数是整数也允许操作数是浮点数,或者允许两者的混合。当把 int 型操作数和 float 型操作数混合在一起时,运算结果是 float 型的。

运算符/和%需要特别注意:

  • /可能产生意外的结果。当两个操作数都是整数时,运算符/通过丢掉分数部分的方法截取结果,因此1/2的结果是0。
  • %要求整数操作数;如果两个操作数中有一个不是整数,那么程序将无法通过编译。
  • 当/和%用于负数时,其结果与具体实现有关。如果操作数中有一个为负数,那么除法的结果既可以向上取整也可以向下取整。

由实现定义

术语由实现定义( implementation-defined )出现频率很高,意思是指软件在特定的平台上编译、链接和执行。根据实现的不同,程序的行为可能会稍有差异。 C语言的目的之一是达到高效率,这经常意味着要与硬件行为相匹配。当-9除以7时,一些机器可能产生的结果是-1,而另一些机器的结果为-2,C标准简单地反映了这一现实。 最好避免编写与实现定义的行为相关的程序。

运算符的优先级和结合性

C语言允许在所有表达式中用圆括号进行分组。但如果不使用圆括号,就采用运算符优先级( operator precedence )的规则来解决问题。算术运算符有下列相对优先级:

  • 最高优先级:+ -(一元运算符)
  • 中级优先级:* / %
  • 最低优先级:+ -(二元运算符)

例如:

i + j * k;    // 等价于 i + (j * k)

当一个表达式包含两个以上相同优先级的运算符时,单独的运算符优先级的规则是不够的。这种情况下,运算符的结合性( associativity )开始发挥作用。如果运算符是从左向右结合的,那么称这种运算符是左结合的( left associative )。二元算术运算符都是左结合的,所以:

i - j - k;    // 等价于 (i - j) - k

如果运算符是从右向左结合的,那么称为右结合的( right associative )。一元运算符都是右结合的。

赋值运算符

一旦计算出表达式的值就常常需要把这个值存储在变量中,以便后面使用。C语言的=运算符(assignment)可以用于此目的。

简单赋值

表达式v = e的赋值效果是求出表达式e的值,并把此值复制给v。e可以是常量、变量或较为复杂的表达式:

i = 5;
j = i;
k = 10 * i + j;

如果v和e的类型不同,那么赋值运算发生时会把e的值转化为v的类型:

int i;
i = 72.99; /* i is now 72 */

赋值操作产生结果,赋值表达式v=e的值就是赋值运算后v的值。因此,表达式i = 72.99的值是72。

副作用

大多数C语言运算符不会改变操作数的值,但是也有一些会改变。由于这类运算符所做的不再仅仅是计算出值,所以称它们有副作用( side effect )。简单的赋值运算符就是一个有副作用的运算符,它改变了运算符左边的操作数。表达式i=0产生的结果为0,作为副作用,把0赋值给i。

运算符=是右结合的。所以:

i = j = k = 0;
i = (j = (k = 0)); // 等价

左值

大多数C语言运算符允许它们的操作数是变量、常量或者包含其他运算符的表达式。然而,赋值运算符要求它左边的操作数必须是左值( lvalue )。左值表示存储在计算机内存中的对象,而不是常量或计算结果。变量是左值,而诸如10或2*i这样的表达式则不是左值。

复合赋值

利用变量原有值计算出新值并重新赋值给这个变量在C语言程序中是非常普遍的。例如:

i = i + 2;

C语言的复合赋值运算符( compound assignment operator )允许缩短这种语句和其他类似的语句。

i += 2;

+=运算符把右侧操作数的值加上左侧的变量,并把结果赋值给左侧的变量。还有另外的9种复合赋值运算符,包括:

-= *= /= %=

自增运算符和自减运算符

++表示操作数加1,--表示操作数减1。++和--既可以作为前缀( prefix )运算符,也可以作为后缀( postfix )运算符使用。

++和--也有副作用,它们会改变操作数的值。计算表达式++i的结果是i+1,副作用是自增i。计算表达式i++的结果是i,副作用是自增i。

这个自增操作一定会在下一条语句执行前完成。

表达式求值

上述总结的运算符在下表列出了其优先级、结合性。更多讨论见书本p39。

优先级 类型名称 符号 结合性
1 后缀自增、自减 ++ -- 左结合
2 前缀自增、自减,一元正负 ++ -- + - 右结合
3 乘法类 * / % 左结合
4 加法类 + - 左结合
5 赋值 = *= /= %= += -= 右结合

子表达式的求值顺序

C语言没有定义子表达式的求值顺序(除了含有逻辑与运算符及逻辑或运算符、条件运算符以及逗号运算符的子表达式)。