3.6 总结
3.6.1 for循环与while循环对比
可以发现在while循环中依然存在循环的三个必须条件,但是由于风格的问题,使得三个部分很可能偏离较远,这样查找修改不够集中、方便。所以,for循环的风格更胜一筹。for循环使用的频率也最高。
3.6.2 while 与 do…while的区别
- while :先
循环条件判断
再执行循环操作
- do…while :先
执行循环操作
再循环条件判断
3.6.3 拓展练习
❓ 问下列语句循环多少次
#include <stdio.h> int main() { int i = 0; int k = 0; for (i = 0, k = 0; k = 0; i++, k++) { k++; } return 0; }
✏️ 循环0
次。 k = 0 是 赋值操作。而在C语言当中,0代表false,1代表true,因此 k = 0 是将 0 赋值给 k,这是k的值就为0了,因此不执行循环。
4.章节练习
拓展方案是算法优化,感兴趣的可以琢磨一下。如果现在看不懂也没关系,记得以后学到了某个地方突然想起了一定要回来看它,就像那个女孩依旧在原地等你,不要辜负她❤️,她其实愿意等你好久好久。
4.1 计算阶乘
🏠 原题链接: https://www.luogu.com.cn/problem/P5739
4.1.1 题目描述
求 n!,也就是 1×2×3⋯×n。
4.1.2 代码
#include<stdio.h> int main() { int n; //求 n 的阶乘 int res = 1; //阶乘的结果存储在res("result"的缩写)中,一定要赋值为1 scanf_s("%d", &n); for (int i = 1; i <= n; i++) { res = res * i;//将 res * i 的值赋给 res } printf("res = %d", res); return 0; }
4.1.3 拓展方案:函数递归
#include<stdio.h> /* fun()函数的大括号中(代码块)的内容也可以用三元运算符来简写: return n > 1 ? n * fun(n - 1) : 1; */ int fun(int n) { if (n > 1) return n * fun(n - 1); else //n = 1时就结束递归,返回1即可 return 1; } int main() { int n; //求 n 的阶乘 scanf_s("%d", &n); int res = fun(n); //阶乘的结果存储在res("result"的缩写)中 printf("res = %d", res); return 0; }
❓ 留下一个问题,如果我们设置的 n 过大,比如为1000,那这个res显然用int是无法装下的,即使用 long long int 类型也是无法容纳下那么大一个数的,那应该怎么解决呢?
✏️ 这个问题,可以用
数组
来解决,因此我们放到数组章节再来探讨这道题目。
4.2 素数个数
🏠 原题链接:https://www.luogu.com.cn/problem/P3912
4.2.1 题目描述
求1到1000中素数的个数。
4.2.2 思路分析
判断一个数是不是素数,我们首先得知道素数的定义:除了1和它本身,不能被其它自然数整除的数是素数,因此遍历每一个数,再对每一个数进行除法运算,看它能否被除1和它自身的数整除。我们又知道素数肯定不是偶数,所以我们循环遍历的时候每次循环迭代+2
4.2.3 代码
4.2.3.1 简单方法
#include<stdio.h> int main() { int i, j; int isPrimerNumber; // 2 一定是素数,提前打印输出 printf("2\n"); // i = i + 2 保证了 i 一定是奇数 for (i = 3; i < 1000; i = i + 2)//也可以写 i += 2 [复合赋值运算符] { //默认1即true,表示为素数 isPrimerNumber = 1; /* 1.对每个 i 进行判断是否为素数 2.循环变量初始化 j = 3:从3开始取模,为什么不为1和2 - j=1,则肯定能取模为0,且不管是否为素数,肯定能被1和自身整除 - j=2,我们已经在外层循环筛选出 i 肯定不为偶数因此没必要。 */ for (j = 3; j <= sqrt(i); j++) { if (i % j == 0)// 取模运算符:% { //如果 i 能对 j 取模的结果为0,即说明j能被i整除,标记一下false,为非素数 isPrimerNumber = 0; //退出内层循环,因为已经判断出了此时 i 不为素数,也没有继续循环下去的必要了 break; } } if (isPrimerNumber) { //则说明 i 是素数 printf("%d\n", i); } } return 0; }
4.2.3.2 稍作优化:sqrt函数
判断 i 是否能被 2~i \sqrt{i}i 间的整数整除:
- 输入的数n不能被 2~i \sqrt{i}i 间的整数整除,说明是素数
- 输入的数n能被 2~i \sqrt{i}i 间的整数整除,说明不是素数
❓ 为什么可以进行这样的范围缩小呢?
对一个数n,如果他能分解成n=pq,那么pq里必然有一个大于等于根号n一个小于等于根号n,也就是说一个合数n必然有一个因子是小于等于根号n的. 所以对一个数n,只要检验他有没有小于等于根号n的因子就可以了(检验小于等于n的因子使循环次数变少,这也是简化的原因)
#include<stdio.h> #include<math.h> //使用sqrt开平方根函数一定要记得引入math库 int main() { int i, j; int isPrimerNumber; // 2 一定是素数,提前打印输出 printf("2\n"); // i = i + 2 保证了 i 一定是奇数 for (i = 3; i < 1000; i = i + 2)//也可以写 i += 2 [复合赋值运算符] { //默认1即true,表示为素数 isPrimerNumber = 1; /* 1.对每个 i 进行判断是否为素数 2.循环变量初始化 j = 3:从3开始取模,为什么不为1和2 - j=1,则肯定能取模为0,且不管是否为素数,肯定能被1和自身整除 - j=2,我们已经在外层循环筛选出 i 肯定不为偶数因此没必要 */ for (j = 3; j <= sqrt(i); j++) { if (i % j == 0)// 取模运算符:% { //如果 i 能对 j 取模的结果为0,即说明j能被i整除,标记一下false,为非素数 isPrimerNumber = 0; //退出内层循环,因为已经判断出了此时 i 不为素数,也没有继续循环下去的必要了 break; } } if (isPrimerNumber) { //则说明 i 是素数 printf("%d\n", i); } } return 0; }
4.2.3.3 进一步优化:带一点欧拉筛算法的味道
#include<stdio.h> #include<math.h> //使用sqrt开平方根函数一定要记得引入math库 int main() { int i, j; int isPrimerNumber; // 2 一定是素数,提前打印输出 printf("2\n"); // i = i + 2 保证了 i 一定是奇数 for (i = 3; i < 1000; i = i + 2)//也可以写 i += 2 [复合赋值运算符] { //默认1即true,表示为素数 isPrimerNumber = 1; /* 1.对每个 i 进行判断是否为素数 2.循环变量初始化 j = 3:从3开始取模,为什么不为1和2 - j=1,则肯定能取模为0,且不管是否为素数,肯定能被1和自身整除 - j=2,我们已经在外层循环筛选出 i 肯定不为偶数因此没必要 3.循环变量迭代(这里其实就有了一种名为 欧拉筛 算法的思想了) j += 2:这导致了j一定为奇数,那为什么不考虑j为偶数的情况呢, 你想,如果 j 可以是偶数且能被 i 整除,那么 i 一定2的倍数 我们在外层循环又强调了 i 肯定是奇数,那是不是j为偶数没有意义了。 */ for (j = 3; j <= sqrt(i); j += 2) { if (i % j == 0)// 取模运算符:% { //如果 i 能对 j 取模的结果为0,即说明j能被i整除,标记一下false,为非素数 isPrimerNumber = 0; //退出内层循环,因为已经判断出了此时 i 不为素数,也没有继续循环下去的必要了 break; } } if (isPrimerNumber) { //则说明 i 是素数 printf("%d\n", i); } } return 0; }
4.2.4 拓展方案:欧拉筛
同样的,这个欧拉筛算法的知识点我们放在数组来讲,是一种进一步优化求素数的算法,这里先做个大概了解。感兴趣的可以先去了解,还是把代码放下面,现在不做讲解。
#include<stdio.h> int judge[10000]; //保存 这个数是否为素数 int prime[10000]; //用来保存已经找到的素数 int count; //统计素数的个数 int main() { int n; scanf_s("%d",&n); for(int i = 2;i <= n;i++) { if(!judge[i]) //如果judge[i] 没有被标记为合数 { prime[count] = i; //将 i 保存进数组 count++; } //i*prime[j] 为合数 for(int j = 0;j < count && i*prime[j] <= n;j++) //j 遍历的 是 已保存 的 素数 { judge[i*prime[j]] = 1; // 将 i*prime[j] 进行标记为合数 if(i % prime[j] == 0)//如果 prime[j] 是 i 的最小质因子,跳出 break; } } printf("%d\n",count); }
4.3 核实登录
4.3.1 题目描述
模拟登录while+switch:输入1登陆成功,其他都登陆失败,需要重新登录,直到登陆成功
4.3.2 代码
#include<stdio.h> int main() { int n; while (1) { scanf_s("%d", &n); switch (n) { case 1: printf("登陆成功\n"); return 0; default: printf("登陆失败\n"); } } return 0; }
4.4 鸡兔同笼
4.4.1 题目描述
鸡兔同一个笼子中,已知头共 n 个,脚共 m 个,问有鸡兔各多少只,请用循环来解决该问题
4.4.2 思路分析
- 假设鸡有0只,那么兔子有(n-0)只,当满足 0 * 2 + n * 4 == m(脚的总个数) 时,说明我们假设成立,不用再向下假设,此题得解。
- 假设鸡有1只,那么兔子有(n-1)只,当满足 1 * 2 + (n-1) * 4 == m(脚的总个数) 时,说明我们假设成立,不用再向下假设,此题得解。
- 假设鸡有2只,那么兔子有(n-2)只,当满足 2 * 2 + (n-2) * 4 == m(脚的总个数) 时,说明我们假设成立,不用再向下假设,此题得解。
很明显,这样的假设顺序我们可以用循环来实现。
4.4.3 代码
#include<stdio.h> int main() { //设置鸡共 i 只,兔共 j 只 int i, j; //设置头共n个,脚共m个 int n, m; scanf_s("%d %d", &n, &m); for (i = 0; i <= n; i++) { //鸡 i 只,又已知头为 n 个,则兔子(n-i)个 j = n - i; if (2 * i + 4 * j == m) { printf("鸡 %d 个,兔 %d 个", i, j); return 0; } } printf("此题无解!"); return 0; }
4.4.3 一些说明
很显然,如果用循环来做很复杂,因为设鸡有a只,兔有b只,则 a + b = n,2a + 4b = m,联解得 a = (4n-m)/2
,b = n-a
。输入了n和m,a和b我们可以直接求得,不需要循环,但这里只是一个对循环操作的练习,不用太在意。
4.5 斐波那契数列
4.5.1 题目描述
斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……
求斐波拉契数列第15个值。
4.5.2 思路
在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)
(n ≥ 2,n ∈ N*)。这也很容易转换为代码。
4.5.3 代码
#include<stdio.h> int main() { int first = 1;//第一项 int second = 1;//第二项 int three; for (int i = 0; i < 13; i++) { /* 记住:每次都是利用前两个数来求第三个数, 求完后前两个数都往后移动一位。 1.第一次循环: - three = 2 - first = second =》 1 - second = three =》 2 2.第二次循环: - three = 3 - first = second =》 2 - second = three =》 3 3.第三次循环: - three = 5 - first = second =》 3 - second = three =》 5 */ three = first + second; //第三项 first = second; //将第二项的值赋给第一项 second = three; //将第三项的值赋给第二项 } printf("%d", three); return 0; }
4.5.4 拓展方案:函数递归
#include <stdio.h> int fib(int input){ if (input <= 2) { return 1;//前两项都为1,所以直接返回1即可 } return fib(input - 1) + fib(input - 2); } void main() { int input = 0; scanf("%d",&input); printf("第%d位斐波那契数为:%d",input,fib(input)); }
4.6 打印三角形
4.6.1 题目描述
请使用循环打印出如下三角形:
4.6.2 思路分析
打印出一个占四行的三角形,它的每一行前面会有空格,因此首先要算出空格多少的规律,以及每行的数目。设行数为 n ,在这里 n 就为4。
- 空格计算:
- 第一行:空格数量为 n - 1,即3
- 第二行:空格数量为 n - 2,即2
- …
- 第 i 行:空格数量为 n - i,因此进行
(n - i)
次printf(" ");
打印空格的操作
- 星星计算:
- 第一行:星星数量为 2 * 1 - 1,即1
- 第二行:星星数量为 2 * 2 - 1,即3
- 第三行:星星数量为 2 * 3 - 1,即5
- …
- 第 i 行:星星数量为 2 * i - 1,因此进行
(2 * i - 1)
次printf("*");
打印星星的操作
实际上,你从我的上述可以看出,这就是一个找数学规律的过程,然后我们把找到的规律转变为代码就行了。
4.6.3 代码
#include<stdio.h> int main() { int n = 4;//表示三角形的总行数 int i, j; for (i = 1; i <= n; i++) { //打印空格 n- i 次 for (j = 0; j < n-i; j++) { printf(" "); } //打印星星 2*i-1 次 for (j = 0;j < 2*i-1; j++) { printf("*"); } //打完一行就换行,接着下次循环即打印下一行 printf("\n"); } return 0; }
4.6.4 拓展训练:打印菱形
💬 其实很简单,既然能够打印正三角形,而菱形又是由正三角形和倒三角形组成的,所以菱形应该很容易打印。自己先尝试一下吧。
#include<stdio.h> int main() { int n = 4;//表示三角形的总行数 int i, j; //打印正三角形 for (i = 1; i <= n; i++) { //打印空格 n- i 次 for (j = 0; j < n - i; j++) { printf(" "); } //打印星星 2*i-1 次 for (j = 0; j < 2 * i - 1; j++) { printf("*"); } //打完一行就换行,接着下次循环即打印下一行 printf("\n"); } //打印倒三角形 for (i = n - 1; i >= 1; i--) { //打印空格 n- i 次 for (j = 0; j < n-i; j++) { printf(" "); } //打印星星 2*i-1 次 for (j = 0; j < 2 * i - 1; j++) { printf("*"); } //打完一行就换行,接着下次循环即打印下一行 printf("\n"); } return 0; }
🔖 小结
💬 本章我们了解了三大基本结构:顺序、分支、循环,第四节练习题很有必要做一做,遇到任何疑惑联系软件协会
🏠 了解更多关注软协官网:https://www.csuftsap.cn/
💚 来自软件协会编辑,注册会员即可获取全部开源.md资源,请勿转载,归软件协会所有。