系列前言
本系列是【C语言从青铜到王者】系列的子系列,属于实战讲演部分。博主将实战讲演部分单独成篇,希望对每一个题目都进行从无到有的思路开导、代码实现的过程分析、优化算法的方法讲解。博主会尽最大可能把复杂的问题讲清楚,既为看官,也为自己,虽学无止境,望精益求精。
本篇内容摘要
第一期王者实战训练营重点在于通过实例来熟悉C语言编程的方法和分支循环语句的应用。
算法篇计算性较强,应用篇趣味性较强
算法篇:
经典算术题
二分查找法
应用篇:
让程序“动起来”:标题浮现小程序
模拟密码输入:输密码小程序
人生第一款小游戏:猜数字小游戏
人生第一款“黑客软件”:强制关机小程序
本篇预备知识
算法篇
经典算术题
用程序解决算术题是我们计算机被发明出来的初衷。让我们从基础的算数应用开始,将我们所学的知识都用起来吧!
计算 n!
知识补充: n!读作“n的阶乘”,意思是从1一直乘到n,得到的结果就是n!
例如:3!= 3 × 2 × 1 = 6; 5! = 5 × 4 × 3 × 2 × 1 = 120
思路分析:
n!中需要的数字是公差为1的等差数列,然后每次都执行的同样的乘法操作,优先考虑使用循环结构(我用的是for循环)来解决这道题
思路转化成代码:
首先是n的阶乘,那我们就先需要一个变量从键盘接收这个n。我们实际上就是需要计算1×2×3× … ×n,可以从1开始,每次乘比它自己大1的数,所以我们需要一个循环变量来改变每次乘的值,需要另一个变量来记录累乘以后的值,最后输出累乘以后的值
给出这种方法的代码(从1乘到n):
#include<stdio.h>
int main()
{
int i = 0;
int n = 0;
int ret = 1;
scanf("%d", &n);
for (i = 1; i <= n; i++)
{
ret *= i;
}
printf("%d", ret);
}
我们还可以不计算1×2×3× … ×n的值,反过来计算n × … × 3 × 2 × 1,这样的话我们for循环的初值就是n,for循环的调整部分就是i- -(i=i-1)
给出这种方法的原码(从n乘到1):
#include<stdio.h>
int main()
{
int i = 0;
int n = 0;
int ret = 1;
scanf("%d", &n);
for (i = n; i > 0; i--)
{
ret *= i;
}
printf("%d", ret);
}
计算 1!+ 2!+ 3!+ … + n!
思路分析:
这道题其实是在第一题的基础上增加了一些东西,上一题我们已经通过代码计算出了n!,现在需要我们求出1!+2!+3!+ … +n!,可以看到,整个式子中出现的数字又是一个公差为1的等差数列,而且如果我们把1!、2!、n!看成一组确定数,那其实就是重复的进行加法运算。整体的程序逻辑框架同样考虑循环结构(我用的是for循环)。由于上一题计算n!的时候也是用了循环,所以我们可能会出现循环里面套循环的情况,也就是循环嵌套
思路转化成代码
同样,先思考我们需要哪些变量。上一题中需要的变量都不能动(键入的n、循环变量i、记录累乘的变量ret)。由于又来了一个循环,所以需要一个新的循环变量j;然后每次计算出的n!要累加到一个数上,它的结果作为整个式子的结果,所以需要一个累加量sum。程序的思路就是for循环里面套for循环,内部的循环负责计算n!,外部的循环负责把每次计算的结果累加。由于每次计算累乘的时候都需要从1开始,所以在每次内循环执行前先把ret初始化
给出原码:
#include<stdio.h>
int main()
{
int i = 0;
int j = 0;
int n = 0;
int ret = 1;
int sum = 0;
scanf("%d", &n);
for (j = 1; j <= n; j++)
{
ret = 1;//一定要在计算n!前将ret初始化为1
for (i = 1; i <= j; i++)
{
ret *= i;
}
sum += ret;
}
printf("%d", sum);
return 0;
}
算法优化:
思路一代码的效率不高,主要原因是使用了循环嵌套增加了代码的时间复杂度,我们看看一层循环能不能解决问题。
改进思路:
由于每次计算出前一个n!时,(n+1)!其实就是(n+1)*(n!),也就是说我们可以直接把上一次计算好的阶乘数拿来用,不必要再从头算起了。操作就是把每次循环中初始化ret的语句删除,这样每次上次计算好的ret都可以拿来用了
给出原码:
#include<stdio.h>
int main()
{
int i = 0;
int n = 0;
int ret = 1;
int sum = 0;
scanf("%d",& n);
for (i = 1; i <= n; i++)
{
ret *= i;
sum += ret;
}
printf("%d", sum);
return 0;
}
这样的算法优化我们自己想不一定能想到,博主也是看别人的学来的。编程的世界很大,作为初学者很多知识都是沉淀了很久的,在精力有限的情况下,我们既要培养自己独立思考的能力,也要学会模仿学习,避免重复造轮子的情况。
计算两数的最大公约数
思路分析:
约数就是因数。最大公约数的定义是“相同且最大的因数”。既然是因数,那它就肯定不会超过两个数中的任何一个数,既然是公因数,那它就得同时能被两数整除,既然是最大公因数,我们可以从大往小遍历各个数,第一个出现的公因数就是最大公因数
思路转化成代码:
既然是两个数,那么先得有两个变量从键盘接收这两个数的值;然后我们需要比较出较小的那个数,把它的值作为我们遍历的起点,就需要第三个中间变量来完成换值的操作。所以我们程序应当分两步走,先把两个数中较小的那个数的值取出,然后从这个值开始从大到小遍历,找出第一个公因数
给出原码:
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
int c = 0;
scanf("%d %d", &a, &b);
if (a > b)
{
c = a;
a = b;
b = c;
}
int i = 0;
for (i = a; i > 0; i--)
{
if (a % i == 0 && b % i == 0)
{
printf("%d", i);
}
}
return 0;
}
讲解几个点吧:
if (a > b)
{
c = a;
a = b;
b = c;
}
这个代码块的作用是保证a中存储的一定是小值,b中的一定是大值。整体的思路就是:如果a<b,什么都不执行,如果a>b,就交换a和b的值。
for (i = a; i > 0; i--)
这个语句的作用是用小的数往下遍历
if (a % i == 0 && b % i == 0)
{
printf("%d", i);
}
这个代码块的作用是判断i是不是两个数的公约数,是的话就打印出来
算法优化:
计算最大公约数的一个古老的问题,它有一个非常经典且高效的算法:辗转相除法
为好学的小伙伴给出原理:
辗转相除法的原理
我们直接来看这个方法是怎么使用的:
给出两数a、b,欲求两数最大公约数。设c=a%b,若c等于0,则c为两数最大公约数,若c不等于0,则设d=b%c,若d等于0,则d为两数最大公约数,若d不等于0,则设e=c%d … 也就是说,每次都让三个数中第二小的数去对最小的数取模,直到模为零,第二小的数就是最大公约数。
给出原码:
#include<stdio.h>
int main()
{
int m = 0;
int n = 0;
scanf("%d%d", &m, &n);
int t = 0;
while (t = m % n)
{
m = n;
n = t;
}
printf("%d", n);
}
这种算法还有一个好处就是不需要比较两个数的大小,因为如果是用小的数对大的数取模,算法就会自动交换两个数。
会计算最大公约数以后我们如何计算两个数的最小公倍数呢?
这里给出一个用最大公约数计算最小公倍数的思路:
假设a、b的最大公约数是c,最小公倍数是d a×b=a/c×c×b/c×c=(a/c×b/c×c)×c=d×c
即:两数相乘=两数的最大公约数×两数的最小公倍数
打印1000-2000年间的闰年
网上对闰年的定义有很多,我取最常见的一种来说:
可以被4整除的,但是不能被100整除的,但是又可以被400整除的公历年份
思路分析:
我们可以遍历1000-2000的所有数,然后对每个数都审查一下,符合条件的打印出来,就是闰年
思路转化成代码:
遍历1000-2000的所有数,这里需要使用循环结构;然后判断每个数是不是同时满足这三个条件,这里需要用到选择结构;由于是多重复合条件,可能需要用到逻辑表达式
#include<stdio.h>
int main()
{
int year = 0;
for (year = 1000; year <= 2000; year++)
{
if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
printf("%d ", year);
}
}
打印100-200间的素数
素数就是质数,因数只有1和它自己的数就是素数
思路分析:
同样是属于在一个范围内找出满足特定条件的数的问题。思路就是遍历每个数然后做判断。这里的判断是因数只有1和它自己,那我们就让因数从2开始增加,如果第一个因数就是它自己,说明它就是素数
思路转化成代码:
遍历所有数需要循环结构;检查素数需要循环结构来试验因数和选择结构来判断第一个因数是不是它自己
给出原码:
#include<stdio.h>
int main()
{
int i = 0;
int num = 0;
for (i = 100; i < 200; i++)
{
for (num = 2; num <= i; num++)
{
if (i % num == 0)
break;
}
if (num == i)
printf("%d ", i);
}
return 0;
}
算法优化一阶:
我们考虑一个常识性的问题:偶数是不是素数?答案是否定的。所以我们可以直接遍历100-200间的奇数
#include<stdio.h>
int main()
{
int i = 0;
int num = 0;
for (i = 101; i < 200; i+=2)
{
for (num = 2; num <= i; num++)
{
if (i % num == 0)
break;
}
if (num == i)
printf("%d ", i);
}
return 0;
}
算法优化二阶:
再考虑一个问题,如果a×b=c,那a和b就同时是c的因数,我们把a和b叫做“一对因数”,那么,这一对因数中较小的数就不可能超过这个数的平方根。
所以我们判断因数的范围就不需要从2开始遍历到这个数了,只需要遍历到这个数的平方根即可
计算平方根的函数是sqrt函数,sqrt(a)就是a的平方根。使用sqrt函数需要引用头文件math.h
给出原码:
#include<stdio.h>
#include<math.h>
int main()
{
int i = 0;
int num = 0;
int a = 0;
for (i = 101; i < 200; i+=2)
{
for (num = 2; num <= sqrt(i); num++)
{
a = 0;
if (i % num == 0)
a++;
if (1 == a)
break;
}
if (0 == a)
printf("%d ", i);
}
return 0;
}
二分查找法
查找法最基础的应用就是在一个有序数组中找到指定数。
比如我们有一个1-10的有序升序数组,我们需要把7找出来并且输出到屏幕上
顺序查找法
思路分析:
我们可以直接从前往后遍历数组,把每个数和7做比较,相等的那个数就是7
思路转化成代码:
存储有序数组需要用到数组。遍历需要用到循环。比较是否等于7需要用到选择结构做判断。打印需要printf.
顺序查找原码:
#include<stdio.h>
int main()
{
int arr[] = {
1,2,3,4,5,6,7,8,9,10 };
int k = 7;
int i = 0;
for (i = 0; i < 10; i++)
{
if (arr[i] == k)
{
printf("%d", arr[i]);
}
}
return 0;
}
二分查找法
有的小伙伴要问了:我都有一种查找法了,我为啥还要学另一种呢?我们来想象这样一个有序数组,它是从1开始到232(约等于43亿),如果我们从中随便找一个数,比如3267467238,用顺序查找法我们需要查找3267467238次
如果我们使用二分查找法呢?答案是无论是什么数,至多只需要32次就能查找出来。是不是突然感受到算法的作用了呢?
二分查找法的思路和原码讲解:
二分查找法关键在“二分”二字,它的思路是每次直接取出这个有序数组的最中间的那个数,然后让它和我们指定查找的数做比较,如果这个中间数小了,说明比中间数小的所有数(包括这个中间数)都不是指定数,可以直接排除,所以剩下的就只剩值为“中间数+1”的数到最大数这个范围内的数;如果这个中间数大了,说明比中间数大的所有数(包括这个中间数)都不是指定数,可以直接排除,所以剩下的就只剩值为“中间数-1”的数到最小数这个范围内的数;如此往复的比较,循环夹逼,终有一次,会发现中间数就是我们需要找的数,那么我们就找到了这个数
二分查找原码:
#include<stdio.h>
int main()
{
int arr[] = {
1,2,3,4,5,6,7,8,9,10 };
int k = 7;
int sz = sizeof(arr) / sizeof(arr[0]);
int left = 0;
int right = sz - 1;
while (left<=right)
{
int mid = (left + right) / 2;
if (arr[mid] < k)
{
left = mid + 1;
}
else if (arr[mid] > k)
{
right = mid - 1;
}
else
{
printf("找到啦!数字是:%d\n", arr[mid]);
break;
}
}
if (left > right)
{
printf("没找到,是不是数字超出范围了呢\n");
}
return 0;
}
我们来逐句理解一下:
int arr[] = {
1,2,3,4,5,6,7,8,9,10 };
定义一个有序数组
int k = 7;
定义我们需要查找的那个数
int sz = sizeof(arr) / sizeof(arr[0]);
计算数组元素的个数(数组元素总长度/数组中每一个元素的长度)
int left = 0;
int right = sz - 1;
定义左右下标,初始化为整个数组的左右下标(数组下标从0开始)
while (left<=right)
由于是升序数组,当左下标小于等于右下标时进入循环
来看看循环体:
int mid = (left + right) / 2;
定义中间数
if (arr[mid] < k)
{
left = mid + 1;
}
else if (arr[mid] > k)
{
right = mid - 1;
}
else
{
printf("找到啦!数字是:%d\n", arr[mid]);
break;
}
这段对应前文的“二分查找法思路”
if (left > right)
{
printf("没找到,是不是数字超出范围了呢\n");
}
这段代码实际上增加了程序的健壮性(健壮性的意思就是程序可以应对各种情况不至于因为bug崩溃)。如果我们的指定数不在数组中,就找不到。这是由于当指定数不在数组中时,程序从左右夹逼到最后仍然找不到数就会出现交叉的情况,这个情况就说明没找到数,指定数应该是超出范围了。
二分查找法将查找的速度从线性级别提升到了指数级别,意义重大,希望小伙伴们重点掌握。
应用篇
让程序“动起来”:标题浮现小程序
C语言还可以做动画?相信读到这的你会有点不可思议。今天就让我们来编写一个让标题逐渐浮现的小程序吧
思路分析:
程序的思路是让标题逐渐出现,那我们就需要一行“***************”作为幕布,来遮掩我们的标题,然后把幕布的前后元素依次与标题的字符做交换,直至标题完全显现。
思路转化成代码:
我们需要两个等长的字符串,一个是我们的标题,一个是“幕布”,所以需要定义的变量就是两个字符串数组。由于需要使用数组,为了明确数组下标,我们先计算出数组元素个数,然后就可以确定左下标和右下标。再让两字符串最左和最右的元素分别交换。这样我们就打印出了这个动画的“每一帧”
然后我们介绍两个知识:一个是Sleep()函数,它的作用是让内容在屏幕上停留一段时间,括号内可以填数字代表停留的时间,单位是ms;另一个是系统命令system(“cls”),cls是clean screen,意思是清空屏幕。Sleep函数的头文件是windows.h,system函数的头文件是stdlib.h,使用它们前都得先引用头文件
我们在每次打印出“一帧”的语句后使用sleep函数来让它停留一段时间和system(“cls”)来清空屏幕,这样就实现了动画的效果
给出原码:
#include<stdio.h>
#include<string.h>
#include<windows.h>
#include<stdlib.h>
int main()
{
char arr1[] = "welcome to the world of C !";
char arr2[] = "###########################";
int left = 0;
int right = strlen(arr1) - 1;
while (left <= right)
{
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf("%s\n", arr2);
Sleep(200);
system("cls");
left++;
right--;
}
printf("%s\n", arr2);
return 0;
}
模拟密码输入:输密码小程序
我们每天需要登录游戏,登录社交软件,登录银行账户…生活中输入密码的情况无处不在。今天就让我们来编写一个输入密码的小程序吧
知识补充一:getchar和putchar
由于博主才疏学浅,在这里引用大佬的解释,真的是没有一句废话。我配了张图方便大家理解
getchar()
用于读取用户从键盘输入的单个字符,它有一个整型的返回值,当发生读取错误的时候,返回字符串EOF(整型值为-1),当读取正确的时候,它会返回用户从键盘输的第一个字符的ASCII码。
当程序调用getchar运行程序时,getchar就等着用户从按键输入,用户输入的字符被存放在键盘缓冲区中.直到用户按回车为止(回车字符\n也会被放在缓冲区中),当用户键入回车之后,getchar才开始从输入流中每次读入一个字符,输入的字符不只一个的时候,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完之后,才等待用户按键。
getchar函数输入数字也按字符处理。
单个的getchar函数输入多于一个字符时,只接收第一个字符。
putchar()
向终端输出一个字符。其格式为putchar(ch),其中ch可以是被单引号(英文状态下)引起来的一个字符,可以是介于0~127之间的一个十进制整型数(包含0和127,超过127就不是ASCII码了),也可以是事先用char定义好的一个字符型变量。
当c为一个被单引号(英文状态下)引起来的字符时,输出该字符(该字符也可为转义字符 )。
当c为一个介于0~127(包括0及127)之间的十进制整型数时,它会被视为对应字符的ASCII代码,输出该ASCII代码对应的字符;
当c为一个事先用char定义好的字符型变量时,输出该变量所指向的字符。
当整型变量ch超出8位变量的范围时,ch则会变强制转化为8位变量(即取其低八位传过去输出),当为负数的时候,由于计算机存储负数是用补码表示的,所以传过去的二进制补码也被当做正数处理,也是取其低八位
getchar()和putchar()函数包含在头文件stdio.h中,使用时需先声明此头文件
原文连接:详解getchar和putchar
#include<stdio.h>
int main()
{
int ch = 0;
ch = getchar();
putchar(ch);
return 0;
}
运行效果就是:输入一个字符,按下回车,屏幕上会在下一行打印这个字符
- 小问题解答:为什么ch是整型变量却可以接收字符?
我们可以看一下getchar函数到底长啥样的,右键getchar,选择转到定义
可以看到getchar()的返回值是int
使用代码清空缓冲区
在这里引出getchar的目的其实来学习编写一个非常常用的清空缓冲区的小程序:
int tmp = 0;
while ((tmp = getchar()) != '\n')
{
;
}//清空缓冲区里的所有字符
while后的条件是getchar获取的字符不是“换行/回车”时,就跳出循环,其他所有字符都执行循环
这个循环的循环体是一个“;”,这是啥?这表示的是一条空语句,也就是说我们啥都不干。所以这个循环的目的就是:getchar拉取缓冲区所有除了“回车”的字符,拉取到回车的时候跳出循环,由于回车是我们向缓冲区输入字符的终止命令,所以回车必然是缓冲区的最后一个字符。这样一来,就实现了清空缓冲区
知识补充二:字符串比较函数strcmp
使用函数需要引用头文件string.h
语法是
strcmp (字符串1,字符串2);
结果会返回整型值,
若字符串1和字符串2的每个字符都相等,则返回0;
若字符串1与字符串2不相等:
比较第一对不相等的字符的ASCII码值,谁的值大就代表哪个字符串大
若字符串1>字符串2,则返回正数;
若字符串1<字符串2,则返回负数。
代码实现模拟输入密码
我们来编写一个模拟我们日常输入密码的小程序
程序的功能是这样的:我们先自己创建密码,然后确认密码,然后输入密码。有三次输入密码的机会,一次正确就跳出循环,如果都输错了就没有机会了。
思路分析:
我们需要进行三个步骤,一个是创建密码,一个是确认密码,一个是登录时输入密码
思路转化成代码:
首先考虑需要创建的变量,我们需要创建三个字符数组来存储这三次键入的密码。每次输入密码后需要比较密码,需要用到刚刚讲过的字符串比较函数,需要用到判断结构,字符串相等就说明密码正确,不等即错误。仅有三次输入密码的机会,需要用到循环结构,在次数为三的时候跳出循环。确认密码失败的时候要结束程序,使用goto语句直接跳转到程序末尾。每次输入密码时,为了保证读取的就是当前输入的东西,要使用刚刚讲的技巧清空缓冲区的字符。
给出原码:
#include<stdio.h>
#include<string.h>
int main()
{
char password[20] = {
0 };
printf("请创建初始密码:\n");
scanf("%s", password);
int tmp = 0;
while ((tmp = getchar()) != '\n')
{
;
}//清空缓冲区里的所有字符
printf("请确认密码:\n");
char password1[20] = {
0 };
int i = 0;
for (i = 0; i < 3; i++)
{
scanf("%s", password1);
if (strcmp(password, password1) == 0)
{
printf("确认密码正确,已自动储存\n");
break;
}
else
{
printf("确认密码失败,请重新输入\n");
}
}
if (i == 3)
{
printf("确认密码错误,退出程序\n");
goto end;
}
int tmp1 = 0;
while ((tmp1 = getchar()) != '\n')
{
;
}
printf("请输入密码:\n");
char password2[20] = {
0 };
int j = 0;
for (j = 0; j < 3; j++)
{
scanf("%s", password2);
if (strcmp(password, password2) == 0)
{
printf("登录成功!\n");
break;
}
else
{
printf("密码错误,请重新输入:\n");
}
}
if (i == 3)
{
printf("三次密码均错误,退出程序\n");
}
end:
return 0;
}
人生第一款小游戏:猜数字小游戏
知识补充三:时间戳与生成随机数
如何在C语言中生成一个随机数呢?我们可以直接调用库函数rand(),它的头文件是stdlib.h
来看一下效果:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int a = rand();
printf("%d", a);
}
随机数是不是就生成成功了呢?并不是,当我们多次运行程序时,结果是:
这是为什么呢?
原来C语言规定使用rand()函数前必须先调用函数srand()函数来设置随机数的生成器,查阅库函数表可知srand()有参数,是unsigned int(无符号整型),我们随便输一个100进去,来看一下效果:
#include<stdio.h>
#include<stdlib.h>
int main()
{
srand(100);
int a = rand();
printf("%d", a);
}
结果是365,和上面的41不一样了
当我们重复运行时
还是365?怎么又是一个确定的值了呢?我们试着改变一下srand的参数,比如输个200
#include<stdio.h>
#include<stdlib.h>
int main()
{
srand(200);
int a = rand();
printf("%d", a);
}
结果是691,也就是说生成的数值会随着srand参数的变化而变化
那我们现在就需要一个不受人为控制的,不断的变化的数,来充当srand()的参数,这样就可以生成随机数了
在此引入时间戳的概念。时间戳是我们系统的时间转换出来的一个数字
给出网页链接,大家可以看看自己系统的时间是怎么转换成一个常数的(其实是现在的时间相较于你这台计算机出生的时间的差值,可以说是你这台计算机的年龄)
那怎么用时间戳呢?时间戳的函数是time(),返回值是一个有符号整型,为了配合srand函数我们使用一下强制类型转换;time()有参数,但是我们这里不需要用,所以参数写NULL;time()有头文件,名为time.h
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
srand((unsigned int)time(NULL));
int a = rand();
printf("%d", a);
}
看一下结果:
我们终于生成了随机数!
代码实现猜数字小游戏
游戏规则是这样的:
我们在程序中产生一个0-100之间的随机数,然后我们来不断猜它,猜大了就输出猜大了,猜小了就输出猜小了,最后看看谁猜的次数最少
思路分析:
我们刚刚已经知道了怎么产生随机数,现在要做的就只需要读取玩家输入的数字,和这个随机数不断作比较,我们再给出比较的结果提示就好了
思路转化成代码:
刚刚产生的随机数过大,我们需要的是生成1-100的随机数,我们将上面用时间戳生成的随机数对100取模,这样生成的随机数就是0-99了,然后再+1,这样范围就是1-100了:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
srand((unsigned int)time(NULL));
int a = rand();
int b = a % 100 + 1;
printf("%d", b);
}
在主程序里,为了让我们的游戏更有模有样,我们来打印一个菜单,使用函数来写放在主程序外部,这样主程序比较清晰。打印出菜单以后我们需要赋予菜单功能,所以需要定义一个变量存储玩家输入的1/0的指令,判断是否游玩需要用到选择结构,不断游玩需要用到循环结构。然后整个游戏的程序我们再定义一个函数game放在主程序外部
在game函数中,变量定义上,我们需要用刚刚产生随机数的方法来产生随机数,需要一个guess变量来存储玩家输入的数,比较玩家的数和随机数的大小需要用到选择结构,给出提示需要在每次判断后打印出提示语句。再一次游玩中,还需要定义一个变量num来记录猜数字的次数,每次猜错都要+1,加上最终猜对的那一次,一共需要(猜错次数+1次),所以变量初始化为1.每一次游玩结束,为了使用户体验更好,还可以穿插system(“cls”)语句来清空屏幕。
给出原码:
#include<string.h>
#include<windows.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void menu()
{
printf("******************************\n");
printf("******** 1: play ********\n");
printf("******************************\n");
printf("******************************\n");
printf("******** 0: exit ********\n");
printf("******************************\n");
}
void game()
{
srand((unsigned int)time(NULL));
int a = rand();
int b = a % 100 + 1;
int guess = 0;
int num = 1;
while (1)
{
printf("请猜数字(1-100):");
scanf("%d", &guess);
if (guess < b)
{
printf("猜小了\n");
num++;
}
else if (guess > b)
{
printf("猜大了\n");
num++;
}
else
{
printf("恭喜你,猜中啦!\n");
printf("你一共用了%d次\n", num);
break;
}
}
}
int main()
{
int input = 0;
menu();
do
{
printf("请选择是否玩耍(1/0):");
scanf("%d", &input);
switch (input)
{
case 1:
system("cls");
printf("猜数字游戏开始!\n");
Sleep(2000);
system("cls");
game();
break;
case 0:
Sleep(200);
system("cls");
printf("已退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
}
下面是游玩截图
人生第一款“黑客”软件:强制关机小程序
知识补充四:调用系统命令行
我们的电脑(windows系统)有一个应用程序叫cmd——命令提示符。我们在这个提示符里输入一些指令,电脑就会自动的根据指令做出行动。
在程序中调用的语法是
system(“命令行内容”);
system函数的头文件是stdlib.h
关机的命令行是shutdown -s -t 60
shutdown -s是设置关机,-t是倒计时,60是60秒
取消关机的命令行是shutdown -a
代码实现强制关机小程序
这个程序的要求是可以让电脑自动进入倒计时为一分钟的关机程序,如果用户输入“我是猪”,就可以结束关机
思路分析:
我们既然直接进入关机,那么就直接调用系统命令行输入命令即可。然后再用选择语句判断一下,如果用户很乖,确实输入的是“我是猪”,那就再次调用系统命令行输入终止关机的命令
思路转化成代码:
变量定义部分只需要一个变量来存储用户输入的内容,这个内容是汉字,我们需要使用字符串数组,数组长度给一个大一点的数(20就够了)。判断字符串是否是“我是猪”需要用到字符串比较函数strcmp()。如果用户不输入“我是猪”,就用goto跳转到scanf语句继续接受内容。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
char input[20] = {
0 };
system("shutdown -s -t 100");
printf("同学,你的电脑将在100秒内关机\n");
again:
printf("如果你输入:我是猪,就取消关机\n");
scanf("%s", input);
if (strcmp(input,"我是猪") == 0)
{
system("shutdown -a");
}
else
{
printf("不乖的话你的电脑就要关机了哦");
goto again;
}
return 0;
}
同学们可以自己和好朋友之间玩玩,也可以自己修改强制输入的内容。但是一定不要过火,分寸很重要哦!
转载:https://blog.csdn.net/qq_51379868/article/details/115690172