C语言入门 | “猜数字”小游戏 保姆级详解
2026/8/5 11:39:38 网站建设 项目流程

一、游戏要求

1.电脑自动生成1~100的随机数

2.玩家猜数字,猜数字过程中,根据猜测数据的大小给出大了或小了的反馈,直到猜对,游戏结束。

二、初步逻辑分析

1.布置游戏菜单

使得游戏可以无限地玩下去,每一局结束后,玩家可以自己选择是要继续还是退出

2.玩游戏过程

生成随机数;

循环猜测;

三、随机数生成原理

1.rand函数

首先,是需要函数 int rand(void)。即使你从来没见过这个函数,但顾名思义,大概是一个生成随机数的函数,没错,它就是用来生成随机数的。

rand函数会返回一个伪随机数,这个随机数的范围是在0~RAND_MAX之间,这个RAND_MAX的大小是依赖编译器上实现的,但是大部分编译器上是32767.

但,仅仅使用rand函数就可以了吗?我们来验证一下:

int main() { printf("%d\n", rand()); printf("%d\n", rand()); printf("%d\n", rand()); return 0; }

读者可以自行尝试,将这段代码在自己的电脑多次运行,是不是始终是这个结果。如果是这样,那又何来的“随机”呢?

原来,只有rand函数本身,生成的是伪随机数。它是对一个叫“种子”的基准值进行运算生成的随机数,而默认的“种子”是“1”。

2.srand函数

那么,如何改变“种子”呢?C语言又提供了一个函数 void srand (unsigned int seed),是用来初始化随机数的生成器的,即通过参数seed设置“种子”。如下图:

int main() { srand(2); printf("%d\n", rand()); printf("%d\n", rand()); printf("%d\n", rand()); return 0; }

int main() { srand(3); printf("%d\n", rand()); printf("%d\n", rand()); printf("%d\n", rand()); return 0; }

那么问题又来了,既然要随机数,那就要求“种子”是动态变化的,如果做到这一点呢?这里就又不得不介绍一个新的函数

3.time函数

time_t time (time_t* timer):

返回值:1970年1月1日0时0分0秒到现在程序运行时间之间的差值,单位是秒

返回类型:time_t 本质上就是32位或者64位的整型类型

参数:指针这里不细讲,如果timer是NULL(空指针),就只返回这个时间的差值。这个时间差也被叫做时间戳。

了解了以上内容,我们就可以生成真随机数了!

#include <stdio.h> #include <stdlib.h> #include <time.h> int main() { srand((unsigned int )time(NULL)); printf("%d\n", rand()); printf("%d\n", rand()); printf("%d\n", rand()); return 0; }

4.注意事项

当然,使用rand和srand函数需包含头文件<stdlib.h>,使用time函数需包含头文件<time.h>。

还有一个细节问题,细心的读者应该会发现,这里有一个强制类型转换。因为time函数返回值的数据类型是time_t,是32位或64位的整型类型,而srand函数的参数需要的数据类型是32位的unsigned int类型,所以进行了强制类型转换。


以下这段算是插曲,笔者的个人思考,可略过不读:

笔者写到这里的时候还产生了一个问题,随着时间的不断推移,time函数返回值越来越大,如果有一天超过了time_t数据类型能存放的最大值该怎么办?从某个时间开始强制类型转换的过程中不会丢失数据吗?因此查阅了一些资料。以下是分析过程:

