目录
再探while循环
程序注释
while循环的测试条件是如下表达式:
status == 1
==运算符是c的相等运算符,该表达式判断status是否等于1。当status的值不为1时,循环结束。
要让程序正常运行,每次循环都要获取num的一个新值,并重置status。程序利用scanf()的两个不同的特性来完成。首先,使用scanf()读取一个num的一个新值;然后,检查scanf()的返回值判断是否成功获取值。scanf()返回成功读取项的数量。如果用户输入的不是数字(如,q),scanf()会读取失败并返回0.
如何告诉循环何时停止?该程序利用scanf()的双重特性避免了在循环中交互输入时的这个棘手问题。例如,假设scanf()没有返回值,那么每次循环只会改变num的值。虽然可以使用num的值来结束循环,比如把num>0或num!= 0作为测试条件,但是这样用户就不能输入某些值,如-3或0.
总之,因为while循环是入口条件循环,程序在进入循环体之前必须获取输入的数据并检查status的值,所以在while前面要有一个scanf()。要让循环继续执行,在循环内需要一个读取数据的语句,这样程序才能获取下一个status的值,所以在while循环末尾还要一个scanf(),它为下一次迭代做好了准备。可以把下面的伪代码作为while循环的标准格式:
获得第1个用于测试的值
当测试为真时
处理值
获取下一个值
c风格读取循环
第二种形式同时使用scanf()的两种特性。首先,如果函数调用成功,scanf()会把一个值存入num,然后,利用scanf()的返回值(0 或 1,不是num的值)控制while循环。因为每次迭代都会判断循环的条件,所以每次迭代都要调用scanf()读取新的值做判断。换句话说,c的语法特性让你可以用下面的精简版替换标准版本:
当获取值和判断值都成功
处理该值
while语句
while循环的通用形式如下:
while(expression)
statement
statement部分可以是以分号结尾的简单语句,也可以是用花括号括起来的复合语句。
终止while循环
在构建while循环时,必须让测试表达式的值有变化,表达式最终要为假。否则,循环就不会终止(实际上,可以使用break和if语句来终止循环)
何时终止循环
while:入口条件循环
while循环是使用入口条件循环。所谓“有条件”指的是语句部分的执行取决于测试表达式描述的条件,如(index < 5)。该表达式是一个入口条件,因为必须满足条件才能进入循环体。在下面的情况中,就不会进入循环体,因为条件一开始就为假:
index = 10;
while( index++ < 5)
{
printf("have a fair day or better.\n");
}
语法要点
使用while时,要牢记一点:只有在测试条件后面的单独语句(单独语句或复合语句)才是循环部分。
用关系运算符和表达式比较大小
while循环经常依赖测试表达式做比较,这样的表达式被称为关系表达式,出现在关系表达式中间的运算符叫关系运算符。如下图:
运算符 | 含义 |
< | 小于 |
<= | 小于或等于 |
== | 等于 |
>= | 大于或等于 |
> | 大于 |
!= | 不等于 |
什么是真
在c中,表达式一定有一个值,关系表达式也不例外。
由此可知,对于c语言,表达式为真的值是1,表达式为假的值是0。一些c程序使用下面的循环结构,由于1为真,所以循环会一直进行。
while(1)
{
......
}
其他真值
一般而言,所有的非零值都视为真,只有0被视为假。在c中,真的概念还真宽!也可以说,只要测试条件的值为非零,就会执行while循环。这是从数值方面而不是从真/假方面来看测试条件。要牢记:关系表达式为真,求值得1;关系表达式为假,求值得0。因此,这些表达式实际上相当于数值。
如果待比较得一个值是常量,可以把该常量放在左侧有助于编译器捕获错误:
5 = canoes //语法错误
5 == canoes //检查 canoes得值是否为5
可以这样做是因为c语言不允许给常量赋值, 编译器会把赋值运算符的这种用法作为语法错误标记出来。无多经验丰富的程序员在构建比骄是否相等的表达式时,都习惯把常量放在左侧。
总之,关系运算符用于构成关系表达式。关系表达式为真时值为1,为假时值为0。通常用关系表达式作为测试条件的语句(如while和if)可以使用任何表达式作为测试条件,非零为真,零为假。
新的_Bool类型
在编程中,表示真或者假的变量被称为布尔变量。所以_Bool是c语言中布尔变量的类型名。_Bool类型的变量只能存储1(真)或0(假)。如果把其他非零数值赋给_Bool类型的变量,该变量会被设置为1。这反映了c把所有的非零值都视为真。
优先级和关系运算符
关系运算符的优先级比算术运算符低。比赋值运算符高。这意味着x > y + 2和x > (y + 2)相同,x = y > 2和 x = (y > 2)相同。换言之,如果y大于2,则给x赋值1,否则赋值0,y的值不会赋值给x。
关系运算符比赋值运算符的优先级高,因此,x_bigger = x > y;相当于x_bigger = (x>y)。与其他运算符一样,关系运算符的结合律也是从左往右。因此:
ex != wye == zee 与 (ex !=wye) == zee 相同
首先,c判断ex与wyx是否相等;然后,用得出的值1或0(真或假)再与zee比较。
不确定的循环和计算循环
一些while循环是不确定循环,所谓不确定循环,指在测试表达式为假之前,预先不知道要执行多少次循环。另外,还有一类是计数循环。这类循环之前就知道要重复执行多少次。
在创建一个重复执行固定次数的循环中涉及了3个行为:
1.必须初始化计数器;
2.计数器与有限的值作比较;
3.每次循环时递增计数器;
while循环的测试条件比较,递增运算符执行递增。递增发生在循环的末尾,这可以防止不小心漏掉递增。因此,这样做比将测试和更新组合放在一起要好,但是计算器的初始化放在循环外,就有可能忘记初始化。
for循环
for循环把上述3个行为(初始化,测试和更新)组合在一起。
关键字for后面的圆括号中有3个表达式,分别用两个分号隔开。第1个表达式是初始化,只会在for循环开始时执行一次。第2个表达式是测试条件,在执行循环之前对表达式求值。如果表达式为假,循环结束。第3个表达式执行更新,在每次循环结束时求值。
for循环的第1行包含了循环所需的所有信息:num的初值,num的终值和每次循环num的增量。
利用for的灵活性
以前面程序示例中的for循环为例,第1个表达式给计数器赋初值,第 2个表达式表示计数器的范围,第3个表达式递增计数器。除此之外,for循环还有其他9种用法。
--------可以使用递减运算符来递减计数器
--------可以让计数器递增2,10等
-------可以用字符代替数字计数
该程序能正常运行是因为字符在内部是以整数形式存储的,因此该循环实际上仍是用整数来计数。
----------除了测试迭代次数外,还可以测试其他条件。在for_cube程序种,可以把:
for(num = 1; num <= 6; num ++)
替换成:
for(num = 1; num*num*num <= 216; num++)
如果与控制循环次数相比,你更关心限制立方体的大小,就可以使用这样的测试条件。
----------可以让递增的量几何增长,而不是算术增长。也就是说,每次都乘上而不是加上一个 固定的量:
------可以省略一个或多个表达式(但是不能省略分号),只要在循环中包含能结束循环的语句即可。
如果省略第2个表达式被视为真,所以下面的循环会一直运行。
for(;;)
printf("I want some action\n");
其他赋值运算符:+= ,-=,*=,/=,%=
c有许多赋值运算符。最基本最常用的是=,它把右侧表达式的值赋给左侧的变量。其他赋值运算符都用于更新变量。其用法都是左侧是一个变量名,右侧是一个表达式。赋给变量的新值是根据右侧表达式的值调整后的值。确切的调整方案取决于具体的运算符。例如:
score +=20 与 socres = socres + 20 相同
dimes -=2 与 dimes = dimes - 2 相同
bunnies *= 2 与 bunnies = bunnies *2 相同
time /= 2.73 与 time = time /2.73相同
reduce %= 3 与 reduce = reduce % 3 相同
还可以使 用更复杂的表达式,例如:
x *= 3 * y +12与 x = x *(3*y +12)相同
并非一定要使用这些组合形式的赋值运算符。但是,它们让代码更加紧凑,而且与一般形式相比,组合形式的赋值运算符生成的机器代码更高效。当需要在for循环中塞进一些复杂的表达式时,这些组合的赋值运算符特别有用。
逗号运算符
逗号运算符扩展了for循环的灵活性,以便在循环中包含更多的表达式。如下程序演示了一个打印一类邮件资费的程序(邮资为首重46美分/盎司,续重20美分/盎司)
逗号运算符并局限在for循环中使用,但是这是它最常用的地方。 逗号运算符有两个其他性质。首先,它保证了被分隔的表达式从左往右求值(换言之,逗号是一个序列点,所有逗号左侧项的所有副作用都在程序执行逗号右侧项之前发生)。因此,ounces在cost之前被初始化。在该例中,顺序并不重要。
其次,整个逗号表达式的值是右侧项的值。例如,下面语句
x = (y = 3, (z = ++y + 2) + 5);
先把3赋给y,递增y为4,然后把4加2之和(6)赋给z,接着加上5,最后把结果11赋给x。假设在写数字时不小心输入了逗号:
houseprice = 249,500;
这不是语法错误,c编译器会将其解释为一个逗号表达式,即houseprice = 249 是逗号左侧的子表达式,500是右侧的子表达式。因此,整个逗号表达式的值是逗号右侧表达式的值,而且左侧的赋值表达式把249赋给变量houseprice。因此,这与下面代码的效果相同:
houseprice = 249;
500;
任何一个表达式后面加上一个分号就成为了表达式语句。所以,500;也是一条语句,但是什么也不做。下面的语句:
houseprice = (249,500);
赋给houseprice的值是逗号右侧子表达式的值,即500.
逗号也可以用作分隔符。在下面语句中的逗号都是分隔符,不是逗号运算符:
char ch,date;
printf("%d %d\n",chimps,chumps);
当zeno遇到for循环
希腊哲学家提出zeno曾经提出箭永远不会达到它的目标。首先,他认为箭要到达目标的一半,然后再到达剩余距离的一半,然后继续到达剩余距离的一半,这样就无穷无尽。zeno认为箭的飞行过程有无数个部分,所以要花费无数时间才能结束这一过程。
我们采用一种定量的方法,假设箭用1秒走完一半的路程,然后用1/2秒走完剩余距离的一半,然后用1/4再走完剩余距离的一半。无限序列来表示总时间:
1 + 1/2 + 1/4 + 1/8 + 1/16 + ....
尽管不断添加新的项,但是总和看起来变化不大。
出口条件循环:do while
while循环和for循环都是入口条件循环,即在循环的每次迭代之前检查测试条件,所以有可能根本不执行循环体中的内容。c语言还有出口条件循环,即在循环的每次迭代之后检查测试条件,这保证了至少执行循环体中的内容一次。这种循环被称为do while循环。
下面是do while循环的通用形式:
do
statement
while(expression);
statement可以是一条简单语句或复合语句。注意,do while循环以分号结尾。
do while循环在执行完循环体后才执行测试条件,所以至少执行循环体一次;而for循环或while循环都是在执行体之前先执行测试条件。do while循环适用于那些至少要迭代一次的循环。例如,包含do while循环的密码程序伪代码
do
{
提示用户输入密码
读取用户输入的密码
}while(用户输入的密码不等于密码);
其他行为;
如何选择循环
如何选择使用哪一种循环?首先,确定是需要入口条件循环还是出口条件循环。通常,入口条件用得比较多,有几个原因。其一,一般原则是在执行循环之前测试条件比较好。其二,测试放在循环的开头,程序的可读性更高。另外,在许多应用中,要求在一开始不满足测试条件时就直接跳过整个循环。
假设需要一个入口条件循环,用for循环还是while循环?这取决于个人喜好,因为二者皆可。要让for循环看起来像while循环,可以省略第1个和第3个表达式。例如:
for( ; test ; )
与下面的while效果相同
while( test )
要让while循环看起来像for循环,可以在while循环的前面初始化变量,并在while循环中包含更新语句。例如:
初始化;
while(测试)
{
其他语句
更新语句
}
与下面的for循环效果相同:
for(初始化;测试;更新)
其他语句
一般而言,当循环涉及初始化和更新变量时,用for循环比较合适,而在其他情况下用while循环更好。对于下面这种条件,用while循环就很合适:
while( scanf("%ld", &num) == 1)
对于涉及索引计数的循环,用for循环更合适。例如:
for( count = 1; count <= 100 ; count++)
嵌套循环
嵌套循环指在一个循环内包含另外一个循环。嵌套循环常用于按行和列显示数据,也就是说,一个循环处理一行中的所有列,另一个循环处理所有的行。
程序分析
外层循环从row为0开始循环,到row为6循环结束。因此,外层循环要执行6次,row的值从0变为5.每次迭代要执行的第1条语句是内层的for循环,该循环要执行10次,在同一行打印字符A~J;第2条语句是外层循环的printf("\n");,该语句的效果是另起一行,这样在下一次运行内存循环时,将在下一行打印的字符。
注意,嵌套循环中的内层循环在每次外层循环迭代时都执行完所有的循环。
嵌套变式
内层循环和外层循环所做的事情相同。可以通过外层循环控制内层循环,在每次外层循环迭代时内层循环完成不同的任务。 内层循环开始打印的字符取决于外层循环的迭代次数。
数组简介
数组是按顺序存储的一系列类型相同的值,如10个char类型的字符或15个int类型的值。整个数组有一个数组名,通过整数下标访问数组中单独的项或元素。例如,以下声明:
float debts[20];
声明debts是一个内含20个元素的数组,每次元素都可以存储float类型的值。数组的第1个元素是debts[0],第2个元素是debts[1],一次类推,直到debts[19]。注意,数组元素的编号从0开始,不是从1开始。可以每个元素赋float类型的值。
考虑到影响执行的速度,c编译器不会检查数组的下标是否正确。下面的代码,都不正确:
debts [20] = 88.32 //该数组元素不存在
debts [33] = 828.12;//该数组元素不存在
编译器不会查找这样的错误。当运行程序时,这会导致数据被放置在已被其他数据占用的地方,可能会破坏程序的结果甚至导致程序异常中断。
可以把字符串存储在char类型的数组中(一般而言,char类型数组的所有元素都存储char类型的值)。如果char类型的数组末尾包含一个表示字符串末尾的空字符\0,该数组中的内容就构成了一个字符串。
用于识别数组元素的数字被称为下标,索引或偏移量。下标必须是整数,而且要从0开始计数。数组的元素被依次存储在内存中相邻的位置
在for循环中使用数组
读取10个高尔夫分数,稍后进行处理。使用数组,就不用创建10个不同的变量来存储10个高尔夫分数。而且,还可以用for循环来读取数据。程序打印总分,平均分,差点,它是平均分与标准分的差值)
由于scanf()会跳过空白符,所以可以在一行输入10个数字,也可以每行只输入一个数字,或者混合使用空格和换行符隔开每个数字(因为输入是缓冲的,只有当用户键入Enter键后数字才会被发送给程序。)
使用函数返回值的循环示例
power()函数的原型,它声明程序将使用一个名为power()的函数。开头的关键字double表明power()函数返回一个double类型的值。编译器要知道power()函数返回值的类型,才能知道有多少字节的数据,以及如何解释它们。这就是为什么声明函数的原因。
使用带返回值的函数
声明函数,调用函数,定义函数,使用关键字return,都是定义和使用带返回值函数的基本要素。
既然在使用函数返回值之前要声明函数,那么为什么在使用scanf()的返回值之前没有声明scanf()?为什么在定义中说明了power()的返回类型为double,还要单独声明这个函数?
编译器在程序中首次遇到power()时,需要知道power()的返回类型。此时,编译器尚未执行到power()的定义,并不知道函数定义中的返回类型是double。因此,必须通过前置声明预先说明函数的返回类型。前置声明告诉编译器,power()定义在别处,其返回类型为double。如果把power()函数的定义置于main()的文件顶部,就可以省略前置声明,因为编译器在执行到main()之前已经知道power()的所有信息。但是,这不是c的标准风格。因为main()通常只提供整个程序的框架,最好把main()放在所有函数定义的前面。另外,通常把函数放在其他文件中,所以前置声明必不可少。
为什么不用声明scanf()函数就可以使用它?其实,已经声明了。stdio.h头文件中包含了scanf(),printf()和其他I/O函数的原型。scanf()函数的原型表明,它返回的类型是int。
编程练习
1.编写一个程序,创建一个包含26个元素的数组,并在其中存储26个小写字母。然后打印打印数组的所有内容。
2.使用嵌套循环,按下面的格式打印字符:
$
$$
$$$
$$$$
$$$$$
使用嵌套循环,外层循环控制打印的行数,内层循环控制打印行的内容。内层循环打印的个数随着外层循环递增。
3. 使用嵌套循环,按下面 的格式打印字母:
F
FE
FED
FEDC
FEDCB
FEDCBA
使用嵌套循环打印,外层循环控制打印的行数,内层循环控制打印的内容,打印的个数随着外层循环递增。同时打印的第一个内容都是F,说明每次都要初始化为F,可以使用逗号运算符。
4.使用嵌套循环,按下面的格式打印字母:
A
BC
DEF
GHIJ
KLMNO
PQRSTU
使用嵌套循环,外层循环控制打印的行数,内存循环控制打印的内容,打印的内容的值ASCII依次递增,可以使用逗号运算符。
5.编写一个程序,提示用户输入大写字母。使用嵌套循环以下面金字塔的格式打印字母:
A
ABA
ABCBA
ABCDCBA
ABCDEDCBA
打印这样的图形,要根据用户输入的字母来决定。例如,上面的图形是在用户输入E后的打印结果。提示:用外层循环处理行,每行使用3个内层循环,分别处理空格,以升序打印字母,以降序打印字母。
申明两个计数器i,j。计算器i用于控制外层循环,计数器用于控制内层循环。以升序打印字母的时候每次都需要初始化从A开始打印,可以使用逗号表达式。
6.编写一个程序打印一个表格,每一行打印一个整数,该数的平方,该数的立方。要求用户输入表格的上下限。使用一个for循环
7.编写一个程序把一个单词读入一个字符数组中,然后倒序打印这个单词。提示:strlen()函数可用于计算数组最后一个字符的下标。
strlen()函数来确定字符数组的有效长度,通过返回值来确定下标,注意循环的起始位置是单词长度-1,循环的终止位置是0。 最后一个字符的下标 = strlen(array) - 1 。
8.编写一个程序,要求用户输入两个浮点数,并打印两数之差除以两数乘积的结果。在用户输入非数字之前,程序应循环处理用户输入的每对值。
循环的入口条件scanf函数成功读取浮点型数据的个数
11.编写一个程序,在数组中读入8个整数,然后按倒序打印这8个整数。
倒序打印的时候需要注意,最后一个元素的下标为n-1
15.编写一个程序,读取一行输入,然后把输入的内容倒序打印出来。可以把输入存储在char类型的数组中,假设每行字符不超过255。回忆一下,根据%c转换说明,scanf()函数一次只能从输入中读取一个字符,而且在用户按下enter键时scanf()函数会生成一个换行字符(\n)。
在使用%c转换说明符时,如果scanf()遇到用户输入回车符,会自动生成换行符(\n),因此可以使用换行符来判断用户输入完毕。