随机数的类为Random,命名空间为System。
转到Random的定义可以看到如下代码(我把注释删掉了):
namespace System { public class Random { public Random(); public Random(int Seed);
public virtual int Next(); public virtual int Next(int minValue, int maxValue); public virtual int Next(int maxValue); public virtual void NextBytes(byte[] buffer); public virtual double NextDouble(); protected virtual double Sample(); } }
我们从易到难挨个说说。
Random();
这是无参构造方法,比如在程序中这样写:
Random a = new Random();
得到的a就是一个随机数生成器,它可以用来生成随机数。
int Next();
这个方法返回值为int类型,调用它就能得到一个随机数,反复调用就能反复得到随机数。
例:
static void Main(string[] args) { Random a = new Random(); for (int i = 0; i < 10; i++) { Console.WriteLine(a.Next()); } Console.ReadKey(); }
这样就得到了10个随机数,运行结果如下:
需要注意的是,虽然看上去Next方法返回值是int类型,但实际上它只返回非负整数,而且不包括int类型能表示的最大的那个整数(2^31 - 1)
int Next(int minValue, int maxValue);
这个方法能指定随机数的生成范围,左闭右开区间,即生成的数能包含minValue,不包含maxValue。可以包含负数。但是maxValue的值不能大于minValue的值,否则运行时会抛出异常。
int Next(int maxValue);
这个方法指定随机数的最大值(不包含maxValue),并且它也只能生成非负整数,与Next(0, maxValue)是一个道理,如果传入的maxValue为负数,那么运行时抛出异常,如果maxValue的值为0或1,那么生成的随机数只能是0。
double NextDouble();
这个方法能返回一个大于或等于 0.0 且小于 1.0 的随机浮点数。
void NextBytes(byte[] buffer);
这个方法传入指定长度的byte类型的数组,用byte类型的随机数填充数组,如:
static void Main(string[] args) { Random a = new Random(); byte[] b = new byte[10]; a.NextBytes(b); foreach(byte i in b) { Console.WriteLine(i); } Console.ReadKey(); }
运行结果:
double Sample();
这个方法比较特殊,从声明可以看出来,其他的方法权限都是public,这个方法是protected。从方法的描述上能看到,这个方法返回的也是大于或等于 0.0 且小于 1.0 的随机浮点数,它与NextDouble看上去似乎只是权限不一样,看了下面这段代码就知道了:
namespace sdq { class MyRandom : Random { protected override double Sample() { return 0.125; } } class Program { static void Main(string[] args) { MyRandom a = new MyRandom(); for (int i = 0; i < 10; i++) { Console.WriteLine(a.NextDouble()); } Console.ReadKey(); } } }
代码分析:
首先定义了一个类MyRandom,继承至Random,由于Random中的Sample()是虚方法,因此可以在派生类中将Sample()重写,我在这里是将它固定返回0.125。在Main方法中构造了a这个对象,并且调用了a.NextDouble()去生成10个随机数,运行结果如下:
从结果可以看出来,Random的Sample方法可以改变NextDouble()方法的行为,如果用户想自定义获取随机数的方法,则可以通过重写Sample来实现。
Random(int Seed);
最后再说一下这个带参构造函数,它用来在构造对象时指定随机数生成器的种子,而不带参的构造函数则是以时间作为种子。至于种子是个什么东西,我也不知道,通过下面这个例子也许能说明:
using System; using System.Threading; namespace test { class Program { static void Main(string[] args) { Random a = new Random(123); Thread.Sleep(1000); Random b = new Random(123); for (int i = 0; i < 10; i++) { Console.WriteLine("{0}\t{1}", a.Next(), b.Next()); } Console.ReadKey(); } } }
执行结果:
可见a对象和b对象是使用相同的种子123构造出来的,之后它们每次生成的随机数的值都是一样的。而如果使用默认的构造方法(以时间为种子)则不会有这种情况。
注:之所以调用Thread.Sleep(1000);是为了错开两次构造随机数对象的时间,如果不这么做的话,使用默认构造方法,连续两次调用Random()得到的结果仍然可能会一样,因为两次调用Random()时的时间是一样的。
我还没有去研究过windows系统时间的最小单位,按一般经验判断可能是us,想想1us内调用两次构造方法时间是够够的吧。