unsigned int 取值范围:0~4294967296(0~

32位整型取值范围:-2147483648~2147483647(~

如果某个编译器的time_t类型是32位整型,那么在2038年时间戳就会超过其所能存放的最大值,这就是“2038危机”,好在现在大多数编译器都采用64位整型;如果是64位整型,那么time函数距离原生溢出还有约2922亿年的距离,但是强制类型转换之后,会把后32位截断,使得在2106年触发数据丢失,但好像离现在还有一断距离。


最后一个问题,我们如何产生指定范围内的随机数呢?比如,产生1~100的随机数。 rand()%100+1 为什么是这样,我们一步一步来分析: 先是"rand%100",这代表产生0~99的整数;(可理解为:定步长) 再是“+1”,这代表要从1开始;(可理解为:定起点) 因此,可以总结出一个公式:a~b的随机数 rand()%(b-a+1)+a

#include <stdio.h> #include <stdlib.h> #include <time.h> int main() { srand((unsigned int)time(NULL)); printf("%d\n", rand() % 100 + 1); printf("%d\n", rand() % 100 + 1); printf("%d\n", rand() % 100 + 1); printf("%d\n", rand() % 100 + 1); printf("%d\n", rand() % 100 + 1); return 0; }

四、代码的实现

PS:由于笔者不想篇幅过长缺乏可读性,所以下面的每段代码忽略调试截图,但代码一定是经过调试的,如果读者有兴趣也可以在自己的电脑上进行验证

1.布置游戏菜单

将游戏菜单通过printf函数打印出来,不用很花里胡哨,但要有“玩”和“退出”的选项以及用户的选择机会

#include <stdio.h> int main() { int input = 0; printf("--------------------\n"); printf("---- 1.play ----\n"); printf("---- 0.exit ----\n"); printf("--------------------\n"); printf("请选择:"); scanf("%d", &input); return 0; }

(这里为什么设置“1”和“0”而不是别的,和后面的循环条件有关,后面会有说明)

根据用户输入值的不同,程序需要作出不同的反应,因此,这里需要一个选择结构,选择“1”就玩游戏,选择“0”就退出游戏,选择其他的就提示选择错误。if语句和switch语句都可以,但是switch更简洁。

#include <stdio.h> int main() { int input = 0; printf("--------------------\n"); printf("---- 1.play ----\n"); printf("---- 0.exit ----\n"); printf("--------------------\n"); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: printf("玩游戏\n"); //这里要写玩游戏的程序,但是为了调试方便,暂时先这样写 break; case 0: printf("退出游戏\n"); break; default: printf("没有这个选项,请重新选择\n"); break; } return 0; }

很明显的是,在这里需要一个循环结构,使得这个程序会无限循环,除非用户自己不想玩了。那么while/for/do while三种循环语句用哪种比较好呢?显然,这里用do while会比较合适,因为这段程序至少需要被执行一次。

#include <stdio.h> int main() { int input = 0; do { printf("--------------------\n"); printf("---- 1.play ----\n"); printf("---- 0.exit ----\n"); printf("--------------------\n"); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: printf("玩游戏\n"); //这里要写玩游戏的程序,但是为了调试方便,暂时先这样写 break; case 0: printf("退出游戏\n"); break; default: printf("没有这个选项,请重新选择\n"); break; } } while (input); return 0; }

前面提到的为什么菜单打印“1”和“0”供用户选择,原因就在于,这样可以让循环结构的条件判断更加简洁。在C语言中,0代表假,非0代表真,因此,让“1”代表玩游戏,“0”代表退出游戏,然后将存放用户输入数据的变量input直接作为循环是否继续进行的判断条件是非常巧妙的。

2.编写玩游戏的程序

显然,当用户选择“1”选项后,应该进行玩游戏的过程。整个游戏的程序不说逻辑多复杂,但至少没个二十行代码应该是下不来的。所以如果这个代码块直接写在"case 1"的分支下面,是非常影响可读性的,因此,将整个玩游戏的程序封装成一个函数是一个不错的选择。

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> void game() { } int main() { int input = 0; do { printf("--------------------\n"); printf("---- 1.play ----\n"); printf("---- 0.exit ----\n"); printf("--------------------\n"); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("退出游戏\n"); break; default: printf("没有这个选项,请重新选择\n"); break; } } while (input); return 0; }

下面我们主要关注玩游戏的程序。

首先是自动生成随机数,然后给用户猜测的机会。

#include <stdio.h> #include <stdlib.h> #include <time.h> void game() { int r = rand()%100+1; int guess = 0; printf("请猜数字:"); scanf("%d", &guess); } int main() { int input = 0; srand((unsigned int)time(NULL)); do { printf("--------------------\n"); printf("---- 1.play ----\n"); printf("---- 0.exit ----\n"); printf("--------------------\n"); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("退出游戏\n"); break; default: printf("没有这个选项,请重新选择\n"); break; } } while (input); return 0; }

至于为什么种子是放在main函数里面,而不是game函数里面,或许是因为整个程序中srand函数只调用一次,放在前面更简洁明了?笔者不才,这个解释都说服不也自己,那就更别提给别人解释清楚了,询问AI感觉也不太明白,自己也还在持续学习当中,也许是知识储备还不够,读者如果感兴趣,可以去自行搜索相关资料。

根据用户输入的数字,程序需要提示用户是猜大了还是猜小了,因此这里需要选择结构,显然if语句是比较合适的。

void game() { int r = rand()%100+1; int guess = 0; printf("请猜数字:"); scanf("%d", &guess); if (guess < r) printf("猜小了\n"); else if (guess > r) printf("猜大了\n"); else printf("恭喜你,猜对了!\n"); }

当然,不可能只给一次机会,现在假设可以猜无数次,那么就需要循环结构。这里采用while循环,因为不知道要猜多少次,所以可先设置一个死循环,循环条件为“1”。

void game() { int r = rand()%100+1; int guess = 0; while (1) { printf("请猜数字:"); scanf("%d", &guess); if (guess < r) printf("猜小了\n"); else if (guess > r) printf("猜大了\n"); else printf("恭喜你,猜对了!\n"); } }

但是呢,也不能一直循环,猜对了之后,本次游戏就结束了,应该显示最初的游戏菜单才对,用户可以自行选择要不要再次游戏,这样逻辑才是通顺的。因此,用户猜对应跳出循环

void game() { int r = rand()%100+1; int guess = 0; while (1) { printf("请猜数字:"); scanf("%d", &guess); if (guess < r) printf("猜小了\n"); else if (guess > r) printf("猜大了\n"); else { printf("恭喜你,猜对了!\n"); break; } } }

这样,这个猜数字小游戏的代码就全部编写完成了,下面是所有的代码,可供参考。

#include <stdio.h> #include <stdlib.h> #include <time.h> void game() { int r = rand()%100+1; int guess = 0; while (1) { printf("请猜数字:"); scanf("%d", &guess); if (guess < r) printf("猜小了\n"); else if (guess > r) printf("猜大了\n"); else { printf("恭喜你,猜对了!\n"); break; } } } int main() { int input = 0; srand((unsigned int)time(NULL)); do { printf("--------------------\n"); printf("---- 1.play ----\n"); printf("---- 0.exit ----\n"); printf("--------------------\n"); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("退出游戏\n"); break; default: printf("没有这个选项,请重新选择\n"); break; } } while (input); return 0; }

五、加花部分

1.设定可猜测次数

也许,我们还可以设置一下可猜测的次数,比如,最多能猜5次。

如何实现呢?可以定义一个循环变量,初始值是5,使其每猜一次就-1,直到变为0跳出循环。

void game() { int r = rand()%100+1; int guess = 0; int count = 5; while (count) { printf("请猜数字:"); scanf("%d", &guess); if (guess < r) printf("猜小了\n"); else if (guess > r) printf("猜大了\n"); else { printf("恭喜你,猜对了!\n"); break; } count--; } }

这样还是感觉缺少点什么。猜测成功时,打印恭喜用户,那么猜测失败是不是也同样需要告知用户呢?并且最好告诉他正确值是多少。

很明显,猜测失败后用printf函数打印的那句话是需要放在循环外面的。

当然,这里我们又要考虑一个问题。现在跳出循环有2种可能:一种是猜对了,break跳出循环;另一种是猜测次数用完了,没有猜对跳出循环。

那就需要一个if语句进行一次判断了,判断条件是什么呢?

很容易联想到我们上面定义的循环变量count。只有当count变量等于0时,才代表是猜测失败了跳出循环,否则是猜对了才跳出循环

因此,我们有了以下代码

void game() { int r = rand()%100+1; int guess = 0; int count = 5; while (count) { printf("你还有%d次机会\n",count); printf("请猜数字:"); scanf("%d", &guess); if (guess < r) printf("猜小了\n"); else if (guess > r) printf("猜大了\n"); else { printf("恭喜你,猜对了!\n"); break; } count--; } if (count == 0) printf("很遗憾,5次机会已使用完,正确值是%d\n", r); }

2.计时猜测

我们是否还可以为玩家记录一下他所用的时间呢?

答案当然是可以的。

怎么实现呢?前面我们了解了时间戳的概念,那么就可以很自然的想到,在游戏开始时,我们计算一下时间戳,在游戏结束时,再计算一下时间戳,两个时间戳相减,就可以得到啦!

void game() { int r = rand()%100+1; int guess = 0; int count = 5; time_t start = time(NULL); while (count) { printf("你还有%d次机会\n",count); printf("请猜数字:"); scanf("%d", &guess); if (guess < r) printf("猜小了\n"); else if (guess > r) printf("猜大了\n"); else { printf("恭喜你,猜对了!\n"); break; } count--; } time_t end = time(NULL); if (count == 0) printf("很遗憾,5次机会已使用完,正确值是%d\n", r); printf("本次游戏用时%lld秒\n", end - start); }

再说明一个细节问题,time函数返回值的数据类型是time_t类型(具体前文有提到),笔者使用的编译器默认time_t类型是64位整型,因此printf函数打印时用的占位符是%lld,和 long long int 数据类型(大小也是64位)使用的占位符相同。

六、完整代码供参考

#include <stdio.h> #include <stdlib.h> #include <time.h> void game() { int r = rand()%100+1; int guess = 0; int count = 5; time_t start = time(NULL); while (count) { printf("你还有%d次机会\n",count); printf("请猜数字:"); scanf("%d", &guess); if (guess < r) printf("猜小了\n"); else if (guess > r) printf("猜大了\n"); else { printf("恭喜你,猜对了!\n"); break; } count--; } time_t end = time(NULL); if (count == 0) printf("很遗憾,5次机会已使用完,正确值是%d\n", r); printf("本次游戏用时%lld秒\n", end - start); } int main() { int input = 0; srand((unsigned int)time(NULL)); do { printf("--------------------\n"); printf("---- 1.play ----\n"); printf("---- 0.exit ----\n"); printf("--------------------\n"); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("退出游戏\n"); break; default: printf("没有这个选项,请重新选择\n"); break; } } while (input); return 0; }

最后,非常感谢小伙伴能阅读到这里,欢迎留言提问或讨论。

完!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询