C语言程序设计(三)
循环
while循环

- 如果我们把while翻译作"当",那么一个while循环的意思就是:当条件满足时,不断地重复循环体内的语句。(即先判断后循环)
- 循环执行之前判断是否继续执行循环,所以有可能循环一次也没有执行(即条件不成立不进入循环);
- 条件成立是循环继续的条件。
while(<循环条件>){
<循环体语句>
}
tips:循环就意味着它要考虑到更多的情况是否在循环内进行,所以要进行验证;但人工验证的同时不可能全部都试一遍,因此要从一般到特殊,尽可能的包含到。测试程序常使用边界数据,如有效范围两端的数据、特殊的倍数等。
do-while循环

do
{
<循环体语句>
}while(<循环条件>);
- 在进入循环的时候不做检查,而是在执行完一轮循环体的代码之后,再来检查循环的条件是否满足,如果满足则继续下一轮循环,不满足则循环结束。(即先循环后判断)
- 注意: do-while 循环的 while 循环后面要有分号
; 来结束语句。
区别两种循环:do-while 循环和 while 循环很像,区别在于 do-while 循环的循环体执行结束的时候才来判断条件。也就是说,无论如何,循环都会执行至少一遍,然后再来判断条件。与while循环相同的是,条件满足时执行循环,条件不满足时循环结束。
循环应用1
循环计算
计算
l
o
g
2
x
log_2x
log2?x:
#include <stdio.h>
int main()
{
int x;
int ret = 0;
scanf("%d",&x);
int t = x;
while ( x > 1 ){
x /= 2;
ret++;
}
printf("log2 of %d is %d.", t, ret);
return 0;
}
计数循环
#include <stdio.h>
int main()
{
int count = 100;
while ( count >= 0){
count--;
printf("%d ", count);
}
printf("发射\n");
return 0;
}
- 这个循环需要执行多少次? (101)
- 循环停下来的时候,有没有输出过0? (有)
- 循环结束后,count的值是多少? (-1)
tips: 如果要模拟运行一个很大次数的循环,可以模拟较少的循环次数,然后做出推断。
比如这个题,count = 100;我们可以把count先设为3,然后列举出输出结果: 2 1 0 -1;即输出为4次,即循环执行4次;有输出0;最后输出为-1。进而可以推断执行count + 1次。
猜数游戏
让计算机来想一个数,然后让用户来猜,用户每输入一个数,就告诉它是大了还是小了,直到用户猜中为止,最后还要告诉用户它猜了多少次。
(因为需要不断重复让用户猜,所以需要用到循环在实际写出程序之前,我们可以先用文字描述程序的思路核心重点是循环的条件,人们往往会考虑循环终止的条件)
思路:
-
计算机随机出一个数,记在变量number里; -
一个负责记次数的变量count初始化为0; -
让用户输入一个数字a; -
count递增加一; -
判断a和number的大小关系,如果是大,就输出大,如果是小,就输出小; -
如果a和number是不相等的;程序回到第三步(3.); -
否则,程序输出“猜中”和次数,然后结束。 循环条件是a和number不相等
srand(time(0));
int number = rand()%100+1;
int count = 0;
int a = 0;
printf("我已经想好了一个1到100之间的数。");
do {
printf("请猜这个1到100之间数:");
scanf("%d", &a);
count ++;
if ( a > number ) {
printf("你猜的数大了。");
} else if ( a < number ) {
printf("你猜的数小了。");
}
} while (a != number);
printf("太好了,你用了%d次就猜到了答案。\n", count);
拓展: 随机数
- 每次调用rand()函数就可以得到一个随机的整数
- x % n (取余) 的结果是[0, n-1]的一个整数
算平均数
-
sum =
∑
i
=
1
n
x
i
n
\frac{\sum_{i = 1}^n{x_i}}{n}
n∑i=1n?xi?? -
让用户输入一系列的正整数,最后输入
?
1
-1
?1 表示用户输入结束,然后程序算出这些数字的平均数,输出输入的数字的个数和平均数 -
变量->算法->流程图->程序
变量
- 一个记录读到的整数变量
- 平均数要怎么计算?
- 只需要每读到一个数,就把它加到一个累加的变量里,到全部数据读完,再拿它去除读到的数的个数就可以了
- 一个变量记录累加的结果,一个变量记录读到的数的个数(两个变量)
算法
- 初始化变量sum和count为0;
- 读入number;
- 如果 number 不是 -1,则将 number 加入 sum,并将 count+1,回到2;
- 如果 number 是-1,则计算和打印出 sum/count (注意换成浮点数来计算)
流程图

