8.C Primer Plus 5.4表达式与语句与5.5类型转换
5.4表达式与语句
C的基本程序步骤由语句组成,而大多数语句都由表达式构成。
5.4.1表达式
表达式(expression)由运算符和运算对象组成(前面介绍过,运算对 象是运算符操作的对象)。
最简单的表达式是一个单独的运算对象,以此为基础可以建立复杂的表达式。
下列均为表达式:

运算对象可以是常量、变量或二者的组合。一些表达式由子 表达式(subexpression)组成(子表达式即较小的表达式)。在上例中,c/d为a*(b+c/d)/20的子表达式。
C表达式的最重要的特性:每个表达式都有一个值。
要获得这个值,必须根据运算符优先级规定的顺序来执行操作。
但是,有赋值运算符(=)的表达式的值是这些表达式的值与赋值运算符左侧变量的值相同。
而例如q>3这样的含关系运算符的表达式的值则要么是0要么是1,如果该关系表达式成立则为1,若不成立则为0.
下列为一些表达式和其值:

上例中最后一个表达式看起来很奇怪,但是在C语言中完全正确(但是不建议使用),因为其为两个子表达式的和,而每个子表达式都有其值。
5.4.2语句
语句是C程序的基本构建块。一条语句相当于一条完整的 计算机指令。在C语言中,大部分的语句都以分号结尾。
因此,下列示例:

仅为一条表达式而非语句。
而下列示例:

则是一条语句。
最简单的语句即空语句: ;//空语句
C把末尾加上一个分号的表达式都看作是一条语句(即,表达式语句)。
例如:


这些语句在程序中什么也不做,不算是真正有用的语句。
真正有用的语句应用于改变值或者调用函数,例如:

虽然一条语句(或者至少是一条有用的语句)相当于一条完整的指令
但并不是所有完整的指令都是语句,例如下例:

上例中y=5也是一条完整的指令,但却不是一条语句,而是语句当中的一部分。
由此分号用于区别于识别这种一条完整的指令并非一条语句的情况(即简单语句)。
下列示例程序演示一些常见的语句:

注意:声明创建了名称和类型,并为其分配内存位置。注意,声明不是表达式语句。也就是说,如果删除声明后面的分号,剩下的部分不是一个表达式,也没有值,例如:int import /*不是表达式,没有值 */
C语言中常见的语句类型:
1.赋值表达式语句:它为变量分配一个值。赋值表达式语 句的结构是,一个变量名,后面是一个赋值运算符,再跟着一个表达式,最后以分号结尾。
注意:在while循环中有一个赋值表达式语句。赋值表达式 语句是表达式语句的一个示例。
2.函数表达式语句:其会引起函数调用。
上例中,调用printf()函数打印结 果。while语句有3个不同的部分(见图5.6)。首先是关键字while;然后, 圆括号中是待测试的条件;最后如果测试条件为真,则执行while循环体中 的语句。该例的while循环中只有一条语句。可以是本例那样的一条语句, 不需要用花括号括起来,也可以像其他例子中那样包含多条语句。多条语句 需要用花括号括起来。这种语句是复合语句。

while语句是一种迭代语句,有时也被称为结构化语句,因为它的结构 比简单的赋值表达式语句复杂。
副作用和序列点
副作用
副作用是对数据对象或文件的修改。
例如:states = 50;
其副作用为将变量的值设置为50,看起来这似乎是该语句的主要目的,而其实在C语言中,该语句的主要目的是给表达式求值。
给出表达式4 + 6,C会 对其求值得10;给出表达式states = 50,C会对其求值得50。对该表达式求值 的副作用是把变量states的值改为50。
与赋值运算符一样,递增和递减运算符也有副作用,使用它们的主要目的就是使用它们的副作用。而调用 printf()函数时,它显示的信息其实是副作用(printf()的返 回值是待显示字符的个数)。
序列点
序列点(sequence point)是程序执行的点,在该点上,所有的副作用都在进入下一步之前发生。
在C语言中,语句中的分号标记了一个序列点。
也就是说在一个语句中,赋值运算符、递增运算符和递减运算符对运算对象做 的改变必须在程序执行下一条语句之前完成。
注意:一些运算符也有序列点;任何一个完整的表达式的结束也是一个序列点。
所谓完整表达式(full expression),就是指这个表 达式不是另一个更大表达式的子表达式。例如,表达式语句中的表达式和 while循环中的作为测试条件的表达式,都是完整表达式。
序列点的作用:序列点有助于分析后缀递增何时发生。

在上例中,我们可能会以为“先使用值,再递增它”的意思是,在 printf()语句中先使用guests,再递增它。
但是,表达式guests++ < 10是一个完 整的表达式,因为它是while循环的测试条件,所以该表达式的结束就是一 个序列点。因此,C 保证了在程序转至执行 printf()之前发生副作用(即,递 增guests)。同时,使用后缀形式保证了guests在完成与10的比较后才进行递增。

上例中,表达式4 + x++不是一个完整的表达式,所以C无法保证x在子表达式4 + x++求值后立即递增x。
这里,完整表达式是整个赋值表达式语句,分号标记了序列点。
所以,C 保证程序在执行下一条语句之前递增x两次。
C并未指明是在对子表达式求值以后递增x,还是对所有表达式求值后再递增x。因此要尽量避免编写类似的语句。
5.4.3复合语句(块)
复合语句是用花括号括起来的一条或多条语句, 复合语句也称为块。
shoes2.c程序使用块让while语句包含多条语句,如下示例:
程序段1:

