CC++ & Algorithm

你的游戏程序为什么每次打开都是同一个“随机数”?

2026年8月8日关联知识点:C++随机数 rand0 次阅读

你有没有遇到过这种情况:辛辛苦苦写了一个猜数字小游戏,第一次运行,电脑心里想的“随机数”是 42;你关掉程序再打开,它还是 42;再打开,依然是 42。这哪是随机?这分明是“套路”。

其实这不能怪 rand 函数,它只是按照一个固定的算法在“演”随机数。真正的幕后黑手是:你没有给它一个不一样的种子。

随机数的真相:伪随机 + 种子

C++ 里的 rand() 并不会凭空产生真正的随机,它更像是拿一个初始值(种子)去喂一个公式,公式算出一个数,然后这个数又作为下一次运算的输入,如此下去,产生一串数字。这串数字看起来杂乱无章,但只要种子一样,整串数字就完全一样。

所以,rand() 本身没什么问题,问题在于程序每次启动时,默认的种子都一样。第一次运行从 41 开始,第二次还是从 41 开始,第三次依然如此——游戏自然毫无惊喜。

破解方法就是引入 srand,用当前时间做种子:

srand(time(0));

时间每秒都在变,这样每次运行程序,种子不同,后续的随机数序列也就完全不同了。这个操作在整个程序里只需执行一次,放在 main 函数的开头。千万别在循环里反复调用 srand,否则同一秒内生成的“随机数”可能全部相同——这算新手最容易踩的坑之一。

把随机数变成你想要的区间

rand() 直接返回的是 0 到 RAND_MAX 之间的整数,在多数环境里 RAND_MAX 是 32767。但我们日常需要的往往是特定区间,比如骰子的 16、百分制的 0100、抽奖编号的 50~100。

核心公式非常简单:

rand() % (最大值 - 最小值 + 1) + 最小值

举个例子,想生成闭区间 [10, 20] 的整数,很多人第一反应会写 rand() % 10 + 10。这个表达式确实生成了 10 个数,但范围只有 10 到 19,整整漏掉了 20。正确写法是:

int num = rand() % 11 + 10;   // 区间长度 = 20 - 10 + 1 = 11

这个模数搞错的坑,在各类考题里反复出现。比如有题目问你“生成 [10,20] 闭区间的随机整数,哪个表达式正确”,选项里 rand() % 11 + 10 是正解,而 rand() % 10 + 10 则完美地错过了最大的那个数。原因很简单:模数代表的是区间内包含多少个整数,[10,20] 一共有 11 个数,而不是 20-10=10 个。

如果你想要随机小数,比如模拟一个 10% 的中奖概率,可以把 rand() 转成 double 再除以 RAND_MAX

double ratio = (double)rand() / RAND_MAX;   // 0.0 ~ 1.0
if (ratio < 0.1) {
    // 中奖
}

这样处理完,随机数就能用来做很多事了。

一道题告诉你随机根本不是重点

有段时间我给学生讲循环和坐标计算,拿了一道“蜜蜂飞舞”的题目当练习。题目描述得很浪漫:两只蜜蜂在三维空间里飞,每次飞行给出各自的速度向量和飞行时间,最后让你算两只蜜蜂的距离,保留四位小数。

题目里根本没有出现 rand,但我为什么在讲随机数的时候提它?因为这道题把“随机”这个概念彻底从你脑子里赶走——它的难点在于:你不需要随机,只需要老老实实模拟。

解题思路其实非常直白:

  1. 读入飞行次数 n
  2. 在每次飞行中,把第一只蜜蜂的坐标分别加上 ai * tibi * tici * ti;第二只蜜蜂同理,加上 di * tiei * tifi * ti
  3. n 次飞行结束后,用两点间距离公式输出结果。

关键代码就几行:

x1 += a * t; y1 += b * t; z1 += c * t;
x2 += d * t; y2 += e * t; z2 += f * t;

最后算距离:

double dist = sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2) + pow(z1 - z2, 2));
printf("%.4lf\n", dist);

这道题的意义在于,它让我们意识到:很多看似“随机”的场景,本质上是确定性的模拟。反过来,如果你真的需要不确定性,比如让每次运行的蜜蜂初始位置都不同,那你才需要 rand 出场。随机数不是用来解决所有“未知”问题的银弹,它是工具,什么时候用、怎么用,取决于你想让程序呈现什么样的行为。

随机数也能很优雅:复利计算里的“确定性”

再看一个和随机数完全相反的例子——复利存款。题目给你本金 P、年利率 r%、年限 N,要求复利计算后的本利和,结果四舍五入取整数。

这个问题的核心是公式:

本利和 = P × (1 + r/100)^N

其中 N 不超过 5,所以直接循环乘 N 次即可。为什么提这个?因为有些同学一看到“利率”“年份”就想着随机模拟,或者用复杂的金融模型,其实题目已经把公式给你了,你只需要把年利率从整数 r 转成小数 r / 100.0,然后老老实实乘 N 次:

double money = P;
for (int i = 0; i < N; i++) {
    money *= (1 + r / 100.0);
}
int ans = (int)(money + 0.5);  // 四舍五入

这个例子告诉我们:确定性计算和随机数生成是编程里的两条平行线。已知规则,就按规则算;规则本身就是不确定的,比如掷骰子、抽奖,才需要 rand。把这两者混为一谈,代码就会变得又乱又难调试。

理解随机数,才能驾驭随机性

回到开头的问题:如何让程序里的“随机数”真正随起来?记住三件事:

  1. srand(time(0)) 只调用一次,放在程序入口。
  2. 区间映射公式rand() % (最大值 - 最小值 + 1) + 最小值,模数千万别算错。
  3. 随机数只是伪随机,对于日常练习、游戏、模拟完全够用;但如果你将来做密码学、科学模拟,就需要拥抱 C++11 的 <random> 库,比如 mt19937,它能提供质量更高、更可控的随机分布。

随机数的魅力在于,它让程序第一次有了“不确定性”。一个猜数字游戏,有了随机种子,每次玩都是全新的挑战;一个抽奖程序,有了随机数,才可能保证公平。理解了背后的机制,你就不会再被“为什么每次运行结果都一样”这种问题卡住了。

现在,找个题目练练手吧。写一个掷骰子模拟器,或者一个随机点名器,用上 srand 和区间公式,亲手感受一下“不可预测”的乐趣。


关于作者

我是赵老师,持有 NOI 信息学奥赛教练证书,拥有 15 年以上的软件开发经验,从事信息学少儿编程教学已有 8 年时间。

这些年累计帮助 多名 学生通过编程特长升入自己心仪的目标学校。

如果你在编程学习上有任何疑问,欢迎联系我:18620372957(微信同号)

这篇文章对你有帮助吗?

成为第一个评价的人

想系统学习这个知识点?查看完整知识点 →