第1章 猜数游戏

本章中要编写的是“猜数游戏”程序。我们先来做一个测试版本,这个测试版的程序只能比较玩家输入的数值和计算机准备的数值,之后再逐渐为其追加其他功能。

图像说明文字

1-1 猜数判定

本章中会编写一个“猜数游戏”的程序。首先我们要做的是一个测试版本,用来显示玩家从键盘输入的值和计算机事先准备好的“目标数字”的比较结果。

通过if 语句实现条件分支

List 1-1 所示的程序是测试版的“猜数游戏”。

先运行程序。因为程序提示输入0~9 的数值,所以我们就在键盘上键入数值,这样一来,程序就会把键入的数值和“目标数字”进行比较,并显示出比较后的结果。

图像说明文字

本游戏中的“目标数字”是7,用变量ans 表示,从键盘输入的值则用变量no 表示。

程序通过阴影部分的if 语句来判断no 和ans 两个变量值的大小关系,然后如Fig.1-1 所示,根据判断结果显示“再小一点。”“再大一点。”“回答正确。”。

输出的字符串中包含两种转义字符。一个是我们很熟悉的\n,表示换行;另一个是\a,表示警报。在大多数环境下,一旦输出警报就会响起蜂鸣音,因此本书在运行示例中采用图像说明文字符号表示警报。

▲关于转义字符,我们会在第2 章中详细介绍。

图像说明文字

if 语句的嵌套

下面让我们来了解一下比较no 和ans 这两个变量值的if语句的结构。

if 语句是通过对名为控制表达式的表达式进行求值(专栏1-1),再根据求值结果把程序流程分为不同的分支的语句,它包含两种语句结构,如右图所示。

▲() 中的表达式为控制表达式。 图像说明文字

本程序中的if 语句采用以下形式。

if( 表达式) 语句 else if( 表达式) 语句 else 语句

当然,这里并不是为了把程序流程分成三个分支才特意采用这种语句结构的。从字面意思 可知,if 语句是一种语句,因此else 控制的语句也可以是if 语句。如Fig.1-2 所示,程序采 用了在if 语句中嵌套if 语句的结构。

图像说明文字

实现多分支的方法

为了跟本程序的if 语句1 达到同样效果,笔者编写了2 和3 中的if 语句。

下面让我们一起来比较并讨论一下这三个程序,以加深对if 语句的理解。 图像说明文字

程序2

最末尾的else 语句后面追加了阴影部分的内容。只有当两个判断(no > ans) 和(no < ans) 都不成立,也就是no 和ans 相等时,程序才会运行这部分。

在阴影部分进行的判断是肯定会成立的条件。

图像说明文字

程序3

这里有三个if 语句并列。无论变量no 和ans 之间的大小关系如何,程序都会进行这三个条件判断。

图像说明文字

笔者根据变量no 和ans 的大小关系,对这三个程序中都进行了什么判断(对哪个控制表达式进行了求值)进行了总结,如Table 1-1 所示。

Table 1-1 三个程序所进行的判断

图像说明文字

比如,我们假设no 大于ans,则程序1 和程序2 都只会进行①的判断,即(no > ans)。

▲因为如果no 大于ans,那么在执行完printf("\a 再小一点。\n") 后,整个if 语句就执行完毕了。 而在程序3 这种有三个独立的if 语句并列的情况下,则会执行三次判断,即①的(no >ans)、②的(no < ans),还有③的(no == ans)。这种实现方法效率最低。

*

不管在何种条件下,判断次数最少的都是程序1 的if 语句。

程序1 的if 语句的优点不光只有判断次数少而已。为了让大家理解这一点,这里通过Fig.1-3 来说明。

图像说明文字

图a 的if 语句

图a 的if 语句跟程序1 中的if 语句结构相同,程序流程都分为三个分支。执行的不是“处 理A”就是“处理B”,再不然就是“处理C”。

▲不会出现没有执行任何处理或者执行了两项和三项处理的情况。

图b 的if 语句

图b 的if 语句是根据变量x 的值进行分支的。

看上去程序似乎执行了“处理X”“处理Y”“处理Z”三者中的一项处理,然而变量x 的值如果不是1、2、3,那么程序就不会进行任何处理。

如Fig.1-4 所示,程序流程实质上分为四个分支。

