本系列博客用于记录学习浙江大学翁恺老师的C语言程序设计,系列笔记链接如下:
C语言程序设计学习笔记:P1-程序设计与C语言
C语言程序设计学习笔记:P2-计算
C语言程序设计学习笔记:P3-判断
C语言程序设计学习笔记:P4-循环
C语言程序设计学习笔记:P5-循环控制
C语言程序设计学习笔记:P6-数据类型
C语言程序设计学习笔记:P7-函数
C语言程序设计学习笔记:P8-数组
C语言程序设计学习笔记:P9-指针
C语言程序设计学习笔记:P10-字符串
C语言程序设计学习笔记:P11-结构类型
C语言程序设计学习笔记:P12-程序结构
C语言程序设计学习笔记:P13-文件
C语言程序设计学习笔记:P14-链表
一、循环
1.1 循环
问题引出
现在我有一个题目:程序读入一个4位以下(含4位)的正整数,然后输出这个整数的位数。如输入352,输出3。
人的方式:眼睛一看就知道了,我们一眼就可以看出352是三位数。
计算机的方式:计算机最不擅长的就是一眼看出来结果,因此一个合适的方法便是判断数的范围来决定它的位数。由于352∈[100,999],因此352是三位数。因此,我们写出代码如下。通过级联的if来依次判断输入的数字是否大于最大的三位数、最大的两位数、最大的一位数,从而得到数字的位数。
代码实现
#include <stdio.h>
int main()
{
int x;
int n=1;
scanf("%d", &x);
if (x > 999)
{
n=4;
}
else if (x > 99)
{
n = 3;
}
else if (x > 9)
{
n = 2;
}
else
{
n = 1;
}
printf("%d\n", n);
return 0;
}
我们进行测试,可以看出结果正确。
代码的局限性
如果输入任意范围的正整数怎么办?如果是5位数、6位数、7位数…,那我们得一直增加if的个数,这如何才是个头?我们知道,如果让你看到352这个数,可以一眼就知道是三位数。但是如果人看到123812843267518273618273612675317这个数,能一眼看出是多少位吗?答案是明显不能的,那我们就会去数数,一路数过来。
关于数数,人是怎么做的:
人数数的过程是这样的:从左往右数,一次划掉一个数字,并将位数加1。举个例子,输入352,那么我们首先划去第一位的3,保留后面的52并将位数加1。
计算机怎么做:
计算机该怎样实现这个功能呢?要完成上面操作我们可以让352%100=52
,此时成功划去3并保留52,接着重复上面操作。那么,对于刚才那个非常大的数,要划去第一个数,需要123812843267518273618273612675317%100000000000000000000000000000000=23812843267518273618273612675317
问题便是怎么得到那个100000000000000000000000000000000?
思路:
可以看出从左往右划不现实,无法得知应该去模哪一个数。如果换一下,从右边开始划,直到没数可以划,同时在这个过程中计数,这样就能够得到正确的结果。
123812843267518273618273612675317 / 10 =12381284326751827361827361267531
12381284326751827361827361267531 / 10 = 1238128432675182736182736126753
1238128432675182736182736126753 / 10 = 123812843267518273618273612675
…
代码实现
int x;
int n = 0;
scanf("%d", &x);
n++;
x /= 10;
if < x>0)
{
n++;
x/=10;
if (x > 0)
{
n++;
x/=10;
if ...
}
}
printf("%d\n", n);
解决方案
我们写出代码,可以看出需要一直判断划完后的数是否为0,如果不为0就需要继续。可以看出这事还是没完没了。那么我们也许需要这个东西:while。当x大于0时,我们需要一直去做这个事。
代码实现
#include <stdio.h>
int main()
{
int x;
int n=0;
scanf("%d", &x);
n++;
x /= 10;
while (x > 0)
{
n++;
x /= 10;
}
printf("%d\n", n);
return 0;
}
我们进行测试,可以看出结果正确。
1.2 while循环
单看语法的话,while和if非常相似,只用把判断条件前面的if改为while即可。不同的是,括号里面的操作if只执行1次,而while要重复地执行。
//if:
if (x > 0)
{
x /= 10;
n++;
}
//while
while (x > 0)
{
x /= 10;
n++;
}
while的流程图可以如下图所示:
括号里面反复执行的叫做循环体。循环体内一定要有改变条件的机会,不然会一直在循环里面出不来。如下面这个例子,由于x大于0且x的值不会改变,因此n会一直加下去。
int x = 10;
int n = 0;
while (x > 0)
{
n++;
}
printf("%d", n);
在上面一节我们写过一段判断整数位数的代码。while前面的两行代码与循环体内的代码一样,那我们能够把外面的代码放进来吗?
n++;
x /= 10;
while (x > 0)
{
n++;
x /= 10;
}
我们来试一试。
#include <stdio.h>
int main()
{
int x;
int n=0;
scanf("%d", &x);
while (x > 0)
{
n++;
x /= 10;
}
printf("%d\n", n);
return 0;
}
当我们输入0时,结果为0,明显错误。因为这个程序需要一些特殊的代码来判断输入为0时的情况,即必须先执行一次循环体内的操作。
1.3 do-while 循环
问题引出
上面判断整数位数的代码(包括输入为0)的算法流程应该是怎么样的:
①用户输入x;
②初始化n为0;
③x = x / 10,去掉个位;
④n ++;
⑤如果x>0,回到3;
⑥否则n就是结果。
我们有没有更好的结构使得先将事情做一轮然后再去判断呢?这就是我们的do-while循环。在进入循环的时候不做检查,而是在执行完一轮循环体的代码之后,再来检查循环的条件是否满足。如果满足则继续下一轮循环,不满足则结束循环。do-while的语法如下,注意不要忘了while后面的那个分号。
do
{
<循环体语句>
} while ( <循环条件> );
其流程图如下图所示:
do-while循环和while循环很像,区别是do-while在循环体执行结束的时候才来判断条件。也就是说,无论如何循环都会执行至少一遍,然后再来判断条件。与while循环相同的是,条件满足时执行循环,条件不满足时结束循环。因此,判断整数位数的代码可以写成下面这样。
do-while实现判断整数位数代码
#include <stdio.h>
int main()
{
int x;
int n=0;
scanf("%d", &x);
do
{
n++;
x /= 10;
}while (x > 0);
printf("%d\n", n);
return 0;
}
小测验
1、while循环的条件满足的时候循环继续,而do-while的条件满足的时候循环就结束了。
答案:错误
2、以下代码片段执行结束后,变量i的值是多少?
答案:0
3、以下代码片段执行结束后,变量i的值是多少?
答案:21
二、循环应用
2.1 循环计算
题目1:求一个数是2的多少次幂。(设置输入的这个数是2的整数次幂)
#include <stdio.h>
int main()
{
int x=64;
int ret = 0;
int t = x; //使用一个变量保存x,因为后面需要打印原始的x,后面x的值会改变
while (x > 1)
{
ret++;
x /= 2;
}
printf("log2 of %d is %d\n", t, ret);
return 0;
}
题目2:我们再看个例子,下面我有一段代码。
#include <stdio.h>
int main()
{
int count = 100;
while ( count>= 0 ) {
count--;
printf("%d ", count);
}
printf("发射\n");
return 0;
}
看着这段代码,我们有以下几个小问题:
①这个循环需要执行多少次?
②循环停下来的时候,有没有输出最后的0?
③循环结束后,count的值是多少?
小套路: 如果要模拟运行一个很大次数的循环,可以模拟较少的循环次数,然后作出推断。我们先将count设置为3来看看。可以看出循环执行了4次,输出了最后的0,循环结束后count的值为-1。
2.2 猜数游戏
题目
现在我们想做一个猜数游戏。让计算机来想一个数,然后让用户来猜,用户每输入一个数,就告诉它是大了还是小了,直到用户猜中为止,最后还要告诉用户它猜了多少次。
思路
看到这个问题我们可以知道因为需要不断重复让用户猜,所以需要用到循环,核心重点是循环的条件,尤其是循环终止的条件。我们用文字描述出我们设计代码的思路:
1、计算机随机想一个数,记在变量number里;
2、一个负责计次数的变量count初始化为0;
3、让用户输入一个数字a;
4、count递增(加一);
5、判断a和number的大小关系,如果a大,就输出“大”;如果a小就输出“小”;
6、如果a和number是不相等的(无论大还是小),程序转回到第3步;
7、否则,程序输出“猜中”和次数,然后结束。
代码实现
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
/*
需要使用stdlib.h和time.h两个库。
每次召唤rand()就得到一个随机的整数。
加上srand(time(0))是为了让得到的随机整数更加真实。
将 随机数%n 会得到一个∈[0, n-1]的整数。根据这个来产生1-100之间的数。
*/
srand(time(0));
int number = rand()%100+1;
int count = 0;
int a = 0;
printf("我已经想好了一个1到100之间的数。");
//用do-while比较好,因此一定会执行一次
do {
printf("请猜这个1到100之间数:");
scanf("%d", &a);
if ( a > number ) {
printf("你猜的数大了。");
} else if ( a < number ) {
printf("你猜的数小了。");
}
count ++;
} while (a != number);
printf("太好了,你用了%d次就猜到了答案。\n", count);
return 0;
}
运行结果
我们来测试一下,可以看出结果正确。实际上,猜100内的整数最多用7次,具体原因我们后面分析。
2.3 算平均数
题目
让用户输入一系列的正整数,最后输入-1表示输入结束,然后程序计算出这些数字的平均数,输出输入的数字的个数和平均数。
思路
①需要一个记录读到的整数的变量
②平均数要怎么算?只需要每读到一个数,就把它加到一个累加的变量里,到全部数据读完,再拿它去除读到的数的个数就可以了。
③需要一个变量记录累加的结果
④需要一个变量记录读到的数的个数
程序的算法流程如下图所示:
代码实现
#include <stdio.h>
int main()
{
int sum = 0;
int count = 0;
int number;
scanf("%d", &number);
while ( number != -1 ) {
sum += number;
count ++;
scanf("%d", &number);
}
printf("The average is %f.\n", 1.0 * sum / count);
return 0;
}
运行结果
我们进行测试,可以看出结果正确。
2.4 整数求逆
题目
输入一个正整数,输出逆序的数。如输入48102,输出20184。这个问题会存在两种情况。①对于一些以0结束的正整数,如700,输出7。②对于一些以0结束的正整数,如700,输出007
第一种情况代码实现
#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;
}
进行测试,可以看出结果正确。
第二种情况代码实现
#include <stdio.h>
int main()
{
int x;
scanf("%d", &x);
int digit;
int ret = 0;
while ( x> 0 ) {
digit = x%10;
printf("%d", digit);
ret = ret*10 + digit;
x /= 10;
}
return 0;
}
进行测试,可以看出结果正确。
小测验
1、以下哪种运算能从变量x中取得十进制最低位的数字
A. x / 10
B. x % 10
C. x * 10
D. 10 / x
答案:B
2、当需要累加一些值的时候,用来记录累加结果的变量应该被初始为:
答案:0