宁乡电商网站建设价格,做我女朋友的表白句的网站,学建模去什么学校,部署iis网站叠甲#xff1a;以下文章主要是依靠我的实际编码学习中总结出来的经验之谈#xff0c;求逻辑自洽#xff0c;不能百分百保证正确#xff0c;有错误、未定义、不合适的内容请尽情指出#xff01; 文章目录 1.何为控制语句2.控制语句-分支语句2.1.if2.2.switch 3.控制语句-循…叠甲以下文章主要是依靠我的实际编码学习中总结出来的经验之谈求逻辑自洽不能百分百保证正确有错误、未定义、不合适的内容请尽情指出 文章目录 1.何为控制语句2.控制语句-分支语句2.1.if2.2.switch 3.控制语句-循环语句3.1.while3.2.do...while3.3.for 4.控制语句-转向语句4.1.break4.2.continue4.3.goto 5.一些简单练习5.1.倒计时关机程序5.2.判断一个数是否为素数5.3.计算 1!2!...n! 的值5.4.求解两个数的最小公倍数和最大公因数5.4.1.公倍数和公因数概念5.4.2.朴素求得最小公倍数5.4.3.朴素求得最大公约数5.4.4.LCM 和 GCD 的关系5.4.5.使用辗转相除法解决 5.5.使用循环来清空缓冲区5.6.简易二分法查找算法 1.何为控制语句
一份 C 代码由多种语句组合而成, C 的语句主要分为下面几种
表达式语句函数调用语句控制语句复合语句空语句
我这里主要给您提及控制语句的相关语法其他的语句的相关概念您可以简单使用 google/bing/chatGPT 查询一下…
C 语言是结构化的程序设计语言而这些结构实际上是对现实生活中的一种抽象基本所有要做的事情都会包含“顺序、选择、循环”结构。
因此映射到 C 语言中就对应了三种控制语句
分支语句if、switch循环语句do...while、while、for跳转语句break、goto、continue、return
控制语句可以按照某种要求跳转到对应的代码中进行执行。
在分支语句和循环语句中依靠表达式的真假零表示真非零为假来跳转代码决定执行哪些语句。但跳转语句则无需在意真假是无条件的跳转代码。
另外多种控制语句直接也可以互相嵌套使用构成复杂的代码…
2.控制语句-分支语句
2.1.if
if 语句会分为单分支、双分支、多分支的情况一个 if 语句可以被视为一个整体经过逻辑判断后只能选择执行其中一个分支的代码。
而所谓的逻辑判断就是指判断表达式内的真假if 语句一旦遇到
表达式的整体结果判定为假的时候就会跳过跟在后面的代码块的所有内容继续判断下一个表达式的真假表达式的整体结果判定为真的时候就会立刻执行后面跟着的代码块的内容执行完后就抛弃其他分支的代码也就执行完一条 if 语句
而表达式的真和假根据 零表示真非零为假 来判断例如
对于代码 int num 1; num 3; 中 num 3 的整体结果就是真对于代玛 int num 3; 3 - num 中 3 - num 的整体结果就是假对于代码 int x 9; int y; y x; 中 y x 的整体结果就是真
//单分支语句的语法形式
if (表达式)
{语句0;
}//双分支语句的语法形式
if (表达式)
{语句1;
}
else
{语法2;
}//多分支语句的语法形式
if (表达式1)
{语句1;
}
else if (表达式2)
{语句2;
}
else if (表达式3)
{语句3;
}
//... 其他的 if 语句
else if (表达式n-1)
{语句n-1;
}
else
{ 语句n;
}这里举一个简单的例子当用户输入一个数字程序可以通过 if 语句检查该数字是否为正数、负数、零并给出相应的提示信息。
//if 语句的使用例子
#include stdio.h
int main()
{int num;printf(请输入一个整数: );scanf(%d, num);if (num 0){printf(这是一个正数\n);}else if (num 0){printf(这是一个负数\n);}else{printf(这是零\n);}return 0;
}写双分支语句、多分支语句时如果分支内只需要执行一条语句则可以省略代码块 {} 但是语句特别多的时候最好是要养成加上代码块 {} 的习惯。
//省略代码块
#include stdio.h
int main()
{int num;printf(请输入一个整数: );scanf(%d, num);if (num 0)printf(这是一个正数\n);else if (num 0)printf(这是一个负数\n);elseprintf(这是零\n);return 0;
}补充分支语句的书写习惯对比 //两种 if 书写习惯的对比
//代码1
if (表达式) {//代码块1
} else {//代码块2
}//代码2
if(表达式)
{//代码块1
}
else
{//代码块2
}代码1 和 代码2 是写的较多两种形式第一种强调是 if 以及内容是一个整体、第二种强调语句的“块”状两种写法都可以凭您的美感而言。 警告关于 的判断写法 分支语句判断相等时最常见的错误就是将 写作 漏写一个等号带来的困扰有时是灾难性的好在现代的编译器在很多情况下都会警告这一做法不过您依然不能太过依赖这点。 //有错误的代码
int num 0;
if(num 0) //这里原意是判断一个数是否为 0但这里变成了赋值语句把 num 又赋值为 0导致表达式的判断结果为 0也就是假后续代码块将不会被执行...
{printf(hehe\n);
}而关于这种写法有一种典型的检错方法就是把变量和常量判断反过来写。 //两种 的判断写法
//代码 1
int num 1;
if(num 0)
{printf(hehe\n);
}//代码 2
int num 1;
if(0 num) //这里把变量和常量反过来写了
{printf(hehe\n);
}代码2 的写法会比较好一点自带检错功能。假设 num 0 错写成了 num 0那么意义就不同了相当于 0 赋值给了 num编译器不一定会报错但是如果将 0 num 写成 0 num那么编译器就会报错因为无法使用变量赋值给一个常量。 不过这种方法也只能规避常量和变量的情况无法规避变量和变量之间比较的情况。 警告if 和 else 的就近对应原则悬空 else //有问题的代码
#include stdio.h
int main()
{int num1 0;int num2 1;if(num1 1) //第一个 ifif(num2 1) //第二个 ifprintf(hello);else //这个 else 故意不进行缩进printf(limou);return 0;
}实际上第二个 if 才是和 else 对应的这里是故意不缩进的这里发现程序结果是什么都不打印。这就是没有加上“代码块”的后果有可能造成和您内心所需不同的结果。但是如果加上代码块就会情况就好了一些。 //没问题的代码
#include stdio.h
int main()
{int num1 0;int num2 1;if(num1 1) //第一个 if{if(num2 1) //第二个 if{printf(hello);}}else{printf(limou);}return 0;
}这样代码就会在第一个表达式判断为假时执行 printf(limou); 从而打印 limou。因此加上代码块一是增加代码可读性二是减少逻辑错误。我个人推荐就算是单分支语句也最好加上“代码块”不要太过于省事。 2.2.switch
switch 语句的运行和 if 语句有些类似。
//switch 的语法形式
switch(整型表达式)
{case 值 1:语句 1;break;case 值 2:语句 2;break;case 值 3:语句 3;break;//...还可以添加更多个 case 语句case 值 n-1:语句 n-1;break;case 值 n:语句 n;break;default:语句;break;
}当 整型表达式 的计算值和 case 后的值相匹配时就会执行 case 后续的代码直到遇到 break 时才结束 switch 语句其中 default 子句是默认选项当 整型表达式 的结果均不满足 值1~值n 时就会执行 default 后的语句每条 switch 只能有一条 default 子句default 放在其他 case 之前之后都可以并没有顺序要求最后一个 default 的 break 不写也没有问题当然最好还是加上为好维护代码的时候可以减少 bug 的产生
case 后面的多条语句可以不加大括号当然最好还是写上逻辑会更加清晰一些… 补充if 和 switch 最大的差别在于 if 可以范围判断switch 是枚举判断。 注意一个上面细节 直到遇到 break 时才结束 switch 语句这句话很值得琢磨如果匹配到对应的 case 选项后没有遇到 break 怎么办可以看看下面的的代码当 b 1 或 2 时都是输出 hello 你好 default当 b 3 或 4 时都是输出 你好 default
//缺少 break 的 switch 语句
#include stdio.h
int main()
{int num 0;scanf(%d, num);switch (num){case 1:case 2:printf(hello );case 3:case 4:printf(你好 );default:printf(default);}return 0;
}因此可以利用这一特性来达到多种选择对应同一个结果的目的。
//多个 case 标签指向相同的语句块
#include stdio.hint main()
{int choice;//提示用户输入选择printf(请选择选项(1、2、3): );scanf(%d, choice);//使用switch语句根据用户的选择执行相应的操作switch(choice){case 1:case 2:case 3:{printf(您选择了选项 1 或 2 或 3\n);break;}case 4:{printf(您选择了选项 4\n);break;}default:{printf(无效的选项\n);break;}}return 0;
}警告在一个 case 结束时忘记写 break 语句也会引发一些逻辑错误这种错误只有通过调试解决编译器不会做任何警告或报错。 警告switch 后必须是整形变量表达式case 也必须是整形常量表达式不然无法运行。 3.控制语句-循环语句
3.1.while
//while 语法形式
while (表达式)
{//代码
}通过 while 语句可以在表达式为真时重复执行代码块内部的代码。
//打印出 0 到 9 的平方结果
#include stdio.h
int main()
{int i 0;while (i 10){printf(%d * %d %d\n, i, i, i * i);i;}return 0;
}3.2.do…while
//do...while 语法形式
do {//代码
} while (表达式);do...while 的显著特点就是至少会执行一次循环体里的语句使用场景少见但也不是没有。
//打印出 0 到 9 的平方结果(和上面代码是等价的)
#include stdio.h
int main()
{int i 0;do{printf(%d * %d %d\n, i, i, i * i);i;} while(i 10);return 0;
}警告while() 后面是有 ; 的不要遗漏… 3.3.for
//for 语法形式
for (初始迭代变量; 判断迭代变量; 更新迭代变量)
{语句;
}for 语句的执行顺序如下
初始迭代变量在循环开始时初始化迭代变量。这是在循环的第一次迭代之前执行的。通常用来初始化计数器或设置初始条件。判断迭代变量在每次迭代之前判断迭代变量的值是否满足循环条件。如果条件为真非零则继续执行循环体如果条件为假零则跳出循环执行循环后面的代码。执行循环体如果循环条件为真则执行循环体中的语句。循环体可以是单个语句或语句块可以包含任意合法的C语句。更新迭代变量在每次迭代结束后更新迭代变量的值。这一步通常用来递增或递减迭代变量以控制循环的迭代次数或改变循环条件。再次判断迭代变量在更新迭代变量之后再次判断迭代变量的值是否满足循环条件。如果条件为真则继续执行下一次迭代跳转步骤 2如果条件为假则退出循环执行循环后面的代码。退出循环当循环条件不满足时退出循环执行循环后面的代码。
实际上 for 语句是 while 在实践经验中总结出来的语法两者完全可以互相转换写出等价的循环代码。
//while 循环的代码
#include stdio.h
int main()
{printf(while 循环: \n);int i 0; //初始迭代变量while (i 5) //{printf(%d , i); //需要循环的代码i; //更新迭代变量}printf(\n);return 0;
}//for 循环
#include stdio.h
int main()
{printf(for 循环\n);for (int i 0; i 5; i) //这里就包含了 初始迭代变量; 判断迭代变量; 更新迭代变量{printf(%d , i); //需要循环的代码}printf(\n);return 0;
}上述的 while 和 for 代码是完全等价的而 for 循环语句从形式上比 while 有优势修改赋值、条件、执行语句一目了然。
这里有一个细节i 5 并没有写成 i 4两者实际都正确但是一般习惯写成左闭右开区间这样做可以提高可读性。尤其是在遍历数组的时候。
//使用左闭右开的范围
#include stdio.h
int main()
{int arr[5] { 1, 2, 3, 4, 5 };for (int i 0; i 5; i) //这里填 5 和数组大小是一样的程序员一看就知道这个循环会遍历 5 次比写 4 会多一层意义{printf(%d , i);}return 0;
}还有一点比较有趣的是 for 循环里的三个表达式是“表达式”因此还可以写更长的代码放入里面。
//内部表达式更长的 for 代码
#include iostreamint main()
{//在初始表达式中使用多个变量并初始化并且在迭代表达式中对两个变量进行迭代for (int i 0, j 10; i 5; i, --j){std::cout i: i , j: j std::endl;}//在判断表达式中使用复杂的逻辑判断int sum 0;for (int i 0; i 10 sum 100; i){sum i;std::cout i: i , sum: sum std::endl;}return 0;
}补充之前我提过给变量命名最好不要使用单个字母而是应该给予有意义的英文单词或英文词组。但是这里有一个小例外由于循环语句中的迭代变量 i、j、z 已经被大家熟用因此迭代变量不多、且主要目的仅仅是作简单遍历时使用这几个单字符也没有什么不好的因为大家都知道循环中的 i、j、z 是迭代变量无需再用其他的英文名做提示。 4.控制语句-转向语句
4.1.break
break 在 C 语言中有两种作用
跳出循环体只要在循环体内部执行了 break 语句就不再执行循环体的代码这种情况更加常见跳出 switch 选项
//使用 break 语句
#include stdio.h
int main()
{int i 0;while (i 10){printf(%d\n, i);if(5 i) //循环打印到 5 的时候{break; //直接跳出循环, 这样的话就不会打印 5 后面的数字了}i;}return 0;
}警告break 在多重循环中只会终止一层循环。 //使用 break 跳转多次循环的代码
#include stdio.h
int main()
{int matrix[3][3] {{1, 2, 3},{4, 5, 6},{7, 8, 9}};int target 5;int found 0;int row, col;for (row 0; row 3; row){for (col 0; col 3; col){if (matrix[row][col] target){found 1;break; //终止内部循环}}if (found) {break; //终止外部循环}}if (found){printf(找到了目标值 %d位置是 (%d, %d)\n, target, row, col);}else{printf(未找到目标值 %d\n, target);}return 0;
}4.2.continue
continue 则是跳出本次循环直接进入下一次的循环。
//使用 continue 语句
#include stdio.h
int main()
{int i 0;while (i 10){printf(%d\n, i);if (5 i) //如果 i 等于 5则跳过当前循环迭代直接执行下一次循环迭代{continue;}i;}return 0;
}4.3.goto
goto 是一种利用标记来无条件跳转的跳转语句由于没有任何条件容易被滥用导致代码逻辑一团糟应该尽可能少用。
//使用 goto 语句
#include stdio.h
int main()
{goto A_LABEL; //提前跳转了printf(haha\n); //这部分代码因为跳转而被忽略了A_LABEL:printf(hehe\n); //因此只会打印 hehe, 而不会打印 hahareturn 0;
}尽量少用 goto但是在多层循环中如果想实现跳出多层循环单纯靠 break 有些困难除非多写几个 break不然它只能从内层循环退回上一层循环这种情况就可以考虑使用 goto。
//多重循环下的 goto 代码
for(...) {for(...) {for(...) {if(disaster) //假设能够进入这个 if 语句的代码就说明出错了goto error; //直接跳出最外层, 不要再执行下去了, 避免出现其他更加意外的地方}}//...
}error://处理错误情况5.一些简单练习
这里我简单带你解决一些实际问题以便让您思考程序的编写过程和适应多种语句的组合嵌套。
5.1.倒计时关机程序 需求程序运行后倒计时 60 秒后开始关机如果输入 NO 则停止关机。 首先需要知道怎么在命令行使用命令来对计算机进行关机和取消关机这里以 Windows11 的电脑为例。使用 [winr] 输入 cmd 后回车得到命令行窗口输入以下指令进行关机。 上述就是使用命令行来操作电脑使用命令行可以替代很多鼠标能做的操作而且还可以做一些鼠标无法操作的早期的程序员在计算机中工作是不一定需要用到鼠标的程序员可以利用键盘输入不同的指令来对计算机进行操作。
因此只要我们知道对应的关机指令还有在 C 语言中通过某个库函数来调用对应的关机指令就行而这个库函数在 C 语言里面就是 systen()该函数接受一个 char* 指针指向的字符串可以执行字符串对应的指令最终结果在 cmd 中执行的效果相同。
//关机程序
#include stdio.h
#include string.h //strcmp() 库函数对应的头文件
#include stdlib.h //system() 库函数对应的头文件int main()
{char arr[20] { 0 };system(shutdown -s -t 60);while (1) //一直死循环就行{printf(您的电脑将在 60s 后关机输入 “NO” 则取消关机: );scanf(%s, arr); //获取输入if (strcmp(arr, NO) 0) //比较用户输入的字符串是否为 NO{//是则取消关机printf(取消关机\n);system(shutdown -a);break;}else{//不是则给与提示printf(错误指令\n);}}return 0;
}运行程序后就会进入关机状态只有输入 NO 才能取消关机。 补充这里再补充一个小点如果您希望把程序分享给其他人则可以在程序项目所在文件路径下寻找 .exe 文件然后把这个文件发送给别人即可… 发送给对方后直接双击这个文件就可以运行您编写的代码当然请您不要恶意整蛊别人… 5.2.判断一个数是否为素数 需求用户给定一个数 number判断该数是不是素数。 最简单的思路是如果 number 能够被 [2, number) 中任意一个数整除就说明不是素数也就是所谓的试除法
//试除法(除所有的数)
#include stdio.h
#include stdbool.h//判断一个数是否为素数
int IsPrime(int number)
{//小于等于 1 的数一定不是素数if (number 1){return 0;}//能被整除则不是素数for (int i 2; i number; i){if (number % i 0){return 0;}}//代码走到这里就说明 number 一定是素数return 1;
}int main()
{for (int num 0; num 100; num){if (IsPrime(num)){printf(%d is a prime number.\n, num);}else{printf(%d is not a prime number.\n, num);}}return 0;
}而如果我们仔细分析就会发现如果 number 能被整除就一定有一对数都可以对 number 进行整除操作。例如6 可以被 2 或 3 整除。因此如果采用试除法的话如果一个数不是素数就一定会在遍历除法中经历至少两次整除并且对应的两个因子是一小一大的。我们只需要其中判断一次取得一小的因子就行因此只需要遍历 number/2 次。
//试除法(除一半的数)
#include stdio.h
#include stdbool.h//判断一个数是否为素数
int IsPrime(int number)
{//小于等于 1 的数一定不是素数if (number 1){return 0;}//能被整除则不是素数for (int i 2; i number / 2; i){if (number % i 0){return 0;}}//代码走到这里就说明 number 一定是素数return 1;
}int main()
{for (int num 0; num 100; num){if (IsPrime(num)){printf(%d is a prime number.\n, num);}else{printf(%d is not a prime number.\n, num);}}return 0;
}还有一种更绝的办法可以少遍历更多的次数 numbera*b 中a 或 b 中至少有一个数小于等于 sqrt(number)则(2, sqrt(i)] 之间不能把 number 整除的数为质数。例如 100 的因数有1和100, 2和50, 4和25, 5和20, 10和10。看出来没有成对的因数中其中一个必然小于等于 100 的开平方另一个大于等于 100 的开平方。
//试除法(除开平方个数)
#include stdio.h
#include stdbool.h
#include math.h//判断一个数是否为素数
int IsPrime(int number)
{//小于等于 1 的数一定不是素数if (number 1){return 0;}//能被整除则不是素数for (int i 2; i sqrt(number); i){if (number % i 0){return 0;}}//代码走到这里就说明 number 一定是素数return 1;
}int main()
{for (int num 0; num 100; num){if (IsPrime(num)){printf(%d is a prime number.\n, num);}else{printf(%d is not a prime number.\n, num);}}return 0;
}再做一个优化由于只有 2 一个偶质数因此如果一个数是偶数就直接从源头上干掉一半的数据。
//试除法(利用偶素数特性)
#include stdio.h
#include stdbool.h
#include math.h//判断一个数是否为素数
int IsPrime(int number)
{//小于等于 1 的数一定不是素数if (number 1){return 0;}//偶数中是 2 的直接判断为素数, 否则直接判断为非素数if (number % 2 0){if (number 2){return 1;}else{return 0;}}//能被整除则不是素数for (int i 2; i sqrt(number); i){if (number % i 0){return 0;}}//代码走到这里就说明 number 一定是素数return 1;
}int main()
{for (int num 0; num 100; num){if (IsPrime(num)){printf(%d is a prime number.\n, num);}else{printf(%d is not a prime number.\n, num);}}return 0;
}补充如果可以的话您可以查询一些其他的质数筛选法。 5.3.计算 1!2!…n! 的值 需求编写代码根据用户输入的 n 值计算 1!2!...n! 的值 //双循环
#include stdio.h
int main()
{int ret 1;int sum 0;//输入最大的 nint maxN 0;scanf(%d, maxN);for (int i 1; i maxN; i) //计算从 1! 到 n! 中每一项 i!(其中 i∈[1, n]){ret 1; //每次加入循环先把 ret 都置为 0for (int j 1; j i; j) //计算 i! 的值{ret * j;}//i! 的最终值最后都存储在 ret 中sum ret; //sum}printf(%d\n, sum);return 0;
}//单循环
#include stdio.h
int main()
{int n 0;int ret 1;int sum 0;int manN 0;scanf(%d, manN);for (int j 1; j manN; j){ret * j;sum ret; //这里循环第一次就可以计算到 1! 循环第二次可以计算 1!2! ... 一直循环第 manN 次可以计算 1!2!...n!}printf(%d\n, sum);return 0;
}单循环的方式比双循环更加节约计算成本。
5.4.求解两个数的最小公倍数和最大公因数 需求根据用户给定的两个数得到最小公倍数Least Common Multiple和最大公因数Greatest Common Divisor。 5.4.1.公倍数和公因数概念
什么是公倍数直接举一个例子
数 12 倍数为 12, 24, 36, 48, ...数 18 倍数为 18, 36, 54, 72, ...
所以 12 和 18 的公倍数是 36, ...。
什么是公因数直接举一个例子
数 12 因数为 1, 2, 3, 4, 6, 12数 18 因数为 1, 2, 3, 6, 9, 18
所以 12 和 18 的公因数是 1, 2, 3, 6。
5.4.2.朴素求得最小公倍数
最朴素的想法就是按照定义出发先得到 12 和 18 的一众倍数然后检查是否有相同的数。而当 12 出现 12*18216 18 出现 18*12216 这个倍数时该数一定是两数的一个可以被确定的公倍数。
而如果 12*i, (i∈[1, 18)) 和 18*j(j∈[1, 12)) 的一众倍数中没有相等的公倍数那么 216 就一定是最小的公倍数。
因此可以确定公倍数的范围在 [1, 216] 之间。
抽象出来的话对于 number10 和 number20 来说公倍数的范围为 [1, number1*number2]。
因此我们可以先让 number1 和 number2 分别列出各自的倍数直到列举到 number1*number2如果两列倍数中没有找到公倍数 targer则最小公倍数是 number1*number2否则最小公倍数就是 target。
或者另外一个角度来说最小公倍数只存在于区间 [1, number1*number2] 中如果 number1 和 number2 能够被该区间中某一个数整除该数就是公倍数而如果从最小的数开始尝试能否整除找到的第一个能被整除的数就是最小公倍数。
//求解两个数的最小公倍数(朴素解法)
#include stdio.h//找到两个数的最小公倍数
int FindLCM(int num1, int num2) //Least Common Multiple 即 LCM
{int maxMultiple num1 * num2;//从 [1, maxMultiple] 中逐个检查直到找到最小公倍数for (int i 1; i maxMultiple; i){//找到同时是两个数倍数的数if (i % num1 0 i % num2 0){return i; //返回最小公倍数}}return maxMultiple; //如果没有找到, 则返回两个数的乘积
}int main()
{int num1 0, num2 0;//从用户输入获取两个数printf(请输入两个正整数: );scanf(%d %d, num1, num2);//调用函数找到最小公倍数并打印结果printf(最小公倍数是: %d\n, FindLCM(num1, num2));return 0;
}5.4.3.朴素求得最大公约数
最朴素的想法就是从定义出发12 和 18 中如果有公因数则分别列出各自的因数列表此时一定会发现两列的因数中最大那个数就是自己这个数本身例如 1, 2, 3, 4, 6, 12 都是 12 的因数最大就是 12 自己本身。
而求解公因数就是求 {1, 2, 3, 4, 6, 12} 和 {1, 2, 3, 6, 9, 18} 两个集合的并集因此两数的最大公因数一定要处于 [1, 12] 中。
只需要倒过来遍历 [1, 12] 中每一个将每一个数都拿去给 18 和 12 除即可。
直到两个数都可以被整除就找到最大公因数。
抽象出来的话对于 number1 和 number2找到 minNumbermin(number1, number2)确定最大公因数就在 [1, minNumber] 中遍历其中每一个 target直到 number1%target 0 numebr2%target 0则 target 就是最大公因数。
//求解两个数的最大公因数(朴素解法)
#include stdio.hint Min(int num1, int num2)
{if (num1 num2){return num2;}else //num1 num2{return num1;}
}int FindGCD(int num1, int num2)
{int minNum Min(num1, num2);for (int target minNum; target 1; target--){if (num1 % target 0 num2 % target 0)return target;}return -1; //尽管这个 return -1 不太可能会被执行, 但是我为了编译器不进行警告, 还是加了这个返回 -1 就表示出错的返回值
}int main()
{int num1 0, num2 0;//从用户输入获取两个数printf(请输入两个正整数: );scanf(%d %d, num1, num2);//调用函数找到最小公倍数并打印结果printf(最大公约数是: %d\n, FindGCD(num1, num2));return 0;
}5.4.4.LCM 和 GCD 的关系
用最大公约数GCD和最小公倍数LCM的关系可以来互相求解根据以下公式 L C M ( a , b ) a × b G C D ( a , b ) LCM(a, b) \frac{{a \times b}}{{GCD(a, b)}} LCM(a,b)GCD(a,b)a×b 只要知道两个数的最小公倍数就可以知道最大公约数因此缺陷也就是需要先知道两个数的最小公倍数。反过来只要知道两个数的最大公约数就可以知道最小公倍数。
//求解两个数的最大公因数(关系解法)
#include stdio.h//找到两个数的最小公倍数
int FindLCM(int num1, int num2) //Least Common Multiple 即 LCM
{int maxMultiple num1 * num2;//从 [1, maxMultiple] 中逐个检查直到找到最小公倍数for (int i 1; i maxMultiple; i){//找到同时是两个数倍数的数if (i % num1 0 i % num2 0){return i; //返回最小公倍数}}return maxMultiple; //如果没有找到, 则返回两个数的乘积
}//找到两个数的最大公约数
int FindGCD(int num1, int num2)
{return (num1 * num2) / FindLCM(num1, num2);
}int main()
{int num1 0, num2 0;//从用户输入获取两个数printf(请输入两个正整数: );scanf(%d %d, num1, num2);//调用函数找到最小公倍数并打印结果printf(最大公约数是: %d\n, FindGCD(num1, num2));return 0;
}补充关于对应的数论证明待补充… 5.4.5.使用辗转相除法解决
辗转相除法是求解最大公因数的一种高效方法来源于 《几何原本》 中古希腊人的智慧这里简单提及一下使用过程。
假设有数 12 和 30求两个数的最大公因数则直观来看如下图 转化为代码如下注意下面代码很精妙。
//求解两个数的最小公倍数和最大公因数(辗转相除法)
#include stdio.h//计算最大公因数
int FindGCD(int num1, int num2) //算法可以保证 num1 在计算过程中为较大值
{while (num2 ! 0) //使用辗转相除法来计算{int temp num2; //缓存num2 num1 % num2; //计算边num1 temp; //计算边}return num1;
}//计算最小公倍数
int FindLCM(int num1, int num2)
{return (num1 * num2) / FindGCD(num1, num2); //利用 LCM 和 GCD 的关系来通过最大公因数计算最小公倍数
}int main()
{int num1 0, num2 0;//从用户输入获取两个数printf(请输入两个正整数: );scanf(%d %d, num1, num2);//求出最小公倍数和最大公约数printf(最小公倍数是: %d\n, FindLCM(num1, num2));printf(最大公因数是: %d\n, FindGCD(num1, num2));return 0;
}5.5.使用循环来清空缓冲区 需求清空缓冲区避免意外程序出现意外的结果。 这里还需要给您介绍一些连续使用循环语句来不断获取多次输入的代码还会给您介绍新的输入输出函数。首先介绍一对输入输出函数 getchar() 和 putchar()他们的作用是获取一个字符、输出一个字符。
//使用 getchar() 和 putchar()
#include stdio.h
int main()
{int ch 0; //注意 getchar() 的返回值是 intgetchar()读取成功返回 ASCII 码值故用整型接受没有问题(读取失败返回的是 EOFEOF 的值是 -1所以用 int 接受 getchar 的返回值会更好)ch getchar();putchar(ch);return 0;
}然后再给您科普一下缓冲区的概念缓冲区是计算机内存中的一块空间区域用于临时存储数据以便在程序执行过程中进行读取和写入操作。在 C 语言中scanf() 和 printf() 这两个函数都使用了缓冲区输入缓冲区和输出缓冲区。
scanf() 函数用于从标准输入简单理解为键盘读取输入并根据指定的格式将输入数据存储到变量中。在 scanf() 函数中用户输入的数据首先被存储在输入缓冲区中然后 scanf() 再根据格式指示符从缓冲区中读取数据。
scanf() 函数在第一次遇到空白字符空格、制表符、换行符时会停止从输入缓冲区中读取输入直到遇到非空白字符。而如果再次读取到空白字符就会停止读取并且把空白字符放回输入缓冲区中。如果 scanf() 函数后面还有其他输入函数比如 getchar()它可能会读取到之前输入的换行符或其他残留在输入缓冲区中的字符因此我们就需要将缓冲区中的字符清除避免误读。
考虑没有清理缓冲区的代码
//未清理缓冲区
#include stdio.hint main()
{char ch 0;printf(请输入一个字符: );scanf(%c, ch); //输入非空白字符后输入 \n printf(请确认输入(Y/N): );char flag getchar();if (Y flag){printf(确认成功\n);}else{printf(确认失败\n);}return 0;
}上述代码中如果输入字符后回车还没输入 Y/N 进行确认就提前显示出“确认失败”这是因为 scanf() 读取完字符后再次遇到了空白字符把空白字符留在了缓冲区中。而使用 getchar() 时就会被该函数读取走因此 \n ! Y 于是输出“确认失败”。
可以简单验证一下在使用 scanf() 输入字符后使用不同的空白字符结束输入看看 getchar() 是不是发生了误读。
//未清理缓冲区, 并且验证 getchar() 是否发生了误读
#include stdio.hint main()
{char ch 0;printf(请输入一个字符: );scanf(%c, ch); //输入非空白字符后输入 \n 或者 或者 \t 结束输入试试 printf(请确认输入(Y/N): );char flag getchar();if (\n flag){printf(意外读取到\\n\n);}else if (\t flag){printf(意外读取到\\t\n);}else if ( flag){printf(意外读取到 \n);}if (Y flag){printf(确认成功\n);}else{printf(确认失败\n);}return 0;
}如果我们希望清理输入缓冲区的代码我们可以利用控制台输入最后一个输入字符始终是 \n 这一点来使用循环清空其他空白字符。
//封装清理缓冲区的 Empty()
#include stdio.hvoid Empty(void)
{char ch 0;do{ch getchar();} while (ch ! \n); //在控制台中 scanf() 输入结束后最后一次输入一定是换行符, 因此不用考虑其他空白字符, 除了 \n 其它滞留在缓冲区中的字符都会陷入循环而被读走, 避免以下后续输入//但是如果是文件读取, 清理情况就有些不同, 这就会涉及到文件 IO 的知识了, 以后我再来带您重新回顾这个知识(除了使用键盘输入, 实际上还可以使用文件来输入)...
}//有清理缓冲区
#include stdio.hvoid Empty(void)
{int ch 0;while ( ((ch getchar()) ! EOF) ){;}
}int main()
{char ch 0;printf(请输入一个字符\n);scanf(%c, ch);printf(请确认输入(Y/N)\n);Empty(); //调用函数来清理缓冲区char flag getchar();if (Y flag){printf(确认成功\n);}else{printf(确认失败\n);}return 0;
}我可以带您观察以下调试过程。 补充这里我给您介绍的重点是清空输入缓冲区而实际上有些时候需要清空输出缓冲区这个以后结合 fflush() 进行展开。 警告小心写出死循环的代码仔细分析好循环条件后再编写循环代码。 警告for 内部三个表达式是可以进行一定的省略的但是最好不要随便乱省略比如下面的代码 //不省略表达式
int main()
{int i 0;for(i 0; i 3 ; i){for(j 0; j 3 ; j){printf(hello\n);}} //打印九次 helloreturn 0;
}如果修改后 //省略表达式
int main()
{int i 0;int j 0;for(; i 3 ; i){for(; j 3 ; j){printf(hello\n);}} //只会打印三次 hello而不是 9 次原因在于 j 没有被重新初始化为 0return 0;
}5.6.简易二分法查找算法 题目给定任意一个有序的整型数组例如 int arr[] { 1, 4, 9, 10}在其中查找是否存在数 target。 最简单粗暴的方法就是遍历查找遇到一个数组元素就和 target 进行比较一旦相等就说明存在。
//遍历查找
#include stdio.h
int main()
{//输入需要查询的数字int target 0;scanf(%d, target);//遍历查找 target 是否存在与于 arr 数组中int arr[] { -2, 1, 3, 4, 6, 8, 9 };for (int i 0; i sizeof(arr)/sizeof(int); i){//如果数组中某个数和 target 相等就说明 arr 中存在和 target 相等的数字if(arr[i] target)printf(target %d 存在, arr[i]);}return 0;
}但这样做的效率比较低我们来了解一种叫做二分查找的思想这种思想实际上就是高中数学中的二分求解法考虑一种小游戏游戏的目的是猜对数字假设 B 提前在一个数组中准备好了一个数字由 A 来进行猜测。A 向 B 提供数字而 B 只能说“数大了”、“数小了”、“是这个数”则 A 会根据 B 的回答调整自己的答案直到猜对。
假设被猜的数字的范围是 1~10B 准备好数字 7A 只知道 B 准备的数最小是 1最大是 10并且还知道所有的数从左到右是递增的至于中间是什么数一概不知
A 此时知道答案一定在 [1, 10] 范围中直接问 B5 是不是答案B 回答“数小了”A 此时知道答案一定在 [6, 10] 范围中直接问 B8 是不是答案B 回答“数大了”A 此时知道答案一定在 [6, 7] 范围中直接问 B7 是不是答案B 回答“是这个数”
到这里 A 一直取范围的中间值只用了 3 次就找到列表中如果遍历数组的话可能需要更多的查询次数。
//二分查找
int main()
{int arr [] { -2, 1, 3, 4, 6, 8, 9 }; //有序数组int i 0;int k 0;scanf(%d, k);int sz sizeof(arr) / sizeof(arr [0]);//计算数组的大小int left 0;//左下标int right sz - 1;//右下标int flag 0;int mid 0;while (left right){//mid (left right) / 2;//中间下标但是有点小问题, 加起来容易溢出mid left (right - left) / 2;//中间下标这样求平均值比较好if (arr [mid] k){left mid 1;}else if (arr [mid] k){right mid - 1;}else{printf(找到了下标是 %d\n, mid);flag 1;break;}}if (0 flag){printf(没有找到\n);}return 0;
}