title: 第六章 C控制语句:循环
author: HardyDragon
tags: C Notes
第六章 C控制语句:循环
6.1 再探while循环
程序注释
C风格读取循环
6.2 while语句
6.2.1 终止while循环
何时终止循环
while:入口条件循环
语法要点
6.3 用关系运算符和表达式比较大小
6.3.1 什么是真
6.3.2 其他真值
6.3.3 真值的问题
6.3.4 新的_Bool 类型
6.3.5 优先级和关系运算符
6.4 不确定循环和计数循环
6.5 for循环
6.6 其他赋值运算符: += 、-=、*=、/=、%=
6.7 逗号运算符
6.8 出口条件循环:do while
6.9 如何选择循环
6.10 嵌套循环
6.10.1 程序分析
6.10.2 嵌套变式
6.11 数组简介
6.12 使用函数返回值的循环示例
6.12.1 程序分析
6.12.2 使用带返回值的函数
6.13 关键概念
6.14 本章小结
6.15 复习题
6.16 编程练习
6.1 再探while循环
// summing.c -- 根据用户输入的整数求和
#include <stdio.h>
int main(int argc, char const *argv[])
{
long num;
long sum = 0L;
int status;
printf("please enter an integer to be sum(Q to quit):\n");
status = scanf("%ld", &num);
while (status == 1)
{
/* code */
sum = sum + num;
printf("please enter an integer to be sum(Q to quit):\n");
status = scanf("%ld", &num);
}
printf("sum is %ld\n",sum);
return 0;
}
result:
please enter an integer to be sum(Q to quit):
44
please enter an integer to be sum(Q to quit):
33
please enter an integer to be sum(Q to quit):
88
please enter an integer to be sum(Q to quit):
121
please enter an integer to be sum(Q to quit):
q
sum is 286
该程序使用long类型以存储更大的整数,尽管C编译器会把0自动转换为合适的类型,但是为了保持程序的一致性,把sum初始化为0L,而不是0(int类型的0);
回顾一下,scanf() 的返回值若成功读取一个整数,就将读取的数放入变量,并返回1。如果输入的类型匹配失败就返回0;如果scanf() 在转换值前出现了问题会返回一个特殊值EOF(-1),这个也会导致循环终止。
因为while循环时入口条件循环,程序在进入循环体前必须获取输入的数据并检查status的值,所以while前面要有一个scanf() ,要让循环继续执行,在循环内需要一个读取数据的语句,这样程序才能获取下一个status的值,所以scanf() 函数末尾还要有一个scanf() 它为下一次迭代做好了准备。
-
C风格的读取循环
status = scanf("%ld", &num); while (status == 1) { /* code */ status = scanf("%ld",&num); }
以上读取循环输入的代码可以写的更简洁:(相当于省略中间变量status)
while (scanf("%ld", &num) == 1) { /* code */ }
6.2 while语句
关于终止while循环要注意其条件语句,最小负值减1一般会得到最大值,最大值加1会得到一个负值。
注意while 的分号;位置,while也是一个单独语句。
// while2.c -- 注意分号位置
#include<stdio.h>
int main(int argc, char const *argv[])
{
int n=0;
while (n++<3);
printf("n is %d \n",n);
return 0;
}
result:
n is 4
利用这种空语句的循环,可以做到:跳过第一个非空白字符或数字。
while (scanf("%d", &num) == 1)
;
// 跳过整数输入
只要输入一个整数,那么条件为真,继续循环读取输入;注意分号最好单独放一行便于理解,但是可以使用continue来更好的处理这种奇葩的情况。
6.3 用关系运算符和表达式比较大小
!= 不等于
// cmpflt.c -- 浮点数比较
#include <math.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
const double ANSER = 3.14159;
double response;
printf("PI = ?\n");
scanf("%lf", &response);
while (fabs(response - ANSER) > 0.001)
{
/* code */
printf("Try again!\n");
scanf("%lf", &response);
}
printf("OK!\n");
return 0;
}
result:
PI = ?
3.14
Try again!
3.12
Try again!
3.1415
OK!
比较浮点数时尽量只是用大于和小于符号< >,因为浮点数的舍入误差会导致逻辑上应该相等的两数却不相等。使用 math.h 头文件的 fabs() 函数可以方便的比较浮点数的大小。
真假值:非0值就是真,0为假
所以可以简写条件语句:
while(goats!=0);
为:
while(goats);
-
新的_Bool 类型
_Bool 类型的变量只能存储1(真)或0(假),如果把其他非零数值赋给 _Bool 类型的变量,该变量会被设置为1,这反映了C把所有非零值都视为真。
// boolean.c -- 使用_Bool 类型的变量 #include <stdio.h> int main(int argc, char const *argv[]) { long num; long sum = 0L; bool input_is_good; printf("please enter an integer to be sum:\n"); input_is_good = scanf("%ld", &num) == 1; while (input_is_good) { /* code */ sum = sum + num; printf("please enter an integer to be sum:\n"); input_is_good = scanf("%ld", &num) == 1; } printf("sum = %ld\n",sum); return 0; }
result:
please enter an integer to be sum: 12 please enter an integer to be sum: 21 please enter an integer to be sum: q sum = 33
这里我使用的是 bool 才可以。如果不支持可以使用C99的 stdbool.h 头文件,让头文件让 bool 成为 _Bool 的别名,或者使用int替换。
6.4 不确定循环和计数循环
是否确定循环次数?
计数循环:
// sweetie1.c -- 计数循环
#include<stdio.h>
int main(int argc, char const *argv[])
{
const int NUMBER = 22;
int count = 1;
while (count<=NUMBER)
{
/* code */
printf("Go!\n");
count++;
}
return 0;
}
6.5 for循环
但是这种写法比较low,容易忘记初始化计数器,看看下面的for循环:
// sweetie2.c -- 使用for循环的计数循环
#include<stdio.h>
int main(int argc, char const *argv[])
{
const int NUMBER = 22;
int count;
for(count=1;count<=NUMBER;count++){
printf("Go! %d\n",count);
}
return 0;
}
result:
Go! 1
Go! 2
Go! 3
Go! 4
Go! 5
Go! 6
Go! 7
Go! 8
Go! 9
Go! 10
Go! 11
Go! 12
Go! 13
Go! 14
Go! 15
Go! 16
Go! 17
Go! 18
Go! 19
Go! 20
Go! 21
Go! 22
for后面的圆括号有3个表达式,分别用两个分号分隔,第一个表达式是初始化,只会在for循环开始的时候执行一次,第二个表达式是测试条件,在执行循环前先对表达式求值,如果条件表达式为假,循环结束。第三个表达式执行更新,for中的表达式都是完整的表达式,所以每个表达式的副作用(如:递增变量即改变变量的值)都会在下一条表达式之前生效。
// for_cude.c -- for 循环创建一个立方表
#include<stdio.h>
int main(int argc, char const *argv[])
{
int num;
printf(" n cubed\n");
for(num=1;num<=6;num++){
printf("%5d %5d\n",num,num*num*num);
}
return 0;
}
result:
n cubed
1 1
2 8
3 27
4 64
5 125
6 216
接下来看看for的其他用法:
- 递减运算符
// for_down.c
#include<stdio.h>
int main(int argc, char const *argv[])
{
int secs;
for(secs = 5;secs>0;secs--){
printf("%d seconds!\n",secs);
}
printf("OK!\n");
return 0;
}
result:
5 seconds!
4 seconds!
3 seconds!
2 seconds!
1 seconds!
OK!
- 计数器递增数字可以任意改变
// for_13s.c
#include <stdio.h>
int main(int argc, char const *argv[])
{
int n;
for (n = 2; n < 60; n = n + 13)
{
/* code */
printf("%d \n", n);
}
return 0;
}
result:
2
15
28
41
54
- 可以用字符代替数字计数
// for_char.c
#include <stdio.h>
int main(int argc, char const *argv[])
{
char ch;
for (ch = 'a'; ch <= 'z'; ch++)
{
/* code */
printf("The ASCII value for %c is %d\n",ch,ch);
}
return 0;
}
result:
The ASCII value for a is 97
The ASCII value for b is 98
The ASCII value for c is 99
The ASCII value for d is 100
The ASCII value for e is 101
The ASCII value for f is 102
The ASCII value for g is 103
The ASCII value for h is 104
The ASCII value for i is 105
The ASCII value for j is 106
The ASCII value for k is 107
The ASCII value for l is 108
The ASCII value for m is 109
The ASCII value for n is 110
The ASCII value for o is 111
The ASCII value for p is 112
The ASCII value for q is 113
The ASCII value for r is 114
The ASCII value for s is 115
The ASCII value for t is 116
The ASCII value for u is 117
The ASCII value for v is 118
The ASCII value for w is 119
The ASCII value for x is 120
The ASCII value for y is 121
The ASCII value for z is 122
- 可以省略一个或多个表达式,只要在循环中包含结束循环的语句即可。
- 第一个表达式不一定是给变量赋值,也可以使用printf() 语句,但是记住这个表达式只能执行一次。
// for_show.c
#include<stdio.h>
int main(int argc, char const *argv[])
{
int num =0;
for(printf("Keep going!\n");num!=6;){
scanf("%d",&num);
}
printf("This is right!\n");
return 0;
}
result:
Keep going!
1
2
3
56
6
This is right!
6.6 其他赋值运算符
简写运算符,注意优先级。
6.7 逗号运算符
逗号运算符扩展了for循环的灵活性,例如:
// postage.c -- 一类邮资
#include <stdio.h>
int main(int argc, char const *argv[])
{
const int FIRST_OZ = 46;
const int NEXT_OZ = 20;
int ounces, cost;
printf("ounces cost\n");
for (ounces = 1, cost = FIRST_OZ; ounces <= 16; ounces++, cost += NEXT_OZ)
{
printf("%5d $%4.2f\n", ounces, cost / 100.0);
}
return 0;
}
result:
ounces cost
1 $0.46
2 $0.66
3 $0.86
4 $1.06
5 $1.26
6 $1.46
7 $1.66
8 $1.86
9 $2.06
10 $2.26
11 $2.46
12 $2.66
13 $2.86
14 $3.06
15 $3.26
16 $3.46
该程序在初始化表达式和更新表达式中使用了逗号运算符。逗号的存在让两个变量都初始化了,以及更新表达式有了的两个改变。
逗号运算符有两个性质:
- 首先,它保证了被它分隔的表达式从左往右求值,因为逗号就是一个序列点,所以逗号左侧项的所有副作用都在程序执行逗号右侧前生效。
- 注意不要犯这样的错误:
num = 249, 500;
因为这样等于
#include <stdio.h>
int main()
{
int mice, num;
num = 249, 500;
printf("%d\n",num);
num = 249;
500;
printf("%d\n",num);
return 0;
}
result:
249
249
6.8 出口条件循环do while
for 和 while 循环都是入口条件循环,在迭代之前检查测试条件,do while 是出口条件循环,先迭代后检查测试条件,这样至少保证执行一次。有时do while 可以比while 更简洁一些。
6.9 如何选择循环
当循环设计初始化和更新变量时,用for循环比较合适,但是有些情况用while更好,如循环输入:
while(scanf("%ld",&num)==1)
6.10 嵌套循环
嵌套循环常用语按行和列显示数据
// rows1.c -- 使用嵌套循环
#include<stdio.h>
#define ROW 6
#define CHARS 10
int main(int argc, char const *argv[])
{
int row;
char ch;
for(row = 0;row<ROW;row++){
for(ch='A';ch<('A'+CHARS);ch++){
printf("%c",ch);
}
printf("\n");
}
return 0;
}
result:
ABCDEFGHIJ
ABCDEFGHIJ
ABCDEFGHIJ
ABCDEFGHIJ
ABCDEFGHIJ
ABCDEFGHIJ
嵌套循环有外层循环和内层循环。每次外层循环迭代要先执行完所有的内层循环才能执行下一个外层循环。
// rows2.c -- 依赖外部循环的嵌套循环
#include <stdio.h>
int main(int argc, char const *argv[])
{
const int ROWS = 6;
const int CHARS = 6;
int row;
char ch;
for (row = 0; row < ROWS; row++)
{
for (ch = ('A' + row); ch < ('A' + CHARS); ch++)
{
printf("%c", ch);
}
printf("\n");
}
return 0;
}
result:
ABCDEF
BCDEF
CDEF
DEF
EF
F
因为每次迭代都要把row的值和’A’ 相加,所以ch在每一行都被初始化为不同的字符,然而,测试条件并没有改变,所以每行依旧是F结尾,这使得每一行打印的字符都比上一行少一个。
6.11 数据简介
循环要经常使用数组。通过整数下标访问数组中单独的元素。声明数组:
float debts[20];
声明debts是一个含有20个元素的数组,且每个元素都可以存储float类型的值。数组元素的编号从0开始。使用数组元素和使用同类型的变量一样,例如可以这样把值读入指定的元素中。
scanf("%f",&debts[4]);
注意取地址符 &
// scores_in.c -- 使用循环处理数组
#include <stdio.h>
#define SIZE 10
#define PAR 72
int main(int argc, char const *argv[])
{
int index, score[SIZE];
int sum = 0;
float average;
printf("输入 %d 高尔夫分数:\n", SIZE);
for (index = 0; index < SIZE; index++)
{
scanf("%d", &score[index]);
}
for (index = 0; index < SIZE; index++)
{
/* code */
printf("%5d",score[index]);
}
printf("\n");
for(index=0;index<SIZE;index++){
sum += score[index];
}
average = (float)sum/SIZE;
printf("sum = %d,average = %.2f\n",sum,average);
printf("average - PAR = %.0f\n",average-PAR);
return 0;
}
result:
杈撳叆 10 楂樺皵澶垎鏁?
99 95 109 105 100
96 98 93 99 97 98
99 95 109 105 100 96 98 93 99 97
sum = 991,average = 99.10
average - PAR = 27
虽然输入了11个数字,但是由于循环只读取10个数字,剩下一个便去了缓冲区。输入以空白分隔。
复现输入结果的编程习惯不错
6.12 使用函数返回值的循环示例
// power.c -- 计算数的整数幂
#include <stdio.h>
double power(double n, int p); // ANSI 函数原型
int main(int argc, char const *argv[])
{
double x, xpow;
int exp;
printf("输入底数和指数:\n");
while (scanf("%lf%d", &x, &exp) == 2)
{
/* code */
xpow = power(x, exp);
printf("%.3g 为底,%d 为指数 的结果是 %.5g\n", x, exp, xpow);
}
printf("OK!\n");
return 0;
}
double power(double n, int p)
{
double pow = 1;
int i;
for (i = 1; i <= p; i++)
{
pow *= n;
}
return pow;
}
result:
输入底数和指数:
1.2 12
1.2 为底,12 为指数 的结果是 8.9161
2
16
2 为底,16 为指数 的结果是 65536
q
OK!
scanf() 若成功读取两个值就返回2;函数可以先声明再定义。
6.13 关键概念
6.14 本章小结
6.15 复习题
数组名就是地址,scanf() 使用时不用加取地址符 &