1. 猜数字游戏为什么是分支循环教学里的经典项目接触过编程入门的人应该都有印象老师讲到分支和循环的时候十有八九会拿猜数字游戏来举例。原因很简单这个项目几乎把C语言最基础的控制流结构全串起来了。你需要用if/else判断用户猜大了还是猜小用while或for把重复猜测的过程圈起来还要处理一个几乎所有新手都会懵的点——怎么让程序每次运行都生成不一样的数字。很多人卡住的地方恰恰就是最后这一点。明明代码逻辑没问题if判断也对while循环也写了但每次运行程序答案都是同一个数。我见过不少学生拿着代码来找我一脸委屈地说“老师我的随机数不随机”。其实问题不在分支循环本身而在你对随机数生成机制的理解。先别急着写代码我们先把思路理清楚。猜数字游戏的本质是一个“反馈循环”程序生成一个目标数字1到100之间。用户输入一个猜测值。程序比较猜测值和目标值给出“大了”“小了”或“恭喜猜中”的反馈。如果没猜中回到第2步如果猜中游戏结束。这个流程里分支负责第3步的反馈逻辑循环负责第2步和第4步的重复过程随机数负责第1步的“不确定性”。三者缺一不可而且顺序不能乱。如果把随机数生成放在了循环里面每猜一次目标数字就变了游戏就彻底失去意义。这也是新手最常见的错误之一。所以这个项目真正教你的不只是if和while怎么用而是怎么把多个基础语法组合在一起形成一个逻辑自洽的小系统。等你把猜数字游戏跑通了后续再做菜单系统、登录验证、简单的交互程序思路都会有质的提升。毕竟控制流是程序运行的主干而随机数只是主干上的一片叶子但恰恰是这片叶子让整个程序“活”了起来。2. rand() 的陷阱为什么每次运行都是一样的数字在C语言里生成随机数最基础的方式是调用rand()函数。它会返回一个从0到RAND_MAX之间的整数RAND_MAX在不同的编译器里值不一样但通常是32767。你可能会想要1到100之间的数直接rand() % 100 1不就行了理论上是这样但如果你直接这么写运行几次之后就会发现问题。#include stdio.h #include stdlib.h int main() { printf(%d\n, rand() % 100 1); printf(%d\n, rand() % 100 1); return 0; }你把这串代码编译两次或者在同一台机器上运行两次每次输出的结果一模一样。为什么因为rand()实际上生成的是“伪随机数序列”它不是真正随机的而是基于一个初始值经过数学计算得出的序列。这个初始值叫做“随机数种子”默认情况下是固定不变的1。只要你没有设置种子每次程序启动后rand()都会从同一个位置开始生成序列结果自然相同。要解决这个问题必须先调用srand()函数来设置一个不一样的种子。最常见的做法是把当前时间作为种子因为时间每分每秒都在变化这样每次运行程序时种子都不同生成的数列也就不同了。#include stdio.h #include stdlib.h #include time.h int main() { srand((unsigned int)time(NULL)); printf(%d\n, rand() % 100 1); return 0; }这里有几个需要细看的点。首先srand()的入参类型是unsigned int而time(NULL)返回的是time_t类型通常可以直接赋值但为了严谨最好强转一下也就是(unsigned int)time(NULL)。其次time(NULL)返回的是自1970年1月1日00:00:00 UTC到当前时刻的秒数。每秒变一次所以如果你在程序运行之后的极短时间内连续启动两次种子可能是一样的但对我们这个演示项目来说完全够用了。再一个需要注意的地方srand()只应该在程序开始时调用一次而不是每次生成随机数之前都调用。如果写在循环里因为循环跑得很快同一秒内多次调用time(NULL)得到的种子是相同的那么循环里生成的随机数序列反而会变得非常规律甚至每次都一样。这就失去随机性了。我在教学时经常强调srand()负责播种rand()负责收获播种只需要做一次。3. 从 1 到 100区间换算的数学与边界坑如果你只想要0到99之间的数那直接rand() % 100就行了。但猜数字游戏通常要求目标在1到100之间所以你需要理解怎么把rand()的输出映射到你想要的区间。int target rand() % 100 1;这个式子的逻辑是rand() % 100的结果范围是0到99加1之后就变成了1到100。这看起来很简单但有一个潜在的问题取模运算会导致分布不均匀。举个例子假设RAND_MAX是32767rand() % 100实际上是拿这个数除以100取余数。余数变化范围是0到99但这里有一个微妙的不平衡。32767除以100商是327余数是67。这意味着余数0到67出现的次数会比余数68到99多一次因为从0到32767这32768个数中每100个一组最后一组只到67。所以余数0到67会多出现一次。虽然对猜数字游戏来说这个偏差微乎其微几乎是感觉不到的但如果你在写更严谨的程序比如彩票抽奖就必须考虑这个问题。对于区间偏差的中庸解决方案如果你只是做练习题rand() % 100 1完全没问题。但如果较真可以用一个更标准的方法用rand()生成一个浮点数比例再映射到区间。int target (int)(1.0 * rand() / RAND_MAX * 100) 1;这样是把rand()的结果变成一个0到1之间的小数比例然后乘以区间长度100再向下取整并加1。分布会均匀很多。不过对猜数字游戏来说日常使用直接取模就够了别把所有精力耗在这种细节上等你水平提升了自然会理解其中区别。还有边界问题。游戏逻辑里你必须明确用户输入的数字范围。如果用户输入0或101程序应该怎么处理我的建议是在游戏开始前就约定好范围为1到100然后在输入判断时加上一个前置判断如果不在这个范围内就直接要求重新输入而不是继续死磕“大了还是小了”。你可以用分支嵌套来实现也可以在循环的开头做一次合法性检查。if (guess 1 || guess 100) { printf(输入无效请输入1到100之间的整数。\n); continue; }这看起来是小事但很多人的代码就是在这个地方翻车不是越界判断忘写了就是判断写成了导致逻辑完全反了。边界条件是程序里最容易出问题的地方猜数字游戏作为一个基础项目正好可以帮你养成这个习惯。4. 游戏主循环的实现while if/else 的嵌套逻辑现在我们把前面讲的东西拼到一起写一个完整的游戏主体。先确定游戏规则程序生成1到100之间的随机数用户最多猜10次每猜一次程序给出提示猜中即结束。核心肯定是一个while循环。循环条件怎么写有两种思路一是用无限循环再加上break跳出二是用一个变量控制循环次数。我这里选择用一个int attempts记录次数用int maxAttempts 10;做上限循环条件写成currentAttempt maxAttempts。每次猜完currentAttempt。猜中之后用break跳出循环。#include stdio.h #include stdlib.h #include time.h int main() { int target, guess, currentAttempt 0, maxAttempts 10; srand((unsigned int)time(NULL)); target rand() % 100 1; printf(我已经想好了一个1到100之间的数字你有 %d 次机会。\n, maxAttempts); while (currentAttempt maxAttempts) { printf(第 %d 次猜测, currentAttempt 1); scanf(%d, guess); if (guess 1 || guess 100) { printf(输入无效请输入1到100之间的整数。\n); continue; } currentAttempt; if (guess target) { printf(恭喜你猜中了答案就是 %d。用了 %d 次。\n, target, currentAttempt); break; } else if (guess target) { printf(太小了继续加油。\n); } else { printf(太大了别着急。\n); } if (currentAttempt maxAttempts) { printf(很遗憾次数用完了。正确答案是 %d。\n, target); } } return 0; }这段代码的结构很典型while负责“重复猜”if/else负责“判断反馈”continue负责“跳过非法输入但不消耗次数”break负责“猜中后提前退出”。你仔细体会一下这里continue的位置——它放在currentAttempt之前所以非法输入不会浪费你的猜测次数。这是一个细节很多初学者会把continue放在次数增加之后结果输一次错的就先废掉一次机会体验很差。另外注意我把currentAttempt放在合法性检查之后。这样做的目的是一旦用户输错格式或者范围程序会直接回到循环开头重新等待输入不会把这次输入算作一次有效猜测。逻辑上更合理也给用户留足了容错空间。循环结构里还有一个细节如果你用了while (1)无限循环那循环里就必须有break否则程序永远跑不完。但如果你用变量控制循环次数那循环结束后还有可能没猜中所以需要在循环外面或者循环最后补充“次数用完”的提示。我在上面代码里选择在循环内部判断currentAttempt maxAttempts时打印失败信息这样代码比较紧凑读者也更容易跟上思路。5. 避坑实录输入缓冲、死循环和“伪随机”的真相写这个游戏的时候我自己当年就踩过几个坑后来教学生也看他们反反复复踩有必要单独拎出来讲。第一个坑是scanf的输入缓冲残留。假设你用scanf(%d, guess)读整数用户输入了abc这时候scanf会读取失败但你留下的回车符或者字母还在输入缓冲区里。如果此时程序没有处理这个残留内容再下一次scanf时可能继续读取失败导致控制台出现死循环一样的疯狂输出现象就是不管你输入什么程序都在报“输入无效”。解决的思路有几种最简单的是限制用户只能输入整数并在读取失败后用while (getchar() ! \n);清空缓冲区。但我不建议初学者一上来就搞这些因为容易搞晕。我推荐的做法是接受这个小概率情况不让它是死循环就行。比如用一个临时char变量接收一下或者直接提示用户重新输入同时加上fflush(stdin)——但注意fflush(stdin)在部分编译器上不是标准行为不一定可靠。第二个坑是死循环。如果你把continue写在不恰当的位置比如在猜中之后仍然continue那break永远执行不到程序就无限循环。我做小测试的时候有一次就把continue误放在了break之前运行时怎么都跳不出来只能强杀进程。这种隐蔽问题特别耗时间所以现在每写一个循环我都会特意确认循环内部哪些路径可以退出循环。第三个坑还是伪随机。有些人会把srand(time(NULL))放到循环里面每次猜之前都重新播种。表面上看起来没问题但因为在同一秒内启动或运行time(NULL)返回的值是一样的种子相同所以你每次得到的目标数字可能就固定在第一次生成的那个值附近。这让随机效果大打折扣。记住一句话播种一次后续一直用rand()就好别没事干反复播种。第四个坑是范围不对称。rand() % 100生成0到99如果你要1到100就必须加1。如果写反了变成rand() % 100 0那范围就成了0到99导致100永远不会出现。这个问题看着简单但人一着急就是会写错排查起来又特别容易忽视。我建议你写完后自己检查一下边界值把RAND_MAX代入去看看最大值和最小值能不能对上。第五个坑是输入负数。如果用户猜了个负数比如-5你的程序如果只判断了guess 100却没判断guess 1那负数就会被当成“小了”来处理。这样感觉也没啥大问题但如果你后来想加入计分系统负数的存在会让统计变得很难看。所以边界判断一定要两头都截住。说这么多其实都是小事但新手阶段最难的不是学会语法而是学会在出现问题时冷静拆解、一步步把问题定位出来。猜数字游戏就是一个很好的训练场。6. 让它更像游戏次数限制、提示范围和循环嵌套扩展基础版跑通以后你可以给它升级正好也可以再多练练分支和循环的嵌套用法。第一个升级是增加难度选择。在游戏开始前用switch或者if/else让用户选择“简单、普通、困难”三种模式不同模式对应的数字范围不一样。比如简单是1到50普通是1到100困难是1到500。这其实就是把随机数生成的区间参数化。int maxRange; printf(请选择难度1.简单 2.普通 3.困难\n); scanf(%d, difficulty); if (difficulty 1) { maxRange 50; } else if (difficulty 2) { maxRange 100; } else { maxRange 500; } int target rand() % maxRange 1;第二升级是记录玩家的猜测历史并在猜中后打印出来。这需要用数组但如果你还没学到数组也可以用多个变量简单演示。或者延迟到学完数组之后再回来重构。第三个升级是循环嵌套。比如你希望玩完一轮之后询问用户是否再来一局每局内部是while循环外部再套一个while循环控制“是否继续”。这就是标准的循环嵌套场景。int playAgain 1; while (playAgain) { // 这里写一整局游戏的代码包括生成目标数和猜测循环 printf(再玩一局吗输入1继续输入0退出); scanf(%d, playAgain); }这样的结构能帮你把“内层循环跑固定次数外层循环跑不确定次数”的关系理清楚。第四个升级是对随机数的更进一步理解你可以设定一个“最高分”变量每次游戏结束后如果待用户用的次数比最高分少就更新最高分。然后需要使用分支判断来更新记录这又复习了一遍if。我在自己做这个练习的时候最大的收获其实是调试过程。写一个这个规模的小程序你会反复用到打印输出、单步逻辑推演还会看到不同编成习惯导致的细微差异。比如有人喜欢把srand写在调用之前十几行的位置有人喜欢紧挨着rand写有人喜欢用printf(debug: %d\n, target);来临时输出调试信息跑完再注释掉——这些习惯都没有对错但都对效率影响很大。最后再说一个我在实际测试中发现的小技巧如果你用的IDE是Visual Studio别忘记包含time.h头文件否则time(NULL)会报“未定义标识符”。如果你在Linux下用gcc编译直接gcc main.c -o guess就行不需要额外的库。在Windows下srand(time(NULL))和rand()都是在stdlib.h和time.h里声明的不用额外加#include windows.h。这种头文件层面的坑看起来不起眼但第一次编译失败时能拦住你好几分钟。总而言之今天的明线是“随机数生成”暗线其实是“控制流的组合逻辑”。希望这篇笔记能帮那些卡在rand()和srand()上的初学者迈过这道坎。下次再遇到类似的循环迷阵试着画一画流程图或者把大循环拆成小阶段来调试思路会清晰很多。
企业数字化 ERP 产品动态
相关推荐
同步相量计算算法对比:FFT、小波与HHT的Matlab实现 电网里做测量和算法的人,大概率都跟同步相量计算打过交道。PMU装置往现场一挂,要求就是能在各种复杂工况下把电压电流的相量(幅值、相位、频率)给准了,一旦动态扰动来了信号畸变,传统傅里叶那套就会露馅。这… · 2026/9/26 9:58:46
Oracle 11.2.0.3 ODBC驱动Windows部署全指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 9:58:40
Atlas 300V 24G推理加速卡实战:昇腾NPU上部署YOLO全流程 最近好几个朋友不约而同问我同一个问题:Atlas 300V 24G是不是运算加速卡?紧接着第二个问题往往是,既然有这张卡,能不能在上面部署YOLO?我猜大家是搜资料时看到“Atlas部署YOLO”的内容来来去去都模棱两可,又… · 2026/9/26 10:37:34
OFDM-IM索引调制理论到MATLAB仿真:发射机、ML接收机与参数调优 简介:一套面向无线通信研究者与工程师的OFDM-IM(正交频分复用索引调制)仿真代码,基于MATLAB实现,用于理解索引调制如何提升频谱效率与能效,可在低信噪比环境下评估系统性能。资源仅含1个主程序m文件&#x… · 2026/9/26 10:37:22
Vue大屏自适应实战:TaoToken统一Key下v-scale-screen与rem方案配置对比 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 10:37:22
Atlas 300V Pro 24G部署YOLO全流程:硬件选型到性能调优 最近后台收到好几个朋友在问同一个问题:“Atlas 300V 24G是运算加速卡吗?”、“能不能用来部署YOLO?”。其实这个标题本身就能看出大家的核心诉求:手上拿到(或者准备入手)一块昇腾Atlas 300V系列推理卡&… · 2026/9/26 10:37:16
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46