跳转至

任务四 Random类

在 C# 中,Random 类是一个专门用于生成伪随机数(Pseudo-random numbers)的工具类。它存在于 System 命名空间下。

对于初学者来说,可以从“原理、用法、陷阱、进阶”四个维度来深度理解它。


1. 核心原理:为什么叫“伪”随机数?

计算机本质上是确定性的逻辑机器,无法生成真正的物理随机数。

  • 种子(Seed)Random 类是基于数学算法生成的。它需要一个“起点”,这个起点被称为种子
  • 默认种子:当你使用 new Random() 时,C# 默认使用系统时钟(当前时间戳)作为种子。
  • 相同种子 = 相同序列:如果两个 Random 对象的初始种子完全一样,它们生成的随机数序列也会完全一模一样。

2. 基本用法详解

要使用它,必须先通过 new 关键字进行实例化。

(1) 生成整数:Next()

这是最常用的方法,有三种重载形式:

  • rd.Next():返回一个非负的任意整数(0 到 )。
  • rd.Next(int max):返回 之间的整数。注意:不包含 max 本身。
  • rd.Next(int min, int max):返回 之间的整数。

(2) 生成浮点数(小数):NextDouble()

  • 返回一个 之间的双精度浮点数。
  • 如果你想生成 0 到 100 之间的小数,可以使用 rd.NextDouble() * 100

(3) 生成随机布尔值(True/False)

Random 类没有直接的 NextBool 方法,但我们可以通过逻辑模拟:

bool coinFlip = rd.Next(0, 2) == 0; // 50% 概率为 true

3. 初学者必踩的“大坑”

坑点:在循环内部重复创建实例。

// 错误示范
for (int i = 0; i < 100; i++) {
    Random rd = new Random(); 
    Console.WriteLine(rd.Next(1, 100)); // 结果几乎全部相同!
}

原因: 计算机运行速度极快,在 1 毫秒内循环可能已经执行了上百次。因为种子是基于时钟的,这上百个实例拿到的种子完全一样,计算出来的第一个随机数自然也就一模一样。

正确方案:单例化(只 new 一次)。

Random rd = new Random(); // 放在循环外面
for (int i = 0; i < 100; i++) {
    Console.WriteLine(rd.Next(1, 100)); // 得到完美的随机分布
}

4. 常用技巧示例

场景 A:从列表/数组中随机抽取

string[] students = { "Alice", "Bob", "Charlie", "David" };
Random rd = new Random();
// 索引范围是 0 到 Length-1,正好符合 Next(0, Length) 的排除性
string luckyOne = students[rd.Next(0, students.Length)];

场景 B:随机百分比触发(掉落率/暴击)

Random rd = new Random();
int rate = rd.Next(1, 101); // 1-100 均匀分布

if (rate <= 5) {
    Console.WriteLine("掉落史诗级装备(5%概率)");
}

5. 总结表

