2.7 类型转换
类型转换(当一个运算符的几个操作数类型不同时,就需要通过一些规则把它们转换为某种共同的类型)一般来说,自动转换是指把“比较窄的”操作数转换为“比较宽的”操作数,并且不丢失信息的转换。
由于char类型就是较小的整型,因此在算术表达式中可以自由使用char类型的变量。为了保证程序的可移植性,如果要在char类型的变量中存储非字符数据,最好指定signed或unsigned限定符。
C语言中,很多情况下会进行隐式的算术类型转换。一般来说,如果二元运算符(具有两个操作数的运算符称为二元运算符,比如+或*)的两个操作数具有不同的类型,那么在进行运算之前先要把“较低”的类型提升为“较高”的类型,运算的结果为较高的类型。
注意,表达式中float类型的操作数不会自动转换为double类型,这一点与最初的定义有所不同。一般来说,数学函数(如标准头文件math.h中定义的函数)使用双精度类型的变量。使用float类型主要是为了在使用较大的数组时节省存储空间,有时也为了节省机器执行时间(双精度算术运算特别费时)。
赋值时也要进行类型转换。赋值运算符右边的值需要转换为左边变量的类型,左边变量的类型即赋值表达式结果的类型。无论是否进行符号扩展,字符型变量都将被转换为整型变量。当把较长的整数转换为较短的整数或char类型时,超出的高位部分将被丢弃。
由于函数调用的参数是表达式,所以在把参数传递给函数时也可能进行类型转换。在没有函数原型的情况下,char与short类型都将被转换为int类型,float类型将被转换为double 类型。因此,即使调用函数的参数为char 或float 类型,我们也把函数参数声明为int或double类型。
在任何表达式中都可以使用一个称为强制类型转换的一元运算符强制进行显式类型转换。在下列语句中,表达式将按照上述转换规则被转换为类型名指定的类型:
(类型名) 表达式
2.8自增运算符和自减运算符
++与–这两个运算符特殊的地方主要表现在:它们既可以用作前缀运算符(用在变量前面,如++n)。也可以用作后缀运算符(用在变量后面,如n++)。在这两种情况下,其效果都是将变量n的值加1。但是,它们之间有一点不同。表达式++n先将n的值递增1,然后再使用变量n 的值,而表达式n++则是先使用变量n 的值,然后再将n 的值递增1。
2.9 按位运算符
C语言提供了6个位操作运算符。这些运算符只能作用于整型操作数,即只能作用于带符
号或无符号char、short、int、long类型:
& 按位与(AND)(经常用于屏蔽某些二进制位)
| 按位或(OR)(常用于将某些二进制位置为1)
^ 按位异或(XOR)(与每一位都是1的数进行异或,相当于原来的数不变;与每一位都是0的数进行异或,相当于对原来的数进行求反操作)
<< 左移
“>> ” 右移
~ 按位求反(一元运算符)
移位运算符<<与>>分别用于将运算的左操作数左移与右移,移动的位数则由右操作数指定(右操作数的值必须是非负值)。因此,表达式x << 2 将把x 的值左移2 位,右边空出的2 位用0 填补,该表达式等价于对左操作数乘以4。在对unsigned类型的无符号值进行右移位时,左边空出的部分将用0 填补;当对signed 类型的带符号值进行右移时,某些机器将对左边空出的部分用符号位填补(即“算术移位”),而另一些机器则对左边空出的部分用0填补(即“逻辑移位”)。
2.10赋值运算符
在赋值表达式中,如果表达式左边的变量重复出现在表达式的右边,如:i = i+2则可以将这种表达式缩写为下列形式:
i += 2其中的运算符+=称为赋值运算符。大多数二元运算符(即有左、右两个操作数的运算符,比如+)都有一个相应的赋值运算符op=,其中,op可以是下面这些运算符之一:
+ - * / % << >> & ^ |
赋值语句具有值,且可以用在表达式中。下面是最常见的一个例子:
while ((c = getchar()) !=EOF)
2.11 条件表达式
条件表达式(使用三元运算符“? :”)提供了另外一种方法编写这段程序及类似的代码段,在表达式expr1 ? expr2 : expr3中,首先计算expr1,如果其值不等于0(为真),则计算expr2 的值,并以该值作为条件表达式的值,否则计算expr3 的值,并以该值作为条件表达式的值。expr2 与expr3 中只能有一个表达式被计算。
2.12 运算符优先级和求值顺序
下面显示出了C语言中所有的运算符,其中同一行中的各运算符具有相同的优先级,各行间从上往下优先级逐行降低。
() [] -> . 从左至右
! ~ ++ – + - * (type) sizeof 从右至左
* / % 从左至右
+ - 从左至右
<< >> 从左至右
< <= > >= 从左至右
== != 从左至右
& 从左至右
^ 从左至右
| 从左至右
&& 从左至右
|| 从左至右
?: 从左至右
= += -= *= /= %= &= ^= |= <<= >>= 从右至左
, 从右至左
注:一元运算符+、-、&与比相应的二元运算符+、-、&与的优先级高。
C语言没有指定同一运算符中多个操作数的计算顺序(&&、||、?:和,运算符除外)。例如,在形如x = f() + g();的语句中,f()可以在g()之前计算,也可以在g()之后计算。因此,如果函数f 或g 改变了另一个函数所使用的变量,那么x 的结果可能会依赖于这两个函数的计算顺序。为了保证特定的计算顺序,可以把中间结果保存在临时变量中。
类似地,C语言也没有指定函数各参数的求值顺序。因此,下列语句
printf(“%d %d\n”, ++n, power(2, n)); /* 错*/
在不同的编译器中可能会产生不同的结果,这取决于n的自增运算在power调用之前还是之后执行。解决的办法是把该语句改写成下列形式:
++n;
printf(“%d %d\n”, n, power(2, n));
函数调用、嵌套赋值语句、自增与自减运算符都有可能产生“副作用”——在对表达式求值的同时,修改了某些变量的值。在有副作用影响的表达式中,其执行结果同表达式中的变量被修改的顺序之间存在着微妙的依赖关系。
在任何一种编程语言中,如果代码的执行结果与求值顺序相关,则都是不好的程序设计风格。很自然,有必要了解哪些问题需要避免,但是,如果不知道这些问题在各种机器上是如何解决的,就最好不要尝试运用某种特殊的实现方式。