在编程学习中,随机数生成是一个基础且关键的知识点。它不仅是模拟现实世界不确定性的核心工具,还能为游戏开发、算法设计注入独特的趣味性。本文将结合C语言标准库函数,系统讲解随机数的生成原理与实现方法,并基于此开发一个简单的猜数字游戏。内容将涵盖随机数生成的底层原理、常见问题的解决方案、完整代码示例及运行效果演示。
在C语言当中,C 语言的标准库中提供了一个用于生成随机数的函数,名为 rand,其函数原型如下:
intrand (void);
该函数声明于 <stdlib.h> 头文件中,调用时无需传入参数,返回值为一个范围在 0 到 RAND_MAX(C 语言标准规定的宏常量,通常取值为 32767)之间的伪随机整数。
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
return 0;
}第一次运行结果:
41 18467 26500 19169
第二次运行结果:
41 18467 26500 19169
如果直接调用 rand 函数,会出现一个明显的问题——每次运行程序,生成的随机数序列都会完全相同。这一现象并非函数异常,而是由 rand 函数的工作机制决定的:它生成的是伪随机数,而非真正的随机数。
伪随机数的核心特征的是:它不是基于随机的物理过程生成的,而是通过固定的数学算法,对一个初始基准值(称为“种子”)进行运算后得到的数值序列。真正的随机数具有完全不可预测性,无法通过前一个值推导下一个值;但伪随机数的序列是“可复现”的——只要初始种子不变,算法输出的随机数序列就会完全一致。
而 rand函数在默认情况下,会使用一个固定的种子(通常是 1)进行初始化。正因为这个默认种子始终不变,所以每次运行程序时,rand都会从同一个起点开始计算,最终输出完全相同的随机数序列。因此,若要让每次运行生成不同的随机数,核心是让初始化的“种子”值在每次程序运行时发生变化。
为解决上述问题,C 语言标准库中又额外提供了一个专门用于初始化随机数生成器的函数 ——srand,其核心作用是为 rand 函数的伪随机数生成算法设置初始 “种子”。该函数的原型如下:
voidsrand (unsigned intseed);
程序在调用 rand 函数之前会先调用 srand 函数,通过 srand 函数的参数 seed 来设置 rand 函数生成随机数的时候的种子,只要种子在变化,每次生成的随机数列也就变化起来了。 那也就是说,给 srand 的种子如果是随机的,rand 就能生成随机数;在生成随机数的时候又需要生成一个随机数,这就矛盾了。 为此,我们则又需要引用另一个函数——time函数,来为srand提供随每次程序运行而动态变化的种子值。
在实际 C 语言编程中,我们通常会选择程序运行时的系统时间作为 srand 的种子 —— 核心原因是时间始终处于动态流转中,能确保每次程序启动时,传入 srand 的种子值都不同,从根本上解决伪随机数序列重复的问题。 而 C 语言标准库中的 time 函数,恰好能帮我们获取这个动态变化的时间值,其函数原型为:
time_ttime (time_t* timer);
返回值含义:time 函数返回的是当前 “日历时间”,具体是从 1970 年 1 月 1 日 00:00:00(UTC 时间,即 Unix 时间戳起点) 到程序运行时刻的秒数差值,这个差值也被称为 “Unix 时间戳”。
返回值类型:返回值类型为 time_t—— 这是 C 语言专门定义的时间类型,本质上是 32 位或 64 位的整型(具体取决于编译器和操作系统,用于适配不同的时间范围)。
参数用法若 timer 不为 NULL,函数会将计算出的时间戳同时存入 timer 指向的内存地址(相当于 “返回值 + 输出参数” 双重传递); 若 timer 为 NULL,函数仅通过返回值传递时间戳(这是我们搭配 srand 时最常用的用法)。
注意事项
使用 time 函数前,必须包含其对应的头文件 <time.h>。
//VS2022 上time_t类型的说明
#ifndef _CRT_NO_TIME_T
#ifdef _USE_32BIT_TIME_T
typedef __time32_t time_t;
#else
typedef __time64_t time_t;
#endif
#endif
typedef long __time32_t;
typedef __int64 __time64_t;如果只是让time返回时间戳,我们就可以这样编写
time(NULL)//调用time函数返回时间戳,这里没有接收返回值结合以上函数,我们就可以实现真正的随机性
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
//因为srand的参数是unsigned int类型,我们将time函数的返回值强制类型转换
srand((unsigned int)time(NULL)); // 使用当前时间作为种子
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
return 0;
}srand 函数无需频繁调用,在程序的一次完整运行周期中,仅需调用一次即可满足需求。
1、如果我们要生成0~99之间的随机数,方法如下:
rand() % 100;//余数的范围为0~992、如果要生成1~100之间的随机数,方法如下:
rand() % 100 + 1;//余数范围为0~99,0~99数字加1,范围是1~1003、如果要生成100~200之间的随机数,方法如下:
100 + rand() % (200-100+1);//余数范围是0~100,加100后是100~2004、如果要生成a~b之间的随机数,方法如下:
a + rand() % (b-a+1);菜单模块的核心功能是向用户展示操作选项,引导用户完成 “开始游戏” 或 “退出程序” 的选择,它对应程序中的menu函数。
void menu()
{
printf("************************\n");
printf("****** 1. 开始游戏 ******\n");
printf("****** 0. 退出游戏 ******\n");
printf("************************\n");
}游戏核心模块负责实现猜数字的完整逻辑,包括生成随机数、接收用户猜测、判断结果并给出提示,对应程序中的game函数。
#include <stdio.h>
#include <stdlib.h>
void game() {
// 生成1~100的随机数
int r = rand() % 100 + 1;
int guess = 0;
// 循环接收猜测,直到猜对为止
while (1) {
printf("请猜数字>: ");
scanf("%d", &guess);
// 判断猜测结果并给出提示
if (guess < r) {
printf("猜小了\n");
} else if (guess > r) {
printf("猜大了\n");
} else {
printf("恭喜你,猜对了\n");
break; // 猜对后退出循环,结束本轮游戏
}
}
}主控制模块是程序的入口,负责串联菜单模块和游戏核心模块,完成整体流程控制,对应程序中的main函数。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 声明其他模块的函数
void menu();
void game();
int main() {
// 初始化随机种子,确保每次运行生成不同的随机数序列
srand((unsigned int)time(NULL));
int input = 0;
// do-while循环:先执行一次菜单展示,再判断是否退出
do {
menu(); // 调用菜单模块,展示选项
printf("请选择: ");
scanf("%d", &input);
// 根据用户选择执行对应操作
switch (input) {
case 1:
game(); // 选择1,调用游戏核心模块,开始游戏
break;
case 0:
printf("游戏结束\n"); // 选择0,退出程序
break;
default:
printf("选择错误,请重新输入\n"); // 处理无效输入
break;
}
} while (input != 0); // 当输入为0时,退出循环,程序结束
return 0;
}#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 菜单模块:展示操作选项
void menu() {
printf("*********************\n");
printf("****** 1. play ******\n");
printf("****** 0. exit ******\n");
printf("*********************\n");
}
// 游戏核心模块:实现猜数字逻辑
void game() {
int r = rand() % 100 + 1; // 生成1~100的随机数
int guess = 0;
while (1) {
printf("请猜数字>: ");
scanf("%d", &guess);
if (guess < r) {
printf("猜小了\n");
} else if (guess > r) {
printf("猜大了\n");
} else {
printf("恭喜你,猜对了\n");
break; // 猜对后退出本轮游戏
}
}
}
// 主控制模块:程序入口与流程控制
int main() {
srand((unsigned int)time(NULL)); // 初始化随机种子,确保每次运行随机数不同
int input = 0;
do {
menu(); // 显示菜单
printf("请选择: ");
scanf("%d", &input);
switch (input) {
case 1:
game(); // 启动游戏
break;
case 0:
printf("游戏结束\n"); // 退出程序
break;
default:
printf("选择错误,请重新输入\n"); // 处理无效输入
break;
}
} while (input != 0); // 输入0时退出循环
return 0;
}若我们想提高游戏的挑战性,以限制游戏可猜的次数。(如:猜五次还猜不出来,则算其失败) 则我们可以这样进行编写:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void game() {
int r = rand() % 100 + 1; // 生成1~100的随机数
int guess = 0;
int count = 5; // 定义变量,设置次数值
while (count > 0) //为0时跳出循环
{
printf("剩余猜测次数: %d\n", count);
printf("请猜数字>: ");
scanf("%d", &guess);
if (guess < r) {
printf("猜小了\n");
} else if (guess > r) {
printf("猜大了\n");
} else {
printf("恭喜你,猜对了\n");
return; // 猜对直接结束函数
}
count--; // 每猜一次,次数减1
}
// 循环结束即次数耗尽,游戏失败
printf("次数用完,游戏失败!正确答案是: %d\n", r);
}
void menu() {
printf("*********************\n");
printf("****** 1. play ******\n");
printf("****** 0. exit ******\n");
printf("*********************\n");
}
int main() {
srand((unsigned int)time(NULL)); // 初始化随机种子
int input = 0;
do {
menu();
printf("请选择: ");
scanf("%d", &input);
switch (input) {
case 1:
game();
break;
case 0:
printf("游戏结束\n");
break;
default:
printf("选择错误,请重新输入\n");
break;
}
} while (input != 0);
return 0;
}