#include <stdio.h>
int main()
{
int number ;
int sum = 0;
int count = 0;
scanf("%d",&number);
while (number != -1){
sum += number;
count++;
scanf("%d",&number);
}
printf("平均数为%f\n",1.0*sum/count);
return 0;
}
整数分解
- 一个整数是由1至多位数字组成的,如何分解出整数的各个位上的数字,然后加以计算
-
对一个整数做%10的操作,就得到它的个位数; -
对一个整数做/10的操作,就去掉了它的个位数; -
然后再对2的结果做%10,就得到原来数的十位数; -
以此类推。
举个栗子:
一个5位数的整数12345,如何将它的各个位上的数字进行分解?
- 先将12345对10取余,即12345%10 = 5,得到个位上的数;
- 再把12345用10来整除,即12345/10 = 1234,就去掉个位;得到四位数;
- 再对2的结果用10取余,即1234%10=4,得到十位上的数;
- 再把1234用10来整除,即1234/10 = 123,得到三位数;
- 以此类推,分别求出1,2,3,4,5;
- 这样就得到整数的分解
- 对于计算机来说,很多都是逻辑处理,它并不能像人类一样正常一眼就能看出几位数中的各个位上的数是多少,它只知道这个数的大小,并能对其进行运算。
应用:数的逆序
#include <stdio.h>
int main()
{
int x;
scanf("%d",&x);
int digit;
int ret = 0;
while(x>0){
digit = x%10;
ret = ret*10 + digit;
printf("x = %d, digit = %d, ret = %d\n", x, digit, ret);
x /= 10;
}
printf("%d",ret);
return 0;
}
第三种循环
for循环
for( <初始动作(条件)>; <循环继续的条件>; <每一轮循环结束后要做的动作> ){
<循环体语句>;
}
- for循环像一个计数循环:设定一个计数器,初始化它,然后在计数器到达某值之前,重复执行循环体,而每执行一轮循环,计数器值以一定步进行调整,比如加1或者减1。
for = 对于 (可以将for 当成对于 )
- for ( count=10; count>0; count-- )
- 就读成:“
对于 一开始的count=10,当count>0时,重复做循环体,每一轮循环在做完循环体内语句后,使得count–。“ - tips: 做求和的程序时,记录结果的变量应该初始化为0,而做求积的变量时,记录结果的变量应该初始化为1。循环控制变量i 只在循环里被使用了,在循环外面它没有任何用处。因此,我们可以把变量i 的定义写到for语句里面去。