图像说明文字

如此,图b 与图a 的if 语句的结构完全不同,因此不能省略最后的判断if(x == 3)。

▲如果省略了,那么即使x 的值不是3,而是4 或5 等,“处理Z”也会被运行。

*

程序1 的if 语句的结构如图a ,在末尾的else 语句后面是没有if 语句的,因此一看就明白不存在更多分支。

就程序的易读性而言,程序1 也要优于程序2 ,因为程序2 在末尾的else 语句后放了个“多余”的判断。

▲如果一定要对程序的读者强调“当no 等于ans 时这么做”,则可以按照程序2 那样来实现。 通常,编译器的优化技术会内部删除程序2 的这种“多余”的判断,因此我们没必要太在意效率问题。

图像说明文字 图像说明文字

1-2 重复到猜对为止

如果“猜数游戏”只允许玩家输入一次数值,那未免太无趣了。我们把程序改良一下,让玩家可以一直重复输入直到猜对为止。

通过do 语句循环

如果玩家只能输入一次数值,那么想要猜对数值的话,就需要不停地重启程序直到猜对为止,这样一来不只是没意思,还麻烦得要命。

下面我们把程序改良一下,让玩家能够反复输入数值直到猜对为止,改良后的程序如List 1-2所示。

图像说明文字

List 1-2 中删除了List 1-1 中if 语句的后半截,并在此基础上追加了阴影部分的do 语句。

do 语句是通过先循环后判断(后述)重复进行处理的语句,其结构如右图所示。

图像说明文字

▲和之前学习的if 语句,以及接下来要学习的while 语句和for 语句等语句的结构不同,do 语句 的末尾带有分号“;”。

do 和while 围起来的语句叫作循环体。只要() 中的表达式,也就是控制表达式的求值结果不为0,那么循环体就会被一直重复运行下去,直到控制表达式的求值结果为0,才会结束重复运行。

下面参照Fig.1-5 来理解如何通过本程序的do 语句实现循环。

图像说明文字

do 语句的控制表达式为no != ans。

运算符!= 对左边和右边的操作数的值是否不相等这一条件进行判断。如果这个条件成立, 程序就会生成int 型的1,不成立则会生成int 型的0。

如果读取的值no 和目标数字ans 不相等,那么对控制表达式no != ans 进行求值,得到的值就是1。因此需要通过do 语句来重复运行程序,再次运行用{} 括起来的代码块,也就是循环体。

当程序读取到的no 和目标数字ans 是同一个值时,控制表达式的求值结果就是0,循环就结束了,此时画面显示“回答正确。”,程序运行结束。

相等运算符和关系运算符

相等运算符(equality operator)和关系运算符(relational operator)的判断条件成立的话就会生成int 型的1,不成立就会生成int 型的0。

▲int 型的1 表示“真”,0 表示“假”(专栏1-2)。

相等运算符“==”“!=”

判断两个操作数是否相等

关系运算符“<”“>”“<=”“>=”

判断两个操作数的大小关系。

通过while 语句循环

除了do 语句外,C 语言的循环语句 还有while 语句和for 语句。

我们用先判断后循环的while 语句试着写出之前的程序,如List 1-3 所示。

图像说明文字

while 语句的结构如右图所示。 图像说明文字

只要控制表达式的求值结果不为0,那么作为循环体的语句就会永远重复运行下去。但是求值结果一旦为0,就不再循环了。

因为本程序的while 语句的控制表达式是1,所以循环会永远进行下去。这样的循环一般称为“无限循环”。

① 声明一组要反复执行的命令,直到满足某些条件为止。——译者注

break 语句

一直重复的话,程序会永无止境。为了强制跳出循环语句,我们在本程序中使用了break 语句。 因为break 语句是在no 和ans 相等时运行的,所以通过while 语句进行的循环会被强制中断。

▲使用break 语句的程序往往不容易读也不容易理解,我们只在“某个特殊的条件成立时,因为某种原因想强制结束循环语句”的情况下使用break 语句就好。这里所举的“猜数游戏”的循环 结构很简单,因此实现这个程序不需要用到break 语句,像List 1-2 那样通过do 语句(不使用break 语句)就能实现。

while 语句和do 语句

