复合语句块
复合语句(compound statement) 是用花括号括起来的一条或多条语句,复合语句也称为块(block) 。
shoes2.c程序使用块让while语句包含多条语句。 比较两个程序段:
/* 程序段 1 */
index = 0;
while (index++ < 10)
sam = 10 * index + 2;
printf("sam = %d\n", sam);
/* 程序段 2 */
index = 0;
while (index++ < 10)
{
sam = 10 * index + 2;
printf("sam = %d\n", sam);
}
程序段1, while循环中只有一条赋值表达式语句。 没有花括号, while语句从while这行运行至下一个分号。 循环结束后, printf()函数只会被调用一次。
程序段2, 花括号确保两条语句都是while循环的一部分, 每执行一次循环就调用一次printf()函数。 根据while语句的结构, 整个复合语句被视为一条语句
前面的两个while程序段, 注意循环体中的缩进。 缩进对编译器不起作用, 编译器通过花括号和while循环的结构来识别和解释指令。
程序段2中, 块或复合语句放置花括号的位置是一种常见的风格。 另一种常用的风格是:
while (index++ < 10)
{
sam = 10*index + 2;
printf("sam = %d \n", sam);
}
这种风格突出了块附属于while循环, 而前一种风格则强调语句形成一个块。
使用缩进可以指明程序的结构。
总结 表达式和语句
表达式由运算符和运算对象组成。 最简单的表达式是不带运算符的一个常量或变量(如, 22 或beebop) 。
语句:到目前为止, 读者接触到的语句可分为简单语句和复合语句。 简单语句以一个分号结尾。
赋值表达式语句: toes = 12;
函数表达式语句: printf("%d\n", toes);
空语句: ; /* 什么也不做 */
复合语句(或块) 由花括号括起来的一条或多条语句组成。
类型转换
在语句和表达式中应使用类型相同的变量和常量。当类型转换出现在表达式时, 无论是unsigned还是signed的char和short都会被自动转换成int, 如有必要会被转换成unsigned int(如果short与int的大小相同, unsigned short就比int大。 unsigned short会被转换成unsigned int) 。 在K&R那时的C中, float会被自动转换成double(目前的C不是这样) 。 由于都是从较小类型转换为较大类型, 所以这些转换被称为升级(promotion)。
涉及两种类型的运算, 两个值会被分别转换成两种类型的更高级别。
类型的级别从高至低依次是long double、 double、 float、 unsignedlong long、 long long、 unsigned long、 long、 unsigned int、 int。当long 和 int 的大小相同时, unsigned int比long的级别高。
在赋值表达式语句中, 计算的最终结果会被转换成被赋值变量的类型。 这个过程可能导致类型升级或降级(demotion) 。 所谓降级, 是指把一种类型转换成更低级别的类型。
当作为函数参数传递时, char和short被转换成int, float被转换成double。
待赋值的值与目标类型不匹配时。
目标类型是无符号整型, 且待赋的值是整数时, 额外的位将被忽略。
如果目标类型是一个有符号整型, 且待赋的值是整数, 结果因实现而异。
如果目标类型是一个整型, 且待赋的值是浮点数, 该行为是未定义的。
当浮点类型被降级为整数类型时, 原来的浮点值会被截断。
第9行和第10行: 字符'C'被作为1字节的ASCII值储存在ch中。 整数变量i接受由'C'转换的整数, 即按4字节储存67。 最后, fl接受由67转换的浮点数67.00。
第11行和第14行: 字符变量'C'被转换成整数67, 然后加1。 计算结果是4字节整数68, 被截断成1字节储存在ch中。 根据%c转换说明打印时, 68被解释成'D'的ASCII码。
第12行和第14行: ch的值被转换成4字节的整数(68) , 然后2乘以ch。为了和fl相加, 乘积整数(136) 被转换成浮点数。 计算结果(203.00f) 被转换成int类型, 并储存在i中。
第13行和第14行: ch的值('D', 或68) 被转换成浮点数, 然后2乘以ch。 为了做加法, i的值(203) 被转换为浮点类型。 计算结果(339.00) 被储存在fl中。
第15行和第16行: 演示了类型降级的示例。 把ch设置为一个超出其类型范围的值, 忽略额外的位后, 最终ch的值是字符S的ASCII码。 或者, 更确切地说, ch的值是1107 % 265, 即83
第17行和第18行: 演示了另一个类型降级的示例。 把ch设置为一个浮点数, 发生截断后, ch的值是字符P的ASCII码。
强制类型转换运算符
强制类型转换(cast) , 即在某个量的前面放置用圆括号括起来的类型名, 该类型名即是希望转换成的目标类型。 圆括号和它括起来的类型名构成了强制类型转换运算符(cast operator)
(type)
用实际需要的类型(如, long) 替换type即可。
C的一些运算符
赋值运算符:= 将其右侧的值赋给左侧的变量
算术运算符:
+ 将其左侧的值与右侧的值相加
- 将其左侧的值减去右侧的值
- 作为一元运算符, 改变其右侧值的符号
* 将其左侧的值乘以右侧的值
/ 将其左侧的值除以右侧的值, 如果两数都是整数, 计算结果将被截断
% 当其左侧的值除以右侧的值时, 取其余数(只能应用于整数)
++ 对其右侧的值加1(前缀模式) , 或对其左侧的值加1(后缀模式)
-- 对其右侧的值减1(前缀模式) , 或对其左侧的值减1(后缀模式)
其他运算符:
sizeof 获得其右侧运算对象的大小(以字节为单位) , 运算对象可以是一个被圆括号括起来的类型说明符, 如sizeof(float), 或者是一个具体的变量名、 数组名等, 如sizeof foo
(类型名) 强制类型转换运算符将其右侧的值转换成圆括号中指定的类型, 如(float)9把整数9转换成浮点数9.0
带参数的函数
程序的函数头:
void pound(int n)
如果函数不接受任何参数, 函数头的圆括号中应该写上关键字 void。由于该函数接受一个 int 类型的参数, 所以圆括号中包含一个int类型变量n的声明。 参数名应遵循C语言的命名规则
声明参数就创建了被称为形式参数(formal argument或formal parameter, 简称形参) 的变量。 该例中, 形式参数是 int 类型的变量 n。 像pound(10)这样的函数调用会把 10 赋给 n。 在该程序中, 调用pound(times)就是把 times 的值(5) 赋给 n。 我们称函数调用传递的值为实际参数(actual argument或actual parameter) , 简称实参。 所以, 函数调用pound(10)把实际参数10传递给函数, 然后该函数把10赋给形式参数(变量n) 。 也就是说,main()中的变量times的值被拷贝给pound()中的新变量n。
实参和形参
argument和parameter经常可以互换使用, 但是C99标准规定了: 对于actual argument或actual parameter使用术语argument(译为实参) ;对于formal argument或formal parameter使用术语parameter(译为形参) 。形参是变量, 实参是函数调用提供的值, 实参被赋给相应的形参。
变量名是函数私有的, 即在函数中定义的函数名不会和别处的相同名称发生冲突。 如果在pound()中用times代替n, 那么这个times与main()中的times不同。
函数调用。 第1 个函数调用是pound(times), times的值5被赋给n。 因此, printf()函数打印了5个井号和1个换行符。 第2个函数调用是pound(ch)。 这里, ch是char类型, 被初始化为!字符, 在ASCII中ch的数值是33。 但是pound()函数的参数类型是int, 与char不匹配。 程序开头的函数原型在这里发挥了作用。 原型(prototype) 即是函数的声明, 描述了函数的返回值和参数。pound()函数的原型说明了两点:该函数没有返回值(函数名前面有void关键字) ;该函数有一个int类型的参数。
函数原型告诉编译器pound()需要一个int类型的参数。 相应地, 当编译器执行到pound(ch)表达式时, 会把参数ch自动转换成int类型。
在ANSI C之前, C使用的是函数声明, 而不是函数原型。 函数声明只指明了函数名和返回类型, 没有指明参数类型。
关键概念
C 通过运算符提供多种操作。 每个运算符的特性包括运算对象的数量、优先级和结合律。 当两个运算符共享一个运算对象时, 优先级和结合律决定了先进行哪项运算。 每个 C表达式都有一个值。
本章小结
一般而言, 运算符需要一个或多个运算对象才能完成运算生成一个值。 只需要一个运算对象的运算符(如负号和 sizeof) 称为一元运算符, 需要两个运算对象的运算符(如加法运算符和乘法运算符) 称为二元运算符。
表达式由运算符和运算对象组成。 在C语言中, 每个表达式都有一个值, 包括赋值表达式和比较表达式。 运算符优先级规则决定了表达式中各项的求值顺序。 当两个运算符共享一个运算对象时, 先进行优先级高的运算。如果运算符的优先级相等, 由结合律(从左往右或从右往左) 决定求值顺序。
大部分语句都以分号结尾。 最常用的语句是表达式语句。 用花括号括起来的一条或多条语句构成了复合语句(或称为块) 。 while语句是一种迭代语句, 只要测试条件为真, 就重复执行循环体中的语句。
在C语言中, 许多类型转换都是自动进行的。 当char和short类型出现在表达式里或作为函数的参数(函数原型除外) 时, 都会被升级为int类型;float类型在函数参数中时, 会被升级为double类型。 在K&R C(不是ANSIC) 下, 表达式中的float也会被升级为double类型。 当把一种类型的值赋给另一种类型的变量时, 值将被转换成与变量的类型相同。 当把较大类型转换成较小类型时(如, long转换成short, 或 double 转换成 float) , 可能会丢失数据。
定义带一个参数的函数时, 便在函数定义中声明了一个变量, 或称为形式参数。 然后, 在函数调用中传入的值会被赋给这个变量。 这样, 在函数中就可以使用该值了。
C控制语句:循环
执行语句序列;如果满足某些条件就重复执行语句序列(循环通过测试选择执行哪一个语句序列(分支) 。
程序注释
status == 1
==运算符是C的相等运算符(equality operator) , 该表达式判断status是否等于1。 不要把status== 1与status = 1混淆, 后者是把1赋给status。status == 1, 只要status等于1, 循环就会重复。 每次循环, num的当前值都被加到sum上, 这样sum的值始终是当前整数之和。 当status的值不为1时,循环结束。 然后程序打印sum的最终值。
每次循环都要获取num的一个新值, 并重置status。程序利用scanf()的两个不同的特性来完成。 首先, 使用scanf()读取num的一个新值; 然后, 检查scanf()的返回值判断是否成功获取值。scanf()返回成功读取项的数量。 如果scanf()成功读取一个整数, 就把该数存入num并返回1, 随后返回值将被赋给status(注意, 用户输入的值储存在num中, 不是status中) 。 这样做同时更新了num和status的值, while循环进入下一次迭代。 如果用户输入的不是数字(如, q) , scanf()会读取失败并返回0。 此时, status的值就是0, 循环结束。 因为输入的字符q不是数字, 所以它会被放回输入队列中(实际上, 不仅仅是 q, 任何非数值的数据都会导致循环终止, 但是提示用户输入q退出程序比提示用户输入一个非数字字符要简单) 。
该程序的结构。
把sum初始化为0
提示用户输入数据
读取用户输入的数据
当输入的数据为整数时,
输入添加给sum,
提示用户进行输入,
然后读取下一个输入
输入完成后, 打印sum的值
这叫作伪代码(pseudocode) , 是一种用简单的句子表示程序思路的方法, 它与计算机语言的形式相对应。 伪代码有助于设计程序的逻辑。 确定程序的逻辑无误之后, 再把伪代码翻译成实际的编程代码。使用伪代码的好处之一是, 可以把注意力集中在程序的组织和逻辑上, 不用在设计程序时还要分心如何用编程语言来表达自己的想法。
while循环是入口条件循环, 程序在进入循环体之前必须获取输入的数据并检查status的值, 所以在 while 前面要有一个 scanf()。让循环继续执行, 在循环内需要一个读取数据的语句, 这样程序才能获取下一个status的值, 所以在while循环末尾还要有一个scanf(), 它为下一次迭代做好了准备。伪代码作为while循环的标准格式:
获得第1个用于测试的值
当测试为真时
处理值
获取下一个值
风格读取循环
status = scanf("%ld", &num);
while (status == 1)
{
/*循环行为 */
status = scanf("%ld", &num);
} 可以用这些代码替换:
while (scanf("%ld", &num) == 1)
{ /*循环行为*/
}
第二种形式同时使用scanf()的两种不同的特性。 首先, 如果函数调用成功, scanf()会把一个值存入num。 然后, 利用scanf()的返回值(0或1, 不是num的值) 控制while循环。 因为每次迭代都会判断循环的条件, 所以每次迭代都要调用scanf()读取新的num值来做判断。
while语句
while循环的通用形式如下:
while ( expression )
statement
statement部分可以是以分号结尾的简单语句, 也可以是用花括号括起来的复合语句。
程序示例中的expression部分都使用关系表达式。 也就是说, expression是值之间的比较, 可以使用任何表达式。 如果expression为真(或者更一般地说, 非零) , 执行 statement部分一次, 然后再次判断expression。 在expression为假(0) 之前, 循环的判断和执行一直重复进行。每次循环都被称为一次迭代(iteration)。
终止while循环
while循环有一点非常重要: 在构建while循环时, 必须让测试表达式的值有变化, 表达式最终要为假。 否则, 循环就不会终止。
何时终止循环
while:入口条件循环
while循环是使用入口条件的有条件循环。 所谓“有条件”指的是语句部分的执行取决于测试表达式描述的条件, 如(index < 5)。 该表达式是一个入口条件( entry condition) , 因为必须满足条件才能进入循环体。
index = 10;
while (index++ < 5)
printf("Have a fair day or better.\n");
把第1行改为:
index = 3;
就可以运行这个循环
语法要点
屏幕上会一直输出以上内容, 除非强行关闭这个程序。变量n的值不会改变, 条件n < 3一直为真。 该循环会一直打印n is 0, 除非强行关闭程序。 这是一个无限循环(infinite loop) 的例子, 没有外部干涉就不会退出。
while语句本身使用复合语句, 在语句构成上, 它也是一条单独的语句。 该语句从while开始执行, 到第1个分号结束。 在使用了复合语句的情况下, 到右花括号结束。要注意放置分号的位置。
循环在执行完测试条件后面的第 1 条语句(简单语句或复合语句) 后进入下一轮迭代, 直到测试条件为假才会结束。程序中第7行的测试条件后面直接跟着一个分号, 循环在此进入下一轮迭代, 因为单独一个分号被视为一条语句。n的值在每次循环时都递增1, 但是第8行的语句不是循环的一部分, 因此只会打印一次循环结束后的n值。
在C语言中, 单独的分号表示空语句。 跳过输入到第1个非空白字符或数字。
while (scanf("%d", &num) == 1); /* 跳过整数输入 */
只要scanf()读取一个整数, 就会返回1, 循环继续执行。 注意, 为了提高代码的可读性, 应该让这个分号独占一行, 不要直接把它放在测试表达式同行。一方面更容易看到空语句, 一方面提醒自己空语句是有意而为之。