for和while对比
例如:
int i;
for (int i=1; i<=n; i++){
fact *= i;
}
int i=1;
while (i<=n){
fact *=i;
i++;
}
-
任何一个for循环都可以改成while循环 -
for(初始动作; 条件; 每轮的动作){ }
- for中的每一个表达式都是可以省略的
- for(; 条件; ) == while (条件)
- 但是两个分号
; 是不可省略, 是为了分隔开条件的
循环次数
- for (i = 0; i<n; i++)
- 则循环的次数是n,而循环结束以后,i的值还是n。循环的控制变量i,是选择从0开始还是从1开始,是判断i<n还是判断i<=n,对循环的次数,循环结束后变量的值都有影响。
- 如果是for(i=1; i<n; i++), 循环次数为n-1;循环结束后i的值为n
- 如果for(i=1; i<=n; i++), 循环次数为n; 循环结束后i的值为n+1
举个栗子:
#include <stdio.h>
int main()
{
int i;
for( i=1; i<=5; i++){
printf("i=%d\n",i);
}
printf("\n最后i=%d\n",i);
return 0;
}
Tips for loops:
- 如果有固定次数,用for循环
- 如果必须执行一次,用do-while循环
- 其他情况用while循环
另外注意 ++i 和 i++在for语句中是不做区别的, 都是用i加1后的值, 本质上都是取运算后 i 的值
i++和++i的区别详情请见: https://blog.csdn.net/honorzoey/article/details/112167850?spm=1001.2014.3001.5501
循环控制
素数判断
- 判断一个数是不是素数(只能被1和自己整除的数,不包括1)
#include <stdio.h>
int main()
{
int x;
scanf("%d",&x);
int isPrime = 1;
int i = 2;
while (i<x){
if (x%i == 0){
isPrime = 0;
}
i++;
}
if (isPrime == 1){
printf("%d是素数\n",x);
}else{
printf("%d不是素数\n",x);
}
return 0;
}
可以用for循环
#include <stdio.h>
int main()
{
int x;
scanf("%d",&x);
int isPrime = 1;
int i;
for (i=2;i<x;i++){
if (x%i == 0){
isPrime = 0;
}
}
if (isPrime == 1){
printf("%d是素数\n",x);
}else{
printf("%d不是素数\n",x);
}
return 0;
}
break 和 continue
-
break : 跳出循环 (和在switch -case中的用法一样,跳出语句) -
回顾: switch语句可以看作是?种基于计算的跳转,计算控制表达式的值后,程序会跳转到相匹配的case(分支标号)处。分支标号只是说明switch内部位置的路标,在执行完分支中的最后一条语句后,如果后面没有break,就会顺序执行到下面的case里去,直到遇到一个break,或者switch结束为止
-
continue : 跳过循环这一轮剩下的语句进入下一轮循环(如果没下一轮循环则继续下个语句执行) -
break和continue只能对它们所在的那层循环起作用。
#include <stdio.h>
int main()
{
int x;
scanf("%d",&x);
int isPrime = 1;
int i;
for (i=2;i<x;i++){
if (x%i==0){
isPrime = 0;
break;
}
}
if (isPrime == 1){
printf("%d是素数\n",x);
}else{
printf("%d不是素数\n",x);
}
return 0;
}
#include <stdio.h>
int main()
{
int x;
scanf("%d",&x);
int isPrime = 1;
int i;
for (i=2;i<x;i++){
if (x%i==0){
isPrime = 0;
continue;
}
printf("%d\n",x);
}
if (isPrime == 1){
printf("%d是素数\n",x);
}else{
printf("%d不是素数\n",x);
}
return 0;
}
断点调试一下可以看到break和continue的执行情况