特性 说明
命名空间 System
包含/排除 包含下限 (Inclusive),排除上限 (Exclusive)
线程安全 Random 类不是线程安全的(多线程环境建议使用 ThreadSafeRandom 或新版的 Random.Shared
安全性 它是非密码学安全的。如果是生成支付密码、密钥,请改用 System.Security.Cryptography.RandomNumberGenerator

你会发现现代 C# (.NET 6+) 引入了一个更简单的写法:Random.Shared.Next(1, 101),这样你连 new 都不用了。你想让我教你如何使用这个更现代、更安全的写法吗?

在 C# 中,Random 类是生成伪随机数的工具,广泛应用于游戏(如掉落率、洗牌)、模拟测试和验证码生成。

对于初学者,可以从以下四个核心方面来掌握它的用法:


1. 基础用法:创建与调用

要使用随机数,首先需要实例化(创建一个对象),然后调用它的方法。

// 1. 实例化
Random rd = new Random();

// 2. 生成一个非负整数
int num1 = rd.Next(); 

// 3. 生成 0 到 99 之间的整数 (不包含 100)
int num2 = rd.Next(100); 

// 4. 生成 1 到 6 之间的整数 (模拟掷骰子)
int dice = rd.Next(1, 7); 

注意边界值: Next(min, max) 方法遵循 “包含上限,排除下限” 的原则。即 Next(1, 10) 只能产生 1 到 9,永远不会产生 10。


2. 常见方法速查表

方法 说明 示例
Next() 返回一个 0 到 之间的整数 rd.Next()
Next(max) 返回一个 之间的整数 rd.Next(10) (0-9)
Next(min, max) 返回一个 之间的整数 rd.Next(5, 15) (5-14)
NextDouble() 返回一个 之间的随机双精度浮点数 rd.NextDouble()

3. 初学者最容易掉的“坑”:重复问题

这是初学者最常遇到的问题:为什么我生成的随机数全都一模一样?

  • 错误做法: 在循环内部实例化 Random

    for (int i = 0; i < 5; i++) {
        Random rd = new Random(); // 错误!由于运行太快,系统时钟种子相同,导致结果一致
        Console.WriteLine(rd.Next(1, 100));
    }
    

  • 正确做法: 在循环外部只实例化一次。

    Random rd = new Random(); // 正确!
    for (int i = 0; i < 5; i++) {
        Console.WriteLine(rd.Next(1, 100));
    }
    


4. 两个实战小技巧

A. 如何随机获取数组元素?

这是随机数最常见的用途:从一组数据中随机抽选。

string[] rewards = { "金牌", "银牌", "铜牌", "谢谢参与" };
Random rd = new Random();

// 使用 Next(0, 数组长度) 获取索引
int index = rd.Next(0, rewards.Length);
Console.WriteLine($"你抽中了:{rewards[index]}");

B. 如何实现百分比概率?

如果你想让某件事有 20% 的概率发生(例如游戏暴击):

Random rd = new Random();
int chance = rd.Next(1, 101); // 1-100

if (chance <= 20) {
    Console.WriteLine("触发暴击!");
} else {
    Console.WriteLine("普通攻击");
}

总结

掌握 Random 类只需要记住三点:

  1. 左闭右开Next(1, 3) 只有 1 和 2。
  2. 单例创建:一个程序通常只需要一个 Random 实例。
  3. 整数与浮点:默认 Next 是整数,NextDouble 是小数。

你想尝试写一个小程序,利用随机数来模拟一个简单的“抽卡系统”吗?

生成随机数(Random 类)是 C# 编程中增加程序趣味性和交互性的重要工具。掌握它不仅能做游戏,还能模拟现实世界的数据。

这里为你设计了 10 道由浅入深的随机数入门练习题:


1. 基础:随机点名

题目: 创建一个包含 5 个名字的字符串数组(如 "小明", "小红" 等),程序运行时随机打印出其中的一个名字。

  • 目的: 练习如何将随机数作为数组的索引(下标)。

2. 范围判定:硬币投掷器

题目: 模拟掷硬币。生成一个 0 或 1 的随机数,如果结果是 0 输出“正面”,如果是 1 输出“反面”。

  • 目的: 练习 random.Next(min, max) 的边界值(注意:不包含 max)。

3. 多样化:随机颜色生成

题目: 生成三个 0 到 255 之间的随机整数,分别代表 RGB 颜色的红、绿、蓝分量,并按照 RGB(12, 255, 70) 的格式输出。

  • 目的: 练习多次调用 Next() 方法。

4. 验证码生成:4位数字

题目: 使用 for 循环生成一个 4 位数的随机数字验证码(每一位都是独立的 0-9 数字),并以字符串形式拼接输出。

  • 目的: 练习在循环中生成随机数并进行字符串累加。

5. 游戏逻辑:暴击判定

题目: 模拟游戏攻击。设定玩家攻击力为 50,生成一个 1 到 100 之间的随机数作为“暴击概率”。如果随机数大于 80,则触发暴击,输出“双倍伤害:100”;否则输出“普通攻击:50”。

  • 目的: 练习利用随机数进行百分比逻辑判定。

6. 数据处理:随机数组填充

题目: 创建一个长度为 10 的整数数组,用 1 到 100 之间的随机数填满它,最后打印出这个数组以及其中的最大值。

  • 目的: 练习随机数与数组、循环的综合运用。

7. 模拟场景:随机验证密码

题目: 预设一个正确密码(如 123)。让计算机用 for 循环尝试 10 次,每次随机生成一个 100 到 150 之间的数字,看是否能在 10 次内“撞中”正确密码。

  • 目的: 练习随机数与逻辑搜索。

8. 数学应用:随机加法题

题目: 程序自动生成两个 1 到 20 之间的随机整数 和 ,然后在屏幕上显示 $a$ + $b$ = ?,由用户输入答案,程序判断用户答对还是答错。

  • 目的: 练习随机数在教育交互类程序中的应用。

9. 洗牌算法初探:位置交换

题目: 有一个数组 [1, 2, 3, 4, 5]。随机生成两个 0 到 4 之间的索引 ij,交换数组中这两个位置的数字。运行 3 次后打印数组。

  • 目的: 理解随机交换逻辑。

10. 综合挑战:随机天气模拟

题目: 模拟一周的天气。使用循环遍历 7 天,每天随机生成一个温度(-10 到 35 度)。

  • 要求:如果是 30 度以上,输出“高温天气”;如果是 0 度以下,输出“严寒警告”;否则输出“气温正常”。
  • 目的: 练习多重分支条件(if-else if-else)与随机数的配合。

💡 核心知识提示

在 C# 中使用随机数,千万不要在循环内部写 Random rd = new Random();,否则因为计算机运行太快,生成的随机数可能会完全一样。

正确写法:

Random rd = new Random(); // 定义在循环外面
for (int i = 0; i < 10; i++) 
{
    int num = rd.Next(1, 101); // 1-100
}

你想先看哪一道题的代码实现参考?

为了方便你学习,我整理了这 10 道随机数练习题的标准答案。你可以逐个运行并观察结果,建议重点关注随机数是如何与 iffor数组 配合使用的。


1 - 4 基础应用

Random rd = new Random();

// 1. 随机点名
string[] names = { "小明", "小红", "小刚", "小亮", "小芳" };
int nameIndex = rd.Next(0, names.Length);
Console.WriteLine($"随机点名:{names[nameIndex]}");

// 2. 硬币投掷
int coin = rd.Next(0, 2); // 生成 0 或 1
Console.WriteLine(coin == 0 ? "正面" : "反面");

// 3. 随机颜色 RGB
int r = rd.Next(0, 256);
int g = rd.Next(0, 256);
int b = rd.Next(0, 256);
Console.WriteLine($"生成的颜色是:RGB({r}, {g}, {b})");

// 4. 4位数字验证码
string code = "";
for (int i = 0; i < 4; i++) {
    code += rd.Next(0, 10).ToString();
}
Console.WriteLine($"您的验证码是:{code}");

5 - 7 逻辑判定与循环

// 5. 暴击判定
int baseDamage = 50;
int critChance = rd.Next(1, 101);
if (critChance > 80) {
    Console.WriteLine($"暴击!触发双倍伤害:{baseDamage * 2}");
} else {
    Console.WriteLine($"普通攻击:{baseDamage}");
}

// 6. 随机数组填充并求最大值
int[] numbers = new int[10];
int max = 0;
for (int i = 0; i < numbers.Length; i++) {
    numbers[i] = rd.Next(1, 101);
    if (numbers[i] > max) max = numbers[i];
    Console.Write(numbers[i] + " ");
}
Console.WriteLine($"\n最大值是:{max}");

// 7. 随机验证密码 (撞库模拟)
int correctPass = 123;
bool isFound = false;
for (int i = 1; i <= 10; i++) {
    int attempt = rd.Next(100, 151);
    if (attempt == correctPass) {
        Console.WriteLine($"第{i}次尝试成功!密码是 {attempt}");
        isFound = true;
        break;
    }
}
if (!isFound) Console.WriteLine("10次机会已用完,未猜中密码。");

8 - 10 综合交互与算法

// 8. 随机加法题
int num1 = rd.Next(1, 21);
int num2 = rd.Next(1, 21);
Console.Write($"{num1} + {num2} = ? 请输入答案:");
if (int.TryParse(Console.ReadLine(), out int userAns) && userAns == (num1 + num2)) {
    Console.WriteLine("回答正确!");
} else {
    Console.WriteLine($"打错啦,正确答案是 {num1 + num2}");
}

// 9. 洗牌逻辑 (随机交换)
int[] cards = { 1, 2, 3, 4, 5 };
for (int k = 0; k < 3; k++) {
    int i = rd.Next(0, cards.Length);
    int j = rd.Next(0, cards.Length);
    // 交换变量
    int temp = cards[i];
    cards[i] = cards[j];
    cards[j] = temp;
}
Console.WriteLine("交换3次后的数组:" + string.Join(", ", cards));

// 10. 随机天气模拟
for (int day = 1; day <= 7; day++) {
    int temp = rd.Next(-10, 36);
    string status = "";
    if (temp > 30) status = "高温天气";
    else if (temp < 0) status = "严寒警告";
    else status = "气温正常";
    Console.WriteLine($"周{day}:{temp}度,{status}");
}

💡 核心知识点梳理

为了帮你更好地理解这些代码,请参考下表:

操作 代码语法 注意事项
初始化 Random rd = new Random(); 必须写在循环外面,否则随机数会重复。
整数范围 rd.Next(1, 10); 包含 1,但不包含 10(范围是 1-9)。
数组索引 rd.Next(0, array.Length); 这是获取数组随机元素的标准写法。
百分比概率 if (rd.Next(1, 101) <= 20) 代表有 20% 的概率触发某个事件。

接下来建议

你已经练习了 for 循环和随机数,这已经具备了写小型游戏的基础。你想尝试学习如何把这些逻辑封装成方法 (Method) 吗?这样你的程序会变得更加整洁和专业。

练习题

// 1. 初始化随机数
Random random = new Random();
int targetNumber = random.Next(1, 101); // 生成 1 到 100 之间的随机数
int maxAttempts = 5; // 设置最大尝试次数

Console.WriteLine("欢迎来到猜数字游戏!");
Console.WriteLine($"我已经想好了一个 1 到 100 之间的数字。你有 {maxAttempts} 次机会。");

// 2. 使用 for 循环控制尝试次数
for (int i = 1; i <= maxAttempts; i++)
{
    Console.Write($"\n第 {i} 次尝试,请输入你的猜测:");

    // 获取用户输入并转换为整数
    string input = Console.ReadLine();
    int guess;

    if (!int.TryParse(input, out guess))
    {
        Console.WriteLine("请输入有效的数字!");
        continue; 
    }

    // 3. 逻辑判断
    if (guess == targetNumber)
    {
        Console.WriteLine($"恭喜你!你猜对了,答案就是 {targetNumber}!");
        break; // 猜对了,提前跳出循环
    }
    else if (guess < targetNumber)
    {
        Console.WriteLine("太小了,再试一次。");
    }
    else
    {
        Console.WriteLine("太大了,再试一次。");
    }

    // 4. 检查是否是最后一次机会
    if (i == maxAttempts)
    {
        Console.WriteLine($"\n很遗憾,机会用完了!正确答案是 {targetNumber}。");
    }
}

Console.WriteLine("\n游戏结束,按任意键退出...");
Console.ReadKey();

练习题

模拟掷骰子:点数大作战 题目: 模拟两人(玩家和电脑)轮流掷骰子 5 次。

逻辑:

使用 for 循环运行 5 轮。

每轮使用 Random 为玩家和电脑各生成一个 1-6 的随机数。

比较大小,记录谁赢了那一轮。

目标: 循环结束后,输出最终的比分(例如:玩家 3 : 2 电脑)并宣布最终胜者。

提升点: 学习如何在循环中维护“计数器”(Score Counter)变量。

using System;

class Program
{
    static void Main()
    {
        // 1. 初始化随机数生成器和得分变量
        Random random = new Random();
        int playerWinCount = 0;
        int computerWinCount = 0;
        int totalRounds = 5;

        Console.WriteLine("=== 欢迎来到点数大作战 (5轮对决) ===");

        // 2. 使用 for 循环进行 5 轮比赛
        for (int i = 1; i <= totalRounds; i++)
        {
            Console.WriteLine($"\n--- 第 {i} 轮开始 ---");
            Console.WriteLine("按任意键掷出你的骰子...");
            Console.ReadKey(true); // 参数 true 表示不显示按下的键

            // 生成 1 到 6 之间的随机数
            int playerRoll = random.Next(1, 7);
            int computerRoll = random.Next(1, 7);

            Console.WriteLine($"你掷出了: {playerRoll}");
            Console.WriteLine($"电脑掷出了: {computerRoll}");

            // 3. 比较本轮点数
            if (playerRoll > computerRoll)
            {
                Console.WriteLine("结果:你赢了本轮!");
                playerWinCount++; // 玩家得分累加
            }
            else if (playerRoll < computerRoll)
            {
                Console.WriteLine("结果:电脑赢了本轮!");
                computerWinCount++; // 电脑得分累加
            }
            else
            {
                Console.WriteLine("结果:平局!双方均不得分。");
            }

            // 实时打印当前比分
            Console.WriteLine($"当前比分 - 玩家 {playerWinCount} : {computerWinCount} 电脑");
        }

        // 4. 最终结果判定
        Console.WriteLine("\n==============================");
        Console.WriteLine("游戏结束!");
        Console.WriteLine($"最终总比分:玩家 {playerWinCount} VS 电脑 {computerWinCount}");

        if (playerWinCount > computerWinCount)
        {
            Console.WriteLine("祝贺你!你获得了最后的胜利!");
        }
        else if (playerWinCount < computerWinCount)
        {
            Console.WriteLine("遗憾!电脑获得了最后的胜利。");
        }
        else
        {
            Console.WriteLine("最终结果是平局!");
        }

        Console.WriteLine("==============================");
        Console.WriteLine("按任意键退出...");
        Console.ReadKey();
    }
}