目录
1 if语句
while循环的测试条件利用scanf()的返回值来结束循环,因为scanf()在读到非数字字符时会返回0。if语句被称为分支语句或选择语句,因为它相当于一个交叉点,程序需要在两条分支中选择一个执行。if语句的通用形式如下:
if(expression)
statement
2 if else语句
c还提供了if else形式,可以在两条语句之间作选择。if else语句的通用形式:
if(expression)
statement1
else
statement2
如果expression为真(非00,则执行statement1;如果expression为假或0,则执行else后面的statement2.statement1和statement2可以是一条简单语句或复合语句。c并不要求一定要缩进,但这是标准风格。缩进让根据测试条件的求值结果来判断执行哪部分语句一目了然。 如果要在if和else之间执行多条语句,必须用花括号把这些语句括起来称为一个快。
2.1 介绍getchar()和putchar()
getchar()函数不带任何参数,它从输入队列中返回下一个字符。例如,下面的语句读取下一个字符输入,并把该字符赋值给变量ch;
ch = getchar();
该语句与下面的语句效果相同:
scanf("%c",&ch);
putchar()函数打印它的参数。例如,下面的语句把之前赋给ch的值作为字符打印出来:
putchar(ch);
该语句与下面的语句效果相同:
printf("%c",ch);
由于这些函数只处理字符,所以它们比更通用的scanf()和printf()函数更快,更简洁。这两个函数通常定义在stdio.h头文件中(而且,它们通常是预处理宏,而不是真正的函数)。
可以将while里面的测试条件换成下面这行代码:
while( (ch = getchar() ) != '\n');
2.2 多重选择else if
下面是某电力公司的电费清单,单位是千瓦时(kwh):
首 360kwh: $0.13230/kwh
续 108kwh: $0.15040/kwh
续 252kwh: $0.30025/kwh
超过 720kwh: $0.34025/kwh
符号常量表示不同的费率和费率分界点,以便把常量统一放在一处。这样,电力公司在更改费率以及费率分界点时,更新数据非常方便。该程序由一个if else语句组成,else 部分包含另一个 if else语句,该 if else 语句的else部分又包含另一个if else语句。
2.3 else与if配对
如果没有花括号,else与离它最近的if匹配,除非最近的if被花括号起来。
2.4 多层嵌套的if语句
把嵌套if应用在下面的程序中。给定一个整数,显示所有能整除它的约数。如果没有约数,则报告该数是一个素数。
为方便起见,程序应该使用一个循环让用户能连续输入待测试的数。这样,测试一个新的数字时不必每次都要重新运行程序。伪代码如下:
提示用户输入数字
当scanf()返回值为1
分析该数并报告结果
提示用户继续输入
测试条件中使用scanf(),把读取数字和判断测试条件确定是否结束循环合并在一起。
如何找出约数。最直接的方法如下:
for( div = 2; div < num; div ++)
if( num % div == 0)
printf(“%d is divisible by %d \n”,num,div);
该循环检查2-num之间的所有数字,测试它们是否能被num整除。但是,这个方法有点浪费时间。如果144%2得0,说明2是144的约数:如果144除以2得72,那么72也是144的一个约数。所以,num % div 测试成功说明可以获得两个约数。144的成对约数:2和72,3和48,4和36,6和24,8和18,9和16,12和12,16和9,18和8,等等。在得到12和12这对约数后,又开始得到已找到得相同约数。因此,不用循环到143,在达到12以后就可以停止循环。
测试的数只要到num的平方根就可以了,不用到num。我们不用在程序中计算平方根,可以这样编写测试条件:
for(div = 2;(div * div) <= num; div++)
if(num % div == 0)
printf("%d is divisible by %d and %d.\n",num,div,num / div);
如果num是144,当div = 12时停止循环。如果num是145,当div = 13 时停止循环。
如果待测的数是一个完全平方数怎么办?报告144可以被12和12整除显得有点傻。可以使用嵌套if语句测试div是否等于num/div。如果是,程序只打印一个约数:
for(div = 2; (div *div) <= num; div ++)
{
if( num % div == 0)
{
if(div * div != num)
{
printf("%d is divisible by %d and %d.\n",num,div,num/div);
}
else
{
printf("%d is divisible by %d.\n",num,div);
}
}
}
如何知道一个数字是素数?如果num是素数,程序流不会进入if语句。要解决这个问题,可以在外层循环把一个变量设置为某个值(如,1),然后在if语句中把该变量重新设置为0。循环完成后,检查该变量是否是1,如果是,说明没有进入if语句,那么该数就是素数。这样的变量通常称为标记。
一直以来,c都习惯用int作为标记的类型,其实新增的_Bool类型更适合。另外,如果在程序中包含了stdbool.h头文件,便可用bool代替_Bool类型,用true和false分别代替1和0。
该程序在for循环的测试表达式中使用了逗号运算符,这样每次输入新值时都可以把isPrime设置为true。
该程序会把1认为是素数,其实它不是。
3 逻辑运算符
if语句和while语句通常使用关系表达式作为测试条件。有时,把多个关系表达式组合起来会很有用,这个时候就可以使用逻辑运算符。下面的程序,计算输入的一行句子中除单引号和双引号以外其他字符的数量。
程序首先读入一个字符,并检查它是否是一个句点,因为句点标志一个句子的结束。接下来,if语句的测试条件中使用了逻辑与运算符&&。该if语句翻译成文字是“如果待测试的字符不是双引号,并且它也不是单引号,那么charcount递增1。
逻辑运算符两侧的条件必须都为真,整个表达式才为真。逻辑运算符的优先级比关系运算符低,所以不必在子表达式两侧加圆括号。
逻辑运算符 | 含义 |
&& | 与 |
|| | 或 |
! | 非 |
假设exp1和exp2是两个简单的关系表达式,那么:
1.当且仅当exp1和exp2都为真时,exp1 && exp2 才为真;
2.如果exp1或exp2为真,则exp1 || exp2 为真;
3.如果exp1为假,则 !exp为真;如果exp1为真,则!exp1为假。
3.1 优先级
!运算符的优先级很高,比乘法运算符还高,与递增运算符的优先级相同,只比圆括号的优先级低。&&运算符的优先级比 | |运算符高,但是两者的优先级都比关系运算符低,比赋值运算符高。因此,表达式 a > b && b > c || b >d 相当于 (a > b) && (b > c) || (b > d)。
3.2 求值顺序
除了两个运算符共享一个运算对象的情况外,c通常不保证先对复杂表达式中哪部分求值。例如,下面的语句,可能先对表达式 5 + 3求值,也可能先对表达式 9 + 6求值:
apples = (5+3) * (9+6);
但是逻辑运算符是个例外,c保证逻辑表达式的求值顺序是从左往右。&&和| |运算符都是序列点,所以程序在从一个运算对象执行到下一个运算对象之前,所有的副作用都会生效。而且,c保证一旦发现某个元素让整个表达式无效,便立即停止求值。
逻辑运算符的运算对象通常是关系表达式。!运算符只需要一个运算对象,其他两个逻辑运算符都需要两个运算对象,左侧一个,右侧一个。
3.3 范围
&&运算符可用于测试范围。例如,要测试score是否90到100的范围内,可以这样写:
if (range >= 90 && range <= 100)
printf("good");
下面这种写法有语义上的错误,而不是语法错误:
if( 90 <= range <=100)
printf("good");
由于<=运算符的求值顺序是从左往右,所以编译器把测试表达式解释为:
(90 <= range) <= 100
子表达式 90 <= range 的值要么是 1,要么是0 。这两个值都小于100,所以不管range的值是多少,整个表达式都恒为真。因此,在范围测试中要使用&&。
4 一个统计单词的程序
该程序要逐个字符读取输入,知道何时停止读取。然后,该程序能识别并计算这些内容:字符,行数和单词。
要查找一个单词里面是否有某个字符,可以在程序读入单词的首字符时把一个标记(名为inword)设置为1。也可以在此时递增单词计数。然后,只要inword为1,后续的非空白字符都不记为单词的开始。下一个空白字符,必须重置标记为0(或false),然后程序就准备好读取下一个单词。以下是伪代码:
如果c不是空白字符,且inword为假
设置inword为真,并给单词计数
如果c是空白字符,且inword为真
设置inword为假
!inword 与 inword == false 等价。
5 条件运算符: ?:
c提供条件表达式作为if else语句的一种便捷方式,该表达式使用?:条件运算符。该运算符分为两部分,需要3个运算对象。条件运算符是c语言中唯一的三元运算符。
条件表达式的通用形式如下:
expression1 ? expression2 : expression3
如果expression1 为真,那么整个条件表达式的值与expression2的值相同:如果expression1为假,那么整个条件表达式的值与expression3的值相同。
条件运算符完成的任务用if else语句也可以完成。但是,使用条件运算符的代码更简洁,而且编译器可以生成更紧凑的代码。如下程序计算给定平方英尺的面积需要多少罐油漆。
该程序使用的变量都是int类型,除法的计算结果(sq_feet / COVERAGE)会被截断。也就是说,351/350得1。所以,cans被截断成整数部分。如果sq_feet % COVERAGE 得 0,说明sq_feet被COVERAGE整除,cans的值不变;否则,肯定有余数,就要给cans加1。这👉下面的语句完成:
cans += ( sq_feet % COVERAGE == 0) ? 0 : 1;
6 循环辅助:continue 和 break
一般而言,程序进入循环后,在下一次循环测试之前会执行完循环体中的所有语句。continue和break语句可以根据循环体中的测试结果来忽略一部分循环内容,甚至结束循环。
6.1 continue语句
3种循环都可以使用continue语句。执行到该语句时,会跳过本次迭代的剩余部分,并开始下一轮迭代。如果continue语句在嵌套循环内,则只会影响包含该语句的内层循环。
while循环读取输入,直至用户输入非数值数据。循环中的if语句筛选除无效的分数。假设输入188,程序会报告:188 is an invalid value。在本例中,continue语句让程序跳过处理有效输入部分的代码。程序开始下一轮循环,准备读取下一个输入值。
有两种方法可以避免使用continue,一是省略continue,把剩余部分放在一个else块中:
if( score < 0 || socre > 100)
/*printf()语句*/
else
{ /*语句*/
}
另一种方法是,用以下格式来代替:
if( socre >= 0 && score <= 100)
{
/*语句*/
}
这种情况下,使用continue的好处是减少主语句组中的一级缩进。当语句很长或嵌套较多时,紧凑简洁的格式提高了代码的可读性。
continue还可用作占位符。例如,下面的循环读取并丢弃输入的数据,直至读到行末尾:
while( getchar() != '\n')
;
当程序已经读取一行中的某些内容,要跳至下一行开始处时,这种用法很方便。问题是,一般很难注意到一个单独的分号。如果使用continue,可读性会更高:
while( getchar() != '\n')
continue ;
continue语句让程序跳过循环体的余下部分。那么,从何处开始继续循环?对于while和do while循环,执行continue语句后的下一个行为是对循环的测试表达式求值。如下代码:
count = 0;
while( count < 10)
{
ch = getchar();
if ( ch == '\n')
continue;
putchar(ch);
count ++;
}
该循环读取10个字符(除换行符外,因为当ch是换行符时,程序会跳过count++;语句)并重新显示它们,其中不包括换行符。执行continue后,下一个被求值的表达式是循环测试条件。
对于for循环,执行continue后的下一个行为是对更新表达式求值,然后是对循环测试表达式求值。例如,考虑下面的循环:
for( count = 0; count < 10; count++)
{
ch = getchar();
if( ch == '\n')
continue;
putchar(ch);
}
该例中,执行完continue后,首先递增count,然后将递增后的值和10作比较。因此,该循环与上面while循环的例子稍有不同。while循环的例子中,除了换行符,其余字符都显示;而本例中,换行符也计算在内,所以读取的10个字符中包含换行符。
6.2 break语句
程序执行到循环中的break语句时,会终止包含它的循环,并继续执行下一阶段。
可以这样控制循环:
while(scanf("%f %f",&length,&width) == 2)
但是,用break可以方便显示用户输入的值。
7 多重选择:switch 和 break
7.1 switch语句
要对紧跟在关键字switch后圆括号中的表达式求值。在上面的程序当中,该表达式是刚输入给ch的值。然后程序扫描标签列表,知道发现一个匹配的值为止。然后程序跳转至那一行。如果没有匹配的标签怎么办?如果有default:标签行,就跳转至改行;否则,程序继续执行在switch后面的语句。
break语句在其中起什么作用?它让程序离开switch语句,跳至switch语句后面的下一条语句。如果没有break语句,就会从匹配标签开始执行到switch末尾。
break语句可用于循环和switch语句中,但是continue只能用于循环中。另外,c语言的case一般都指定一个值,不能使用一个范围。
switch在圆括号中的测试表达式的值应该是一个整数值(包括char类型)。case标签必须是整数类型(包括char类型)的常量或整型常量表达式(即,表达式中只包含整型常量)。不能用变量作为case标签。switch的构造如下:
7.2 只读每行的首字符
在上面的程序当中,当输入dab时,只处理了第1个字符。这种丢弃一行中其他字符的行为,经常出现在响应单字符的交互程序中。可以用下面的代码实现这样的行为:
while( getchar() != '\n')
continue;
循环从输入中读取字符,包括按下enter键产生的换行符。注意,函数的返回值并没有赋给ch,以上代码所做的只是读取并丢弃字符。由于最后丢弃的字符是换行符,所以下一个被读取的字符是下一行的首字母。在外层的while循环中,getchar()读取首字母并赋给ch。
假设用户一开始就按下enter键,那么程序读到的首个字符就是换行符。下面的代码处理这种情况:
if(ch == '\n')
continue;
7.3 多重标签
假设如果ch是字母i,switch语句会定位到标签为case 'i':的位置。由于该标签没有关联break语句,所以程序流直接执行下一条语句,即i_ct ++;。如果ch是字母I,程序流会直接定位到case 'I' :。本质上,两个标签都指的是相同的语句。
7.4 switch 和 if else
何时使用switch?何时使用if else?你经常会别无选择。如果是根据浮点类型的变量或表达式来选择,就无法使用switch。如果根据变量在某范围内决定程序流的去向,使用switch就很麻烦,这种情况用if就很方便:
if( integer < 1000 && integer > 2)
使用switch要涵盖以上范围,需要为每个整数(3 - 999)设置标签。但是,如果使用switch,程序通常运行快一些,生成的代码少一些。
8 goto语句
goto语句有两部分:goto 和标签名。标签的命名遵循变量名规则,如下所示:
goto part2;
要让这条语句正常工作,函数还必须包含另一条标为part2的语句,该语句以标签名后紧跟一个冒号开始:
part2 : printf("Refined analysis:\n");
9 编程练习
1.编写一个程序输入,读到#字符停止。程序报告读取的空格数,换行符数和所有其他字符的数量。
使用while循环从输入队列中读取字符,测试条件为 (ch = getchar()) != '#'。定义三个计数器来计算空格数,换行符数和其他字符数。根据ch的值,使用嵌套if else来递增不同的计算器。
2.编写一个程序读取输入,读到#字符停止。程序要打印每个输入的字符以及对应的ASII码(十进制)。每行打印8个“字符-ASCII码”组合。建议:使用字符计数和求模运算符(%)在每8个循环周期时打印一个换行符 。
使用while循环从输入队列中读取字符,测试条件为 (ch = getchar()) != '#' 。定义一个字符计算器,对字符计数器求模,当为8的倍数是打印一个换行符。同时如果是读取到换行符,可以使用continue跳过本次迭代的剩余部分,直接进入下一次迭代。
3.编写一个程序,读取整数直到用户输入0。输入结束后,程序应报告用户输入的偶数(不包括0)个数,这些偶数的平均值,输入的奇数个数及其奇数的平均值。
4.使用if else语句编写一个程序读取输入,读到#停止。用感叹号替换句号,用两个感叹号替换原来的感叹号,最后报告进行了多少次替换。
使用while循环读取用户的输入,测试条件为 (ch = getchar())!= '#' 。定义两个计数器,然后对ch的值判断。如果ch的值为 '.' 就递增 第一个计算器,并打印'!' ; 如果ch的值为 '!' 就递增第二个计数器,并打印两个 !! ; 如果以上两种情况都不会,直接打印ch的值。
5.使用switch重写练习4
6.编写程序读取输入,读到#停止,报告ei出现的次数。
注意:该程序要记录前一个字符和当前字符。用"receive your eieio award"这样的输入来测试
从输入队列中循环读取数据,如果当前读取的值是e,就继续读取并判断下一值是否为i。最后打印ei出现的次数
7.编写一个程序,提示用户输入一周工作的小时数,然后打印工资总额,税金和净收入。做如下假设:
a:基本工资 = 10.00美元/小时
b:加班(超过40小时) = 1.5倍的时间
c:税率: 前300美元为15%
续150美元为20%
余下的为25%
用#define定义符号常量。不用在意是否复合当前的税法。
8.修改练习7的假设a,让程序可以给出一个供选择的工资等级菜单。使用switch完成工资等级选择。运行程序后,显示的菜单应该类似这样:
**************************************************************************
enter the number corresponding to the desired pay rate or action:
1) $8.75/hr 2)$9.33/hr
3) $10.00/hr 4)$11.20/hr
5) quit
**************************************************************************
如果选择1-4其中一个数字,程序应该询问用户工作的小时数。程序要通过循环运行,除非用户输入5。如果输入1~5以外的数字,程序应提醒用户输入正确的选项,然后再重复显示菜单提示用户输入。使用#define创建符号常量表示各工资等级和税率。
打印选择菜单 - -循环获取用户输入的值(测试条件为 (scanf("%d",&input)) )-- 对用户输入的值进行判断:
1)如果值是1到4,提示用户输入工作小时数
2)如果值是1-5提示以外的数,提醒用户输入正确的选项
3)如果是值是5,退出循环
9.编写一个程序,只接收正整数输入,然后显示所有小于或等于该数的素数。
提示用户输入数字
当scanf()的返回值为1时
分析该数并报告结果
提示用户继续输入
判断是否是素数可以使用以下方法:
for( div =2,标记num为素数 ; (div *div)< =num; div++)
if( num % div == 0)
{
if( (div * div) != num)
{
打印div为约数,num/div也为约数
}
else
{
打印div为约数
}
重置标记为不是素数
}
if(判断标记的值)
{ 打印num为素数
}