42 C 语言随机数生成详解:rand/srand 使用技巧、随机数范围控制、真实场景应用

发布于:2025-06-13 ⋅ 阅读:(25) ⋅ 点赞:(0)

1 随机数

1.1 随机数定义

        随机数指的是在特定范围内,每个数字被选中的概率均等,且其出现结果不可提前预知的数字序列

        由于计算机本质上是遵循确定性逻辑运行的程序化系统,无法直接生成真正意义上的随机数。因此,计算机通常借助特定算法生成伪随机数。这类伪随机数在统计特性上与真随机数极为相似,足以满足大多数实际应用场景的需求。以掷骰子游戏为例,在理想状况下,骰子的每个点数(1 - 6)出现的概率相等,且每次投掷的结果相互独立。计算机在模拟掷骰子时,就需要生成具有类似特性的随机数。

1.2 随机数应用场景

  • 游戏开发:在 RPG 等游戏中,利用随机数模拟敌人攻击伤害、暴击概率(如设定 20% 暴击率,生成 0 - 99 随机数,0 - 19 则判定暴击),以及道具掉落、地图生成等,为游戏增添趣味性与不确定性。
  • 密码学:生成密钥时需高质量随机数保障不可预测性,否则如对称加密算法中,密钥若可预测,攻击者可能通过分析加密数据破解信息,故对随机数算法和质量要求严苛。
  • 模拟实验:科学研究和工程领域模拟实验常用随机数。气象模拟中,气流变化、云层形成等随机因素用随机数模拟可提高准确性;交通流量模拟用随机数表示车辆出现时间、速度,能研究不同交通状况。
  • 抽样调查:统计学抽样调查借助随机数选样本,保证代表性与公正性。大型人口调查时,用随机数生成器选样本编号,确保个体被选机会均等,避免人为干扰。

2 随机数生成方法

        在 C 语言中,生成随机数主要依赖标准库里的 <stdlib.h> 头文件,其中 rand() 函数和 srand() 函数起着关键作用。

2.1 rand() 函数

函数原型

#include <stdlib.h>  // 必须包含此头文件才能使用 rand() 函数

int rand(void);

功能说明

        rand() 函数用于生成一个伪随机数,其取值范围为 0 到 RAND_MAX(在大多数系统中 RAND_MAX 的值为 32767)。

        该函数基于内部的随机数生成算法,每次调用都会返回一个新的伪随机整数。若未设置随机种子,程序每次运行时生成的序列是相同的

  • 返回值:
    • 返回一个介于 0 和 RAND_MAX 之间的整数(包括两端)

注意事项

  • 默认种子固定:如果不使用 srand() 设置随机种子,程序每次运行将生成相同的随机数序列
  • 随机数范围限制:rand() 返回的数值上限由 RAND_MAX 决定
  • 非加密安全:rand() 生成的随机数不具备密码学安全性,不适用于涉及密钥、验证码等安全敏感场景
  • 分布不均风险:直接对 rand() 的结果取模可能会导致随机数分布不均匀

示例程序

#include <stdio.h>
#include <stdlib.h> // 必须包含此头文件才能使用 rand() 函数

int main()
{
    // 生成并打印 5 个随机数
    for (int i = 0; i < 5; i++)
    {
        printf("随机数 %d: %d\n", i + 1, rand());
    }

    return 0;
}

        程序在 VS Code 中的运行结果如下所示:

2.2 srand() 函数

函数原型

#include <stdlib.h>  // 必须包含此头文件才能使用 srand() 函数

void srand(unsigned int seed);

功能说明

        srand() 函数用于设置伪随机数生成器的起始种子值,该种子值会影响后续调用 rand() 所生成的随机数序列

        在默认情况下,如果不调用 srand(),程序会自动以一个固定的种子(等效于 srand(1))开始生成随机数,导致每次运行程序时都会得到相同的 “随机” 序列

  • 参数:
    • seed:一个无符号整型值,作为随机数生成器的种子。不同的种子将产生不同的随机数序列

注意事项

  • 建议每次运行使用不同种子:通常结合 (unsigned int)time(NULL) 作为种子传入 srand(),以确保每次运行程序时生成的随机数序列都不同
  • 仅需调用一次:应在程序中只调用一次 srand(),多次调用可能导致随机性降低
  • 种子影响整个序列:同一个种子会生成完全相同的随机数序列,这在调试或测试时非常有用
  • 避免低质量种子:不要使用简单的、可预测的种子来生成安全相关的随机数(如密码生成),应使用更安全的随机源

示例程序

#include <stdio.h>
#include <stdlib.h> // 必须包含此头文件才能使用 rand()、srand() 函数
#include <time.h>   // 必须包含此头文件才能使用 time() 函数

