-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathch08s02.html
105 lines (91 loc) · 10.1 KB
/
ch08s02.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>2. 数组应用实例:统计随机数</title><link rel="stylesheet" href="styles.css" type="text/css" /><meta name="generator" content="DocBook XSL Stylesheets V1.73.2" /><link rel="start" href="index.html" title="Linux C编程一站式学习" /><link rel="up" href="ch08.html" title="第 8 章 数组" /><link rel="prev" href="ch08s01.html" title="1. 数组的基本概念" /><link rel="next" href="ch08s03.html" title="3. 数组应用实例:直方图" /></head><body><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2. 数组应用实例:统计随机数</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch08s01.html">上一页</a> </td><th width="60%" align="center">第 8 章 数组</th><td width="20%" align="right"> <a accesskey="n" href="ch08s03.html">下一页</a></td></tr></table><hr /></div><div class="sect1" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="id2733695"></a>2. 数组应用实例:统计随机数</h2></div></div></div><p>本节通过一个实例介绍使用数组的一些基本模式。问题是这样的:首先生成一列0~9的随机数保存在数组中,然后统计其中每个数字出现的次数并打印,检查这些数字的随机性如何。随机数在某些场合(例如游戏程序)是非常有用的,但是用计算机生成完全随机的数却不是那么容易。计算机执行每一条指令的结果都是确定的,没有一条指令产生的是随机数,调用C标准库得到的随机数其实是伪随机(Pseudorandom)<a id="id2733724" class="indexterm"></a>数,是用数学公式算出来的确定的数,只不过这些数看起来很随机,并且从统计意义上也很接近均匀分布(Uniform Distribution)<a id="id2733734" class="indexterm"></a>的随机数。</p><p>C标准库中生成伪随机数的是<code class="literal">rand</code>函数,使用这个函数需要包含头文件<code class="literal">stdlib.h</code>,它没有参数,返回值是一个介于0和<code class="literal">RAND_MAX</code>之间的接近均匀分布的整数。<code class="literal">RAND_MAX</code>是该头文件中定义的一个常量,在不同的平台上有不同的取值,但可以肯定它是一个非常大的整数。通常我们用到的随机数是限定在某个范围之中的,例如0~9,而不是0~<code class="literal">RAND_MAX</code>,我们可以用%运算符将<code class="literal">rand</code>函数的返回值处理一下:</p><pre class="programlisting">int x = rand() % 10;</pre><p>完整的程序如下:</p><div class="example"><a id="id2733797"></a><p class="title"><b>例 8.2. 生成并打印随机数</b></p><div class="example-contents"><pre class="programlisting">#include <stdio.h>
#include <stdlib.h>
#define N 20
int a[N];
void gen_random(int upper_bound)
{
int i;
for (i = 0; i < N; i++)
a[i] = rand() % upper_bound;
}
void print_random()
{
int i;
for (i = 0; i < N; i++)
printf("%d ", a[i]);
printf("\n");
}
int main(void)
{
gen_random(10);
print_random();
return 0;
}</pre></div></div><br class="example-break" /><p>这里介绍一种新的语法:用<code class="literal">#define</code>定义一个常量。实际上编译器的工作分为两个阶段,先是预处理(Preprocess)<a id="id2733822" class="indexterm"></a>阶段,然后才是编译阶段,用<code class="literal">gcc</code>的<code class="literal">-E</code>选项可以看到预处理之后、编译之前的程序,例如:</p><pre class="screen">$ gcc -E main.c
...(这里省略了很多行stdio.h和stdlib.h的代码)
int a[20];
void gen_random(int upper_bound)
{
int i;
for (i = 0; i < 20; i++)
a[i] = rand() % upper_bound;
}
void print_random()
{
int i;
for (i = 0; i < 20; i++)
printf("%d ", a[i]);
printf("\n");
}
int main(void)
{
gen_random(10);
print_random();
return 0;
}</pre><p>可见在这里预处理器做了两件事情,一是把头文件<code class="literal">stdio.h</code>和<code class="literal">stdlib.h</code>在代码中展开,二是把<code class="literal">#define</code>定义的标识符<code class="literal">N</code>替换成它的定义20(在代码中做了三处替换,分别位于数组的定义中和两个函数中)。像<code class="literal">#include</code>和<code class="literal">#define</code>这种以#号开头的行称为预处理指示(Preprocessing Directive)<a id="id2733895" class="indexterm"></a>,我们将在<a class="xref" href="ch21.html#prep">第 21 章 <i>预处理</i></a>学习其它预处理指示。此外,用<code class="literal">cpp main.c</code>命令也可以达到同样的效果,只做预处理而不编译,<code class="literal">cpp</code>表示C preprocessor。</p><p>那么用<code class="literal">#define</code>定义的常量和<a class="xref" href="ch07s03.html#struct.datatag">第 3 节 “数据类型标志”</a>讲的枚举常量有什么区别呢?首先,<code class="literal">define</code>不仅用于定义常量,也可以定义更复杂的语法结构,称为宏(Macro)<a id="id2733947" class="indexterm"></a>定义。其次,<code class="literal">define</code>定义是在预处理阶段处理的,而枚举是在编译阶段处理的。试试看把<a class="xref" href="ch07s03.html#struct.datatag">第 3 节 “数据类型标志”</a>习题2的程序改成下面这样是什么结果。</p><pre class="programlisting">#include <stdio.h>
#define RECTANGULAR 1
#define POLAR 2
int main(void)
{
int RECTANGULAR;
printf("%d %d\n", RECTANGULAR, POLAR);
return 0;
}</pre><p>注意,虽然<code class="literal">include</code>和<code class="literal">define</code>在预处理指示中有特殊含义,但它们并不是C语言的关键字,换句话说,它们也可以用作标识符,例如声明<code class="literal">int include;</code>或者<code class="literal">void define(int);</code>。在预处理阶段,如果一行以#号开头,后面跟<code class="literal">include</code>或<code class="literal">define</code>,预处理器就认为这是一条预处理指示,除此之外出现在其它地方的<code class="literal">include</code>或<code class="literal">define</code>预处理器并不关心,只是当成普通标识符交给编译阶段去处理。</p><p>回到随机数这个程序继续讨论,一开始为了便于分析和调试,我们取小一点的数组长度,只生成20个随机数,这个程序的运行结果为:</p><pre class="screen">3 6 7 5 3 5 6 2 9 1 2 7 0 9 3 6 0 6 2 6</pre><p>看起来很随机了。但随机性如何呢?分布得均匀吗?所谓均匀分布,应该每个数出现的概率是一样的。在上面的20个结果中,6出现了5次,而4和8一次也没出现过。但这说明不了什么问题,毕竟我们的样本太少了,才20个数,如果样本足够多,比如说100000个数,统计一下其中每个数字出现的次数也许能说明问题。但总不能把100000个数都打印出来然后挨个去数吧?我们需要写一个函数统计每个数字出现的次数。完整的程序如下:</p><div class="example"><a id="id2734065"></a><p class="title"><b>例 8.3. 统计随机数的分布</b></p><div class="example-contents"><pre class="programlisting">#include <stdio.h>
#include <stdlib.h>
#define N 100000
int a[N];
void gen_random(int upper_bound)
{
int i;
for (i = 0; i < N; i++)
a[i] = rand() % upper_bound;
}
int howmany(int value)
{
int count = 0, i;
for (i = 0; i < N; i++)
if (a[i] == value)
++count;
return count;
}
int main(void)
{
int i;
gen_random(10);
printf("value\thow many\n");
for (i = 0; i < 10; i++)
printf("%d\t%d\n", i, howmany(i));
return 0;
}</pre></div></div><br class="example-break" /><p>我们只要把<code class="literal">#define N</code>的值改为100000,就相当于把整个程序中所有用到<code class="literal">N</code>的地方都改为100000了。如果我们不这么写,而是在定义数组时直接写成<code class="literal">int a[20];</code>,在每个循环中也直接使用20这个值,这称为硬编码(Hard coding)<a id="id2734105" class="indexterm"></a>。如果原来的代码是硬编码的,那么一旦需要把20改成100000就非常麻烦,你需要找遍整个代码,判断哪些20表示这个数组的长度就改为100000,哪些20表示别的数量则不做改动,如果代码很长,这是很容易出错的。所以,<span class="emphasis"><em>写代码时应尽可能避免硬编码</em></span>,这其实也是一个“<span class="quote">提取公因式</span>”的过程,和<a class="xref" href="ch07s02.html#struct.abstract">第 2 节 “数据抽象”</a>讲的抽象具有相同的作用,就是避免一个地方的改动波及到大的范围。这个程序的运行结果如下:</p><pre class="screen">$ ./a.out
value how many
0 10130
1 10072
2 9990
3 9842
4 10174
5 9930
6 10059
7 9954
8 9891
9 9958</pre><p>各数字出现的次数都在10000次左右,可见是比较均匀的。</p><div class="simplesect" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a id="id2734145"></a>习题</h3></div></div></div><p>1、用<code class="literal">rand</code>函数生成[10, 20]之间的随机整数,表达式应该怎么写?</p></div></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch08s01.html">上一页</a> </td><td width="20%" align="center"><a accesskey="u" href="ch08.html">上一级</a></td><td width="40%" align="right"> <a accesskey="n" href="ch08s03.html">下一页</a></td></tr><tr><td width="40%" align="left" valign="top">1. 数组的基本概念 </td><td width="20%" align="center"><a accesskey="h" href="index.html">起始页</a></td><td width="40%" align="right" valign="top"> 3. 数组应用实例:直方图</td></tr></table></div></body></html>