5.4.1 自动类型转换 1、非赋值运算的类型转换
数据类型的自动转换需遵循的规则见图5.1所示。为保证运算的精度不降低,采用以下方法。
高 double float
unsigned long long
unsigned unsigned short
低 int char ,short
图5.1 数据类型自动转换规则
(1)水平方向的转换:所有的char型和short型自动转换成int型,所有的unsigned short型自动转换成unsigned型,所有的long型自动地转换成unsigned long型,所有的float型自动地转换成double型。
(2)垂直方向的转换:经过水平方向的转换,如果参加运算的数据类型仍然不相同,再将这些数据自动转换成其中级别最高的类型。
例如,设变量ac的类型是char,变量bi的类型是int,变量d的类型是double,求解表达式ac + bi – d 。运算次序是:先计算ac + bi ,将ac转换为int型后求和,结果是int型;再将ac + bi 的和转换为double型,再与d相减,结果是双精度double型。 2、赋值运算的类型转换
赋值运算时,将赋值号右侧表达式的类型自动转换成赋值号左侧变量的类型。
例如:设变量x的类型是double,计算表达式 x = 1 。运算时,先将int型常量1转换成double型常量1.0,然后赋值给 x ,结果是double型。又如,设变量a的类型是short,变量b的类型是char,变量c的类型是long,求解表达式c = a + b 。运算次序是:先计算a + b ,将a 和b转换成int型后求和,结果是int型;再将a + b的和转换成变量c的类型long,然后赋值给c,结果是long型。
利用这条规则时,如果赋值号右侧表达式的类型比赋值号左侧变量的类型级别高,运算精度会降低。
例如:,设变量ai的类型是int,计算表达式ai = 2.56 。运算时,先将double型常量2.56转换成int型常量2,然后赋值给ai,结果是int型。
在赋值运算时,赋值号两侧数据的类型最好相同,至少右侧数据的类型比左侧数据的类型级别低,或者右侧数据的值在左侧变量的取值范围内,否则会导致运算精度降低,甚至出现意想不到的结果。 5.4.2 强制类型转换
使用强制类型转换运算符,可以将一个表达式转换成给定的类型。其一般形式是: ( 类型名 ) 表达式;
例如:设i是int型变量,( double ) i将i的值转换成double型,而( int )3.8将3.8转换成int型,得到3。
11
无论是自动类型转换,还是强制类型转换,都是为了本次运算的需要,对数据的类型进行临时转换,并没有改善数据的定义。例如,表达式 ( double ) i 的类型是double,而i的类型并没有改变,仍然是int 。
强制类型转换是运算而不是函数,故 ( int ) x 不能写成int ( x )。
强制类型转换运算的优先级较高,与自增运算符 ++ 相同,它的结合性是从右到左。例如:( int ) 3.8 + 1.3等价于 (( int ) 3.8 ) + 1.3,它的值是4.3,而 ( int ) ( 3.8+1.3 ) 的值是5。
5.5 表达式
5.5.1 算术表达式 1、算术运算符
算术运算符分为单目运算符和双目运算符两类,见表6.5所示。单目运算符只需要一个操作数,而双目运算符需要两个操作数。
表 5 . 5 算术运算符
目 数 单 目 双 目
运算符 + + - - + - + - * / % 名 称 自增 自减 正值 负值 加 减 乘 除 模(求余)
2、自增运算符和自减运算符
自增运算符 ++ 和自减运算符 - - 有两个功能。 (1)使变量的值增1或减1
例如:设n是一个整型变量并已赋值,则: ++ n 和 n ++ 都相当于 n = n + 1 ; - - n 和 n - - 都相当于 n = n – 1 ; (2)取变量的值作为表达式的值。
例如:计算表达式 ++ n 和 n ++ 的值,则:
++ n 的运算顺序是:先执行 n = n + 1,再将n的值作为表达式 ++ n 的值。 n ++ 的运算顺序是:先将n的值作为表达式n + + 的值,再执行 n = n + 1 ;
自增运算符和自减运算符的运算对象只能是变量,不能是常量或表达式。如:
3 ++ 或 ++ ( i + j ) 都是非法的表达式。
3、算术运算符的优先级和结合性
在算术四则运算中,遵循“先乘除后加减”的运算规则。同样,在C语言中,计算表达式的值也需要按运算符的优先级从高到低顺序计算。例如,表达式 a + b * c相当于 a + ( b * c ) ,这是因为操作数b的两侧有运算符 + 和 * ,而 * 的优先级高于 + 。 如果操作数两侧运算符的优先级相同,则按结合性(结合方向)决定计算顺序,若结合方向为“从左到右”,则操作数先与左面的运算符结合;若结合方向为“从右到左”,则操作数先与右面的运算符结合。
C语言中算术运算符的优先级和结合性见表5.6所示,同一行实线上的运算符优先级相同,不同行的运算符的优先级按从高到低的次序排列,可以用圆括号来改变运算符的执行次序。
12
表5.6 部分运算符的优先级和结合性
运算符种类 运 算 符 结 合 方 向 优 先 级 逻辑运算符 ! 高 ++ - - + - * ( 单目 ) 从右向左(右结合) 算术运算符 * / % ( 双目 ) + - ( 双目 ) < <= > >= 关系运算符 == ! = 从左向右(左结合) && 逻辑运算符 || 条件表达式 ? : 赋值运算符 = += -= *= /= %= 从右向左(右结合) 逗号运算符 . 从左向右(左结合) 低 例如,表达式-5 + 3 % 2等价于(-5)+(3 % 2 ),结果为-4;表达式3 * 5 % 3等价于 (3*5)% 3,结果为0,这是因为5两侧运算符 * 和 % 的优先级相同,按从左到右的结合方向,5先与 * 结合。而表达式 – i ++ 等价于 – ( i ++ ),这是因为i 两侧运算符 - 和 ++ 的优先级相同,按从右到左的结合方向,i 先与 ++ 结合。 4、算术表达式
用算术运算符将运算对象连接起来的符合C语言语法规则的式子称为算术表达式,运算对象包括常量、变量和函数等表达式。算术表达式的值和类型由参加运算的运算符和运算对象决定。 5、副作用的说明
(1)C语言中,自增和自减是两个很特殊的运算符,相应的运算会得到两个结果。例如:设n = 3,表达式 n ++ 经过运算之后,其值为3,同时变量n的值增1为4。即在求解表达式时,变量的值改变了,称这种变化为副作用。在编程时,副作用的影响往往会使得运算的结果与预期的值不相符。
要慎用自增、自减运算,尤其不要用它们构造复杂的表达式。
(2)C语言中,根据运算符的优先级和结合性决定表达式的计算顺序,但对运算符两侧操作数的求值顺序并未做出明确的规定,允许编译系统采取不同的处理方式。例如,计算表达式f( ) + g( ) 时,可以先求f( )再求g( ),也可以相反。如果求值顺序的不同影响了表达式的结果,即相同的源程序在不同的编译系统下运行,结果可能不同,就给程序的移植造成了困难。所以,在实际应用中应该避免这种情况。 5.5.2 赋值表达式 1、赋值运算服
C语言将赋值作为一种运算,赋值运算符 ” =” 的左边必须是一个变量,作用是把一个表达式的值赋给一个变量。赋值运算符的优先级比算术运算符低,它的结合方向是从右向左,见表5.6所示。例如,表达式x = ( 3 * 4 ) 等价于x = 3 * 4。表达式x = y = 3等价于x = ( y = 3 )。 2、赋值表达式
13
用赋值运算符将一个变量和一个表达式连接起来的式子称为赋值表达式。赋值表达式的简单形式是: 变量 = 表达式;
赋值表达式的运算过程是:
(1)计算赋值运算符右侧表达式的值。
(2)将赋值运算符右侧表达式的值赋给赋值运算符左侧的变量。
(3)将赋值运算符左侧的变量的值作为赋值表达式的值。
在赋值运算时,如果赋值运算符两侧的数据类型不同,在上述运算过程的第(2)步,系统首先将赋值运算符右侧表达式的类型自动转换成赋值运算符左侧变量的类型,再给变量赋值,并将变量的类型作为赋值表达式的类型。
例如,设n是整型变量,计算表达式 n = 3.14 * 2的值,首先计算3.14 * 2得到6.28,将6.28转换成整型值6后赋给n,该赋值表达式的值是6,类型是整型。
又如,设x是双精度浮点型变量,计算表达式x = 10 / 4的值,首先计算10 / 4得到2,将2转换成双精度浮点型值2.0后赋给x,该赋值表达式的值是2.0,类型是双精度浮点型。
在赋值表达式中,赋值运算符右侧的表达式也可以是一个赋值表达式。如:
x = ( y = 3 ); 求解时,先计算表达式y = 3,再将该表达式的值3赋给x,结果使得x和y都赋值为3;相当于计算x = 3和y = 3两个赋值表达式。
由于赋值运算符的结合性是从右到左,因此,x = ( y = 3 )等价于x = y = 3,即多个简单赋值运算可以组合为一个连赋值的形式。 3、复合赋值运算符
赋值运算符分为简单赋值运算符和复合赋值运算符。简单赋值运算符就是” = “,复合赋值运算符又分为复合算术赋值运算符和复合位赋值运算符,在” = “前加上算术运算符就构成了复合算术赋值运算符,见表5.7所示。复合位赋值运算符在5.5.7节介绍。
所以,赋值表达式的一般形式是: 变量 赋值运算符 表达式; 表5.7 复合算术赋值运算符
运 算 符 名 称 等 价 关 系 ( exp 指表达式 ) + = 加赋值 x + = exp等价于 x = x + ( exp ) - = 减赋值 x - = exp等价于 x = x – ( exp ) * = 乘赋值 x * = exp等价于 x = x * ( exp ) / = 除赋值 x / = exp 等价于 x = x / ( exp ) % = 求余赋值 x % = exp等价于 x = x % ( exp ) 注:exp指数表达式
x * = y – 3等价于x = x * ( y – 3 ),而不是x = x * y – 3
5.5.3 关系表达式 1、关系运算符
关系运算符(见表5.8所示)是双目运算符,用于对两个操作数进行比较。 表 5.8 关系运算符
运 算 符 < < = > > = = = ! =
名 称 小于 小于或等于 大于 大于或等于 等于 不等于 优先级 高 低
14
关系运算符的优先级低于算术运算符,高于赋值运算符和逗号运算符,它的结合方向是从左向右(见表5.6)。例如,设a、b、c是整型变量,ch是字符型变量,则: (1)a > b = = c等价于 ( a > b ) = = c (2)d = a > b等价于 d = ( a > b )
(3)ch > ? a ? + 1等价于ch > ( ? a ? + 1 ) (4)d = a + b > c等价于 d = ( ( a + b ) > c ) (5)3 < = x <= 5等价于( 3 < = x ) < = 5 (6)b – 1 = = a ! = c等价于(( b – 1 ) = = a ) ! = c 2、关系表达式
用关系运算符将两个表达式连接起来的式子,称关系表达式。关系表达式的值反映了关系运算(比较)的结果,它是一个逻辑量,取值“真”或“假”。由于C语言没有逻辑型数据,就用整数1代表“真”,0代表“假”。这样,关系表达式的值就是1或0,它的类型是整型。
例5.7 关系表达式的运用 #include
{ char ch = 'w'; // w 的ASCII码十进制为119 int a = 2,b = 3,c = 1,d,x = 10;
printf(\//a不大于b为假得0 ,而0是不等于c(c=1)的,所以结果是假0 printf(\//a不大于b得假0,而0 赋给d,d是0,结果是0
printf(\// 'a'是97,加1为98。ch为w值是119。119大于98,结果是真1 printf(\// a+b是5,5是大于c的,得真1,真1赋给d,结果是真1 printf(\// b-1得2,2等于a得真1,1应得c(1),但这里是!=故得假0 printf(\// 3小于10,得真1,而1小于5得真1,所以结果是真1。 return 0;
}
运行结果:0 0 1 1 0 1
程序输出了6个表达式的值,其中有两个是赋值表达式,请读者根据运算符的优先级做出判断。
关系表达式b – 1 = = a ! = c等价于关系表达式(( b – 1 ) = = a ) ! = 1,当a = 2,b = 3时,( b – 1 ) = = a的值是1,再计算1!= 1,得到0。
关系表达式3 <= x <= 5等价于关系表达式( 3 <= x ) <= 5,当 x = 10时,3 <= x的值是1,再计算1 <= 5,得到1。其实,无论x取什么值,关系表达式3 <= x的值不是1就是0,都是小于5,即3 <= x <= 5的值恒为1。由此看出,关系表达式3 <= x <= 5无法正确表示代数式3 <= x <= 5,如x = 4,3小于4,它得真是1,而1小于5得真,这不是代数式的表示,只是一种关系的运算。 5.5.4 逻辑表达式 1、逻辑运算符
15