💃🕺kiko小剧场
kiko:各位真的是好久不见,【百炼成神】专栏终于要回归了!
小明:真的是拖欠了好久哦🤯···我都不打算订阅了的说。
kiko:呜呜呜😭,我保证,以后一周至少两更!一周两更!
小明:行叭!那就赶紧开始叭~
🌟🌟往期必看🌟🌟
目录
📜典型例题2.打印[100,999]范围内所有的“水仙花数”。
🍺知识点1:循环结构与转向结构
Q1:什么是循环结构?
A1:循环结构是程序中的一种很重要的结构,在给定条件成立时,反复执行某程序段,直到条件不成立为止,给定的条件称为循环条件,反复执行的程序段称为循环体。C语言提供以下3种循环语句:while语句、for语句、do-while语句。
🍯1.1 while语句
🥝1.while语句的基本知识
Q1:什么是while语句?
A1:while语句在实现循环过程中,先判断循环条件,再执行循环体,其一般语法格式为:
while(表达式) 循环体语句;
执行过程:当表达式为真,即非0时,执行循环语句,然后重复上述过程,一直到表达式为0时,while语句结束。
📜案例1.无限循环
#include<stdio.h> int main() { int a=1; while(a) printf("hehe\n"); return 0; }
kiko🎃:案例1的循环体中没有改变循环控制变量值的语句,while后的表达式始终为1,因此循环永远不结束。
📜案例2.正常循环
#include<stdio.h> int main() { int a=1; while(a<10) { printf("hehe\n"); a++; } return 0; }
对于正常的循环过程,我们需要注意几点特征:
(1)循环体在包含一条以上语句时,应当使用代码块括起来{ },以复合语句的形式出现,否则它只认为while后面的第一条语句是循环体。while(a<10) printf("hehe\n"); //只循环这一句 a++; //不处于while循环中
(2)循环前,必须给循环控制变量赋初值,例如本例中将a赋值了1。
int a=1;//在while循环前初始化了a while(a<10) {···
(3)循环体中,必须有改变循环控制变量值的语句,使得循环趋向结束,例如本例中的a++,如果没有这条语句,循环将永远不会结束。
📜典型例题1.演示字符从两端向中间移动
什么叫从两端向中间移动呢,我们可以先举个例子,在下例中可以看到字幕从两边开始向中间补全,这就是我们希望达到的结果,那么具体我们该如何通过while循环来实现呢?
"##########" ——>"ki######zz" ——>"kiko##ngzz"——>"kikokingzz"
我们不妨设置两个数组列表,然后不断通过while循环,将arr1中数组元素值赋值给arr2的对应位置,从两端向中间赋值,然后通过while循环过程中不断清屏+打印的操作来实现。
char arr1[] = { "This is kikokingzz !" }; char arr2[] = { "####################" };
具体的程序代码如下:
#include<stdio.h> #include<string.h> #include<windows.h>//为了使用后面的Sleep函数 #include<stdlib.h>//为了使用system函数 int main() { char arr1[] = { "This is kikokingzz !" }; char arr2[] = { "####################" }; int left = 0; int right = strlen(arr1) - 1;//strlen不将\0算入,因此不需要-2 while (left <= right) { arr2[left] = arr1[left]; arr2[right] = arr1[right]; printf("%s\n", arr2); //休息1s Sleep(20);//睡眠函数-单位是毫秒 system("cls");//执行系统命令函数,cls-清空屏幕 left++; right--; } printf("%s\n", arr2); return 0; }
kiko🎃:值得注意的是这边用到了睡眠函数Sleep( ),它会使计算机程序(进程,任务或线程)进入休眠,单位是毫秒,因此上面的Sleep(20)就是休眠了20毫秒;system函数代表执行系统命令,system("cls")就是执行命令”清屏“的意思。
🥝2.while语句与转向语句
Q1:什么是转向语句?
A1:转向语句可以改变程序的流程,使程序从其所在的位置转向另一处执行。转向语句包括3种,分别是goto语句、break语句、continue语句。其中break语句、continue语句经常用在循环结构中,所以我们在这里提到啦!
🥝3.break语句的基本知识
在循环中遇到break,就停止后期所有的循环,直接终止当前循环。
Q1:如何理解“当前”二字?
A1:“当前”指的是,break语句只是跳出当前的循环语句,对于嵌套的循环语句,break语句的功能可以视作从内层循环跳到外层循环;我们可以通过下面这个例子来进行解释:
#include<stdio.h> int main() { int i=1; int j=5; while(i<11) { while(i<j) { if(i==5) break; printf("%d\n",i); i++; } } return 0; }
当i==5时,就会发生break,此时break语句跳出的只是当前的循环语句,即跳出了子循环,也就是跳出了下面这个while(i<j)的循环:
while(i<j) { if(i==5) break; printf("%d\n",i); i++; }
但是值得注意的是,此时程序仍然处于大循环while(i<11)中:
while(i<11) { while(i<j) { ··· } }
🥝4.continue语句的基本知识
Q1:什么是continue语句?它与break有什么区别?
A1:continue语句的功能是结束当前循环体的执行,而重新执行下一次循环;也就是本次循环中continue后边的代码不会再执行,而是直接跳转到while语句的判断部分,进行下一次循环。同样我们可以通过一个例子来解释:
📜案例1.输出1~10中不能被5整除的整数。
典型错解:没有理解continue的本质,continue后面的代码不会再执行,因此在以下程序中,当i==5时,会执行continue语句,进而导致之后的printf语句、i++语句都不会执行,进而i的值依旧保持为5,以这个状态重新进入while循环,最终导致程序卡死。
int main() { int i = 1; while (i <= 10) { if (i % 5 == 0) { continue; } printf("%d\n", i); i++; } }
正确思路:上边的错解是因为i++这个语句被continue阻断了,因此我们只要想办法把i++这个语句放在continue之前,这样就不会进行死循环了,因此我的改法如下:
int main() { int i = 0; while (i < 10) { i++; if (i % 5 == 0) { continue; } printf("%d\n", i); } }
🍯1.2 for语句
🥝1.for语句的基本知识
Q1:什么是for语句?
A1:for语句不但可以用于循环次数确定的情况;也可以用于循环次数不确定的情况,即只给出循环结束条件的情况。其一般语法格式为:
for(表达式1;表达式2;表达式3) 循环体语句; 表达式1:初始化部分,用于初始化循环变量的 表达式2:条件判断部分,用于判断循环时候终止 表达式3:调整部分,用于循环条件的调整
执行过程:首先计算表达式1的值;然后判断表达式2,如果表达式2为真,就执行循环体语句,然后执行表达式3;如果表达式2为假,就直接跳出循环,执行for后面的语句。
📜典型例题2.打印[100,999]范围内所有的“水仙花数”。
所谓的水仙花数是指一个三位数,其各位数字的立方和等于该数本身,例如153就是一个水仙花数,因为。
int main() { int a, b, c; for (int i = 100; i < 1000; i++)//从i=100,一直循环进行到i=1000时跳出循环 { a = i % 10;//分解出个位 b = (i / 10) % 10;//分解出十位 c = i / 100;//分解出百位 if (a * a * a + b * b * b + c * c * c == i) printf("%d\n", i); } }
📜典型例题3.计算n的阶乘(n!)
int main() { int num = 0; int ret = 1; scanf("%d", &num); for (int i = 1; i <= num; i++) { ret *= i; } printf("%d", ret); }
kiko🎃:使用for循环可以清晰地看出阶乘进行的循环次数,即计算num的阶乘就要进行num次循环,这是相当常见的例子,这个案例是用来引出下面这道经典例题的。
📜典型例题4.计算计算1!+2!+3!+......+n!
基础解法:
int main() { int num,result=0; int ret; scanf("%d", &num); for (int top = 1; top <= num; top++)//这是计算大单元的循环,即1!+2!+3!的循环 { ret = 1;//计算每一项的阶乘前,要先将ret初始化为1,不然ret的值仍然为前一项的阶乘值 for (int i = 1; i <= top; i++)//这是具体计算n!单元的小循环 { ret *= i; } result += ret; } printf("%d", result); }
kiko🎃:这里我们通过使用两个循环嵌套的方式来解题,通过题设我们发现,这里面其实暗藏了两个循环:外部是一个大循环,即1!+2!+··+n!,进行n次相加,也就是进行n次循环;而每一小项本身又是一个小循环,即n!会进行n次阶乘,也要进行n次循环,因此最终选择了一个for循环嵌套的方式求解。
ret = 1;//计算每一项的阶乘前,要先将ret初始化为1,不然ret的值仍然为前一项的阶乘值
在嵌套过程中,但是值得注意的是ret=1;这条语句,由案例2可知,在进行每项元素的阶乘前ret的值需要保持为1,但是由于这里加了一个大循环在,因此如果不在内层for循环调用前为ret赋值为1,ret将会保持上一轮内层for循环的计算值,导致计算出错。但反过来说,我们同样可以利用这一点,来进行简化操作。
进阶解法:
int main() { int num,result=0; int ret = 1; scanf("%d", &num); for (int top = 1; top <= num; top++)//这是计算大单元的循环,即1!+2!+3!的循环 { ret = ret * top; result = ret + result; } printf("%d", result); }
kiko🎃:进阶解法的提高之处就在于后一项元素的阶乘其实等于前一项最终结果 ret 乘上后一项的值,举个例子来说就是 5! 等于 4!*5,而 4! 就相当于前一轮的 ret值 ,因此我们保留 ret值,只需要在每一次的循环中乘后一元素的值即可。
🥝2.for语句的扩展形式
for(表达式1;表达式2;表达式3) 循环体语句; 表达式1:初始化部分,用于初始化循环变量的 表达式2:条件判断部分,用于判断循环时候终止 表达式3:调整部分,用于循环条件的调整
(1)表达式1和表达式3可以是一个简单的表达式,也可以是逗号表达式:
for(i=0,j=10;i<j;i++,j--) k=i+j;
(2)表达式2可以由一个较复杂表达式的值确定:
for(i=0;s[i]!=c&&s[i]!='\0';++i)
这里我们可以举一个结合上述两种扩展情况的例子:
int main() { int x ,y; for(x=0,y=0; x<2 && y<5; ++x,y++) { printf("hehe\n"); } return 0; }
(3)表达式2一般是关系表达式或逻辑表达式,但也可以是数值表达式或字符表达式,只要其值不等于0就执行循环体语句:
for(k=1;k-4;k++)//当k的值等于4时,终止循环;k-4是数值表达式 s=s+k;
🥝3.for循环省略形式
for(表达式1;表达式2;表达式3) 循环体语句; for语句中的3个表达式都是可以省略的。
(1)省略“表达式1”:此时应在for语句之前给循环变量赋初值。
i=1;//提前为i赋值 for(;i<=100;i++)//省略表达式1 sum+=i;
有时候这样的省略可能会带来一些问题,比如我们看看下面这两段程序,猜猜看它们分别最终打印了什么结果?
//程序段1:不省略情况 for (i=0; i < 3; i++) { for (j=0; j < 3; j++)//每一次进入子循环时,j的值都是从0开始 { printf("hehe\n"); } } //程序段2:省略表达式1 int i = 0; int j = 0; for (; i < 3; i++) { for (; j < 3; j++)//由于省略表达式1,第二次进入循环时,j的值保持了上一次跳出循环的值 { printf("hehe\n"); } }
kiko🎃:第一个程序打印9个hehe,而第二个程序只打印了3个hehe,这是因为第二个程序的内层for循环省略了表达式1,导致了第二次进入循环时,j的值依然为3,会直接跳出内层循环,最终只打印了i=0时这种情况的3个hehe。
(2)省略“表达式2”:表示不判断循环条件,可以认为表达式2的值始终为真,循环将永远进行下去。
for(i=1;;i++) sum+=i;//省略表达式2,该语句会无终止地进行下去。
(3)省略“表达式3”:此时应当在循环体内部实现循环变量的改变,以保证循环可以正常结束。
for(i=1;i<10;) { sum+=i; i++; }
(4)省略“表达式1”和“表达式3”:这种情况只给出循环条件,就有点像while语句了,for语句只起到了判断循环的作用。
i=1; for(;i<=10;) { sum+=i; i++; }
(5)省略全部表达式:这种情况既不设置初值,也不判断条件,循环变量改变,循环体将无终止地进行下去。
for(;;) { printf("hehe\n");//无止尽地打印hehe }
🥝4.for循环的一点小tip
Tip1:for循环中注意不要将=与==搞混
说到这点,我们不妨先来看一道题,来引出我们的一些tip~
//Q1:请问下面这段循环会进行几次? #include<stdio.h> int main() { int i = 0; int k = 0; for (i = 0, k = 0; k = 0; i++, k++) { k++; } return 0; }
这道题显然是不会进入循环的,这是因为for循环的表达式2语句并不是判断语句,而是赋值语句,k=0,即将0赋值给了k,表达式2为0,即循环条件为假,循环压根就不会进去;当然如果我们将它改成k=1,就会形成死循环。
Tip2:尽量不要在for循环体内修改循环变量,防止for循环失去控制
#include<stdio.h> int main() { int i = 0; for (i = 0; i < 10; i++) { if (i = 5)//此处是=,是赋值语句,不是==,修改导致此程序死循环 printf("haha\n"); printf("hehe\n"); } return 0; }
这道题也与=、==有关,只不过这里的问题出在了for循环内部,将if语句中的条件改为了i=5,这就导致了两种错误:
- 对于if语句来说:这是一个赋值语句,if判断条件恒为真,所以一定会打印haha。
- 对于for语句来说:每次进入if语句都会为i赋值为5,因此永远可以进入for循环。
🍯1.3 do-while语句
🥝1.do-while语句的基本知识
Q1:什么是do-while语句?
A1:do-while语句与之前的for、while循环都不同,它是先执行循环判断语句,后判断条件表达式的循环,也就是说不管判断表达式真假,它至少都会执行一次循环判断语句。其一般的语法格式为:
do { 循环体语句; }while(表达式);
执行过程:首先执行一次循环体语句,然后判断表达式真假;如果为真(非0),则再执行循环体语句;如果为假,就结束循环。
📜典型例题5.计算两个数的最大公约数
计算两个数的最大公约数时,我们通常采用辗转相除法,用于计算两个非负整数a,b的最大公约数。
Q2:什么是辗转相除法?
A2:辗转相除法是以除数和余数反复做除法运算,当余数为 0 时,取当前算式除数为最大公约数,我们可以参考一下下边的例子,然后开始码程序!
1.求 200 和 14 两个正整数的最大公约数:(使用辗转相除法) 200 / 14 = 14······4 14 / 4 = 3······2 4 / 2 = 2······0 因此最大公约数为2 2.求 200 和 13 两个正整数的最大公约数: 200 / 13 = 15······5 13 / 5 = 2······3 5 / 3 = 1······2 3 / 2 = 1······1 2 / 1 = 2······0 因此最大公约数为1
程序代码:
#include<stdio.h> int main() { int big, small,val,remainder;//定义被除数、除数、中间充量、余数 scanf("%d%d", &big, &small); if (big < small) { val= small; small = big; big = val; }//保证是大数(被除数)除以小数(除数),这样计算才有意义 do//计算最大公约数,不管两个数怎么样,都要先计算一轮 { remainder = big % small;//remainder是余数 big = small;//辗转相除法的精髓:将上一轮的除数变成被除数 small = remainder;//将余数变为除数 } while (remainder != 0);//当余数为0时结束循环 printf("%d", big); }
📜典型例题6.在有序数组中查找具体数字
我们需要在一串有序数组中{1,2,3,4,5,6,7,8,9,10}查找到数字7的下标。
这里我们可以用到折半查找算法,也就是二分查找,每次查找我们都可以将有序数组的中间值与所查找的元素做比较,进而可以在每次查找过程中排除一半元素,但值得注意的是,这种方法仅适用于有序排列的元素中的查找。
#include<stdio.h> int main() { int num, left, right; int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int size = sizeof(arr) / sizeof(arr[0]);//计算元素个数 left = 0; right = size - 1; scanf("%d", &num); do//怎么都得查一次,因此至少进行一次循环,采用do-while循环 { int mid = (right + left) / 2; if (arr[mid] > num) { right = mid - 1;//右开 } else if (arr[mid] < num) { left = mid;//左闭 } else { printf("%d", mid); break;//跳出当前do-while循环 } } while (left <= right); }
kiko🎃:二分查找我们需要注意两点,第一就是循环的判断条件,left<=right;第二就是左标记left和右标记right的变化,对于缩小区间我们只需要记住一点,那就是保证左闭右开。左闭,指的就是当arr[mid]<num时,左边界包含mid,即left=mid;右开指的是当arr[mid]>num时,右边界不包含mid,即right=mid-1;
🍯1.4 转向语句
kiko🎃:关于转向语句我们其实已经在上面提过了,因此这里主要再回顾一下前面两种转向语句,再学习一种新的转向语句:
- break语句:结束当前循环,对于嵌套的循环来说,如果break语句位于内层循环,那么它的作用就是从内层循环跳到外层循环。
- continue语句:结束当前循环体的执行,即continue后面的语句不再执行,重新回到循环的判定部分,进行下一次循环(可能终止、可能继续执行循环)。
接下来我们就要学习一种新的转向语句,那就是goto语句。
🥝1.goto语句的基本知识
Q1:什么是goto语句?
A1:goto语句是无条件转向语句,即转向到指定语句标号处,执行标号后面的程序。其一般语法格式为:
{ 语句标号: //语句标号必须在同一个代码块内,不能跨函数跳转 ··· goto 语句标号; }
结构化的程序不推荐使用goto语句,这是因为goto语句会使得程序的流程变得无规律、可读性差,但在一些情况下,是推荐使用goto语句的:
- 与if语句搭配:实现循环结构。
- 从循环体中跳出,甚至一次性跳出多重循环,因为break语句只能结束当前层次的循环,而continue只能结束当前层次的本次循环。
for(...) { for(...) { for(...)//循环嵌套三层 { if(disaster)//如果用break跳出循环,需要3个break跳出深层循环 goto error;//很容易跳出深层嵌套结构 } } } .... error: if(disaster) //处理错误情况
📜典型例题7.打印1~20的数字(不可使用循环语句)
通常来说打印1~20的数字我们直接用一个for循环就结束了,但是这里题目特别指定了不允许使用循环语句,因此我们没办法用while、for、do-while,这时我们就可以使用if + goto语句来实现循环。
#include<stdio.h> int main() { int count = 1; begin: printf("%d\n", count); count++; if (count <= 20) goto begin;//转向到begin处 }