25、随机数生成:rand 和 srand 的正确用法
随机数,听起来很简单对吧?
很多新手写代码时,直接调个 rand() 就完事了。结果每次运行程序,出来的「随机数」都一样。嗯,这其实是个经典坑。我当年刚学C语言时也踩过,还一度怀疑自己电脑坏了。
rand() 到底有多「随机」?
说白了,rand() 生成的是伪随机数。它背后有一套固定的数学公式,每次调用都按这个公式算出一个数。如果你不给它一个「起点」,它每次都是从同一个起点开始算。那结果当然每次都一样。
这个起点,我们叫它「种子」。
核心结论:rand() 本身不随机,种子决定了随机序列。种子固定,序列就固定。
srand() 才是关键
srand() 就是用来设置种子的。你给它一个不同的种子,它就能生成不同的随机序列。
那问题来了:种子从哪来?
我个人的习惯是,用 time(NULL) 作为种子。因为时间每时每刻都在变,这样每次运行程序,种子都不一样。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
srand((unsigned)time(NULL)); // 用当前时间做种子
for (int i = 0; i < 5; i++) {
printf("%d\n", rand());
}
return 0;
}
这样每次运行,输出的5个数都不一样了。
小提示:srand() 只需要在程序开头调用一次。不要在循环里反复调用,否则反而会破坏随机性。
控制随机数的范围
实际项目中,我们很少直接要 rand() 返回的那个大数。通常我们需要一个特定范围内的随机数,比如 0~99,或者 1~100。
常用的写法是:
// 生成 0 到 (n-1) 之间的随机数
int r = rand() % n;
// 生成 1 到 n 之间的随机数
int r = rand() % n + 1;
// 生成 [a, b] 之间的随机数
int r = rand() % (b - a + 1) + a;
举个例子,生成 1~100 的随机数:
int score = rand() % 100 + 1;
注意:用取模的方式会有轻微的分布不均匀问题。如果对随机质量要求极高(比如加密、抽奖),建议用更专业的随机数库。但日常嵌入式开发中,这个写法完全够用。
一个完整的例子
我在项目中做过一个简单的猜数字小游戏,正好用到了随机数:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
int target, guess;
srand((unsigned)time(NULL));
target = rand() % 100 + 1; // 1~100
printf("猜一个1~100之间的数字:\n");
do {
scanf("%d", &guess);
if (guess > target) {
printf("大了,再试试:\n");
} else if (guess < target) {
printf("小了,再试试:\n");
} else {
printf("恭喜你,猜对了!\n");
}
} while (guess != target);
return 0;
}
每次运行,目标数字都不一样。这就是 srand() 的功劳。
常见错误与避坑指南
- 忘记调用 srand():每次运行结果都一样,等于伪随机变成了「固定随机」。
- 在循环里反复调用 srand():如果循环很快,time(NULL) 返回的值可能没变,导致生成相同的随机数。
- 种子用固定值:比如
srand(42),那每次运行结果都一样。调试时可以这么用,但正式代码别这样。
我曾经在一个数据采集项目中,因为忘记调用 srand(),导致每次上电采集到的「随机噪声」完全一样。排查了半天,最后发现是种子没设。从那以后,我写任何用到随机数的代码,第一件事就是检查 srand() 有没有写。
知识体系图
下面这张图帮你理清 rand 和 srand 的关系:
总结一下
rand()生成伪随机数,需要配合srand()设置种子。srand()在程序开头调用一次即可,常用time(NULL)做种子。- 用取模运算控制随机数范围,注意分布均匀性问题。
- 调试时可以用固定种子,方便复现问题。
随机数这东西,看着简单,用不好就容易出 bug。你想想看,如果每次运行结果都一样,那还叫随机吗?