int main()
{
    // 使用当前时间作为随机种子
    // time(NULL) 返回当前时间(时间戳),以秒为单位
    srand((unsigned int)time(NULL));

    // 生成并打印 5 个随机数
    for (int i = 0; i < 5; i++)
    {
        printf("随机数 %d: %d\n", i + 1, rand());
    }

    return 0;
}

        程序在 VS Code 中的运行结果如下所示:


生成指定范围随机数

        在 C 语言中,rand() 函数生成的随机数范围是 [0, RAND_MAX],其中 RAND_MAX 是 <stdlib.h> 中定义的常量,表示 rand() 函数能返回的最大值。但在实际应用中,我们常常需要生成特定范围内的随机数,下面将分别介绍如何生成指定范围的随机整数和随机浮点数。

3.1 [min, max] 区间内随机整数生成

        要将 rand() 生成的随机数限制在 [min, max] 范围内,可以使用取模运算(%)和适当的偏移量。具体公式如下:

int randomNumberInRange = rand() % (max - min + 1) + min;

        假设我们想要生成一个在 [min, max] 范围内的随机整数。我们可以通过以下步骤来理解这个公式的数学逻辑:

  1. 初始随机数范围:
    • rand() 函数生成的随机数满足 0 <= rand() <= RAND_MAX
  2. 模运算:
    • 使用 rand() % (max - min + 1) 这一步是为了将 rand() 的结果限制在一个更小的范围内。
    • 具体来说,rand() % (max - min + 1) 的结果满足 0 <= (rand() % (max - min + 1)) < max - min + 1
  3. 平移范围:
    • 通过 (rand() % (max - min + 1)) + min,可以将结果的范围从 [0, max - min + 1)(即 [0, max - min])平移到 [min, max]

3.2 [min, max] 区间内随机浮点数生成

        要将 rand() 生成的随机数限制在 [min, max] 范围内,可以使用乘除运算和适当的偏移量。具体公式如下:

(double)rand() / RAND_MAX * (max - min) + min;

        假设我们想要生成一个在 [min, max] 范围内的随机浮点数。我们可以通过以下步骤来理解这个公式的数学逻辑:

  1. 初始随机数范围:
    • rand() 函数生成的随机数满足 0 <= rand() <= RAND_MAX
  2. 归一化:
    • (double)rand() / RAND_MAX 将 rand() 的结果归一化到 [0, 1] 范围内
    • 由于 rand() 返回的是整数,(double)rand() 将整数转换为浮点数,以便进行浮点数运算。
  3. 缩放和平移:
    • (double)rand() / RAND_MAX * (max - min) 将归一化的结果缩放到 [0, max - min] 范围内
    • (double)rand() / RAND_MAX * (max - min) + min 将结果的范围从 [0, max - min] 平移到 [min, max]

注意:

        由于浮点数的精度问题,(double)rand() / RAND_MAX 可能无法精确等于 1.0(即使数学上应该如此),因此 max 可能无法被严格覆盖。

3.3 随机数生成函数封装

        为了方便使用,可以将生成指定范围随机数的功能编写成函数,在需要的时候调用即可。以下是封装后的函数示例:

// 生成指定范围 [min, max] 的随机整数
int generateRandomInt(int min, int max)
{
    return rand() % (max - min + 1) + min;
}

// 生成指定范围内 [min, max] 的随机浮点数
double generateRandomFloat(double min, double max)
{
    return (double)rand() / RAND_MAX * (max - min) + min;
}

        下面是一个完整的示例程序,展示了如何使用封装好的函数生成指定范围的随机整数和随机浮点数:

#include <stdio.h>
#include <stdlib.h> // 必须包含此头文件才能使用 rand()、srand() 函数
#include <time.h>   // 必须包含此头文件才能使用 time() 函数

// 生成指定范围 [min, max] 的随机整数
int generateRandomInt(int min, int max)
{
    return rand() % (max - min + 1) + min;
}

// 生成指定范围内 [min, max] 的随机浮点数
double generateRandomFloat(double min, double max)
{
    return (double)rand() / RAND_MAX * (max - min) + min;
}

int main()
{
    // 设置随机数种子
    srand((unsigned int)time(NULL));

    // 生成并打印一个在 [10, 50] 范围内的随机整数
    int minInt = 10;
    int maxInt = 50;
    int randomInt = generateRandomInt(minInt, maxInt);
    printf("随机整数: %d\n", randomInt);

    // 生成并打印一个在 [1.0, 5.0) 范围内的随机浮点数
    double minFloat = 1.0;
    double maxFloat = 5.0;
    double randomFloat = generateRandomFloat(minFloat, maxFloat);
    printf("随机浮点数: %.2f\n", randomFloat);

    return 0;
}

        程序在 VS Code 中的运行结果如下所示:


