C (xiv) while、for、do-while 循环合成(I)
uu们,小弟我本科在读,文章我会一直坚持更新下去,包括但不限于C初阶、C进阶、数据结构、C++、Linux、MySQL、项目、QT开发、各种算法······(之后会持续更新),并且站在小白的视角尽可能通俗易懂地把这些写出来,同时加上各种生活场景帮助大家理解知识,并知道说“噢,用来可以这样用”。
整体工程量比较大,可以给小弟打赏一下多买点营养餐的费用吗?(1元也可以,万分感谢)。
本文目录:????????????
一、数学问题(一)(难度:*)
二、图形的打印(难度:**)
三、密钥(难度:**)
四、数学问题(二)(难度:***)
五、实际场景(难度:***)--- 自选小炒
【分解目标1】选择吃与不吃
【分解目标2】选择如下(自选小炒配料):>(注:米、面、粉免费)
【分解目标3】选择哪种肉,哪种菜
【分解目标4】同学选完后,给下一个同学选择
六、后记
前言:????????????
三大循环结构、各种语句和几个关键字的综合运用。????
while循环 --- 军训匕首操情景
https://blog.****.net/2401_87025655/article/details/142651077
for循环 --- 黑神话情景
https://blog.****.net/2401_87025655/article/details/142684637
do - while循环 --- 致敬革命烈士
https://blog.****.net/2401_87025655/article/details/142699352
goto语句 --- 关机程序(另一位作者的)
https://blog.****.net/m0_68865259/article/details/124537858?fromshare=blogdetail&sharetype=blogdetail&sharerId=124537858&sharerefer=PC&sharesource=2401_87025655&sharefrom=from_link
一、数学问题(一)(难度:*)
场景一:求12 与 18的最大公因数
法一:????(正向思维)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 12;
int b = 18;
int max = 1; //1、两个正整数的公因数肯定有1,不妨先令最大公因数为1
int i = 1;
for (i = 1; i <= 12; i++)
{
if (a % i == 0 && b % i == 0)
{
max = i; //2、之后如果找到比1更大的,再赋值给max
}
}
printf("%d", max);
return 0;
}
法二:????(逆向思维)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 12;
int b = 18;
int max = 0;
int i = 0;
for (i = 12; i > 0; i--)
{
if (a % i == 0 && b % i == 0)
{
max = i;
break; //从大到小 --- 找到那个最大的就打破循环
}
}
printf("%d", max);
return 0;
}
场景二:求十个整数的最大值
法一:do - while循环
法二:for循环
????https://blog.****.net/2401_87025655/article/details/142699137
场景三: 编写程序数一下 1到 100 的所有整数中出现多少个数字9
本题有一个初学者容易掉进的坑(反正我就掉了,哈哈????)
话不多说,上分析链接????
https://blog.****.net/2401_87025655/article/details/142699192
场景四:一个正整数的逆序打印
输入示例:1234
输出示例:4321
分析详见????C题(八)一个正整数的逆序打印(用循环结构实现)
https://blog.****.net/2401_87025655/article/details/142708516
二、图形的打印(难度:**)
场景一:矩形、直角三角形
????https://blog.****.net/2401_87025655/article/details/142651077
场景二:正三角形
???? https://blog.****.net/2401_87025655/article/details/142675413
三、密钥(难度:**)
场景一:“芝麻开门 ”是通往C语言的大门的暗号,现在你需要说对暗号,大门才会打开。
????分析的链接:
https://blog.****.net/2401_87025655/article/details/142693306
场景二:输入一串密码,如:“123 xxxx” ,然后读取并确认,是 --- Y;否 --- N。
????分析的链接:
https://blog.****.net/2401_87025655/article/details/142694774
四、数学问题(二)(难度:***)
场景一:水仙花数是指一个三位数,它的每个数位上的数字的立方和等于它本身。
例如,153就是一个水仙花数,因为1³ + 5³+ 3³ = 1 + 125 + 27 = 153。
????分析的链接(难点剖析):
https://blog.****.net/2401_87025655/article/details/142697771
场景二:打印1到100之间的素数。(素数也叫质数,是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。例如2、3、5、7、11等都是素数。)
????分析的链接 :(含对C语言中0与非0的认知、flag证伪理论)
https://blog.****.net/2401_87025655/article/details/142703599
五、实际场景(难度:***)--- 自选小炒
场景一:
“要吃什么吗,靓仔?” 热情的食堂阿姨问你了,你要怎么回答?
【分解目标1】选择吃与不吃
第一步:打印菜单(先执行 --- 用do - while循环)
do
{
//菜单栏
printf("******* 1、炒面 *******\n");
printf("******* 2、炒饭 *******\n");
printf("******* 3、炒粉 *******\n");
printf("**** 0、不了,谢谢 ****\n");
}
while();
第二步:开始选择
//选择栏
int input = 1;
printf("请选择:>");
scanf("%d", &input);
第三步:循环实现 --- 如果不吃,阿姨就问下一位同学
基于:在C语言中,非0表示真,0表示假,所以????
选择 1、2 、3(非0)时,加上取反操作符 !-----> 变为0,表示假,循环终止。
选择 0 时,加上 !------> 变为非0,表示真,循环继续。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int input = 1;
printf("要吃什么吗,靓仔?\n");
do
{
//菜单栏
printf("******* 1、炒面 *******\n");
printf("******* 2、炒饭 *******\n");
printf("******* 3、炒粉 *******\n");
printf("**** 0、不了,谢谢 ****\n");
//选择栏
printf("请选择:>");
scanf("%d", &input);
//如果不吃
if (input == 0)
{
printf("\n你呢,同学?\n\n");
}
//如果吃
else
{
printf("循环体1");
}
}
while (!input); //非0表示真,0表示假
return 0;
}
【分解目标2】选择如下(自选小炒配料):>(注:米、面、粉免费)
————两肉两菜 :8元
————三肉两菜 :9元
————三肉三菜 :10元
接下来,编写循环体1
printf("同学,有8、9、10元的配料,你要几元的配料呀?:>");
int input1 = 0;
scanf("%d",&input1);
switch (input)
{
case 8:
printf("语句1\n");
break;
case 9:
printf("语句2\n");
break;
case 10:
printf("语句3\n");
break;
}
tip:因为我这里说明在 8、9、10中选,所以我switch内部没有写default子句。(当然,有写更好)。
【分解目标3】选择哪种肉,哪种菜
以8元的为例,选两肉两菜,共4个选择,所以语句1为 ????
printf("同学,选一下配料。\n");
int j = 0;
for (j = 4; j > 0; j--)
{
printf("这个。\n");
}
同理,9元---5个选择 ; 10元 --- 6个选择。
【分解目标4】同学选完后,给下一个同学选择
语句1、2、3执行完后都加上input = 0,使得do - while循环继续。
最终代码 ????
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int input = 1;
printf("要吃什么吗,靓仔?\n");
do
{
//菜单栏
printf("******* 1、炒面 *******\n");
printf("******* 2、炒饭 *******\n");
printf("******* 3、炒粉 *******\n");
printf("**** 0、不了,谢谢 ****\n");
//选择栏
printf("请选择:>");
scanf("%d", &input);
//如果不吃
if (input == 0)
{
printf("\n你呢,同学?\n\n");
}
//如果吃
else
{
//选小炒配料
printf("同学,有8、9、10元的配料,你要几元的配料呀?:>");
int input1 = 0;
scanf("%d",&input1);
int j = 0;
switch (input1)
{
case 8:
printf("同学,选一下配料。\n");
for (j = 4; j > 0; j--)
{
printf("这个。\n");
}
input = 0;
break;
case 9:
printf("同学,选一下配料。\n");
for (j = 5; j > 0; j--)
{
printf("这个。\n");
}
input = 0;
break;
case 10:
printf("同学,选一下配料。\n");
for (j = 6; j > 0; j--)
{
printf("这个。\n");
}
input = 0;
break;
}
}
}
while (!input); //非0表示真,0表示假
return 0;
}
运行结果:????
要吃什么吗,靓仔?
******* 1、炒面 *******
******* 2、炒饭 *******
******* 3、炒粉 *******
**** 0、不了,谢谢 ****
请选择:>0
你呢,同学?
******* 1、炒面 *******
******* 2、炒饭 *******
******* 3、炒粉 *******
**** 0、不了,谢谢 ****
请选择:>1
同学,有8、9、10元的配料,你要几元的配料呀?:>8
同学,选一下配料。
这个。
这个。
这个。
这个。
******* 1、炒面 *******
******* 2、炒饭 *******
******* 3、炒粉 *******
**** 0、不了,谢谢 ****
请选择:>2
同学,有8、9、10元的配料,你要几元的配料呀?:>9
同学,选一下配料。
这个。
这个。
这个。
这个。
这个。
******* 1、炒面 *******
******* 2、炒饭 *******
******* 3、炒粉 *******
**** 0、不了,谢谢 ****
请选择:>
六、后记
这个do - while循环常用于菜单栏的弹出,之后如果有写设计游戏库的博文的话,会用到。
上一篇: hutool bug
推荐阅读
-
C (xiv) while、for、do-while 循环合成(I)
-
学习C语言的初级循环——while和do-while
-
C语言循环语句:while、for和do-while用法总结
-
韦根26协议读头的使用及proteus仿真-模拟韦根26读头的数据发送 使用定时器T1,采用16位定时器方式。 //8051 T1初始化 void Timer1_init { TMOD=0x10; //T1 16位定时器模式 ET1=0; //关闭定时器中断 TR1=0; //关闭定时器 TF1=0; //清除TF1标志 } 例如,就发送上面的这个数据:01000110111000001001010101 十进制的18580053 发送数据0的时候,就是将数据线D0拉低404us,发送数据1的时候,就是将数据线D1拉低404us。 首先设置定时器初值,用STC的下载器计算404us的预装入值。 拉低数据线,等待404us到时,之后抬高数据线,再等待2ms的时间,一位数据就发送完成了。 void Send_bit(bit bD) { //拉低数据线D0 404us TL1 = 0x8C; //设置定时初值 TH1 = 0xFE; //设置定时初值 if(bD==0) Send_D0=0; else Send_D1=0; TR1=1; //开启定时器 while(TF1 ==0); //等待溢出 //时间到抬高数据线 if(bD==0) Send_D0=1; else Send_D1=1; TF1=0; //清溢出标志 TR1=0; //关定时器 //下面是数据位的间隔 2ms TL1 = 0xCD; //设置定时初值 TH1 = 0xF8; //设置定时初值 TR1=1; //开启定时器 while(TF1 ==0); //等待溢出 TF1=0; //清溢出标志 TR1=0; //关定时器 } 将韦根26协议的数据装入一个无符号长整型变量里: //二进制 0 100011011100000100101010 1 头尾两位为奇偶校验位,十进制是18580053 unsigned long WG26=18580053; 无符号长整型是四个字节32位,装入26位的数据,则最前面的6位是无效的,循环移位6次,把无效数据移除。 //000000 01000110111000001001010101 for(i=0; i<6; i++) { WGdata=WGdata<<1; } //现在WGdata中的数据是 01000110111000001001010101 000000,后面多了6个0。 有效数据已经移动到最前面,可以开始发送了,循环26次发送数据 for(i=0; i<26; i++) { if( (WGdata & 0x80000000) == 0x80000000 ) Send_bit(1); //如果最高位为1,发送1 else Send_bit(0); //如果最高位为0,发送0 WGdata=WGdata<<1; //左移1位 } } 完整发送函数: //发送韦根26数据,用4个字节保存,一共32位 void SendWG26(unsigned long WGdata) { uchar data i; //从最高位开始发送数据,将开头的6个无效数据位隔过去 //18580053 //000000 01000110111000001001010101 //01000110111000001001010101 000000 for(i=0; i<6; i++) { WGdata=WGdata<<1; } //有效数据位已经移到了开头,开始发送数据 for(i=0; i<26; i++) { if( (WGdata & 0x80000000) == 0x80000000 ) Send_bit(1); else Send_bit(0); WGdata=WGdata<<1; } } 数据的接收 将数据线D0,D1连接到与门74HC08上,两条数据线上有数据发送时会产生INT0的下降沿中断。 (这只是仿真图,实际硬件连接有所不同) 在中断服务程序中接收数据: 还是用一个节的无符号长整型数据WG26,将收到的数据记入其最低位。每接到一位数据,左移一次。当接收到26个数据时,认为收到了读头发来的完整数据。设置接收完成标志ReceiveFlag=1;供主程序查询。 这里设置了一个超时检测,就是接收到的两位数据之间的时间间隔如果大于5ms就认为数据超时,(因为读头发来的数据每位之间的间隔是2ms)。这样,如果有意外的脉冲干扰,引起计数数据位的count值错误,也只会产生一次数据接收错误,将各种标志和变量全部清零后,不会影响下一次的数据接收。 在中断服务程序退出之前,一定要清除中断标志IE0,以免响应了无效数据的中断标志,产生接收错误。 void INT0_ISR(void) interrupt 0 //外部中断0服务程序 { //如果接到的两位数据之间间隔超过5ms,定时器溢出标志TF1置位 //超时检测使用定时器T1,16位定时方式 EX0=0; //关中断 //如果有定时器超时标志置位 if(TF1==1) //数据有误,放弃数据 { LCD_StrDisp(0x00,"Try Again "); LCD_StrDisp(0x40,"TimeOut Error "); Beep(10); //隔过至少一个数据包的时间,以便放弃不完整的数据 //延时100ms Delay50ms; Delay50ms; TR1=0; //关定时 TF1=0; //清标志 TL1 = 0x00; //设置定时初值 5ms 溢出 TH1 = 0xEE; //设置定时初值 5ms 溢出 count=0; WG26=0; ReceiveFlag=0; } //如果数据位间隔未超时 else { WG26=WG26<<1; if(RD0==0) //接收到了0 WG26=WG26&0xFFFFFFFE; else if(RD1==0) //接收到了1 WG26=WG26|0x00000001; count++; if(count==26) { count=0; ReceiveFlag=1; TR1=0; //关定时 TF1=0; //清标志 } else { //为接收下一位做准备 TR1 = 0; //关定时 TF1 = 0; //清除TF1标志 TL1 = 0x00; //设置定时初值 TH1 = 0xEE; //设置定时初值 //超过5ms溢出标志被置位 TR1 = 1; //定时器1开始计时 } } IE0=0; //清除INT0中断标志,很重要! EX0=1; //开中断 } 在主程序查询到接收完成标志后,开始对数据进行奇偶校验位的核对。 得到奇校验位,记入odd=1 将无效的6位移除 得到偶校验位,记入even=0 将偶校验位移除,统计前12位有几个1 100011011100 000100101010
-
F#探险之旅(二):函数式编程(上)-函数式编程范式简介 F#主要支持三种编程范式:函数式编程(Functional Programming,FP)、命令式编程(Imperative Programming)和面向对象(Object-Oriented,OO)的编程。回顾它们的历史,FP是最早的一种范式,第一种FP语言是IPL,产生于1955年,大约在Fortran一年之前。第二种FP语言是Lisp,产生于1958,早于Cobol一年。Fortan和Cobol都是命令式编程语言,它们在科学和商业领域的迅速成功使得命令式编程在30多年的时间里独领风骚。而产生于1970年代的面向对象编程则不断成熟,至今已是最流行的编程范式。有道是“*代有语言出,各领风骚数十年”。 尽管强大的FP语言(SML,Ocaml,Haskell及Clean等)和类FP语言(APL和Lisp是现实世界中最成功的两个)在1950年代就不断发展,FP仍停留在学院派的“象牙塔”里;而命令式编程和面向对象编程则分别凭着在商业领域和企业级应用的需要占据领先。今天,FP的潜力终被认识——它是用来解决更复杂的问题的(当然更简单的问题也不在话下)。 纯粹的FP将程序看作是接受参数并返回值的函数的集合,它不允许有副作用(side effect,即改变了状态),使用递归而不是循环进行迭代。FP中的函数很像数学中的函数,它们都不改变程序的状态。举个简单的例子,一旦将一个值赋给一个标识符,它就不会改变了,函数不改变参数的值,返回值是全新的值。 FP的数学基础使得它很是优雅,FP的程序看起来往往简洁、漂亮。但它无状态和递归的天性使得它在处理很多通用的编程任务时没有其它的编程范式来得方便。但对F#来说这不是问题,它的优势之一就是融合了多种编程范式,允许开发人员按照需要采用最好的范式。 关于FP的更多内容建议阅读一下这篇文章:Why Functional Programming Matters(中文版)。F#中的函数式编程 从现在开始,我将对F#中FP相关的主要语言结构逐一进行介绍。标识符(Identifier) 在F#中,我们通过标识符给值(value)取名字,这样就可以在后面的程序中引用它。通过关键字let定义标识符,如: let x = 42 这看起来像命令式编程语言中的赋值语句,两者有着关键的不同。在纯粹的FP中,一旦值赋给了标识符就不能改变了,这也是把它称为标识符而非变量(variable)的原因。另外,在某些条件下,我们可以重定义标识符;在F#的命令式编程范式下,在某些条件下标识符的值是可以修改的。 标识符也可用于引用函数,在F#中函数本质上也是值。也就是说,F#中没有真正的函数名和参数名的概念,它们都是标识符。定义函数的方式与定义值是类似的,只是会有额外的标识符表示参数: let add x y = x + y 这里共有三个标识符,add表示函数名,x和y表示它的参数。关键字和保留字关键字是指语言中一些标记,它们被编译器保留作特殊之用。在F#中,不能用作标识符或类型的名称(后面会讨论“定义类型”)。它们是: abstract and as asr assert begin class default delegate do donedowncast downto elif else end exception extern false finally forfun function if in inherit inline interface internal land lazy letlor lsr lxor match member mod module mutable namespace new nullof open or override private public rec return sig static structthen to true try type upcast use val void when while with yield 保留字是指当前还不是关键字,但被F#保留做将来之用。可以用它们来定义标识符或类型名称,但编译器会报告一个警告。如果你在意程序与未来版本编译器的兼容性,最好不要使用。它们是: atomic break checked component const constraint constructor continue eager event external fixed functor global include method mixinobject parallel process protected pure sealed trait virtual volatile 文字值(Literals) 文字值表示常数值,在构建计算代码块时很有用,F#提供了丰富的文字值集。与C#类似,这些文字值包括了常见的字符串、字符、布尔值、整型数、浮点数等,在此不再赘述,详细信息请查看F#手册。 与C#一样,F#中的字符串常量表示也有两种方式。一是常规字符串(regular string),其中可包含转义字符;二是逐字字符串(verbatim string),其中的(")被看作是常规的字符,而两个双引号作为双引号的转义表示。下面这个简单的例子演示了常见的文字常量表示: let message = "Hello World"r"n!" // 常规字符串let dir = @"C:"FS"FP" // 逐字字符串let bytes = "bytes"B // byte 数组let xA = 0xFFy // sbyte, 16进制表示let xB = 0o777un // unsigned native-sized integer,8进制表示let print x = printfn "%A" xlet main = print message; print dir; print bytes; print xA; print xB; main Printf函数通过F#的反射机制和.NET的ToString方法来解析“%A”模式,适用于任何类型的值,也可以通过F#中的print_any和print_to_string函数来完成类似的功能。值和函数(Values and Functions) 在F#中函数也是值,F#处理它们的语法也是类似的。 let n = 10let add a b = a + blet addFour = add 4let result = addFour n printfn "result = %i" result 可以看到定义值n和函数add的语法很类似,只不过add还有两个参数。对于add来说a + b的值自动作为其返回值,也就是说在F#中我们不需要显式地为函数定义返回值。对于函数addFour来说,它定义在add的基础上,它只向add传递了一个参数,这样对于不同的参数addFour将返回不同的值。考虑数学中的函数概念,F(x, y) = x + y,G(y) = F(4, y),实际上G(y) = 4 + y,G也是一个函数,它接收一个参数,这个地方是不是很类似?这种只向函数传递部分参数的特性称为函数的柯里化(curried function)。 当然对某些函数来说,传递部分参数是无意义的,此时需要强制提供所有参数,可是将参数括起来,将它们转换为元组(tuple)。下面的例子将不能编译通过: let sub(a, b) = a - blet subFour = sub 4 必须为sub提供两个参数,如sub(4, 5),这样就很像C#中的方法调用了。 对于这两种方式来说,前者具有更高的灵活性,一般可优先考虑。 如果函数的计算过程中需要定义一些中间值,我们应当将这些行进行缩进: let halfWay a b = let dif = b - a let mid = dif / 2 mid + a 需要注意的是,缩进时要用空格而不是Tab,如果你不想每次都按几次空格键,可以在VS中设置,将Tab字符自动转换为空格;虽然缩进的字符数没有限制,但一般建议用4个空格。而且此时一定要用在文件开头添加#light指令。作用域(Scope)作用域是编程语言中的一个重要的概念,它表示在何处可以访问(使用)一个标识符或类型。所有标识符,不管是函数还是值,其作用域都从其声明处开始,结束自其所处的代码块。对于一个处于最顶层的标识符而言,一旦为其赋值,它的值就不能修改或重定义了。标识符在定义之后才能使用,这意味着在定义过程中不能使用自身的值。 let defineMessage = let message = "Help me" print_endline message // error 对于在函数内部定义的标识符,一般而言,它们的作用域会到函数的结束处。 但可使用let关键字重定义它们,有时这会很有用,对于某些函数来说,计算过程涉及多个中间值,因为值是不可修改的,所以我们就需要定义多个标识符,这就要求我们去维护这些标识符的名称,其实是没必要的,这时可以使用重定义标识符。但这并不同于可以修改标识符的值。你甚至可以修改标识符的类型,但F#仍能确保类型安全。所谓类型安全,其基本意义是F#会避免对值的错误操作,比如我们不能像对待字符串那样对待整数。这个跟C#也是类似的。 let changeType = let x = 1 let x = "change me" let x = x + 1 print_string x 在本例的函数中,第一行和第二行都没问题,第三行就有问题了,在重定义x的时候,赋给它的值是x + 1,而x是字符串,与1相加在F#中是非法的。 另外,如果在嵌套函数中重定义标识符就更有趣了。 let printMessages = let message = "fun value" printfn "%s" message; let innerFun = let message = "inner fun value" printfn "%s" message innerFun printfn "%s" message printMessages 打印结果: fun value inner fun valuefun value 最后一次不是inner fun value,因为在innerFun仅仅将值重新绑定而不是赋值,其有效范围仅仅在innerFun内部。递归(Recursion)递归是编程中的一个极为重要的概念,它表示函数通过自身进行定义,亦即在定义处调用自身。在FP中常用于表达命令式编程的循环。很多人认为使用递归表示的算法要比循环更易理解。 使用rec关键字进行递归函数的定义。看下面的计算阶乘的函数: let rec factorial x = match x with | x when x < 0 -> failwith "value must be greater than or equal to 0" | 0 -> 1 | x -> x * factorial(x - 1) 这里使用了模式匹配(F#的一个很棒的特性),其C#版本为: public static long Factorial(int n) { if (n < 0) { throw new ArgumentOutOfRangeException("value must be greater than or equal to 0"); } if (n == 0) { return 1; } return n * Factorial (n - 1); } 递归在解决阶乘、Fibonacci数列这样的问题时尤为适合。但使用的时候要当心,可能会写出不能终止的递归。匿名函数(Anonymous Function) 定义函数的时候F#提供了第二种方式:使用关键字fun。有时我们没必要给函数起名,这种函数就是所谓的匿名函数,有时称为lambda函数,这也是C#3.0的一个新特性。比如有的函数仅仅作为一个参数传给另一个函数,通常就不需要起名。在后面的“列表”一节中你会看到这样的例子。除了fun,我们还可以使用function关键字定义匿名函数,它们的区别在于后者可以使用模式匹配(本文后面将做介绍)特性。看下面的例子: let x = (fun x y -> x + y) 1 2let x1 = (function x -> function y -> x + y) 1 2let x2 = (function (x, y) -> x + y) (1, 2) 我们可优先考虑fun,因为它更为紧凑,在F#类库中你能看到很多这样的例子。 注意:本文中的代码均在F# 1.9.4.17版本下编写,在F# CTP 1.9.6.0版本下可能不能通过编译。 F#系列随笔索引页面