程序段2:

在程序段1中,while循环中只有一条赋值表达式语句。没有花括号,while语 句从while这行运行至下一个分号。循环结束后,printf()函数只会被调用一 次。
在程序段2中,花括号确保两条语句都是while循环的一部分,每执行一次循 环就调用一次printf()函数。根据while语句的结构,整个复合语句被视为一 条语句。
带复合语句的while循环:

提示 风格提示
注意循环体中的缩进,缩进对编译 器不起作用,编译器通过花括号和while循环的结构来识别和解释指令。这里,上面两个示例中的缩进是为了让读者一眼就可以看出程序是如何组织的。
程序段2中,块或复合语句放置花括号的位置是一种常见的风格。
另一种常见的风格是:

这种风格突出了块附属于while循环,而前一种风格则强调语句形成一 个块。对编译器而言,这两种风格完全相同。
总之,使用缩进可以为读者指明程序的结构。
总结 表达式与语句
表达式:
表达式由运算符和运算对象组成。最简单的表达式是不带运算符的一个 常量或变量(如,22 或beebop)
更复杂的示例如:55+22和vap = 2*(vip+(vup = 4))等。
语句:
到目前为止,读者接触到的语句可分为简单语句和复合语句。简单语句以一个分号结尾。如下所示:

复合语句(或块)由花括号括起来的一条或多条语句组成。如下while语句所示:

5.5类型转换
在C语言中,通常,在语句和表达式中应使用类型相同的变量和常量。
当使用混合类型时,C 不会像 Pascal那样停在那里死掉,而是采用一套规则进行自动类型转换。
为避免在无意间混合使 用类型的情况下(许多UNIX系统都使用lint程序检查类型“冲突”。如果选择 更高错误级别,许多非UNIX C编译器也可能报告类型问题)。我们最好先了解一些基本的类型转换规则:
1.当类型转换出现在表达式时,无论是unsigned还是signed的char和short 都会被自动转换成int,如有必要会被转换成unsigned int(如果short与int的大 小相同,unsigned short就比int大。这种情况下,unsigned short会被转换成 unsigned int)。例如:在K&R时的C语言,float类型会被自动转换为double类型(目前现在的C语言不是这样的)。由于都是从较小类型转换为较大类型,所以这些转换被称为升级。
2.涉及两种类型的运算,两个值会被分别转换成两种类型的更高级别。
3.类型的级别从高至低依次是long double、double、float、unsignedlong long、long long、unsigned long、long、unsigned int、int。例外的情况是,当 long 和 int 的大小相同时,unsigned int比long的级别高。之所以short和char类 型没有列出,是因为它们已经被升级到int或unsigned int。
4.在赋值表达式语句中,计算的最终结果会被转换成被赋值变量的类 型。这个过程可能导致类型升级或降级。所谓降级,是指把一 种类型转换成更低级别的类型。
5.当作为函数参数传递时,char和short被转换成int,float被转换成 double。
注意:类型升级通常都不会有什么问题,但是类型降级会导致真正的麻烦。原因很简单:较低类型可能放不下整个数字。例如,一个8位的char类型变量 储存整数101没问题,但是存不下22334。
当待转换的类型和目标类型不匹配时,解决方法取决于转换涉及的类型。此时,应遵循的规则如下:
1.目标类型是无符号整型,且待赋的值是整数时,额外的位将被忽略。 例如,如果目标类型是 8 位unsigned char,待赋的值是原始值求模256。
2.如果目标类型是一个有符号整型,且待赋的值是整数,结果因编译器的实现而异。
3.如果目标类型是一个整型,且待赋的值是浮点数,该行为是未定义的。
当浮点数值转换为整数类型时,浮点数类型被降级为整数类型,原来浮点数的浮点值会被截断。例如,23.12和23.99都会被截断为23,-23.5 会被截断为-23。如下示例演示上述规则:

输出如下:

在我们的系统中,char是8位,int是32位。
程序分析:


5.5.1强制类型转换运算符
通常,应该避免自动类型转换,尤其是类型降级。
然而, 有时需要进行精确的类型转换,或者在程序中表明类型转换的意图。这种情 况下要用到强制类型转换(cast),即在某个量的前面放置用圆括号括起来 的类型名,该类型名即是希望转换成的目标类型。圆括号和它括起来的类型 名构成了强制类型转换运算符(cast operator)。其通用形式为:(type)。
用实际需要的类型(如,long)替换type即可。
下示例演示两次int强制类型转换:
mice = 1.6+1.7;
mice = 1.6(int)+1.7(int);
第1 行使用自动类型转换。首先,1.6和1.7相加得3.3。然后,为了匹配 int 类型的变量,3.3被类型转换截断为整数3。
第2行,1.6和1.7在相加之前都 被转换成整数(1),所以把1+1的和赋给变量mice。
本质上,两种类型转换 都好不到哪里去,要考虑程序的具体情况再做取舍。
一般而言,不应该混合使用类型(因此有些语言直接不允许这样做), 但是偶尔这样做也是有用的。C语言会避免给程序员设置障碍,但程序员必须承担使用的责任和风险。
总结C的一些运算符
下列为我们学过的一些运算符:



浙公网安备 33010602011771号