4 编程练习

4.1 随机骰子游戏

        模拟一个简单的骰子游戏,玩家掷一个六面骰子,随机生成一个 1 到 6 之间的整数。

#include <stdio.h>
#include <stdlib.h> // 必须包含此头文件才能使用 rand()、srand() 函数
#include <time.h>   // 必须包含此头文件才能使用 time() 函数

int main()
{
    srand((unsigned int)time(NULL)); // 初始化随机数种子
    // 1 ~ 6 一共有 (6 - 1 + 1) = 6 种可能,所以使用 % 6
    int diceRoll = rand() % 6 + 1; // 生成 1 到 6的随机数

    printf("你掷出了:%d\n", diceRoll);

    return 0;
}

        程序在 VS Code 中的运行结果如下所示:

4.2 随机密码生成器

        编写一个程序,生成一个随机的四位数字密码。

#include <stdio.h>
#include <stdlib.h> // 必须包含此头文件才能使用 rand()、srand() 函数
#include <time.h>   // 必须包含此头文件才能使用 time() 函数

int main()
{
    srand((unsigned int)time(NULL)); // 初始化随机数种子
    // 四位数字 1000 ~ 9999 一共有 (9999 - 1000 + 1) = 9000 个数字,所以使用 % 9000
    int password = rand() % 9000 + 1000; // 生成 1000 到 9999 的随机数

    printf("你的随机密码是:%d\n", password);

    return 0;
}

        程序在 VS Code 中的运行结果如下所示:

4.3 随机选择餐馆

        假设你有三家最喜欢的餐馆,编写一个程序,随机选择其中一家作为今天的晚餐地点。

#include <stdio.h>
#include <stdlib.h> // 必须包含此头文件才能使用 rand()、srand() 函数
#include <time.h>   // 必须包含此头文件才能使用 time() 函数

int main()
{
    srand((unsigned int)time(NULL)); // 初始化随机数种子
    const char restaurants[3][20] = {"餐馆A", "餐馆B", "餐馆C"};
    int index = rand() % 3; // 生成 0 到 2 的随机数
    printf("今天去:%s\n", restaurants[index]);

    return 0;
}

        程序在 VS Code 中的运行结果如下所示:

4.4 随机抽奖系统实现

        设计一个抽奖系统,有 N 个参与者(N 由用户输入),随机抽取 M 个获奖者(M 由用户输入,且 M <= N)。需要确保每个获奖者都是唯一的,并且不能重复中奖。

C 代码实现思路:

  • 使用数组存储参与者编号。
  • 使用随机数生成器选择获奖者,并确保不重复。
  • 可以通过打乱数组顺序或使用额外的标记数组来实现不重复选择。
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> // 必须包含此头文件才能使用 rand()、srand() 函数
#include <time.h>   // 必须包含此头文件才能使用 time() 函数

#define MAX_PARTICIPANTS 100 // 定义最大参与者数量

int main()
{
    // time(0) 等价于 time(NULL),返回当前时间的秒数
    srand((unsigned int)time(0)); // 使用当前时间初始化随机数种子,以确保每次运行程序时随机数不同

    int N, M; // 参与者总数和获奖者数量
    printf("请输入参与者总数 N:");
    scanf("%d", &N); // 读取用户输入的参与者总数 N
    printf("请输入获奖者数量 M:");
    scanf("%d", &M); // 读取用户输入的获奖者数量 M

    if (M > N)
    {
        printf("错误:获奖者数量不能超过参与者总数。\n");
        return 1; // 如果 M 大于 N,输出错误信息并返回 1,表示程序异常结束
    }

    int participants[MAX_PARTICIPANTS];        // 用于存储参与者编号的数组
    bool selected[MAX_PARTICIPANTS] = {false}; // 用于标记参与者是否已被选中的布尔数组,初始化为 false

    // 初始化参与者编号
    for (int i = 0; i < N; i++)
    {
        participants[i] = i + 1; // 将参与者编号从 1 开始依次赋值给数组
    }

    // 随机选择获奖者
    printf("获奖者编号:");
    for (int i = 0; i < M; i++)
    {
        int index; // 用于存储随机索引的变量
        do
        {
            index = rand() % N; // 生成一个 0 到 N-1 之间的随机索引
        } while (selected[index]); // 确保选择的索引未被选中过

        selected[index] = true;             // 标记该索引对应的参与者为已选中
        printf("%d ", participants[index]); // 输出获奖者的编号
    }
    printf("\n");

    return 0; // 程序正常结束
}

        程序在 VS Code 中的运行结果如下所示: