第四章 表达式
表达式是显示如何计算值的公式。最简单的表达式是变量和常量。变量表示程序运行时计算出的值;常量表示不变的值。
运算符是构建表达式的基本工具。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语言没有定义子表达式的求值顺序(除了含有逻辑与运算符及逻辑或运算符、条件运算符以及逗号运算符的子表达式)。