很难看出程序中的while 是属于do 语句的一部分还是属于while 语句的一部分,下面我们结合右图中所示的程序来思考一下。

首先把0赋给变量x,然后通过do 语句对变量x 的值进行增量操作,直到x 等于5 为止。

接下来,在while 语句中对x 的值进行减量操作,并显示其结果。

图像说明文字

▲关于增量(increment)运算符++ 和减量(decrement)运算符--,在1-4 节中会详细为大家讲解。

如右图所示,我们把{} 括起来的代码块当作do 语句的循环体。

图像说明文字

这样一来,只要看每一行的开头就能区分while 属于哪一部分了。

while:如果开头是while,则属于while 语句的开头部分。
}while:如果开头是},则属于do 语句的结尾部分。

不管是do 语句还是while 语句抑或是for 语句,只要其循环体是单一的语句,那么就没 必要特意导入代码块。

话虽如此,对do 语句来说,其循环体如果是单一的语句,导入代码块则会增加程序的易读性。

先判断后循环和先循环后判断

根据何时判断是否继续处理,循环可以分为两种。

先判断后循环(while 语句和for 语句)

在进行处理前,先判断是否要继续处理。会出现循环体一次也没有运行的情况。

先循环后判断(do 语句)

在进行处理后,再判断是否要继续处理。循环体至少会被运行一次。

1-3 随机设定目标数字

在前面的“猜数游戏”中,“目标数字”都是事先在程序里设置好的,所以我们事先是知道答案的。为了提升游戏的趣味性,我们来让这个值自动变化。

rand 函数:生成随机数

为了每次游戏时都能改变“目标数字”,我们需要一个随机数。用于生成随机数的就是rand 函数,如下所示。

图像说明文字

这个函数生成的随机数是int 型的整数。在所有编程环境中其最小值都为0,但最大值则 取决于编程环境,所以我们用<stdlib.h> 头文件将其定义成一个名为RAND_MAX 的对象宏 (object-like macro),其定义的示例如下所示。

RAND_MAX
#define RAND_MAX 32767 /* 定义的示例:值根据编程环境而有所差别*/

RAND_MAX 的值根据规定不得低于32767,因此rand 函数的运行过程如Fig.1-6 所示。

图像说明文字

下面我们来尝试实际生成并显示随机数。请运行List 1-4 所示的程序。

图像说明文字

首先显示的是能够生成的随机数的“范围”。最小值是0,最大值是RAND_MAX 的值(值取决于编程环境)。

然后显示的是rand() 返回的随机数值,当然这个值在0 ~ RAND_MAX 的范围内。

对于“再运行一次?”的问题,如果选择了“是”,那么就能重复生成并显示随机数。

请多运行几次程序,结果如Fig.1-7 所示,总会生成一个相同的随机数序列。这很令人费解,rand 函数生成的值真的是随机的吗?

图像说明文字

srand 函数:设置用于生成随机数的种子

rand 函数是对一个叫作“种子”的基准值加以运算来生成随机数的。之所以先前每次运行程序都会生成同一个随机数序列,是因为rand 函数的默认种子是常量1。要生成不同的随机数 序列,就必须改变种子的值。

负责执行这项任务的就是srand 函数,如下所示。

图像说明文字

比如,假设我们调用了srand(50)。这样一来,之后调用的rand 函数就会利用设定的新种子值50来生成随机数。

Fig.1-8 所示为在某个编程环境中生成的随机数序列的示例。

当种子值为1 时,在最初调用rand 函数时生成的是41,再调用时生成18467,接下来是6334……

如果种子值是50,则会依次生成201、20851、6334……

图像说明文字

如上图所示,一旦决定了种子的值,之后生成的随机数序列也就确定了。因此如果想要每次运行程序时都能生成不同的随机数序列,就必须把种子值本身从常量变成随机数。

然而,为了生成随机数而需要随机数,这本身很矛盾。

▲rand 函数生成的是叫作伪随机数的随机数。伪随机数看起来像随机数,却是基于某种规律生成的。

因为能预测接下来会生成什么数值,所以才叫作伪随机数。真正的随机数是无法预测接下来会生成什么数值的。

我们一般使用的方法是把运行程序时的时间当作种子。List 1-5 的程序中就使用了这个方法。

图像说明文字