循环的嵌套
#include <stdio.h>
int main()
{
int x;
int i;
for (x=1; x<=100; x++){
int isPrime = 1;
for (i=2;i<x;i++){
if (x%i==0){
isPrime = 0;
break;
}
}
if (isPrime == 1){
printf("%d ", x);
}
}
return 0;
}
凑硬币
- 如何用1角、2角和5角的硬币凑出10元以下的金额呢?
#include <stdio.h>
int main()
{
int x;
int one, two, five;
scanf("%d",&x);
for( one = 1; one < x*10; one++){
for(two = 1; two < x*10/2; two++){
for(five = 1; five < x*10/5; five++){
if(one+2*two+5*five == x*10){
printf("%d个1角加%d个2角加%d个5角等于%d元\n",one,two,five,x);
}
}
}
}
return 0;
}
#include <stdio.h>
int main()
{
int x;
int one, two, five;
for( x = 1; x <= 10; x++){
int exit = 0;
for( one = 1; one < x*10; one++){
for(two = 1; two < x*10/2; two++){
for(five = 1; five < x*10/5; five++){
if(one+2*two+5*five == x*10){
printf("%d个1角加%d个2角加%d个5角等于%d元\n",one,two,five,x);
exit = 1;
break;
}
}
if (exit == 1) break;
}
if(exit == 1) break;
}
}
return 0;
}
注意: 和上次的局部变量和全局变量的问题 int exit = 0;要设置在第一层for循环里面
break和continue只能对它们所在的那层循环起作用。
#include <stdio.h>
int main()
{
int x;
int one, two, five;
for( x = 1; x <= 10; x++){
for( one = 1; one < x*10; one++){
for(two = 1; two < x*10/2; two++){
for(five = 1; five < x*10/5; five++){
if(one+2*two+5*five == x*10){
printf("%d个1角加%d个2角加%d个5角等于%d元\n",one,two,five,x);
goto out;
}
}
}
out:
break;
}
}
}
也能达到只用break的做法
goto
{
{
{
{
...;
goto <命名1>;
}
}
}
<命名1>:
<所要做的动作>;
}
- 跟函数跳转类似,就好比如重新定义一个"跳转"函数,当语句执行到goto语句时,跳转到你想要跳转的地方
循环应用2
求和
f
(
n
)
=
1
+
1
2
+
1
3
+
1
4
+
.
.
.
+
1
n
f(n) = 1+\frac{1}{2}+\frac{1}{3}+\frac{1}{4}+...+\frac{1}{n}
f(n)=1+21?+31?+41?+...+n1?
#include <stdio.h>
int main()
{
int n;
int i;
double ret = 0.0;
scanf("%d", &n);
for (i=1; i<=n; i++){
ret += 1.0/i;
}
printf("%f\n",ret);
}
tips: 除了double、float可以转换成浮点数之外,也可以用一个int类型的数乘于1.0就可以转化为float型(浮点数)。
f
(
n
)
=
1
?
1
2
+
1
3
?
1
4
+
.
.
.
+
1
n
f(n)=1-\frac{1}{2}+\frac{1}{3}-\frac{1}{4}+...+\frac{1}{n}
f(n)=1?21?+31??41?+...+n1?
#include <stdio.h>
int main()
{
int n;
int i;
double ret = 0.0;
int sign = 1;
scanf("%d",&n);
for (i=1; i<=n; i++){
ret += 1.0*sign/i;
sign = -sign;
}
printf("%f\n",ret);
return 0;
}
其中可以直接把int sign = 1;换成 double sign = 1.0; 这样下面就不用1.0*sign
正序分解整数
注: 取整数的最高位的做法: n位数整除
1
0
n
10^n
10n, 得到最高位数,即
x
÷
1
0
n
x \div 10^n
x÷10n; 取除了最高位之外的数: 对 n 位数与
1
0
n
10^n
10n 取余, 即
x
%
1
0
n
x\%10^n
x%10n 得到n-1位数。
逆序输出:先取余,后整除;
d = x % 10
x / =10
正序输出: 先整除后取余.
d = x / mask
x %= mask
而mask就是
1
0
n
10^n
10n, n随着位数的变化而变化
#include <stdio.h>
int main()
{
int x;
scanf("%d",&x);
int mask = 1;
int t = x;
while ( t>9 ){
t /= 10;
mask *= 10;
}
do{
int d = x / mask;
printf("%d", d);
if (mask>9){
printf(" ");
}
x %= mask;
mask /= 10;
}while(mask > 0);
printf("\n");
return 0;
}
可以用浮点运算函数
#include<math.h>
mask = pow(10,n)
但是在计算机中比较慢
最大公约数
- 输入两个数 a 和 b,输出它们的最大公约数
- 输入:12 18
- 输出:6
两种算法:枚举和辗转相除法
枚举
在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。 是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。
- 设t为2;
- 如果u和v都能被t整除,则记下这个t
- t加1后重复第2步,直到t等于u或v;
- 那么,曾经记下的最大的可以同时整除u和v的t就是gcd
#include <stdio.h>
int main()
{
int a,b;
int min;
scanf("%d %d", &a, &b);
if (a<b){
min = a;
}else{
min = b;
}
int ret = 0;
int i;
for(i=1; i<min; i++){
if (a%i == 0){
if(b%i == 0){
ret = i;
}
}
}
printf("%d和%d的最大公约数是%d。\n", a, b, ret);
return 0;
}
辗转相除法
欧几里得算法又称辗转相除法,是指用于计算两个非负整数a,b的最大公约数。计算公式 gcd(a,b) = gcd(b,a mod b)。
-
如果b等于0,计算结束,a就是最大公约数; -
否则,计算a除以b的余数,让a等于b,而b等于那个余数; -
回到第一步。
#include <stdio.h>
int main()
{
int a, b;
int t;
scanf("%d %d",&a,&b);
while(b!=0){
t = a%b;
a = b;
b = t;
printf("a=%d,b=%d,t=%d\n",a, b, t);
}
printf("gcd = %d\n",a);
return 0;
}
|