1. 随机数概述
在编程中,随机数有着广泛的应用:游戏开发、密码学、模拟实验、抽奖系统等。C语言提供了生成伪随机数的函数库。
1.1 什么是伪随机数?
计算机无法产生真正的随机数,而是通过算法生成看似随机的数列,称为伪随机数。给定相同的种子,每次生成的随机数序列是相同的。
专业术语:
· 随机数种子:生成随机数序列的起始值
· 伪随机数:由算法生成的、具有统计随机性的数列
2. 核心函数
C语言通过 <stdlib.h> 头文件提供随机数函数:
函数 作用 返回值
rand() 生成随机数 0 到 RAND_MAX 之间的整数
srand() 设置随机数种子 void
2.1 RAND_MAX 常量
RAND_MAX 是 <stdlib.h> 中定义的常量,表示 rand() 能返回的最大值。通常为 32767(2^15-1),但在不同平台可能不同。
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("RAND_MAX = %d\n", RAND_MAX);
return 0;
}
3. 基本使用方法
3.1 最简单的随机数
#include <stdio.h>
#include <stdlib.h>
int main() {
// 直接调用 rand()
printf("随机数1: %d\n", rand());
printf("随机数2: %d\n", rand());
printf("随机数3: %d\n", rand());
return 0;
}
问题:每次运行程序,生成的随机数序列完全相同!
3.2 设置随机数种子
#include <stdio.h>
#include <stdlib.h>
#include <time.h> // time() 函数需要此头文件
int main() {
// 使用当前时间作为种子
srand((unsigned int)time(NULL));
printf("随机数1: %d\n", rand());
printf("随机数2: %d\n", rand());
printf("随机数3: %d\n", rand());
return 0;
}
关键点:
· time(NULL) 返回从1970年1月1日至今的秒数
· 每次运行程序,种子不同,因此随机数序列不同
· 通常只在程序开始时调用一次 srand()
4. 生成指定范围的随机数
4.1 生成 [0, n) 范围的随机数
int n = 100;
int randomNum = rand() % n; // 范围:0 到 99
4.2 生成 [a, b] 范围的随机数
通用公式:
int randomNum = a + rand() % (b - a + 1);
示例:
// 生成 1 到 100 的随机数
int num = 1 + rand() % 100;
// 生成 50 到 100 的随机数
int num = 50 + rand() % 51;
// 生成 -10 到 10 的随机数
int num = -10 + rand() % 21;
4.3 生成 [a, b] 范围的随机浮点数
// 生成 0.0 到 1.0 的随机浮点数
double randomDouble = (double)rand() / RAND_MAX;
// 生成 a 到 b 的随机浮点数
double randomInRange = a + (double)rand() / RAND_MAX * (b - a);
5. 完整代码示例
5.1 基础示例
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
// 初始化随机数种子
srand((unsigned int)time(NULL));
printf("========== 随机数生成示例 ==========\n\n");
// 1. 生成 0-99 的随机数
printf("1. 0-99 范围的随机数:\n");
for (int i = 0; i < 5; i++) {
printf(" %d\n", rand() % 100);
}
// 2. 生成 1-100 的随机数
printf("\n2. 1-100 范围的随机数:\n");
for (int i = 0; i < 5; i++) {
printf(" %d\n", 1 + rand() % 100);
}
// 3. 生成 0.0-1.0 的随机浮点数
printf("\n3. 0.0-1.0 范围的随机浮点数:\n");
for (int i = 0; i < 5; i++) {
printf(" %.4f\n", (double)rand() / RAND_MAX);
}
// 4. 生成 10.0-20.0 的随机浮点数
printf("\n4. 10.0-20.0 范围的随机浮点数:\n");
for (int i = 0; i < 5; i++) {
double num = 10.0 + (double)rand() / RAND_MAX * 10.0;
printf(" %.2f\n", num);
}
return 0;
}
5.2 实用示例:抽奖程序
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
srand((unsigned int)time(NULL));
int lotteryNum = 1 + rand() % 100; // 1-100 的中奖号码
int guess;
int attempts = 0;
printf("===== 幸运抽奖游戏 =====\n");
printf("猜一个 1-100 之间的数字:\n");
do {
printf("请输入你的猜测:");
scanf("%d", &guess);
attempts++;
if (guess > lotteryNum) {
printf("猜大了!\n");
} else if (guess < lotteryNum) {
printf("猜小了!\n");
} else {
printf("恭喜!你猜对了!\n");
printf("总共猜测了 %d 次\n", attempts);
}
} while (guess != lotteryNum);
return 0;
}
5.3 实用示例:随机密码生成器
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
// 生成随机密码
void generatePassword(char *password, int length) {
// 密码字符集:大写字母 + 小写字母 + 数字
char charset[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789";
int charsetSize = strlen(charset);
for (int i = 0; i < length; i++) {
int index = rand() % charsetSize;
password[i] = charset[index];
}
password[length] = '\0';
}
int main() {
srand((unsigned int)time(NULL));
int length;
printf("请输入密码长度:");
scanf("%d", &length);
char password[length + 1];
generatePassword(password, length);
printf("生成的随机密码:%s\n", password);
return 0;
}
5.4 实用示例:模拟掷骰子
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 掷一个骰子
int rollDie() {
return 1 + rand() % 6;
}
// 掷两个骰子
void rollTwoDice() {
int die1 = rollDie();
int die2 = rollDie();
printf("骰子1:%d 骰子2:%d 总和:%d\n", die1, die2, die1 + die2);
}
// 统计每个点数出现的次数
void diceStatistics(int rolls) {
int count[7] = {0}; // 索引 1-6 使用
for (int i = 0; i < rolls; i++) {
int result = rollDie();
count[result]++;
}
printf("\n%d 次掷骰子统计:\n", rolls);
for (int i = 1; i <= 6; i++) {
printf("点数 %d:%d 次 (%.2f%%)\n",
i, count[i], (double)count[i] / rolls * 100);
}
}
int main() {
srand((unsigned int)time(NULL));
printf("===== 模拟掷骰子 =====\n\n");
printf("单次掷骰子:%d\n", rollDie());
printf("\n掷两个骰子:\n");
rollTwoDice();
printf("\n");
diceStatistics(100);
return 0;
}
6. 进阶知识
6.1 更好的随机数生成器
标准库的 rand() 存在一些局限性:
· 周期较短(通常为 2^31)
· 随机性不够好
更好的选择:· C11 标准:rand_s()(Windows)
· POSIX:random()、srandom()
· Linux:/dev/urandom 设备文件
6.2 使用 random() 函数
#define _XOPEN_SOURCE
#include <stdlib.h>
int main() {
srandom((unsigned int)time(NULL));
// random() 返回 0 到 2^31-1 的值
long randomNum = random();
// 生成 1-100 的随机数
int num = 1 + random() % 100;
return 0;
}
6.3 洗牌算法(Fisher-Yates)
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 随机交换数组中的元素(洗牌)
void shuffle(int arr[], int n) {
for (int i = n - 1; i > 0; i--) {
// 生成 0 到 i 的随机索引
int j = rand() % (i + 1);
// 交换 arr[i] 和 arr[j]
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int main() {
srand((unsigned int)time(NULL));
int cards[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int size = sizeof(cards) / sizeof(cards[0]);
printf("原始顺序:");
for (int i = 0; i < size; i++) {
printf("%d ", cards[i]);
}
shuffle(cards, size);
printf("\n洗牌后:");
for (int i = 0; i < size; i++) {
printf("%d ", cards[i]);
}
printf("\n");
return 0;
}
7. 常见陷阱与注意事项
7.1 多次调用 srand()
// ❌ 错误示例:在循环中调用 srand()
for (int i = 0; i < 5; i++) {
srand((unsigned int)time(NULL)); // 错误!
printf("%d ", rand() % 100);
}
// 可能输出:41 41 41 41 41(因为 time() 秒数未变)
// ✅ 正确示例:只调用一次
srand((unsigned int)time(NULL));
for (int i = 0; i < 5; i++) {
printf("%d ", rand() % 100);
}
7.2 取模的均匀性问题
// 当 RAND_MAX+1 不能被 n 整除时,rand() % n 不是完全均匀的
// 例如 RAND_MAX=32767,n=10000,某些数字出现的概率略高
// 改进方法:拒绝采样
int getRandom(int n) {int limit = RAND_MAX - (RAND_MAX + 1) % n;
int r;
do {
r = rand();
} while (r > limit);
return r % n;
}
7.3 随机浮点数的精度
// 方法1:直接除法(推荐)
double r1 = (double)rand() / RAND_MAX; // 范围 [0, 1]
// 方法2:避免整数除法
double r2 = rand() / (RAND_MAX + 1.0); // 范围 [0, 1)
8. 练习题
基础练习
生成 10 个 1-50 的随机数,并找出最大值和最小值
模拟掷硬币 1000 次,统计正面和反面的次数
生成 20 个 0-100 的随机分数,计算平均分
进阶练习
实现一个简单的彩票系统:从 1-35 中随机抽取 5 个不重复的数字
编写一个程序,模拟两个玩家掷骰子,点数大者获胜,统计胜率
实现一个随机数组排序(随机打乱)函数
挑战练习
实现蒙特卡洛方法计算圆周率 π
编写一个程序,生成随机验证码(包含大写字母、小写字母、数字)
实现一个简单的老虎机游戏
9. 蒙特卡洛方法计算 π
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
double estimatePi(int points) {
int inside = 0;
for (int i = 0; i < points; i++) {
double x = (double)rand() / RAND_MAX; // 0-1 的随机数
double y = (double)rand() / RAND_MAX;
// 检查点是否在单位圆内
if (x * x + y * y <= 1.0) {
inside++;
}
}
// π ≈ 4 × (圆内点数 / 总点数)
return 4.0 * inside / points;
}
int main() {
srand((unsigned int)time(NULL));
int points[] = {100, 1000, 10000, 100000, 1000000};
printf("蒙特卡洛方法估算 π 值:\n");
printf("点数\t\t估算值\t\t误差\n");
printf("----------------------------------------\n");
for (int i = 0; i < 5; i++) {
double pi = estimatePi(points[i]);
printf("%d\t\t%.6f\t%.6f\n",
points[i], pi, fabs(pi - 3.1415926535));
}
return 0;
}
10. 知识点总结
知识点 说明
rand() 生成伪随机数,范围 0 到 RAND_MAX
srand() 设置随机数种子,通常使用 time(NULL)
time(NULL) 获取当前时间戳(秒),作为种子
rand() % n 生成 0 到 n-1 的随机数
a + rand() % (b-a+1) 生成 a 到 b 的随机整数
(double)rand() / RAND_MAX 生成 0 到 1 的随机浮点数
种子只设置一次 多次调用 srand() 会导致随机性变差
11. 课后作业
抽奖程序:设计一个抽奖程序,从 10 个参与者中随机抽取 3 个中奖者(不重复)
随机点名器:编写一个程序,从班级名单(可硬编码)中随机点名
猜数字游戏增强版:添加难度选择(不同范围),记录最佳成绩
密码生成器增强版:允许用户自定义字符集(是否包含特殊符号等)
记住:随机数是许多有趣程序的基础。掌握了随机数的使用,你就能创造出各种游戏、模拟程序和实用工具。多动手实践,才能真正理解随机数的奥秘!