请运行一下程序。如Fig.1-9 所示,每次启动都会生成不同的随机数序列。

▲关于获取当前时间所使用的time 函数,我们会在第6 章详细学习,在此之前,只需把程序中的阴影部分当成是固定的一部分即可(#include

图像说明文字

随机设定目标数字

rand 函数生成的值范围是0 ~ RAND_MAX,话虽如此,但我们需要的随机数不会每次都恰好在这个范围内。

一般情况下,我们需要的是某个特定范围内的随机数。如果我们需要“大于等于0 且小于等于10”的随机数,可以像下面这样求出。

rand() % 11 /* 生成大于等于0且小于等于10的随机数 */

这里使用的方法是把非负整数值除以11,就得到余项(余数)为0, 1, …, 10。

▲大家注意不要把非负整数值错除以10。用10 除得到的余数是0, 1, …, 9,无法生成10。

*

现在大家已经掌握了如何生成随机数,那么我们就来把猜数游戏中的“目标数字”设定为0 ~ 999 的随机数吧。对应的程序如List 1-6 所示。

▲笔者没有以while 语句版本的List 1-3 为基准,而只在do 语句版本的List 1-2 的基础上做了一些细微的修改,增加了部分内容。

图像说明文字

阴影部分把生成的随机数除以1000后得到的余数赋给了变量ans。

仅仅是把目标数字变成了随机数,就大大地提升了猜数游戏的趣味性。大家可以多运行几次感受一下。

话说回来,大家知道怎么才能最快猜中吗?一开始输入499,然后根据程序的判定结果(是大还是小)再输入749 或者249,每次都把范围缩小到一半。

*

目标数字的范围很容易变更。下面举两个具体的例子。

把目标数字定为1 ~ 999

将程序的阴影部分改写成下面这样。

ans = 1 + rand() % 999; /* 生成1~999的随机数 */

把目标数字定为3 位数的整数(100 ~ 999)

将程序的阴影部分改写成下面这样。

ans = 100 + rand() % 900; /* 生成100~999的随机数 */

*

最后只要把测试版本的kazuate1.c、kazuate2.c、kazuate3.c 再重复追加和修正2到3 行,猜数游戏就完成了。

图像说明文字

限制输入次数

只要不断输入数值,终会猜对。为了给玩家以紧张感,我们把玩家最多可输入的次数限制 在10 次之内。变更后的程序如List 1-7 所示。

图像说明文字

变量max_stage 表示玩家最多可输入的次数,在这里是10 次。

另一个新的变量remain 表示还能够输入多少次。当然,其初始值是max_stage,也就是 10。如Fig.1-10 所示,玩家每次输入数值时,都会对remain 的值进行减量操作(如10, 9, 8, …), 即在原基础上减去1。

当这个值为0时,游戏就结束了,因此do 语句的判断不仅包含表达式no != ans,还要 加上阴影部分的remain > 0。

连接两个表达式的逻辑与运算符&& 只会在两边的操作数都不为0时生成int 型的1,否则 便生成0。

因此,不仅当玩家猜中时(图a )循环会结束,当玩家输入10 次都没猜中,remain 变成0(图 b )时,循环也会结束。

▲关于循环结束的条件和&& 运算符,我们会在专栏 1-2 中学到。

此外,用max_stage 减去remain 就可以知道玩家是在第几次猜中了目标数字。如图a所示,游戏结束时的remain 值是7,所以用max_stage 减去remain,也就是用10减去7,答案为3。

▲因为max_stage 的声明中已经指定了const,所以max_stage 的值无法变更。这样一来,如果 把应该写成remain-- 的部分写成了max_stage--,就会发生编译错误(防止遗漏)。

图像说明文字

图像说明文字 图像说明文字

1-4 保存输入记录

如果程序能保存玩家输入的值,玩家就能在游戏结束时确认自己猜的数字距离目标数字有多近(或者有多远)。

数组

下面我们来把程序改良一下,令其能保存玩家已输入的数值,并在游戏结束时显示这些数值。改良后的程序如List 1-8 所示。

▲程序的运行示例可以在第26 页看到。

本程序利用数组(array)来存储已输入的值。数组是一种将同一类型的变量排成一列的数据结构,数组内的各个变量就是数组元素。

在声明数组时,数组元素的个数必须是常量表达式。也就是说,下面这样的声明会引起编译错误。

int max_stage = 10; 
int num[max_stage]; /* 错误:max_stage不是常量表达式 */

因此本程序中没有采用变量max_stage,而设了一个对象宏MAX_STAGE,将其声明为1 。

▲编译初期,要把宏MAX_STAGE(程序的3 处灰色阴影部分)替换成10。

接下来把存储所输入数值的数组num 声明为2 。如Fig.1-11 所示,数组num 的元素类型是int 型,元素个数是10。

▲因为这个声明int num[MAX_STAGE]; 会被替换成int num[10];,所以不会发生错误。

图像说明文字

图像说明文字

在数组的声明中,[] 里的值是元素个数,而[] 里的值是下标(subscript),用于访问(读取)各个元素。

首个元素的下标是0,之后的下标逐一递增,因此可以用表达式num[0],num[1],…,num[9] 依次访问数组num 的元素。由于末尾元素的下标值等于元素个数减1,因此不存在num[10] 这个元素。

数组num 的各个元素和一般的(非数组的单独的)int 型对象具有相同的性质,能够赋值和获取值。

▲声明int a[10]; 中的[] 是用于声明的符号(标点),用于访问元素的a[3] 中的[] 是下标运算符(subscript operator)。

 本书中将前者用[] 表示,后者用粗体的[] 表示。

把输入的值存入数组

让我们结合Fig.1-12 来理解如何把玩家输入的值存入数组的元素中。

本程序中新引入的变量是stage。这个变量用于代替List 1-7 中表示剩余输入次数的变量remain。游戏开始时其初始值为0,之后玩家每输入一个数值,stage 的值都会逐次递增,当值等MAX_STAGE,也就是等于10时,游戏结束。

负责把读取的值存入数组的正是图中的阴影部分。这里共有三个运算符,即[]、++、=。

*

增量运算符++(也称为递增运算符)包括前置形式的++a 和后置形式的a++ 两种形式。我们先来了解一下它们都有哪些不同之处。

前置增量运算符++a

前置形式的++a 会在对整个表达式进行求值之前,先对操作数的值进行增量。因此当a 的值为3 时,运行以下代码的话,a 首先被增量成4,然后程序会把表达式++a 的求值结果4 赋给b,最终a 和b 都等于4。

b = ++a; /* 先对a进行增量再赋给b */

后置增量运算符a++

后置形式的a++ 会在对整个表达式进行求值之后,再对操作数的值进行增量。因此当a 的值为3 时,运行以下代码的话,表达式a++ 的求值结果3 首先被赋给b,然后程序会对a 进行增量,增量结果为4。最后的结果a 等于4,b 等于3。

b = a++; /* 先赋给b再对a进行增量 */

▲这里所说的前置和后置的求值时间也同样适用于进行减量操作的减量运算符--。

本程序的阴影部分中使用了后置形式的增量运算符。下面我们来了解一下玩家输入的值是如何一个一个地保存到数组元素中的。

▲1-4 节的Fig.1-11 把数组的各个元素纵向排列,方框中写有访问各个元素的“表达式”。这次 Fig.1-12 则把各个元素横向排列,方框中写着各个元素的“值”,各个元素的下标是方框上面的小数字。

  另外,实心圆符号●中所写的下标值和变量stage 的值是一致的。

图像说明文字

a 玩家输入500,因为变量stage 的值是0,所以程序会把500赋给num[0],再把stage 的值增量为1。

b 玩家输入250,因为变量stage 的值是1,所以程序会把250赋给num[1],再把stage 的值增量为2。

通过反复进行上述处理,即可把玩家输入的值按顺序依次存入数组

通过for 语句来显示输入记录

一旦游戏结束,程序就会显示出玩家的输入记录。负责进行这项操作的就是下面这个for 语句。

for (i = 0; i < stage; i++)
printf(" %2d : %4d %+4d\n", i + 1, num[i], num[i] - ans);

可以像下面这样解释这个for 语句所进行的循环。

首先把i 的值设为0,当i 的值小于stage 时,就不断往i 的值上加1,以此来让循环体运行stage 次。

猜数游戏的主体do 语句结束时,变量stage 的值等于玩家输入数值的次数。如果玩家输入到第7 次就猜对了,那么stage 的值就是7。此时通过for 语句循环的次数是7 次。

如Fig.1-13 所示,在各个循环中,数组num 内元素num[i] 的下标是i。实心圆符号●内的下标和变量i 的值是一致的。

我们在循环体内通过printf 函数来显示3 个值。

1 第几次输入    i + 1
2 玩家输入的值    num[i]
3 玩家输入的值与正确答案之差 num[i] - ans

1 表示的是变量i 加上1 之后的值。下标是从0开始的,而我们数数是从1 开始的,加上1 是为了弥补变量的值和显示的值之间的差距。

▲如图c ,变量i 的值是2,加上1 后显示结果就是3。

2 则会直接显示出玩家输入的值 num[i]。

▲如图c ,num[i] 也就等于num[2],显示结果是125。

3 表示的是玩家输入的值和正确答案之差,如果玩家输入的值大于正确答案,就在显示结果中加上符号“+”来表示,如果玩家输入的值小于正确答案,就在显示结果中加上符号“-”来表示。

▲如图c ,因为num[i] 的值为125,减去正确答案116,得出差值为9,显示结果就是“+9”。 大家都知道(通过平日的积累也应该有所了解),在使用格式字符串"%d" 表示int 型的数 值时,只有当数值为负值时才会在数值前加上符号“-”。

图像说明文字

一旦将格式字符串设为"%d",那么数值即使是正值和0 也会带有符号。

我们将会在第2 章详细学习printf 函数和格式字符串。

图像说明文字

按顺序逐个访问数组内的各个元素就叫作遍历(traverse)。这是一个基础术语,还请大家务 必牢记。

*

接下来,for 语句会在变量i 的值小于stage 的期间一直循环。因此,for 语句结束时变量i 的值就等于stage,而不是stage - 1。

▲把本程序的for 语句改写成while 语句时的代码如下所示。

i = 0;
while (i < stage) {
printf(" %2d : %4d %+4d\n", i + 1, num[i], num[i] - ans); 
i++;
}

 循环体会在变量i 的值为0,1,…,stage - 1 时运行,共运行stage 次。最后调用printf 函数时,变量i 的值为stage - 1。当这个值增量后等于stage 时,控制表达式 i < stage 不成立,循环结束。

数组元素的初始化

我们再来详细学习一下数组。首先是用于初始化的声明。

要将元素初始化,需要对应各个元素把初始值按顺序依次排列并用逗号“,”一一隔开,再用“{}”把它们括起来。

例如像下面这样,一旦进行了声明,元素a[0]、a[1]、a[2]、a[3]、a[4] 就会依次被初始化为1、2、3、4、5。

int a[5] = {1, 2, 3, 4, 5};

下面是把所有元素初始化为0的声明。

int a[5] = {0, 0, 0, 0, 0}; /* 把所有元素都初始化为0 */

但是,在给出了“{}”形式的初始值的数组声明中,没有被赋予初始值的元素会被初始化为0。因此如果我们像下面这样声明的话,a[1] 之后没有被赋予初始值的所有元素都会被初始化为0。这样看上去会更简洁一些。

int a[5] = {0}; /* 把所有元素都初始化为0 */

▲对有静态存储期(5-3 节)的数组(包括在函数外定义的数组和在函数内加上static 定义的数组)而言,即使不赋予该数组初始值,所有的元素也都会被初始化为0。

在赋予了初始值的数组的声明中,可以省略元素个数。

int a[] = {1, 2, 5}; /* 省略元素个数 */

此时根据初始值的个数,数组a 的元素个数被视为3 个。也就是说,上面的声明和下面的声明是一样的。

int a[3] = {1, 2, 5};

另外,如果初始值的个数超过了数组的元素个数,程序就会报错。

int a[3] = {1, 2, 3, 5}; /* 错误:初始值太多了 */

此外,初始值{1, 2, 3} 不能作为右侧表达式用于赋值,因此以下赋值会导致程序报错。

int a[3]; 
a = {1, 2, 3}; /* 错误:不能这样赋值*/

▲关于初始值我们还会在后面的章节继续学习。

获取数组的元素个数

List 1-8 在声明数组以前把该数组的元素个数定义成了宏。

在一些不太适合用宏定义元素个数的情况下,首先要声明数组,再求元素个数。

求数组元素个数最常用的方法是使用sizeof 运算符,List 1-9 中的程序就采用了这个方法。

图像说明文字

如Fig.1-14 所示,通过sizeof(a) 可求出数组的大小,通过sizeof(a[0]) 则可求出各个元素的大小。

int 型的大小根据编程环境的不同而有所不同,但通过sizeof(a) / sizeof(a[0])求出的值是数组的元素个数,跟int 型的大小无关。例如,如果int 型是2 字节,那么sizeof(a) 就是10,sizeof(a[0]) 就是2,因此可求出元素个数等于10 / 2,也就是5。此外,如果int 型是4 字节,那么通过计算20 / 4,可得到元素个数仍为5。

图像说明文字

由前文可知,变量na 会被初始化为数组a 的元素个数5。如果把数组a 的声明进行如下变更,那么变量na 就会被初始化为6。实际的运行示例也是如此(如右图所示)。

int a[] = {1, 3, 5, 7, 9, 11};

图像说明文字

不需要随着初始值的增减去修改程序的其他地方。

▲有些教材介绍的是采用sizeof(a) / sizeof(int) 而非sizeof(a) / sizeof(a[0]) 来求数组元素个数的方法,但这种方法并不可取。

 各位想象一下,如果因为某种原因要变更元素类型的话,那我们该怎么办?假设“因为要存入数组元素中的数值超出了int 型的范围,所以需要将元素类型变更成long 型”,在这种情况下,就必须把表达式sizeof(a) / sizeof(int) 改成sizeof(a) / sizeof(long)。

 采用表达式sizeof(a) / sizeof(a[0]) 就不必考虑元素类型。

图像说明文字

自由演练

建议大家不要满足于读懂本书中的程序,还要试着解答下述问题,自己来设计和开发程序,锻炼自己的编程能力。

  • 因为是自由演练,所以没有答案。

练习1-1

编写一个“抽签”的程序,生成0~6 的随机数,根据值来显示“大吉”“中吉”“小吉”“吉”“末吉”“凶”“大凶”。

练习1-2

把上一练习中的程序加以改良,使求出某些运势的概率与求出其他运势的概率不相等(例如可以把求出“末吉”“凶”“大凶”的概率减小)。

练习1-3

编写一个“猜数游戏”,让目标数字是一个在-999 和999 之间的整数。

同时还需思考应该把玩家最多可输入的次数定在多少合适。

练习1-4

编写一个“猜数游戏”,让目标数字是一个在3 和999 之间的3 的倍数(例如3, 6, 9, …, 999)。编写以下两种功能:一种是当输入的值不是3 的倍数时,游戏立即结束;另一种是当输入的值不是3 的倍数时,不显示目标数字和输入的数值的比较结果,直接让玩家再次输入新的数值(不作为输入次数计数)。

同时还需思考应该把玩家最多可输入的次数定在多少合适。

练习1-5

编写一个“猜数游戏”,不事先决定目标数字的范围,而是在运行程序时才用随机数决定目标数字。打个比方,如果生成的两个随机数是23 和8124,那么玩家就需要猜一个在23 和8124之间的数字。

另外,根据目标数字的范围自动(根据程序内部的计算)选定一个合适的值,作为玩家最多可输入的次数。

练习1-6

编写一个“猜数游戏”,让玩家能在游戏开始时选择难度等级,比如像下面这样。

请选择难度等级(1)1 ~ 9 (2)1 ~ 99 (3)1 ~ 999 (4)1 ~ 9999:

练习1-7

使用List 1-8 的程序时,即使玩家所猜数字和正确答案的差值是0,输入记录的显示结果也会带有符号,这样不太好看。请大家改进一下程序,让差值0不带符号。

练习1-8

把List 1-8 里的do 语句改写成for 语句。

目录

  • 前言
  • 相关问题
  • 导 读
  • 第1章 猜数游戏
  • 第2章 专注于显示
  • 第3章 猜拳游戏
  • 第4章 珠玑妙算
  • 第5章 记忆力训练
  • 第6章 日历
  • 第7章 右脑训练
  • 第8章 打字练习
  • 第9章 文件处理
  • 第10章 英语单词学习软件