You are on page 1of 313

高职高专现代信息技术辅导教材

C 语言程序设计——示例与习题解析

宗大华 蒋 玮 编

人 民 邮 电 出 版 社
图书在版编目(CIP)数据
C 语言程序设计——示例与习题解析/宗大华,蒋玮编.—北京:人民邮电出版社,2003.7
ISBN 7-115-11248-7
I.C… II.①宗… ②蒋… III.C 语言程序设计—示例与习题解析 IV.TP312-44
中国版本图书馆 CIP 数据核字(2003)第 031397 号

内 容 提 要

本书是关于 C 语言程序设计的习题汇编,共十章。每章对所涉及内容的基本概念、重点、难点做了详尽
叙述;同时针对相关内容列举了大量典型示例;最后给出了五种类型的课外习题、答案以及对答案的分析。
全书列举的示例与习题,不下 500 多道,适合于学习 C 语言各个层面的读者,更加适合注重编程学习的高职
高专学生。本书对认识 C 语言、掌握 C 语言,进而领悟到用 C 语言解决问题的方法都会有所裨益。

高职高专现代信息技术辅导教材
C 语言程序设计——示例与习题解析

 编 宗大华 蒋 玮
责任编辑 潘春燕

 人民邮电出版社出版发行 北京市崇文区夕照寺街 14 号

邮编 100061 电子函件 315@ptpress.com.cn

网址 http://www. ptpress.com.cn

读者热线:010-67180876

北京汉魂图文设计有限公司制作
印刷厂印刷
新华书店总店北京发行所经销

 开本:787×1092 1/16

印张:19.5

字数:471 千字 2003 年 7 月第 1 版
印数:1 – 0 000 册 2003 年 7 月北京第 1 次印刷

ISBN 7-115-11248-7/TP· 2282

定价:25.00 元
本书如有印装质量问题,请与本社联系 电话:(010)67129223
编者的话

本书是关于 C 语言程序设计的习题汇编,共分十章。每章涉及的内容与一般 C 语言教材


大致相同,只是为了使内容更显紧凑,做了部分归并与调整。每章的编排格式基本遵循以下
框架:第 1 节罗列本章的基本概念及知识点,包含基本概念、重点、难点三个部分。第 2 节
为典型示例分析,通过分析来加深读者对本章内容的理解;这些经过提炼的经典例题,都具
有一定的代表性和相当的深度、难度。第 3 节为课外习题答案与解析,它包含五种题型:单
选题、填空题、是非判断题、程序阅读题、程序编写题。
希望通过这些基本题目的练习激发读者的学习积极性并帮助大家认识 C 语言、掌握 C 语
言,进而领悟到用 C 语言解决问题的方法。
学习外语贵在“张口” ,学习程序设计语言则要勇于“动手”。只有通过上机实践,通过
不断地去体验失败、螺旋式地上升,才能真正体会到“失败是成功之母”的内涵与真谛。
本书中的示例与习题,多达 500 多道,除了来自作者多年的执教经验,还有部分从各方
渠道收集整理而成。在此对不吝提供资料,认识的和不认识的朋友们,表示最为诚挚的谢意!
如果本书能给您带来欢乐和助益,将使我们感到莫大的欣慰。
书中不当之处,恳请读者批评、指正。邮件地址:zong@public.intercom.com.cn。

编者
2003 年 4 月于北京
目 录

第1章 C 语言概述 ······························ 1

1.1 本章基本概念及知识点 ························· 1


1.1.1 基本概念 ····························· 1
1.1.2 本章重点 ····························· 3
1.1.3 本章难点 ····························· 5
1.2 典型示例分析 ····························· 6
1.3 课外习题、答案与解析 ························· 8
1.3.1 课外习题 ····························· 8
1.3.2 答案与解析 ··························· 11

第2章 基本数据类型 ··························· 13

2.1 本章基本概念及知识点 ························ 13


2.1.1 基本概念 ···························· 13
2.1.2 本章重点 ···························· 15
2.1.3 本章难点 ···························· 18
2.2 典型示例分析 ···························· 20
2.3 课外习题、答案与解析 ························ 24
2.3.1 课外习题 ···························· 24
2.3.2 答案与解析 ··························· 27

第3章 运算符与表达式 ·························· 30

3.1 本章基本概念及知识点 ························ 30


3.1.1 基本概念 ···························· 30
3.1.2 本章重点 ···························· 32
3.1.3 本章难点 ···························· 37
3.2 典型示例分析 ···························· 42
3.3 课外习题、答案与解析 ························ 46
3.3.1 课外习题 ···························· 46
3.3.2 答案与解析 ··························· 50

第4章 顺序结构与选择结构的程序设计 ··················· 54

4.1 本章基本概念及知识点 ························ 54


4.1.1 基本概念 ···························· 54
4.1.2 本章重点 ···························· 56
–1–
C 语言程序设计—示例与习题解析

4.1.3 本章难点 ···························· 61


4.2 典型示例分析 ···························· 64
4.3 课外习题、答案与解析 ························ 71
4.3.1 课外习题 ···························· 71
4.3.2 答案与解析 ··························· 78

第5章 循环结构的程序设计 ························ 84

5.1 本章基本概念及知识点 ························ 84


5.1.1 基本概念 ···························· 84
5.1.2 本章重点 ···························· 85
5.1.3 本章难点 ···························· 91
5.2 典型示例分析 ···························· 97
5.3 课外习题、答案与解析 ························ 103
5.3.1 课外习题 ···························· 103
5.3.2 答案与解析 ··························· 110

第6章 函数与变量存储类型 ························ 117

6.1 本章基本概念及知识点 ························ 117


6.1.1 基本概念 ···························· 117
6.1.2 本章重点 ···························· 119
6.1.3 本章难点 ···························· 128
6.2 典型示例分析 ···························· 132
6.3 课外习题、答案与解析 ························ 137
6.3.1 课外习题 ···························· 137
6.3.2 答案与解析 ··························· 143

第7章 指针与一维数组 ·························· 153

7.1 本章基本概念及知识点 ························ 153


7.1.1 基本概念 ···························· 153
7.1.2 本章重点 ···························· 154
7.1.3 本章难点 ···························· 164
7.2 典型示例分析 ···························· 170
7.3 课外习题、答案与解析 ························ 179
7.3.1 课外习题 ···························· 179
7.3.2 答案与解析 ··························· 187

第8章 多维数组与字符串 ························· 194

8.1 本章基本概念及知识点 ························ 194


8.1.1 基本概念 ···························· 194
–2–
目 录

8.1.2 本章重点 ···························· 195


8.1.3 本章难点 ···························· 205
8.2 典型示例分析 ···························· 209
8.3 课外习题、答案与解析 ························ 214
8.3.1 课外习题 ···························· 214
8.3.2 答案与解析 ··························· 221

第9章 结构、共用、枚举 ························· 230

9.1 本章基本概念及知识点 ························ 230


9.1.1 基本概念 ···························· 230
9.1.2 本章重点 ···························· 232
9.1.3 本章难点 ···························· 242
9.2 典型示例分析 ···························· 247
9.3 课外习题、答案与解析 ························ 254
9.3.1 课外习题 ···························· 254
9.3.2 答案与解析 ··························· 262

第 10 章 文件、编译预处理 ························· 270

10.1 本章基本概念及知识点 ······················· 270


10.1.1 基本概念 ··························· 270
10.1.2 本章重点 ··························· 271
10.1.3 本章难点 ··························· 283
10.2 典型示例分析 ··························· 287
10.3 课外习题、答案与解析 ······················· 291
10.3.1 课外习题 ··························· 291
10.3.2 答案与解析 ·························· 297

–3–
第1章 C 语言概述

1.1 本章基本概念及知识点
本章涉及以下三个方面的内容:
• 程序设计语言与 C 语言;
• C 语言的字符集与词法规定;
• C 语言程序的基本结构。
要说明的是,本书所举示例以及所给习题,都在 Turbo C 2.0 上检验通过。因此在这里
也将附带对 Turbo C 环境下开发 C 语言程序的步骤做一个简要的介绍。

1.1.1 基本概念
1.程序
用某种计算机语言描述的解决问题的方法和步骤,被称为“程序”。
2.二进制语言
用二进制代码表示机器指令的语言,被称为“二进制语言”,它是计算机硬件惟一能够
识别和直接执行的语言。又称为“机器语言”。
3.汇编语言
汇编语言是一种机器语言的助忆符(即帮助记忆的符号)形式。它基本上与计算机机器
指令一一对应。
4.汇编程序
用汇编语言编写的程序,只有经过翻译才能成为相应的机器语言程序,从而得以在计算
机上执行。完成这种翻译工作的程序,称为“汇编程序”。
5.面向机器的语言
二进制语言就是机器指令。汇编语言和机器指令一一对应。因此计算机上的这两种语言
与具体的机器紧密相关。不同型号计算机上所提供的二进制语言或汇编语言是不同的。用它
们编写的程序,只能在同类型的计算机上运行。这样的程序设计语言统称为“面向机器的语
言”。
6.高级语言与低级语言
“高级语言”是“高级程序设计语言”的简称,泛指那些独立于机器的程序设计语言。
–1–
C 语言程序设计—示例与习题解析

它们接近人们日常使用的自然语言和数学表达式,有一定的语法规则,由它们编写的程序不
能直接被计算机执行。相对于高级语言,把与机器紧密相关的程序设计语言泛称为“低级语
言”,即“低级程序设计语言” 。
7.源程序
用高级语言编写的程序被称为“源程序” 。
8.解释程序与编译程序
用高级语言编写的源程序,只有被翻译成二进制语言后,才能在计算机上执行。这种翻
译过程通常有两种方式。一种是“解释执行”方式,即翻译一句执行一句,这时称完成翻译
工作的程序为“解释程序” 。另一种是“编译执行”方式,即将源程序全部翻译成二进制语言
后再执行,这时称完成翻译工作的程序为“编译程序”。
9.目标程序
在采用“编译执行”方式时,由编译程序产生出来的二进制语言程序被称为相对于该源
程序的“目标程序” 。
10.字符集
允许出现在 C 语言源程序中的所有基本字符的总体,称为 C 语言的“字符集”。它由数
字、英文字母、图形符号以及转义字符四部分组成。
(1)数字:10 个十进制的数字,即 1、2、3、4、5、6、7、8、9、0。
(2)英文字母:26 个大写英文字母 A~Z,26 个小写英文字母 a~z。
(3)图形符号:~(波浪号) !(惊叹号) #(井号) %(百分号) ^(异或号)
&(与符号) *(星号) ((左圆括号) )(右圆括号) −(减号) +(加号) \(反
斜杠) | (或符号) { (左花括号) }(右花括号) [ (左方括号) ](右方括号) ;
(分号) ‘ (单引号) “(双引号):(冒号) <(小于号) >(大于号) ?(问号) ,
(逗号)
.(句号) /(正斜杠) =(等号) _(下划线) (空格)
(4)转义字符(说明见下) 。
11.转义字符
在 C 语言源程序中,可以用在反斜杠号( “\”
)后面跟随特定的单个字符或若干字符的
方法,来表示键盘上的字符以及某些不可见的功能控制符。这时,尾随反斜杠后的字符失去
原有的含义,而具有了特定意义。通常称反斜杠为“转义符”,称反斜杠以及随后的字符整体
为一个“转义字符” 。表 1-1 是 C 语言的转义字符表。

表 1-1 C 语言的转义字符表
转义字符 含 义 转义字符 含 义
\n 回车换行符 \a 响铃符号
\t Tab 符号 \” 双引号
\v 垂直制表符号 \’ 单引号
\b 左退一格符号 \\ 反斜杠
\r 回车符号 \ddd 1~3 位八进制数 ddd 对应的键盘符号
\f 换页符号 \xhh 1~2 位十六进制数 hh 对应的键盘符号

–2–
第1章 C 语言概述

12.保留字
在 C 语言中,将具有特定含义的、用于构成语句成分或作为存储类型和数据类型说明的
那些单词,统称为“保留字” ,有时也称为“关键字”
。C 语言的保留字只能小写。表 1-2 列
出了 C 语言中可以使用的所有保留字。

表 1-2 C 语言的保留字表
保 留 字 含 义 保 留 字 含 义 保 留 字 含 义
char 字符型 void 空值型 while 当
int 整型 const 常量型 do 做
long 长整型 volatile 可变量型 break 终止

short 短整型 auto 自动 continue 继续


float 单精度实型 extern 外部 goto 转向
double 双精度实型 static 静态 return 返回

unsigned 无符号型 register 寄存器 switch 开关


signed 有符号型 typedef 类型定义 default 缺省
struct 结构体型 if 如果 case 情况
union 共用体型 else 否则 sizeof 计算字节数
enum 枚举型 for 对于

13.标识符
在 C 语言中,用户自己说明的,用来标记常量、变量、函数和数组等名称的字符序列,
称为“标识符”。因此,标识符是用户给程序中需要辨认的对象所起的名字。一个标识符必须
符合下面所列的语法规则:
(1)标识符只能以字母或下划线开头;
(2)在第一个符号后,可以是任意字母、数字和下划线;
(3)标识符中区分字母的大、小写;
(4)标识符的长度一般限制为 8 个字符;
(5)C 语言的保留字不能作为标识符使用。
14.主函数
任何一个 C 语言程序都由一个或多个函数组成,其中必有且只有一个名为 main 的函数。
无论它位于程序代码的什么位置,C 程序总是从它开始执行。这个函数被称为“主函数”。
15.注释
在 C 语言程序中,可以用“/*”开头、“*/”结束括住字符串,以便对程序的功能、语
句的意义和数据的含义等做出说明,从而提高程序的可阅读性。这就是 C 语言的“注释” 。注
释可以出现在程序的任何位置,它不参加编译,也不会出现在所产生的目标程序中。

1.1.2 本章重点
1.C 语言函数的基本结构
C 语言程序中的函数,都由函数头与函数体两部分组成。函数头包含函数名、函数类型、
函数参数及其类型说明表等;函数体是指函数头下面由一对花括号括起来的那一部分内容,

–3–
C 语言程序设计—示例与习题解析

里面内容是对函数内部所用变量进行的说明和是对函数所要完成工作的语句描述。要特别强
调的是,在 C 语言程序中,每一个语句都以分号“;
”结束。
例 1-1 编写一个计算整数平方的函数。

[解]

调用该函数时,把要求其平方的整数作为参数传递给它,即这里的 y。然后在这里计算
出 y 的平方值,存放在变量 z 里。最后由 return 语句返回的 z 值恰是 y 的平方。
2.C 语言程序的基本结构
每一个 C 语言程序都是由一个或若干个函数所组成的,其中有且仅有一个名为 main 的
主函数。主函数在整个程序里的位置可以任意,但程序的执行总是从它开始,并在其中结束。
主函数可以调用其他函数;其他函数之间也可以相互调用。整个程序所要完成的功能就由这
些函数描述出来,并加以实现。
例 1-2 在键盘上输入两个整数,输出其中的大者。
[解]

这里,整个程序由两个函数组成。主函数 main()开始运行后,通过调用系统函数 scanf


接收键盘的两个输入数据,存放在变量 a 和 b 里。以此为参数,调用子函数 max。max 对传递
过来的 a 和 b 的值进行比较,将大者存放在变量 z 里返回,从而存入主函数的变量 c。
3.Turbo C 环境下程序的编辑、编译和执行
在 Turbo C 环境下开发 C 语言程序,要经过如下四个主要阶段:
–4–
第1章 C 语言概述

(1)编辑阶段:通过使用编辑器,把 C 语言程序录入计算机,并以文件的形式存放到磁
盘上,这个过程称为编辑。它将产生出以“.c”为扩展名的源程序文件。
(2)编译阶段:源程序不能直接执行,必须通过 C 编译程序将它翻译成计算机能识别的
二进制目标代码,这个过程称为编译。它产生出以“.obj”为扩展名的目标程序文件。
(3)连接阶段:目标程序仍不能立即在机器上执行,因为程序中还会用到系统提供的库
函数(如 printf),需要把它们与产生的目标程序连接在一起,形成一个整体,这个过程称
为连接。它将产生出以“.exe”为扩展名的可执行程序文件。
(4)执行阶段:运行可执行文件,以获取所需要的结果。整个过程如图 1-1 所示。

图 1-1 Turbo C 环境下开发 C 语言程序的 4 个阶段

1.1.3 本章难点
1.正确理解转义字符
转义字符的定义如前所述。从表 1-1 可以看出,转义字符有如下三种书写形式。
(1)在反斜杠“\”符号的后面写上一个指定的字符。这时该字符就失去原有的含义而
被转换,用来在程序中表示那些无法直接显示的控制符号。例如, “\b”就把字符 b 的含义转
换成“左退一格”的控制符号。这样,在屏幕上的表现是使光标位置左移一个字符。
例 1-3 执行下面的 C 语言程序,在屏幕上的输出结果是什么?
main()
{
printf("How ab\bre you?\n");
}
这是转义字符‘\b’
[解] 系统函数 printf 中格式控制串里的转义字符‘\b’会使光标左退一格。因此,
在已打印完第一、第二两个字母 ab 后,光标又后退到字母 b 的位置重新输出。于是字母 a
后面出现的字母不再是 b 而改为 r。所以打印结果是“How are you?”

(2)在反斜杠“\”符号的后面写 1 到 3 位的八进制数。这时该八进制数被解释为是某个字
–5–
C 语言程序设计—示例与习题解析

符所对应的八进制 ASCII 码值,这个整体就代表该字符。例如,字母‘a’的十进制 ASCII 码值


为 97,相应的八进制 ASCII 码值为 141。因此,在程序中书写‘\141’
,这个整体就代表字母‘a’

(3)在反斜杠“\”符号的后面写字母‘x’,然后再写 1 到 2 位的十六进制数。这时该
十六进制数被解释为是某个字符所对应的十六进制 ASCII 码值,这个整体就代表该字符。例
如,字母‘m’的十进制 ASCII 码值为 109,相应的十六进制 ASCII 码值为 6D。因此,在程序
中书写‘\x6D’ ,就是字母‘m’ 。
关于转义字符有以下三点注意:
• 反斜杠后紧随的字母 x,起到标识其后的是十六进制数的作用。例如,‘\46’里的 46
是八进制数,它代表字符‘&’ ;而‘\x46’里的 46 是十六进制数,它代表字符‘F’ 。
• 反斜杠后紧随的字母 x,只能是小写。如果写成大写,则 C 编译程序会给出出错信息。
• 若反斜杠后紧随的字符不是表 1-1 中所列的情形,那么这个反斜杠就不起作用,形同
虚设。例如‘\x’就是小写字母‘x’ ;‘\8’就是数字‘8’。
例 1-4 执行下面的 C 语言程序,在屏幕上的输出结果是什么?
main()
{
printf("I am a \student");
}
这是一个反斜杠
[解] 反斜杠的后面是字母‘s’,它不在表 1-1 的那些字符之列,因此不形成所谓的转
义字符。所以屏幕上的输出结果是“I am a student.” 。
2.几个转义字符的特殊用途
从例 1-4 可以看出,转义符“\”在字符串里是不起作用的。但如果字符串里真正需要
包含反斜杠时,就需要靠转义字符“\\”等发挥作用了。比如要求输出的结果是“I am a
\student.”
,那么对 printf 函数的调用就应该写成:
printf("I am a \\student");
表 1-1 里的“\””、“\’”两个转义字符的作用类似。

1.2 典型示例分析
示例 1 找出下面三个程序中存在的语法错误。
(1)main()
{
int i
scanf("%d", &i );
printf("%d \n", i );
}
(2)Main()
{

–6–
第1章 C 语言概述

int i ;
print ("%d \n", i );
}
(3)main()
{
int a, b;
scanf ("%d%d", &a, &b ); /* 输入两个整数 a 和 b /*
printf("%d \n", a+b ); /* 输出 a、b 之和 a+b
}
[解]
(1)里变量说明语句“int i”后面缺少语句结束符“;”(分号) 。这时,C 编译程序会
给出如下的出错信息:
Declaration syntax error (说明出现语法错误)
C 语言规定,所有的语句都应以分号(; )结尾,分号是 C 语言不可缺少的组成部分。即
使是程序中的最后一条语句,末尾的那个分号也不能省略。
(2)的错误在于把主函数名 main 误写为 Main。C 语言对字母的大、小写是非常敏感的,
它们之间不能等同看待。
(3)里程序语句的书写都没有错,错误发生在注释上。scanf 函数后面的注释结束符用
错了,应该是“*/”,而不是“/*” 。另外 printf 函数后面的注释遗漏了结束符,这在 C 语言
里都是不允许的。如果进行编译,C 编译程序会给出如下出错信息:
Unexpected end of file in comment started on line #(文件在第#行注释处意外结
束)
下面列出程序设计时容易犯的错误:
• 在函数 printf、scanf 的格式控制串里,漏写前后的一个或两个双引号。
• 把函数 printf 名写成 print。
• 遗忘注释的结束符*/。
• 把注释的开始符误当成结束符使用。
• 遗忘语句结束符分号(; )。
• 忽略 C 语言对字母大小写的敏感性,在应该用小写字母的地方用了大写字母。
• 忘记写主函数名 main 后面的圆括号。
• 忘记写主函数最后的右花括号。
示例 2 阅读下面三个程序,写出其输出结果。
(1)main()
{
printf ("Welcome to Beijing ! \n") ;
}
(2)main()
{
printf ("Welcome") ;

–7–
C 语言程序设计—示例与习题解析

printf ("to Beijing ! \n") ;


}
(3)main()
{
printf ("Welcome\nto\nBeijing ! \n") ;
}
[解]
(1)在屏幕上的输出是:
Welcome to Beijing!
(2)在屏幕上的输出是:
Welcometo Beijing!
它的第一个 printf 函数输出 Welcome。由于后面没有安排转义字符“\n”
,因此光标停
留在字母“e”的后面。于是执行第二个 printf 函数,就紧接字母“e”输出,从而出现了“to”
与“Welcome”连上的效果。
(3)在屏幕上的输出是:
Welcome
to
Beijing!
之所以出现这种输出效果,是因为在 printf 函数的格式控制串里,在“Welcome”、“to”
的后面都安排了转义字符“\n”的缘故。
示例 3 编写程序,要求在屏幕上输出如下信息:
J.Chen \ Apt. #648 Beijing. \"Agent 108"
PRC
[解] 程序编写如下:
#include<stdio.h>
main()
{
printf ("J.Chen \\ Apt. #648 Beijing. \\\"Agent 108\"\n");
printf("PRC") ;
}
由于输出信息中要求出现反斜杠符号以及双引号,为此只有使用转义字符“\\”和“\””
才能达到这一目的。

1.3 课外习题、答案与解析

1.3.1 课外习题

一、单选题

–8–
第1章 C 语言概述

1.二进制语言是一种( )的语言。
A.面向机器 B.面向过程 C.面向对象 D.面向问题
2.下列字符中不属于转义字符的是( ) 。
A.\n B.\t C.\b D.\k
3.下列不正确的 C 语言标识符是( ) 。
A._char B.a?b C.x D.st2_flag
4.下列选项中,可作为 C 语言标识符的是( ) 。
A._pointer B.3_day C.#abc D.flag.bbc
5.C 语言的源程序( )主函数。
A.可以没有 B.可以有多个 C.有且只有一个 D.若有,只有一个
6.C 语言提供的合法保留字是( )。
A.swicth B.cher C.Char D.default
7.以下可作为 C 语言用户定义标识符的是( )组标识符。
A.void define WORD B.a3_b3 _123 IF
C.For −abc Case D.2a Do sizeof
8.C 语言程序编译时,程序中的注释部分将( ) 。
A.参加编译,并会出现在目标程序中
B.参加编译,但不会出现在目标程序中
C.不参加编译,也不会出现在目标程序中
D.不参加编译,但会出现在目标程序中
9.以下的( )是不正确的转义字符。
A.‘\\’ B.‘\’ C.‘\81’ D.‘\0’
10.转义字符\x65 对应的字母是( )。
A.A B.a C.e D.E

二、填空题

1.C 语言程序总是从 开始执行。


2.用 C 语言语句编写的程序被称为 C 语言的 。
3.C 语言的源程序通常由一个或多个 组成。
4.C 语言的源程序通常以 作为其扩展名。
5.C 语言的函数体用 开始,用 结束。
6.C 语言程序中的语句都用 作为结束符。
7.转义字符\n 的含义是 ,它把光标定位在屏幕下一行的开始位置。
8.源程序经过编译后产生出的结果被称为 。
9.转义字符是由 符号开始的单个字符或若干个字符组成的。
10.C 语言的注释可以出现在程序的任何地方。它总是以 作为开始标记,以
作为结束标记。

三、是非判断题(在括号内打“√”或“×”)

–9–
C 语言程序设计—示例与习题解析

1.printf 函数总是从新行的起始位置开始打印。 ( )
2.如果在 C 语言程序中要打印三行,则必须用三条调用 printf 函数的语句。( )
3.在 printf 函数的格式控制串里使用转义字符\n,就会把光标定位到屏幕上下一行的
开始位置。 ( )
4.C 语言认为标识符 number 和 NumbeR 代表同一个变量。( )
5.C 语言程序都是由函数组成的。 ( )
6.一个键盘上的字符放在反斜杠的后面,就形成了一个转义字符。( )
7.在程序中,写字母 A 与写\81 的作用是一样的。( )
8.在程序中,写\x43 与写\103 的作用是一样的。( )

四、程序阅读题

1.找出程序中存在的错误:
main
{
int k;
printf("n=%d, k=%d\n", k);
}
2.找出程序中存在的错误:
main()
{
int n;
double k
printf ("n=%d, k=%d\n", n, k );
3.找出程序中存在的错误:
main()
{
int a, b
a = 10 ;
b = a∗9 ;
print ("B =%d \n", B )
}
4.下面程序的输出结果是什么?
main()
{
printf("I am studying");
printf(" The C Programming Language. ");
printf("\n");
}

– 10 –
第1章 C 语言概述

1.3.2 答案与解析

一、单选题

1.A 2.D3.B4.A5.C
6.在 C 语言里有保留字 switch,但这里的 A 所给的是 swicth,所以 A 不是答案;B 不
能作为答案是明显的;C 里给出的 Char 与 C 语言里的保留字 char 只是第一个字母的大小写
不同,但这一差别对 C 语言是至关重要的,所以它也不能是答案。于是正确的答案是 D。
7.A 里的 define 与 WORD 可以作为用户使用的标识符,但 void 是 C 语言的保留字,因
此 A 不是所求答案;C 里的 For 与 Case 可以作为用户使用的标识符,但-abc 不是正确的标
识符,因此 C 不是答案;D 里的 Do 可以作为用户使用的标识符,但 2a 不是正确的标识符,
sizeof 是 C 语言的保留字,因此 D 也不是答案。于是正确的答案是 B 。
8.C
9.C 是不正确的转义字符。因为在转义符后面跟随数字时,或是一个 1~3 位的八进制
数,或是在 x 后跟随一个 1~2 位的十六进制数。由于现在反斜杠后没有 x,所以它后面跟随
的应该是八进制数。但八进制数只能出现数字 0~7,不能出现 8。故 C 是错误的。
10.C

二、填空题

1.主函数(main) 2.源程序 3.函数 4.c


5.左花括号“{” 右花括号“}” 6.分号“;
” 7.回车换行
8.目标程序 9.反斜杠“\” 10./* */

三、是非判断题

1.结论是不对的。C 语言程序中除了第一条 printf 函数调用语句是从新行的起始位置


开始打印外,后面的 printf 函数调用语句的打印位置要由前面的 printf 函数调用语句来决
定。如果前面的语句里安排有转义字符\n,那么打印就会从新行的起始位置开始打印;如果
前面的语句里没有安排转义字符\n,那么打印就紧接着前面的输出继续下去。
2.结论是不对的。在 C 语言程序的一条调用 printf 函数的语句里如果在打印内容中间
安排了两个转义字符\n,就会打印出三行的输出效果。
3.√
4.注意到 C 语言对字母大小写的敏感性,就会知道 number 和 NumbeR 在 C 语言里不代
表同一个变量。
5.√
6.×
7.结论是不对的。只能说在程序中,写字母 A 与写\101 的作用是一样的。因为转义符
后跟随八进制数时,不能出现数字 8。
8.√。在程序中,写\x43 与写\103 都代表是大写字母 C。

– 11 –
C 语言程序设计—示例与习题解析

四、程序阅读题

1.主函数名后面的圆括号忘记写了。
2.这里有两个错误,一个是在语句
double k
的后面忘记用分号做为语句的结束符;一个是没有函数体的右花括号,使函数体的花括
号不配对。
3.这里有三个错误,一个是在语句
int a, b
的后面忘记用分号作为语句的结束符;第二个是 printf 里书写的变量是 B 而不是 b,C
语言不会把 B 视为 b;最后一个错误是 printf 调用语句后面丢失分号。正确的应该是:
main()
{
int a, b;
a = 10 ;
b = a∗9 ;
print ("b =%d \n", B );
}
4.程序的输出结果是:
I am studying The C Programming Language.
注意:这里第一个 printf 语句后面没有转义字符\n。下一个 printf 语句的输出应该紧
接着进行。但由于第二个 printf 语句输出内容“The C Programming Language.”的 The 前
面有一个空格,所以它不会输出成“I am studyingThe C Programming Language.”

– 12 –
第2章 基本数据类型

2.1 本章基本概念及知识点
本章涉及以下三个方面的内容:
• C 语言中的常量和变量;
• C 语言的基本数据类型;
• 格式输出函数 printf 和格式输入函数 scanf 的使用。

2.1.1 基本概念
1.常量
在程序执行过程中其值保持不变的数据称为“常量”。常量也就是通常所说的常数。在 C
语言里,有整型常量、实型常量、字符常量和字符串常量四种常数。
2.整型常量
取值为整数的常量称为“整型常量”,简称“整常量” 。在 C 语言中,可以采用十进制、
八进制和十六进制三种形式来书写整型常量。
• 十进制整常量就是通常意义下的整数。例如,25、2008、−32768、0 等。要注意,在
C 语言里用十进制表示整常量时,第一个数字不能是 0(除了 0 本身外)。
• 八进制整常量是在通常意义下的八进制整数前加上前缀数字 0 构成。例如,在 C 语言
里,0124 表示八进制数 124,它相当于十进制的 84;又如−012 表示八进制数−12,即是十进
制数的−10。
• 十六进制整常量是在通常意义下的十六进制整数前加上前缀 0x(数字 0 和小写字母 x)
构成。例如,在 C 语言里,0x15 表示十六进制数,它相当于十进制的 21;又如−0x12 表示十
六进制数−12,即是十进制数的−18。
3.实型常量
• 取值为实数的常量称为“实型常量” ,简称“实常量”。在 C 语言中只有十进制的实型
常量。它有两种书写形式,即一般形式与指数形式。
• 一般形式的实型常量即是通常意义下的实数,它由整数、小数点、小数三部分构成。
小数点是必须的。整数或小数部分可以省略。例如 12.245、−1.2345、0.618、.123 或 123.
都是 C 语言中合法的一般形式的实型常量。
– 13 –
C 语言程序设计—示例与习题解析

• 指数形式的实型常量由尾数、小写字母 e 或大写字母 E、以及指数三部分构成。e 或


E 必须要有。尾数部分可以是整数,也可以是实数。指数部分只能是整数(可以带“+”或“−”
符号) 。例如 2.75e3、6.E−5、.123E+4 等都是 C 语言合法的指数形式的实型常量;而 1234、.E−8、
e3、3.28E、8.75e3.3 等都不是 C 语言合法的指数形式的实型常量。
4.字符常量
在 C 语言中,用单引号前、后括住的单个字符被称为“字符常量”。例如‘b’ 、 ‘G’、
‘=’ 、‘\101’等都是字符常量。
5.字符串常量
在 C 语言中,用双引号前、后括住的零个或若干个字符被称为“字符串常量” ,简称“字
符串” 。例如:"a character string"、"A"、"123"、"\t\"Name \\Address\n"等
都是字符串常量。要注意的是,若双引号内没有括任何字符,即"",称为“空字符串”。
6.字符串的长度
一个字符串常量中所包含的字符个数,称为该字符串的长度。字符串中若有转义字符,
则它只作为一个字符计算。例如,字符串“This is”的长度是 7(6 个英文字母和一个空格
符 ),“abcDEF_BBC”的长度是 10, “tb\101&xy\x44”的长度是 7(其中\101 和\x44 是转义字
符 )。
7.数据类型的长度
不同类型数据所占用的内存区域大小是不同的。这个区域的字节数被称为是这种数据类
型的长度。表 2-1 列出了 C 语言中基本数据类型的长度。

表 2-1 C 语言基本数据类型表
基本数据类型 数据类型符 占用字节数(长度)
整型 int 2
短整型 short 2
长整型 long 4
无符号整型 unsigned int 2
无符号短整型 unsigned short 2
无符号长整型 unsigned long 4
单精度实型 float 4
双精度实型 double 8
字符型 char 1

8.变量
在程序执行过程中其值会发生变化的量,被称为“变量”。通常用变量来保存程序执行
过程中的输入数据、中间结果以及最终结果等。
9.变量名
用户应该为程序中的每一个变量起一个名字。这个名字称为“变量名”。在 C 语言中,
变量的命名应符合标识符的规定,出现在变量名中的英文字母采用小写。
10.变量的地址
每一个变量都有自己的数据类型,并用内存中的一块存储区存放自己的数据。这个存储

– 14 –
第 2 章 基本数据类型

区的起始地址就是该“变量的地址”。

– 15 –
C 语言程序设计—示例与习题解析

2.1.2 本章重点
1.变量的数据类型说明
C 语言规定,任何一个变量都必须遵循“先说明后使用”的原则。即只有已说明的变量,
才能在程序中使用;否则由于违反 C 语言的语法规则,程序不会通过编译。例如,
#include<stdio.h>
main()
{
int a, b;
scanf ("%d%d", &a, &b );
sum= a + b;
printf("sum = %d\n", sum);
}
当对此程序进行编译时,会给出如下出错信息:
Undefined symbol ' sum ' (符号 sum 未说明)
这是由于程序中变量 sum 未经说明就使用而引起的编译错误。
在 C 语言中,说明一个变量的一般形式是:
数据类型符 变量名;

数据类型符 变量名 1,变量名 2,…,变量名 n ;
称为“变量说明语句”
。例如,
int i; /* 说明一个名为 i 的整型变量 */
float x, y; /* 说明了名为 x 和 y 的两个实型变量 */
char c; /* 说明一个名为 c 的字符型变量 */
在进行变量说明时,应注意如下几点。
(1)变量说明语句以分号结束。
(2)同一类型的多个变量安排在一条说明语句中时,各变量名之间要用逗号隔开,最后
一个变量名后面以分号结束。
(3)在程序的同一部分,不许有重名的变量说明出现。例如,
#include<stdio.h>
main()
{
int x, y, sum;
float sum;
… …
}
在这个主函数 main 的范围内,变量 sum 被说明了两次,这是不能允许的。它会给出如
下出错信息:
Redeclaration of ' sum ' (符号 sum 进行了重复说明)

– 16 –
第 2 章 基本数据类型

(4)C 语言中只有字符变量(被说明为 char 数据类型),没有字符串变量。


(5)在函数中说明变量时,应将它们放在函数体的最前面。例如,
#include<stdio.h>
main()
{
int a, b;
scanf ("%d%d", &a, &b );
int sum;/* 没有把这个说明语句放在函数体的最前面 */
sum= a + b;
printf("sum = %d\n", sum);
}
这里,把对变量 sum 的说明放在了对函数 scanf 的调用语句后面,C 语言中的编译程序
就会给出如下出错信息:
Expression syntax (表达式语法错误)
2.数据类型说明与存储分配的关系
由表 2-1 知,每一种数据类型所占用的内存字节数是不同的。对变量做数据类型说明,
其目的是告知 C 编译程序,要为这个变量分配多少个字节来存放它的数据。这样,也就限定
了该变量的取值范围。
例如,在 C 语言中,用 int 表示整型数据类型。于是,若要把变量 i 说明为是整型的,
就要进行如下的说明:
int i;
在字长为 16 位的机器上,C 编译程序将开辟 2 字节的内存空间来存放它的内容。正因为
如此,这个变量的取值范围是:
−32768 ≤ i ≤ +32767
例 2-1 分析 C 编译程序对如下语句的处理:
int x = 729;
[解] C 编译程序将为变量 x 分配两个字节的内存单元来存放 x 的值,比如这两个字节
的地址分别是 65498 和 65499。直观地,如图 2-1 所示。

图 2-1 数据类型说明与内存分配示例

– 17 –
C 语言程序设计—示例与习题解析

从低字节 65498 来看,里面的数值是十进制的 217;从高字节 65499 来看,里面的数值


是十进制的 2;从两个字节的整体看,正好是十进制的 729。
3.printf 与 scanf 函数的使用
printf 与 scanf 两个函数是 C 标准函数库里提供的格式输出/输入函数。它们的功能很
多,在任何一本 C 语言的教材里,都能找到详细叙述。这里只是概要地说明。
(1)printf 函数
printf 函数的功能是按照给定的输出格式要求,把所列的参数值在标准终端设备上显示
出来。printf 函数的一般使用形式为
printf ( 格式控制串,参数 1,参数 2,…);
其中“参数 1、参数 2,…”等是要输出的数据,它们组成输出参数表。“格式控制串”
是用双引号括起来的一个字符串常量,它有两部分内容:一是一般字符,它们将按照原样输
出;一是转换说明,它们的作用是把输出参数表中的一个个数据转换成指定的格式后再输出。
格式控制串中的每一个转换说明都由“%”开头,后面跟随格式字符。
例如,图 2-2 给出一条 printf 语句。执行该语句,
就把“Tow roots: x1 = ”、“\t x2 =”以及“\n”按照
原样输出(注意,\t 和\n 是两个转义字符,输出时将按
照转义字符的本身含义输出) 。另一方面,语句又把参数
表中所列的参数 re1 和 re2,按照格式控制串里给出的两
个转换说明“%f” ,分别将它们转换成指定的数据格式后
在指定的位置处进行输出。 图 2-2 printf 函数各部分的示意
表 2-2 给出了 printf 函数中最常用的格式字符及示
例。

表 2-2 printf 函数中最常用的格式字符


格式字符 输出形式 应用示例 输出示例
d 十进制 int 型 printf("x=%d\n", x); x=212

f 十进制 double 型 printf("sum=%f\n", sum); sum=0.628000


c 单个字符 printf("It is %c\n", c); It is W

s 字符串 printf("*** %s ***\n", s); *** Beijing ***


u 无符号十进制数 printf("address=%u\n", &x); address=65498

o 无符号八进制数 printf("Oct=%o\n", x); Oct=324


x 无符号十六进制数 printf("Hex=%x\n", x); Hex=D4

要正确使用 printf 函数,必须注意如下几点。


• 格式控制串必须在双引号内。
• 格式控制串内的转换说明个数应与参数表里所列的参数个数吻合,类型一致;
• 对参数表里所列诸参数,其计算顺序是自右向左进行的。因此,要注意右边的参数值
是否会影响到左边的参数取值;
• 格式控制串中给出的转换说明个数,应与参数表中所列参数个数相一致。
(2)scanf 函数

– 18 –
第 2 章 基本数据类型

scanf 函数的功能是接受来自键盘上的输入数据,按照格式控制符的要求将数据进行格
式转换,然后存放到相应参数所指示的单元地址里。scanf 函数的一般使用形式为:
scanf ( 格式控制串,参数 1,参数 2,… );
其中“格式控制串”是用双引号括起的一个字符串常量,它有两部分内容:一是作为输入数
据之间间隔的一般字符,输入时必须按照原样从键盘键入;一是以“%” 开头、后面跟随格
式字符的转换说明,由它们指出数据输入时的格式。 “参数 1、参数 2,…”等给出输入数据
存放的地址。所以参数表中的每一个参数都应该是变量的地址,而不是变量名。
例如,图 2-3 给出一条 scanf 语句。执行到该语句时,系统
会等待用户的输入。这时用户就应该在键盘上先输入一个整型数
据,它被存放到变量 a 所对应的存储地址中;接着输入间隔字符
逗号“,”;再是输入一个实型数据,它被存放到变量 y 所对应的
存储地址中。最后按键盘上的 Enter 键,结束输入。
要正确使用 scanf 函数,必须注意如下几点。 图 2-3 scanf 函数各部分示意
• 所有数据从键盘输入完毕后,以回车换行(即键盘上的 Enter
键)作为整个数据输入的结束;
• 若格式控制串的每个转换说明之间没有给出间隔符,那么输入时可以用空格键、Tab
键或回车换行键作为两个输入数据之间的间隔,但如果安排了间隔符,那么输入时必须遵照
规定输入;
• 参数表中的每一个参数必须是一个存储地址,而不能是其他,因此在变量名前不要忘
记加上取地址运算符“&” ;
• 格式控制串中给出的转换说明个数,应与参数表中所列参数个数相一致。
表 2-3 给出了 scanf 函数中最常用的格式字符及示例。

表 2-3 scanf 函数中最常用的格式字符


格式字符 输入形式 应用示例 输入示例
%d 匹配十进制整数 scanf("%d", &x); 输入 212,则 x 为 212

%f 匹配十进制实数 scanf("%f", &f); 输入 6.28,则 f 为 6.280000


%c 匹配单个字符 scanf("%c", c); 输入 A,则 c 为‘A’

%s 匹配字符串 scanf("%s", t); 输入 Beijing,则 t 中为字符串“Beijing”


%u 匹配无符号十进制整数 scanf("%u", &x); 输入 65498,则 x 为 65498

%o 匹配八进制数 scanf("%o", &x); 输入 324,则 x 为八进制数 324


%x 匹配十六进制数 scanf("%x", &x); 输入 D4,则 x 为十六进制数 D4

2.1.3 本章难点
1.区分 ' a ' 和"a"
在 C 语言里,字符常量是指用单引号括起来的单个字符,而字符串常量则是指用双引号
括起来的一个字符序列。因此,' a ' 是一个字符常量,而"a"则是一个字符串常量。
在内存中,是通过用一个字节存放其 ASCII 码值来表示某一个字符的。这对于字符常量
或字符串常量而言,都是一样做的。不过,字符常量里只含单个字符,字符个数是一定的;

– 19 –
C 语言程序设计—示例与习题解析

而字符串常量里所含字符的个数是不相同的。为了能够判定一个字符串在哪里结束,C 语言
是这样来处理的,每个字符串在内存中占用的字节数等于字符串长度加 1,用最后一个字节
存放值为 0 的所谓“空字符” ,作为字符串的结束标志。这个标志,在书写时常用“\0”或“NULL”
来表示。前面给出的 ' a ' ,在内存中只占用 1 字节;而"a"要在内存中占用 2 字节。如
图 2-4 所示(这里没有用字符的 ASCII 码值来表示字符)。
根据上面的叙述可知,一个字符串在内存占用的字节数是该字符串长度加 1。例如,字
符串"string"的长度是 6,因为它含 6 个字符;而
它在内存中实际占用 7 字节,因为要用最后一个字
节存放结束符。
2.区分 1、' 1 ' 和"1"
1 是一个整型数值,' 1 ' 是一个字符常量,"
图 2-4 字符常量与字符串常量在内存中的存放情况
1"是一个字符串常量。在内存里,要开辟 2 字节来
存放整型数字 1 的值, 开辟一个字节存放字符 1 的 ASCII 码值,开辟 2 字节来存放字符串"1",
第 1 字节放字符 1 的 ASCII 码值,第 2 字节放字符串结束符。如图 2-5 所示。

图 2-5 数值、字符常量、字符串常量在内存中的存放情况

3.区分 ' 0 ' 和 ' \0 '


' 0 ' 是把 0 作为字符看待,形成一个字符常量。存储时,用一个字节存放它的 ASCII
码值,即十进制数的 48,如图 2-6(a)所示;而 ' \0 ' 是把空字符(即 NULL)视为一个
字符,形成一个字符常量。存储时,用一个字节存放它的 ASCII 码值,即十进制数的 0,如
图 2-6(b)所示。所以 ' 0 ' 和 ' \0 ' 是不同的。

图 2-6 ' 0 ' 和 ' \0 ' 在内存中的存放情况

4.注意区分转义字符\ddd、\xhh 与八进制、十六进制整型常数表示法
在转义符“\”后跟 1~3 个数字时,C 语言就认为这些数字是以八进制数表示的某个字
符的 ASCII 码值,它与以前缀数字 0 打头的八进制整型常数的表示法是不同的。同样地,在
转义符“\”后先跟小写字母 x、然后跟 1~2 个数字时,C 语言就认为这些数字是以十六进制
数表示的某个字符的 ASCII 码值, 它与以前缀 0x 打头的十六进制整型常数的表示法是不同的。
即\0x4D 不是正确的转义字符,\x4D 才是正确的转义字符写法。
5.正确使用 printf 和 scanf 函数
– 20 –
第 2 章 基本数据类型

如前所述,对于 printf 函数要特别注意参数表的计算顺序问题,对于 scanf 函数要特


别注意参数前面的取地址运算符“&”问题。
例 2-2 下面程序的输出结果是什么?
#include<stdio.h>
main()
{
int x = 6;
printf( "x1=%d, x2=%d, x3=%d\n", x+2, x−5, x=10 );
}
[解] 这里的第 1 个%d 对应于表达式 x+2,第 2 个%d 对应于表达式 x−5,第 3 个%d 对应
于表达式 x=10。由于 C 语言规定在 printf 函数里,参数表所列诸参数的计算顺序是自右向
左进行的。因此应该先计算 x=10,它将变量 x 的值由初始的 6 变为 10;然后在 x 的值是 10
的基础上计算 x−5。所以该表达式的值为 5;最后仍在 x 的值为 10 的基础上计算 x+2。所以
该表达式的值为 12。因此打印结果是:
x1=12,x2=5,x3=10
这里容易犯的错误是忽略了计算顺序的规定,于是在 x 初值为 6 的基础上算出 x+2 的值
为 8,算出 x−5 的值为 1,然后是将 x 的值变为 10。结果错误地认为应该打印出:
x1=8,x2=1,x3=10

2.2 典型示例分析
示例 1 阅读如下程序,给出输出结果。
#include<stdio.h>
main()
{
char c1, c2;
c1 = 97;
c2 = 99;
printf ("%c\t %c\n", c1, c2);
printf ("%d\t %d\n", c1, c2);
}
[解] 输出结果是:
a c
97 99
在 C 语言里,字符常量在内存中占用一个字节,字节里存放的是该字符的 ASCII 码
值。于是,当把这个数值视为 ASCII 码值时,它就是代表一个字符;当把它视为一个 0~
255 的无符号整数时,它就是一个整型常量。在此前提下,字符常量与整型常量就是通
– 21 –
C 语言程序设计—示例与习题解析

用的。
程序中的第 1 条 printf 语句里,由转换说明“%c”可以看出是把要输出变量 c1 和 c2
的内容视为 ASCII 码值来看待,所以打印的是字母 a 和 c。程序的第 2 条 printf 语句里,由
转换说明“%d”可以看出是把要输出变量 c1 和 c2 的内容视为整型数值来看待,所以打印的
是数字 97 和 99。
示例 2 编写程序,它接受键盘上输入的一个字符。在把该字符的 ASCII 码值加 1 后,
输出新的字符。例如,输入 A(ASCII 码为 65),输出 B(ASCII 码为 66)

[解] 程序编写如下:
#include<stdio.h>
main()
{
char i; /* 这里也可以说明为 int i; */
scanf ("%c", &i);
printf ("%c\n", i+1);
}
C 语言允许字符型变量与整数直接进行算术运算。含义是把该字符型变量里当前的 ASCII
码值与整数进行运算。例如,若在键盘上输入 A,则变量 i 里存放的是其 ASCII 码值 65。于
是 i+1=65+1 为 66。这样,printf 语句就会输出字母 B 了。
示例 3 编写程序,它接受键盘上输入的两个字符(都在 ' 0 ' ~ ' 9 ' 和 ' A ' ~ '
F ' 范围内) ,代表一个十六进制数,然后输出与它相等的十进制数。例如,输入 3A,输出
58。
[解] 程序编写如下:
#include<stdio.h>
main()
{
int a, b;
scanf ("%x%x", &a, &b);
printf ("The decimal value of %x%x is = %d \n", a, b, 16∗a+b );
}
在本程序里,限定变量 a 和 b 都只接受在 ' 0 ' ~ ' 9 ' 和 ' A ' ~ ' F ' 范围内的
一个字符。于是,由它们可以拼接成一个两位的十六进制数。为了把这个十六进制数转换成
十进制数,必须代入公式 16∗a+b。
示例 4 编写程序,它接受键盘上输入的一个正整数,输出其十六进制值。例如输入 58,
输出 3A;输入 257,输出 101。
[解] 程序编写如下:
#include<stdio.h>
main()
{
int m;

– 22 –
第 2 章 基本数据类型

scanf ("%d", &m);

printf ("The Hex value of %d is = %x \n", m, m );


}
示例 5 编写程序,它接受键盘上输入的一个小写字母,然后将其转换成大写字母输出。
例如,输入 g,输出 G。
[解] 程序编写如下:
#include<stdio.h>
main()
{
char c1;
scanf ("%c", &c1);
printf ("The capital letter of %c is %c \n", c1, c1−32 );
}
由英文字母大、小写 ASCII 码值之间的关系可以看到,一个大写字母的 ASCII 码值加上
32 即是相应小写字母的 ASCII 码值。 例如, A 的 ASCII 码值是 65,则 a 的 ASCII 码值是 65+32,
即 97。本程序正是利用了这种关系,实现了英文大、小写字母之间的转换。
示例 6 若 x 是 int 型变量,y 是 float 型变量。所用的 scanf 调用语句格式为
scanf ( "x = %d, y = %f", &x, &y );
则为了将数据 10 和 66.6 分别赋给 x 和 y,正确的输入方式应该是什么?
[解] 按照 scanf 函数的使用规则,为了能将数据 10 和 66.6 分别赋给 x 和 y,在键盘
上首先应该输入“x =” ,然后输入数值 10。接着输入逗号“,”以及“y = ” ,然后输入数值
66.6,最后按键盘上的 Enter 键,结束输入。其他的输入方式都是不对的。
示例 7 请写出下面程序的输出结果:
#include<stdio.h>
main()
{
char c1 = ' A ' ;
int a = 4, b = 8;
long m = 2344673;
unsigned t = 65535;
printf ("%d%d\n", a, b);
printf ("%4d%4d\n", a, b);
printf ("%−4d, %−4d\n", a, b );
printf ("%c, %d, %o, %x\n", c1, c1, c1, c1);
printf ("%ld, %lo, %lx\n", m, m, m);
printf ("%u, %o, %x, %d\n", t, t, t, t);
}
[解] 其输出结果如下:
– 23 –
C 语言程序设计—示例与习题解析

48
***4***8
4***8***
A, 65, 101, 41
2344673, 10743341, 23c6e1
65535, 177777, ffff, −1
这里,用到 C 语言格式输出中各种附加格式字符的使用。要注意,答案里以“*”号代
表空格位置和个数。比如,语句 printf ("%4d%4d\n", a, b);中所给出的是两个“%4d”
形式的转换说明。它表示输出的每一个数据最小宽度是 4 列的位置。但现在每一个数据都只
有一位,因此要在它们的左边用空格填补,所以输出的形式是“***4***8”。又比如,下一条
printf 语句给出的是两个“%−4d”形式的转换说明。它表示输出的每一个数据最小宽度是 4
列的位置,且是要求左对齐。但现在每一个数据都只有一位,因此要在它们的右边用空格填
补,所以输出的形式是“4***8***” 。
C 语言格式输入、输出语句的格式字符、附加格式字符较多。通过实践,就能掌握对它
的正确使用。表 2-4 给出了 printf 函数中附加格式字符的含义。

表 2-4 printf 函数中的附加格式字符


附加格式字符 含 义
l 用于长整型,可加在格式字符 d、o、x 前面
m(代表正整数) 数据最小宽度,不足时左边补空格(除非 m 前有‘−’号)
n(代表正整数) 对实数,表示输出小数位数;对字符串,表示截取的字符个数

− 输出的数字或字符左对齐,不足时右边补空格

示例 8 请写出下面程序的输出结果:
#include<stdio.h>
main()
{
float x=86.6764, y=−784.125;
printf ("%f,%f\n",−x, y);
printf ("%−12f,%−12f\n", x, y);
printf ("%8.2f, %8.2f,%4f,%4f,%3f,%3f\n", x, y, x, y, x, y );
printf ("%e, %10.2e\n", x, y);
printf ("%s, %5.3s\n", "MICROSOFT", "MICROSOFT");
}
[解] 其输出结果如下:
86.676399, −784.125000
86.676399***, −784.125000
***86.68,*784.12,86.676399,−784.125000,86.676399,−784.125000
8.66764e+01,**−7.8e+02
MICROSOFT,**MIC

– 24 –
第 2 章 基本数据类型

2.3 课外习题、答案与解析

2.3.1 课外习题

一、单选题

1.下列数据中属于字符串常量的是( ) 。
A.MICRO B.' MICRO ' C."MICRO" D.' micro '
2.在内存中应该开辟( )个字节来存放字符' \n ' 。
A.1 B.2 C.3 D.4
3.在内存中应该开辟( )个字节来存放字符串"CHINA" 。
A.5 B.8 C.7 D.6
4.在下面的数据中, ( )是合法的长整型常数。
A.2058764 B.35L C.0.4625 D.2.18E+02
5. 下面的( )不是合法的 C 语言常量。
A.123 B.' M ' C.0892 D."Flag"
6.下面( )对变量的说明是错误的。
A.char c1, int x; B.int a, b; float x, y;
C.int a; int b; D.char c1; int x;
7.下面( )对变量的说明是正确的。
A.Int a, b, c; B.int x, float y;
C.int a, x; D.int a, x
8.下面的( )是合法的 C 语言字符常量。
A.\ ' 074 ' B.' \x43 ' C.' ab ' D . "
\n"
9.若有说明 int x = 0xDEF;,则下面三条语句:
printf ("%4d\n",x);
printf ("%4o\n",x);
printf ("%4x\n",x);
的输出是( ) 。
A.3567 B.3567 C.3567 D.3567
6757 6757 06757 6757
def def 0xdef 0def
10.已知 int 类型数据长度为 2 字节,那么 unsigned int 类型数据的取值范围是( ) 。
A.0~255 B.−32768~32767C.−256~255 D.0~65535
11.若 w、x、y、z 都是 int 型变量。则执行以下语句:
scanf ("%4d+%3d+%2d+%1d", &w, &x, &y, &z);
– 25 –
C 语言程序设计—示例与习题解析

printf ("%4d+%3d+%2d+%1d\n", w, x, y, z);


后,要求输出为 1234+123+12+1 的正确输入形式是( ) 。
A.1234123121<回车> B.1234123412341234<回车>
C.1234+1234+1234+1234<回车> D.1234+123+12+1<回车>

二、填空题

1.在内存中要用 个字节来存储字符串"Double"。
2.字符串"Double"的长度是 。
3.字符 0 的十进制 ASCII 码值是 48,则字符 9 的十进制 ASCII 码值是 。
4.字符型(char)数据在内存中是以其 的形式存储的。
5.8 位无符号二进制数能够表示的最大十进制数是 。
6.语句 printf ("a\bRe\ ' CH\ ' y\\\bou\n");的输出结果是 。
7.若有说明 char s1="5"; 那么 s1 里包含 个字符。
8.十进制数 125 在一个字节的二进制表示是 。

三、是非判断题(在括号内打“√”或“×”)

1.有变量说明语句:unsigned int x=65535;则执行 printf("%d\n",x);后,输出


结果为−1。( )
2.字符串长度与字符串在内存所占用的字节数是一个概念。( )
3.在 C 语言中,用 int 或用 Int 来说明整型变量都是可以的。( )
4.在 C 语言里,"12"就是整型数 12。 ( )
5.在 C 语言里,\014、\xB7 等形式上虽然出现多个字符,但都只代表一个字符。( )
6.在 C 语言里,是用保留字 float 来说明实型变量的。( )
7.在格式输入语句 scanf("x=%d, y=%f", &x, &y);里
,“x=”、
“y=”以及“,”会自
动显示在屏幕上,用户只需键入数据就可以了。 ( )
8.C 语言里,int 等数据类型所占用的内存字节数是由所用机器的字长来决定的。( )

四、程序阅读题

1.下面程序的输出结果是什么?已知字母 A 的 ASCII 码值为 65。


#include<stdio.h>
main()
{
char c1 = ' B ' ;
int x = 10;
c1 = c1 + x;
printf ("%c, %d\n",c1, x);
}
2.程序:
main()
– 26 –
第 2 章 基本数据类型

{
printf ("%d\n", NULL);
printf ("%d, %c\n", 50, 50);
printf ("%d, %c, %o\n", 48+10, 48+10, 48+10);
}
的输出结果是:
0
50, 2
58, :, 72
请做出解释。
3.说明下面程序的输出结果:
main()
{
int i = 66;
char c = ' A ' ;
float f = 43.576;
printf ("%d %f %c\n", i, f, c);
printf ("%d %8.2f %d\n", i, f, c);
printf ("%c %d\n", c+1, c);
}
4.说明下面程序的输出结果:
main()
{
int x = ' f ' ;
printf ("%c\n", ' A ' +(x – ' a ' + 1));
}

五、程序设计题

1.编写程序,它接受键盘上输入的一个大写字母,然后将其转换成小写字母输出。例
如,输入 G,输出 g。
2.编写一个程序,它读进由三个大写字母组成的单词,然后用小写字母输出这个单
词。
3.编写一个程序,由键盘输入半径数值,输出圆面积。
4.编写一个程序,它接收用户输入的三角形的底和高(都是 float 型),计算出面积。
然后输出所给数据和面积。
5.编写一个程序,它接收用户输入的房间地板的长和宽(以米为单位的 float 型),

出地板面积和所许地毯费用(¥15.20 元/平方米)。

– 27 –
C 语言程序设计—示例与习题解析

2.3.2 答案与解析

一、单选题

1.C 2.A 3.D 4.B 5.C 6.A 7.C 8.B 9.A 10.D 11.D

二、填空题

1.7 2.6 3.57 4.ASCII 码值 5.255


6.Re’CH’you。这里有两个左退一格的转义字符\b。第 1 个它使后面的字母 R 掩盖了
前面输出的字母 a;第 2 个它使后面的字母 o 掩盖了前面输出的反斜杠(转义字符\\)。另外,
转义字符\’达到输出单引号的目的。
7.1 8.01111101

三、是非判断题

1.√ 2.× 3.× 4.× 5.√ 6.√ 7.× 8.√

四、程序阅读题

1.答:L, 10
变量 c1 里初始存放的是大写字母 B 的 ASCII 码值 66。在执行了语句“c1 = c1 + x;”
后,c1 里的值变为 76,它是大写字母 L 的 ASCII 码值。因此在 printf 语句里按“%c”格式
输出 c1 时,就应该打印出 L。另外,程序里并未改变变量 x 的值。因此按“%d”格式输出它
时,仍然打印出 10。
2.答:第一条 printf 语句要将字符 NULL 以“%d”格式打印出来,也就是要输出它的
ASCII 码值。所以第 1 个输出结果是 0;第二条 printf 语句里的“%c”是要把以 50 为 ASCII
码值的字符进行输出,这个字符就是“2” ;第三 条 printf 语句里的“%c”是要把以 48+10=58
为 ASCII 码值的字符进行输出,这个字符就是“:” ,而“%o”是要把 48+10=58 按八进制数输
出,即是 72。
3.答:输出结果是:
66 43.576000 A
66 43.58 65
B 65
4.答:输出大写字母 G。因为 x– ' a ' 是字符常量 ' f ' 的 ASCII 码值减去字符常
量 ' a ' 的 ASCII 码值。结果是 5,加 1 后为 6。于是 ' A ' +6 得到的是大写字母 G 的 ASCII
码值。按格式“%c”输出,即是字母 G。

五、程序设计题

1.程序编写如下:
#include<stdio.h>
main()
– 28 –
第 2 章 基本数据类型

{
char c1;
scanf ("%c", &c1);
printf ("The small letter of %c is %c \n", c1, c1+32 );
}
2.程序编写如下:
#include<stdio.h>
main()
{
char c1, c2, c3;
scanf ("%c%c%c", &c1, &c2, &c3);
printf ("%c%c%c \n", c1+ ' a ' − ' A ' , c2+32, c3+32 );
}
比如,输入 CAT,就会输出 cat。如果你记得英文字母小写与大写的 ASCII 码值之间相
差 32,那么可以如答案中所做,直接用“c2+32”得到 c2 里大写字母的小写 ASCII 码值;如
果忘记了它们之间的差值,那么可以用这里的“c1+ ' a ' − ' A '”方法,得到大写字母的
小写 ASCII 码值。
3.程序编写如下:
#include<stdio.h>
main()
{
float r; /* 变量 r 用以存放输入的半径数值 */
float mj; /* 变量 mj 用以存放所求圆面积 */
printf ("Enter a value of redius"); /* 输出提示信息 */
scanf ("%f", &r); /* 输入半径数值,存于变量 r 中 */
printf ("The area of circle is %f \n", mj);
}
4.程序编写如下:
#include<stdio.h>
main()
{
float a, h, area; /* 存放三角形底、高和面积的变量 */
printf ("Enter base length and height of triangle!"); /* 输出提示信息 */
scanf ("%f%f", &a, &h); /* 输入三角形的底和高,存于变量 a 和 h 中 */
area = a∗h/2; /* 计算面积,存于变量 area 中 */
printf ("The area of a triangle with base = %f and hight = %f is %f \n", a, h, area);
}
注意,这里计算面积的语句:area = a∗h/2; 可以不要,而将其合并到最后的 printf
语句里。这时 printf 语句的写法应改为
– 29 –
C 语言程序设计—示例与习题解析

printf ("The area of a triangle with base = %f and hight = %f is %f \n",


a, h, a∗h/2);
5.程序编写如下:
#include<stdio.h>
main()
{
float len, wid, area; /* 存放地板长、宽和面积的变量 */
printf ("Enter the length and the width of floor!"); /* 输出提示信息 */
scanf ("%f%f", &len, &wid); /* 输入地板长、宽,存于变量 len 和 wid 中 */
area = len∗wid; /∗ 计算面积,存于变量 area 中 */
printf ("The area of the floor is %5.2f square meters\n", area); /* 打印出房
间面积 */
printf ("The cost for the carpet is ¥%5.2f\n", area∗15.20); /* 打印地毯
费用 */
}

– 30 –
第3章 运算符与表达式

3.1 本章基本概念及知识点
本章涉及以下三个方面的内容:
• C 语言的各类运算符;
• C 语言的各种表达式及其构成;
• 运算符的优先级和结合性。

3.1.1 基本概念
1.运算符
在数学运算中表示各种运算的符号称为“运算符”。C 语言的运算符种类繁多,主要有算
术运算符、赋值运算符、关系运算符、逻辑运算符、逗号运算符、位运算符、条件运算符和
长度运算符等。
2.单目运算符
只有一个运算对象的运算符称为“单目运算符” 。比如,−(负号)就是一个单目运算符。
3.双目运算符
有两个运算对象的运算符称为“双目运算符” 。两个运算对象总是分布在运算符的左右
两侧。比如,+(加号)就是一个双目运算符。
4.三目运算符
有三个运算对象的运算符称为“三目运算符”。这是人们习惯中很少遇到的、C 语言特有
的一种运算符。
5.前缀单目运算符
出现在运算对象前面(即左侧)的单目运算符,称为“前缀单目运算符”。
6.后缀单目运算符
出现在运算对象后面(即右侧)的单目运算符,称为“后缀单目运算符”。
7.表达式
用运算符把运算对象连接在一起所形成的式子,称为“表达式”。随表达式中运算符的
不同,在 C 语言里有算术表达式、赋值表达式、关系表达式、逻辑表达式、逗号表达式和条
件表达式等。
– 30 –
第 3 章 运算符与表达式

8.表达式的值
每个表达式按照运算符所规定的运算规则进行运算,最终得到的结果称为该“表达式的
值”。
9.运算符的优先级
当表达式中有多个运算符时,先做哪个运算符的运算、后做哪个运算符的运算,必须遵
循一定的规则。这种执行的先后顺序,就称为“运算符的优先级” 。优先级高的运算符的运算
先做,优先级低的运算符的运算后做。圆括号能够改变运算的执行顺序,其内的运算优先级
最高。
10.运算符的结合性
表达式中出现相同级别的运算符时,就有一个运算符的“结合性”问题。如果它们遵循
自左向右先遇到谁就做谁的规则,则称是“自左向右结合的”;如果它们遵循自右向左先遇到
谁就做谁的规则,则称是“自右向左结合的”。
表 3-1 给出了 C 语言中各种运算符以及它们的优先级和结合性。

表 3-1 C 语言运算符汇总
运算符类型 运 算 符 优先级 结合性
基本 ( )、[ ]、->、. 15 自左向右

单目 !、~、++、--、+、-、(type)、*、&、sizeof 14 自右向左
*、/、% 13
算术 自左向右
+、- 12
移位 >>、<< 11 自左向右
<、<=、>、>= 10
关系 自左向右
==、!= 9
& 8
位逻辑 ^ 7 自左向右

| 6
&& 5
逻辑 自左向右
|| 4

条件 ? : 3 自右向左
赋值 =、+=、-=、*=、/=、%=、|=、^=、&=、>>=、<<= 2 自右向左
逗号 , 自左向右

11.数据类型的转换
在计算或赋值时,经常会出现运算对象数据类型不一致的情形。C 语言提供自动式和强
制式两种办法,以便统一它们的类型,最终完成操作。这个过程称为“数据类型的转换”。
12.位运算
对整个运算对象按二进制位进行操作的运算,称为“位运算”。
13.逻辑运算
对取值“真”(以非零数值表示)或“假”(以数值 0 表示)的运算对象进行操作的
运算,称为“逻辑运算”。逻辑运算的结果只能是“真”(以数值 1 表示)或“假”(以

– 31 –
C 语言程序设计—示例与习题解析

数值 0 表示)。

– 32 –
第 3 章 运算符与表达式

3.1.2 本章重点
1.算术运算符和算术表达式
在 C 语言中,有如下三组算术运算符:
• 单目前缀算术运算符:+(正) 、−(负)
• 双目算术运算符:+(加) 、−(减)、*(乘)、/(除)、%(模)
• 单目前缀、单目后缀算术运算符:++(增 1) 、−(减 1)
这些运算符都以数值为运算对象,运算结果也都是数值型的。
关于算术运算符,要注意如下几点。
(1)除法运算符“/”的功能与运算对象的数据类型有关。若两个运算对象都是整型的,
则它实行的是“整除” ,即运算结果是舍弃余数后的商值;否则就是一般意义下的除法。
(2)模运算符“%”的运算对象只能是整型的。
(3)增 1、减 1 运算符的运算对象是整型、字符型,以及后面的指针型、整型数组元素
等变量。
(4)增 1、减 1 运算符作为前缀时,其运算对象应先增 1 或减 1,然后以新值参与其他
级别较低的运算;作为后缀时,其运算对象应先以原值参与其他级别较低的运算,然后再增
1 或减 1,从而获得新值。
例 3-1 有如下变量说明:
int a = 64, b = 7;
float x = 123.7, y = 3.1;
求 a / b、a % b、a / y、a%y、x % y 的值。
[解] a / b 的结果是 9,这是因为分子分母都是整型的,所以“/” 实行的是整除运
算。a % b 的结果是 1。a / y 的结果是 20.645162,这是因为分子是整型,分母是实型,所
以“/”实行的是一般除法。而 a%y 或 x%y 对于 C 语言来说都是非法的算术表达式,因为它们
没有保证分子和分母都是整型的。这时,C 编译程序会给出出错信息:
Illegal use of floating point in function main(在 main 函数中出现非法浮点运算)
例 3-2 试分析以下语句执行后诸变量的值:
(1)int y = 1, z = 3, x;
x = y – z ++;
(2)int y = 1, z = 3, x;
x = y − ++ z;
(3)int x = 4, w = 5;
y = w ++ ∗ w ++ ∗ w ++;
z = − − x ∗ − − x ∗ − − x;
[解]
(1)在表达式 x = y – z ++里,变量 z 左边运算符“−”的优先级低于其右边的运算符“++”。
但这时“++”是 z 的后缀单目运算符,因此 z 应该先以原值参与其他级别较低的运算(即先
与 y 做减法),然后再增 1,以获得新值。所以应该把 y – z = −2 的值赋给变量 x,然后 z
再增 1 成为 4。故最终结果是:
– 33 –
C 语言程序设计—示例与习题解析

x = −2, y = 1, z = 4。
(2)在表达式 x = y – ++z 里,变量 z 左边的运算符“++”是前缀单目运算符,因此 z
应该先增 1,然后以新值参与其他级别较低的运算(即与 y 做减法)。z 先增 1,其值成为 4。
然后与 y 做减法,结果为−3,赋给变量 x。故最终结果是:
x = −3, y = 1, z = 4。
(3)在表达式 y = w ++ ∗ w ++ ∗ w ++里,三个 w 都应该先以原值参与其他级别较低的
运算(即先做乘法“∗” ),将结果赋给变量 y 后,再进行三个“++”。因此执行这一语句后,
结果是:y = 125(即 5∗5∗5=125),w = 8(即 w 在原值 5 的基础上三次增 1) 。
在表达式 z = − − x ∗ − − x ∗ − − x 里,x 应先做三次减 1,然后再进行乘法。要注
意,x 三次减 1 后,x 成为 1。三个 1 相乘仍得 1,所以 z 的值为 1(这里 z 不能等于 3∗2∗1,
因为 x 对应的是同一个存储单元) 。因此执行这一语句后,结果是:x = 1,z = 1。
所以,最终结果是:w = 8,x = 1,y = 125,z = 1。
2.赋值运算符和赋值表达式
在 C 语言中,有如下三组赋值运算符:
• 基本赋值运算符(简称赋值运算符):= (赋值)
• 算术自反赋值运算符:+=(加赋值) 、−=(减赋值) 、∗=(乘赋值)、/=(除赋值) 、%=
(模赋值)
• 位自反赋值运算符:>>=(位右移赋值) 、<<=(位左移赋值) 、&=(位与赋值)、|=(位
或赋值)、^=(位按位加赋值)
它们都是双目运算符,且都实行自右向左的结合性。由各种赋值运算符构成的表达式,
称为“赋值表达式”。
关于赋值运算符,要注意如下几点:
(1)赋值运算符的左边必须是变量。最简单的赋值表达式具有形式:
变量 = 表达式
其含义是执行时首先计算赋值号( “=”)右边表达式的值,然后把该值赋给(即存入)
左边的变量。
(2)两组自反式赋值运算符的作用是把“运算”和“赋值”两个动作结合起来,成为一
个复合运算符。执行时,先把运算符左边变量的当前值与右边整个表达式的值进行规定的运
算,然后把运算结果赋给(即存入)左边的变量。
例 3-3 设有如下变量说明:
int x = 8, y = 8, z = 8;
那么执行语句:x −= y – z;后,变量 x、y、z 的值各是多少?
[解] x−= y − z 是一个自反式的赋值表达式,运算符“−=”的右边是一个整体。所以,
该语句等同于语句:x = x − (y − z);,即 x = x – y + z;。按 x、y、z 的值计算右边表
达式,求得结果为 5。将其赋给左边的变量 x,于是 x 取值 5。最终三个变量的值为
x = 5, y = 8, z = 8
3.关系运算符和关系表达式
C 语言中共有六个关系运算符:
<(小于) <=(小于或等于) ==(等于)
– 34 –
第 3 章 运算符与表达式

>(大于) >=(大于或等于) !=(不等于)


它们都是双目运算符,用于比较左、右两个运算对象的大小关系。由关系运算符构成的
表达式,称为“关系表达式” 。
关系表达式中的运算对象可以是数值,也可以是字符。若是字符,则将按照它们的 ASCII
码值来进行比较。关系表达式只能有两个可能结果:关系成立或不成立。“成立”意味这个关
系表达式的结果为“真” ,以数值 1 表示;“不成立” 意味这个关系表达式的结果为“假”,
以数值 0 表示。
在 C 语言里,要区分符号“==”和符号 “=” 。前者是关系运算符,表示两个量之间的“等
于或相等”的关系;而后者是赋值运算符,表示把一个表达式的值赋给(或存入)一个变量。
4.逻辑运算符和逻辑表达式
C 语言里共有三个逻辑运算符,它们是:
&&(逻辑与) 、||(逻辑或) 、!(逻辑非)
其中,&&和||是双目运算符,!是单目运算符。由它们构成的表达式,称为“逻辑表达式”。
通过关系运算符,只能形成简单的比较条件。要把多个简单的条件组合形成复杂的条件,
就必须利用逻辑运算符。逻辑运算符作用在关系表达式或数值上,运算结果是以数值 1(“真” )
或 0(“假” )表示的逻辑值。出现在逻辑表达式中的数值,以非 0 数值表示逻辑真,以数值 0
表示逻辑假。
关于逻辑运算符,要注意如下几点。
(1)参加逻辑运算时,用非 0 值来表示逻辑真,而逻辑运算的结果是用数值 1 来表示逻
辑真;至于逻辑假,无论是参与逻辑运算时还是求得的运算结果,都是以数值 0 来表示的。
(2)对于&&运算符,只要其左边的运算对象为假,则整个表达式肯定取假(数值 0) ,C
编译不再对右边的运算对象进行求值。
(3)对于||运算符,只要其左边的运算对象为真,则整个表达式肯定取真(数值 1) ,C
编译不再对右边的运算对象进行求值。
例 3-4 用 C 语言表示如下 4 个条件:
(1)20 < x (2)x < 30 (3)20 < x < 30 (4)20 < x < 30 或 x < −100
[解] (1)和(2)给出的是简单条件,可以直接使用关系运算符“< ”表示出来,即
表示为 20 < x 和 x < 30;(3)里给出的是由两个简单条件 20 < x 和 x < 30 构成的复杂条
件,x 既要大于 20,又要小于 30,因此要用到逻辑运算符&&,条件表示为 20<x && x<30; (4)
里给出的是更为复杂的条件,即要求 x 满足条件 20 < x < 30,或者满足条件 x < −100,因
此要用到逻辑运算符||,条件表示为 20<x && x<30 || x<−100。
5.逗号运算符和逗号表达式
逗号运算符就是把逗号(“,” )作为运算符。若干个表达式通过逗号“连接”在一起,
由此构成的表达式称为“逗号表达式”。逗号表达式的一般形式为:
表达式 1,表达式 2,表达式 3, …… ,表达式 n
逗号表达式的执行顺序是从左到右计算诸表达式的值,最右边表达式的值是该逗号表达
式的值。
例 3-5 执行下面程序后,输出的结果是什么?
main()

– 35 –
C 语言程序设计—示例与习题解析

{
int x = 10, y;
y = (x + 4, x + 10);
printf ("x = %d, y = %d\n", x, y);
}
[解] 这里,赋值表达式 y = (x + 4, x + 10)的右端是一个逗号表达式。该表达式的
值是最右边表达式 x + 10 的值。因此是把 20 赋给变量 y。所以输出的结果是:
x = 10, y = 20
如果把语句 y = (x + 4, x + 10);改写为 y = x + 4, x + 10;,那么程序变为:
main()
{
int x = 10, y;
y = x + 4, x + 10;
printf ("x = %d, y = %d\n", x, y);
}
这时整个语句由逗号表达式构成,它的第 1 个表达式是 y = x + 4,第 2 个表达式是 x +
10。计算第 1 个表达式,使变量 y 取值为 14。第 2 个表达式的值 20 是整个逗号表达式的值。
这样一来,程序的输出结果成为:
x = 10, y = 14
6.条件运算符和条件表达式
在 C 语言里,由“?”和“:”两个符号组合成条件运算符,是惟一的一个三目运算符。
由它构成的表达式,称为“条件表达式” 。条件表达式的一般形式为:
<表达式 1> ? <表达式 2> : <表达式 3>
其中,第 1 个运算对象<表达式 1>是一个逻辑表达式,第 2、3 个运算对象<表达式 2> 和<表
达式 3>的类型必须相同。
条件表达式的运算含义是:先计算<表达式 1>的值。如果其值为真(非 0) ,则条件表达
式以<表达式 2>的值作为自己的值;如果其值为假(0),则以<表达式 3>的值作为自己的值。
因此,<表达式 1>起到一个判定作用,整个条件表达式要根据它的值来决定自己的最终取值。
例 3-6 试分析下面程序的功能。
#include<stdio.h>
main()
{
int x, y;
scanf ("%d", &x);
y = (x % 2 == 0 ) ? 0 : 1;
printf ("x = %d, y = %d\n", x, y);
}
[解] 该程序的功能是:要求用户在键盘上输入一个整型数。随之判定该数是偶数还是
奇数。如果是偶数,则让变量 y 取值 0,否则取值 1。最后输出 x、y。比如,运行程序,在
– 36 –
第 3 章 运算符与表达式

键盘上输入数值 12,则输出:
x = 12, y = 0
又比如,在键盘上输入数值 15,则输出:
x = 12, y = 1
在程序里,“(x % 2 == 0 ) ? 0 : 1”是一个条件表达式。它根据关系表达式 x % 2 ==
0 成立与否,决定是取值 0 还是 1,然后把该值赋给变量 y。
7.位运算符及相应的表达式
C 语言有如下两组位运算符:
• 位逻辑运算符:&(按位与) 、|(按位或) 、^(按位加)、~(按位非)
• 位移位运算符:<<(左移) 、>>(右移)
在这些运算符中,除了按位非( “~”)是单目运算符外,其他的都是双目运算符。
由位逻辑运算符构成的表达式,称为“位逻辑表达式”;由位移位运算符构成的表达式,
称为“移位表达式”。
位运算符的运算对象是由二进制位组成的数据整体,且只能是整型和字符型的。运算结
果仍是整型或字符型的。
关于位移位运算符,要注意如下几点。
(1)移位表达式的一般形式是:
<表达式 1> <移位运算符> <表达式 2>
其中,<表达式 1>是移位操作的运算对象,<表达式 2>是移位的次数。比如,
x << 3 表示要把变量 x 里的二进制内容整体左移 3 个二进制位;
y >> 5 表示要把变量 y 里的二进制内容整体右移 5 个二进制位。
(2)对于左移运算符,移出去位的内容全部丢弃,空出的位用数字 0 填补。
(3)对于右移运算符,移出去位的内容全部丢弃。如果运算对象是无符号数,则空出的
位用数字 0 填补;如果运算对象是带符号数,则空出的位用符号位内容填补。
图 3-1 是位移位运算符的操作示意图。(a)和(b)是左移位的情形,无论是否带符号,
右侧移空的位总是用 0 去填补。 (c)和(d)是右移位的情形。在无符号数时,用 0 去填补左
侧移空的位;在带符号数时,用符号位去填补左侧移空的位。

图 3-1 移位运算示意图

8.长度运算符
sizeof 在 C 语言里被称为“长度运算符”,用以计算某变量或某数据类型说明符所需占
– 37 –
C 语言程序设计—示例与习题解析

用的字节数。它是一个单目运算符,被测对象应括在圆括号内。即
sizeof(变量名) 或 sizeof(数据类型说明符)
例如,在字长为 16 位的 PC 机上,若有变量说明 int x;那么
sizeof (x) 或 sizeof (int)
的运算结果都是 2(2 个字节) 。
9.数据类型的转换
C 语言中,数据类型的转换有自动式和强制式两种办法。
• 自动数据类型的转换
自动数据类型的转换是由 C 语言自动完成的,它分为两步:第一步,是将表达式中参加
运算的各个数据都转换成表达式中数据长度最长的那种数据类型后,再进行运算求值;第二
步,在把计算结果赋给某个变量前,应该将结果的数据类型转换成该变量的数据类型,然后
再把值赋给变量。例如,有如下变量说明:
int r = 12;
float pi = 3.1415;
double area;
执行语句 area = r ∗ pi; 。该语句右边的算术表达式 r ∗ pi 里,变量 r 是 int 型的,pi
是 float 型的。因此在计算前,C 语言自动先将 r 的类型转换成为 float 型,然后再计算。
这时求出值的类型也是 float 型的。但由于左边的变量 area 是 double 型的,因此在把计算
结果赋给 area 之前,C 语言还要把该结果转换成 double 型,然后才将其存入 area。
• 强制数据类型的转换
当程序中认为有必要时,程序设计人员可以使用强制式的数据转换方式,来得到所希望
的数据类型。具体做法是在需要进行类型转换的表达式前面用圆括号括住数据类型说明符,
这样,C 语言就能把该表达式转换成所指定的数据类型。其一般格式是:
(数据类型说明符)(表达式)
例如, (float)(x+5)表示计算出 x+5 的值之后,把该值强制转换成 float 型。
关于数据类型的转换,要注意如下两点。
(1)无论是自动转换还是强制转换,都具有临时性,不会改变参与者原来的类型和取值。
(2)当把数据长度长的结果转换存入数据长度短的变量中时,可能会影响到数据的精度,
因为超长部分被截掉了。

3.1.3 本章难点
1.增 1、减 1 运算符的正确使用
C 语言的增 1、减 1 运算符是非常有用的两个运算符。但它会使初学者感到不习惯,而
且还有前缀、后缀两种不同作用的操作形式。
(1)这两个运算符的运算对象只能是一个简单的变量名,而不能是表达式或常量。例如,
有变量说明 int x;,那么++x、x++等都是 C 语言合法的表达式形式;但++(x+1)、(x+1)++、
++100 等都不符合 C 语言的语法规则。
(2)这两个运算符不仅有对运算对象进行“运算”(即加 1、减 1)的作用,还有将运算后
的值回存到运算对象的作用。比如,有变量说明 int x=10;,那么++x 或 x++不仅是对变量 x
– 38 –
第 3 章 运算符与表达式

进行加 1 操作,并且还使 x 的取值由原先的 10 变为 11。表 3-2 解释了这两个运算符的双重作


用。

表 3-2 增 1、减 1 运算符的作用


运 算 符 表 达 式 解 释
++ ++x 先将 x 加 1 回存,再用 x 的新值参与表达式运算

++ x++ 先用 x 的值参与表达式运算,然后加 1 回存成为 x 的新值


− − − −y 先将 y 减 1 回存,再用 y 的新值参与表达式运算
− − y− − 先用 y 的值参与表达式运算,然后减 1 回存成为 y 的新值

例 3-7 阅读下面程序,给出输出结果。
#include<stdio.h>
main()
{
int x;
x = 5;
printf ("%d, \t", x); /* 第 1 个 printf 语句 */
printf ("%d, \t", x++); /* 第 2 个 printf 语句 */*
printf ("%d \n", x); /* 第 3 个 printf 语句 */
x = 5;
printf ("%d, \t", x); /* 第 4 个 printf 语句 */
printf ("%d, \t", ++x); /* 第 5 个 printf 语句 */
printf ("%d \n", x); /* 第 6 个 printf 语句 */
}
[解] 输出结果是:
5, 5, 6
5, 6, 6
该程序演示了增 1 运算符++作为前缀和后缀是的差别。第 1 个 printf 语句当然是打印
出数字 5,并由转义字符“\t” ,产生一个 Tab(制表)符,形成输出数据之间的间隔;第 2
个 printf 语句里的 x++,使得先输出 x 的原有值 5,然后增 1,成 6;所以第 3 个 printf 语
句输出 x 时,其值为 6。这样就形成了第一行的输出。在恢复 x 取值为 5 后,第 4 个 printf
语句打印出数字 5;第 5 个 printf 语句中的++x,表明应先在 x 上进行增 1 操作,使得 x 取
值为 6,然后才打印。故它打印出数字 6;第 6 个 printf 语句打印 x 的值,当然是 6。
(3)在程序设计时,++、− −这两个单目运算符必须紧挨着运算对象书写,中间不能有
空格。否则就会被视为是两个运算,从而发生错误。
(4)在出现增 1 运算符与加号连用(或出现减 1 运算符与减号连用)时,C 语言遵循“最
长匹配”原则,即采用自左向右尽可能多地将若干字符组成运算符的方法,来对表达式做出
解释。例如,
i+++j 将理解为 (i++)+j,而不是 i+(++j)
x− − −y 将理解为 (x− −)−y,而不是 x−(− −y)

– 39 –
C 语言程序设计—示例与习题解析

2.加深对逻辑运算符的理解
由关系运算符构成的条件都是比较简单的。比如条件:sex==1、age<=25、rate!=2.75,
它们都只是去检验一个单纯的条件:一个人的性别是否为女、一个人的年龄是否小于等于 25、
利率是否不等于 2.75 等。C 语言提供的逻辑运算符则可以把简单的条件组合成复杂条件,从
而形成各式各样的决策与判断。
(1)为保证两个条件都为真时才做某件事,就应使用逻辑运算符“&&” 。
表 3-3 给出了逻辑运算符“&&”的真值表。

表 3-3 逻辑运算符“&&”的真值表
<表达式 1>取值 <表达式 2>取值 <表达式 1> && <表达式 2> 的取值
0 0 0

0 非0 0
非0 0 0

非0 非0 1

从&&运算符的真值表里可以看出,只有&&两边表达式都取真值(非 0)时,整个逻辑表
达式才取真值。因此,在判定<表达式 1>取假值后,就不必再去计算<表达式 2>的取值。C 语
言正是这样做的。可见,在程序设计时,应把最有可能为假的条件放在&&运算符的左边,这
样能减少程序的执行时间。
例 3-8 分析下面程序的输出结果。
#include<stdio.h>
main()
{
int x, m, n, a, b;
m = n = a = b = 8;
x = (m = a>b ) && (n = a>b );
printf ("x=%d, m=%d, n=%d\n", x, m, n);
}
[解] 输出结果是:
x = 0, m = 0, n = 8
这里,变量 m、n、a、b 初始值都是 8。语句
x = (m = a>b ) && (n = a>b );
把逻辑表达式(m = a>b ) && (n = a>b )的值赋给变量 x。逻辑运算符&&左右两边都是赋值
表达式:左边是把关系表达式 a>b 的值赋给变量 m;右边是把关系表达式 a>b 的值赋给变量 n。
在计算这个逻辑表达式取值时,左边的关系表达式 a>b 不成立,因此取值为 0。也就是
说,变量 m 的值为 0。所以&&左边的条件取值为假。于是,C 语言不再去计算这个逻辑表达式
右边的条件,而直接把逻辑值 0 赋给变量 x。正因为没有去计算右边表达式的取值,所以并
没有做把关系表达式 a>b 的值赋给变量 n 的操作。因此变量 n 仍保持它原有的值 8。
(2)只要两个条件中的一个为真时就去做某件事,应该使用逻辑运算符“||” 。

– 40 –
第 3 章 运算符与表达式

表 3-4 给出了逻辑运算符“||”的真值表。

表 3-4 逻辑运算符“||”的真值表
<表达式 1>取值 <表达式 2>取值 <表达式 1> || <表达式 2> 的取值

0 0 0
0 非0 1
非0 0 1
非0 非0 1

从||运算符的真值表里可以看出,只要||两边表达式中有一个取真值(非 0)时,整个
逻辑表达式就取真值。因此,在判定<表达式 1>取真值后,就不必再去计算<表达式 2>的取值。
C 语言正是这样做的。可见,在程序设计时,应把最有可能为真的条件放在||运算符的左边,
这样能减少程序的执行时间。
(3)要“反转”一个条件的含义,就使用逻辑运算符“!”。
表 3-5 给出了逻辑运算符“!”的真值表。

表 3-5 逻辑运算符“!”的真值表
<表达式>取值 ! <表达式> 的取值
0 1
非0 0

3.区别关系运算符“==”和赋值运算符“=”
在 C 语言里,把运算符“==”和运算符“=”用错,是经常发生的事情,即在程序中把应该用
“==”的地方用了“=” ,或把应该用“=”的地方用了“==” 。不过,对于 C 语言来说,这不会发生
什么语法错误,它们都能够顺利地通过编译,并得以执行。这种逻辑错误,只有在用数据测试程
序时,才有可能发现。因此在编写程序时,应该尽量避免混淆使用它们的情形发生。
为什么 C 编译程序检查不出来这两个运算符用错的情形呢?原因有两个方面:一是因为
C 语言总是把取非 0 值的表达式视为真,把取 0 值的表达式视为假;二是 C 语言的赋值表达
式总是会产生一个值。
用运算符“==”构成的关系表达式,无非是要判定某个变量是否与某个表达式的取值相
等。比如,counter == 4。如果 counter 等于 4,则该关系表达式的结果为真(1) ;否则为
假(0) 。若误把运算符“=”用在了这里,成为一个赋值表达式 counter = 4,即把数值 4 赋
给变量 counter。由于赋给 counter 的值为非 0,所以整个赋值表达式的值为真(1)。可见,
counter = 4 仍然是一个可以做出判断的条件。可以看出,把运算符“==”误用成运算符“=” ,
会改变运算符左端变量的值。这是它存在的逻辑错误。
相反地,用运算符“=”构成赋值表达式,无非是要把一个新值赋给变量,改变该变量
的原来取值。比如,x = 5,表示不管 x 原先的取值是多少,从现在起它的值就是 5 了。如果
误把运算符“==”用在这种情况,成为了一个关系表达式:x == 5。那么编译程序只是去计
算这个关系表达式的值,如果 x 等于 5,条件为真,表达式返回值 1;如果 x 不等于 5,条件
为假,表达式返回值 0。但由于这里没有赋值运算符,所以无论返回何值,都不会改变 x 的
值。本来希望 x 取新值,但现在 x 的值保持原值不变。这是它存在的逻辑错误。

– 41 –
C 语言程序设计—示例与习题解析

4.位运算符的简单应用
所有的数据在计算机内部都是用一个二进制序列来表示的,每一位的值或为 1,或为 0,
比如 00111010。连续的 8 个二进制位构成一个“字节”。字节再往上是“字”。随机器不同,
组成字的字节数是不一样的。通常,计算机都是用字来存放整型数。比如 16 位机上一个字的
长度为 16 个二进制位,因此在这种机器上是用两个字节来表示整数的;32 位机的字长为 32
个二进制位,因此在这种机器上是用四个字节来表示整数的。
无论是哪个位运算符,都是以字符型或整型数据作为其运算对象的。它们对字节或字中
的各位进行测试、设置或移位。运算结果仍然是字符型或整型的。
(1)通过按位与运算,可以获取一个字符中的某几位位值。
例如,想得到 x=10101100 的低 4 位内容,那么只需要设置一个取值为 00001111 的字符
型变量。比如取名为 mask,它的特点是高 4 位全是 0,低 4 位全是 1,然后用它去与变量 x
做按位与运算( “&”
):

可以看出,在所得结果里,它的高 4 位全是 0,低 4 位里的内容与 x 里低 4 位的内容完


全相同。
(2)通过按位或运算,可以将某一位设置为 1。
例如,想将 x 的第 4 位设置为 1,其他位保持原样,那么只需要设置一个取值为 00010000
的字符型变量。比如取名为 setbit,它的特点是除了第 4 位为 1 外,其余位都是 0,然后用
它去与变量 x 做按位或运算( “|”):
x: 10101100
setbit: 00010000
:
结果: 10111100

第4位

可以看出,在所得结果里,除第 4 位被设置成 1 外,其余位的取值保持不变。


(3)通过按位加运算,可使指定位取值翻转,其他位保持不变。
例如,x=01011010,想把它的整个低 3 位翻转,即 0 变成 1,1 变成 0。为此只需要设置
一个取值为 00000111 的字符型变量,比如取名为 y,它的特点是第 0、1 和 2 位取值为 1(这
是低 3 位),其余位为 0。然后用它去与变量 x 做按位加运算(“^” ):
x: 01011010
y: 00000111
^:
结果
z: 01011101
第2位 第0位
第1位
可以看出,在所得结果里,高 5 位的取值保持与变量 x 一致,低 3 位的取值正好与 x 的
低 3 位取值相反。

– 42 –
第 3 章 运算符与表达式

(4)通过连续几个按位加,可以不用临时变量,达到交换值的目的。
例如,a=01000011,b=00101101,那么想将 a 和 b 的值交换(即 a 取 b 的值,b 取 a 的
值),可以通过以下三条语句来实现:
a = a ^ b ;
b = a ^ b ;
a = a ^ b ;
直观地,

(5)区分位逻辑运算符与逻辑运算符。
在程序设计中,经常会把位逻辑运算符“&”和“|”与逻辑运算符“&&”和“||”搞混,
这是需要注意的。它们的第 1 个区别是运算对象不同:位逻辑运算符是对二进制位进行操作,
而逻辑运算符是对整个表达式进行操作;它们的第 2 个区别是运算方式不同:位逻辑运算符
是按二进制位逐一进行运算,而逻辑运算符只是判断表达式的真或假;它们的第 3 个区别是
运算对象位置的不同:位逻辑运算符的两个运算对象的位置是可以交换的,而逻辑运算符的
两个运算对象的位置是不可以交换的。

3.2 典型示例分析

示例 1 编写程序,接收键盘输入的字符。如果字符是英文小写字母,则将其转换成大
写后输出,否则输出原字符。
[解] 现在要求根据输入的字符,来判断要不要进行转换。到目前为止所掌握的根据条
件进行判断的方法只有利用条件表达式。
假定输入的字符存放在变量 x 里。如果它是英文小写字母,那么就应该满足条件:
x>= ' a ' && x<= ' z '
根据题目的意思,若是小写字母,就应该将它转换成大写,然后输出;否则就将原字符
输出。这可以用两条不同的 printf 语句来实现:
printf("%c\n", x− ' a ' + ' A ' ); /* 如果是小写字母,则转换成大写后输出 */

printf("%c\n", x); /* 如果是其他字符,原样输出 */
经过这样的分析,程序中所需要的条件表达式就应该是:
(x>= ' a ' && x<= ' z ' ) ? printf("%c\n", x− ' a ' + ' A ' ) : printf("%c\n", x)

– 43 –
C 语言程序设计—示例与习题解析

因此,完整的程序如下:
#include<stdio.h>
main()
{
char x;
printf("Enter a caracter!");
scanf("%c", &x);
(x>= ' a ' && x<= ' z ' ) ? printf("%c\n", x− ' a ' + ' A ' ) : printf("%c\n", x) ;
}
示例 2 试分析下面程序的输出。
#include<stdio.h>
main()
{
int x, y;
x = 10;
y = 3;
printf("%d," , x/y) ;
printf("%d\n", x%y) ;
x = 1;
y = 2;
printf("%d, %d\n", x/y, x%y) ;
}
[解] 本题主要检验对 C 语言中的整除和求模运算的理解。第 1 条 printf 语句里,x/y
的分子和分母都是整型数,符合整除要求,于是输出 3;第 2 条 printf 语句里,x%y 的分子
和分母都是整型数,符合求模要求,于是输出 1。在重新设置 x 与 y 的取值后,第 3 条 printf
语句里,x/y 和 x%y 的分子和分母都是整型数,符合要求,于是分别输出 0 和 1。所以整个输
出应该是:
3, 1
0, 1
示例 3 有如下变量说明:
char ch;
int i;
float f;
double d, result;
试分析语句 result = (ch/i)+(f∗d)−(f+d); 中数据类型的转换过程。
[解] 本语句中参加运算的数据类型是不同的,有整形、单精度实型,还有双精度实
型等。因此在执行过程中,C 语言会对它们自动进行数据类型的转换。转换过程如图 3-2
所示:
从图中看出,先是把字符型的 ch 转换成为 int 型,求出(ch/i)的结果是 int 型的;把
– 44 –
第 3 章 运算符与表达式

float 型的 f 转换成为 double 型,求出(f∗d)和(f+d)的结果都是 double 型的。然后,


把(ch/i)
转换成为 double 型,以便求出最终结果。所以,最终结果是 double 型的。
*

图 3-2 数据类型转换示意

示例 4 若 x,y 都是 int 型变量,在 16 位 PC 机上执行语句:


y = ( x = 32767, x+1 ) ;
后,y 的取值(用十六进制表示)是多少?
[解] 所给语句的右端是一个逗号表达式。其执行过程是先把 32767 赋给变量 x,然后
计算出 x+1(32767+1=32768),把它作为整个逗号表达式的值赋给变量 y。所以,该语句执行
后 y 的十进制结果表示是 32768。
为了得到 y 的十六进制表示,用图示的方法加以说明。在 16 位 PC 机上,int 型变量要
占用 16 个二进制位(两个字节)表示。变量 x 可以如图 3-3(a)所示。

图 3-3 int 型数据的二进制图例

在 x 的基础上加 1,得到 y 值为 32768,即图 3-3(b)所示。为了得到该数的十六进制表示,


只需 4 位一划分、4 位一划分。从而得到它的十六进制表示为 8000。这可以用以下的程序来验证:
#include<stdio.h>
main()
{
int x, y;
y =(x=32767, x+1);
printf("x=%d, x=%x\n ", x, x) ;
printf("y=%u, y=%x\n", y, y) ;
}
该程序输出:x=32767, x=7fff
– 45 –
C 语言程序设计—示例与习题解析

y=32768, y=8000
示例 5 两次运行下面的程序:
#include<stdio.h>
main()
{
int x;
scanf ("%d", &x);
(x++ > 5) ? printf ("%d\n", x) : printf ("%d\n", x− −);
}
第 1 次在键盘上输入数字 6,第 2 次输入数字 4。试问其结果是多少?
[解] 第 1 次输入数字 6,它使得条件表达式
(x++ > 5) ? printf ("%d\n", x) : printf ("%d\n", x− −)
中的条件(x++ > 5)成立,因此整个条件表达式以 printf ("%d\n", x)为值,即打印
出 x。由于前面是 x++,故这时打印的 x 值应为 7。
第 2 次输入数字 4,它使得条件表达式
(x++ > 5) ? printf ("%d\n", x) : printf ("%d\n", x− −)
中的条件(x++ > 5)不成立,因此整个条件表达式以 printf ("%d\n", x− −)为值,即先打
印出 x,然后 x 再减 1。由于前面是 x++,故这时打印的 x 值应为 5(最终 x 的值为 4) 。所以,
两次执行程序,输出的结果是 7 和 5。
示例 6 执行下面的程序后,变量 b 里的值是什么?
#include<stdio.h>
main()
{
int x = 35;
char c = ' A ';
int b;
b = ( ( x & 15 ) && ( c < ' a ' ) ) ;
printf ("%d\n", b) ;
}
[解] 这里是要把逻辑表达式 ( x & 15 ) && ( c < ' a ' ) 的值赋给变量 b。该逻辑
表达式进行的是逻辑与( “&&”)运算。它的左边是一个位逻辑表达式,右边是一个关系表达
式。
先来看左边的 x & 15。从图 3-4 看出,运算结果是 3,即是逻辑真,取值为 1。

– 46 –
第 3 章 运算符与表达式

图 3-4 位逻辑运算示意

再来看右边的关系表达式 c < ' a ' 。由于变量 c 里存放的是 ' A ' ,它的 ASCII 码值
(65)小于 ' a ' 的 ASCII 码值(97)
。因此,该关系成立,运算结果为 1。
所以,整个逻辑表达式的结果是 1。因此,程序执行后,变量 b 里的值是 1。
示例 7 执行下面的程序后,打印出的结果是什么?
#include<stdio.h>
main()
{
int x , y = 1, z;
x= ((9+6)%5>=9%5+6%5) ? 1 :0 ;
printf ("x = %d\n", x) ;
y += z = x + 3 ;
printf ("y = %d\t z = %d\n", y, z) ;
x = y = z = 1;
− −x && ++y || z++;
printf ("x = %d\t y = %d\t z = %d\n", x, y, z);
}
[解] 执行结果输出如下:
x = 0
y = 4 z = 3
x = 0 y = 1 z = 2
赋值表达式 x= ((9+6)%5>=9%5+6%5) ? 1∶0 的右边是一个条件表达式。究竟是把 1 赋给 x,
还是把 0 赋给 x,要由“?”前的 (9+6)%5>=9%5+6%5 取值来决定。关系运算符“>=”左边的值
是 0,右边的值是 5(9%5 的值是 4,6%5 的值是 1) 。0>=5 不成立,因此是把 0 赋给 x。
在赋值表达式 y += z = x + 3 里, “=”与“+=”的优先级相同,自右向左结合。因此应先
做 x + 3,并把值赋给 z。于是 z 的值为 3。然后把 z 的值与 y 的值相加,再赋给 y。故 y 的值为
4。
在逻辑表达式− −x && ++y || z++ 里,&&的优先级高于||的优先级,因此计算由左向
右进行。在− −x 后,x 的值成为 0。于是− −x && ++y 的值为 0,不用去计算++y,y 的值仍
保持为 1。但对于运算符“||” ,它的左边是 0,还必须计算 z++才能确定整个取值。计算 z++,
使变量 z 的值成为 2。

3.3 课外习题、答案与解析

3.3.1 课外习题

一、单选题
– 47 –
C 语言程序设计—示例与习题解析

1.以下运算符中有县优先级最高的是( )。
A.&& B.& C.|| D.!=
2.有变量说明 int a = 3;,则表达式 a<1 && − −a>1 的运算结果和 a 的值应该是( ) 。
A.0 和 2 B.0 和 3 C.1 和 2 D.1 和 3
3.有变量说明 int m, n, x, y;,顺序执行下面语句:
m = 10;
n = 5;
x = (− −m == n++) ? − −m : ++n ;
y = n;
则变量 y 的最终值为( ) 。
A.5 B.10 C.6 D.7
4.判断 char 型变量 c1 是否为小写字母的正确表达式是( )
A.(c1>= ' a ')&&(c1<= ' z ' ) B.(c1>=a)&&(c1<=z)
C.' a ' <=c1<= ' z ' D.a<=c1<=z
5.设 int x = 12;,则执行完语句 a += a −= a*a; 后,a 的值是( ) 。
A.552 B.264 C.−264 D.144
6.C 语言中,运算对象必须是整型数的运算符是( ) 。
A.% B./ C.%和\ D.%和/
7.设 int x = 11;,则表达式(x++*1/3)的结果是( ) 。
A.5 B.3 C.4 D.6
8.若已定义 x、y 为 double 型变量,则表达式 x = 1, y = x + 3/2 的值是( ) 。
A.1 B.2 C.2.0 D.2.5
9.有变量说明语句 int a, b, c;,顺序执行下面语句:
a = b = c = 1;
++a || ++b && ++c;
那么,变量 b 的值应是( ) 。
A.2 B.1 C.0 D.3
10.已知字母 A 的 ASCII 码值为 65,执行下面的程序:
#include<stdio.h>
main()
{
char c1 = ' B ' , c2 = ' Y ' ;
printf ("%d, %d\n", ++c1,− −c2);
}
则输出结果是( )。
A.66,89 B.67,88 C.B,Y D.C,X

二、填空题

1.有表达式“20<x≤30”,用 C 语言正确描述它应该是 。
– 48 –
第 3 章 运算符与表达式

2.表达式 a=1,a+=1,a+1,a++的值是 。
3.执行程序:
#include<stdio.h>
main()
{
char x = 040;
printf ("%d\n", x = x<<1);
}
后,其输出结果是 。
4.字符型(char)数据在内存中是以其 的形式存储的。
5.变量 m、x、y、z 都是 int 型,顺序执行下面的语句:
m = 1; x = 2; y = 3; z = 4;
m = (m < x) ? m : x ;
m = (m < y) ? m : y ;
m = (m < z) ? m : z ;
变量 m 的最终取值为 。
6.设 int x = 4, y= 2;,那么表达式( x >> 2 ) / ( y >> 1 )的值是 。
7.若有变量说明语句
int w = 1, x = 2, y = 3, z = 4;
则表达式 w > x ? w : z > y ? z : x 的值是 。
8.执行下面程序:
#include<stdio.h>
main()
{
int i; long m; float f; double x;
i = m = f = x = 20/3;
printf ("%d, %ld, %.1f, %.1f\n", i, m, f, x);
}
输出的结果是 。
9.设 a = 3, b = 2, c =1,则表达式 a>b>c 的值是 。
10.执行下面的程序:
#include<stdio.h>
main()
{
int a = 0100, b = 100;
printf ("x = %d, y = %d\n", − −a, b++);
}
则输出结果是 。

– 49 –
C 语言程序设计—示例与习题解析

三、是非判断题(在括号内打“√”或“×”)

1.在 C 语言中,所有运算符都遵循自左向右的结合性。( )
2.在逻辑运算表达式 5 && (a>b)中,数值 5 被视为逻辑真。( )
3.逻辑运算表达式 5 && 4 的结果为 1。 ( )
4.在 C 语言中,运算符“/”的两个运算对象只能是整型的。( )
5.12.1%12.1 的运算结果是 0。 ( )
6.Sizeof(x)的结果是 4,表明变量 x 需占用内存的 4 个字节。
( )
7.x++1 表示变量 x 在原有值基础上增 2。( )
8.在 C 语言中,x==1 与 x=1 的含义是不相同的。( )
9.18<<2 的结果是 72。( )
10.把某个值赋给一个变量,就会达到修改该变量所对应单元的目的。( )

四、程序阅读题

1.下面的程序执行后,输出的结果是什么?
#include<stdio.h>
main()
{
int x;
x= −8+5∗3/6+9 ;
printf ("x = %d\t", x) ;
x=15%7+3%5−8 ;
printf("x = %d \t", x) ;
x = −3∗6/(4%6);
printf ("x = %d \n", x);
}
2.下面的程序执行后,输出的结果是什么?
#include<stdio.h>
main()
{
int x, y;
double w = 3.2;
x = 1.2;
y = (x + 3.8)/5.0;
printf ("%d \n", w*y);
}
3.有如下程序:
#include<stdio.h>
main()

– 50 –
第 3 章 运算符与表达式

{
int x, y, z;
scanf ("%d%d", &x, &y);
z = (x < y) ? y : x;
z = z*z;
printf ("%d \n", z);
}
假定在键盘上输入 3 和 8。试问输出的变量 z 的值是多少?

五、程序设计题

1.从键盘接收两个无符号整数(限制在 0~255 之间)。把第 1 个数的高 4 位与第 2 个


数的低 4 位拼接成一个新的数,把第 1 个数的低 4 位与第 2 个数的高 4 位拼接成一个新的数。
分别输出这 4 个数的十进制数值。
2.输入一个华氏温度,输出摄氏温度。其变换公式为
C=5/9∗(F−32)
3.编写一个程序,从键盘读取一个整数,然后输出该数以及它的奇偶性。
4.编写一个程序,从键盘读取两个整数,然后输出其中的大者。

3.3.2 答案与解析

一、单选题

1.D
2.B
3.D 注意:语句 x = (− −m == n++) ? − −m : ++n ; 里的 n++和++n 都会改变 n 的值,
从而影响到 y 的取值。
4.A
5.C 注意:这里“∗”的优先级最高,因此应该最先做 a∗a。然后由于“+=”和“−=”
的优先级相同,因此按照其自右向左结合的原则,做“−=”,再做“+=”。所以结果是 C。
6.A
7.B 注意:x 应该先用原值 11 与 1 相乘,然后才加 1。所以除法的分子应该是 11。于
是 11/3 是进行整除,结果为 3。但 x 的值由 11 变为 12。
8.C 注意:这里是一个逗号表达式,它以第 2 个表达式作为它最终的取值。第 1 个表
达式把 1 赋给 x。但因 x 是 double 型的,所以从第 1 个表达式里出来,C 语言将 x 的值自动
转换成 1.0。然后计算第 2 个表达式,同样也有数据类型的转换问题。所以最终的取值应是
2.0。
9.B 注意:只要运算符“||”左端取值为真,C 语言就不再去计算其右端表达式的值。
所以 b 仍保持原值 1 不变。
10.B

– 51 –
C 语言程序设计—示例与习题解析

二、填空题

1.x>20 && x<=30


2.2 注意:其值不能是 3。但 a 的最终值为 3。
3.64 注意:这里的 x=040 是八进制表示,如图 3-5 左图所示。在 printf 语句中,是
要将 x 的值左移一位后赋给 x,然后按十进制输出。对左图的数据左移一位后,结果成为右
图所示。它的八进制表示表示是 100,即是十进制的 64。

图 3-5 填空题 3 例图

4.ASCII 码值
5.1
6.1 注意:x >> 2 是把 x 右移两位,结果使 x 从 4 变为 1;y >> 1 是把 y 右移 1 位,
结果使 y 从 2 变为 1。于是 1/1 的结果是 1。
7.4
8.6, 6, 6.0, 6.0 注意:这里虽然都是把 20/3 赋给 i、m、f、x,但因为它们的
类型不同,所以 i 和 m 接受的是整型数值 6,f 和 x 接受的则是经过自动数据类型转换后的实
型数值 6.0( printf 中限制 f、x 只输出小数点后一位)。
9.0 注意:因 a>b 成立,故取值 1。但 1>c 不成立(因为 c=1) ,所以最终值为 0。
10.x = 63, y = 100 注意:这里 a = 0100 是八进制表示,相当于十进制的 64。b = 100
是十进制表示。在 printf 语句中,要求把− −a 和 b++都用十进制形式打印出来。对于 a,应
先减 1 再打印,故为 63;对于 b,应先打印,再加 1。故打印出 100,但最终值为 101。

三、是非判断题

1.× 2.√ 3.√ 4.× 5.×


6.√ 7.× 8.√ 9.√ 10.√

四、程序阅读题

1.答:输出结果为
x=3 x=−4 x=−4
2.答:结果是 0。因为虽然把 1.2 赋给变量 x,但它 是 int 型的,所以在执行语句 x = 1.2;
后,x 等于 1。在计算 y 时,右边是 4.8/5.0。但由于 y 是 int 型的,所以最终 y 是 0。因此
打印的结果是 0。
3.答:64。

五、程序设计题

1.答:编写程序如下:
#include<stdio.h>

– 52 –
第 3 章 运算符与表达式

main()
{
unsigned w, x, y, z, temp1, temp2;
printf ("Enter two numbers.\n");
scanf ("%u%u", &w, &x);
temp1 = w & 0xF0 ;
temp2 = x & 0x0F;
y = temp1^temp2;
temp1 = w & 0x0F ;
temp2 = x & 0xF0;
z = temp1^temp2;
printf ("w = %u\tx = %u\ty = %u\tz = %u\n", w, x, y, z) ;
}
这里,用变量 w 和 x 存放两个输入的数据。先利用位逻辑运算“&”,在变量 temp1 里得
到第 1 个数的高 4 位在 temp2 里得到第 2 个数的低 4 位。然后利用位逻辑运算“^”
,在变量
y 里得到拼接成的第 1 个新的数,同样地,利用位逻辑运算“&”,在变量 temp1 里得到第 1
个数的低 4 位在 temp2 里得到第 2 个数的高 4 位。然后利用位逻辑运算“^”,在变量 z 里得
到拼接成的第 2 个新数,最后按十进制将它们打印输出。例如,输入 124 和 39,则输出 119,
44。又如,输入 244 和 128,则输出 240 和 132。
2.答:编写程序如下:
#include<stdio.h>
main()
{
float c, f;
printf ("Enter a F-temperature.\n");
scanf ("%f", &f);
c = (5.0/9.0) * (f−32);
printf ("The C-temperature is %5.2f\n", c) ;
}
这里要注意,应该把变量 c、f 设置成 float 型。另外,在代入公式时,5/9 应该写成
5.0/9.0,即要用实数表示。否则 5/9 的结果是 0,因为分子分母都是整数,
“/”将做整数除
法。
3.答:编写程序如下:
#include<stdio.h>
main()
{
int x;
printf ("Enter a number:\n");
scanf ("%d", &x);

– 53 –
C 语言程序设计—示例与习题解析

(x % 2 == 0)? printf ("%d is a even number.\n", x) : printf ("%d is a odd nubmer\n",


x);
}
4.答:编写程序如下:
#include<stdio.h>
main()
{
int x, y, max;
printf ("Enter two numbers:\n");
scanf ("%d%d", &x, &y);
max = (x > y)? x : y;
printf ("The larger number is %d\n", max);
}
这里,由条件表达式(x > y)? x : y,得到 x 与 y 之间的大者,并将它赋给临时变量 max
保存,然后通过 printf 语句打印出来。在程序中,用临时变量保存某些中间结果,是常用的
一种程序设计手段。

– 54 –
第4章 顺序结构与选择
结构的程序设计

4.1 本章基本概念及知识点
本章涉及以下五个方面的内容:
• C 语言的赋值语句和复合语句;
• 单分支选择语句;
• 双分支选择语句;
• 多分支选择语句;
• C 语言中的字符输入/输出函数。

4.1.1 基本概念
1.程序结构
计算机程序是由语句序列组成的。程序中语句的执行顺序,称为“程序结构”。C
语言中有三种基本的程序结构:顺序结构,选择结构和循环结构。每种结构都包含若
干种语句。
2.顺序结构
若语句的执行顺序与书写顺序相同,那么这种程序结构称为“顺序结构”。
3.选择结构
依据给定条件来决定语句是否执行,这种程序结构称为“选择结构”。
4.赋值语句
在赋值表达式后面跟随一个语句结束符分号( “;”),就构成一个“赋值语句”。赋值语
句是 C 语言程序中最简单、使用最频繁的语句。其一般形式为
<变量> = <表达式>;
功能是计算赋值号右端<表达式>的值,然后赋给左端的<变量>,于是该变量获得新值。
5.复合语句
在 C 语言中,由一对花括号括起来的若干语句形成一个整体,被称为“复合语句” ,又
称为“分程序” 。要注意,复合语句中最后一条语句后面的分号不能省略,否则会造成语法错
– 54 –
第 4 章 顺序结构与选择结构的程序设计

误。比如,有程序如下:
#include<stdio.h>
main()
{
int w, x, y, z;
w = 1; x = 2;
if (x>w)
{
y = x + 3; /* 这两条语句构成一个复合语句 */
z = w + 5 /* 这是复合语句中的最后一条语句,丢失了分号 */
}
else
{
y = x + 5;
z = w + 3;
}
}
C 语言将给出错误信息:
Statement missing ; (语句缺少 ; )
6.空语句
在 C 语言中,称只由一个分号组成的语句为“空语句”。从语法上看,空语句起到语句
的作用。编译时,它并不产生任何指令代码,因此也就不执行任何动作。
7.单分支选择结构与 if 语句
条件为真时执行指定的语句,否则就跳过这些语句。这种程序结构称为“单分支选择结
构” 。在 C 语言中,用 if 语句来描述单分支选择结构。
8.双分支选择结构与 if-else 语句
在条件为真或假时,分别执行指定的不同语句。这种程序结构称为“双分支选择结构”。
在 C 语言中,用 if-else 语句来描述双分支选择结构。
9.if 语句的嵌套
在 if(或 if-else)语句中,又包含一个或多个 if(或 if-else)语句。这种情形被称
为“if 语句的嵌套” 。
10.多分支选择结构与 switch 语句
依据一个整型表达式所取不同的值,分别执行指定的不同语句。这种程序结构称为“多
分支选择结构” 。在 C 语言中,用 switch 语句来描述多分支选择结构。
11.头文件
C 语言中提供事先编写好的程序段,供用户调用,以减轻程序设计的工作量。这些程序
段称为系统函数。由于系统函数很多,因此将它们分类放在扩展名为“.h”的磁盘文件中。
这些文件被称为“头文件” ,也称“头函数” 。要在程序中使用某个系统函数,必须在程序的
开头用文件包含编译预处理命令,将包含该系统函数的头文件列出,即:
– 55 –
C 语言程序设计—示例与习题解析

#include <头文件名.h> 或 #include “头文件名.h”

4.1.2 本章重点
1.单分支选择语句 if
C 语言中,用 if 语句来表达程序的单分支选择结构。这种选择语句的一般形式和执行流
程如图 4-1 所示。

图 4-1 if 语句及其流程图

这里,<语句>可以是复合语句。以后,凡是注明<语句>的地方,都可以使用复合语句代
之,不再重复说明。
例 4-1 编写一个程序,接收键盘输入的一个实型数。如果它大于等于 60,则打印输出
信息“Passed”。
[解] 由于题目中要求“接收键盘输入的一个实型数”,因此需要说明一个 float 型的
变量,比如 float num;,来接收输入的实数。
另外,题目要求在所接收的实数大于等于 60 时,打印信息“Passed”,不然,什么也不
做。因此,可以用下面的 if 语句来描述:
if (num>=60)
printf("Passed");
整个程序编写如下:
#include<stdio.h>
main()
{
float num;
printf ("Please enter a real number:");/* 提示信息 */
scanf ("%f",&num); /* 接收从键盘输入的实数,存于 num 中 */
if (num>=60) /* 做出单分支选择 */
printf ("Passed\n");
}
2.双分支选择语句 if-else
C 语言中,用 if-else 语句来表达程序的双分支选择结构。这种选择语句的一般形式和
执行流程如图 4-2 所示。

– 56 –
第 4 章 顺序结构与选择结构的程序设计

图 4-2 if-else 语句及其流程图

例 4-2 编写一个程序,接收键盘输入的两个整型数。根据两数的关系,打印出相等或
不等的信息。
[解] 由于题目中要求“接收键盘输入的两个整型数”,因此需要说明两个 int 型的变
量,比如 int num1, num2;,来接收输入的整数。
另外,题目要求根据两数是否相等,打印出“相等”或“不等”的信息。这可用下面的
if-else 语句来描述:
if (num1== num2) /* 注意,这里用“==”
,不能用“=” */
printf ("%d is equal to %d\n", num1, num2);
else
printf ("%d is not equal to %d\n", num1, num2);
整个程序编写如下:
#include<stdio.h>
main()
{
int num1, num2;
printf ("Enter two integers:"); /* 提示信息 */
scanf ("%d%d",&num1, &num2); /* 接收键盘输入的两个整数 */
if (num1 == num2) /* 做出双分支选择 */
printf ("%d is equal to %d\n", num1, num2);
else
printf ("%d is not equal to %d\n", num1, num2);
}
3.嵌套的 if 语句
在 C 语言中,可以用嵌套的 if 语句,来构成多分支选择结构。这种嵌套语句的一般形
式和执行流程如图 4-3 所示。
例 4-3 编写一个程序,接收键盘输入的两个整型数。打印出两数之间等于、小于或大
于的关系信息。
[解] 整个程序编写如下:
#include<stdio.h>
main()
{
int num1, num2;

– 57 –
C 语言程序设计—示例与习题解析

printf ("Enter two integers :"); /* 提示信息 */


scanf ("%d%d",&num1, &num2); /* 接收键盘输入的两个整数 */
if (num1 == num2) /* 做出多个分支选择 */
printf ("%d is equal to %d\n", num1, num2);
else if (num1<num2)
printf ("%d is less than %d\n", num1, num2);
else
printf ("%d is greater than %d\n", num1, num2);
}

图 4-3 if 语句的嵌套结构

4.多分支选择语句 switch
在 C 语言中,多分支选择语句 switch 的一般形式和执行流程如图 4-4 所示。

图 4-4 switch 语句及其流程图

关于 switch 语句,要注意以下几点。
(1)所有 case 子句后所列的常量表达式值都不能相同。
(2)break 语句的作用是退出 switch 语句。如果<语句 i>的后面没有安排 break 语句,
那么执行完<语句 i>后,会继续往下执行<语句 i+1>。
(3)一定要用圆括号把 switch 后面的<表达式>括起来。否则会给出出错信息:
Switch statement missing ( (switch 语句缺少‘(’

– 58 –
第 4 章 顺序结构与选择结构的程序设计


Switch statement missing ) (switch 语句缺少‘)’

(4)一定要用花括号将 switch 里的 case、default 等括起来。
(5)default 可以省略。如果有它,其位置不一定放在整个语句的最后。
例 4-4 输入一个无符号短整数和数制选择符。根据不同的数制选择符,将短整数按十
进制、八进制、十六进制值输出。
[解] 假定数制选择符是:字符‘D’或‘d’为十进制,字符‘O’或‘o’为八进制,
字符‘X’或‘x’为十六进制。因为要根据用户输入的数制选择符对短整数做出多个处理选
择,所以可以使用 switch 语句来实现这一功能。具体地,整个程序编写如下:
#include<stdio.h>
main()
{
unsigned short x;
char ch;
printf ("Enter a unsigned short integer :"); /* 提示信息 */
scanf ("%d",&x); /* 接收键盘输入的整数 */
getchar ();
printf ("Enter a character:"); /* 提示信息 */
scanf ("%ch", &ch); /* 接收键盘输入的数制选择符 */
switch (ch) /* 根据 ch 的当前取值,做出多分支选择 */
{
case ' d ' : /* 显示十进制数 */
case ' D ' :
printf ("The decimal numeral is %d \n", x);
break;
case ' o ' : /* 显示八进制数 */
case ' O ' :
printf ("The octal numeral is %d\n", x);
break;
case ' x ' : /* 显示十六进制数 */
case ' X ' :
printf ("The hexadecimal numeral is %d\n", x);
break;
default: /* 显示数制选择符输入错信息 */
printf ("Input error!\n");
break;
}
}
注意:程序中第 1 个 scanf 语句的后面加了一条“getchar ();”语句,其作用将在下
– 59 –
C 语言程序设计—示例与习题解析

面讲述。另外,程序中如下写法:
case ' d ' : /* 显示十进制数 */
case ' D ' :
printf ("The decimal numeral is %d \n", x);
break;
是允许的,它表示取值 ' d ' 或 ' D ' 都去做列在 case ' D ' 后面的事情。
5.字符输入/输出函数
(1)字符输入函数
getch 与 getchar 是 C 语言里的两个字符输入函数。其功能是接收来自键盘上输入的一
个字符,返回输入字符的 ASCII 码值。它们都是无参函数。一般形式为
getch () 或 getchar ()
这两个函数的区别是前者立即接收用户的输入,不把字符回显到屏幕上;而后者则在用
户按 Enter 键后,才接收输入的第 1 个字符,并在屏幕上回显该字符。
(2)字符输出函数
putchar 是 C 语言里的字符输出函数。它是有参函数。其功能是将参数代表的字符在标
准输出设备(通常是显示器)上加以输出。一般形式为
putchar (‘字符’) 或 putchar (字符变量)
要注意,getch、getchar、putchar 都在名为“stdio.h”的头文件里。因此,若要在程
序里使用它们,必须在程序的开头用文件包含编译预处理命令,将其列出,即
#include <stdio.h> 或 #include "stdio.h"
例 4-5 从键盘上读入一个字符。如果是小写字母,则转换成大写显示出来;如果是大
写字母,则转换成小写显示出来;如果是一般字符,则原样输出。
[解] 程序编写如下:
#include<stdio.h>
main()
{
char ch;
printf ("\nEnter a character :\n"); /* 提示信息 */
ch = getchar(); /* 用 getchar 函数接收键盘输入的字符,并回显 */
if ( ' a ' <=ch && ch<= ' z ' ) /* 做出多个分支选择 */
putchar(ch−32); /* 输出该字符的大写 */
else if ( ' A ' <=ch && ch<= ' Z ' )
putchar(ch+32); /* 输出该字符的小写 */
else
putchar(ch); /* 原样输出 */
}
这里,使用 getchar()接收键盘的输入,使用 putchar (ch)把变量 ch 里的字符加以
输出。在图 4-5(a)里,输入小写 r,输出大写 R;输入大写 F,输出小写 f;输入字符*,
原样输出字符*。可见,当用 getchar ()接收键盘输入时,由于它的回显功能,因此会先把
– 60 –
第 4 章 顺序结构与选择结构的程序设计

输入的字符加以显示,然后再输出转换后的字符。
但是,如果把 getchar ()改为没有回显功能的 getch()来接收键盘的输入,那么情况就
如图 4-5(b)所示。即输入不会在屏幕上显示,屏幕上只显示 putchar (ch)的输出结果。

图 4-5 getchar()与 getch()的区别

现在来说明一下例 4-4 程序中第 1 个 scanf 语句的后面加一条“getchar ();”语句的


原因。scanf 是 C 语言中的格式输入函数。在使用它时,输入完所有数据后要按回车键作为
数据输入的结束。这个回车键也以一个字符的形式存放在键盘缓冲区里。正好这时后面的
scanf ("%ch", &ch);是接收一个字符,因此它就接收了这个回车键,从而引起错误。在
第 1 条 scanf 语句的后面安排一条“getchar();”语句,就是让它去接收这个回车键,以避
免错误的发生。

4.1.3 本章难点
1.if 语句与 switch 语句的关系
由 if 语句的嵌套形式,构造出了阶梯式的多分支选择结构。但因为是嵌套式的,所以
使用起来缺少灵巧性,在编程时容易出错。当层次太多时,编程者甚至自己也会对选择的关
系混淆不清了。
基于这个原因,C 语言又提供了实现多分支选择的 switch 语句,即通常所说的开关语句。
它通过将变量的当前值逐个与整型常量或字符常量比较,找出匹配者,从而决定执行哪个语
句段。
虽然 if 语句的嵌套形式和 switch 语句都能实现多分支选择,在某种场合也可以互换替
代,但 if 语句适应于各种条件的选择,能够计算关系或逻辑表达式;switch 语句只适用于
检验表达式与哪个值相等的情形。它们之间仍然是有区别的。
– 61 –
C 语言程序设计—示例与习题解析

要用 switch 语句来替代例 4-3 的 if 语句是不可能的。但要用 if 语句来改写例 4-4 是


能够做到的。
例 4-6 实现 if 语句与 switch 语句之间的改写。
(1)把下面给出的 if 语句程序段改写成 switch 语句程序段:
if (ivalue == 1)
ncount1++;
else if (ivalue == 2)
ncount2++;
else if (ivalue == 3)
ncount3++;
else
ncount4++;
(2)把下面给出的 switch 语句程序段改写成 if 语句程序段:
switch (ivalue)
{
case 1:
case 2:
case 3:
ncount1++;
break;
default:
ncount2++;
break;
}
[解] (1)对应的 switch 语句程序段是:
switch (ivalue)
{
case 1:
ncount1++;
break;
case 2:
ncount2++;
break;
case 3:
ncount3++;
break;
default:
ncount4++;
break;

– 62 –
第 4 章 顺序结构与选择结构的程序设计

}
(2)对应的 if 语句程序段是:
if (ivalue == 1 || ivalue == 2 || ivalue == 3)
ncount1++;
else
ncount2++;
2.if-else 嵌套语句结构中,if 与 else 的配对
在使用嵌套的 if-else 语句时,要特别关注 else 与哪一个 if 配对。比如有一个字符变
量 ch。关于它有程序段如下:
if (ch>= ' 0 ' && ch<= ' 9 ' ) /* 第 1 个 if */
if(ch== ' 5 ' ) /* 第 2 个 if */
y=0;
else
y=1;
对该程序段可以有两种理解。第一种理解是 else 与第 1 个 if 配对,其含义是如果 ch
里是数字字符,并且是数字字符 ' 5 ' ,那么 y 等于 0;如果 ch 里不是数字字符,那么 y
等于 1.第二种理解是 else 与第 2 个 if 配对。其含义是如果 ch 里是数字字符,并且是数字
字符 ' 5 ' ,那么 y 等于 0。如果 ch 里是除了数字字符 ' 5 ' 以外的其他数字字符,那么
y 等于 1。
在程序设计中当然不允许语句出现二义性的。对 if 和 else 之间的关系,作为 C 语言有
这样的规定:else 总是和距离它最近的 if 配对。因此,对上面的程序段,只有第二种理解
是正确的。C 语言是按第二种理解来执行的。
为了达到第一种理解的执行效果,可以借助于复合语句。即用花括号把
if(ch== ' 5 ' )
y=0;
括起来。于是上面的程序段应改写成:
if (ch>= ' 0 ' && ch<= ' 9 ' )
{
if(ch== ' 5 ' )
y=0;
}
else
y=1;
这样,就确保 else 与第一个 if 配对了。
例 4-7 有如下程序段:
if (x<10)
if (y>10)
printf ("* * * * *\n");
else

– 63 –
C 语言程序设计—示例与习题解析

printf ("# # # # #\n");


printf ("$ $ $ $ $\n");
试问当 x 等于 9、y 等于 11 以及 x 等于 11、y 等于 9 时其输出各是什么?
[解] 按照 else 与 if 就近配对的原则,以及编写程序时的缩进书写格式,该程序应该
是:
if (x<10)
if (y>10)
printf ("* * * * *\n");
else
printf ("# # # # #\n");
printf ("$ $ $ $ $\n");
因此,当 x 等于 9、y 等于 11 时,其输出应该是
* * * * *
$ $ $ $ $
当 x 等于 11、y 等于 9 时,其输出应该是
$ $ $ $ $

4.2 典型示例分析

示例 1 从键盘上输入三个实数,按由小到大的顺序输出。
[解] 程序编写如下:
#include<stdio.h>
main()
{
float x, y, z, temp;
printf ("Enter three real numbers:");
scanf ("%f%f%f",&x, &y, &z);
if (x > y)
{
temp = x;
x = y;
y = temp;
}
if (x > z)
{
temp = x;
x = z;
z = temp;

– 64 –
第 4 章 顺序结构与选择结构的程序设计

}
if (y > z)
{
temp = y;
y = z;
z = temp;
}
printf ("%5.2f, %5.2f, %5.2f\n", x, y, z);
}
这里,用 x、y、z 存放三个实数。并总是希望 x 里的数为最小,y 里的数其次,z 里的
数最大。因此做了三次比较。第 1 次是在 x 和 y 之间进行比较,如果 x 里的数大于 y 里的数,
则通过中间变量 temp 进行交换,以达到 x 里的数比 y 里的数小的目的。第 2 次是在 x 和 z
之间进行比较,以达到 x 里的数比 z 里的数小的目的。通过两次比较,在 x 里放的肯定是三
个数中的最小者。这样,在 y 和 z 之间进行比较后,三个数的大小关系就排定了。
示例 2 编写一个“猜猜看”程序。即给定一个样板数,比如 123,让用户在键盘上输
入整数。将其与样板数比较。如果相等,则输出“正确”字样;否则输出太大或太小的信息。
[解] 由于要进行比较,因此使用 if-else 语句来编写程序。整个程序如下:
#include<stdio.h>
main()
{
int guess;
int mag=123;
printf ("Enter a integer:");
scanf ("%d", &guess);
if (guess == mag)
{
printf ("--- Right ---\n");
printf ("%d is the magic number.\n", mag);
}
else if (guess > mag)
printf ("—wrong -- : Too high !\n");
else
printf ("—wrong -- : Too low !\n");
}
这里,变量 mag 用于存放样板数 123,用户在键盘上输入的数存放在变量 guess 里。通
过 if-else 语句进行比较,然后做出选择,并输出相应信息。其中,
else if (guess > mag)
printf ("—wrong -- : Too high !\n");
else

– 65 –
C 语言程序设计—示例与习题解析

printf ("—wrong -- : Too low !\n");


可以用下面的语句代替:
else
guess >mag ? printf("—wrong -- : Too high !") : printf ("—wrong -- : Too low !\n");
示例 3 编写一个程序,用户输入两个整数,然后通过菜单选择,对这两个整数施行加、
减、乘、除等不同的运算,并将两个操作数和运算结果输出。
[解] 整个程序如下:
#include<stdio.h>
main()
{
char ch;
int x1, x2;
printf ("Enter two integers:");
scanf ("%d%d", &x1, &x2);
printf ("1--- addition\n"); /* 4 个菜单选择项 */
printf ("2--- subtraction\n");
printf ("3--- multiplication\n");
printf ("4--- division\n");
printf ("Enter your choice :");
getchar(); /* 让开由于 scanf 产生的回车符 */
ch = getchar(); /* 接收用户的选择,存放在变量 ch 里 */
switch (ch)
{
case ' 1 ' :
printf ("%d + %d = %d\n", x1, x2, x1+x2);
break;
case ' 2 ' :
printf ("%d − %d = %d\n", x1, x2, x1−x2);
break;
case ' 3 ' :
printf ("%d ∗ %d = %d\n", x1, x2, x1*x2);
break;
case ' 4 ' :
printf ("%d / %d = %5.2f\n", x1, x2, (float)x1/x2);
break;
default:
printf ("No option selected !\n");
break;
}

– 66 –
第 4 章 顺序结构与选择结构的程序设计

}
比如,键入两个整数是 155 和 13。选择 4(做除法)时输出结果是 11.92;选择 1(做
加法)时输出结果是 168 等。这里要说明的是程序中的 case ' 4 ' 。由于 x1、x2 都是整
型数,因此一般情况下“/”是做整除,即当键入两个整数是 155 和 13 且选择 4 时,输出
结果是 11,而不是 11.92。为了能实施一般的除法,这里对 x1 进行了强制数据类型转换,
即(float)x1。从这个示例可以学到如何用 C 语言编写菜单,以及如何用 switch 语句来对
菜单做出选择。
示例 4 有如下程序:
#include<stdio.h>
main()
{
int x;
printf ("Enter a number:\n");
scanf ("%d",&x);
switch (x)
{
case 1:
printf ("x = 1\n");
break:
case 2:
x = 1;
case 3:
x += 2;
printf ("x = %d\n",x);
break;
case 4:
printf ("x = %d, ", x++);
printf ("x = %d\n", x);
break;
}
}
运行这个程序 4 次,分别输入数据 1、2、3、4。试问每次的输出结果是什么?
[解] 输入 1 时,输出的结果是:x=1。
输入 2 时,输出的结果是:x=3。这是因为输入 2 时,先做 case 2 下的 x = 1。但因为
后面没有跟随 break,所以继续做 case 3 后面的 x += 2 和 printf ("x = %d\n",x)。于
是输出 3。
输入 3 时,输出的结果是:x=5。
输入 4 时,输出的结果是:x=4, x=5。这是因为在做 printf ("x = %d, ", x++)时,x
值为 4,将 4 输出后才对 x 进行++操作,所以紧接着由 printf ("x = %d\n", x)输出 x=5。
– 67 –
C 语言程序设计—示例与习题解析

示例 5 有如下程序段:
if (y == 8)
if (x == 5)
printf ("@ @ @ @ @\n");
else
printf ("# # # # #\n");
printf ("$ $ $ $ $\n");
printf ("& & & & &\n");
(1)假定 x=5、y=8,如何仅用安插花括号的方法,使上述程序段输出结果:
@ @ @ @ @
$ $ $ $ $
& & & & &
(2)假定 x=5、y=8,如何仅用安插花括号的方法,使上述程序段输出结果:
@ @ @ @ @
(3)假定 x=5、y=8,如何仅用安插花括号的方法,使上述程序段输出结果:
@ @ @ @ @
& & & & &
[解] 这是对 C 语言复合语句的应用。
(1)的花括号安排是:
if (y == 8)
{
if (x == 5)
printf ("@ @ @ @ @\n");
else
printf ("# # # # #\n");
}
printf ("$ $ $ $ $\n");
printf ("& & & & &\n");
(2)的花括号安排是:
if (y == 8)
{
if (x == 5)
printf ("@ @ @ @ @\n");
}
else
{
printf ("# # # # #\n");
printf ("$ $ $ $ $\n");
printf ("& & & & &\n");

– 68 –
第 4 章 顺序结构与选择结构的程序设计

}
(3)的花括号安排是:
if (y == 8)
{
if (x == 5)
printf ("@ @ @ @ @\n");
}
else
{
printf ("# # # # #\n");
printf ("$ $ $ $ $\n");
}
printf ("& & & & &\n");
示例 6 输入一个年份,判定其是否为闰年。
[解] 一个年份是闰年的条件是:如果该年份能够被 4 除尽、但不能被 100 除尽,或
该年份能够被 400 除尽。如果用变量 year 存放输入的年份,那么这个条件用 C 语言可以表
示为
(year % 4 == 0) && (year % 100 <> 0) || (year % 400 == 0)
于是程序编写如下:
#include<stdio.h>
main()
{
int year;
printf ("Enter a year’s number :");
scanf ("%d", &year);
if ((year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0))
printf ("%d is a leap year.\n", year);
else
printf ("%d is not a leap year.\n", year);
}
这里要说明的是 if 后面的条件
(year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0)
必须用圆括号括起来,即必须写成
if ((year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0))
而不能写成
if (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0)
如果写成后者,C 语言会给出出错信息:
Expression syntax(表达式语法错)
示例 7 有 5 级分制转换成百分制的规则如下:
– 69 –
C 语言程序设计—示例与习题解析

5 级分制:5+ 5 5− 4+ 4 4− 3 2 1
百分制: 100 90 85 80 75 70 60 <60 <60
编写程序,将输入的 5 级分转换成相应的百分,然后输出。
[解] 程序编写如下:
#include<stdio.h>
main()
{
char c1, c2;
printf ("Enter score:");
scanf ("%c%c", &c1, &c2);
switch (c1) /* 根据输入的数字字符进行选择 */
{
case ' 5 ' :
switch (c2) /* 根据输入的附带字符进行选择 */
{
case ' + ' :
printf ("score = 100\n");
break;
case ' \n ' :
printf ("score = 90\n");
break;
case ' − ' :
printf ("score = 85\n");
break;
}
break; /* 这里安放 break 是必须的 */
case ' 4 ' :
switch (c2)
{
case ' + ' :
printf ("score = 80\n");
break;
case ' \n ' :
printf ("score = 75\n");
break;
case ' − ' :
printf ("score = 70\n");
break;
}

– 70 –
第 4 章 顺序结构与选择结构的程序设计

break;
case ' 3 ' :
printf ("score = 60\n");
break;
case ' 2 ' :
case ' 1 ' :
printf ("score < 60\n");
break;
default:
printf ("Input error!\n");
}
}
这里的 5 级分制,除了数字字符外,还可能有附带的字符,即“+”、 “−”。因此程序
中采用了两次使用 switch 语句的办法,来选择转换的目标。这就是 switch 语句的嵌套结
构。
注意,在内层的 switch 语句里,没有安排 default 子句。这样,当输入的附带字符不
是“+”、“−”时,就打印不出结果。为了弥补这一缺憾,可修改 case ' 5 ' 、case ' 4 '
的内层 switch 语句。比如,把 case ' 5 ' 的内层 switch 语句修改成:
switch (c2)
{
case ' + ' :
printf ("score = 100\n");
break;
default: /* 在此增加一条 default 子句 */
case ' \n ' :
printf ("score = 90\n");
break;
case ' − ' :
printf ("score = 85\n");
break;
}
即在 case ' \n ' 前添加一条 default 子句(它在 switch 语句里的位置不一定放在最后)

这样一来,当在键盘上输入“5*”时,就认为是只输入了 5,因此打印输出 90。

4.3 课外习题、答案与解析

4.3.1 课外习题

– 71 –
C 语言程序设计—示例与习题解析

一、单选题

1.下面程序中, ( )不能正确实现功能:对于输入的学生成绩,如果分数大于等于 80,


是优良;如果分数小于 60,属不及格;其他都算及格。
A.#include<stdio.h> B.#include<stdio.h>
main() main()
{ {
int score; int score;
scanf ("%d",&score); scanf ("%d",&score);
if (score>=80) if (score<60)
printf ("Good!\n"); printf ("Failed!\n");
else if (score>=60) else if (score>=60)
printf ("Passed!\n"); printf ("Passed!\n");
else else
printf ("Failed!\n"); printf ("Good!");
} }
C.#include<stdio.h> D.#include<stdio.h>
main() main()
{ {
int score; int score;
scanf ("%d",&score); scanf ("%d",&score);
if (score<60) if (score>=60)
printf ("Failed!\n"); if (score>=80)
else if (score>=80) printf ("Good!\n");
printf ("Good!\n"); else
else printf ("Passed!\n");
printf ("Passed!\n"); else
} printf ("Failed!\n");
}
2.与语句 if (a>b) if (c>d) x=1; else x=2; 等价的语句是( )。
A.if (a>b) {if (c>d) x=1; else x=2; }
B.if (a>b) {if (c>d) x=1;} else x=2;
C.if ( (a>b) && (c>d) ) x=1; else x=2;
D.if (x<=b) x=2; else if (c>d) x=1;
3.若有程序段如下:
a=b=c=0; x=35;
if (!a) x− −;
else if (b) ; if (c) x=3;
else x=4;

– 72 –
第 4 章 顺序结构与选择结构的程序设计

执行后,变量 x 的值是( )。
A.34 B.4 C.35 D.3

4.设有说明语句 int a=1, b=0;,执行下面的程序段:


switch (a)
{
case 1:
switch (b)
{
case 0:
printf (" - - - 0 - - -\n"); break;
case 1:
printf (" - - - 1 - - -\n"); break;
}
case 2:
printf (" - - - 2 - - -\n"); break;
}
输出的结果是( )。
A.- - - 0 - - - B.- - - 0 - - -
- - - 1 - - -
C.- - - 0 - - - D.- - - 0 - - -
- - - 1 - - - - - - 2 - - -
- - - 2 - - -
5.下列 if 语句中,不正确的是( )。
A.if (x<y) scanf("%d",&x) else scanf ("%d",&y);
B.if (x<y) ;
C.if (x == y) x += y;
D.if (x<y) {x++; y++;}
6.执行下面的程序:
#include<stdio.h>
main()
{
int x;
scanf ("%d",&x);
if (x++>5)
printf ("%d\n",x);
else
printf ("%d\n",x− −);
}

– 73 –
C 语言程序设计—示例与习题解析

若从键盘上输入的数字是 6 和 4,则输出是( ) 。
A.7 和 5 B.6 和 3 C.7 和 4 D.6 和 4

– 74 –
第 4 章 顺序结构与选择结构的程序设计

二、填空题

1.若执行下面的程序时,在键盘上输入 3 和 4,则输出是 。
#include<stdio.h>
main()
{
int x, y, z;
scanf ("%d%d",&x, &y);
z = x;
if (x<y) z= y;
z = z*z;
printf ("%d\n", z);
}
2.执行下面程序:
#include<stdio.h>
main()
{
int x=3, y=0, z=0;
if (x=y+z)
printf ("* * * * *\n");
else
printf ("# # # # #\n");
}
其输出结果是 。
3.执行下面程序:
#include<stdio.h>
main()
{
int x;
scanf ("%d", &x);

if (x++ > 5)
printf ("%d\n", x);
else
printf ("%d\n", x− −);
}
若输入 5,其输出结果是 。
4.执行下面程序:
#include<stdio.h>

– 75 –
C 语言程序设计—示例与习题解析

main()
{
int x, a=10, b=20, bt1=4, bt2=0;
if (a<b)
if (b != 15)
if ( !bt1 )
x = 1;
else
if ( !bt2 )
x = 10;
printf ("x=%d\n", x);
}
其输出结果是 。
5.程序填空:
#include<stdio.h>
main()
{
int x, y;
scanf ("%d", &x);
y=x%2;
switch( ① )
{
case 0:
printf ("It is a even integer.\n");
② ;
default:
printf ("It is a odd integer.\n");
}
}

三、是非判断题(在括号内打“√”或“×”)

1.在键盘上键入 abcd 并按回车键后,函数 getchar()接收的一个字符是 d。 ( )


2.多分支的 switch 结构中,每一个 case 子句后面都必须有 break; 出现。( )
3.多分支的 switch 结构中,default 子句的位置不一定放在最后。( )
4.用花括号括起若干条语句,就构成了一个复合语句。( )
5.复合语句中最后一条语句的语句结束符分号“;”可以省略。( )
6.if (x = y)
printf ("%d is equal to %d\n", x, y);
表达了“如果 x 等于 y,则打印信息:x is equal to y”的意思。
( )
– 76 –
第 4 章 顺序结构与选择结构的程序设计

四、程序阅读题

1.阅读下面的程序,写出其执行结果。
#include<stdio.h>
main()
{
int a = 10, b = 14, c = 3;
if (a<b) a = b;
if (a<c) a = c;
printf ("a=%d, b=%d, c=%d\n", a, b, c);
}
2.有程序段如下:
if (x<10)
{
if (y>10)
printf ("* * * * *");
}
else
{
printf ("# # # # #");
printf ("$ $ $ $ $");
}
试问当 x 等于 9、y 等于 11,以及 x 等于 11、y 等于 9 时,其输出各是什么?
3.有程序如下:
#include<stdio.h>
main()
{
int a, b;
a = 5;
b = (a>20)<1;
if (b == 1)
printf ("True\n");
else
printf ("False\n");
}
试问程序的输出是什么?
4.有如下程序:
#include<stdio.h>
main()

– 77 –
C 语言程序设计—示例与习题解析

{
int a, b;
scanf ("%d", &a);;
b = a % 13;
if (b == 0)
printf ("It ' s true\n");
else
printf ("It ' s false\n");
}
现在执行该程序两次,第 1 次输入 325,第 2 次输入 332。试问各输出什么?
5.有如下程序:
#include<stdio.h>
main()
{
int x, y = 1, z;
if (y == 0)
x = 0;
else
x=1;
printf ("1. x=%d, y=%d\n", x, y);
if (z = y<0)
x = 3;
else
if (y == 0)
x = 5;
else
x = 7;
printf ("2. x=%d, z=%d\n",x, z);
}
执行该程序后,输出的信息是什么?
6.有程序如下:
#include<stdio.h>
main()
{
char ch;
ch = getchar();
if (ch>= ' a ' && ch<= ' m ' || ch>= ' A ' && ch<= ' M ' )
ch = ch+3;
else

– 78 –
第 4 章 顺序结构与选择结构的程序设计

if (ch>= ' n ' && ch<= ' z ' || ch>= ' N ' && ch<= ' Z ' )
ch = ch−3;
printf ("%c\n", ch );
}
假设从键盘上输入 Exit 或输入 next 后回车。试问 printf 语句打印出什么信息?

五、程序设计题

1.输入一个整数,若它大于 5 且小于 10,则打印信息“5< AND <10” 。


2.编写一个程序,接收用户在键盘输入的一个字符。如果该字符是字母,则输出信息:
“It is a letter !”。如果该字符是数字,则输出信息:“It is a number !”。如果既不是
字母,也不是数字,则输出信息: “It isn’t a letter or number !”
3.编写一个程序,接收用户在键盘输入的一个整数,然后输出它是大于 0、等于 0 或小
于 0 的信息 ,即“The number is a positive.”、
“The number is a negative.”或“The number
is zero.”。
4.保证在键盘上输入一个 0~9 的整数。根据判定,当输入的是数字 0、1、2、3 时,输
出其是数字 0、数字 1、数字 2 的信息。当输入的是数字 3~9 时,输出“是其他数字”的信息。
5.编写一个程序,接收用户输入的一个实数,按下列公式计算并输出 y 的值,取小数
点后两位。

4.3.2 答案与解析

一、单选题

1.B
2.C
3.B 注意:在做这种题目时,最好将其用缩进的书写格式加以改写,以使程序层次清
晰,阅读起来就比较容易。本题所给程序段可以改写成:
if (!a)
x− −;
else
if (b)
;
if (c)
x=3;

– 79 –
C 语言程序设计—示例与习题解析

else
x=4;
整个程序由两条 if-else 语句构成。仔细观察可以看出,真正影响 x 取值的是第 2 条
if-else 语句。由于现在 c=0,因此应该做 else 后面的赋值语句:x=4;。所以最终 x 取值为
4。
4.D 注意:该程序是 switch 的嵌套形式。对于外层 switch 来说,由于当前 a 等于 1,
故做它的 case 1 子句。它的 case 1 子句就是 switch (b)。由于当前 b 取值 0,故做它的 case
0。于是输出“- - - 0 - - -”
。做完 case 0 后,遇到 break,退出 switch (b)。这里特别
要注意的是外层 switch (a)在它的 case 1 子句后面没有安排 break,继续做其 case 2 后面
的语句。因此又会输出“- - - 2 - - -”。所以正确的答案是 D。
5.A 注意:A 之所以错,是因为 scanf(“%d”,&x) 后面没有语句结束符分号。B 是正
确的,if (x<y)后面只有一个分号,表示当 x<y 时,什么也不做(空语句) 。
6.A

二、填空题

1.16 注意:最初,z 被赋值 3。但因为条件 x<y 成立,故又将 y 值赋给了 z。因此最


终输出的 z 应该是 16。
2.# # # # # 注意:变量 x 初始时被赋予值 3。但在 x=y+z 里,一方面把 x 赋予 0,
另一方面表明 if 的条件不成立。所以 if-else 选择的是执行语句 printf (“# # # # #\n”),
故输出结果是 # # # # # 。
3.6 注意:由于键入 5,所以条件(x++ > 5)不成立,因此去执行 else 后面的 printf
语句。但是在测试完(x++ > 5)后,x 要“++” 。因此执行 printf (“%d\n”, x− −)时,输
出的结果是 6(x 的最终值是 5,因为要“− −” )。
4.x=10 注意:条件(a<b)、(b != 15)都成立,但( !bt1 )不成立,因此要执行 else
后面的 if 语句。由于条件( !bt2 )成立,所以 x 被赋予 10。因此输出结果是 x=10。
5.① y ② break

三、是非判断题

1.× 2.× 3.√ 4.√ 5.× 6.×


注意:在 C 语言中,表示“如果 x 等于 y”,应写成“if (x == y)”
,而不应写成“if (x
= y)”。

四、程序阅读题

1.答:输出结果是:a=14, b=14, c=3。


2.答:当 x 等于 9、y 等于 11 时,程序段应执行语句 printf ("* * * * *"); ,故
输出:
* * * * *
当 x 等于 11、y 等于 9 时,程序段应执行语句 printf ("# # # # #"); 和 printf ("
$ $ $ $ $"); 故输出:
– 80 –
第 4 章 顺序结构与选择结构的程序设计

# # # # #
$ $ $ $ $
3.答:由于 a =5,所以赋值语句 b = (a>20)<1; 右端的表达式“a>20”取值为 0,
“(a>20)<1”取值为 1。因此是把 1 赋给变量 b。既然 b 等于 1,所以双分支 if 语句做 printf
("True\n") ;。故输出 True。
4.答:该程序的功能是检查输入的数据是否能被 13 整除。如果能整除,即满足条件 b ==
0。因此输出信息:“It’s true” ;否则输出信息:“It’s false” 。
由于 325 能被 13 整除,故打印信息“It’s true” ;由 于 332 不能被 13 整除,故打印
信息“It’s false” 。
5.答:输出信息:1. x=1, y=1
2. x=7, z=0
理由是:在第 1 条 printf 之前,由于 y 取值 1,不等于 0,因此 x 被赋予值 1(即 x=1) 。
所以第 1 条 printf 语句输出信息 1. x=1, y=1。在第 1 条 printf 之后,由于 y<0 不成立,
故变量 z 被赋予值 0,所以应该执行:
if (y == 0)
x = 5;
else
x = 7;
但因为 y 不等于 0,因此 x 被赋予值 7。所以第 2 条 printf 语句输出信息 2. x=7, z=0。
6.答:如果是输入 Exit 后回车,则打印出大写字母‘H’;如果是输入 next 后回车,
则打印出小写字母‘k’ 。这是因为函数 getchar ()在遇到回车符后,总是只接收输入字符串
的第 1 个字符。因此,如果输入的是 Exit 后回车,那么它接收的是大写字母‘E’。这样,ch
就落在 ch>= ' A ' && ch<= ' M ' 区间里,使条件 ch>= ' a ' && ch<= ' m ' || ch>= '
A ' && ch<= ' M ' 取真值,故对 ch 做 ch=ch+3 的操作,所以 printf 语句打印出大写字
母‘H’ 。如果是输入 next 后回车,那么 getchar ()函数接收的小写字母‘n’。这样,ch 就
落在 ch>= ' n ' && ch<= ' z ' 区间里,使条件 ch>= ' n ' && ch<= ' z ' || ch>= ' N
' && ch<= ' Z ' 取真值,故对 ch 做 ch = ch−3 的操作,所以 printf 语句打印出小写字母
‘k’ 。

五、程序设计题

1.答:程序编写如下:
#include<stdio.h>
main()
{
int num;
printf ("Enter a number:");
scanf ("%d", &num);
if (num>5)
if (num<10)

– 81 –
C 语言程序设计—示例与习题解析

printf ("5< AND <10\n");


}
也可以编写成:
#include<stdio.h>
main()
{
int num;
printf ("Enter a number:");
scanf ("%d", &num);
if (num>5 && num<10)
printf ("5< AND <10\n");
}
2.答:程序编写如下:
#include<stdio.h>
main()
{
char ch;
printf ("Please input a character:");
scanf ("%c",&ch);
if (ch>= ' a ' && ch<= ' z ' || ch>= ' A ' && ch<= ' Z ')
printf ("It is a letter !\n");
else if (ch>= ' 0 ' && ch<= ' 9 ')
printf ("It is a number !\n");
else
printf ("It isn’t a letter and a number !\n");
}
3.答:该程序要求在用户输入数据的基础上,做出不同的选择。所以应该设置一个变
量,比如 int num; ,由它接收键盘的输入。然后使用 if-else 的嵌套形式,对它的取值进
行判断,做出正确的选择。因此程序编写如下:
#include<stdio.h>
main()
{
int num;
printf ("Enter a number:\n");
scanf ("%d", &num);
if (num > 0)
printf ("The number is a positive.\n");
else if (num < 0)
printf ("The number is a negative.\n");

– 82 –
第 4 章 顺序结构与选择结构的程序设计

else
printf ("The number is zero.\n");
}

4.答:编写程序如下:
#include<stdio.h>
main()
{
int num;
printf ("Please enter a number:\n");
scanf ("%d",&num);
if (num<0 || num>9) /* 如果输入超出范围,打印出错信息 */
printf ("Input error !\n");
else
{
switch (num)
{
case 0:
printf ("The number is 0.\n");
break;
case 1:
printf ("The number is 1.\n");
break;
case 2:
printf ("The number is 2.\n");
break;
default:
printf ("It is other number.\n");
}
}
}
注意,该程序主要由 if-else 语句构成。
如果输入的数据不在 0~9 之间,就打印出“Input
error”信息,什么也不做。只有输入数据在 0~9 之间时,才去执行 switch 语句。该语句根
据题目要求,按照 num 取值的不同,进行多分支选择。
5.答:编写程序如下:
#include<stdio.h>
main()
{
float x;

– 83 –
C 语言程序设计—示例与习题解析

printf ("Please enter a real number:\n");


scanf ("%f", &x);
if (x<=10 )
printf ("y = %5.2f\n", x);
else if (x>10 && x<50)
printf ("y = %5.2f\n", 2*x+6);
else
printf ("y = %5.2f\n", 3*x−15);
}

– 84 –
第5章 循环结构的程序设计

5.1 本章基本概念及知识点
本章涉及以下 5 个方面的内容:
• for 循环语句;
• while 循环语句;
• do-while 循环语句;
• continue/break 语句;
• goto 语句和语句标号。

5.1.1 基本概念
1.循环结构
在程序代码中,重复执行一段程序,直到某个条件满足时止。这种程序结构称为“循环
结构”。在 C 语言中,for 语句、while 语句、do-while 语句和 goto 语句都可以用来描述不
同的循环结构。
2.循环体
在循环结构中,被重复执行的那一段程序,称为“循环体”。
3.次数型循环结构
如果已知循环体执行的次数,那么这种循环结构被称为“次数型循环结构” 。在 C 语言
中,for 循环语句最适合用来实现次数型循环结构。
4.当型循环结构
如果是先检验控制循环的条件,当条件成立时就循环,条件不成立时就退出循环,那么
这种循环结构被称为“当型循环结构”。在 C 语言中,while 循环语句用于实现当型循环结构。
5.直到型循环结构
如果先执行循环体,然后再检验控制循环的条件,条件成立时继续循环,条件不成立时
退出循环,那么这种循环结构被称为“直到型循环结构” 。在 C 语言中,do-while 循环语句
用于实现直到型循环结构。
6.循环的嵌套结构
在一个循环的循环体内,又包含另一个完整的循环结构。这种程序结构称为“循环的嵌
– 84 –
第 5 章 循环结构的程序设计

套结构” ,有时也称为“多重循环” 。
7.外循环与内循环
在循环的嵌套结构中,实现外层循环的循环语句称为“外循环”。在外层循环的循环体
中所包含的循环称为“内循环” 。
8.循环控制变量
在循环语句中,根据其取值来控制循环执行的变量,称为“循环控制变量” 。
9.无条件转移语句
无须根据条件就能改变程序执行流程的语句,称为“无条件转移语句”。在 C 语言中,
goto 语句就是无条件转移语句。
10.语句标号
在 C 语言中,安放在某条语句之前的一个标识符,称为该语句的“语句标号”。因此语
句标号实际上是程序中某语句的代号。语句标号与语句之间,必须用冒号(“:”)隔开。

5.1.2 本章重点
1.次数型循环结构与 for 语句
在 C 语言里,用 for 语句来实现次数型循环结构。这种语句的一般形式和执行流程如图 5-1 所示。

图 5-1 for 语句的一般形式和执行流程

图 5-1(a)中代表循环体的<语句>,可以是空语句,单条语句,或用花括号括起的复合
语句。从图 5-1(b)可以看出,在程序中遇到 for 语句时,其执行过程可分为以下 5 步。
第 1 步:计算<表达式 1>,对循环控制变量赋初值(在整个循环中,它只做一次)。
第 2 步:计算<表达式 2>。
第 3 步:判断<表达式 2>的计算结果。如果其值不为 0,表示循环条件成立,于是去执
行循环体,然后做第 4 步;如果其值为 0,表示循环条件不成立,则去做第 5 步。
第 4 步:计算<表达式 3>,对循环控制变量的取值进行修正,然后去做第 2 步。
第 5 步:退出 for 循环,去做其后的语句。
for 语句是 C 语言里最为灵活的循环语句,在下一节还要对它做进一步讲述。做为次数
型循环结构,for 语句最典型、最简单的使用形式是:
for(循环变量赋初值;终值;增量)
<语句>
– 85 –
C 语言程序设计—示例与习题解析

例 5-1 编写一个求 1+2+3+…+100 的累加和程序。


[解] 题目要求从 1 累加到 100。不难看出,这是要做 99 次加法,所以是一个典型的次
数型循环问题。程序中应该设置两个变量:一个能从 1 变到 100,并且每次增加 1;一个记录
累加和。据此,程序编写如下:
#include<stdio.h>
main()
{
int i, sum = 0; /* 由于要在 sum 里做累加,因此 sum 的初值应为 0 */
for (i=1; i<=100; i++)
sum = sum+i;
printf ("1+2+3+…+100 = %d\n",sum);
}
在进入 for 语句时,由“i=1”把循环控制变量 i 设置为初值 1。由于它满足条件“i<=100” ,
因此第 1 次做循环体语句: “sum = sum+i;”。这时 sum 等于 0,i 等于 1。故相加后把结果 1
赋给 sum。随之做“i++” ,使 i 由 1 变为 2。由于它仍满足条件“i<=100” ,因此第 2 次做循
环体语句: “sum = sum+i;”。这时 sum 等于 1,i 等于 2。故相加后把结果 3 赋给 sum。随之
做“i++” ,使 i 由 2 变为 3。如此等等,一直做下去。直到把 100 累加到 sum 去。随之 做“i++” ,
使 i 由 100 变成 101。这时它就不满足条件“i<=100”了,故循环停止,转去做 for 语句后
面的 printf 语句,将结果打印出来。
该程序投入运行后,将在屏幕上打印出:
1+2+3+…+100 = 4950
图 5-2 给出了这个循环结构头部中各个部分的含义。
本例中的“i++”表示循环控制变量 i 每次自增 1。在本例
图 5-2 典型 for 循环结构头部各部分含义
里,这种自增 1 的表达式,下面4种写法都是等价的:
i = i +1, i += 1, ++i, i++
2.当型循环结构与 while 语句
在 C 语言里,用 while 语句来实现当型循环结构。这种语句的一般形式和执行流程如图
5-3 所示。

图 5-3 while 语句的一般形式和执行流程

从图 5-2(b)可以看出,在程序中遇到 while 语句时,其执行过程可分为以下三步。

– 86 –
第 5 章 循环结构的程序设计

第 1 步:计算<表达式>。
第 2 步:判断<表达式>的计算结果。如果其值不为 0,表示循环条件成立,于是去执行
循环体,然后再去做第 1 步;如果其值为 0,表示循环条件不成立,则去做第 3 步。
第 3 步:退出 while 循环,去做其后的语句。
例 5-2 接收键盘输入的一个个字符,并加以输出,直到键入的字符是‘#’时终止。
[解] 由于是不断地从键盘输入字符,并逐一加以输出,所以这是一个典型的循环问题,
循环体就是输入字符和输出字符。这可以用 C 语言提供的 getch(或 getchar)以及 putchar
函数来实现。从题目中可以看出,只要键盘上输入的字符不是‘#’ ,输入就应该继续下去。
也就是说,如果设置一个接收键盘输入字符型的变量 ch,那么当条件“ch != ‘#’”满足
时,输入就应该继续下去。所以,程序可以用当型的 while 语句来实现。整个程序如下:
#include<stdio.h>
main()
{
char ch;
ch = ' \0 ' ; /* 为变量 ch 赋初值 */
while (ch != ' # ' )
{
ch = getchar();
putchar(ch);
}
}
为了保证能进入到 while 的循环体里面去,以便开始接收键盘的输入,于是程序先为变
量 ch 赋初值为‘\0’
。但这样的语句安排,造成了接收显示在前、判断在后的局面。也就是
说,程序并不是一接收到字符‘#’就停止运行,而是在接收了字符‘#’、并加以显示后,进
入到下一次循环判断,才能退出循环。
为了能在一接收到字符‘#’后就立即停止运行,可把程序改变如下:
#include<stdio.h>
main()
{
char ch;
while ((ch= getchar()) != ' # ' )
putchar(ch);
}
这时,用键盘输入来代替为 ch 赋初值。只有输入的字符不是‘#’时,该字符才会被显
示出来。由于把输入语句并到了条件中,于是循环体就只有一条输出语句了。
例 5-3 用 while 语句改写例 5-1。
[解] 程序编写如下:
#include<stdio.h>
main()

– 87 –
C 语言程序设计—示例与习题解析

{
int i, sum = 0;
i=1;
while (i<=100)
{
sum = sum+i;
i++;
}
printf ("1+2+3+…+100 = %d\n", sum);
}
要注意,for 语句具有按增量自动改变循环控制变量值的功能,但 while 语句却没有这
个功能。因此在 while 语句的循环体内,必须要有改变循环控制变量取值的语句。本程序中
的语句“i++;”就起到这个作用:每循环一次,它的值就加 1,从而向终值 100 靠近一步。
也正因为如此,循环才能最终停止下来。
3.直到型循环结构与 do-while 语句
在 C 语言里,用 do-while 语句来实现直到型循环结构。这种语句的一般形式和执行流
程如图 5-4 所示。

图 5-4 do-while 语句的一般形式和执行流程

从图 5-4(b)可以看出,在程序中遇到 do-while 语句时,其执行过程可分为四步。


第 1 步:执行循环体。
第 2 步:计算<表达式>。
第 3 步:判断<表达式>的计算结果。如果其值不为 0,表示循环条件成立,于是又去做
第 1 步;如果其值为 0,表示循环条件不成立,则去做第 4 步。
第 4 步:退出 do-while 循环,去做其后的语句。
要注意,do-while 语句中跟随在 while 后面的条件表达式一定要括在圆括号里,并且右
圆括号后面要有一个分号结束。否则,会打印出错信息:
Do-while statement missing ;(Do-while 语句遗失分号)
例 5-4 编写一个程序,用以记录在键盘上输入的字符个数,直到输入回车换行符时止。
[解] 程序编写如下:
– 88 –
第 5 章 循环结构的程序设计

#include<stdio.h>
main()
{
int n = −1;
char ch;
do
{
n++;
}while((c=getchar()) != ' \n ' ); /* 别忘记以分号结束 */
printf ("The character’s number of input is %d .\n", n);
}
假如在键盘上输入 abcdefg 后回车,那么会显示信息:
The character’s number of input is 7 .
程序里,之所以把变量 n 的初始值设置为−1,是为了确保计数的准确性。如果一开始就
按回车键,那么 n 就应该是 0。在程序设计中,这些细微之处是必须考虑到的。
4.break 语句和 continue 语句
在 C 语言的循环结构中,可以使用 break 和 continue 语句来实现循环体中语句执行流
程的转向。使用 break 语句,可以立即退出该循环结构,转去执行整个循环语句后面的第一
条语句。因此 break 语句通常用来根据某个条件的满足与否,提前退出循环。图 5-5(a)描
述了 break 在 while 循环结构中的作用。使用 continue 语句,可以用来跳过循环体中余下的
语句, 提前进入下一次循环条件的测试, 进而继续执行下面的循环。 图 5-5(b)
描述了 continue
在 while 循环结构中的作用。注意,图中虚线范围内是循环体语句。

图 5-5 break 和 continue 语句在循环中的作用

– 89 –
C 语言程序设计—示例与习题解析

例 5-5 阅读下面的程序,它输出什么结果?
#include<stdio.h>
main()
{
int x;
for(x = 1; x<=10; x++)
{
if (x == 5)
break;
printf ("%d\t", x);
}
printf ("\nBroke out of loop at x = %d\n", x);
}
[解] 这是一个在 for 循环结构中使用 break 语句的例子。题目的中心意思是由 x 从 1~
10 控制 printf 的执行,把当时 x 的取值打印出来。但如果 x 等于 5,那么就强行停止循环。
所以程序的执行结果是打印出如下结果:
1 2 3 4
Broke out of loop at x = 5
例 5-6 阅读下面的程序,它输出什么结果?
#include<stdio.h>
main()
{
int x, y;
for (x = 1; x<=10; x++)
{
if (x == 5)
{
y = x;
continue;
}
printf ("%d\t", x);
}
printf ("\nUsed continue to skip printing the value : %d\n", y);
}
[解] 这是一个在 for 循环结构中使用 continue 语句的例子。题目的中心意思是由 x
从 1~10 控制 printf 的执行,把当时 x 的取值打印出来。但如果 x 等于 5,那么就强行结束
这一次循环,即不去执行 printf,而进入下一次循环。所以程序的执行结果是打印出如下结
果:
1 2 3 4 6 7 8 9 10

– 90 –
第 5 章 循环结构的程序设计

Used continue to skip printing the value : 5


5.无条件转移与 goto 语句
在 C 语言里,用 goto 语句来实现程序流程的无条件转移。为此,需要用语句标号来配
合,即一方面在 goto 保留字的后面,跟随一个语句标号(之间要有一个空格),由它指明转
移的目标;另一方面在程序的某条语句前安放该语句标号(中间用冒号隔开),以标识出目的
地。
goto 语句主要有两个用途,一是与 if 语句配合,构成循环结构;一是在循环嵌套时,
能从内层循环直接跳到所需位置,而不是借用 break 语句,一层一层地往外跳转。
比如,为了求 1~100 的累加和,可以利用 if 和 goto 语句形成循环,写如下程序段来
实现:
int i = 1, sum = 0;
loop: sum = sum+i;
i++; (i <= 100)时,无条件
if (i <= 100) 转移到 loop 处执行
goto loop;
printf ("1+2+3+…+100 = %d\n", sum);
在程序段里, “loop:”与“goto loop”遥相呼应。每次在 sum 上累加 i 后,通过“i++”
改变 i 的值。只要条件“i<=100”成立,就由 goto loop 无条件转移到 loop:处进行再一次
的累加,从而达到循环的目的。
在程序中安放 goto 语句,会使程序的层次不清,结构混乱,不易阅读和理解。因此现
在编程时不提倡使用它。

5.1.3 本章难点
1.区别 while 与 do-while
while 与 do-while 的最大区别在于:前者先检验控制循环的条件,后执行循环体;后者
是先执行循环体,然后去检验控制循环的条件。因此,前者的循环体有时一次也不做,而后
者的循环体至少要做一次。
例 5-7 有如下两个程序:
(1)#include<stdio.h> (2)#include<stdio.h>
main() main()
{ {
int x, sum=0; int x, sum=0;
scanf("%d",&x); scanf("%d",&x);
while(x<=10) do
{ {
sum +=x; sum +=x;
x++; x++;
} }while(x<=10);
printf("sum=%d\n",sum); printf("sum=%d\n",sum);
– 91 –
C 语言程序设计—示例与习题解析

} }
试问,第 1 次输入 1、第 2 次输入 11 时,两个程序各输出什么?
[解] 第 1 次输入 1,程序(1)和(2)都是求 1 到 10 的累加和。因此它们的输出都
是:sum=55。第 2 次输入 11。对于程序(1)来讲,由于先检验 while 后面的条件“x<=10”
得不到满足,故该循环语句什么也不做,打印出:sum=0;对于程序(2)来讲,由于先进入
循环体,执行语句“sum +=x;” ,求 得 sum 等于 11 后,做“x++;”
,使 得 x 等于 12。这时检
验 while 后面的条件“x<=10” 得不到满足,故停止循环,打印出:sum=11。这个不对的结
果显然是由于 do-while 进入循环体前,未检验条件的缘故。这是使用 do-while 时要特别注
意的地方。
2.关于循环嵌套
C 语言中提供的三种循环:for、while、do-while,不仅自身可以嵌套,而且相互之间
也可以嵌套。
例 5-8 如下的嵌套循环,输出的结果是什么?
#include<stdio.h>
main()
{
int x, y, result = 0;
for (x=1; x<100; x++)
for (y=x; y<=100; y++)
result = result + 1;
printf ("loop’s number is %d\n", result);
}
[解] 这是 for 的嵌套循环。关于 x 的是外循环,关于 y 的是内循环。从“x=1”和“x<100”
可知,外循环总共做 99 次。相对于每一次外循环,当 x=1 时,内循环做 1~100 共 100 次;
当 x=2 时,内循环做 2~100 共 99 次;当 x=3 时,内循环做 3~100 共 98 次;如此等等。当
x=99 时,内循环要做 99~100 共 2 次。所以 result 最终计数为 5049,即程序打印出信息:
loop’s number is 99
可以把它改写成关于 while 与 for 的嵌套循环,即
#include<stdio.h>
main()
{
int x, y, result = 0;
while(x<100)
{
for (y=x; y<=100; y++)
result = result + 1;
x++;
}
printf ("loop’s number is %d\n", result);

– 92 –
第 5 章 循环结构的程序设计

}
也可以把它改写成关于 for 与 do-while 的嵌套循环,即
#include<stdio.h>
main()
{
int x, y, result = 0;
for (x=1; x<100; x++)
{
y = x;
do
{
result = result + 1;
y++;
}while(y<=100);
}
printf ("loop’s number is %d\n", result);
}
3.正确使用 break 和 continue 语句
(1)break 语句
C 语言中,break 语句可以用在两个地方:一个是用在 switch 里的 case 子句,在那里,
通过它中断语句的执行,跳出整个 switch 语句;另一个是用在循环结构的循环体中,这时,
它常与单分支的 if 语句配合,达到跳出本层循环的目的。
例 5-9 编写一个 C 语言程序,打印出如下的图形(a)和(b) :

[解] (a)是一个循环嵌套问题。外层控制打印多少行,内层控制每行打印多少个星
号和空格。由于每个循环的次数都是已知的,所以使用 for 循环嵌套来实现。程序编写如下:
#include<stdio.h>
main()
{
int x, y;
for(x=1; x<=7; x++) /* 由 x 控制打印多少行 */
{
for(y=1; y<=x; y++) /* 每一行打印的星号个数受控于 x */
{

– 93 –
C 语言程序设计—示例与习题解析

putchar( ' * ' ); /* 打印一个星号 */


putchar( ' ' ); /* 打印一个空格 */
}
putchar( ' \n ' ); /* 打印完一行后,输出一个回车换行 */
}
}
(b)的输出与(a)类同,不一样的是如果一行输出的星号多于 4 个时,就截掉不打。
这可以通过在内循环中使用 break 来实现。程序编写如下:
#include<stdio.h>
main()
{
int x, y;
for(x=1; x<=7; x++) /* 由 x 控制打印多少行 */
{
for(y=1; y<=x; y++) /* 每一行打印的星号个数受控于 x */
{
putchar( ' * ' ); /* 打印一个星号 */
putchar( ' ' ); /* 打印一个空格 */
if (y == 4) /* 如果这一行已打印了 4 个星号,则立即跳出本循环 */
break;
}
putchar( ' \n ' ); /* 打印完一行后,输出一个回车换行 */
}
}
由于 break 只能跳出本层循环,因此如果要从内循环直接跳出外循环,那么就需要在每
层循环中增加对一个标志变量的检测。比如设置标志变量 flag,循环嵌套编写成:
flag=0;
for ( … )
{
while( … )
{

if ( … ) /* 这是内循环里的 if 语句 */
{
flag = 1;
break; /* 这是内循环里的 break,它能跳出 while 循环 */
}

}

– 94 –
第 5 章 循环结构的程序设计

if (flag) /* 这是外循环里的 if 语句 */
break; /* 这是外循环里的 break ,它能跳出 for 循环 */
}
这里,while 是内循环,for 是外循环。希望在内循环里执行 if 语句、并条件成立时,
能一下跳出外循环。为此,在整个循环外将标志变量 flag 设置成 0。一旦在内循环里执行 if
语句、并且条件成立时,就把 flag 改设为 1。这样,执行内循环里的 break 后,就跳到外循
环里的 if 语句处。如果这时 flag 为 1,那么就会执行外循环里的 break,从而跳出外循环。
(2)continue 语句
在 C 语言中,continue 语句只能用在循环结构的循环体里。通常是和单分支的 if 语句
配合,达到立即进入下一轮循环的目的。
例 5-10 编写一个程序,从输入的 100 个数中,打印出其中的正数。
[解] 由于只要求打印输入的正数,所以当判定输入的是负数时,就可以利用 continue
立即进入下一次循环。程序编写如下:

在 do-while 的循环体里,共做三件事:一是往 x 里输入数据;二是在 j 里记录输入数


据的个数,用它来控制循环;三是判断刚输入的数据是正数还是负数。如果是负数,则利用
continue 进入下一次循环,如果是正数,就打印输出。由此可以看出,在 while 或 do-while
的循环体里遇到 continue 时,程序执行流程就直接转向条件检验,进入下一次循环。
如果改用 for 来实现,程序可以是:

可以看出,在 for 循环体里遇到 continue 时,程序执行流程就直接转向循环的增量部


– 95 –
C 语言程序设计—示例与习题解析

分,执行条件检验,进入下一次循环。
4.对 for 语句灵活性的理解
for 语句头部有三个表达式:<表达式 1>、<表达式 2>和<表达式 3>。这三个表达式之间
必须用分号隔开。for 语句可以有多种变体,从而大大增加了它的功能、灵活性和实用性。
(1)for 语句中的三个表达式都是可有可无的(但其间的分号绝对不能没有)。如果省略
<表达式 2>,则意味循环条件永远为真,形成了一个“无限循环”。这时,为了不成为“死循
环”,可以在循环体内安放 break 语句。这样,执行到 break 时,就会立即终止循环。由此可
以看出,for 语句不仅适用于次数已知的循环形式,也适用于根据条件进行循环的形式。另
外,如果在进入 for 循环之前,已经对循环控制变量进行了初始化,那么省略<表达式 1>是
顺理成章的事情。同样地,如果在循环体内对循环控制变量进行了修正,那么就可以省略<
表达式 3>。比如,对于例 5-1,可以改写成如下各种形式:
• 省略<表达式 1>的情形:
int i, sum=0;
i = 1;/* 代替了<表达式 1>的作用 */
for( ; i<=100; i++)
sum = sum + i;
printf("1+2+3+…+100=%d\n", sum);
• 省略<表达式 2>的情形:
int i, sum=0;
for(i = 1; ; i++)
{
if (i>100) /* 代替了<表达式 2>的作用 */
break;
sum = sum + i;
}
printf("1+2+3+…+100=%d\n", sum);
• 省略<表达式 3>的情形:
int i, sum=0;
for(i = 1; i<=100; )
{
sum = sum + i;
i++;/* 代替了<表达式 3>的作用 */
}
printf("1+2+3+…+100=%d\n", sum);
• 省略三个表达式的情形:
int i = 1, sum=0;
for( ; ; )
{
if (i>100)

– 96 –
第 5 章 循环结构的程序设计

break;
sum = sum + i;
i++;
}
printf("1+2+3+…+100=%d\n", sum);
(2)for 语句中的<表达式 1>和<表达式 3>都可以是逗号表达式。
例 5-11 编写一个程序,求 2~100 之间所有偶数之和。
[解] 程序编写如下:

这里,<表达式 1>是逗号表达式,完成对变量 x 和 sum 的初始化。<表达式 3>也是一个


逗号表达式,完成求累加和以及对循环控制变量 x 的增量运算。循环体则只是一个空语句而
已。
(3)for 语句中循环控制变量的值可以递增变化,也可以递减变化。也就是说,增量可
正可负。比如,
• 控制变量的值从 1 变到 100,增量为 1,则可以写成:
for(j=1; j<=100; j++)
• 控制变量的值从 100 变到 1,增量为−1,则可以写成:
for(j=100; j>=1; j− −)
• 控制变量的值从 7 变到 77,增量为 7,则可以写成:
for(j=7; j<=77; j+=7)
• 控制变量的值从 20 变到 2,增量为−2,则可以写成:
for(j=20; j>=2; j−=2)
• 控制变量的值按顺序变化为 2、5、8、11、14、17、20,则可以写成:
for(j=2; j<=20; j+=3)
• 控制变量的值按顺序变化为 99、88、77、66、55、44、33、22、11、0,则可以写成:
for(j=99; j>=0; j−=11)

5.2 典型示例分析
示例 1 阅读下面的程序,它总共循环多少次?其输出是什么?
#include<stdio.h>
main()
{

– 97 –
C 语言程序设计—示例与习题解析

int j;
for (j=1; j<=5; ++j)
switch ( j )
{
case 1:
printf ("\n j=1");
break;
case 2:
j=1;
case 3:
printf ("\n j=3");
j +=2;
break;
case 4:
printf ("\n j = %d", j++);
break;
}
}
[解] 该程序是由变量 j 控制的循环。每次循环根据 j 取值的不同,去做 switch 中不
同的 case 子句。当 j=1 时,做 case 1 子句,打印输出“j=1”后退出 switch。于是进入下
一次循环。当 j=2 时,做 case 2 子句,它把 j 赋值为 1。但因为没有 break 语句的阻拦,因
此继续往下做 case 3 子句。于是打印输出“j=3”、并把 j 赋值为 3 后退出 switch(注意:
printf (“\n j=3”);语句只是把“j=3”原封不动地打印出来,它不是进行赋值。而“j +=2;”
是赋值语句。由于这时 j 为 1,因此执行它后 j 取值为 3) 。退出 switch 后,回到 for 语句去
进行增量修改,即“++j” 。因此 j 变为 4,进而执行 case 4 子句。它打印出“j=4”后,把 j
的取值变为 5( “j++”的结果)。退出 switch 后,回到 for 语句又去进行增量修改。它把 j
变为 6,破坏了 for 语句的循环条件,使循环停止。所以,整个程序只循环 3 次,最终的输
出结果是:
j = 1
j = 3
j = 4
示例 2 编写一个程序,它读入整数值,遇到−1 时停止。输出所输入数据的最大值、最
小值,以及它们的累加和。
[解] 由于并不知道要循环多少次,因此应该使用 while 或 do-while 语句来编程。这
里采用 while 循环语句。程序一直循环下去(程序中用 while(1)体现出来),直到输入−1 时
停止。所以每输入一个数据(程序中被保存在变量 num 里),就要判别它是否为−1( “num ==
−1”
)。如果是,就通过 break 语句结束循环;否则,就把当前输入的数与前面输入的数比较,
分别保留大者(保存在变量 max 里)和小者(保存在变量 min 里) ,并进行累加(在变量 sum
里进行) 。输入结束后,打印输出。完整的程序编写如下:
– 98 –
第 5 章 循环结构的程序设计

#include<stdio.h>
main()
{
int num, max=0, min=0, sum=0;
while (1)
{
printf ("Please enter a integer value num=");
scanf ("%d",&num);
if (num == −1)
break; /* 因为输入了−1,强制跳出循环 */
else
{
if (num>max)
max = num;
else if (num<min)
min = num;
}
sum += num;
} /* 循环在此结束 */
printf ("The largest value of integers is = %d\n", max);
printf ("The smallest value of integers is = %d\n", min);
printf ("The sum of integers is = %d\n", sum);
}
示例 3 编写一个程序,它读入一个正数 n,输出 n 的阶乘 n!。
[解] 程序编写如下:
#include<stdio.h>
main()
{
int j, n;
long fact = 1;
while (1)
{
printf ("\nEnter a positive integer :");
scanf ("%d", &n);
if (n>0)
break;
}
for (j=1; j<=n; j++)
fact ∗= j;

– 99 –
C 语言程序设计—示例与习题解析

printf ("The factorial of %d is %ld\n", n, fact);


}
程序中,用变量 n 接收从键盘输入的数据。为了确保输入的是一个正整数,这里用了一
个 while(1)循环,并配合 break。也就是说,程序开始运行后,只有输入了一个正整数,才
会跳出此 while 循环,进行阶乘的计算。计算阶乘是通过 for 循环实现的。
示例 4 编写一个程序,它读入两个正整数 a 和 b 后,输出大于 a、小于 b 的所有偶数。
[解] 程序中,要保证输入的 a 和 b 是正整数,要保证 a>b,还要保证 b−a>2 且 a、b 之
间要有偶数。只有这样,才能根据 a 是偶数还是奇数,形成一个个偶数加以输出。整个程序
编写如下:
#include<stdio.h>
main()
{
int a, b;
while (1)
{
printf ("Enter first positive integer a = ");
scanf ("%d", &a);
printf ("Enter second positive integer b = ");
scanf ("%d", &b);
if (a<0 || b<0 || !(b>a)) /* 第 1 个 if 语句 */
{
printf ("Input data is not correct !\n");
break; /* 由于输入数据错,退出循环,结束程序执行 */
}
if (b–a <= 2 && a%2 == 0) /* 第 2 个 if 语句 */
{
printf ("There is not even between a and b.\n");
break; /* 由于 a 与 b 之间不可能有偶数,退出循环,结束程序执行 */
}
if (a%2 == 0) /* 第 3 个 if 语句 */
a +=2;
else
a++;
for ( ; a<b; a += 2)
printf ("%d ", a);
break; /* 这个 break 不能丢,否则 while 成为死循环 */
} /* while 循环到此结束 */
}
程序中的第 1 个 if 语句,是检验输入的 a 和 b 是正整数以及 a>b 的第一关;程序中的
– 100 –
第 5 章 循环结构的程序设计

第 2 个 if 语句,是检验 b−a>2 且 a、b 之间有偶数的第二关。只有闯过这两关,才谈得上输


出 a、b 间的偶数问题。程序中的第 3 个 if 语句是根据 a 是偶数还是奇数,来形成下面 for
循环里要输出的第 1 个偶数。实际上,它就是 for 循环里的<表达式 1>(起到初始化的作用)。
正因为如此,for 循环里省略了<表达式 1>。
示例 5 编写一个程序,用循环结构打印出如下结果。

[解] 这里,表头是一部分,它用一条 printf ("A\tA+2\tA+4\tA+6\n"); 语句就可


以实现。仔细观察表中的数据后可以看出:每行的数据都是在它前面数据基础上加 2 得到;
每列的数据都是在它上面数据基础上加 3 得到。这是非常规律的变化,因此可以用循环来实
现。整个程序编写如下:
#include<stdio.h>
main()
{
int x, j;
printf ("A\tA+2\tA+4\tA+6\n"); /* 打印出表头 */
for (x=3; x<=15; x += 3) /* 由 x 控制总共要打印的行数 */
{
for (j=x; j<=x+6; j +=2) /* 由 j 控制每行要打印的数据个数 */
printf ("%d\t", j); /* 打印出一行 */
printf ("\n"); /* 回车换行进到下一行 */
}
}
示例 6 求以下算式的近似值:

1+ 1 + 1 + 1 ++ 1
2 3 4 n
要求至少累加到 1/n 不大于 0.00984 为止。输出循环次数和累加和。
[解] 这是典型的当型或直到型循环,只要 1/n 大于 0.00984,循环就进行下去。因此
可以用 while 或 do-while 语句来实现。本程序采用 do-while 语句。程序编写如下:
#include<stdio.h>
main()
{
float x, sum = 0;
int j = 1;
do
{

– 101 –
C 语言程序设计—示例与习题解析

x = 1.0/j; /* 在 x 里不断形成 1/n */


sum += x;
j++;
}while(x>0.00984);
printf ("Loop ' s number is %d\n", j);
printf ("sum = %f\n", sum);
}
运行程序,输出结果是:
Loop’s number is 103(循环次数是 103)
sum = 5.207083
这里要注意在 x 里形成 1/n 的方法。“/”运算符在 C 语言里有两种含义:如果分子、分
母都是整数,那么它表示整除,其结果是舍弃余数后所得的商值;只有当分子、分母中至少
有一个为实数时,才表示一般的除法。这里,x 是 float 型的变量。如果把 x = 1.0/j; 中的
1.0/j 改写为 1/j,那么表示先进行整除,然后将结果自动转换成 float 型。这样一来就会出
现问题:第 1 次直接进入循环体。由于 1/1 等于 1,自动转换成 float 型后,x 等于 1.000000,
sum 也等于 1.000000。j++后,j 变为 2。这时 x>0.00984,进入第 2 次循环。由于 1/2 等于 0,
自动转换成 float 型后,x 等于 0.000000,累加到 sum 上,使其仍保持为 1.000000。j++后,
j 变为 2。这时 x<0.00984,退出循环。于是打印输出:
Loop’s number is 3
sum = 1.000000
这显然是不合适的结果。所以,在程序设计时,必须注意这些微小的地方,以避免不必
要的错误出现。
示例 7 编写一个程序,它打印出个位数是 6、且能被 3 整除的所有三位正整数及其个
数。要求一行打印 8 个数据。
[解] 题目中要求打印的是三位数,且个位数必须是 6。因此可以采用 for 循环,它的
循环控制变量从 106 开始,直到 996 为止,增量为 10(因为要保证个位数是 6)
。由于还要打
印这种数据的总个数,因此需要设置一个用于计数的变量,比如是 count。发现一个这样的
数,它就加 1。为了满足一行打印 8 个数据要求,可以利用计数器 count,让这样的数一个个
地打印下去。如果 count%8==0,就打印一个回车换行。具体程序如下:
#include<stdio.h>
main()
{
int count=0, j; /* count 是计数器,j 是循环控制变量 */
for (j = 106; j<=996; j +=10)
{
if (j%3 != 0) /* 不满足条件,立即进入下一次循环 */
continue;
else
{

– 102 –
第 5 章 循环结构的程序设计

count++; /* 既然是一个满足条件的数,count 就加 1 */
printf ("%d ", j);
if (count%8 == 0) /* 需要打印回车换行吗? */
printf ("\n");
}
}
printf ("The count is %d\n", count);
}
运行此程序,输出结果如下图所示。

示例 8 编写一个程序,它接收用户从键盘上输入的等差数列的首项(a) 、公差(d)和
项数(n),输出它的和。
[解] 从初等数学可知,等差数列从第 2 项起,每一项等于它前一项加上公差。所以求
数列的和,实际上就是一个循环的问题。完整的程序如下:
#include<stdio.h>
main()
{
int a, d, n, j, sum;
printf ("Please enter three integers:\n");
scanf ("%d%d%d", &a, &d, &n);
for (j=1, sum=0; j<=n; j++)
sum += a+(j−1)∗d;
printf ("a=%d, d=%d, n=%d, sum=%d\n", a, d, n, sum);
}
这里要注意,进行累加的变量 sum 初始时必须取值为 0,不给它赋初值,运行时可能会
产生错误。另外,往 sum 上累加时,第 1 个加的是首项,它与公差没有关系。从第 2 项开始,
每一项才等于它前一项加上公差。因此完成累加的语句应该如程序中所示,即
sum += a+(j−1)∗d;
而不应该是:
sum += a+ j∗d;

5.3 课外习题、答案与解析

– 103 –
C 语言程序设计—示例与习题解析

5.3.1 课外习题

一、单选题

1.以下关于 break 语句的描述,只有( )是正确的。


A.在循环语句中必须使用 break 语句。
B.break 语句只能用于 switch 语句中。
C.在循环语句中可以根据需要使用 break 语句。
D.break 语句可以强制跳出所有循环。
2.若有 int x, y;,执行程序段:
for (x=1, y=1; y<50; y++)
{
if (x>=10)
break;
if (x%2 == 1)
{
x += 5;
continue;
}
x −= 3;
}
变量 x 的值最终为( ) 。
A.11 B.12 C.13 D.10
3.若有 int a=1,b=10;,执行程序段:
do
{
b −= a;
a++;
}while (b− − <0 );
后,变量 a 的值是( ) 。
A.4 B.2 C.1 D.3
4.若有 int x, y;,执行程序段:
for (x=5; x; x− −)
for (y=0; y<4; y++)
{ … …} /* 循环体 */
则其内循环体共执行( )次。
A.24 B.28 C.30 D.20
5.若有 int x;,执行程序段:
for (x=1; x<=100; x++)

– 104 –
第 5 章 循环结构的程序设计

if (++x%2 == 0)
if (++x%3 == 0)
if (++x%5 == 0)
printf ("%d,", x);
输出的结果是( ) 。
A.35,65,95 B.25,55,85
C.没有满足条件的数据 D.29,59,89
6.若有 int x, y;,执行程序段:
for (x=1; x<5; x +=2)
{
for (y=2; y<5; y++)
printf ("%2d", x∗y);
if (y == 5)
printf ("\n");
}
输出的结果是( )。
A.2 3 4 B.2 3 4 5 C.2 3 4 D.2 3 4 6 9 12
6 9 12 6 9 12 15 6 9 12
10 15 20
7.若有 int x=3;,执行程序段:
do
{
printf ("%3d",x −= 2);
}while (!(− −x));
输出的结果是( ) 。
A.1 3 B.1 −1 C.1 −2 D.1 −3
8.若有 int x=23;,执行程序段:
do
{
printf ("%2d", x− −);
}while (!x);
输出结果是( ) 。
A.21 B.23 C.22 D.24
9.若有 int a=100, b;,则
for (b=100; a != b; ++a, b++)
printf ("- * - * -\n");
则正确的判断是( )。
A.循环体只执行一次 B.是一个死循环
C.循环体一次也不做 D.输出:- * - * -
– 105 –
C 语言程序设计—示例与习题解析

10.以下不正确的叙述是( ) 。
A.使用 while 或 do-while 时,循环控制变量的初始化应安排在循环语句前。
B.while 循环是先判断条件,然后执行循环体。
C.do-while 和 for 循环都是先做一次循环体,然后判断条件。
D.for、while、do-while 的循环体都可以是空语句。

– 106 –
第 5 章 循环结构的程序设计

二、填空题

1.程序三种最基本的结构是 。
2. 循环结构在条件为真时,反复执行一条或一组语句。
3.一组语句循环执行指定的次数,这种循环称为 循环结构。
4.执行循环结构或 switch 结构中的 语句,能够立即退出该结构。
5.循环语句 for (x=0; x != 123; ) scanf ("%d", &x); 在 时被终止。
6.设 int x=5; ,则循环语句 while (x>=1) x− −; 执行后,x 的值是 。
7.执行程序段:
int j=0, sum=1;
do
{
sum += j++;
}while (j<6);
printf ("%d\n",sum);
最终打印输出的结果是 。
8.若有 int x=10;,则执行下面的循环:
while (x>7)
{
x− −;
printf ("%d,", x);
}
printf ("\n");
后,打印输出的结果是 。
9.若有 int x, y, z;,则执行下面的 for 循环:
for (x=0, y=10; x<=y; x++, y− −)
z = x + y;
后,最终变量 z 的值为 。
10.C 语言中 else 总是与 的 if 配对。

三、是非判断题(在括号内打“√”或“×”)

1.break 语句只能用在循环结构的循环体中。( )
2.continue 语句只能用在循环结构的循环体中。
( )
3.for 语句只适用于描述循环次数一定的循环结构。 ( )
4.while 语句的循环体可能一次也不做。( )
5.如果循环体里要包含多条语句,那么就必须用花括号将它们括起,构成一个复合语
句。( )
6.可以用如下的代码:
n = 1;
– 107 –
C 语言程序设计—示例与习题解析

while (n < 10)


printf ("%d ", n++);
打印出 1 到 10 的十个整数值。
( )
7.可以用如下的代码:
for (i=1; i != 10; i +=2)
printf ("%d\n", i);
打印出 1、3、5、7、9。( )
8.可以用如下的代码:
for (x=20; x>=−10; x −=6)
printf ("%d, ", x);
打印出 20, 14, 8, 2,−4, −10。
( )
9.在循环体中使用 break,只能跳出本层循环。 ( )
10.在 for 循环中,如果三个表达式(<表达式 1>、<表达式 2>和<表达式 3>)都省略,
那么它们之间的分号就可以省略。 ( )

四、程序阅读题

1.阅读下面的程序,a 和 b 的最终取值是什么?
#include<stdio.h>
main()
{
int a, b;
for (a=1, b=1; a<=100; a++)
{
if (b>=20)
break;
if (b%3 == 1)
{
b +=3;
printf ("a=%d b=%d ", a, b);
continue;
}
b −= 5;
printf ("a=%d b=%d ", a, b);
}
printf ("\n");
}
2.阅读下面的程序:
#include<stdio.h>
main()

– 108 –
第 5 章 循环结构的程序设计

{
int t, count;
for (t=0; t<100; ++t)
{
count=1;
for ( ; ; )
{
printf ("%d ", count);
count++;
if (count == 10)
break;
}
}
}
3.阅读下面的程序:
#include<stdio.h>
main()
{
int x=1, total=0, y;
while (x<=10)
{
y = x*x;
printf ("%d ", y);
total += y;
++x;
}
printf("\nTotal is %d\n", total);
}
4.阅读下面的程序:
#include<stdio.h>
main()
{
int count;
while (count<=10)
{
printf ("%s\n", count%2 ?"* * * *":"# # # # # # #");
++count;
}
}

– 109 –
C 语言程序设计—示例与习题解析

运行后打印出什么结果?
5.阅读下面的程序:
#include<stdio.h>
main()
{
int row=10, col;
while (row>1)
{
col=1;
while (col<=10)
{
printf ("%s", row%2 ?"<":">");
++col;
}
− −row;
printf ("\n");
}
}
运行后打印出什么结果?
6.阅读下面的程序:
#include<stdio.h>
main()
{
int j, k, x,y;
printf ("Enter integers in the range 1~20:\n");
scanf ("%d%d", &x, &y);
for (j=1; j<=y; j++)
{
for (k=1; j<=x; k++)
printf ("@");
printf ("\n");
}
}
运行后,最终输出什么?
7.阅读下面的程序:
#include<stdio.h>
main()
{
int x;

– 110 –
第 5 章 循环结构的程序设计

scanf ("%d", &x);


if (x>=4)
while (x− −)
;
printf ("%d", ++x);
}
如果输入为 5,则其输出为什么?

五、程序设计题

1.编写一个程序,求 1~100 的奇数和偶数之和,并加以输出。


2.编写一个程序,求出 10~1000 的能够同时被 2、3、7 整除的整数。输出这些数及其
个数。
3.编写一个 C 语言程序,打印出如下的图形(a)、(b)和(c)。

4.老师说:今年我年龄的平方加上夫人的年龄,等于 1053;我夫人年龄的平方加上我
的年龄,等于 873;我们现在的年龄都不超过 40 岁。试编写一个程序,求出老师和他夫人各
自的年龄。
5.利用下面的公式,求π的近似值:
π = 1− 1 + 1 − 1 +± 1 
4 3 5 7 n
循环直到 1/n 的绝对值小于 0.0001 时止。
6.编写一个程序,求 100~999 范围内所有的水仙花数。水仙花数即是满足如下条件的
三位数:它的个位、十位和百位数字的立方和,正好等于该数。

5.3.2 答案与解析

一、单选题

1.C
2.D 注意:第 1 次循环时,x 是 1,是奇数,因此执行 x += 5;。x 变为 6。进入第 2
次循环,x 是 6,是偶数,因此执行 x −= 3;。x 变为 3。进入第 3 次循环,x 是 3,是奇数,
因此执行 x += 5;,x 变为 8。进入第 4 次循环,x 是 8,是偶数,因此执行 x −= 3;,x 变为
5。进入第 5 次循环,x 是 5,是奇数,因此执行 x += 5;,x 变为 10。进入第 6 次循环,x
是 10,满足条件 x>=10,故执行 break,退出循环。因此 x 的值最终为 10。
– 111 –
C 语言程序设计—示例与习题解析

3.B 注意:先做循环体:b 由原先的 10 变为 9,a 由原先的 1 变为 2。这时测试循环


控制条件不成立,于是循环结束。所以 a 的最终取值是 2。
4.D 注意:外循环共执行 5 次(x 从 5 变到 1),相对于每次外循环,内循环要执行 4
次(y 从 0 变到 3)。所以内循环总共执行 20 次。
5.C 注意:要求一个数加 1 后能被 2 除尽、再加 1 后能被 3 除尽、再加 1 后能被 5
除尽,这是不可能的。其实,用题目里所给的三组答案去试一下,知道它们都不符合要
求。
6.A 7.C 8.B 9.C 10.C

二、填空题

1.顺序结构、选择结构和循环结构 2.当型或直到型 3.次数型


4.Break 5.x 等于 123 6.0
7.16 注意:语句 sum += j++;,相当于做两件事,先做 sum=sum+j,再做 j=j+1。由
于 sum 的初值是 1,因此累加的序列是:1+1+2+3+4+5,后面的 5 项是由 j 的变化所致。故 sum
里的最终结果是 16。
8.9,8,7 注意:由于 x 的初始值是 10,因此可以进入循环,且第 1 次做 x− −,使 x
成为 9。打印后第 2 次进入循环体。第 2 次做 x− −,使 x 成为 8。打印后第 3 次进入循环体。
第 3 次做 x− −,使 x 成为 7。这时条件变为不成立,循环结束。
9.10 10.其之前最近、且不带 else

三、是非判断题

1.× 2.√ 3.× 4.√ 5.√


6.× 注意:漏掉了 10。条件应该改为 n<=10 才对。
7.× 注意:不行,是死循环,i 的取值达不到 10。
8.√ 9.√ 10.×

四、程序阅读题

1.答:程序输出如下:
a=1 b=4 a=2 b=7 a=3 b=10 a=4 b=13 a=5 b=16 a=6 b=19 a=7 b=22
由于 b 的初值是 1,进入循环体后,满足 b%3 == 1 的条件,所以做 b +=3;,使 b 变为 4。
打印输出后,b 仍满足 b%3 == 1 的条件,所以做 b +=3;,使 b 变为 7。由于总是对 b 做加 3
的操作,故 b 总是满足 b%3 == 1 条件。直到 b 从 19 加 3 变为 22 后,条件满足 b>=20,因此
由 break 强制退出循环。
2.答:该程序在屏幕上连续显示 100 次 1~10 的值。这是因为一方面,外循环的循环
控制变量 t 是从 0 变到 99,共 100 次,而每次外循环都把 count 重新设置为 1,所以打印总
是从 1 开始。另一方面,每进入一次内循环,循环总是当 count 成为 10 时停止。
3.答:程序的功能是求 1 到 10 的平方和。程序的输出是:
1 4 9 16 25 36 49 64 81 100
Total is 385
– 112 –
第 5 章 循环结构的程序设计

4.答:输出结果如下图所示。

5.答:输出结果如下图所示。

6.答:输出结果如下图所示。

可以看出,输入的第 1 个数(x)决定了输出时每行打印的字符个数,输入的第 2 个数
(y)决定了打印的行数。上图是打印 8 行,每行打印 20 个字符‘@’ 。
7.答:最后输出为 0。这是因为在 while 循环里,最后判别到 x 为 0 时,循环停止。但
在 x 上还要做“− −”操作,从而退出循环后 x 的真正取值是−1。然而在打印时,又先对 x
进行“++”操作。所以最后输出为 0。

五、程序设计题

1.答:程序编写如下:
#include<stdio.h>
main()
{
int j, odd=0, even=0;
for (j=1; j<=100; j++)
if (j%2 == 0)
even += j; /* 是偶数,在 even 上累加 */

– 113 –
C 语言程序设计—示例与习题解析

else
odd += j; /* 是奇数,在 odd 上累加 */
printf ("Odd = %d, even = %d\n", odd, even);
}
2.答:程序编写如下:
#include<stdio.h>
main()
{
int x, count=0;
for (x=10; x<=1000; x++)
if (x%2 == 0)
if (x%3 == 0)
if (x%7 == 0)
{
printf ("%4d", x);
count++;
if (count%7 == 0)
printf ("\n");
}
printf ("\ncount = %d\n", count);
}
这里,使用了三层 if 的嵌套结构。其实,使用一个 if 语句也是可以的,即把三个条件合并
成为下面的一个:
if (x%2 == 0 && x%3 == 0 && x%7 == 0)
这样处理后,程序显得更为简练。另外,在程序中还借用 count 的计数值,来控制每行打印
数据的个数。下面是运行后显示的结果图。左边是用 printf ("%4d", x); 语句打印出来
的,也就是在输出时,借用格式控制串中转换说明“%4d”中的 4,实现输出数据的对齐。如
果采用 printf ("%d", x); 语句打印,其结果如右图所示,输出的数据无法对齐。

3.答:(a)的程序如下:
#include<stdio.h>
main()
{
int x, y;
for (x=10; x>=1; x− −)

– 114 –
第 5 章 循环结构的程序设计

{
for (y=1; y<=x; y++)
printf ("*"); /* 打印一个*号和一个空格 */
printf ("\n");
}
}
(b)的程序如下:
#include<stdio.h>
main()
{
int x, y;
for (x=1; x<=10; x++)
{
for (y=1; y<=10; y++)
if (y<=10−x)
printf (" "); /* 打两个空格 */
else
printf ("*"); /* 打印一个*号和一个空格 */
printf ("\n");
}
}
(c)的程序如下:
#include<stdio.h>
main()
{
int x, y;
for (x=1; x<=10; x++)
{
for (y=1; y<=10; y++)
if (y<=x−1)
printf (" "); /* 打两个空格 */
else
printf ("* "); /* 打印一个*号和一个空格 */
printf ("\n");
}
}
4.答:程序编写如下:
#include<stdio.h>
main()

– 115 –
C 语言程序设计—示例与习题解析

{
int x, y;
for (x=20; x<=40; x++)
{
y=1053−x*x;
if (y*y+x == 873)
break;
}
printf ("The teacher’s age = %d\n", x);
printf ("His wife’s age = %d\n",y);
}
程序中,x 代表老师的年龄,y 代表老师夫人的年龄。考虑到结婚年龄的限制,让 x 控
制的循环从 20 开始,求出一个解后就退出循环。结果是老师 32 岁,夫人 29 岁。
5.答:程序编写如下:
#include<stdio.h>
main()
{
float pi=0.0, n=1.0, y=1.0, x, z;
do
{
x = y/n;
pi += x;
y ∗= −1.0;
n += 2;
z = x<0 ? –x : x;
}while(z>0.000001);
printf ("π= %f\n", 4∗pi);
}
程序中,不断在 x 里形成 1/1、−1/3、1/5、−1/7 等,并将其累加到 pi 上去。由 y∗=−1.0;
构成每项的正负号。用变量 z 来控制循环的次数(不能直接用 x 来控制,因为它可能是负的。
z 里实际上是 x 的绝对值)。运行结束,输出结果:π=3.141598。
6.答:程序编写如下:
#include<stdio.h>
main()
{
int x, y=1, nf, ns, nt;
for (x=100; x<=999; x++)
{
nf=x−x/10∗10; /* 在 nf 里得到三位数的个位数 */

– 116 –
第 5 章 循环结构的程序设计

ns=(x−x/100∗100)/10;/* 在 ns 里得到三位数的十位数 */
nt=x/100; /* 在 nt 里得到三位数的百位数 */
nf=nf∗nf∗nf; /* 在 nf 里形成个位数的立方 */
ns=ns∗ns∗ns; /* 在 ns 里形成十位数的立方 */
nt=nt∗nt∗nt; /* 在 nt 里形成百位数的立方 */
if (nf+ns+nt == x) /* 是一个水仙花数 */
{
printf ("%d", x);
}
printf ("\n");
}
}

– 117 –
第6章 函数与变量存储类型

6.1 本章基本概念及知识点
本章涉及以下 6 个方面的内容:
• 函数的概念和定义方法;
• 函数调用中数据的值传递方式;
• 函数的嵌套调用与递归调用;
• C 语言提供的库函数;
• 变量的存储类型;
• 变量的生存期与作用域。

6.1.1 基本概念
1.函数
在程序设计时,一个大程序通常都分成若干个子模块,每一个实现一个特定的功能。这
种程序模块称为“函数” 。在 C 语言中,只有名为“main”的主函数能独立运行,其他函数只
能通过被调用而得到运行。
2.主调函数与被调函数
函数的使用是通过函数调用来实现的。调用其他函数的函数称为“主调函数”;被别的
函数调用的函数称为“被调函数” 。在 C 语言中,main 一定是主调函数。其他函数在调用别
的函数时,是主调函数,在被别的函数调用时,是被调函数。
3.函数间的数据传递
在函数调用时,主调函数可以将数据传给被调函数使用,被调函数也可以把加工结果返
回,供主调函数使用。这一过程统称为“函数间的数据传递”。
4.形式参数与实际参数
被调函数中,接收主调函数传递过来的数据的那些变量,称为“形式参数”,简称“形
参”。主调函数中,存放传递给被调函数的数据的变量,称为“实际参数”,简称“实参”。主
调函数与被调函数两者间的实参与形参,在数量上和类型上都必须一致。否则会产生语法错
误。比如,实参个数多于形参时,会给出出错信息:
Extra parameter in call to xxxx(调用 xxxx 函数时,出现多余的参数)

– 117 –
C 语言程序设计—示例与习题解析

如果形参个数多于实参时,会给出出错信息:
Too few parameters in call to 'xxxx'
(调 用'xxxx'时,参数不够)
5.有参函数与无参函数
被调用时不接收调用者数据的函数,称为“无参函数”;被调用时要接收从调用者处传
递来数据的函数,称为“有参函数”
6.有返回值的函数与无返回值的函数
如果通过函数调用,被调函数能返回给主调函数一个确定的值,那么就称这个被调函数
是一个“有返回值的函数” ;否则就称这个被调函数是一个“无返回值的函数”。函数的返回
值是通过在函数中安排 return 语句带回给主调函数的。
7.值传递方式
如果主调函数是把实参的数值传递给被调函数中相应的形参,那么这种参数的传递方式
称为“值传递方式” 。
8.单向传递
在采用值传递方式时,被调函数形参接受的是主调函数实参具体的值。主调实参与被调
形参使用的不是同一个变量。因此被调函数在程序中对形参的加工,不会反映到主调函数实
参里。所以,值传递方式是一种“单向传递”的方式。
9.函数的嵌套调用
在调用一个函数的过程中,被调函数又调用了另一个函数。这种函数间的调用关系称为
“函数的嵌套调用” 。
10.函数的递归调用与递归函数
如果在函数调用过程中,又直接或间接地对自己进行了调用,这种函数间的调用关系称
为“函数的递归调用” 。这种函数被称为“递归函数” 。
11.直接递归与间接递归
如果一个函数在其函数体内调用了自己,那么对于主调函数,这种递归称为“直接递归”;
如果一个被调函数在其函数体内调用了它的主调函数,那么对于主调函数,这种递归称为“间
接递归”。
12.内部函数与外部函数
C 语言中,一个源程序为一个编译单位。只允许在本编译单位里使用的函数,称为“内
部函数” 。为了标明一个函数是内部的,必须在该函数定义的最前面,冠以保留字 static。
正因如此,内部函数也称为“静态函数” 。允许被其他编译单位中的函数调用的函数,称为“外
部函数”。为了标明一个函数是外部的,应该在该函数定义的最前面,冠以保留字 extern。
如果在定义函数时,既没有在函数的前面冠以 static,也没有冠以 extern,则 C 语言默认其
为外部函数。
13.函数原型
使用用户自己定义的函数时,在主调函数中应该对此函数做一说明(注意不是“定义”!),
向编译程序提供该函数的名称,返回值类型,形参类型、个数以及顺序等信息,以便对调用
的合法性进行核对。这种对函数的说明,就称为“函数原型”。
14.变量的存储类型
在定义一个变量时,指明其存放在内存什么区域里的信息,称为“变量的存储类型”。
– 118 –
第 6 章 函数与变量存储类型

在 C 语言里,变量可以有 auto(自动型)
、register(寄存器型)
、static(静态型)和 extern
(外部型)等四种存储类型。
15.内部变量与外部变量
任何在函数(或复合语句)内定义的变量,称为“内部变量”;任何在函数之外定义的
变量,称为“外部变量” 。
16.变量的生存期
把为一个变量分配内存单元开始、到收回分配给它的内存单元时止的这一时间段称为
“变量的生存期”。因此,只要一个变量还占据着分配给它的存储单元,它就一直存在。
17.变量的作用域
在变量的生存期内,可以使用这个变量的程序段,称为该“变量的作用域”。因此,只
能在一个变量的作用域里使用这个变量,超出其作用域,就不能使用这个变量了。
18.局部变量与全局变量
生存期只限于某个函数(或复合语句)的变量,称为“局部变量”;生存期从定义开始
一直保持到程序结束的变量,称为“全局变量” 。

6.1.2 本章重点
1.函数的定义
在 C 语言中,函数定义的一般形式是:
<函数类型> <函数名> (<形式参数表>)
{
<数据说明>
<语句>
}
其中,
• <函数类型>:规定了该函数返回值的数据类型,它可以是第 2 章里所涉及的基本数据
类型(char、int、long、float、double 等),以及后面要讲的指针型。如果该函数是无返
回值的,那么这里应该是 void。
• <函数名>:是一个合法的 C 语言标识符。在同一个编译单位内,不能有重名函数。
• <形式参数表>:无论所定义的是有参函数还是无参函数,<函数名>后必须带有一个圆
括号。如果是有参函数,那么在圆括号内逐一书写每一个参数的数据类型以及名称,中间用
逗号隔开。参数的数据类型既可以是基本数据类型,也可以是指针型、数组型等。
• { }:花括号内是函数体,它由<数据说明>和<语句>组成。<语句>描述了函数所要完
成的功能,<数据说明>里给出实现函数功能时需要用到的变量。
关于函数的定义,要注意如下几点。
(1)定义一个内部函数或外部函数时,保留字 static 或 extern 应冠在<函数类型>之前。
也就是说,它们应该出现在函数头的最前端。
(2)如果定义一个函数时,省略了<函数类型>,那么 C 语言将默认它的返回值类型是 int
的。
(3)出现在<形式参数表>里的形参,以及出现在函数体<数据说明>里的变量,都是局部
– 119 –
C 语言程序设计—示例与习题解析

变量。它们的作用域只限本函数,出了本函数,就全部失效了。
(4)除了 void 类型外,所有函数都返回一个值,这个值由 return 语句明确给出。
例 6-1 定义一个函数,它完成求三个实数中最大者的工作。
[解] 根据题意,该函数应该设三个实型形参,比如可以是 float x、float y 和 float
z。接收从主调函数传递过来的需要求出最大值的三个实型实参。在函数体中,对这三个形参
做比较,求出其中的最大值。比如,比较可以这样进行:
float max; /* max 是一个工作变量 */
max = x>y ? x : y; /* max 里暂时存放 x、y 中的大者 */
max = max>z ? max : z; /* max 里存放 x、y、z 中的大者 */
把函数起名为 maxnum,返回值类型应该是 float 的。则完整的函数定义可以是:
float maxnum (float x, float y, float z)
{
float max;
max = x>y ? x : y;
max = max>z ? max : z;
return max;
}
如果把该函数设计成是没有返回值的,那么可以是:
void maxnum (float x, float y, float z)
{
float max;
max = x>y ? x : y;
max = max>z ? max : z;
printf ("The maxinum value is %5.2f\n", max);
}
2.函数的调用
C 语言中,函数调用的一般形式是:
<函数名>(<实际参数表>)
其中,
• <函数名>:是被调函数的名称。
• <实际参数表>:列出调用时所需的实参。这里,实参的个数、类型以及顺序,都应与
被调函数定义中<形式参数表>的设置相同,各实参之间用逗号隔开。如果被调用的是一个无
参函数,那么调用时圆括号内为空,但圆括号不能丢弃不要。
关于函数的调用,要注意如下几点。
(1)如果调用的是一个没有返回值的函数,则采用语句形式调用,最后要有一个语句结
束符(分号) 。
(2)如果调用的是一个有返回值的函数,则这种调用可以参加表达式计算。
(3)在一个编译单位里,若被调函数的定义在主调函数之后,出现了先使用后定义的情
形,那么在主调函数里应该先对被调函数进行说明(即给出被调函数的原型)。但如果被调函
– 120 –
第 6 章 函数与变量存储类型

数是 char 或 int 型的,可以省略这一步。这种安排,与一个变量要先说明后使用的原则是相


同的。
某个函数的函数原型应出现在主调函数函数体的<数据说明>部分。一般格式为
<函数类型> <函数名> (<形式参数类型表>) ;
它其实与函数定义中的函数头基本相同。不同之处,一是这里只列出形参的类型,不用列出
形参的名字;二是由于它是一个语句,因此最后要有一个分号做为语句的结束。
(4)如果被调函数与主调函数不在一个编译单位里,那么,一方面应该把被调函数定义
成外部函数,即定义时在它的函数头最前面冠以保留字“extern”;另一方面,在主调函数函
数体的<数据说明>里列出它的函数原型时,最前面也冠以保留字“extern” ,以便使它们能够
遥相呼应。
例 6-2 编写一个 main 函数,它从键盘接收三个实数,然后调用例 6-1 中的 maxnum 函
数,得到这三个数中的最大值。
[解] 如果把 maxnum 函数设计成是有返回值的,且在 main 函数的前面给出了它的定义,
那么满足先定义后使用的原则,因此,main 函数可以设计如下:
main()
{
float i, j, k, large;
printf ("Enter three real numbers:\n");
scanf ("%f%f%f", &i, &j, &k);
large = maxnum (i, j, k); /* 调用 maxnum 函数,把返回值赋给变量 large */
printf ("The maxinum value is %5.2f\n", large);
}
在 main 函数里,实参 i、j、k 接收键盘的输入,并把得到的值传递给 maxnum 函数里的
形参 x、y、z。x、y、z 就以这些值进行比较,把最大者赋给 max,然后通过 return 语句返
回给 main 函数的变量 large。
在 main 函数里,也可以不设置变量 large,直接把对 maxnum 函数的调用,做为一个表
达式放在 printf 语句里,如下所示:
main()
{
float i, j, k;
printf ("Enter three real numbers:\n");
scanf ("%f%f%f", &i, &j, &k);
printf ("The maxinum value is %5.2f\n", maxnum (i, j, k));
}
假如把 maxnum 函数设计成是没有返回值的(见例 6-1)
,且在 main 函数的前面给出了它
的定义,那么 main 函数可以设计如下:
main()
{
float i, j, k;

– 121 –
C 语言程序设计—示例与习题解析

printf ("Enter three real numbers:\n");


scanf ("%f%f%f", &i, &j, &k);
maxnum (i, j, k); /* 这是 maxnum 函数的语句形式 */
}
这时,最大值的输出,由被调函数给出。
如果 main 和 maxnum 在一个编译单位里,maxnum 不是 char 或 int 型的,其定义在 main
之后,main 里没有给出 maxnum 的原型,如下所示:
main()
{
float i, j, k;
printf ("Enter three real numbers:\n");
scanf ("%f%f%f", &i, &j, &k);
maxnum (i, j, k); /* main 在此先调用 maxnum 函数 */
}
void maxnum (float x, float y, float z) /* maxnum 函数的定义在此才给出 */
{
float max;
max = x>y ? x : y;
max = max>z ? max : z;
printf ("The maxinum value is %5.2f\n", max);
}
这就出现了先使用后定义的情形。C 编译系统会给出出错信息:
Type mismatch in redeclaration (重定义类型不匹配)
为了避免先使用后定义这类错误,必须在主调函数里给出被调函数的函数原型。下面是正确
的安排形式:
main()
{
float i, j, k;
void maxnum (float, float, float); /* 函数 maxnum 的说明(原型) */
printf ("Enter three real numbers:\n");
scanf ("%f%f%f", &i, &j, &k);
maxnum (i, j, k); /* main 在此先调用 maxnum 函数 */
}
void maxnum (float x, float y, float z) /* maxnum 函数的定义在此才给出 */
float max;
max = x>y ? x : y;
max = max>z ? max : z;
printf ("The maxinum value is %5.2f\n", max);
}

– 122 –
第 6 章 函数与变量存储类型

注意,也可以在程序一开始处统一给出被调函数的原型,而不是在主调函数里给出。这
样做的优点是避免在程序中多个地方出现对同一个被调函数的多次说明。
3.变量的存储类型说明
变量的存储类型说明规定一个变量存储在内存的什么
区域。内存中供用户使用的存储空间分为三个部分,如图
6-1 所示。
其中,静态和动态存储区是存放数据的场所。动态存
储区是一个可以重复使用的区域:需要时分配,使用完毕,
立即收回。静态存储区是一个永久性使用的区域:只要分
配了,就一直占用,直到程序运行结束时止。
图 6-1 内存用户区的三个部分
C 语言中用于变量存储类型说明的保留字有 4 个:
auto、register、static、extern。
• auto(自动变量) :它表明应在动态存储区里为该变量分配存储单元,适用于函数(或
复合语句)中说明的局部变量(包括函数的形参) 。具体做法是遇到变量说明,就分配存储单
元;退出变量作用域,就立即释放占用的存储单元,从而提高内存的利用率。
由于程序设计中大量使用的都是 auto 型变量,因此该保留字可以省略。比如,在某函
数中有变量说明:
auto int x, y;
它完全等价于:
int x, y;
• register(寄存器变量):它表明要把这种变量存储在 CPU 的寄存器里,而不是通过
内存访问来确定和修改其值。通常总是把程序中使用频度高的变量说明为是寄存器型的。比
如,有如下名为 pw 的函数定义:
int pw(int m, register int ex)
{
register int temp;
temp = 1;
for ( ; ex; ex− −)
temp += m;
return temp;
}
这里,ex 和 temp 都被说明为是寄存器型的。要注意的是,只有整型或字符型的局部变
量,才能说明为是寄存器型的。另外,如今的编译系统都能识别变量的使用频度,从而自动
地判定把哪一个变量存放在寄存器里,无需程序设计人员自己去指定。所以,目前已很少使
用 register 去说明能量了。
• static(静态变量) :在 C 语言里,静态变量是一种较为特殊的变量,既可以在函数
(或复合语句)里面说明静态局部变量,也可以在函数外面说明静态全局变量。
如果在局部变量前冠以“static”,就成为静态局部变量,编译程序将为该变量在静态
存储区里建立永久的存储单元。因此,即使离开了它的作用域,其值也能得以保留。所以,
– 123 –
C 语言程序设计—示例与习题解析

静态局部变量是一种在两次函数调用之间仍然保持其值的局部变量。它的默认初值是 0。
例 6-3 阅读下面的程序,其输出结果是什么?
#include<stdio.h>
void fun(); /* 函数 fun 的原型 */
main()
{
int k;
for (k = 1; k<= 4; k++)
fun();/* 调用函数 fun */
}
void fun()/* 函数 fun 的定义 */
{
static int j; /* 变量 j 是一个静态局部变量,默认初值为 0 */
j += 2;
printf ("%d,", j);
}
[解] 在函数 main 里,通过循环 4 次调用函数 fun。fun 是一个没有返回值的函数,在
它的里面说明了一个静态局部变量 j。第 1 次调用 fun 时,j 在默认初值 0 的基础上加 2,变
为 2;第 2 次调用 fun 时,j 在上一次 2 的基础上加 2,变为 4;第 3 次调用 fun 时,j 在上
一次 4 的基础上加 2,变为 6;第 4 次调用 fun 时,j 在上一次 6 的基础上加 2,变为 8。因
此,每次调用 fun 时,printf 语句依次打印出:2,4,6,8。
如果在全局变量前冠以“static”,就成为静态全局变量,编译程序将为该变量在静态
存储区里建立永久的存储单元。这种变量的特点是只在它被说明的这个文件中可用,在其他
文件中,既不知道它的存在,也无法改变它的内容。
• extern(外部变量) :C 语言允许将大程序分成若干个独立的模块分别编译,然后连
接起来。各个模块都能使用的全局变量只能说明一次。为此,可在一个文件中说明全局变量,
而在其他文件中用 extern 来说明该变量,以表示这个变量的正式说明不在本模块。比如,有
两个分别编译的文件:

– 124 –
第 6 章 函数与变量存储类型

在文件 1 里说明了三个全局变量 x、y、ch,在文件 2 里要用到它们。于是在文件 2 对它


们的说明前,加上了“extern” ,以便告诉编译程序,在其后的变量说明和名称已经在别的文
件中进行了说明。于是编译程序不再为它们分配内存。
因此,在 C 语言中,一个完整的变量说明语句格式为
<存储类型> <数据类型> <变量名 1>=<初值 1>,<变量名 2>=<初值 2>,…
也就是说,完整的变量说明应该包含 4 个部分的内容:存储类型说明,数据类型说明,变量
名,以及变量所取的初值。
4.变量的生存期和作用域
由 auto 和 register 说明的变量,在进到说明该变量的程序块(统指函数或复合语句)
时,才被分配存储单元,退出该程序块时分配的单元被收回。因此,它在该程序块被调用时
存在,出了该程序块就消亡。可以看出,这种变量的生存期和作用域都是该程序块。
由 static 和 extern 说明的变量,其存储单元位于静态存储区。因此只要这种变量被说
明了,它在整个程序的执行期间都保持其值,也就是说,其生存期是整个程序。有点区别的
是,static 变量的作用域仅限于说明它的程序块,对 extern 变量没有这种限制。
例 6-4 各种变量作用域的举例。
#include<stdio.h>
void fone (); /* 三个函数的原型 */
void ftwo ();
void fthree ();
int x = 1; /* x 被说明为是一个全局变量 */
main ()
{
int x = 5; /* main 函数中说明的局部变量 */
printf ("local x in outer scope of main is %d\n", x); /* 第 1 条 printf 语句 */
{ /* 进入复合语句,形成变量 x 的新作用域 */
int x = 7;
printf ("local x in inner scope of main is %d\n", x); /* 第 2 条 printf 语句 */
} /* 结束变量 x 的新作用域 */
printf ("local x in outer scope of main is %d\n", x); /* 第 3 条 printf 语句 */
fone (); /* 函数 fone 有自动局部变量 x */
ftwo (); /* 函数 ftwo 有静态局部变量 x */
fthree (); /* 函数 fthree 使用全局变量 x */
fone (); /* 函数 fone 对自动局部变量 x 重新初始化 */
ftwo (); /* 静态局部变量 x 保持了以前的值 */
fthree (); /*全局变量 x 也保持其值 */
printf ("local x in main is %d\n", x); /* 第 16 条 printf 语句 */
}
void fone ()
{

– 125 –
C 语言程序设计—示例与习题解析

int x = 25; /* 每次调用函数 fone 时,都会对局部变量 x 初始化 */


printf ("\n local x in fone is %d after entering fone \n", x); /* 第 4、10 条 printf
语句 */
++x;
printf ("local x in fone is %d before exiting fone \n", x); /* 第 5 、 11 条
printf 语句 */
}
void ftwo ()
{
static int x = 50; /* 只在第 1 次调用函数 ftwo 时,才对 x 初始化 */
printf ("\nlocal static x is %d on entering ftwo\n", x); /* 第 6、12 条 printf
语句 */
++x;
printf ("\nlocal static x is %d on exiting ftwo\n", x); /* 第 7、13 条 printf
语句 */
}
void fthree ()
{
printf ("\nglobal x is %d on entering fthree\n", x); /* 第 8、14 条 printf 语句 */
x += 10; /* 这里使用的是全局变量 x */
printf ("\nglobal x is %d on exiting fthree\n", x); /* 第 9、15 条 printf 语句 */
}
[解] 图 6-2 所示的是该程序的演示结果。在所有函数的外面说明了一个全局变量 x,
它的初值是 1。在函数 main 里说明的自动局部变量 x,它的初值是 5。第 1 条 printf 语句打
印出 x 在 main 的外层取值为 5,表明调用进入 main 时,就把全局变量 x 隐藏起来了。

图 6-2 各种变量作用域的演示结果

第 2 条 printf 语句打印出 x 在 main 的复合语句里取值为 7,表明程序进入该复合语句


– 126 –
第 6 章 函数与变量存储类型

时,把外层的 x 隐藏起来了。等到出了复合语句,内层的 x 被撤消,外层的 x 恢复作用。因


此,第 3 条 printf 语句又打印出外层的 x 取值 5。
第 1 次进入函数 fone,它说明了一个自动变量 x,并初始化为 25。所以第 4 条 printf
语句打印出 x 的值为 25,表明全局变量 x 此时又被隐藏起来。在把变量 x 加 1 后,临退出函
数前,第 5 条 printf 语句打印出 x 的值为 26。第 2 次进入函数 fone,x 又被初始化为 25。
所以第 10、11 两条 printf 语句打印出的内容与第 4、5 两条一样。
第 1 次进入函数 ftwo, 它说明了一个静态局部变量 x, 并初始化为 50。于是 第 6 条 printf
语句打印出 x 的值为 50,第 7 条 printf 语句打印出在 x 上加 1 后的值 51。由于在 ftwo 里,
x 是静态局部变量,即使退出该函数,其值也会保存起来。所以第 2 次进入函数 ftwo,执行
第 12、13 条 printf 语句,打印出的值是 51 和 52。
函数 fthree 没有说明任何变量,在它里面出现的 x 就是全局变量。因此,第 1 次进入
它时,第 8 条 printf 语句打印出的就是全局变量的初值 1,第 9 条 printf 语句打印出的是
在 1 的基础上加 10 后的结果 11。第 2 次调用函数 fthree 时,全局变量 x 保持原先的值。所
以第 14、15 条 printf 语句打印的 x 的值是 11 和 21。
第 16 条 printf 语句执行时又回到函数 main,因此起作用的是 main 中说明的自动局部
变量 x。由于没有对它进行任何修改,所以它的值仍然是 5。
5.Turbo C 提供的库函数
库函数有时也称系统函数,是人们根据需要而编写的。通过对库函数的调用,可以减轻
编程人员的负担。
为了便于管理,按功能将库函数划分成组。每一组函数有自己专门使用的数据类型、变
量和函数原型,由它们形成了一个个头文件。程序中要使用哪个库函数,就必须用 C 语言的
包含命令(#include),把涉及到该函数的头文件包含到自己的程序中。对于初学者来说,常
用的头文件有如下几个:
• math.h:数学函数 • ctype.h:字符函数
• string.h:字符串函数 • stdio.h:输入输出函数
• alloc.h:动态存储分配函数
比如,下面是一个调用“求整数绝对值”函数的例子。
#include<stdio.h>
#include<math.h> /* 由于要调用 abs()函数,因此必须包含头文件 math.h */
main()
{
int vlu, x;
printf ("Enter the value :");
scanf ("%d", vlui);
x = abs (vlu); /* 对 abs()函数的调用 */
printf ("The absolute value of %d is : %d\n", vlu, x);
}
又比如,下面是一个调用“判定一个字符是否是字母”的函数的例子。
#include<stdio.h>

– 127 –
C 语言程序设计—示例与习题解析

#include<ctype.h> /* 由于要调用 isalpha()函数,必须包含头文件 ctype.h */


main()
{
char ch;
for ( ; ; )
{
printf ("Input the character'*'to quit\n");
ch = getch ();
if ( isalpha (ch) ) /* 对 isalpha()函数的调用 */
printf ("\n %c is a letter. \n", ch);
else
printf ("\n %c isn’t a letter. \n", ch);
if (ch =='*') /* 如果输入的是字符'*',则停止循环 */
break;
}
}

6.1.3 本章难点
1.参数值传递方式的实现过程
在函数调用时,主调函数可以将数据传递给被调函数,这种传递是通过“参数”来进行
的。进行值传递时,整个处理过程大致如下:
(1)在主调函数中,先为实参变量准备好要传递的数据;
(2)遇到函数调用时,系统为被调函数中的形参变量分配存储单元;
(3)把实参变量的值赋给形参变量。于是,实参、形参虽然不对应于相同的变量,但却
取相同的值;
(4)执行被调函数的功能;
(5)在被调函数中遇到 return 语句或函数的结束右括号,系统收回分配给形参变量的
存储单元,程序执行流程返回主调函数中调用被调函数的下一条语句。
可以看出,在值传递方式下,由于实参、形参对应的不是相同的存储单元,因此在(4)
里,被调函数对形参变量的操作,不会影响到主调函数中的实参。调用结束返回主调函数时,
实参变量仍然保持原有的值。
例 6-5 下面的 main 函数向被调函数 swap 传递两个 int 型实参。swap 的功能是交换形
参的值。
#inclide<stdio.h>
void swap (int, int); /* 在主调函数外给出的函数 swap 的原型 */
main()
{
int num1, num2;
printf ("Enter two numbers:\n");

– 128 –
第 6 章 函数与变量存储类型

scanf ("%d%d",&num1, &num2);


printf ("num1=%d, num2=%d\n",num1, num2); /* 第 1 条 printf */
printf ("&num1=%u, &num2=%u\n",&num1, &num2); /* 第 2 条 printf */
swap (num1, num2); /* 调用 swap 函数进行交换 */
printf ("num1=%d, num2=%d\n",num1, num2); /* 第 6 条 printf */
}
void swap (int x, int y) /* 函数 swap 的定义 */
{
int temp; /* 临时工作单元 */
printf ("x=%d, y=%d\n",x, y); /* 第 3 条 printf */
printf ("&x=%u, &y=%u\n",&x, &y); /* 第 4 条 printf */
temp=x; /* 下面三条语句完成 x、y 中数据的交换 */
x=y;
y=temp;
printf ("x=%d, y=%d\n",x, y); /* 第 5 条 printf */
}
运行该程序,如果输入的两个数据是 12 和 56,则运行结果如图 6-3 所示。请分析每一
条 printf 语句的含义。

图 6-3 例 6-5 的运行结果

[解] 在 main 和 swap 里,各有三条 printf 语句。这些 printf 语句的执行顺序如程序


中所注释。在 main 里,第 1 条 printf 语句负责把从键盘输入到变量 num1、num2 里的数据打
印出来。可以看到,现在 num1 里是 12,num2 里是 56。第 2 条 printf 语句负责把变量 num1、
num2 的地址打印出来。可以看到,现在系统分配给 num1 的存储单元地址是 65490,分配给
num2 的存储单元地址是 65492。执行了这两条 printf 语句后,main 调用 swap,于是程序的
执行流程进入函数 swap。在 swap 里,第 3 条 printf 语句负责把形参 x、y 里当前从实参接
收到的数值打印出来。可以看到,现在 x 是 12,y 是 56,它们与 num1、num2 里的值是相同
的。第 4 条 printf 语句负责把变量 x、y 的地址打印出来。可以看到,现在系统分配给 x 的
存储单元地址是 65486,分配 给 y 的存储单元地址是 65488,这就是说,实参和形参使用的不
是相同的存储单元。第 5 条 printf 语句是在 swap 完成交换后把形参 x、y 里的数值打印出来。
可以看到,现在 x 是 56,y 是 12,它们中的取值确实被交换了。第 6 条 printf 语句是从 swap
返回 main 后再次把变量 num1、num2 里的数据打印出来。可以看到,现在 num1、num2 里仍然
– 129 –
C 语言程序设计—示例与习题解析

保持原有的数值,即是 num1 里是 12,num2 里是 56。这就是说,交换并未影响到实参。


2.静态局部变量的作用
静态局部变量是在函数(或复合语句)中说明的,因此它具有局部性;但系统又是在静
态存储区里为其分配存储单元的,因此它又具有全局性。
例 6-6 分析以下程序,理解一般局部变量与静态局部变量之间的区别。图 6-4 所示的
是其运行的结果。
#include<stdio.h>
void fun()/* 函数 fun 的定义 */
{
static int x=1;
auto int y;
printf ("x=%d, y=%d\n", x, y); /* 第 1 条 printf 语句 */
y = 0;
printf ("y=%d\n", y); /* 第 2 条 printf 语句 */
x++;
y++;
printf ("x=%d, y=%d\n", x, y); /* 第 3 条 printf 语句 */
printf ("- - - - -\n");
}
main()
{
int j;
for (j=1; j<=3; j++)
fun(); /* 对函数 fun 的调用 */
}

图 6-4 例 6-6 的运行结果

[解] main 函数循环调用 fun 三次。在 fun 里,说明了一个 static 变量 x,初值为 1;


说明了一个 auto 变量 y,没有设置初值。第 1 条 printf 语句打印出进入 fun 函数时的情形;
第 2 条 printf 语句打印出为 y 赋值后的情形;第 3 条 printf 语句打印对 x、y 操作后的情形。
先分析自动变量 y,可以看出,每次进入 fun、且没有给它赋初值前,它的取值是不定的。再
– 130 –
第 6 章 函数与变量存储类型

来分析静态局部变量 x,每次进入 fun,它总是继承了上次的结果:第 1 次 x=1,表示是初值;


第 2 次 x=2,这是上一次的结果;第 3 次 x=3,这也是上一次的结果。可见,对于静态局部变
量,对它的赋初值操作只做一次。在两次调用之间,它能保持原有的值。
如果希望在多次调用之间,能够保持某个变量的值,那么就应该将该变量说明为是静态
局部的。当然,可以用全局变量来代替静态局部变量实现这样的功能。但这样做的缺点是:
由于全局变量能够被本文件中的所有函数使用, 因此也就为可能产生的各种干扰敞开了大门。
静态局部变量局限于说明它的函数,从而保证了函数对该变量使用的独立性。
3.函数的递归调用
在 C 语言中,允许函数调用自己,前提是该函数必须能够调用自己。函数的每一次递归,
并不是原样复制,而是用新的参数去再次调用。
要设计出能递归调用的函数,关键是在函数体中不仅要有递归调用的过程,而且要有回
溯的过程。这两个过程的分界点是一个条件:当条件不成立时,不断地调用本函数;当条件
成立时,立即停止调用,从函数中往回返(即回溯) 。因此,编写递归函数时,必须有一个
if 语句使函数返回而不再做递归调用。
假定要设计的递归调用函数对应的递归算法是:
当 n = n0 时,有 f (n0) = f0;
当 n = n 时,有 f (n) = f (n−1)。
那么,一般的递归调用函数可以设计成:
<函数类型> <函数名> (n)
{
int n;
{
if (n == n0)
return (f0) ;
else
return ( f (n−1) );
}
}
例 6-7 设计一个递归调用函数,计算 n 个自然数之和:1+2+3+…+n。
[解] 这里的递归算法是:
当 n = 1 时,sum (1) = 1;
当 n = n 时,sum (n) = sum (n−1) + n;
于是递归调用函数可以设计如下:
int sum (int n) /* 递归函数 sum 的定义 */
{
if (n == 1)
return 1;
else
return (sum (n−1) + n);

– 131 –
C 语言程序设计—示例与习题解析

}
main()
{
int x;
printf ("Enter a positive integer:\n");
scanf ("%d", &x);
printf ("1+2+3+…+%d = %d\n", x, sum (x) ); /* 在此调用递归函数 sum */
}
图 6-5 所示的是该程序两次运行的结果,第 1 次输入的正整数是 10,计算结果是 55;
第 2 次输入的正整数是 50,计算结果是 1275。

图 6-5 例 6-7 的两次运行结果

6.2 典型示例分析

示例 1 定义一个函数 up (ch),功能是如果 ch 是小写字母,则把它转换为大写,然后


通过 up 返回;否则字符 ch 保持不变。
[解] 程序编写如下:
#include<stdio.h>
char up (char ch)
{
return ((ch>= ' a ' && ch<= ' z ' ) ? ch− ' a ' + ' A ' : ch);
}
main()
{
char letter, temp;
printf ("Enter a letter to convert :\n");
scanf ("%c", &letter);
temp = up (letter); /* 调用函数 up */
printf ("The letter %c in uppercase is %c\n", letter, temp);
}
这里,定义了一个名为 up 的函数,它从调用者处接收一个字符型的实参。如果传递过
– 132 –
第 6 章 函数与变量存储类型

来的是一个小写字母,那么必定会落入区间“ch>= ' a ' && ch<= ' z '”之中。于是经过


“ch− ' a ' + ' A '”的加工,就把相应的大写字母返回;否则就原封不动地返回。在主调
函数里,由字符型的临时变量 temp 接收返回值,然后打印输出。
可以不设置临时变量 temp,直接把对函数的调用放入 printf 语句中,即把 main 中最后
的 printf 语句改为
printf ("The letter %c in uppercase is %c\n", letter, up (letter));
示例 2 编写一个函数,它带有一个字符型形参 ch 和一个整型形参 n。功能是显示出由
所给字符 ch 组成的三角形,第 1 行一个字符 ch,第 2 行两个字符 ch,……,第 n 行 n 个字
符 ch,如图 6-6 所示(在那里,输入的字符是“&”,整数是 12)。

图 6-6 示例 2 的要求结果

[解] 程序编写如下:
#include<stdio.h>
void triangle (char ch, int n) /* 显示三角形的函数定义 */
{
int i, j;
for (i=1; i<=n; i++)
{
for (j=1; j<=i; j++)
putchar (ch);
putchar ('\n');
}
}
main()
{
int unm;
char x;
printf ("Enter a character and an integer:\n");
scanf ("%c%d",&x, &num);
triangle (x, num); /* 对 triangle 函数的调用 */
}

– 133 –
C 语言程序设计—示例与习题解析

在例 5-9 已经见过打印三角形的程序。对比一下可以看出,通过设计函数的方式,一方
面可以使每一个程序块的功能更加明确、清晰;另一方面也为其他程序的调用提供了方便。
示例 3 阅读下面的程序:
#include<stdio.h>
int cix = 300; /* 这是一个全局变量 */
void sig (); /* 这是函数 sig 的原型 */
main()
{
int j;
for (j=1; j<=5; j++)
{
cix++; /* 这是对全局变量 cix 的操作 */
printf ("%d,", cix); /* 第 1 条 printf 语句 */
sig ( ); /* 对函数 sig 的调用,总共是 5 次 */
}
}
void sig ( ) /* 函数 sig 的定义 */
{
static int cix = 30; /* 这是一个静态变量 */
cix++; /*这是对静态变量 cix 的操作 */
printf ("%d\n",cix); /* 第 2 条 printf 语句 */
}
写出程序的运行结果。
[解] 本例中 main 函数是对名为 cix 的全局变量进行操作;调用函数 sig 时,是对同
名的静态变量进行操作。由于是 5 次循环,所以 main 对全局变量 cix 做 5 次加 1 操作,sig
对静态变量 cix 做 5 次加 1 操作,它们分别由第 1、2 条 printf 语句打印出来。所以结果是:
301,31
302,32
303,33
304,34
305,35
示例 4 在数学里,称下面的数列:
0,1,1,2,3,5,8,13,21,34,55,89,……
为斐波那契(Fibonacci)数列。试分析诸数之间的关系,利用函数的递归调用,求第 n
个 Fibonacci 数。
[解] 从数列各数之间的关系,可以看出,fibonacci (0) = 0,fibonacci (1) =1,
fibonacci (2) = fibonacci (1)+ fibonacci (0)=1,fibonacci (3) = fibonacci (2)+
fibonacci (1)=2,依次类推,也就是说,可以总结出这里的递归算法是:
fibonacci (0) = 0, 当 n=0;

– 134 –
第 6 章 函数与变量存储类型

fibonacci (1) =1, 当 n=1;


fibonacci (n) = fibonacci (n−1)+ fibonacci (n−2) 当 n>=2。
因此,整个程序编写如下:
#include<stdio.h>
long fibonacci (long); /*fibonacci 函数的原型 */

main()
{
long result, num;
printf ("Enter an integer :\n");
scanf ("%ld", &num);
result = fibonacci (num); /* 在 main 函数里调用 fibonacci 函数 */
printf ("fibonacci (%ld) = %ld\n", num, result);
}
long fibonacci (long n) /* fibonacci 函数的定义 */
{
if (n == 0 || n == 1)
return n;
else
return ( fibonacci (n−1) + fibonacci (n−2) ); /* 递归调用体现于此 */
}
图 6-7 所示的是运行的输出结果。

图 6-7 第 i 项 fibonacci 数

main 函数里,对 fibonacci 函数的调用不是递归调用,递归调用发生在 fibonacci 函数


自身里。 每次调用 fibonacci 时, fibonacci 函数立即测试所给的条件(即 n 是等于 0 还是 1)

如果测试为真,则返回 n 的值;如果测试为假,则都要递归调用 fibonacci 两次。
示例 5 可以用辗转相除的办法求出两个正整数 x、y 的最大公约数 gcm。然后根据下面
的公式:
lcm (x, y) = ( x∗y ) / gcm (x, y)
就可以求出正整数 x、y 的最小公倍数。试编写 gcm 函数,并求任意两个正整数 x、y 的最小
– 135 –
C 语言程序设计—示例与习题解析

公倍数。
[解] 程序编写如下:
#include<stdio.h>
int aqc, bqc; /* 这是两个全局变量 */
int gcm ( int x, int y) /* 这是函数 gcm 的定义 */
{
int most, little, temp;
(x > y) ? (most=x , little=y) : (most=y , little=x);
while (1)
if (most % little) /* 对 most 和 little 里的数进行辗转相除 */
{
temp = little;
little = most % little;
most = temp;
}
else
break;
return (little);
}
main()
{
int g;
long int m;
while (1)
{
printf ("Enter two integers : A =");
scanf ("%d", &aqc);
printf (" B =");
scanf ("%d", &bqc);
if (aqc>0 && bqc>0)
break;
else
printf ("Error data ! Reenter please .\n");
}
g = gcm (aqc, bqc); /* 调用 gcm 函数 */
printf ("Greatest common measure of A abd B is %d\n", g);
m = aqc∗bqc/g;
printf ("Lowest common multiple of A and B is %ld\n", m);
}

– 136 –
第 6 章 函数与变量存储类型

运行该程序,图 6-8 给出其结果。

图 6-8 示例 5 的运行样例

6.3 课外习题、答案与解析

6.3.1 课外习题

一、单选题

1.下面的叙述中,不正确的是( )。
A.在 main 函数中说明的变量的作用域是整个程序。
B.全局变量在程序运行期间一直占用分配给它的存储单元。
C.形式参数是局部变量,
D.return()语句中括号内可以是常量、有确定值的变量或表达式。
2.下面的叙述中,正确的是( )。
A.函数返回值的类型,由 return 语句中所返回值的类型决定。
B.函数返回值的类型,由主调函数的类型决定。
C.函数返回值的类型,由定义函数时指定的函数类型决定。
D.函数返回值的类型,由实参的类型决定。
3.定义函数时,默认的函数类型是( ) 。
A.auto B.register C.static D.extern
4.在函数内,说明一个变量时,可以省略的存储类型是( ) 。
A.auto B.register C.static D.extern
5.C 语言中的函数( ) 。
A.可以嵌套定义 B.嵌套调用和递归调用均可
C.不可以嵌套调用 D.可以嵌套调用,但不可以递归调用
6.在以下对 C 语言函数的描述中,正确的是( ) 。
A.函数必须要有返回值。
B.函数返回值的类型与其形参的类型应该一致。
– 137 –
C 语言程序设计—示例与习题解析

C.值传递时,只能把实参的值传送给形参,反过来是不行的。
D.有调用关系的函数,必须出现在同一个源程序文件中。
7.若函数 fun 定义为
fun (char x)
{
……
}
则该函数( )。
A.无返回值 B.返回值为 auto 类型
C.返回值为 char 类型 D.返回值为 int 类型
8.下面程序的运行结果应该是( )。
#include<stdio.h>
main()
{
int j = 4, m = 1, k;
k = fun (j, m);
printf ("%d,", k);
k = fun (j, m);
printf ("%d\n", k);
}
fun (int x, int y)
{
static int m = 0, i = 2;
i += m + 1;
m = i + x + y;
return (m);
}
A.8,20 B.8,8 C.8,17 D.8,16
9.在主调函数中有对函数 fun 的调用语句:
fun ( (a, b) , (i, j, k) );
那么 fun 的形式参数的个数是( )个。
A.1 B.2 C.4 D.5

二、填空题

1.被调函数是通过函数中的 语句将返回值传递给主调函数的。
2.除了 main 函数外,其他函数必须通过 才能得以执行。
3.在函数头部,使用关键字 来表示它没有返回值。
4.一个变量的 指明该变量可以使用的程序区域。
5.在所有函数之外说明的变量称为 。
– 138 –
第 6 章 函数与变量存储类型

6.要使一个局部变量在两次函数调用中保持其值,必须说明成是 存储类型的。
7.直接或间接调用自己的函数称为 函数。
8.函数形式参数的作用域是 。
9.下面的程序功能是统计 main 循环调用 count 函数的次数(输入字符“#”作为结束
标志)。请完成程序填空。
main() count( char c )
{ {
char ch; static int j = 0;
while ( ① ) j++;
{ if ( ④ )
scan ("%c", &ch); printf ("count = %d\n", j);
count ( ② ); }
if ( ③ )
break;
}
}
10.有公式如下:
π 1 1 2 1 2 3 1 2 3 4
= 1 + + × + × × + × × × + 
2 3 3 5 3 5 7 3 5 7 9
下面是根据公式求π值的、名为 pi 的函数,该函数以所需的精度为形式参数。
double pi (double jd)
{
double sum = 0.0, t = 1.0;
int n;
for ( ① ; t > jd; n++)
{
s += t;
t = n ∗ t/(2∗n+1);
}
return (2.0 ∗ ② );
}
请完成程序填空。

三、是非判断题(在括号内打“√”或“×”)

1.一个函数中只能出现一个 return 语句。( )


2.如果被调函数是有返回值的函数,那么被调函数中必须安排 return 语句
。( )
3.有参函数必定是一个有返回值的函数。 ( )
4.在函数之外说明的变量是全局变量。 ( )
5.在一个文件中,全局变量和局部变量同名时,在局部变量的作用域内,全局变量就
– 139 –
C 语言程序设计—示例与习题解析

不起作用。( )
6.由于是在静态存储区分配存储单元,因此静态局部变量就是全局变量。 ( )
7.全局变量与函数体里说明的局部变量同名时,在函数体里全局变量就被隐蔽,起作
用的是局部变量。 ( )
8.定义一个无返回值的函数时,函数名前的函数类型就可以省略。( )

四、程序阅读题

1.有程序如下:
#include<stdio.h>
int fun (int x, int y)
{
static int w = 0, j = 2;
j += w+1;
w = j + x + y;
return w;
}
main()
{
int i = 4, w = 1, k;
k = fun (i, w);
printf ("%d,", k);
k = fun (i, w);
printf ("%d\n", k);
}
运行此程序,输出的结果是什么?
2.有程序如下:
#include<stdio.h>
fun (int x)
{
int b = 0;
static c = 3;
x = c++ , b++ ;
return (x);
}
main()
{
int a = 2, j, k;
for (j=0; j<2; j++)
k = fun (a++);

– 140 –
第 6 章 函数与变量存储类型

printf ("%d\n", k);


}
运行此程序,输出的结果是什么?
3.有程序如下:
#include<stdio.h>
fun (int x)
{
int w;
if (x == 0 || x == 1)
return (3);
w = x – fun (x − 2);
return w;
}
main()
{
printf ("%d\n", fun (9) );
}
运行此程序,输出的结果是什么?
4.有程序如下:
#include<stdio.h>
unsigned fun (unsigned x)
{
unsigned k = 1;
do
{
k ∗= x % 10;
x /= 10;
}while (x);
return (k);
}
main()
{
unsigned m = 26;
printf ("%d\n", fun (m) );
}
运行此程序,输出的结果是什么?
5.下面的程序完成什么功能?
#include<stdio.h>
int mystery (int, int);

– 141 –
C 语言程序设计—示例与习题解析

main()
{
int x, int y;
printf ("\nEnter two integers :\n");
scanf ("“%d%d", &x, &y);
printf ("The result is %d\n", mystery(x, y) );
}
int mystery (int a, int b)
{
if (b == 1)
return a;
else
return a+mystery(a, b-1);
}
6.有程序如下:
#include<stdio.h>
int d = 1;
fun (int x)
{
int d = 5;
d += x++;
printf ("%d,", d);
}
main()
{
int a = 3;
fun (a);
d += a++;
printf ("%d\n", d);
}
阅读,并给出其执行结果。
7.有名为 fun 的函数如下:
int fun (int x, int y)
{
int z;
z = x;
if ( x>y )
z = 1;
else if (x == y)

– 142 –
第 6 章 函数与变量存储类型

z = 0;
else
z = −1;
return (z);
}
编写 main 函数,以不同的实参调用 fun 如下:
(1) main() (2) main()
{ {
int m = 2, t; int m = 2, t;
t = fun (m, m+1); t = fun (m, ++m);
printf ("%d\n", t); printf ("%d\n", t);
} }
试问对于不同的 main,运行的结果各是什么?

五、程序设计题

1.编写一个函数,能够计算正整数的立方值。main 调用该函数,输出 1~10 的立方。


2.公式 (x∗10+0.5)/10 能够把一个实数精确到小数点后第 1 位。编写一个完成此功能的
函数。main 调用该函数,接收返回值,加以输出。
3.有 A~E 五个人。问 E 的年龄时,回答说:比 D 大 4 岁;问 D 的年龄时,回答说:比
C 大 4 岁;问 C 的年龄时,回答说:比 B 大 4 岁;问 B 的年龄时,回答说:比 A 大 4 岁;问 A
的年龄时,答曰 12 岁。试问 E 的年龄是多少?(用递归函数做)
4.编写一个函数,功能是根据两个形参的关系,返回不同的值:第 1 个大于第 2 个,
则返回 1、第 1 个小于第 2 个,则返回−1;两个相等,则返回 0。在 main 函数中输入两个数,
调用此函数,输出它们的关系。
5.有一个数列,第 1、2 项的值都是 2,随后各项的值是其项数与前一项之和。试编写
一个递归函数,调用它能够得到该数列的所需项。
6.编写带有两个参数的函数 show:r 为实型,n 为整型。功能是打印出 r 的从 1 到 n 的
次幂表。main 函数里根据用户的输入,调用函数 show。
7.“素数” ,是指除 1 以外只能被 1 和自己整除的自然数。所以,1 不是素数,2 是最小
的素数。编写一个函数,用以判定一个数是否是素数,然后在 main 里接收一个输入,调用该
函数,输出是否是素数的信息。
8.编写一个返回值为 double 型的函数 prod,它有一个整型形参 n。功能是按公式
1 × 3 × 5 ×  × (2 × n − 1)
2 + 4 + 6 +  + (2 × n)
进行计算。main 函数接收输入,然后调用 prod,并输出结果。

6.3.2 答案与解析

一、单选题

– 143 –
C 语言程序设计—示例与习题解析

1.A 2.C 3.D 4.A 5.B


6.C 7.D 8.C 9.B

二、填空题

1.return 2.调用 3.void 4.作用域


5.全局变量 6.Static 7.递归 8.本函数体
9.① 1 ② ch ③ ch == ' # ' ④ ch == ' # '
10.① n = 1 ② s

三、是非判断题

1.× 2.√ 3.× 4.√


5.√ 6.× 7.√ 8.×

四、程序阅读题

1.答:阅读此程序时,有三点要注意。第一,在 main 里,自身没有改变所说明的局部


变量 i 和 w 的值,因此两次调用函数 fun 时,i 和 w 都保持取值 4 和 1;第二,fun 里说明了
两个静态局部变量 w 和 j,且程序中对它们的取值进行了修正,所以 main 两次调用 fun 时,
它们的取值不一样,先是 0 和 2,后是 3 和 8;第三,main 和 fun 里都说明了一个名为 w 的
变量,由于它们各自的作用域不同,所以是不相干的。最终 main 里两次的打印结果是 8,17。
2.答:本题要注意三点。第一,main 虽然两次调用函数 fun,但只把第 2 次调用后的
结果进行输出。第 1 次调用 fun 时,是 fun(2),第 2 次调用时是 fun(3)。第二,fun 中的语
句“x = c++ , b++ ;”是由逗号表达式构成的语句,即由表达式“x = c++”和“b++” 构
成的语句。不能把它理解成是把逗号表达式“c++ , b++”的值赋给变量 x,如果是那样的话,
就应该写成 x = (c++ , b++) ;才对。第三,fun 中的 c 是一个静态局部变量,它的初值是 3,
第 2 次调用时是 4。因此第 2 次调用 fun 后返回给 main 的值是 4。既然变量 k 接受的是 4,
所以最后输出结果是 4。
3.答:main 函数很简单,就是用常量 9 去调用函数 fun,然后打印出结果。而函数 fun
却是一个递归函数。递归调用的过程可以写成:
fun(9)=9−fun(7)=9−(7−fun(5))=9−(7−(5−fun(3)))=9−(7−(5−(3−fun(1))))=9−7+5−3
+3=7
所以,程序的输出结果是 7。
4.答:main 函数很简单,就是用常量 26 去调用函数 fun,然后打印出结果。在 fun 接
收了实参 26 后,毫无阻拦地进入到 do-while 循环体中执行。第 1 次执行循环体后,k 等于 6,
x 等于 2。由于满足继续循环的条件,故第 2 次进入循环体。执行后,k 等于 12,x 等于 0。
这时继续循环的条件被破坏,循环结束,返回主调函数。因此最终的输出结果是 12。
如果把 fun 修改如下:
unsigned fun (unsigned x)
{
unsigned k = 1;
– 144 –
第 6 章 函数与变量存储类型

do
{
k∗= x % 10;
printf ("k=%d, ", k); /* 增加的语句 */
x /= 10;
printf ("x=%d\n", x); /* 增加的语句 */
}while (x);
return (k);
}
那么就可以看到每次循环体执行时,变量 k 和 x 的变化情况。
5.答:main 调用函数 mystery。而函数 mystery 自身进行递归调用。mystery 递归调用
进行的次数由 b 决定。比如 b 接收的实参是数值 5,那么每调用一次 mystery,就会在 b 原有
值的基础上减 1,直到 b 等于 1 时停止调用过程。mystery 的功能是对 a 进行累加,累加的次
数就是调用 mystery 的次数。如果在 main 里接收的输入是 12 和 5,那么意味要将 12 累加 5
次,即最后结果是 60。图 6-9 左侧是 4 次运行记录。为了能够知道 mystery 调用的次数,可
以在它里面添加一段程序,将它改造如下:
int mystery (int a, int b)
{
static int i=1; /* 添加的两个语句 */
printf ("i = %d\n", i++);
if (b == 1)
return a;
else
return a+mystery(a, b−1);
}
这样,每调用一次 mystery,静态变量 i 就会加 1。图 6-9 右侧是当输入的两个数是 12
和 5 时的一次运行记录。可以看出,共调用 mystery5 次,结果是 60。
6.答:这里要注意:main 里涉及的变量 d 是说明在所有函数之外的全局变量,而出现
在 fun 里的变量 d 则是在该函数里说明的局部自动变量。main 里先以 a 等于 3 调用 fun 时,
那里的 printf 输出的 d,应该是 fun 的自动变量 d 的值 5 加上 3,故等于 8。调用返回 main
后,printf 输出的 d,应该是全局变量 d 的值 1 加上 3,故等于 4。所以整个输出应该是 8,
4。

– 145 –
C 语言程序设计—示例与习题解析

图 6-9

7.答:函数 fun 的功能是判定两个形式参数之间的关系,从而返回不同的值:x>y 时返


回 1;x==y 时返回 0;x<y 时返回−1。如果运行(1)里的 main,那么它用 2 和 3 去调用 fun。
Fun 返回−1,所以运行结果是打印出−1。如果运行(2)里的 main,由于第 2 个参数++m 后不仅
使自己变为 3,同样也影响到第 1 个参数也成为 3。所以,实际上是以 3,3 的参数去调用 fun
的。显然,最终打印的结果应该是 0。

五、程序设计题

1.答:程序编写如下:
#include<stdio.h>
int cube (int); /* 函数 cube 的原型 */
main()
{
int x;
for (x=1; x<=10; x++)
printf ("%d", cube (x) ); /* 对 cube 函数的调用 */
printf ("\n");
}
int cube (int z) /* 函数 cube 的定义 */
{
return z∗z∗z;
}
2.答:程序编写如下:
#include<stdio.h>
float rtts (float); /* 函数 rtts 的原型 */
main()
{
float x,y;
printf ("Enter a real number:\n");
scan ("%f", &y);

– 146 –
第 6 章 函数与变量存储类型

x= rtts (y); /* 对 rtts 函数的调用 */


printf ("The result is %f\n", x);
}
float rtts (float z) /* 函数 rtts 的定义 */
{
return (z∗10+0.5)/10;
}
3.答:该递归算法应该是:
age(1) = 12, 当 n = 1 时;
age(n) = age(n−1)+4, 当 2<= n <=5 时。
该递归函数的程序编写如下:
#include<stdio.h>
int age (int n)
{
int x;
if (n == 1)
x = 12;
else
x = age (n−1) + 4;
return x;
}
4.答:程序编写如下:
#include<stdio.h>
int sig (float x, float y)
{
int n;
if (x>y)
n = 1;
else if (x == y)
n = 0;
else
n = −1;
return n;
}
main()
{
float num1, num2;
int a;
printf ("\nEnter two numbers :"); /* 提示信息 */

– 147 –
C 语言程序设计—示例与习题解析

scanf ("%f%f",&num1, &num2); /* 接收键盘输入的两个数 */


a = sig (num1, num2);
if (a ==0) /* 做出多个分支选择 */
printf ("%f = %f\n", num1, num2);
else if (a == −1)
printf ("%f < %f\n", num1, num2);
else if (a == 1)
printf ("%f > %f\n", num1, num2);
}
下图是三次运行的结果。

5.答:根据题意,该数列应该是:
2,2,5,9,14,20,27,35,……
可以把递归算法描述如下:
qhf (1) = 2, 当 n=1;
qhf (2) = 2, 当 n=2;
qhf (n) = n + qhf (n−1), 当 n>2。
于是整个程序编写为
#include<stdio.h>
long qhf (int n)
{
if (n == 1 || n == 2)
return 2;
else
return n + qhf (n−1);
}
main()
{
long x;
int y;
printf ("\nEnter a integer:");
scanf ("%d", &y);
x = qhf (y);

– 148 –
第 6 章 函数与变量存储类型

printf ("The iterm is %ld\n", x);


}
6.答:程序编写如下:
#include<stdio.h>
void show (double r, int n)
{
int j;
double aux = 1;
printf ("%5.2f to power is\n", r);
for (j=1; j<=n; j++)
{
aux ∗= r;
printf (" %d\t%5.2f\n", j, aux);
}
}
main()
{
double x;
int y;
printf ("\nEnter a real and an integer value:");
scanf ("%lf%d", &x, &y);
if (y<=0)
{
printf ("%d is not positive, terminating……\n");
getch();
return;
}
show (x, y);
}
下图是两次正确运行、一次输入有误情形的结果。

– 149 –
C 语言程序设计—示例与习题解析

7.答:程序编写如下:
#include<stdio.h>
main()
{
int prime (int); /* prime 函数的原型 */
int n;
printf ("\nEnter an integer:");
scanf ("%d", &n);
if (prime(n)) /* 对函数 prime 的的调用 */
printf ("%d is a prime.\n", n);
else
printf ("%d is not a prime.\n", n);
}
int prime (int x) /* 函数 prime 的定义 */
{
int flag = 1, j;
for (j=2; j < x / 2; j++)
if (x % j == 0)
{
flag = 0;
break;
}
return(flag);
}
函数 prime 里,对主调函数传递过来的整数值做这样的测试:用 2 到 x/2 范围里的每一
个数去除 x。如果有一个数能够除尽 x,那么 x 肯定不是素数,于是把标志变量 flag 设置为
0,并立即退出循环。如果测试完所有范围内的数,都没有一个能除尽 x,那么说明 x 是一个
素数。下图给出了 3 次运行的结果。

– 150 –
第 6 章 函数与变量存储类型

另外,main 和 prime 可以改写如下:


main()
{
int prime (int); /* prime 函数的原型 */
int n;
printf ("\nEnter an integer: ");
scanf ("%d", &n);
prime(n) ? printf ("%d is a prime.\n", n) : printf ("%d is not a prime.\n", n);
}
int prime (int x)
{
int flag = 1, j;
for (j=2; j < x / 2 && flag == 1; j++)
if (x % j == 0)
flag = 0;
return(flag);
}
这时,一旦 flag 等于 0,for 循环就会立即因为条件“flag == 1”不满足而停止。
8.答:程序编写如下:
#include<stdio.h>
double prod (int n) /* 函数 prod 的定义 */
{
int j, sum = 0, chn = 1;
double ret;
for (j=1; j<=n; j++)
{
sum += 2∗j; /* 形成分母 */
chn ∗= 2∗j−1; /* 形成分子 */
}
ret = ( (double)chn / (double)sum ); /* 计算结果 */
return ret;
}
main()

– 151 –
C 语言程序设计—示例与习题解析

{
int num;
double jg;
while (1)
{
printf ("\nPlease enter a positive integer value n = ");
scanf ("%d", &num);
if (num<0)
printf ("\nInput data is not correct. Please again !");
else
break;
}
jg = prod (num); /* 调用函数 prod */
printf ("The result is %7.2f\n", jg);
}
在 main 中,只有当输入的数据是一个正整数时,才允许去调用函数 prod。否则一直循
环下去。要注意函数 prod 中的语句:
ret = ( (double)chn / (double)sum );
由于在形成分子和分母的过程中,变量 chn 和 sum 始终保持为正整数,因此对它们施行
“/”时,就成为进行整数除法,不能得到正确的结果。所以至少要对它们中的一个进行强制
类型转换,以保证进行一般除法,得出正确结果。下面是 3 次运行的结果图。第 1 次输入的
数据是 7,计算结果是 247.50;第 2 次输入的数据是 4,计算结果是 1.25;第 3 次输入的数
据是−2,给出重新输入的信息。重新输入 2 后,计算结果是 0.50。

– 152 –
第7章 指针与一维数组

7.1 本章基本概念及知识点
本章涉及以下 8 个方面的内容:
• 一维数组;
• 指针变量;
• 取地址运算符(“&”)与间接访问运算符(“*”)

• 指针变量的运算;
• 返回指针值的函数(指针型函数);
• 指针和数组做为函数的参数(地址传递方式) ;
• 指针的指针;
• 指向函数和指向数组的指针。

7.1.1 基本概念
1.数组与数组元素
为相同数据类型的一组有序数据起一个共同的名字,并由数据的顺序惟一地确定每一个
数据成员。这种数据集合称为“数组” 。数据成员称为“数组元素”。
2.下标
惟一确定数组元素在数组中的顺序的整数值,称为该数组元素的“下标”。
3.一维数组
仅由一个下标值就能确定数组元素顺序的数组,称为“一维数组”。因此,一维数组中
的所有元素是排列成一行的。因此,有时也把一维数组称为“行数组”。
4.内存单元的地址
计算机的内存储器由一个个连续的单元(字节)组成。每一个单元都有一个编号,称为
“内存单元的地址” 。
5.变量的地址
不同类型变量所需的内存空间大小是不同的。把一个变量所占用内存空间里最前面那个
单元的地址,称为这个“变量的地址”。因此,一个变量名就与一个地址值所标明的存储区域
相对应。
– 153 –
C 语言程序设计—示例与习题解析

6.指针
通过地址,能够找到所需要的内存单元。因此,在 C 语言里,就把地址形象地称为“指
针”,即指针就是内存中的一个地址。
7.指针变量
存放某个变量地址的变量,就称为“指针变量”。也就是说,指针变量里存放的是一个
内存地址,或说指针变量里存放的是一个指针。在不引起混淆的情形下,常把指针变量简略
地称为指针。
8.指向变量的指针
把一个变量对应的地址赋给某个变量,后者就指向前者,则称后者为“指向变量的指针
变量” ,简称“指向变量的指针” 。
9.直接存取方式
通过变量名查到对应的地址。根据这个地址,从内存单元中取出或存入所需的内容。这
种存取方式称为“直接存取方式” 。
10.间接存取方式
通过变量名查到对应的地址。根据这个地址,从内存单元中得到的仍是一个变量的地址。
再由这个地址才能从相应的内存单元中取出或存入所需的内容。这种存取方式称为“间接存
取方式” 。
11.指针的指针
做为一个变量,指针变量本身也与一个地址相对应。存放指针变量地址的变量,就指向
于这个指针变量。因此,它就是一个“指针的指针” 。
12.地址传递方式
如果主调函数是把实参的地址传递给被调函数中相应的形参。那么这种函数间的参数传
递方式,称为“地址传递方式” 。
13.双向传递
函数间参数采用地址传递方式时,被调函数形参接受的是主调函数实参的地址。于是,
被调函数形参与主调函数实参所使用的是同一个变量。被调函数中对形参的加工,会立即反
映到主调函数的实参里。所以,地址传递又称“双向传递”。
14.函数的指针与指向函数的指针
函数虽然不是变量,但在内存中有它的物理地址(即它的入口地址),这个地址就称为
是“函数的指针” 。若把这个地址赋给某一个变量,那么该变量就指向这个函数。因此称其为
“指向函数的指针变量” ,简称为“指向函数的指针
”。
15.指针型函数
如果一个函数返回的是一个指针(即地址),那么该函数称为“指针型函数”,有时也称
“返回指针值的函数” 。

7.1.2 本章重点
1.一维数组
C 语言中,数组是在基本数据类型基础上构造出来的一种新的数据类型。它的本质是把
相同类型的一些变量有序地汇集在一起,由同一个名字、不同的下标来区分各个元素,从而
– 154 –
第 7 章 指针与一维数组

为程序设计带来便利。
在使用数组前,必须先给出数组的说明。说明的一般格式是:
<数据类型> <数组名> [<长度>] = {<初值表>} ;

<数据类型> <数组名> [<长度>];
其中,<数据类型>规定了所说明数组诸元素(即变量)的数据类型,<长度>限定了所说明数
组中包含的元素个数。前一种格式带有对数组元素的初始化,它把数据元素所取的初始值用
逗号隔开,列置在一个花括号内。说明一个数组后,它的元素就以
<数组名> [<下标>]
的形式来引用。其中<下标>的范围,规定从 0 开始,到<长度>−1 止。
比如,有如下说明:
int a[5] = {1,2,3,4,5};
表示 a 是一个数组,由 5 个 int 型变量组成,这 5 个变量表示为 a[0]、a[1]、a[2]、a[3]、
a[4]。分别取值 1、2、3、4、5,即
a[0]=1,a[1]=2,a[2]=3,a[3]=4,a[4]=5。
又比如有如下说明:
double array[10];
这表示把 array 说明为是一个数组,它由 10 个 double 型变量汇集而成,目前还没有给
这 10 个变量 array[0]~array[9]赋初始值。
例 7-1 编写一个程序,打印出数组元素初始化后各元素的取值。
[解] 程序编写如下:
#include<stdio.h>
main()
{
int a[10], j;
double b[10]={1.0, 3.0; 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0, 19.0};
for (j=0; j<=9; j++) /* 完成对数组 a 元素初始化的工作 */
a[j] = 0;
printf ("%s%13s\n","Element","Value");
for (j=0; j<=9; j++) /* 打印出数组 a 各元素的值(都是 0)*/
printf ("%7d%13d\n", j, a[j]);
printf ("%s%13s\n","Element","Value");
for (j=0; j<10; j++) /* 打印出数组 b 各元素的值 */
printf ("%7d%13.2f\n", j, b[j]);
}
图 7-1(a)是数组 a 的输出结果,
(b)是数组 b 的输出结果。为了节约篇幅,把本应打
印在下面的内容,移到了图的右边。
关于数组元素的初始化,要注意如下几点。
(1)如果说明时直接给出的元素初始值个数小于数组长度,那么这些初始值必须顺序排
– 155 –
C 语言程序设计—示例与习题解析

列,中间不能有空隙,最后不够的用 0 补足。比如
double b[10]={1.0, 3.0; 5.0, 7.0, 9.0, 11.0, 13.0};

图 7-1 例 7-1 的打印结果

要求数组长度是 10,只给出 7 个元素的初始值。于是,C 语言会自动把该数组的 b[7]、b[8]


和 b[9]设置取初始值为 0.0。但是,如下的说明语句:
double b[10]={1.0, 3.0; 5.0, , 9.0, 11.0, 13.0, 15.0, 17.0, 19.0};
是错误的,虽然中间用逗号预留了一个空隙。
(2)如果说明时直接给出的元素初始值个数大于数组长度,比如如下的说明语句:
double b[10]={1.0, 3.0; 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 21.0};
说明的数组长度是 10,给出的初始值有 11 个。这时,系统会给出出错信息:
Too many initializers(太多的初始值)
(3)如果说明数组时,未进行元素的初始化,那么当所有元素都赋予同一值时,可以在
程序中用循环的方法对元素赋值;当元素取不同值时,只能逐个赋值,不能采取如下方法:
a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
这时,系统会给出出错信息:
Expression syntax(表达式语法错)
(4)在 C 语言中,不自动对数组的边界进行检查,这会给编程带来隐患。比如,上述程
序里,如果误把打印数组 a 的语句写成:
for (j=0; j<=12; j++)
printf ("%7d%13d\n", j, a[j]);
那么情况有时会像图 7-2 所示。因此在 C 语言程序中使用数组时,编程者必须特别注意对数
组边界的检查,避免出现不必要的麻烦。
2.指针变量的说明
指针变量说明的一般格式是:
<数据类型> *<变量名> = <初值>;

<数据类型> *<变量名>;
在进行了这种说明后,具有<变量名>的变量,就是一个指针变量。前一种格式带有对指
针变量的初始化。
可以看出,指针变量的说明与一般变量的说明类似。注意以下 3 点。
– 156 –
第 7 章 指针与一维数组

图 7-2 数组越界造成的麻烦

(1)若要把一个变量说明为是指针型的,那么说明时必须在该变量名前加上“*”号。
但是,“*”号只起到标明该变量是指针变量的作用,并不是变量名的组成部分。
(2)指针变量里存放的只能是一个地址,而不能是其他。也就是说,指针变量的取值是
地址。因此,采取带有对指针变量初始化的说明形式时,<初值>必须是一个地址值。
(3)指针变量说明里的<数据类型>,指的是该指针所指变量的数据类型。
比如,有变量说明语句:
int count, *ptr;
该语句先说明了一个名为 count 的整型变量,随之又说明了一个名为 ptr 的、指向整型值的
指针变量,读法是“ptr 是一个指向整型值的指针”。如果语句写成:
int *ptr, count;
含义完全与前面一样。千万不能把它理解为说明了两个整型指针:ptr 和 count。说明语句中
的“*”只作用于 ptr。如果要在一个语句里说明两个同类型的指针变量,必须在每一个变量
前加“*”,比如,
int *ptr, *count;
才真正说明了两个指向整数值的指针。
3.取地址运算符“&”与间接访问运算符“*”
(1)取地址运算符“&”
“&”是一个单目运算符,其作用是得到运算对象的地址。比如有变量说明语句:
int x = 15;
int *xptr;
那么语句:
xptr = &x;
把变量 x 的地址赋给指针变量 xptr,于是 xptr 就指向变量 x 了。
(2)间接访问运算符“*”
“*” 是一个单目运算符,其作用是返回运算对象(必须是一个指针!)所指向的变量的
值。比如在前面说明语句的基础上,语句:
printf ("%d\n", *xptr); 与 printf ("%d\n", x);
的作用相同,都是把变量 x 的值 15 打印出来。

– 157 –
C 语言程序设计—示例与习题解析

例 7-2 有程序如下:
#include<stsdio.h>
main()
{
int a = 100, b = 100;
int *p1, *p2;
p1 = &a;
p2 = &b;
printf ("a=%d\t b=%d\n", a, b); /* 第 1 条 printf 语句 */
printf ("*p1=%d\t *p2=%d\n", *p1, *p2); /* 第 2 条 printf 语句 */
*p1 = 200;
*p2 = 300;
printf ("a=%d\t b=%d\n", a, b); /* 第 3 条 printf 语句 */
printf ("*p1=%d\t *p2=%d\n", *p1, *p2); /* 第 4 条 printf 语句 */
}
运行后的输出结果是什么?
[解] 由于*p1 就是 a, *p2 就是 b,所以第 1、2 条 printf 语句输出的都是 100,即
a = 100 b = 100
*p1 = 100 *p2 = 100
随后的两条赋值语句是把变量 a 重新赋值为 200,把变量 b 重新赋值为 300。故第 3、4
条 printf 语句输出的结果是:
a = 200 b = 300
*p1 = 200 *p2 = 300
在使用指针变量时,必须保证它们指向正确的数据类型。比如程序中说明一个指针是整
型的,那么把任何整型变量的地址赋给它(也就是让它指向任何一个整型变量)都是正确的。
但如果像下面程序那样:
main()
{
int *p;
float m = 12.50, n;
p = &m; /* 把实型变量 m 的地址赋给了指向整型变量的指针 p */
n = *p;
printf ("%d\n", n);
}
它把实型变量 m 的地址赋给了规定指向整型变量的指针 p。程序虽然可以通过 C 语言的编译,
但会给出如下警告信息:
Suspicious pointer conversion(可疑的指针转换)
实际运行时,由于 p 被说明的是一个整型指针,所以只把它所指处往下的两个字节内容
传送给 n,而不是将一个实型值所需要的 4 个字节内容传送给 n。也就是说,不可能把 m 的值
– 158 –
第 7 章 指针与一维数组

赋给 n。
4.指针的算术运算
可以对指针进行两种算术运算:加和减。具体地,包括增 1 运算(++) 、减 1 运算(− −)

加上一个整数(+或+=) 、减去一个整数(−或−=)。
在进行指针运算时,要特别关注指针所指的数据类型。指针每次加 1,就指向它所指数
据类型下一个单位的内存位置;每次减 1,就指向它所指数据类型前一个单位的内存位置。
对于字符型指针,它们的增 1 或减 1,正好以一个字节为单位。对于其他所有指针,就要以
它所指向的数据类型字节长度为单位进行增或减。
比如,已知 int 型数据占用 2 个字节。因此,对于 int 型指针 p,p++或 p− −,就要跨
过 2 个字节才能抵达目的地。比如,说明两个指针 ch 和 p,并都赋予初值为地址 3000:
char *ch = 3000; int *p = 3000;
那么,图 7-3 解释了这种对它们每次加 1 的指针移动过程。

图 7-3 指针运算与数据类型

也可以将一个整数加到某个指针上,或从这个指针中减去一个整数。比如“p =p+ 9;” ,


表示把指针 p 从目前位置移到它下面的第 9 个元素处,即要往后跨过 18 个字节。
5.指针做为函数参数
“指针做为函数参数”的另一种说法是“函数参数的地址传递方式”。不仅整型、实型、
字符型数据可以做为函数的参数,指针(即地址)也可以做为函数的参数。当把指针做为函
数参数时,它的作用是把一个变量的地址传送到另一个函数中去。因此,承接此地址的,必
定是一个指针变量才行。
例 7-3 编写一个无返回值函数 sort,功能是比较两个实数的大小,把小者放在第 1 个
形式参数里,大者放在第 2 个形式参数里。主调函数在输入数据后,调用 sort 函数,然后按
由小到大的顺序输出。
[解] 函数 sort 没有返回值,但 main 调用它后,又要能够按由小到大的顺序输出结果。
因此,只能考虑用指针做为函数的参数,从主调函数传递给被调函数,以达到双向传递信息
的效果。程序编写如下:
#include<stdio.h>
main()
{
void sort (float, float); /* 函数 sort 的原型 */
float x, y;
printf ("\nEnter two real numbers :");

– 159 –
C 语言程序设计—示例与习题解析

scanf ("%f%f", &x, &y);


sort (&x, &y); /* 以地址为参数,调用函数 sort */
printf ("min = %f max = %f\n", x, y);
}
void sort (float *ptr1, float *ptr2) /* 函数 sort 的定义 */
{
float temp;
if (*ptr1>*ptr2)
{
temp = *ptr1; /* 在 ptr1、ptr2 的指挥下完成排序 */
*ptr1 = *ptr2;
*ptr2 = temp;
return;
}
}
这里,main 中把变量 x 和 y 的地址传给 sort。sort 中,一方面用指针变量 ptr1、ptr2
接收这两个地址,另一方面在 ptr1、ptr2 的指挥下进行排序操作。因此,这种排序实际上就
是在实参变量里进行的。sort 虽然没有显式地返回任何值,却隐式地借助实参变量把排序结
果带给了主调函数 main。因此,主调函数里的 printf 语句才能打印出正确的顺序。
6.指针的指针
“指针的指针” ,说全了应该是“指针变量的指针”,或“指针变量的指针变量”。
做为指针变量,本质上是一个变量,只是它的里面存放着一种特殊的数据——另一个变
量的地址。既然如此,指针变量本身在内存中就要分配存储区域,这个存储区域显然就有相
应的地址。因此,如果把这个地址也存放在一个变量里,就会形成一个指针链:一个变量里
存放的是地址,这个地址指向一个变量;这个被指向的变量里存放的仍是一个地址,它又指
向再一个变量。如此等等。
指针是一种间接取值的形式。指针的指针,就是一种二次间接取值的形式。指针链拉得
越长,间接取值的重数就会进一步延伸。不过,通常也就用到指针的指针。图 7-4 给出了一
次和二次间接取值形式的示意。

图 7-4 一次和二次间接取值示意

说明指针型指针变量的一般格式是:
<数据类型> **<变量名> = <初值>;

<数据类型> **<变量名>;
在进行了这种说明后,具有<变量名>的变量,就是一个指针的指针了。前一种格式带有对指
– 160 –
第 7 章 指针与一维数组

针型指针变量的初始化。可以看出,它与指针变量说明的不同之处是在<变量名>的前面加了
两个“*”号。比如,有如下变量说明:
float **ptr;
它说明 ptr 是一个指针型的指针变量。要注意,ptr 本身并不是一个指向实型变量的指针,
而是它所指向的指针是一个指向实型变量的指针。
例 7-4 阅读程序:
#include<stdio.h>
main()
{
int x, *p, **q;
x = 100;
p = &x;
q = &p;
printf ("%d,%d\n", *p, **q);
}
运行后的输出是什么?
[解] 题目中把变量 x 的地址赋给了 p,把指针 p 的地址赋给了 q。因此*p 是一次间接
取值,**q 是二次间接取值,取出的都是变量 x 的值,即 100。所以,最终的输出结果是:
100,100
7.数组与指针
说明了一个数组后,C 语言编译程序就为它分配一个连续的内存储区加以存放。计算该
存储区大小的公式是:
数据类型字节数∗数组长度
比如,double 型数据占用 8 个字节,那么下面说明的数组 b:
double b[10];
总共占用 8∗10 = 80 个字节的连续的存储区。
对于数组来说,它的每一个元素都被视为相同类型的变量。比如前面的数组 b,其元素
b[0]~b[9]是 10 个 double 型变量,它们的地址就表示成:
&b[0]、&b[1]、&b[2]、……、&b[9]
C 语言中,规定数组名就是该数组占用内存区域的起始地址。由于地址就是指针,所以
数组与指针有着极密切的关系,它们之间几乎可以互换。比如有如下说明:
int a[10], *p;
p = a; /* 由于 a 是地址,这样就能使指针 p 指向数组 a */

int a[10], *p = a;
那么数组元素 a[i](0<= i<=9)可以有如下4种等价的表示方法:
a[i] 或 p[i] (称为下标表示法)
*(a+i) 或 *(p+i) (称为指针表示法)
不过,数组名与指针之间仍然有以下的不同:
– 161 –
C 语言程序设计—示例与习题解析

(1)数组名代表的地址是一个地址常数,指针是一个变量;
(2)数组名不能修改,指针可以改变指向;
(3)在数组名上只能进行加法,以得到数组元素的地址,指针不仅可以进行加、减法,
而且可以进行增 1 或减 1 运算。
例 7-5 编写一个程序,检验数组元素 a[i]的4种等价表示法。
[解] 程序编写如下,图 7-5 所示的是程序的运行结果(为节省篇幅,做了调整)。
#include<stdio.h>
main()
{
int j, k, b[] = {10, 20, 30, 40};
int *bptr = b; /* 说明指针变量 bptr 指向数组 b */
for (j=0; j<=3; j++)
printf ("b[%d] = %d\n", j, b[j]); /* 显示于图 7-5(a)处 */
printf ("\n");
for (k=0; k<=3; k++)
printf ("*(b+%d) = %d\n", k, *(b+k));/* 显示于图 7-5(b)处 */
printf ("\n");
for (j=0; j<=3; j++)
printf ("bptr[%d] = %d\n", j, bptr[j]); /* 显示于图 7-5(c)处 */
printf ("\n");
for (k=0; k<=3; k++)
printf ("*(bptr+%d) = %d\n", k, *(bptr+k)); /* 显示于图 7-5(d)处 */
printf ("\n");
}

图 7-5 数组元素的 4 种等价表示法验证

可以看出,用指针代替数组名,按照下标来访问数组元素是对的;用数组名代替指针,
借助间接访问运算符“*”来访问数组元素也是对的。不过仍要再次强调,数组名是一个地址
常量,不能认为它与指针完全等同。
8.数组做为函数的参数
数组名是一个地址,因此数组做为函数的参数,实质上就是指针做为函数参数的问
题。
例 7-6 编写一个名为 sum 的 int 函数,它以 int 数组及数组的长度为参数,返回诸元
素之和。
[解] 程序编写如下:
– 162 –
第 7 章 指针与一维数组

#include<stdio.h>
sum (int x[ ], int n) /* 函数 sum 的定义 */
{
int j, total = 0;
for (j=0; j<n; j++)
total += x[j];
return total;
}
main()
{
int a[5], b[10], i;
long m;
printf ("\nEnter elements of array’s A: ");
for (i=0; i<5; i++)
scanf ("%d", &a[i]);
printf ("Enter elements of array’s B:");
for (i=0; i<10; i++)
scanf ("%d", &b[i]);
m = sum (a, 5); /* 调用函数 sum */
printf ("sum of A is = %ld\n", m);
m = sum (b, 10); /* 又一次调用函数 sum */
printf ("sum of B is = %ld\n", m);
}
图 7-6 所示的是一次运行的结果。

图 7-6 把数组做为函数的参数

如果在 main 里,说明了两个指针,并把数组名赋给它们,即


int *p1= a, *p2 = b;
那么,调用 sum 时,就可以改为
m = sum (p1, 5);
m = sum (p2, 10);
由于数组与指针密切,可以把 sum 里的数组参数改为指针。比如,
sum (int *p, int n)
然后把函数体里的 x 都改为 p 即可。

– 163 –
C 语言程序设计—示例与习题解析

7.1.3 本章难点
1.C 语言中函数调用的本质都是值传递
当主调函数把一个简单变量(整型、实型、字符型)做为参数传递给被调函数时,是把
这个变量的值赋给被调函数中相应形参的,称这种参数传递为值传递方式。被调函数修改形
参值时,在自己的单元里进行,因此不会影响主调函数实参单元的内容。正因如此,就说值
传递是单向的,它不会自动把修改结果传回去。
当主调函数把一个指针(即地址)做为参数传递给被调函数时,就把这个地址赋给被调
函数中相应的形参。由于传过来的是一个地址,称这种参数传递为地址传递方式。
其实,主调函数传递给被调函数的地址,肯定是做为主调函数中某个指针变量的内容,
也就是这个指针变量的值。因此,在这个意义下,可以说 C 语言中所有的函数调用,都是采
用值的方式来传递参数的。在被调函数这一方,必须用一个指针型的形参来接收这个地址,
因为其他类型的变量是无法存放地址的。于是,被调函数指针型形参里放的是主调函数中某
个变量的地址。这样一来,被调函数就可以通过这个传过来的地址,把加工的结果存放在由
这个地址(指针)指明的单元中,直接带回给主调函数。所以,地址传递方式又常称为是“双
向传递”的。
例 7-7 阅读下面的程序,理解函数参数“值传递”和“地址传递”方式的不同。
(1)“值传递”方式
#include<stdio.h>
mian()
{
int a, b, c;
printf ("&a=%u, &b=%u, &c=%u\n", &a, &b, &c); /* 第 1 条 printf 语句 */
scan ("%d%d", &a, &b);
c = sub (a, b); /* “值传递”的调用方式 */
printf ("a=%d, b=%d, c=%d\n", a, b, c); /* 第 5 条 printf 语句 */
}
int sub (int x, int y)
{
int z;
printf ("&x=%u, &y=%u\n", &x, &y); /* 第 2 条 printf 语句 */
printf ("x=%d, y=%d\n", x, y); /* 第 3 条 printf 语句 */
x = x – 10;
y = y –5;
printf ("x=%d, y=%d\n", x, y); /* 第 4 条 printf 语句 */
z = x – y;
return z;
}
(2)“地址传递”方式
– 164 –
第 7 章 指针与一维数组

#include<stdio.h>
mian()
{
int a, b, c;
printf ("&a=%u, &b=%u, &c=%u\n", &a, &b, &c); /* 第 1 条 printf 语句 */
scan ("%d%d", &a, &b);
c = sub (&a, &b); /* “地址传递”的调用方式 */
printf ("a=%d, b=%d, c=%d\n", a, b, c); /* 第 6 条 printf 语句 */
}
int sub (int *p, int *q)
{
int z;
printf ("&p=%u, &q=%u\n", &p, &q); /* 第 2 条 printf 语句 */
printf ("p=%u, q=%u\n", p, q); /* 第 3 条 printf 语句 */
printf ("*p=%d, *q=%d\n", *p, *q); /* 第 4 条 printf 语句 */
*p = *p – 10;
*q = *q –5;
printf ("x=%d, y=%d\n", *p, *q); /* 第 5 条 printf 语句 */
z = *p – *q;
return z;
}
[解] 图 7-7(a)是输入数据为 17 和 22 时“值传递”时的运行结果;(b)是在同样输
入下,“地址传递”时的运行结果。

图 7-7 “值传递”和“地址传递”的比较

在“值传递”时,从 main 中的第 1 条 printf 语句打印内容看出,系统分配给变量 a 和


b 的存储单元地址分别是 65490 和 65492。输入数据 17 和 22 后,调用 sub 函数,进入到 sub。
从第 2 条 printf 语句打印内容看出,系统分配给形参 x 和 y 的存储单元地址分别是 65484
和 65486。第 3 条 printf 语句把 x 和 y 中的内容打印出来。可以看出,现在 x 和 y 中的内容
与 main 中 a 和 b 里的内容是一样的,都是 17 和 22(因为是值传递,内容当然一样)。第 4
条 printf 语句把运算后 x 和 y 中的内容打印出来,它们现在分别是 7 和 17。返回 main 后,
第 5 条 printf 语句打印出 a 和 b 里的内容。由于 sub 里对形参 x 和 y 的操作不会影响 main
里的 a 和 b,故 a 和 b 里的内容仍保持原来的 17 和 22。
– 165 –
C 语言程序设计—示例与习题解析

在“地址传递”时,从 main 中的第 1 条 printf 语句打印内容看出,系统分配给变量 a


和 b 的存储单元地址分别是 65490 和 65492。输入数据 17 和 22 后,调用 sub 函数,进入到
sub。从第 2 条 printf 语句打印内容看出,系统分配给形参 p 和 q(注意,它们是指针变量)
的存储单元地址分别是 65484 和 65486。第 3 条 printf 语句把 p 和 q 中的内容打印出来。它
们都是地址值,一个是 65490, 一个是 65492。这正是 main 中变量 a 和 b 的地址。
第 4 条 printf
语句把*p 和*q 打印出来。*p 表示指针 p 所指单元的内容,*q 表示指针 q 所指单元的内容,
这正好就是 a 和 b 里的内容:17 和 22。第 5 条 printf 语句把运算后指针 p 所指单元的内容
以及指针 q 所指单元的内容打印出来,它们是 7 和 17。返回 main 后,第 6 条 printf 语句打
印出 a 和 b 里的内容。由于 sub 里所有的操作都是在指针 p 所指单元(就是 a)和指针 q 所
指单元(就是 b)里进行的,因此操作的结果自动被带回到 main。所以,现在 a 和 b 里的内
容是 7 和 17。
2.正确使用取地址运算符“&”和间接访问运算符“*”
“&”运算符能作用的运算对象是除寄存器类变量外的所有变量。它不能作用于表达式或
常量,因为表达式或常量都没有在内存中分配单元。若有如下说明:
int x, y, *ptr;
register int i;
double var;
那么,&x、&y、&var、&ptr 等都是&运算符合法的使用。但&(x+1)、&5、&i 等却都是非法的。
“*”运算符所作用的运算对象必须是已经经过初始化后的指针变量,只有这样,才能保
证指针已指向内存中的确定单元。比如有如下说明:
int x, *p = &x;
那么,x 与*p 在使用上,既可用 x = 100 达到为 x 赋值 100 的目的,也可用*p =100 达到为 x
赋值 100 的目的。
在把变量 p 说明为是一个指针后,使用时应该注意区分 p 与*p 的不同含义:p 代表指针,
*p 代表 p 所指的变量。只能把一个地址赋给 p,只能把一个数据赋给*p。比如有说明:
int s, *p;
那么,
p = &s;
的写法是正确的,它表示把变量 s 的地址赋给 p。但
*p = &s;
的写法是错误的,它并未把变量 s 的地址赋给 p,而是赋给了指针 p 所指的内存位置。但这
一位置在此时完全是一个未知数。
在使用一个指针前,必须先对它进行初始化。比如有程序段:
int *p;
*p = 100;
这是错误的。在还没有给指针进行初始化之前,并不知道它指向哪里。向一个不知道的地址
里面赋一个值,将可能造成意想不到的破坏。
在使用*运算符时,还必须注意运算符间的优先级。若 p 是一个已初始化的指针。那么
操作:
– 166 –
第 7 章 指针与一维数组

(*p)++ 和 *p++
所表述的含义是不一样的。做为单目运算符,*与++的优先级相同,都采取自右向左的结合性。
圆括号能改变优先级。
对于(*p)++,代表的是先把 p 做为运算符*的运算对象,进行*运算,然后以前面的运算
结果做为运算对象,进行++运算。*p 的运算结果是得到所指单元的内容,对++(即加 1)后
回送。所以最终结果是把 p 当前所指单元的内容加 1。
而*p++,按照自右向左的结合性,是先把 p 做为运算符++的运算对象进行++运算,再以
前面的运算结果做为运算对象,进行*运算。不过由于这里的++对 p 而言是后缀单目运算符,
所以应该先让 p 参加*运算,即取出 p 当前所指单元的内容,然后对 p 做++运算,即让指针加
1。
3.指针型函数
“指针型函数” 的另一种说法是“返回指针值的函数”。也就是说,这种函数的返回值
是一个地址。它的一般定义格式是:
<数据类型> *<函数名> (<形式参数表>)
{
<变量说明>
<语句>
}
可以看出,它与一般函数定义的不同点是在<函数名>前加“*”号。仍要强调的是,定
义时在函数名前加上“*”号,只表明这个函数的返回值是一个指定<数据类型>的指针(地址) ,
它并不是函数名的组成部分。
例 7-8 编写一个函数 larger,它返回三个整型参数中大者的地址。在输入数据后,主
调函数调用 larger,然后根据返回值,打印出大者。
[解] 程序编写如下:
#include<stdio.h>
int *larger (int *, int *, int *); /* 函数 larger 的原型 */
main()
{
int a, b, c, *p;
printf ("\nEnter three integer values :");
scanf ("%d%d%d", &a, &b, &c);
p = larger (&a, &b, &c); /* 对函数 larger 的调用 */
printf ("The largest value is : %d\n", *p);
}
int *larger (int *x, int *y, int *z) /* 函数 larger 的定义 */
{
int *temp;
if (*y>*x)
temp = y;

– 167 –
C 语言程序设计—示例与习题解析

else
temp = x;
if (*z>*temp)
temp = z;
return temp;
}
这里,函数 larger 从调用者处接收三个指针型数据,它们分别指向主调函数中的不同单元。
比较过程中,临时指针 temp 总是指向当前大者所在的单元。所以,最后把它返回,表明返回
的是里面存放着最大数的那个单元的地址,即一个指针。可见,函数 larger 调整的是指针的
指向,没有真正交换单元中的内容。图 7-8 给出了 3 次运行的结果。

图 7-8 例 7-8 运行的示例

4.函数的指针
C 语言中,函数经过编译,就被分配在一个内存区域里存放。这个区域的起始地址,就
是该函数的入口地址,也就是这个函数的指针。C 语言把函数名视为该地址的代表,可以把
这个指针存放到一个指针变量中。于是,这个指针变量就指向这个函数。这样,除了通过函
数名来调用某个函数外,还可以利用指向函数的指针变量来调用函数。改变一个指针变量的
值,就能改变它的指向。因此,用指向函数的指针变量来调用函数,将会显得更加灵活与方
便。
说明指向函数的指针变量的一般格式为
<数据类型> (*<变量名>)(<参数类型表>);
其中,<数据类型>是这个指针变量所指函数的类型,即该函数返回值的类型,<参数类型表>是
列出这个指针变量所指函数各个形参的类型。这里有两点注意,第一,在说明函数指针时,
*<变量名>外两侧的圆括号不能省略,这种说明记号表示<变量名>先与*结合,意味是指针变
量,随后的圆括号“(<参数类型表>)”表示该指针变量指向的是函数。第二,如果<参
数类型表>里所列的类型都是 int 型的,那么其中的<参数类型表>可以省略不写,只保留一个
空的圆括号。比如,有如下说明:
int (*p)(); double (*ptr)();
表示指针变量 p 可以指向任何一个形参都是 int 型、返回值是 int 型的函数,指针变量 ptr
可以指向任何一个形参都是 int 型、返回值是 double 型的函数。如果有下面的函数原型:
int fun (int, int);
double fun1 (int, int, int);
那么以下的赋值语句都是正确的:
– 168 –
第 7 章 指针与一维数组

p = fun; ptr = fun1;


第 1 个表示用指针 p 指向函数 fun;第 2 个表示用指针 ptr 指向函数 fun1。但是,如果希望
用一个指针指向下面的函数原型:
int fun2 (double, int, int);
那么,应该如此说明一个比如名为 p1 的指针:
int (*p1)(double, int, int);
然后写赋值语句:
p1 = fun2;
这里要注意,给函数指针变量赋值时,只需写出函数名,而不用列出函数的参数。
在为指向函数的指针赋值后,就可以用这个指针变量来代替函数名进行函数调用了。有
两种等价的书写方式:
(*<函数指针变量名>) (<实际参数表>)

<函数指针变量名>(<实际参数表>)
在下面的例子里,对此有具体的应用。还要再一次强调的是,使用第 1 种书写方式时,
仍然不能省略“*<函数指针变量名>”外面的圆括号。
例 7-9 在 main 函数里,输入三个整型数,通过一个函数指针,逐一调用两数相减、两
数相加、三数相乘三个函数,并分别输出调用的结果。
[解] 根据题意,一共要编写4个函数:一个是计算两数相减,一个计算两数相加,一
个计算三数相乘,再有是函数 main。编写如下:
#include<stdio.h>
int sub (int x, int y) /* 定义计算两数相减的函数 sub */
{
return x−y;
}
int sum (iny x, int y) /* 定义计算两数相加的函数 sum */
{
return x+y;
}
int prod (int x, int y, int z) /* 定义计算三数相乘的函数 prod */
{
return x∗y∗z;
}
main()
{
int (*p) ( ); /* 说明一个函数指针 */
int a, b, c;
printf ("\nEnter three integer value:");
scanf ("%d%d%d",&a, &b, &c);

– 169 –
C 语言程序设计—示例与习题解析

p = sub;
printf ("a−b = %d\n", (*p) (a, b));
p = sum;
printf ("a+b = %d\n", (*p) (a, b));
p =prod;
printf ("a∗b∗c = %d\n", (*p) (a, b, c));
}
由程序中可以看到,因为所定义的三个函数的形式参数虽然个数不一样,但都是 int 型
的,所以在 main 里用语句:
int (*p) ( );
来说明指向函数的指针。图 7-9 所示的是两次运行 main 时的情形。

图 7-9 例 7-9 的两次运行结果

main 中后面的三条 printf 语句,可以改写成如下形式:


printf ("a−b = %d\n", p (a, b));
printf ("a+b = %d\n", p (a, b));
printf ("a∗b∗c = %d\n", p (a, b, c));
这是利用指向函数的指针来调用函数时的另一种方式。不过,通常在涉及函数的指针时,
用前面的形式比较好,因为它明确地表示出是某指针指向的那个函数。使用后面这种形式,
有时就会产生误解:p 到底是指针还是函数名。

7.2 典型示例分析

示例 1 编写一个名为 alt 的无返回值函数,它有两个 double 型参数 x、y。调用后,


把 x 的值变为 x+y,y 的值变为 x∗y。主调函数随之验证实参的新取值。
[解] 既然限定函数 alt 没有无返回值,但主调函数又要验证传递给 alt 实参后,是否
发生了所要求的变化,因此只能将实参的地址传递给函数 alt。为此,编写程序如下:
#include<stdio.h>
void alt (float *x, float *y)
{
float temp;

– 170 –
第 7 章 指针与一维数组

temp = *x;
*x += ∗y; /* 实现 x 变为 x+y */
*y ∗ = temp; /* 实现 y 变为 x∗y */
}
main()
{
float a, b;
printf ("\nEnter two real numbers:");
scanf ("%f%f", &a, &b);
printf ("a and b before are %5.3f and %5.3f\n", a, b);
alt (&a, &b); /* 把变量 a 和 b 的地址传递给 alt */
printf ("a and b after are %5.3f and %5.3f\n", a, b);
}
图 7-10 所示的是该程序两次运行的结果。

图 7-10 示例 1 的两次运行结果

示例 2 在 main 函数中,任意输入 3 个整数值到变量 a、b、c 里。调换它们,使 a 里放


最小值,b 次之,c 里最大。调换工作由其他函数完成。
[解] 要重新安排 a、b、c 里数值的存放顺序,安排工作又要在别的函数里完成,因此
必须在函数间传递 a、b、c 的地址(即指针)。程序编写如下:
#include<stdio.h>
void swap (int *p1, int *p2)
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
void sort (int *t1, int *t2, int *t3)
{
if (*t1 > *t2)
swap (t1, t2);
if (*t1 > *t3)
swap (t1, t3);

– 171 –
C 语言程序设计—示例与习题解析

if (*t2 > *t3)


swap (t2, t3);
}
main()
{
int a, b, c;
printf ("Enter three integers:");
scanf ("%d%d%d",&a, &b, &c);
printf ("exchange before : a=%d, b=%d, c=%d\n", a, b, c);
sort (&a, &b, &c);
printf ("exchange after : a=%d, b=%d, c=%d\n", a, b, c);
}
这里,用 sort 函数进行排序工作。但由于排序时经常需要进行交换,所以又把交换单
独提出来由函数 swap 来完成。这样做的目的,是使各个函数的任务清晰明了。于是,main
调用函数 sort,函数 sort 又去多次调用函数 swap,形成函数间的嵌套调用。图 7-11 所示的
是程序两次运行的结果。

图 7-11 示例 2 的两次运行结果

示例 3 编写一个无返回值的、名为 inv 的函数,它接收一个 float 型数组 x[]和数组


长度 n,然后将其元素的存放顺序倒置。main 函数调用 inv,检查倒置效果。
[解] 倒置算法的基本思想是:把 x[0]与 x[n−1]对换,再把 x[1]与 x[n−2]对换,……,
如此等等。程序编写如下:
#include<stdio.h>
void inv (float *p, int n) /* 函数 inv 的定义 */
{
int j, k, m=(n−1)/2;
float temp;
for (k=0; k<=m; k++)
{
j = n−1−k;
temp = *(p+k); *(p+k) = *(p+j); *(p+j) = temp;
}
}

– 172 –
第 7 章 指针与一维数组

main()
{
int t;
float a[10] = {4.4, 12.5, 1.8, 6.5, 7.3, 4.2, 15.7, 8.4, 9.7, 3.8};
printf ("\nThe original array:");
for (t=0; t<10; t++)
printf ("%5.2f", a[t]);
printf ("\n");
inv (a, 10); /* 对函数 inv 的调用 */
printf ("The array has been inverted:\n");
for (t=0; t<10; t++)
printf ("%5.2f", a[t]);
printf ("\n");
}
图 7-12 所示的是对数组元素倒排前、后的运行结果。

图 7-12 示例 3 的运行结果

示例 4 “直接选择排序”是一种对数组元素进行排序的方法。基本思想是:首先在所
有元素中选出最小者,把它与第 0 个元素交换;然后在余下的元素中再选出最小者,与第 1
个元素交换;依此类推,直至所有元素排序完毕。比如,有实验数组 a 的 10 个元素如下:
8,[ 4,9,12,7,25,17,6,19,3 ]
第 1 次应该把中括号里的每一个元素与 a[0]相比较,找到最小者 3 后,与 a[0]交换,成为
3,4,[ 9,12,7,25,17,6,19,8 ]
第 2 次应该把中括号里的每一个元素与 a[1]相比较,找到最小者 4 后,与 a[1]交换。由于当
前成 a[1]就是最小,所以维持原状,即
3,4,9,[ 12,7,25,17,6,19,8 ]
第 3 次应该把中括号里的每一个元素与 a[2]相比较,找到最小者 6 后,与 a[2]交换,成为
3,4,6,12,[ 7,25,17,9,19,8 ]
第 4 次应该把中括号里的每一个元素与 a[3]相比较,找到最小者 7 后,与 a[3]交换,成为
3,4,6,7,12,[ 25,17,9,19,8 ]
第 5 次应该把中括号里的每一个元素与 a[4]相比较,找到最小者 8 后,与 a[4]交换,成为
3,4,6,7,8,25,[ 17,9,19,12 ]
第 6 次应该把中括号里的每一个元素与 a[5]相比较,找到最小者 9 后,与 a[5]交换,成为
3,4,6,7,8,9,17,[ 25,19,12 ]
第 7 次应该把中括号里的每一个元素与 a[6]相比较,找到最小者 12 后,与 a[6]交换,成为
– 173 –
C 语言程序设计—示例与习题解析

3,4,6,7,8,9,12,25,[ 19,17 ]
第 8 次应该把中括号里的每一个元素与 a[7]相比较,找到最小者 17 后,与 a[7]交换,成为
3,4,6,7,8,9,12,17,19,[ 25 ]
第 9 次应该把中括号里的每一个元素与 a[8]相比较,找到最小者 19 后,与 a[8]交换。由于
当前成 a[8]就是最小,所以维持原状,即
3,4,6,7,8,9,12,17,19,25
到此,整个排序完毕。
按照这一算法思想,编写名为 select 的函数,它有一个 int 型数组参数,一个 int 型
数组长度参数。功能是实现对数组的排序。函数 main 调用函数 select,并打印出排好序的
数组的元素。
[解] 程序编写如下:
void select (int *p, int n) /* 函数 select 的定义 */
{
int i, j, k, ,t , temp;
for (i=0; i<n−1; i++)
{
k = i;
for (j=i+1; j<n; j++) /* 查本范围内的最小者,将下标记录在 k 中 */
if (p[j]<p[k])
k = j;
temp = p[k]; p[k] = p[i]; p[i] = temp; /* 将最小者与本范围首元素交换 */
printf ("%d:", i+1); /* 打印出这趟选择完成后的中间结果 */
for (t=0; t<n; t++)
printf ("%3d", p[t]);
printf ("\n");
}
}
main()
{
int a[10], j, w=0;
printf ("\nEnter the elements of array:\n");
for (j=0; j<10; j++)
scanf ("%d", &a[j]);
select (a, 10); /* 调用函数 select */
w=0;
printf ("The sorted array :\n"); /* 打印出排序后的数组元素 */
for (j=0; j<10; j++)
{
printf ("a[%d]=%d", j, a[j]);

– 174 –
第 7 章 指针与一维数组

w++;
if( w%5 == 0 )
printf ("\n");
}
}
图 7-13 所示的是对输入数据(8,4,9,12,7,25,17,6,19,3)的排序结果。

图 7-13 示例 4 的运行结果

示例 5 阅读下面的函数,给出它的执行结果:
#include<stdio.h>
void sfn (int [ ], int ); /* 函数 sfn 的原型 */
main ()
{
int a[ ] = {32, 27, 64, 18, 95, 14, 90, 70, 60, 37};
printf ("The values in the array are: \n");
sfn (a, 10); /* 对函数 sfn 的调用 */
printf ("\n");
}
void sfn (int x[ ], int n) /* 函数 sfn 的定义 */
{
if (n>0)
{
sfn (&x[1], n−1); /* 函数 sfn 的递归调用 */
printf("%d", x[0]);
}
}
[解] 函数 main 调用函数 sfn 之后,函数 sfn 自身进行递归调用。分析函数 sfn 可以
发现,当用(a, 10)去调用函数 sfn 时,做两件事情:一是先用(&a[1], 9) 调用函数 sfn。
调用完毕返回后,由 printf 语句打印出 a[0]。注意,a[0]是调用时第 1 个参数的首元素。
因此,当用(&a[1], 9) 去调用函数 sfn 时,就转去先用(&a[2], 8) 调用函数 sfn。调用完
– 175 –
C 语言程序设计—示例与习题解析

毕返回后,由 printf 语句打印出调用时第 1 个参数的首元素 a[1]。同样地,当用(&a[2], 8)


去调用函数 sfn 时,就转去先用(&a[3], 7) 调用函数 sfn。调用完毕返回后,由 printf 语
句打印出调用时第 1 个参数的首元素 a[2],……,如此等等。所以,先是一层一层地调用函
数 sfn,直到 n=0 时止。然后就依序一层一层地返回,进行打印工作。由于最后是用(&a[9],
1) 去调用函数 sfn,返回时当然是先返回到 printf ("%d", a[9]);语句,把 a[9]打印出
来,接着打印 a[8],……。因此程序执行完毕,就会把数组 a 诸元素的值逆序打印出来,即
37 60 70 90 14 95 18 64 27 32
整个过程如图 7-14 所示。

图 7-14 示例 5 的递归执行过程

示例 6 “冒泡排序”也是一种对数组元素进行排序的方法。基本思想是:将数组 a[0]
的值与 a[1]比较,若 a[0]> a[1],则交换;然后比较 a[1]与 a[2],……,直到 a[n−1]和
a[n]比较完毕,这称为一趟起泡,即经过这一趟,最大值沉到数组底部,较小值象空气泡似
地逐渐上浮;紧接着对前 n-1 个元素进行第 2 趟起泡;重复以上过程,每次都使起泡区间里
的最大数沉到相应底部。因此最多经过 n−1 次起泡,数组元素就排好序了。
[解] 可以把整个排序过程抽出来,以函数的形式出现,该函数命名为 bubble,由 main
函数来调用。另外,排序时要不断地进行数组元素的交换。因此也可以把数组元素的交换单
独抽出来,又形成一个函数,命名为 swap,由排序函数调用,从而形成一个函数嵌套调用的
结构。下面的冒泡排序程序结构就是这样安排的。程序编写如下:
#include<stdio.h>
void bubble (int *, int); /* 冒泡排序函数的原型 */
main()
{
int a[10] = {21, 6, 4, 8, 18, 12, 89, 68, 14, 37};
int j;
printf ("Data items in original order:\n"); /* 打印排序前数组元素的值 */
for (j=0; j<10; j++)
printf ("%4d", a[j]);
bubble (a, 10); /* 调用冒泡排序函数 bubble */
printf ("\nData items in ascending order:\n"); /* 打印排序后数组元素的值 */
for (j=0; j<10; j++)

– 176 –
第 7 章 指针与一维数组

printf ("%4d", a[j]);


printf ("\n");
}
void bubble (int *x, int n) /* 函数 bubble 的定义 */
{
int j, k, flag;
void swap (int *, int *); /* 交换函数 swap 的原型 */
for (k=1; k<=n−1; k++) /* 控制冒泡的趟数 */
{
flag = 1;
for (j=0; j<=n−k−1; j++)
if (x[j]>x[j+1])
{
flag = 0; /* 如果这趟有交换,则 flag 为 0 */
swap (&x[j], &x[j+1]); /* 调用交换函数 swap */
}
if (flag) break; /*如果这趟没有交换,则 flag 为 1,立即结束循环 */
}
}
void swap (int *p, int *q) /* 函数 swap 的定义 */
{
int temp;
temp = *p; *p = *q; *q = temp;
}
图 7-15 所示的是它的运行结果。

图 7-15 升序冒泡排序的运行结果

这里,是按升序对数组元素进行排序的。在 main 里,也可以安排一个菜单供使用者选


择:如果选 1,则表示按升序排序;如果选 2,则表示按降序排序。在函数 bubble 里,有一
个指向函数的指针,根据它的指向,或进行升序排序,或进行降序排序。这样,整个程序的
功能就更加完备了。如下所示的程序正是这样安排的。
#include<stdio.h>
void bubble (int *, int, int (*) ( ) ); /* 冒泡排序函数的原型 */
int ascending ( ); /* 升序排序判定函数的原型 */
int descending ( ); /* 降序排序判定函数的原型 */

– 177 –
C 语言程序设计—示例与习题解析

main()
{
int a[10] = {21, 6, 4, 8, 18, 12, 89, 68, 14, 37};
int j, ord;
printf ("\nEnter 1 to sort in ascending order, \n"); /* 选择菜单 */
printf ("Enter 2 to sort in descending order:");
scanf ("%d", &ord);
printf ("Data items in original order:\n"); /* 打印排序前数组元素的值 */
for (j=0; j<10; j++)
printf ("%4d", a[j]);
if (ord == 1)
{
bubble (a, 10, ascending); /* 按升序调用冒泡排序函数 bubble */
printf ("\nData items in ascending order:\n");
}
else
{
bubble (a, 10, descending); /* 按降序调用冒泡排序函数 bubble */
printf ("\nData items in descending order:\n");
}
for (j=0; j<10; j++) /* 打印排序后数组元素的值 */
printf ("%4d", a[j]);
printf ("\n");
}
void bubble (int *x, int n, int (*ptr) ( ) ) /* 函数 bubble 的定义 */
{
int j, k, flag;
void swap (int *, int *); /* 交换函数 swap 的原型 */
for (k=1; k<=n−1; k++) /* 控制冒泡的趟数 */
{
flag = 1;
for (j=0; j<=n-k−1; j++)
if ((*ptr) (x[j], x[j+1]) ) /* 这里用到指向函数的指针 ptr */
{
flag = 0; /* 如果这趟有交换,则 flag 为 0 */
swap (&x[j], &x[j+1]); /* 调用交换函数 swap */
}
if (flag) break; /*如果这趟没有交换,则 flag 为 1,立即结束循环 */
}

– 178 –
第 7 章 指针与一维数组

}
void swap (int *p, int *q) /* 函数 swap 的定义 */
{
int temp;
temp = *p; *p = *q; *q = temp;
}
int ascending (int x, int y) /* 函数 ascending 的定义 */
{
return y<x;
}
int descending (int x, int y) /* 函数 descending 的定义 */
{
return y>x;
}
图 7-16 所示的是运行的结果显示。

图 7-16 选择排序的运行结果

程序中的 ascending 和 descending 两个函数,只是对传递过来的参数值进行比较,然


后返回比较的逻辑结果:0 或 1。如果是 0,表示不必进行元素交换;如果是 1,表示需要进
行元素交换。程序中的 if ((*ptr) (x[j], x[j+1]) ),实质是 if ( ascending (x[j],
x[j+1]) ),或者是 if ( descending (x[j], x[j+1]) ),因为指针 ptr 指向的正是这两个
函数之一。

7.3 课外习题、答案与解析

7.3.1 课外习题

一、单选题

1.设有说明:
int a, *p = &a;

– 179 –
C 语言程序设计—示例与习题解析

则下面运算符使用中,不正确的是( ) 。
A.*&a B.&*a C.*&p D.&*p
2.若指针 p 指向的整型变量值为 25,那么打印语句 printf ("%d\n", ++*p);的输出
结果应该是( )。
A.23 B.24 C.25 D.26
3.若有说明语句:
int a[10], *p=a;
那么正确引用数组元素的写法是( )。
A.a[p] B.p[a] C.*(p+2) D.p+2
4.若有说明语句:
int a[10]={1,2,3,4,5,6,7,8,9,10}, *p=a;
那么不能表示 a 数组元素的表达式是( )。
A.*p B.a[10] C.*a D.a[p-a]
5.若有说明语句:
int a[ ]= {1,2,3,4,5,6,7,8,9,10}, *p=a;
那么值为 3 的表达式是( ) 。
A.p+=2, *(p++) B.p+=2, *++p
C.p+=3, *p++ D.p+=2, ++*p
6.已知 a 是 int 型变量,则对指针变量 p 的正确说明和初始化是( ) 。
A.int *p=a; B.int *p=*a; C.int p=&a; D.int *p=&a;
7.有如下语句:
int a, b, c=4, m=6, n=8;
int *p1=&c, *p2=&m, *p3;
a = p1==&c;
b=3 * (−*p1) / (*p2) + 7;
那么执行后变量 a 的值是( )。
A.−1 B.1 C.0 D.4
8.有如下语句:
int a, b, c=4, m=6, n=8;
int *p1=&c, *p2=&m, *p3;
a = p1==&c;
b=3 * (−*p1) / (*p2) + 7;
那么执行后变量 b 的值是( )。
A.5 B.8 C.7 D.9
9.执行下面的程序:
#include<stdio.h>
sub1(char a, char b)
{
char c;

– 180 –
第 7 章 指针与一维数组

c = a; a = b; b = c;
}
sub2(char *a, char b)
{
char c;
c = *a; *a = b; b = c;
}
sub3(char *a, char *b)
{
char c;
c = *a; *a = *b; *b = c;
}
main()
{
char a, b;
a = ’A’; b = ’B’; sub3 (&a, &b); putchar(a); putchar (b);
a = ’A’; b = ’B’; sub2 (&a, b); putchar(a); putchar (b);
a = ’A’; b = ’B’; sub1 (a, b); putchar(a); putchar (b);
}
输出的结果是( ) 。
A.BABBAB B.ABBBBA C.BABABA D.BAABBA
10.若有说明:
int x, *pb;
那么程序中对 pb 正确赋值的表达式应该是( )。
A.*pb=&x B.pb=x C. pb=&x D.*pb=*x
11.若有说明:
int j, k=7, *p=&j;
则与 j = k;等价的语句是( )。
A.j=*p; B.*p=*&k; C.j=&k; D.j=**p;
12.有如下说明和语句:
int a = 10, b = 20, *p, **q;
q = &p; p = &a; p = &b;
printf ("%d, %d\n", *p, **q);
则输出的结果是( ) 。
A.10, 20 B.10, 10 C.20, 10 D.20, 20
13.下面是函数 fun 的 4 种编写形式,功能是交换 x 和 y 中的值,并通过调用返回交换
结果。不能正确完成该功能的函数是( )。
A.fun (int *x, int *y) B.fun (int x, int y)
{ {

– 181 –
C 语言程序设计—示例与习题解析

int *p; int t;


*p=*x; *x=*y; *y=*p; t=x; x=y; y=t;
} }
C.fun (int *x, int *y) D.fun (int *x, int *y)
{ {
int p; *x=*x+*y; *y=*x−*y; *x=*x−*y;
p=*x; *x=*y; *y=p; }
}

二、填空题

1.任何一个数组的数组元素具有相同的名字和 。
2.同一数组中,数组元素之间是通过 来加以区分的。
3.假定数组 a 有 5 个元素,那么它们的名字分别是 。
4.命名一个数组并指定其类型和元素个数,这个过程称为 。
5.指针变量是把另一个变量的 做为其值的变量。
6.能赋给指针变量的惟一整数是 。
7.说明一个指向 float 型数据的指针 fptr,正确的写法是 。
8.假定有说明:
float num[10]={0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9}, *p=&num[5];
那么做完操作:p −= 4;后,指针 p 指向的元素是 。
9.运算符 返回其运算对象的内存地址。
10.fptr 是指向 float 型变量的指针,并进行了初始化。用语句 能够打印出
fptr 所指向的变量的值。
11.有一个名为 exchange 的函数,它有两个分别指向浮点数的指针参数 x 和 y,没有返
回值。那么 exchange 函数头应该写成 。
12.有一个名为 eval 的函数,它返回一个整数值,有一个整型参数 x,一个指向函数的
指针 poly。poly 所指函数有一个整数参数,返回值也是整数。那么 eval 函数的函数头应该
写成 。
13.下面程序的功能是输出数组 a 中最大元素的下标。请完成程序填空:
#include<stdio.h>
main()
{
int j, k;
int a[ ] = {2, −3, 7, 15, 19, −10, 12, 4};
for (j=0, k=j; j<8; j++)
if (a[j]>a[k])
;
printf ("%d\n", k);
}

– 182 –
第 7 章 指针与一维数组

14.下面函数的功能是计算两个整数 x、y 之和,并通过形式参数 z 传回该值。请完成


程序填空:
void add (int x, int y, ① z )
{
② = x + y;
return;
}
15.有程序如下:
#include<stdio.h>
main()
{
int **k, *j, t = 100;
j = &t; k = &j;
printf ("%d\n", **k);
}
其输出结果是 。

三、是非判断题(在括号内打“√”或“×”)

1.指针变量里存放的是地址值,因此指针变量只能是 int 型的。 ( )


2.在 C 语言中,所谓指针型数据,即指该数据是一个地址。( )
3.有一个一维数组 a[10],那么 a 与&a[0]等价。
( )
4.有如下说明:
int b[10], *p = b;
于是,在使用时 b 与 p 就完全等价了。( )
5.数组中的每一个元素相当于一个变量。若要让一个指针变量指向它,必须用“&数组
元素”的方法向该指针变量赋值。 ( )
6.有如下程序段:
int i, j = 2, k, *p = &i;
k = *p + j;
这里出现的两个“*”号,含义是一样的。( )
7.在 C 语言中,每一个变量在内存里占用的单元数都是相同的。
( )
8.通过指针变量,就能间接地得到它所指向的变量的内容。( )
9.说明语句:
int *p, p1, p2;
只说明了一个能指向 int 型变量的指针。
( )
10.有说明:
int *p;
且 p 指向地址为 1500 的单元,那么经过操作“p++;”后,p 将指向 1501 单元。( )

– 183 –
C 语言程序设计—示例与习题解析

11.数组元素可以是不同数据类型的。( )
12.如果初值表中的初值个数少于数组长度,那么 C 语言编译程序会自动把剩余的元素
初始化为初值表中的最后一个值。( )
13.主调函数把单个数组元素传递给修改该元素值的被调函数时,主调函数就能够得到
修改后的值。( )

四、程序阅读题

1.阅读下面的函数,叙述它的主要功能。
float ave (float x[ ], int n)
{
int j; float s;
for (j=0, s=0; j<n; j++)
s = s + a[j];
return (s / n);
}
2.阅读下面的函数,给出它的执行结果。
#include<stdio.h>
main()
{
int j, a[ ] = {2, 4, 6, 8, 10}, int *p = a;
printf ("%d, %d, %d \n", *p, *(p++), *(p+2) );
printf ("%d, %d, %d \n", *p, *p++, *++p );
p = a;
printf ("%d, %d \n", (*p)++, *(p++) );
for (j=0; j<5; j++)
printf ("a[%d] = %d", j, a[j]);
printf ("\n");
}
3.阅读下面的函数,给出它的执行结果。
#include<stdio.h>
main()
{
int total, a[ ] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
total = wst (a, 10);
printf ("Total of array element values is %d \n", total);
}
int wst (int x[ ], int n)
{
if (n == 1)

– 184 –
第 7 章 指针与一维数组

return x[0];
else
return x[n−1]+wst(x, n−1);
}
4.阅读下面的函数,给出它的执行结果。
#include<stdio.h>
fun1 (int a, int b)
{
return a+b;
}
fun2 (int a, int b)
{
return a−b;
}
sub (int (*t) (), int x, int y)
{
return ( (*t) (x, y) );
}
main()
{
int x, (*ptr) ();
ptr = fun1;
x = sub (ptr, 9, 3);
x += sub (fun2, 8, 3);
printf ("%d\n", x);
}
5.阅读下面的函数,给出它的执行结果。
#include<stdio.h>
void bty (int x, int y, int *cp, int *dp)
{
*cp = x + y;
*dp = x – y;
}
main()
{
int a = 8, b = 5, m, n;
bty (a, b, &m, &n);
printf ("%d, %d\n", m, n);
}

– 185 –
C 语言程序设计—示例与习题解析

6.阅读下面的函数,给出它的执行结果。
#include<stdio.h>
void swap1 (int x, int y)
{
int temp;
temp = x; x = y; y = temp;
}
void swap2 (int *p1, int *p2)
{
int *q;
q = p1; p1 = p2; p2 = q;
}
swap3 (int *p1, int *p2)
{
int w;
w = *p1; *p1 = *p2; *p2 = w;
}
main()
{
int x, y, *ptr1, *ptr2;
x = 5; y = 8;
ptr1= &x; ptr2 = &y;
swap1 (*ptr1, *ptr2);
printf ("%d, %d \n", *ptr1, *ptr2);
swap2 (ptr1, ptr2);
printf ("%d, %d, %d, %d \n", x, y, *ptr1, *ptr2);
swap3 (ptr1, ptr2);
printf ("%d, %d, %d, %d \n", x, y, *ptr1, *ptr2);
}
7.阅读下面的函数,给出它的执行结果。
#include<stdio.h>
main()
{
int j, b, c, a[ ] = {1, 10, −3, −21, 7, 13}, *ptr1, *ptr2;
b = c = 1;
ptr1 = ptr2 = a;
for (j=0; j<6; j++)
{
if ( b < *(a+j) )

– 186 –
第 7 章 指针与一维数组

{
b = *(a+j); ptr1 = &a[j];
}
if ( c> *(a+j) )
{
c = *(a+j); ptr2 = &a[j];
}
}
j = *a; *a = *ptr1; *ptr1 = j; j = *(a+5);
*(a+5) = *ptr2; *ptr2 = j;
for (j=0; j<6; j++)
printf ("%d", a[j]);
printf ("\n");
}

五、程序设计题

1.编写一个名为 prd、无返回值的函数,它带有 3 个参数:一个是 int 型数组 x,int


型数组长度 n,以及 float 型指针 p,由它所指变量带回数组 x 中 n 个元素的平均值。函数
main 调用函数 prd,并输出实际数组元素的平均值。
2.编写一个程序,它读入 50 个 float 型数值,或遇到 0 时停止,将它们存放在一个 float
型数组中。打印这些数组元素的和及积。
3.用数组存放前 25 个 Fibonacci(斐波那契)数,并打印输出。
4.编写一个名为 bst 的函数,它以一个 int 型数组及元素个数为参数,功能是找出数
组中的最小元素和最大元素,将最小元素与数组首元素交换,最大元素与数组尾元素交换。
main 函数用实际数组验证之。
5.编写一个名为 max 的函数,它从调用者处接收两个 int 型数值,返回大的值。在 main
函数里,限定使用指向函数的指针来调用 max 函数,并且打印出返回值。
6.模仿示例 6,把所有功能都局限在 main 函数里,实现升序冒泡排序。
7.编写一个名为 search 的函数,它有 3 个参数:一个是 int 型数组,一个是 int 型数
值(数 组的长度) ,一个是待查的 int 型键值。函数功能是在数组中查找具有键值的元素,如
果找到,返回该数组元素的下标值,否则返回−1。在 main 里调用该函数,并打印有关信息。

7.3.2 答案与解析

一、单选题

1.B 注意:运算符“*”不能作用在一般变量上,只能对指针变量起作用。
2.D 注意:++与*的优先级相同,且自右向左结合。因此先做*,得到 25,再做++,得
到 26。故打印结果为 26。
3.C
– 187 –
C 语言程序设计—示例与习题解析

4.B 注意:*p、*a、及 a[p−a]都表示 a[0],而该数组不应该有 a[10]元素!


5.A 注意:这里都是逗号表达式,第 2 个表达式的值是整个逗号表达式的值。
6.D
7.B
8.A 注意:只有语句“b=3 * (−*p1) / (*p2) + 7;”与 b 的取值有关。应先做 3 * (−*p1),
得−12;再做/ (*p2),得−2;最后做+ 7,得 5。
9.A
10.C
11.B
12.D 注意:q 指向指针 p,因此是指针的指针。而指针 p 最终指向变量 b。所以*p 和
**q 都表示是变量 b。所以输出结果为 20,20。
13.B

二、填空题

1.数据类型 2.下标 3.a[0]、a[1]、a[2]、a[3]、a[4]


4.数组的说明 5.地址 6.地址 7.float *fptr;
8.num[1] 9.& 10.printf("%f\n", *fptr);
11.void exchange (float *x, float *y) 12.int eval (int x, int (*poly)
(int))
13.k = j 14.① int * ② *z
15.100

三、是非判断题

1.× 2.√ 3.√ 4.× 5.√


6.× 7.× 8.√ 9.√ 10.×
11.× 12.× 13.×

四、程序阅读题

1.答:该函数从主调函数处接收一个数组和该数组元素个数的两个参数,然后在变量 s
里形成数组元素的累加和,返回平均值。
2.答:输出结果是:
4,2,6
8,6,6
4,2
a[0] = 2 a[1] = 5 a[2] = 6 a[3] = 8 a[4] = 10
3.答:函数 wst 把求数组诸元素之和用递归调用的方式实现。main 函数用数组 a 和数
组元素个数 10 去调用它。从而得到累加和为 55。
4.答:该程序除 main 外,还有 3 个函数,fun1 的功能是返回两个整数之和;fun2 的
功能是返回两个整数之差;sub 的功能是根据函数指针 t 当时的指向,做所指函数规定的功
– 188 –
第 7 章 指针与一维数组

能。在 main 里,指向函数的指针 ptr 被赋值 fun1,即它指向函数 fun1。因此做语句“x = sub


(ptr, 9, 3);”时,意味用实参 9 和 3 去调用函数 fun1,然后把计算的结果 12 赋给变量 x。
紧接着做语句“x += sub (fun2, 8, 3);” ,相当于“x = x+sub (fun2, 8, 3);”。这时,x
为 12。sub (fun2, 8, 3)表示用实参 8 和 3 去调用函数 fun2,计算结果为 5,与 12 相加后
为 17。故最终打印出 17。
5.答:输出结果为 13,3
6.答:3 条 printf 语句的输出结果是:
5,8
5,8,5,8
8,5,8,5
7.答:程序运行后的输出结果为 13 10 –3 1 7 –21。

五、程序设计题

1.答:程序编写如下:
#include<stdio.h>
void prd (int x[], int n, float *p)
{
int j, temp;
temp = x[0]; /* 把 x[0]赋给 temp 做为初值 */
for (j=1; j<n; j++) /* 在 temp 里形成数组元素的累加和 */
temp += x[j];
*p = (float)temp/n; /* 求出平均值,存入 p 所指单元,传给调用者 */
}
main()
{
int num[10], k; t=0;
float ave;
printf ("\nEnter the elements of array :\n");
for (k=0; k<10; k++)
scanf ("%d", &num[k]);
printf ("The elements of array :\n");
for (k=0; k<10; k++)
{
printf ("num[%d]=%d", k, num[k]);
t++;
if( t%5 == 0 )
printf ("\n");
}
prd (num, 10, &ave); /* 调用函数 prd */

– 189 –
C 语言程序设计—示例与习题解析

printf ("The average is = %5.2f\n", ave);


}
这里,main 将数组名、数组长度、以及存放平均值的单元地址传递给函数 prd。prd 计
算出平均值,存放在指定的单元里。
2.答:程序编写如下:
#include<stdio.h>
main()
{
float arr[50], sum = 0, prod = 1;
int j;
for (j=0; j<50; j++)
{
scanf ("%f", &arr[j]);
if (arr[j] == 0)
break;
}
for (j=0; j<50 && arr[j] != 0; j++)
{
sum += arr[j];
prod∗= arr[j];
}
printf ("The product is %f, the sum is %f\n", prod, sum);
}
3.答:程序编写如下:
#include<stdio.h>
main()
{
int j;
int fib[25] = {1, 1};
for (j=2; j<25; j++)
fib[j] = fib[j−1] + fib[j−2];
for (j=0; j<25; j++)
{
if (j%5 == 0)
printf ("\n");
printf ("%10d", fib[j]);
}
}
下图是输出结果。
– 190 –
第 7 章 指针与一维数组

4.答:程序编写如下:
#include<stdio.h>
void bst (int x[ ], int n)
{
int *pmax, *pmin, *p;
pmax = pmin = x;
for (p=x+1, p<x+n, p++) /* 寻找最小元素和最大元素 */
if (*p>*pmax)
pmax = p;
else if (*p<*pmin)
pmin = p;
*p = x[0]; x[0] = *pmin; *pmin = *p; /* 最小元素与数组首元素交换 */
*p = x[n−1]; x[n−1] = *pmax; *pmax = *p; /* 最大元素与数组尾元素交换 */
}
main()
{
int j, a[ ] = {12, 5, 8, 19, 22, −4, 66, −17, 28, 13 };
printf ("The old order of element :\n");
for (j=0; j<10; j++)
printf("%d", a[j]);
bst(a, 10);
printf ("\nThe new order of element :\n");
for (j=0; j<10; j++)
printf("%d", a[j]);
}
下图是输出结果。

5.答:程序编写如下:
#include<stdio.h>
int max (int x, int y)
{
if (x>y)

– 191 –
C 语言程序设计—示例与习题解析

return x;
else
return y;
}
main()
{
int a, b, c;
int (*pmax) (); /* 说明一个指向返回值为 int 型的函数指针 pmax */
printf ("Enter two integers:");
scanf ("%d%d", &a, &b);
pmax = max; /* 让指针 pmax 指向函数 max */
c = (*pmax) (a, b); /* 通过指向函数的指针来调用函数 */
printf ("The maximum is = %d\n", c);
}
6.答:程序编写如下:
#include<stdio.h>
main()
{
int a[10] = {21, 6, 4, 8, 18, 12, 89, 68, 14, 37};
int j, k, temp, flag;
printf ("Data items in original order:\n");
for (j=0; j<10; j++)
printf ("%4d", a[j]);
for (k=1; k<=9; k++) /* 控制冒泡的趟数 */
{
flag = 1;
for (j=0; j<=9−k; j++)
if (a[j]>a[j+1])
{
flag = 0; /* 如果这趟有交换,则 flag 为 0 */
temp = a[j]; a[j] = a[j+1]; a[j+1] = temp;
}
if (flag) break; /*如果这趟没有交换,则 flag 为 1,立即结束循环 */
}
printf ("\nData items in ascending order:\n");
for (j=0; j<10; j++)
printf ("%4d", a[j]);
printf ("\n");
}

– 192 –
第 7 章 指针与一维数组

7.答:程序编写如下:
#include<stdio.h>
int search (int [ ], int, int); /* 函数 search 的原型 */
main()
{
int a[10], j, key, result;
printf("\nEnter elements of array: \n");
for (j=0; j<10; j++)
scanf ("%d", &a[j]);
printf ("Enter integer search key:");
scanf ("%d", &key);
result = search (a, 10, key); /* 对函数 search 的调用 */
if (result != −1)
printf ("Found subscript in array is = %d\n", result);
else
printf ("Value not found !\n");
}
int search (int x[ ], int n, int skey) /* 函数 search 的定义 */
{
int k;
for (k=0; k<n; k++)
if (x [k] == skey)
return k;
return –1;
}
下图是两次运行后的输出结果。

– 193 –
第8章 多维数组与字符串

8.1 本章基本概念及知识点
本章涉及以下 8 个方面的内容:
• 多维数组;
• 字符数组及字符串;
• 字符串处理函数;
• 二维数组与指针;
• 指向字符串的指针;
• 指向行数组的指针变量;
• 动态存储分配函数;
• 带参数的 main 函数。

8.1.1 基本概念
1.多维数组
如果数组的元素又是一个数组,那么就构成所谓的“多维数组”。多维数组中最常见的
是二维数组,它的每个元素都是一个一维数组。于是,该二维数组元素的个数是它的第 1 个
长度(第 1 维),每一个元素(即一维数组)中元素的个数是它的第 2 个长度(第 2 维
)。
2.字符数组
如果一个一维数组的元素类型是 char 的,那么这个一维数组称为“字符数组”。所以,
字符数组是一维数组的特例。
3.字符串
以‘\0’(即 NULL)结尾的字符数组,称为 “字符串”。可以看出,C 语言是用数组来
存放字符串的。既然字符串是一种特定的字符数组,所以有时也把字符串称为“字符串数组”。
4.指针数组
如果一维数组的每一个元素都是指向相同数据类型的指针(地址),那么这个一维数组
就称为“指针数组”。C 语言中有整型数组、实型数组、字符数组等。指针(地址)也是 C 语
言的一种数据类型,因此出现指针数组是不足为奇的。
5.指向行数组的指针变量
– 194 –
第 8 章 多维数组与字符串

如果让一个指针变量指向整个一行(即一个一维数组),以使对该指针变量进行算术运
算时,按一行为移动的单位,那么这个指针变量称为“指向行数组的指针变量” 。要注意,一
个指针变量指向一个一维数组和指向整个一维数组(即行数组)是不同的,它们是两个截然
不同的概念。
6.动态存储分配
程序运行时,根据实际数据所需内存空间的大小,随时申请存储区域,使用完毕后,立
即将占用的存储区域归还。这种做法称为“动态存储分配”。在 C 语言里,动态存储分配是通
过调用函数 malloc( )、free( )来完成的。
7.void 型指针
若不指定一个指针变量所指向数据的确切类型,那么这个指针变量被称做是“void 型指
针”。因此,一个指针变量是 void 型的,表示它是一个通用的指针变量,所指向的变量可以
是任何一种数据类型。在 C 语言里,动态存储分配函数返回 void 型指针。因此,在将它的值
赋给另一个指针变量时,要进行强制类型转换,使之适合于被赋值的指针变量的类型。

8.1.2 本章重点
1.二维数组
多维数组中最简单且最有用的是二维数组。说明一个二维数组的一般格式是:
<数据类型> <数组名> [<长度 1>] [<长度 2>]
其中<数组名>为标识符,是该二维数组中包含的所有元素共同使用的名字;<数据类型>是该
二维数组中所有元素的类型;中括号里的<长度 1>是该数组第 1 维的大小,<长度 2>是该数组
第 2 维的大小。
比如说明语句:
int a[2][4];
说明了一个有 2∗4 = 8 个元素(即变量)的、名为 a 的整型二维数组。这 8 个变量的名
称分别是:
a[0][0]、a[0][1]、a[0][2]、a[0][3]、
a[1][0]、a[1][1]、a[1][2]、a[1][3]
关于二维数组,要注意如下几点。
(1)二维数组的第 1 维表示“行” ,它的取值是 0~<长度 1>−1;第 2 维表示“列” ,它
的取值是 0~<长度 2>−1。
(2)在内存中,二维数组是按行存放的,即在内存里先存放第 1 行的元素,再存放第 2
行的元素,如此等等。
(3)应该把二维数组理解为“元素是一维数组的一个一维数组”。因此,对于前面的数
组 a,应该理解成它有两个元素:a[0]和 a[1]。a[0]有 4 个元素:a[0][0],a[0][1],a[0][2],
a[0][3];a[1]有 4 个元素:a[1][0],a[1][1],a[1][2],a[1][3]。于是,a 是二维数组的
名字,a[0]和 a[1]是一维数组的名字,a[0][0],a[0][1],a[0][2],a[0][3],a[1][0],
a[1][1],a[1][2],a[1][3]是 8 个元素(即变量)的名字。因此,按照 C 语言的规定,a 代
表二维数组在内存的地址,a[0]和 a[1]代表两个一维数组在内存的地址。它们之间有如下关
系:
– 195 –
C 语言程序设计—示例与习题解析

a = a[0] = &a[0][0], a[1] = &a[1][0]


例 8-1 编写一个程序,它打印出 C 编译程序分配给二维数组:
int a[2][4];
的存储区域地址,以验证它们之间存在的各种关系。
[解] 程序编写如下:
#include<stdio.h>
main()
{
int j, k, a[2][4];
printf ("a=%u, a[0]=%u, a[1]=%u\n", a, a[0], a[1]);
printf ("\n");
for (j=0; j<2; j++)
{
for (k=0; k<4; k++)
printf ("&a[%d][%d]=%u\n", j, k, &a[j][k]);
printf ("\n");
}
}
图 8-1(a)是程序的运行结果,(b)是内存中的具体分配写照。可以看出,C 语言编译
分配给该二维数组的存储区域的起始地址是 65478。因此 a、a[0]以及&a[0][0](0 行 0 元素
的地址)都是 65478,而 a[1]和&a[1][0](1 行 0 元素的地址)都是 65486。

图 8-1 例 8-1 的输出结果

(4)如果对全部元素都赋初值,那么说明二维数组时,可以省略第 1 维的长度不写,但
第 2 维的长度不可少。比如有说明:
int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
等价于说明:
int a[ ][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
(5)把二维数组做为函数的参数时,第 1 维长度不用写,第 2 维长度必须写。
例 8-2 编写一个无返回值的、名为 parray 的函数,它接收一个 int 型二维数组,打印
– 196 –
第 8 章 多维数组与字符串

出数组的诸元素取值。在 main 里,以不同初始化形式的二维数组去调用该函数。


[解] 程序编写如下:
#include<stdio.h>
void parray (int [ ] [3]); /* 函数 parray 的原型 */
main()
{
int ay1[2][3] = { {10, 20, 30}, {40, 50, 60} };
int ay2[2][3] = { 10, 20, 30, 40, 50 };
int ay3[2][3] = { {10, 20 }, {40} };
printf ("Values in ay1 by row are:\n");
parray (ay1); /* 第 1 次对函数 parray 的调用 */
printf ("Values in ay2 by row are:\n");
parray (ay2); /* 第 2 次对函数 parray 的调用 */
printf ("Values in ay3 by row are:\n");
parray (ay3); /* 第 3 次对函数 parray 的调用 */
}
void parray (int x[ ][3]) /* 函数 parray 的定义 */
{
int j, k;
for (j=0; j<2; j++)
{
for (k=0; k<3; k++)
printf ("%d,", x[j][k]);
printf ("\n");
}
}
图 8-2 所示的是程序的运行结果。可以看出程序中对 3 个二维数组采用了不同的初始化
方法,第 1 种是分行给二维数组赋初值,把第 1 个花括号里的数据赋给第 1 行的元素,第 2
个花括号里的数据赋给第 2 行的元素,如此等等,这是按行赋初值;第 2 种是把数据都写在
一个花括号里,依行的顺序把它们赋给数组元素,不足的补 0;第 3 种综合了第 1、2 种的处
理思想,每一个花括号赋给一行元素,不足的补 0。

图 8-2 例 8-2 的运行结果

这里要注意,在给出函数原型时,写法:
– 197 –
C 语言程序设计—示例与习题解析

void parray (int [ ] [3]);



void parray (int [ ] [ ]);
等价。但在定义函数头时,只能写成:
void parray (int x[ ][3])
不能写成:
void parray (int x[ ][ ])
否则编译时会出示错误信息:
Size of structure or array not known(结构或数组大小不定)
2.字符数组与字符串
存放字符数据的一维数组是字符数组。因此字符数组中的每一个元素存放一个字符。
由于字符数组是特殊的一维数组,因此用于一维数组初始化的方法,对字符数组都是有
效的。比如,下面的说明语句:
char a[10] = { ' a ' , ' b ' , ' c ' , ' d ' , ' e ' , ' f ' , ' g ' , ' h ' , '
I ' , ' j ' };

char a[ ] = { ' a ' , ' b ' , ' c ' , ' d ' , ' e ' , ' f ' , ' g ' , ' h ' , ' I
' , ' j ' };
是正确的,它表示数组 a 有 10 个元素。又比如,
char b[10] = { ' a ' , ' b ' , ' c ' , ' d ' , ' e ' };
是正确的,它表示数组 b 有 10 个元素,前 5 个元素被赋初值,有 b[0]= ' a ',b[1]= ' b ',
b[2]= ' c ',b[3]= ' d ',b[4]= ' e '。后面的 5 个元素,C 编译自动把它们的初值定
为空字符‘\0’(NULL) 。
对于字符数组还有另外一种特殊的初始化方法:用字符串常量直接对字符数组进行初始
化,这时无须给出字符数组的长度。比如说明语句:
char c[ ] = {"China"}; /*注意,字符串要用双引号括起 */
或省去花括号,直接写成:
char c[ ] ="China";
这种初始化与前面所述的惯常初始化方法不同之处是:C 编译总是为所说明的数组分配
比字符串长度多一个字节的内存量来存放该数组,以便在最后存放一个字符串结束符‘\0’ 。
因此,上面说明的字符数组长度为 6,而字符串的长度为 5。
例 8-3 从键盘上输入一行最多不超过 80 个字符,遇到 Enter 键时结束,然后从正反两
个方向输出这行字符。
[解] 可在程序中说明一个长度为 80 的字符数组,用以接受来自键盘的输入行,然后
再按要求输出。整个程序编写如下:
#include<stdio.h>
main()
{
int j, k;

– 198 –
第 8 章 多维数组与字符串

char s[80];
printf ("Please enter strings:\n");
for (j=0; j<80; j++)
{
s[j] = getchar();
if (s[j] == ’\n’)
break;
}
printf ("\nThe elements in order are:\n");
for (k=0; k<j; k++)
putchar(s[k]);
printf ("\n\nThe elements in reverse order are:\n");
for (k=j−1; k>=0; k− −)
putchar (s[k]);
}
图 8-3 所示的是程序的运行结果,实验数据是输入英文字母从 a~z,总共 26 个。程序
中要注意后两个循环。 由于输入是在遇到 Enter 键时结束,这时元素 a~z 的下标对应值是 0~
25,而控制变量 j 的取值为 26(Enter 是第 27
个输入的字符) 。所以倒数第 2 个 for 循环的循环
终值应该由表达式“k<j”来控制;倒数第 1 个循
环的循环初值应该是“k=j−1” ,循环终值应该由
表达式“k>=0” 来控制。如果设置错误,就会产
生问题。
3.字符串处理函数 图 8-3 例 8-3 的一次运行结果
C 语言中,用双引号括起来的一系列字符,
构成了字符串常量,它以空字符‘\0’结束,作为字符数组来处理。可以在说明语句中把一
个字符串赋给一个字符数组或指向 char 型的指针变量。比如,下面的两个说明语句:
char name[ ] = "John"; ( 或 char name[ ] = { ' J ', ' o ', ' h ', ' n ', ' \0 ' }; )
char *nameptr = "John";
第 1 条语句建立一个有 5 个元素的数组 name,数组中包含字符‘J’, ‘o’, ‘h’, ‘n’
及‘\0’。 第 2 条语句建立起一个指针变量 nameptr,它指向存放在内存某处的字符串
“John”。
C 语言的函数库中,提供了大量的处理字符串的函数。用户在程序设计时可以直接调用
它们,以减轻编程的工作量。不过,这些函数的原型分布在不同的头文件里。因此使用时,
必须搞清楚在程序里应该包含哪个头文件。
例 8-4 编写一个无返回值的、名为 reverse 的函数,它接收一个数组做为参数,利用
标准输出函数 putchar 从后向前打印出数组元素。在 main 里,利用标准输入函数 gets 把字
符读到一个数组中,然后传递该数组给 reverse 函数,从而得到该数组的逆序输出。
[解] 这里涉及的字符串函数 gets 和 putchar 都在头文件<stdio.h>里。
程序编写如下:
– 199 –
C 语言程序设计—示例与习题解析

#include<stdio.h>
main()
{
char s[80];
void reverse (char *); /* reverse 函数的原型 */
printf ("Enter a line of text:\n");
gets (s);
printf ("\n The line in reverse order is :\n");
reverse (s); /* 调用函数 reverse */
}
void reverse (char *p) /* 函数 reverse 的定义 */
{
if (p[0] == ' \0 ' )
return;
else
{
reverse (&p[1]); /* 由此可知,reverse 是一个递归函数 */
putchar (p[0]);
}
}
这里,采用了与第 7 章示例 5 中类似的方法来处理字符数组中元素的逆序输出,即递归
调用函数 reverse 的方法:如果 reverse 发现数组的首元素是结束符‘\0’,那么就返回;否
则,就用下一个元素开始的数组地址去调用 reverse,调用完后,由 putchar 函数打印出首
元素的内容,从而形成字符数组元素的逆序输出。
4.二维数组与指针
二维数组中的元素被视为变量,因此在元素名的前面加上运算符“&”,就得到该元素的
地址。把这个地址赋给同类型的指针变量,那么它就是一个指向这个二维数组元素的指针了。
由于这个指针的类型与二维数组的类型是一致的,因此在这个指针上做加法或减法,就能够
让这个指针指向二维数组中的其他元素。比如有说明:
float a[3][4], *ptr = &a[1][2];
那么 ptr−6 将指向元素 a[0][1],ptr+5 将指向元素 a[2][2],如图 8-4 所示。可以看出,用
指向二维数组元素的指针去得到二维数组中的任一个元素,要认真地去“数一下”才行。

– 200 –
第 8 章 多维数组与字符串

图 8-4 指向二维数组元素的指针

二维数组名是一个地址,把这个地址赋给同类型的指针变量,它就成为一个指向这个二
维数组的指针了。在二维数组的说明中,<长度 1>表示该数组共有多少行,<长度 2>表示每行
里有多少个元素(几列) 。因此,借助于二维数组元素的两个下标值,就可以计算出每一个元
素前有多少个元素。比如,要求二维数组第 i 行第 j 列的元素 a[i][j]前有多少个元素。由
于第 i 行前共有 i 行,即第 0 行到第 i−1 行。这些行的里面共包含 i∗<长度 2>个元素;第 j
列前共有 j 个元素(0~j−1) 。因此,在元素 a[i][j]的前面,共有元素:i∗<长度 2>+j 个。
另一方面,二维数组在内存中是按行存放的,于是,把二维数组理解为是一个行、行相连的
一维数组后,按照对一维数组指针进行算术运算的办法,二维数组元素 a[i][j]的地址就应
该是:
元素 a[i][j]的地址 = 指针变量 + i∗<长度 2>+j
所以,借助于指向二维数组的指针,第 i 行第 j 列的元素 a[i][j]的内容应该是:
元素 a[i][j]的内容 = *(指针变量+ i∗<长度 2>+j)
例 8-5 利用指向二维数组的指针,为一个 int 型的 2 行 3 列数组输入数据,然后打印输出。
[解] 程序编写如下:
#include<stdio.h>
main()
{
int x[2][3], *xptr = x;
int j, k;
printf ("Enter array elements:");
for (j=0; j<2; j++)
for (k=0; k<3; k++)
scanf ("%d", xptr + j∗3+k);
printf ("\nThe elements of array are :\n");
for (j=0; j<2; j++)
for (k=0; k<3; k++)
printf ("%8d", *(xptr + j∗3+k) );
}
这里,指针 xptr 指向二维数组 x。于是,xptr+ j∗3+k 就是二维数组第 j 行第 k 列元素
x[j][k]的地址,而*(xptr + j∗3+k) 就是二维数组第 j 行第 k 列元素 x[j][k]的内容。

– 201 –
C 语言程序设计—示例与习题解析

5.指向字符串的指针
指向字符串的指针其实质与一维数组的指针是相同的。在指针的应用中,大量地都是利
用它来处理字符串问题。比如前面的例 8-4 是处理字符串的,也可以用指向字符串的指针来
解决它。这时程序如下:
#include<stdio.h>
main()
{
char s[80];
void reverse (char *); /* reverse 函数的原型 */
printf ("Enter a line of text:\n");
gets (s);
printf ("\n The line in reverse order is :\n");
reverse (s); /* 调用函数 reverse */
}
void reverse (char *p) /* 函数 reverse 的定义 */
{
char *q = p; /* 让 q 记住该字符串的起始位置 */
while (*p != ’\0’) /* 让 p 指向字符串的结束 */
p++;
for (− −p ; p >= q; p− − )
putchar(*p);
}
在函数 reverse 里,指针 p 接收的是字符串(也即字符数组)的地址,即它指向该字符
串。然后让临时指针 q 记住该字符串的起始位置,并通过循环让指针 p 指向字符串结束处。
这样一来,由指针 p 的一步步后退,就可以逆向输出字符,直到 p 等于 q 时为止。如图 8-5
所示。

图 8-5 字符串指针的应用

这里要注意,循环使指针 p 指向字符串结束符‘\0’,但输出应该在它的前一个位置开
始进行。因此,输出时的 for 循环里,对控制循环的 p 要先“− −”才行。
例 8-6 利用指向字符串的指针,编写字符串 s1、s2 的比较函数,方法是从下标为 0 的
第 1 个字符开始顺序比较对应字符。比较进行到出现不同字符或到串尾时结束。如果两个字
符串相等,则返回 0;如果比较到 s1 中有一个字符的 ASCII 码值大于 s2 中对应顺序字符的
ASCII 码值,则返回 1,否则返回−1(不准用 C 语言提供的字符串比较函数 strcmp)

[解] 程序编写如下:
#include<stdio.h>

– 202 –
第 8 章 多维数组与字符串

main()
{
int i;
char x[15], y[15];
printf ("Enter first stirng:");
gets(x);
printf ("Enter second string:");
gets(y);
i = strcmp1(x, y); /* 调用自己定义的 strcmp1 函数 */
switch (i)
{
case 0:
printf (" First string = Second string\n");
break;
case 1:
printf ("First string > Second string \n");
break;
case -1:
printf ("First string < Second string \n");
break;
};
}
int strcmp1 (char *s1, char *s2) /* 这是自己定义的 strcmp1 函数 */
{
while (*s1)
if (*s1 − *s2)
return *s1−*s2;
else
{
s1++;
s2++;
}
return 0;
}
在函数 strcmp1 的 while 循环里,利用了 C 语言中所有字符串都是以空字符‘\0’(ASCII
码值为 0)作为结束这一规定。因此 while (*s1)只有到字符串 s1 结尾时才会取值 false。
如果真是这样,那么程序返回 0,以表示两个字符串相等。如果在比较中两个字符串不等,
那么就借助语句“return *s1−*s2;” ,一方面提前结束运行,另一方面返回所需的信息。图
8-6 所示的是该程序 3 次运行的结果。
– 203 –
C 语言程序设计—示例与习题解析

图 8-6 例 8-6 的 3 次运行结果

6.动态存储分配
在 C 语言中,动态存储分配是通过函数 malloc()和 free()来完成的。
(1)函数 malloc()
函数 malloc()的调用形式是:
void *malloc (unsigned size)
也就是说,malloc()的参数是要求分配的内存字节数,返回一个指向被分配内存区域的
void 型指针。如果未能分配到内存,malloc()将返回 NULL 指针。
(2)函数 free()
函数 free()的调用形式是:
void free (void *ptr)
也就是说,free()的参数是一个以前由动态存储分配得到的、现在不再使用的内存地址。
该内存区域归还给系统后,就又可以另派用途,重新参加分配。
在程序中使用这两个函数时,要包含头文件<stdlib.h>。
例 8-7 编写一个程序,动态申请存储区,输入字符串。在将该字符串逆序输出后,释
放该存储区。
[解] 程序编写如下:
#include<stdlib.h>
# include<string.h>
main()
{
char *s;
int j;
s = (char *) malloc (80); /* 申请 80 个字节的存储区 */
if (!s)
{
printf ("memory faild\n");
exit(1);
}
gets (s);

– 204 –
第 8 章 多维数组与字符串

printf ("%s\n", s);


for (j=strlen(s)−1; j>=0; j− −) /* 调用求字符串长度函数 strlen */
printf ("%c", s[j]);
free (s); /* 释放前面申请到的存储区 */
}
在使用 malloc()和 free()时,要注意几点。第一,由于 malloc()正常返回时,返回的
是一个 void 型指针,这个指针可以指向任何类型的数据。因此在把它赋给一个具体的指针时,
必须进行强制类型转换。比如这里是把申请到的地址赋给 char 型的指针变量 s,因此要在
malloc 前冠上(char *),以完成强制类型转换的目的。第二,使用 malloc()时,可能会由于
申请不到存储区而返回空指针。使用空指针有可能导致系统被破坏,因此在程序使用前,必
须对申请的结果进行检验,以确保返回的是一个有效的指针。程序中的安排:
if (!s)
{
printf ("memory faild\n");
exit(1);
}
起到了检验作用。其中,exit()函数用来立即终止程序的执行,无条件返回到操作系统。
当调用 exit()返回 0 时,表示正常中断。这里返回 1,表示出错。不过,在后面的举例中,
为节省篇幅, 有时略去了这段检验程序。 第三,函数 free 所释放的存储区,
一定是通过 malloc()
申请到的,不能让它释放其他存储区。

8.1.3 本章难点
1.指针数组
指针数组的元素是指向相同类型数据的一个个指针。说明指针数组的一般格式与说明数
组的格式类同,即
<数据类型> *<数组名> [<长度>]
差别仅在<数组名>前冠上“*”号,表明该数组的每一个元素都是指针,这些指针所指的是由
<数据类型>标明的数据。比如,
char *name[4];
说明 name[0]、name[1]、name[2]、name[3]四个元素都是指向 char 型数据的指针。
指针数组常用来指向字符串,使对字符串的处理更方便灵活;也可以用来指向若干个函
数,便于在不同条件下调用不同的函数。
例 8-8 编写一个无返回值的、名为 seq 的函数,它有一个 char 型指针数组参数,以及
表示数组元素的个数的 int 型参数。功能是对所指字符串进行排序,然后按升序将诸字符串
打印输出。通过 main 函数,对函数 seq 功能的正确性进行验证。
[解] 程序编写如下:
#include<stdio.h>
#include<string.h>
main()

– 205 –
C 语言程序设计—示例与习题解析

{
void seq (char *[ ], int); /* 函数 seq 的原型 */
char *suit[4]={"Hearts","Diamonds","Clubs","Spades"};
int j;
for (j=0; j<4; j++)
printf ("%d: %s\n", j+1, suit[j]);
seq (suit, 4); /* 对函数 seq 的调用 */
}
void seq (char *x[ ], int n) /* 函数 seq 的定义 */
{
char *temp;
int i, j, k;
for (i=0; i<n−1; i++)
{
k = i;
for (j=i +1; j<n; j++)
if (strcmp(x[k], x[j])>0) /* 这里直接调用 C 函数库的 strcmp 函数 */
k = j;
if (k != i)
{
temp = x[i]; x[i] = x[k]; x[k] = temp;
}
}
printf ("\n");
for (j=0; j<n; j++)
printf ("%d: %s\n", j+1, x[j]);
}
在函数 seq 里,对传递过来的指针数组所指的字符串,直接使用 C 语言提供的字符串比
较函数 strcmp 进行比较。比较采用的是第 7 章示例 6 里提及的
冒泡排序法:在每一趟比较范围内,把最大者沉到下面,小者
逐渐上浮。但不同的是,在这里进行排序时,不是直接交换字
符串,而是改变指针数组中指针的指向;最后让数组元素 x[0]
里的指针指向最“小”的字符串,让 x[n] 里的指针指向最“大”
的字符串。图 8-7 所示的是实验数据的运行结果。
例 8-9 说明一个指向 3 个函数的指针数组,根据选择调
图 8-7 例 8-8 的运行结果
用不同的函数,完成相应的功能。
[解] 程序编写如下:
#include<stdio.h>
void fun0 ();

– 206 –
第 8 章 多维数组与字符串

void fun1 ();


void fun2 ();
main()
{
void (*ptr [3]) () = {fun0, fun1, fun2}; /* 指向函数的指针数组的说明和初始化 */
int x;
printf ("Enter a number between 0 and 3:");
scanf ("%d", &x);
while (x>=0 && x<=2)
{
(*ptr[x]) (x); /* 对指向函数的指针数组元素的应用 */
printf ("Enter a number between 0 and 3:");
scanf ("%d", &x);
}
printf ("You entered 3 to end\n");
return;
}
void fun0 (int i) /* 函数 fun0 的定义 */
{
printf ("You entered %d so function0 was called.\n", i);
}
void fun1 (int i) /* 函数 fun1 的定义 */
{
printf ("You entered %d so function1 was called.\n", i);
}
void fun2 (int i) /* 函数 fun2 的定义 */
{
printf ("You entered %d so function2 was called.\n", i);
}
这里,定义了 3 个象征性的函数:fun0,fun1,fun2。调用它们,将打印出不同的信息。
要注意指向函数的指针数组的说明的正确写法:
void (*ptr [3]) () = {fun0, fun1, fun2};
它类似于指向函数的指针的写法,只是把函数名的地方用数组(数组名和长度)取代。另外
也要关注这种指针数组的使用方法:
(*ptr[x]) (x);
由于数组元素是指向函数的指针,因此,*ptr[x]表示当前数组元素 ptr[x]所指的函数。而后面
跟随的(x),是调用该函数时需要的参数。图 8-8 是 3 次运行的结果:第 1 次键入的是 0,于是
打印出信息:You entered 0 so function0 was called.;第 2 次键入的是 1,于是打印出信息:
You entered 1 so function1 was called.;第 3 次键入的是 3,于是打印出信息:You entered
– 207 –
C 语言程序设计—示例与习题解析

3 to end。

图 8-8 例 8-9 的 3 次运行结果

2.指向行数组的指针变量
说明一个指针变量,让它指向二维数字中的行数组,其一般形式为
<数据类型>(*<变量名>) [<长度>]
其中,用圆括号括住的*<变量名>表示该变量是一个指针,它指向的行数组里拥有<长度>所规
定的元素个数,每一个元素都具有<数据类型>所指明的数据类型。
在说明一个指向行数组的指针变量时,必须用圆括号括住“*”和<变量名>。否则,由
于方括号的优先级高于星号,变量名应该先与方括号结合,成为了一个指针数组。比如,
int *ptr[5];
说明 ptr 是一个数组,有 5 个元素:ptr[0]、ptr[1]、ptr[2]、ptr[3]、ptr[4]。每个元素
里存放的是 int 型变量的地址。而语句:
int (*ptr)[5];
说明 ptr 仅是一个指针,它指向一个有 5 个 int 型数据元素的一维数组。
说明一个指向二维数组中行数组的指针变量且把二维数组名赋给它后,其目的仍是希望
得到该二维数组的每一个元素。假设有说明:
int x[4][8], (*ptr)[8] = x;
x 是一个二维数组,ptr 是一个指向一行有 8 个 int 型元素的指针,且把 x 赋给了 ptr。为了
能得到该二维数组的每一个元素,应该理解下面几个问题。
(1)由于 ptr 指向的单位是一个行数组,因此对它直接进行算术加、减法时,移动的单
位就是一整行,而不是某个数据类型的长度。比如, “ptr+2”表示跳过 x 的第 0、1 两行后,
得到第 2 行的首地址。
(2)ptr 是指向一个有 8 个元素的行数组的指针,又把二维数组 x 的地址赋给了它,这
意味要通过 ptr 把 x 降为一个一维数组来处理;按照一维数组与指针的关系,这个“新”的
一维数组共有 4 个元素,每个元素可以表示为
*(ptr + i) , 0 <= i < 4。
(3)但是,“*(ptr + i)”元素实质上代表的是 x 中的一个 int 型一维数组。由于只有
一维数组的地址,才能代表这个一维数组。因此“*(ptr + i)”是这个 int 型一维数组的地
址,即指针。既然“*(ptr + i)”是这个 int 型一维数组的指针,于是按照一维数组与指针
的关系,这个一维数组每个元素的地址就可以表示为
*(ptr + i) + j , 0 <= j < 8。
(4)既然“*(ptr + i) + j”是第 i 行第 j 列元素的地址,于是,该元素的内容就应该
表示为
– 208 –
第 8 章 多维数组与字符串

*( *(ptr + i) + j )
3.带参数的主函数 main
通常,用 C 语言编写程序时,主函数 main 是不带参数的,即 main 的函数头是:
main()
若为这个程序文件起名 program,那么编译后在 DOS 下运行时,只需键入命令行:
program ( 表示回车)
但有时也需要把必要的参数传递给主函数 main,这就涉及到带参数的主函数 main 了。C
语言规定,当主函数 main 带参数时,只能有两个参数:argc 和 agrv。这时 main 的函数头是:
main(int argc, char *argv[ ])
其中,形参 argc 用来保存命令行中实际参数的个数,它至少是 1,因为程序名是命令行的第
1 个实参;形参 argv 是一个 char 型的指针数组,它的每一个元素分别指向命令行中实际参
数的字符串。若为这个程序文件起名 program,那么编译后在 DOS 下运行时,就需要键入带
有实参的命令行:
program 实参 1 实参 2 …… 实参 n ( 表示回车)
这时,argc 里记录的是数字 n+1;指针数组 argv 的元素 argv[0]里是字符串“program”的
地址,argv[1]里是字符串“实参 1”的地址,……,argv[n]里是字符串“实参 n”的地址。
例 8-10 编写一个带参数的主函数,程序起名为 add,后面带有两个整数。功能是求这
两个数的和。
[解] 程序编写如下:
#include<stdio.h>
main(int argc, char *argv[ ])
{
int x, y, sum;
if (argc<3)
{
printf ("You must enter correct command line. Try again.\n");
exit(0);
}
x = atoi (argv[1]);
y = atoi (argv[2]);
sum = x + y;
printf ("%d + %d = %d\n", x, y, sum);
}
程序中,上来先判别所发命令行的参数个数是否正确,如果 argc<3,说明参数少,故要求
重新输入命令行。命令行输入正确后,把 argv[1]、argv[2]里的形如整数的字符串参数通过调
用 C 语言的函数 atoi,转换成为整数。这样才能相加。图 8-9 给出了在 DOS 提示符下,3 次运行
的结果。第 1 次求出 5+3 的和;第 2 次少了一个参数,让重新发命令;第 3 次求出 7+15 的和。

– 209 –
C 语言程序设计—示例与习题解析

图 8-9 例 8-10 的 3 次运行结果

8.2 典型示例分析

示例 1 阅读下面的程序,述说其功能。
#include<stdio.h>
main()
{
char x[20] = "C language";
char y[20] = "very good";
char *from = x, *to = y;
printf ("before: %s %s\n", x, y);
for (; *from !='\0'; from++, to++)
*to = *from;
*to = '\0';
printf ("after: %s %s\n", x, y);
}
[解] 这实际上是一个字符串拷贝程序,它把 x 里的内容复制到 y 中。由于字符串的长短不一,
因此在把 x 复制到 y 后,应该在 y 的结尾处安放字符串结束符,这是绝对不能忘记的事情。
示例 2 编写一个程序,它读进一行字符,把它们存放在有 80 个元素的字符数组中。如
果在读完 80 个字符前遇到行结束符‘\n’ ,也应停止输入。然后把所有空格和数字移出后,
显示所有字符。
[解] 程序编写如下:
#include<stdio.h>
main()
{
int k;
char line[80];
printf ("Please enter a string:\n");
for (k=0; k<80 && (line[k] = getchar ()) != ' \n ' ; k++)
;

– 210 –
第 8 章 多维数组与字符串

for (k=0; k<80 && line[k] != ' \n ' ; k++)


if (line[k] != ' ' && line[k]< ' 0 ' || line[k]> ' 9 ' )
putchar (line[k]);
putchar ( ' \n ' );
}
题目中的要求:“如果在读完 80 个字符前遇到行结束符‘\n’,也应停止输入”,是由程
序中的条件:
k<80 && (line[k] = getchar ()) != ' \n '
来控制的。打印输出时,由条件:
line[k] != ' ' && line[k]< ' 0 ' || line[k]> ' 9 '
来跳过空格和数字。图 8-10 所示的是一次运行的结果。

图 8-10 示例 1 的一次运行结果

示例 3 考虑下面的程序:
#include<stdio.h>
int x[ ][3] = {{1, 4, 2},{15, 17, 202}};
main()
{
int j, *p;
p = x[0];
for (j=0; j<6; j++)
{
printf ("%dth: %u", j, p);
p++;
}
printf ("\n");
p = x[1];
for (j=0; j<3; j++)
printf("%dth: %d", j, *p++);
printf ("\n");
}
假设第 1 个输出是:0 th: 404,且 sizeof(int)是 2。请给出该程序产生的整个输出。
[解] 按照题目的假定,最先,把二维数组 x 的第 0 行地址“404”赋给了指针 p,所以
打印出 0th: 404。由于该数组是 int 型的,所以 p++后,p 就成为 406。于是接着将打印出来
1th: 406,……,如此等等。最终输出如图 8-11 所示。

– 211 –
C 语言程序设计—示例与习题解析

图 8-11 示例 2 的输出

示例 4 编写一个无返回值函数 strip,它接收两个 char 型数组,通过第 2 个数组,返


回第 1 个数组移去最前面所有空格后的结果。用 main 函数对 strip 的功能加以验证。
[解] 实现 strip 功能,只需扫视第 1 个数组,直至第 1 个非空格字符。然后把第 1 个
数组里的内容拷贝到第 2 个数组里面去即可。程序编写如下:
#include<stdio.h>
void strip (char *s, char *t) /* 函数 strip 的定义 */
{
for ( ; *s == ' ' ; s++) /* 跳过前导空格 */
;
while (*s != ' \0 ' ) /* 把 s 中的内容拷贝到 t */
{
*t = *s;
s++;
t++;
}
*t = ' \0 ' ; /* 在 t 的结尾处加上字符串结束符 */
}
main()
{
char x[80], y[80];
printf ("Enter a string:\n");
gets (x);
strip (x, y); /* 对函数 strip 的调用 */
puts (y);
}
在函数 strip 中,把数组 s 里的内容拷贝到数组 t 里去时,可以像程序中那样安排,也
可以写成:
while (*s != ' \0 ' )
{
*t++ = *s++;
}
*t = ' \0 ' ;
更简略的写法是:
while ( *t++ = *s++)

– 212 –
第 8 章 多维数组与字符串

;
*t = ' \0 ' ;
但无论如何,都不要忘记在把 s 的内容全部拷贝到 t 后,在 t 的最后添加字符串结束符
‘\0’,否则,输出函数就无法停止下来了。
示例 5 假定输入为 found name,阅读下面的程序,给出输出结果。
#include<stdio.h>
char s[15];
int find (int k ) /* 函数 find 的定义 */
{
return (s[k++]);
}
void inter () /* 函数 inter 的定义 */
{
int j;
gets (s);
for (j=0; j<=2; j++)
main();
}
main()
{
static int i = 0;
int w;
if (i == 0)
{
i++;
inter(); /* 调用函数 inter */
}
w = find (i++); /* 调用函数 find */
putchar (w);
}
[解] 首先要解释几点: (1)由于函数 find 和 inter 都用到字符数组 s,故把 s 的说明
放在所有函数外面进行; (2)在 mian 中,变量 i 被说明为是静态的,在第 1 次执行 main 时,
它的值被初始化为 0。正因为如此,第 1 次执行 main 就去做语句:
{
i++;
inter();
}
它使变量 i 改变取值为 1。另外去调用函数 inter。(3)在 inter 里,由 for 循环 3 次调用
main 时,这时由于变量 i 的取值不为 0,故每次都是去做语句:
– 213 –
C 语言程序设计—示例与习题解析

w = find (i++);
putchar (w);
即先是调用函数 find,然后由 putchar 打印出相应的字符。
因此,执行该程序后,打印出: “ound”。
示例 6 编写一个返回 int 型值的函数 sey,它接收一个字符串,返回该串的长度(不
允许使用 C 语言提供的函数 strlen)。在 main 里调用函数 sey,加以验证。
[解] 对于函数 sey,只需在扫视字符串时进行记数即可,一直进行到遇见字符串结束
符为止。整个程序编写如下:
#include<stdio.h>
main()
{
char str[80];
int k;
printf ("Enter a string:");
scanf ("%s", str);
k = sey (str); /* 对函数 sey 的调用 */
printf ("The amount of character is %d\n", k);
}
int sey (char *s) /* 函数 sey 的定义 */
{
int i;
for ( ; *s != '\0'; s++)
++i;
return i;
}

8.3 课外习题、答案与解析

8.3.1 课外习题

一、单选题

1.有说明语句:int a[ ][4] = {1, 5, 8, 7, 12, 22, 9, 41, 55, 27};,则数组 a


第 1 维的长度应该是( ) 。
A.2 B.3 C.4 D.5
2.设有说明语句:char str[100]; int k = 5;,则错误引用数组元素的形式是( ) 。
A.str[10] B.*(str+k) C.*(str+2) D.*((str++) + k)
3.main 函数可以有两个参数,一个名为 argc,一个名为 argv。对于 argv,正确的写
– 214 –
第 8 章 多维数组与字符串

法应该是( )。
A.int argv B.char argv[ ] C.char *argv[ ] D.char **argv[ ]
4.若有说明语句如下:
int x[3][4] = {{10, 15}, {8, 4}, {17, 22}};
int (*ptr)[4] = x;
则能够代表数值 4 的表达式是( ) 。
A.*x[1]+1 B.ptr++, *(ptr+1) C.x[2][2] D.ptr[1][1]
5.若有说明语句:
char s[20] = "international", *ps = s;
则下面选项中不能代表第 1 个字符 t 的表达式是( ) 。
A.ps+2 B.s[2] C.ps[2] D.ps += 2, *ps
6.若有说明语句:int x[3][4];,那么关于 x、*x、x[0]、&x[0][0]的正确叙述是( ) 。
A.只有 x、x[0]和&x[0][0]表示的是元素 x[0][0]的地址
B.x、*x、x[0]、&x[0][0]均表示元素 x[0][0]的地址
C.只有 x[0]和&x[0][0]表示的是元素 x[0][0]的地址
D.只有&x[0][0]才表示元素 x[0][0]的地址
7.有以下程序段:
char a[ ] ="international", *p;
for (p = a; p<a + 10; p += 2)
putchar (*p);
其执行后的结果是( ) 。
A.internatio B.nentoa C.itrai D.nento
8.有以下程序段:
int x[2][3] = {{1, 2, 3},{4, 5, 6}}, m, *p = &x[0][0];
m = (*p) * (*(p+2) ) * (*(p+4) );
执行后,变量 m 的取值是( ) 。
A.15 B.12 C.18 D.10
9.下面给出的数组说明中,合法的是( )。
A.int a[ ] ="string"; B.int a[5] = {0, 1, 2, 3, 4, 5};
C.char a[ ] = {0, 1, 2, 3, 4, 5}; D.char a ="string";
10.有以下程序段:
char str[ ] = "abt\n\012\\\";
printf ("%d\n", strlen(str) );
执行后输出的结果是( )。
A.11 B.7 C.6 D.5
11.设有如下说明:
char x [ ] = {"abcdefg"};
char y [ ] = { ' a ' , ' b ' , ' c ' , ' d ' , ' e ' , ' f ' , ' g ' };
则正确的叙述是( )

– 215 –
C 语言程序设计—示例与习题解析

A.数组 x 和数组 y 等价 B.数组 x 的长度是 7


C.数组 y 的长度 7 D.数组 y 的长度是 8

二、填空题

1.有说明语句:
int x[ ][4] = {{1},{2},{3}};
那么元素 x[1][1]的取值是 。
2.有一个函数,定义为
int length ( char *s)
{
char *p = s;
while (*p++)
;
− −p;
return (p−s);
}
该函数的功能是 。
3.若有程序段如下:
char s1[4] ="35", *p = s1;
printf ("%c\n", *(p+1));
那么执行后输出的结果是 。
4.若有程序段如下:
int k, x[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
for (k=0; k<3; k++)
printf ("%d", x[k][2−k]);
那么执行后输出的结果是 。
5.若有说明:
int s[4][5], (*ptr)[5] = s;
那么用 ptr 表示数组元素 s[2][4]的正确写法应该是 。
6.若有语句:
int s[4][5], *ptr[4], j ;
for (j=0; j<4; j++)
ptr[j] = s[j];
那么用 ptr 表示数组元素 s[2][4]的正确写法应该是 。
7.下面程序的功能是不停地输入一串字符,遇‘\n’符结束。统计输入过程中所输入
数字、字母以及其他字符出现的个数。最后打印统计结果。请进行程序填空。
#include<stdio.h>
main()
{

– 216 –
第 8 章 多维数组与字符串

char ch;
int digit = 0, ① ;
while ((ch = getchar())!=’\n’)
{
if ( ② )
digit++;
else if (ch >= ' a ' && ch<= ' z ' || ch>= ' A ' && ch<= ' Z ' )
alpha++;
else
③ ;
}
printf ("digit=%d, alpha=%d, other=%d\n", digit, alpha, other);
}
8.下面程序的功能是希望输出的结果是“hgindooG” ,请进行程序填空。
#include<stdio.h>
main()
{
char x[ ] ="Goodnight!", *p = ① ;
while (− −p>=&x[0])
putchar( ② );
putchar( ' \n ' );
}
9.下面程序的功能是:main 函数通过循环把数组 x 的诸元素赋值如下图(a)所示,s[0]
未放数据。在 printf 语句里去调用函数 ind 时,把传递过去的字符‘E’放在了 s[0]里,然
后从尾部向数组头部扫视,查找哪个元素与 s[0]相等,然后把找到元素的下标返回。所以,
输出的结果应该是 3,见下图的(b) 。请进行程序填空。
#include<stdio.h>
int ind (char *p, char ch, int n)
{
int k;
*p = ch; k = ① ;
while (ch<p[k])
② ;
return k;
}
main()
{
char x[6];
int j;

– 217 –
C 语言程序设计—示例与习题解析

for (j=1; j<=5; j++)


x[j] = ' A ' + j + 1;
printf ("%d\n", ind (x, ③ , 5));
}

10.下面程序的功能是输出字符数组中最大元素的下标,请进行程序填空。
#include<stdio.h>
main()
{
int p, k;
char s[ ] = "acrgbutpyks";
for (p=0, k=p; p<strlen(s); p++)
if (s[p]>s[k])
① ;
printf ("%d\n", ② );
}
其中 strlen 是 C 语言里提供的求字符串长度的函数。

三、是非判断题(在括号内打“√”或“×”)

1.若有说明:int (*p)[4];,那么 p 是一个有 4 个元素的指针数组。( )


2.若有说明:
char *ptr[4] = {"abcde","fghijkl","xyz","rstuvw"};
那么数组 ptr 的 4 个元素中存放的是 4 个字符串的首地址。( )
3.若有说明:
char a[10], *p = a;
那么*(p+5)表示指针现在指向数组 a 的元素 a[5]。 ( )
4.若有说明:char a[ ] ="abcde";,那么字符数组 a 的长度是 6。 ( )
5.有说明语句:char b[10];,那么 b ="China";把字符串“China”赋给了数组 b。
( )
6.有说明语句:char *p;,那么 p ="China";使指针 p 指向了字符串“China”。 ( )
7.C 语言提供有字符串拷贝函数 strcpy,功能是把第 2 个参数的内容复制到第 1 个参
数。若有说明:char s[12];,那么语句:strcpy (s,"Welcome Home");就把“Welcome Home”
字符串拷贝到了字符数组 s 中 。( )
8.有说明:int s[3][5], j, k;,那么通过下面的循环:
for (j=0; j<=3; j++)
for (k=0; k<=5; k++)

– 218 –
第 8 章 多维数组与字符串

s[j][k] = 0;
就把这个 int 型二维数组的所有元素都赋值为 0 了 。( )
9.有说明:int *p;,只需执行语句:p = (int *) malloc (16);,p 就指向一个动态
申请到的 16 个字节的存储区域了。 ( )
10.有说明:int s[3][5], *p = s;,只需执行语句:free (p);,就把指针 p 所指向
的存储区域归还给系统了。 ( )

四、程序阅读题

1.阅读程序,给出它的输出结果。
#include<stdio.h>
main()
{
int j;
char str[14] = {"I love you!"};
for (j=0; j<14; j++)
if (str[j] != ' \0 ' )
printf ("%c,", str[j]);
else
break;
printf ("\b \n");
}
2.阅读程序,解释它的功能。
#include<stdio.h>
main()
{
char text[100][80];
int i, j, k;
for (i=0; i<100; i++)
{
printf ("%d:", i + 1);
gets (text[i]);
if ( ! *text[i])
break;
}
for (j=0; j<i; j++)
{
for (k=0; text[j][k]!= ' \0 ' ; k++)
printf ("%c", text[j][k]);
printf ("%c", ' \n ' );

– 219 –
C 语言程序设计—示例与习题解析

}
}
3.阅读程序,解释它的功能。
#include<stdio.h>
main(int argc, char *argv[ ])
{
int t, j;
printf ("\n");
for (t=0; t<argc; t++)
{
j = 0;
while (argv[t][j])
{
printf ("%c", argv[t][j]);
++j;
}
printf ("\n");
}
}
4.阅读程序,解释它的功能和输出结果。
#include<stdio.h>
main()
{
char a[ ] ="language", b[ ] ="prograde";
char *p1 = a, *p2 = b;
int j;
for (j=0; j<7; j++)
if (*(p1+j) == *(p2+j))
printf ("%c", *(p1+j));
}
5.阅读程序,给出运行后的输出结果。
#include<stdio.h>
main()
{
int s[4][5]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
int (*ptr)[5] = s;
int *p[4], k;
for (k=0; k<4; k++)
p[k] = s[k];

– 220 –
第 8 章 多维数组与字符串

printf ("%d\n", *(*(ptr+1)+4));


printf ("%d\n", *(p[3]+2));
}
6.阅读下面的程序,叙述其功能和输出的结果。
#include<stdio.h>
#include<string.h>
main()
{
int k;
char str [10], str1 [10];
printf ("Please enter No.1 string:");
gets (str);
for (k=0; k<4; k++)
{
printf ("Please enter No.%d string:", k+2);
get (str1);
if (strcmp(str, str1)<0 )
strcpy (str, str1);
}
printf ("%s\n", str);
}

五、程序设计题

1.编写一个程序,在一维数组里输入一句英文,统计该句里出现的单词个数(单词之
间是用空格分隔的) 。
2.编写一个名为 ctou 的、无返回值的函数,它有一个 char 型的指针参数。功能是把
传递过来的字符串里的小写字母转换成大写。main 调用该函数进行验证。
3.输入一字符串,检查是否回文(所谓“回文” ,指该字符串正反序相同,如 LeveL)。
若是,则输出“Yes” ,否则输出“No”。
4.编写一个名为 substr 的函数,它的参数是两个 char 型指针 p1、p2。功能是返回字
符串 p1 中第 1 次出现的、与 p2 字符串匹配的子串的起始位置。如果没有匹配的子串存在,
则返回−1。
5.编写一个无返回值、名为 jst 的函数,它的一个参数是字符数组,一个参数是该数
组的长度。功能是摘出字符数组中下标为偶数的字符,存放在原数组内,形成一个新的字符
串。在 main 中调用该函数,验证其功能。
6.编写一个程序,它能打印出如下形式的杨辉三角形(比如要求打印 10 行 ):
1
1 1
1 2 1

– 221 –
C 语言程序设计—示例与习题解析

1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
…… …… ……
其各行数值间有如下关系:
(1)各行第 1 个数都是 1;
(2)各行最后 1 个数都是 1;
(3)从第 3 行起,除第 1 个数和最后一个数外,其余各数都是上一行同列及前列位置上的两个
数之和。 比如第 6 行里的第 1 个 10,是它的上一行同列位置上的数 6 和前列位置上的数 4 之和。
7.编写一个无返回值函数 mst,它接收两个字符串 s1 和 s2,功能是把 s2 拼接在 s1 的
后面,在 s1 里形成一个新的字符串。main 用实例对函数 mst 的功能进行验证。
8.编写一个带参数的主函数,功能是将在 DOS 提示符下键入的命令行中的参数逆序打
印出来,中间用逗号和一个空格分隔开(不打印文件名)。

8.3.2 答案与解析

一、单选题

1.B
2.D 注意:*(str+k)相当与 str[5],*(str+2) 相当与 str[2],而 D 要对 str 进行运
算,这是不允许的,因为数组名是一个地址常量。
3.C
4.D 注意:数组元素 x[1][1]的数值为 4。在 A 里,x[1]是数组第 1 行的地址,*x[1]
表示第 1 行第 0 个元素的内容,因此*x[1]等于 8;所以*x[1]+1 表示数组 x 第 1 行第 0 个元
素的内容加 1,等于 9,并不代表 x[1][1]。在 B 里,ptr++使指针 ptr 指向数组 x 的第 1 行;
由于 ptr 是指向行数组的指针, 因此(ptr+1)使 ptr 进一步指向了数组 x 的第 2 行,于是*(ptr+1)
是第 2 行的地址,也不代表 x[1][1]。对于 D,ptr[1][1]相当于*(*(ptr+1)+1),正是数值为
4 的数组元素。
5.A 注意:这里的 B 和 C 能代表第 1 个字符 t 是没有疑问的;D 里的 ps += 2,把指
针 ps 移到指向第 1 个字符 t,所以*ps 正是第 1 个字符 t。唯独 ps+2 是一个指针运算,得到
第 1 个字符 t 所在的地址,并不是该地址里的内容。所以 A 是错误的。
6.B
7.C
8.A 注意:由于 p 指向 x 的元素 x[0][0],故*p 即 x[0][0],取值为 1;*(p+2) 即 x[0][2],
取值为 3;*(p+4) 即 x[1][1],取值为 5。3 者相乘,结果是 15。
9.C 注意:A 里是把一个字符串赋给 int 型数组;B 里数组的长度是 5,赋予了 6 个初
值;D 里的 a 是一个 char 型变量,不能接受字符串。
10.B
11.C

– 222 –
第 8 章 多维数组与字符串

二、填空题

1.0
2.求字符串的长度 注意:只有当指针 p 已经指到字符串结束符时,while 循环才会
停止。所以 p 应该往回退一步再与 s 相减,正好得到该字符串的长度。
3.字符‘5’
4.3 5 7 注意:要打印的内容是元素 x[0][2]、x[1][1]、x[2][0]的取值,因此是
3、5 和 7。
5.*(*(ptr + 2 ) + 4 )
6.*(ptr[2] + 4 )
7.① alpha = 0, other = 0 ② ch>= ' 0 ' && ch<= ' 9 ' ③ other++
8.① &x[8] ② *p
9.① n ② k− − ③ ‘E’
10.① k = p ② k

三、是非判断题

1.× 2.√ 3.× 4.√ 5.× 6.√


7.× 注意:字符数组 s 只有 12 个字节,没有存放字符串结束符‘\0’的地方。
8.× 注意:在此,数组下标超界,行下标不能等于 3,列下标不能等于 5。
9.√ 10.×

– 223 –
C 语言程序设计—示例与习题解析

四、程序阅读题

1.答:程序输出如下图所示。在没有遇到字符串结束符‘\0’时,由 for 循环逐个把


数组 str 里存放的字符串内容打印出来,每个字符间有一个逗号。停止输出后,由最后的
printf 语句中的转义字符‘\b’退一格,退到惊叹号后的逗号处,用空格代之,使惊叹号后
的逗号抹去。

2.答:该程序设置了一个 100 行、每行 80 列的二维数组。先通过变量 i 的控制,不断


地用二维数组的行“text[i]”去调用函数 gets,达到输入一行行信息的目的。要注意,二
维数组的行“text[i]”是一个一维数组的地址,而调用函数 gets,正需要一个 char 型的指
针做为参数。这种输入停止的条件是“! *text[i]”。 “*text[i]”是第 i 行首元素的内容,
所以只有该行的首元素里是一个空字符时,条件“! *text[i]”才成立。所以,在调用函数
gets 时,往一行里输入一个空行时,整个输入结束。这时,在变量 i 里记录下总共输入了多
少行。这一信息在下面的输出里是有用处的。下图是输入 5 行后的运行结果。
3.答:这是一个带参数的主函数。其功能是把执行
时输入的命令行中的各个参数打印出来。程序中,变量 t
控制打印的个数,变量 j 控制每个参数字符串的打印(因
为字符串总是以空字符‘\0’结尾)。下图是把该程序起
名为 dh1,在 DOS 提示符下输入命令行:
dh1 hello zong da hua
后,程序先打印出程序名。可以看出,打印的程序名是一
个包含完整文件路径的名字,即
E:\DAHUA\ZDHJ\DH1.EXE
然后再分别打印出其他参数。

4.答:该程序的功能是对数组 a、b 的元素逐一进行比较,并把相等的元素加以输出。


因此,程序的运行结果是输出 ga。
5.答:ptr 是一个指向二维数组中行数组的指针,第 1 个打印语句中的“*(*(ptr+1)+4)” ,
表示二维数组的元素 s[1][4],故打印出 10;p 是一个指针数组,有 4 个元素。通过循环,
把二维数组 s 的 4 个行的地址(s[0]~s[3])赋给了 p 的 4 个元素,使每个元素指向 s 数组
的一个行。因此第 2 个打印语句中的*(p[3]+2) 表示二维数组的元素 s[3][2],故打印出 18。
6.答:该程序的功能是:要求使用者在输入了两个字符串(一个放在 str 里,一个放
– 224 –
第 8 章 多维数组与字符串

在 str1 里)之后,利用 C 语言提供的字符串比较函数进行比较,然后通过 C 语言提供的字符


串拷贝函数 strcpy,把大者放在 str 里,然后再输入新的字符串进行比较,比较进行 4 次以
后,将 5 个字符串里最大的字符串输出。下图是一次运行的结果。

五、程序设计题

1.答:程序编写如下:
#include<stdio.h>
main()
{
char str[80];
int j, n = 0, w = 0;
char ch;
printf ("Please input a sentence:\n");
gets (str); /* 输入一句英文 */
for (j=0; (ch=str[j]) != ' \0 ' ; j++)
if (ch == ' ' )
w = 0;
else if (w == 0)
{
w = 1;
n++;
}
printf ("There are %d words in this line.\n", n);
}
这里,开辟一个长为 80 个字符的一维数组 str,由它存放输入的英文句子。输入后,从
前往后扫视数组中的每一个元素,直到遇见字符串结束符‘\0’时停止。扫视时,对字符的
性质加以判别。如果是空格就跳过去;如果是字符,则表示进入了一个单词,需要记数。但
有两个问题需要考虑:一是不能光用空格数来反映单词数,因为有时可能会出现连续几个空
格。比如下图第 2 次运行时,单词 banner 与 on 之间出现了 3 个空格;二是不能遇到字符就
记数,对于每一个单词,只能记数一次。因此,在程序里设置了一个标志 w,遇到空格时,
就把 w 置为 0,表示在单词之外。一旦遇到字符且 w 等于 0,则表示是进入一个新的单词,于
是让变量 n 记数,同时把 w 改置为 1,表示现在位于单词之内。

– 225 –
C 语言程序设计—示例与习题解析

2.答:程序编写如下:
#include<stdio.h>
void ctou (char *); /* 函数 ctou 的原型 */
main()
{
char str[80];
printf ("Enter a string:");
gets (str);
ctou (str); /* 调用函数 ctou */
printf ("The string after conversion is: %s\n", str);
}
void ctou (char *p) /* 函数 ctou 的定义 */
{
while (*p != ' \0 ' )
{
if (*p>= ' a ' && *p<= ' z ' )
*p−= 32;
++p;
}
}
程序中最重要的就是根据指针 p 的指点,检查当前的字符是否落在区间
*p>= ' a ' && *p<= ' z '
里。如果是,则表明该字符是英文小写。于是,把它的 ASCII 码值减 32,得到相应字母大写
的 ASCII 码值。
3.答:编程的思想是接收输入后,从字符串的两头开始做字符比较,并向中间逼近。
如果最终左右字符对应的下标相等,那么该字符串是回文,否则就不是回文。程序编写如下:
#include<stdio.h>
main()
{
int length, j, k;
char *x;
scanf ("%s", x);

– 226 –
第 8 章 多维数组与字符串

length = strlen (x);


for (j=0, k=length−1; j<=k; j++, k− −)
if (x[j] == x[k])
continue;
else
break;
if (j<k)
printf ("No\n");
else
printf ("Yes\n");
}
程序中利用 C 语言提供的求字符串长度的函数 strlen,求得字符串 x 的长。于是,用变
量 j(从 0 往大方向增长)和 k(从 length−1 往小方向减少)控制两个方向字符的比较。比
较相同时,就继续进行(continue),比较不同时,就立即停止(break) 。这样,根据 j 和 k
的关系,就能判断应该输出什么信息。
4.答:程序编写的思想如下图所示,即以 p1 所指字符串的每一个字符开始,与字符串
p2 的每一个字符进行比较。如果在 p1 里找到与 p2 匹配的子串,那么 p2 肯定已进行到结束
符‘\0’ ;其他情况就是没有匹配者。

整个程序编写如下:
#include<stdio.h>
int substr(char *p1, char *p2)
{
int k;
char *ptr1, *ptr2;
for (k=0; p1[k] != ' \0 ' ; k++)
{
ptr1 = &p1[k]; /* ptr1 总是记录这次扫视 p1 字符串开始的位置 */
ptr2 = p2; /* ptr2 总是记录 p2 字符串开始的位置 */
while (( *ptr2 != ' \0 ' ) && *ptr2 == *ptr1)
{

– 227 –
C 语言程序设计—示例与习题解析

ptr1++; ptr2++; /* 既然匹配,指针就前进 */


}
if (! *ptr2) /* 若是由于*ptr2== ' \0 ' 而结束 while 循环,那么找到了匹配者 */
return k;
}
return –1;
}
5.答:整个程序编写如下:
#include<stdio.h>
void jst (char *x, int n)
{
int j, k = 0;
for (j=0; j<n; j++)
if (j%2 == 0)
{
x[k] = x[j];
k++;
}
x[k] = ' \0 ' ;
}
main()
{
char s[ ] ="abcdefghijklmnopqrstuvwxyz";
jst (s, 26);
puts (s);
}
在函数 jst 里,让变量 j 控制对字符数组的扫视,从 0 到 n−1(即<n)
;用变量 k 控制回
存元素的下标。只要满足条件“j%2 == 0”,就表明下标为 j 的这个元素是需要回存的。因此
在 k 的指点下进行回存,同时 k 往前进一步。特别要注意在扫视完整个数组后,必须往新字
符串的最后安放一个字符串结束符,即执行语句“x[k] = ' \0 ' ;”。不然,返回到 main 后,
puts 无法打印出正确的结果。
6.答:程序中应该先说明一个 10∗10 的二维数组,用以在它的里面形成杨辉三角形,
然后打印输出。整个程序编写如下:
#include<stdio.h>
main()
{
int j, k, a[10][10];
for (j=0; j<10; j++)
{

– 228 –
第 8 章 多维数组与字符串

a[j][j] = 1; /* 置每行的最后一个元素取值为 1 */
a[j][0] = 1; /* 置每行的第 1 个元素取值为 1 */
}
for (j=2; j<10; j++) /* 在二维数组 a 里形成杨辉三角形 */
for (k=1; k<=j-1; k++)
a[j][k] = a[j−1][k−1] + a[j−1][k];
for (j=0; j<10; j++) /* 打印输出二维数组 a 里的杨辉三角形 */
{
for (k=0; k<=j; k++)
printf ("%4d", a[j][k]);
printf ("\n");
}
printf("\n");
}
下图是程序运行的结果。

7.答:整个程序编写如下:
#include<stdio.h>
void mst (char *, char *); /* 函数 mst 的原型 */
main()
{
char str1[80], str2[80];
printf ("Enter two strings:");
scanf ("%s%s", str1, str2);
mst (str1, str2); /* 调用函数 mst */
printf ("%s\n", str1);
}
void mst (char *s1, *s2) /* 函数 mst 的定义 */
{
while (*s1 != ' \0 ' ) /* 扫视到字符串 s1 尾结束符处 */
++s1;
for ( ; *s2 != ' \0 ' ; s1++, s2++) /* 把字符串 s2 拼接到 s1 的后面 */
*s1 = *s2;

– 229 –
C 语言程序设计—示例与习题解析

}
8.整个程序编写如下:
#include<stdio.h>
main(int argc, char * argv[ ])
{
while (− − argc>0)
{
printf ("%s", argv[argc]);
printf ("\n");
}
}
如果现在把该文件起名为 zdh1。下图是在 DOS 提示符下发命令行:
zdh1 ABCD EFGH IJKL MNOP
后的运行结果。

– 230 –
第9章 结构、共用、枚举

9.1 本章基本概念及知识点
本章涉及以下 8 个方面的内容:
• 结构类型;
• 共用类型;
• 成员运算符(“.” )与指向运算符(“−>”
);
• 结构型数组;
• 指向结构的指针;
• 结构做为函数的参数;
• 枚举类型;
• 用 typedef 定义类型的别名。

9.1.1 基本概念
1.结构类型与结构名
把多个不同数据类型的变量聚合在一起,取一个名字,这个聚合体就被定义成了一种新
的数据类型。这样定义的数据类型统称为“结构类型” 。定义时所取的名字,就是这种结构类
型的名字,称为“结构名” 。可以看出,一个结构名实际上是一种新的、用户自定义的数据类
型的名字。
2.结构成员
聚合在一个结构类型中的各种不同数据类型的变量,称为“结构成员”。结构成员可以
是任何基本数据类型(int、char、double 等),也可以是数组、指针等。
3.结构型变量
在定义了一种结构型的数据类型后,就可以说明具有这种数据类型的变量了。这种变量,
就是具有这种数据类型的“结构型变量” ,简称“结构变量”。
4.结构型数组
以相同结构型变量做为元素的数组,称为“结构型数组”,简称“结构数组”。
5.指向结构的指针变量
说明了一个结构变量后,编译程序就为其在内存分配一个连续的存储区域。把该区域的
– 230 –
第 9 章 结构、共用、枚举

起始地址赋给具有相同结构类型的指针变量后,它就指向了这个结构变量。因此称其为是“指
向结构的指针变量” 。
6.自引用结构类型
如果在定义的结构类型中,包含有一个指针成员,且该指针指向所定义的这种结构类型,
那么这种结构类型称为“自引用结构类型” 。
7.共用类型与共用名
让多个不同数据类型的变量共享同一内存区域,取一个名字,它们被定义成一种新的数
据类型。这样定义的数据类型统称为“共用类型” ,也称“联合类型”。定义时所取的名字,
就是这种共用类型的名字,称为“共用名” ,或称“联合名”。可以看出,一个共用名实际上
是一种新的、用户自定义的数据类型的名字。
8.共用成员
在一个共用类型中共享同一内存区域的变量,称为“共用成员”。共用成员可以是基本
数据类型,也可以是数组、结构等。
9.共用型变量和共用型数组
在定义了一种共用型的数据类型后,就可以说明具有这种数据类型的变量了。这种变量
就是具有这种数据类型的“共用型变量”,简称“共用变量”。以相同共用型变量为元素的数
组,称为“共用型数组” ,简称“共用数组” 。
10.指向共用的指针变量
说明了一个共用变量后,编译程序就按其最大成员所需字节数在内存中分配给它一个连
续的存储区域。把该存储区域的起始地址赋给具有相同共用类型的指针变量后,它就指向了
这个共用变量,因此称为“指向共用的指针变量”。
11.枚举类型与枚举名
用若干个标识符组成的集合来表示若干整数常量值,取一个名字,这个集合就被定义成
了一种新的数据类型。这样定义的数据类型统称为“枚举类型” 。定义时所取的名字,就是这
种枚举类型的名字,称为“枚举名” 。可以看出,一个枚举名实际上是一种新的、用户自定义
的数据类型的名字。
12.枚举常量与枚举常量数值
枚举类型中所列出的一个个标识符,称为“枚举常量”。整数常量值集合中的数值,称
为“枚举常量数值” 。除非指定了起始值,否则第 1 个枚举常量对应的枚举常量数值为 0,以
后每一个枚举常量对应的枚举常量数值依次递增 1。
13.枚举型变量
在定义了一种枚举型的数据类型后,就可以说明具有这种数据类型的变量了。这种变量
就是具有这种数据类型的“枚举型变量” ,简称“枚举变量”。
14.类型别名
C 语言允许为已经存在的数据类型起另外一个名字代之。这就是所谓的“类型别名”。在
C 语言里,通过保留字 typedef 来为一个数据类型起别名。
15.位段
对于结构型或共用型中的 unsigned 类型和 int 类型成员,C 语言允许为它们指定所需存
储的二进制位数。指定了存储位数的结构型或共用型中的这些成员称为“位段” ,也称为“位
– 231 –
C 语言程序设计—示例与习题解析

域”。利用位段可以用最少的二进制位存储数据,达到节省内存的目的。

9.1.2 本章重点
1.结构类型与成员运算符“.”
在 C 语言中,结构类型是向用户提供的定义新数据类型的一种方法。编程人员要先利用
它定义出新的数据类型,然后才能说明具有这种数据类型的变量。
用结构类型来定义新数据类型的一般格式是:
struct <结构名>
{
<结构成员表>
};
其中,struct 是 C 语言的保留字,用以标识一个结构定义的开始;<结构名>是要定义的结构
型新数据类型的名字,它是一个符合 C 语言规定的标识符;<结构成员表>由如下形式的多个
变量说明语句组成:
<数据类型> <变量名>;
每一个代表该结构中的一个成员,以及成员所具有的数据类型。要特别注意,在做结构定义
时,必须把<结构成员表>括在花括号内,右花括号后面要跟随一个分号“;” ,表示定义的结
束。比如,有如下的结构定义:
struct goods
{
char item[30];
int code;
int stock;
};
它定义了一个结构型的、名为 goods(商品)的新数据类型。该数据类型包括 3 个结构成
员:一个是字符数组 item(品名) ,一个是整型 code(编号),一个是整型 stock(库存)。
完成结构定义后,名为 goods 的数据类型就存在了,于是可以说明具有这种数据类型的变
量了。
比如,如下的变量说明语句:
struct goods biscuit;
它表示 biscuit 是一个结构变量,具有 goods 类型。这时,就可以利用成员运算符“.”,来
引用 biscuit 的 3 个成员分量了。比如,可以为 3 个成员分量赋值:
strcpy (biscuit.item,"original");
biscuit.code = 10125;
biscuit.stock = 35;
要注意,由于成员 item 是一个字符数组,在变量说明后为它赋值,应该利用 C 语言提
供的字符串拷贝函数 strcpy,而不能直接写成:
biscuit.item ="original";
这是绝对错误的!
– 232 –
第 9 章 结构、共用、枚举

上面是“先定义结构,再说明结构变量”的做法。在 C 语言里,还可以有“定义结构的
同时说明结构变量”和“定义一个无名结构,同时说明结构变量”的做法。比如,下面是定
义结构的同时说明结构变量并且为结构变量赋初值的例子。
struct goods
{
char item[30];
int code;
int stock;
}biscuit = {"original", 10125, 35};
又比如,下面是定义一个无名结构,同时说明结构变量的例子。
struct
{
char item[30];
int code;
int stock;
}biscuit, bread;
这里,biscuit 和 bread 都是这个无名结构的结构变量。显而易见,这种做法只能是一次性
地用于变量说明,以后要再追加这种数据类型的变量是不可能的,因为没有提供这种数据类
型的名称。
例 9-1 编写一个程序,用于说明结构定义、结构变量的说明以及赋予结构变量初值的
正确使用方法。
[解] 程序编写如下:
#include<stdio.h>
main()
{
struct goods
{
char item[30];
int code;
int stock;
}biscuit = {"original", 10125, 35}, sweets = {"cough gum drops", 11254, 40};
struct goods bread;
strcpy (bread.item,"meat floss");
bread.code = 17680;
bread.stock = 22;
printf ("biscuit: %s, %d, %d\n", biscuit.item, biscuit.code, biscuit.stock);
printf ("sweets: %s, %d, %d\n", sweets.item, sweets.code sweets.stock);
printf ("bread: %s, %d, %d\n", bread.item, bread.code, bread.stock);
}

– 233 –
C 语言程序设计—示例与习题解析

图 9-1 所示的是该程序的一次运行结果。

图 9-1 例 9-1 的一次运行结果

2.结构数组
由相同结构型变量为元素的数组,就是结构数组。因此,结构数组既要遵循结构的使用
方法,也要遵循数组的使用方法。即
(1)要先定义结构,再说明结构数组;或在定义结构的同时说明结构数组;
(2)结构数组元素由数组名和下标区分;
(3)结构数组元素通过成员运算符引用其每一个结构成员。
比如,
struct goods
{
char item[30];
int code;
int stock;
}fruit[20];
是定义结构的同时说明结构数组的例子。它表示名为 fruit 的数组(一个一维数组)共有 20
个元素: fruit[0]到 fruit[19],每一个元素都是 goods 型的。fruit[0].item、fruit[0].code、
fruit[0].stock 分别是 goods 型变量 fruit[0]的成员 item、code、stock。
若在上述定义的基础上,有说明:
struct goods cake[20];
则是先定义结构,后说明结构数组的例子。这里是一个名为 cake 的结构数组,有 20 个元素,
每一个都是 goods 型的变量。
3.指向结构的指针和指向运算符“−>”
在说明了一个结构变量后,把该变量的地址赋给一个同类型的指针变量,那么这个指针
就是指向结构的指针了。
在说明了一个结构数组后,把该数组的地址赋给一个同类型的指针变量,那么这个指针
就是指向结构数组的指针了。比如有如下定义和说明:
struct goods
{
char item[30];
int code;
int stock;
};
struct goods rice, cake[20], *ptr1, *ptr2;

– 234 –
第 9 章 结构、共用、枚举

这里的 rice 是一个 goods 型的结构变量,cake 是一个 goods 型的结构数组,ptr1 和 ptr2


是两个 goods 型的指针变量。如果有:
ptr1 = &rice; ptr2 = cake;
那么指针 ptr1 就指向结构变量 rice,指针 ptr2 就指向结构数组 cake。注意,由于 rice 是
一个变量,因此只有在它的前面冠以取地址运算符“&”,才能得到它的地址,把这个地址赋
给 ptr1,才能使指针 ptr1 指向它;但对于结构数组 cake 而言,数组名本身就是地址,因此
只需将 cake 赋给 ptr2,就使指针 ptr2 指向它了。
一个指针指向结构变量后,可以用指针代替结构变量名,通过间接访问运算符(“*”)
来引用该结构变量的每一个成员,即
(*ptr1).item, (*ptr1)code, (*ptr1)stock。
另外,C 语言还提供所谓的指向运算符“−>”(由减号和大于号拼接而成,中间不允许有
空格)也可以用它更为形象地来引用该结构变量的每一个成员,即
ptr1−>item, ptr1−>code, ptr1−>stock。
这种引用的一般形式是:
<结构指针变量名> −> <成员名>
含义是“这个结构指针变量当前所指结构的这个成员”。这是一种比较好的引用形式。
一个指针指向结构数组后,可以通过++或− −,让其指向原数组元素的下一个或前一个。
也就是说,指针指向结构数组后,对其做算术加减法,跨越的距离是以结构数组元素所需字
节数为单位的。比如在做了操作:
ptr2++;
后,ptr2 就指向 cake[1]了。
例 9-2 编写程序,利用指向结构数组的指针,以及指向运算符“−>”
,为它们的成员赋
初值。
[解] 整个程序编写如下:
#include<stdio.h>
main()
{
struct goods /* 定义结构 goods,并说明结构数组 fruit */
{
char item[30];
int code;
int stock;
}fruit[10];
struct goods *ptr; /* 说明一个 goods 型的指针变量 */
int t;
for (t=0, ptr=fruit; t<10; t++, ptr++)
{
printf ("Please enter %t’s item:", t+1);
scanf ("%s%d%d", ptr−>item, &p−>code, &p−>stock );

– 235 –
C 语言程序设计—示例与习题解析

}
printf ("The result is:\n");
for (t=0, ptr=fruit; t<10; t++, ptr++)
{
printf ("%s %d %d\n",ptr−>item, ptr−>code, ptr−>stock);
}
}
图 9-2 是把 fruit 数组改为 3 个元素、把 t<10 改为 t<3 时的一次运行结果。

图 9-2 例 9-2 的一次运行结果

4.结构做为函数的参数
像其他数据类型一样,结构做为用户自定义的一种数据结构,也可以以参数的形式传递
给函数。把结构传递给函数有 3 种方式:传递结构的单个成员、传递整个结构以及传递指向
结构的指针。由于结构的单个成员或整个结构本身,都视为变量,所以传递它们,等同于传
递一般变量,是一种值传递形式。至于传递指向结构的指针,则属于地址传递。
例 9-3 下面的程序,说明把结构指针做为函数参数时的使用。
[解] 程序编写如下:
#include<stdio.h>
struct abc
{
int x, y;
char str[15];
};
void fun (struct abc *); /* 函数 fun 的原型 */
main()
{
struct abc flg;
flg.x = 15;
flg.y = 25;
strcpy (str,"Hello Zong!");
printf ("before: %d, %d, %s\n", flg.x, flg.y, flg.str);

– 236 –
第 9 章 结构、共用、枚举

fun (&flg); /* 用结构 flg 的地址去调用函数 fun */


printf ("after: %d, %d, %s\n", flg.x, flg.y, flg.str);
}
void fun (struct abc *p) /* 函数 fun 的定义 */
{
p−>x = p−>x + 20;
p−>y = p−>y –20;
strcpy (p−>str, "Hello Jiang!");
}
程序中,先把结构 flg 的 3 个成员 x、y、str 分别赋予初值:15、25 和字符串“Hello Zong!”,
并将其打印出来。然后把 flg 的地址传递给函数 fun。fun 以这个地址为指针,对所指的结构
成员进行操作。这种操作显然直接影响到结构 flg 的每个成员本身的取值。因此,调用返回
后,输出的结构成员内容就变成为 35、5 和字符串“Hello Jiang!”了。
5.共用类型
在 C 语言中,
“共用类型”也称为“联合类型” ,是向用户提供的定义新数据类型的一种
方法。编程人员要先用它定义出新的数据类型,然后才能说明具有这种数据类型的变量。
用共用类型来定义新数据类型的一般格式是:
union <共用名>
{
<共用成员表>
};
其中,union 是 C 语言的保留字,用以标识一个共用定义的开始;<共用名>是要定义的共用
型新数据类型的名字,它是一个符合 C 语言规定的标识符;<共用成员表>由如下形式的多个
变量说明语句组成:
<数据类型> <变量名>;
每一个代表该共用中的一个成员,以及成员所具有的数据类型。要特别注意,在做共用定义
时,必须把<共用成员表>括在花括号内,右花括号后面要跟随一个分号“;” ,表示定义的结
束。比如,有如下的共用定义:
union tage
{
char ch[4];
int k;
double dval;
};
做了这样的定义后,就有了一种新的、名为 tage 的数据类型。于是,可以做如下的变
量说明:
union tage w, s;
它表示 w 和 s 都是 tage 型的变量,每个都有 3 个成员:有 4 个元素的字符数组 ch,整型 k,
以及双精度实型 dval。
– 237 –
C 语言程序设计—示例与习题解析

与结构的用法相同,共用型的定义和变量说明也有 3 种方式,即先定义共用型的数据类
型,再进行共用型变量的说明;定义共用型数据类型的同时,说明共用型变量;定义一个无
名共用型的同时,说明共用型变量。
可以看出,共用型与结构型很相似,区别只是在内存中的实现不同。对于一个结构变量,
其各个成员都有自己的内存单元,相互不发生重叠。因此,一个结构变量所占内存的长度,
等于各个成员所占内存长度之和。但对于共用变量则不然。共用变量所占内存的长度,等于
每个成员所占内存长度中的最大者。共用变量的各个成员都使用这个内存区域,只是不同时
使用罢了。上面名为 tage 的共用型,所需内存单元为 8 个字节。这是因为 ch[4]需要 4 个字
节,k 需要 2 个字节,而 dval 需要 8 个字节。但如果有如下定义:
struct tage
{
char ch[4];
int k;
double dval;
};
那么,这时每一个 tage 型的结构变量就需要占用 4+2+8 共计 14 个字节了。共用型与结构型
在内存中的区别如图 9-3 所示。

图 9-3 共用型与结构型在内存中的比较

可以用共用变量名与成员运算符“.”结合,达到访问其成员的目的。如果有一个同类
型的指针指向该共用变量,那么也可以用指针变量与指向运算符“−>”结合,达到访问其成
员的目的。
由于共用变量的若干个成员共同使用一个存储区域,而这些成员的类型可以完全不同,
因此共用变量在某一时刻起作用的成员,是指最后一次被赋值的成员。这一点是访问共用变
量成员时,特别要小心的地方,否则可能会得到无意义的结果。
例 9-4 下面的程序,说明对共用型变量成员进行访问的方法。
[解] 程序编写如下:
#include<stdio.h>
union tage
{
char ch[4];

– 238 –
第 9 章 结构、共用、枚举

int k;
double dval;
}x, *p; /* x 说明为是 tage 共用型变量,p 是 tage 共用型指针 */
main()
{
strcpy (x.ch,"who"); /* 通过成员运算符来访问成员 */
printf ("%s", x.ch);
x.k = 12;
printf ("%d", x.k);
x.dval = 144.287;
printf ("%lf\n", x.dval);
p = &x; /* 让 p 指向共用型变量 x,x 前应该有“&”运算符 */
strcpy (p−>ch,"the"); /* 通过指向运算符来访问成员 */
printf ("%s", p−>ch);
p−>k = 55;
printf ("%d", p−>k);
p−>dval = 1234.5678;
printf ("%lf\n", p−>dval);
}
6.枚举类型
在 C 语言中,枚举类型也是向用户提供的定义新数据类型的一种方法。编程人员要先用
它定义出新的数据类型,然后才能说明具有这种数据类型的变量。
用枚举类型来定义新数据类型的一般格式是:
enum <枚举名>
{
<枚举常量表>
};
其中,enum 是 C 语言的保留字,用以标识一个枚举定义的开始;<枚举名>是要定义的枚举型
新数据类型的名字,它是一个符合 C 语言规定的标识符;<枚举常量表> 是一些具有实际意义
的、符合 C 语言规定的标识符,中间用逗号隔开。要特别注意,在做枚举定义时,必须把<
枚举常量表>括在花括号内,右花括号后面要跟随一个分号“;” ,表示定义的结束。比如,有
如下的枚举定义:
enum color
{
red, yellow, blue, white, black
};
它表示定义了一种名为 color 的枚举型的新数据类型。如果某一个变量被说明为是这种 color
型的,那么意味该变量就只取 5 个可能的值:red,yellow,blue,white,black。需要注意,
<枚举常量表>里列出的是这种类型的变量可能取的“值”,而不是一个个字符串常量。
– 239 –
C 语言程序设计—示例与习题解析

比如,有如下说明:
enum color ys;
那么 ys 就是 color 型的变量了。语句:
ys = white;
就使该变量取值“white”。
与结构的用法相同,枚举型的定义和变量说明也有 3 种方式,即先定义枚举型的数据类
型,再进行枚举型变量的说明;定义枚举型数据类型的同时,说明枚举型变量;定义一个无
名枚举型的同时,说明枚举型变量。
在枚举定义中,<枚举常量表>里列出的是枚举变量可能取的“值”。这些枚举常量在内
部被编译程序处理成默认以 0 开始的整数常量值相对应:第 1 个枚举常量与数值 0 对应,第
2 个枚举常量与数值 1 对应,如此等等。如果不想遵循这一默认的对应关系,应该在类型定
义时设定,不能在定义外修改。比如,
enum color
{
red = 1, yellow, blue, white, black
};
表明 5 个枚举常量对应的整数常量值是:1、2、3、4、5。又比如,
enum color
{
red, yellow, blue = 5, white, black
};
那么这 5 个枚举常量对应的整数常量值是:0、1、5、6、7。
给出了枚举定义:
enum color
{
red, yellow, blue, white, black
};
后,就表示默认 5 个枚举常量对应的整数常量值是:0、1、2、3、4。写出语句:
yellow=7;
是错误的。
例 9-5 下面的程序,说明枚举变量中枚举常量与对应的整数常量值之间的关系。
[解] 程序编写如下:
#include<stdio.h>
enum color
{
red, yellow, blue, white, black
};
main()
{

– 240 –
第 9 章 结构、共用、枚举

enum color ys;


ys = yellow;
printf ("%d\n", ys);
}
这时的输出是:1。注意,程序中的 printf 语句不能写成:
printf ("%s\n", ys);
它打印不出“yellow”来。
7.用 typedef 定义类型别名
在 C 语言里,使用保留字 typedef 是为已经存在的类型起一个新的名字,而不是像
struct、union、enum 似地去定义一种新的数据类型。使用 typedef 的一般形式是:
typedef <数据类型名> <定义名> ;
其中,<数据类型名>是需要取新名字的、已经存在的数据类型名称;<定义名>是要为已存在
数据类型起的新名字,它是一个符合 C 语言规定的标识符。注意,typedef 是一条起别名的
语句,因此要以分号做为结束,分号不可丢失。比如,
typedef float REAL;
为 float 型起了一个新的名字 REAL。于是,如下的两个变量说明语句:
float x;与 REAL x;
是一样的,都表示说明了一个实型变量 x。习惯上,常把新起的数据类型名用英文大写表示。
为了保证使用 typedef 时正确无误,可以按如下 3 个步骤进行:
(1)先按说明变量的方法写出变量说明语句;
(2)将变量名改写成需要起的别名;
(3)在该语句前冠以保留字 typedef。
比如要为说明一个有 10 个整型元素的数组起别名为 ARRAY,具体做法可以是:
(1)先按说明变量的方法写出变量说明语句:int a[10];;
(2)将变量名改写成需要起的别名:int ARRAY[10];;
(3)在该语句前冠以保留字 typedef:typedef int ARRAY[10];。
这样一来,写变量说明语句:
ARRAY a;
其含义就是:int a[10];。
比如,typedef int * POINTER;
表示 POINTER 代表指向整型变量的指针类型。因此说明:
int *ptr; 与 POINTER ptr;
的作用是等同的,都表示变量 ptr 是一个指向整型变量的指针。又比如,
typedef struct per
{
int num;
char name[20];
}STUDENT;
它把所定义的 struct per 类型与 STUDENT 等同起来。因此说明:
– 241 –
C 语言程序设计—示例与习题解析

struct per slg; 与 STUDENT slg;


的作用是一样的,都表示变量 slg 是 struct per 型的。
typedef 语句仅是给原有数据类型起一个别名,没有定义出任何新的数据类型。使用
typedef,有助于编程者或维护者对程序的阅读和理解,也有助于程序的移植。

9.1.3 本章难点
1.自引用结构类型
C 语言规定,结构型的定义是可以嵌套的,即其成员的数据类型可以是另一个已定义的
结构类型。如果在定义的结构类型中,其成员的类型就是正在定义的结构型,那么这个成员
只能是指针型的。后一种情况,实际上就是一个结构的“自引用”,即由这个指针指向一个相
同类型的结构变量。比如,有如下的结构定义:
struct esp
{
int x;
float y;
struct esp *ptr;
};
关注定义中的粗体下划线处。它表明变量 ptr 是一个指针,它所指向的类型,就是所定义的
结构类型:struct esp。这种结构类型称为 “自引用结构类型”

例 9-6 编写一个程序,理解自引用结构类型中指向自身指针的作用。
[解] 程序编写如下:
#include<stdio.h>
struct esp
{
int x;
float y;
struct esp *ptr;
};
main()
{
struct esp wes, *p=&wes;
printf ("&p=%u, p=%u, &wes=%u\n", &p, p, &wes);
p−>x = 5;
p−>y = 28.12;
p−>ptr = &wes;
printf ("wes.x=%d\nwes.y=%5.2f\nwes.ptr=%u\n", p−>x, p−>y, p−>ptr);
}
从程序中看到,指针变量 p 是指向结构变量 wes 的(struct esp wes, *p=&wes;)
。从
图 9-4 看出,编译程序分配给 p 的单元地址是 65492,它的内容是结构变量 wes 的地址 65484。
– 242 –
第 9 章 结构、共用、枚举

在把值 5、28.12 和 wes 的地址分别赋给 wes 的 3 个成员后,wes.ptr 里的内容也是 65484,


即它也指向结构变量 wes。这是自引用结构中指向自己的一个例子。

图 9-4 例 9-6 的运行结果

2.利用自引用结构类型创建链表
用数组或结构数组来存放程序运行时的各种数据,有两个缺点:一是要为它们开辟固定大小
的连续存储区,无论当前是否需要这么大的存储区,因此总会有存储浪费的现象出现;二是如果
要从中删除或插入新的数据,为保持数据的连续存放,必须移动其他数据,增加了运行时的开销。
为此,应该采用动态存储分配技术来构造一个链表。需要时申请一块存储区,称为链表的
一个节点(实际上,一个节点就是一个结构型变量)。节点一是用来存放数据,二是用它里面指
向结构的指针成员与后面的节点相连接;不再用到某数据时,就调整节点中的结构型指针,把无
用数据占据的节点存储块从链上摘下来,归还给系统。图 9-5 描述了进行链表插入时的情形。

图 9-5 进行链表插入的操作示意

在图 9-5(a)里,head 是一个指向单链表的头指针(这个指针当然是某一种结构型的)。
当前链表里有 4 个节点,每一个节点都由一个数据成员和一个指针成员组成,指针成员里存
放的是下一个节点的地址,从而达到指向另一个结构型变量的目的。现在申请到了一个新的
存储节点,如图 9-5(b)所示。按照数据的排列顺序,要把它插入到链表中。从图 9-5(c)
可以清楚地看到,它应该插入到原先 2 号节点的后面。要完成这一插入,必须调整图中①、
②所标位置处的指针:让 2 号节点的指针改指向 4 号节点;让 4 号节点的指针指向原 8 号节
– 243 –
C 语言程序设计—示例与习题解析

点。
综合地,在 C 语言程序中要实现动态存储分配,需要用到:
(1)自引用结构类型;
(2)malloc 和 free 函数;
(3)sizeof 运算符。
例 9-7 编写一个创建如图 9-5 所示单链表的函数 creat,它没有任何参数,返回一个
指向单链表表首节点的结构型指针。在 main 里调用函数 creat,并打印出各节点中的数据内
容。
[解] 程序编写如下,图 9-6 所示的是其连续输入 3 个数据后的执行结果。
#include<stdio.h>
typedef struct person /* 把结构型 person 另起名为 PSN */
{
char name[20];
struct person *next;
}PSN;
PSN * creat (void) /* 函数 creat 的定义,它返回 PSN 型的指针 */
{
PSN *head, *p, *q;
int k;
head = NULL; /* 先让 head 为空 */
while (1)
{
p = (PSN *)malloc(sizeof(PSN)); /* 申请一块存储区 */
if (p == NULL)
{
printf ("Memory faild!\n");
exit(1);
}
printf ("Please enter name:");
scanf ("%s", p−>name); /* 填入数据 */
if (head == NULL) /* 如果是第 1 个节点,要特殊处理 */
{
head = p;
p−>next = NULL;
q = p; /* 让指针 q 指向当前链表的末尾节点 */
}
else /* 在有了节点后,都到此处处理 */
{
q−>next = p; /* 让当前尾节点的指针成员指向新的节点 */

– 244 –
第 9 章 结构、共用、枚举

p−>next = NULL; /* 新节点的指针成员置为空,表示链尾 */


q = p; /* 仍让指针 q 指向当前链表的末尾节点 */
printf ("Continue? 1—continue\n");
printf (" 0—stop!\n");
scanf ("%d", &k);
if (k == 0) /* 如果不愿意输入了,则退出循环 */
break;
}
return head; /* 返回创建的链表首指针 */
}
main()
{
PSN *ptr;
ptr = creat (); /* 由指针 ptr 接受函数 creat 的返回值 */
for (; ptr != NULL; ptr = ptr−>next) /* 顺着各节点的指针,打印数据 */
printf ("%s\n", ptr−>name);
}

图 9-6 链表程序的运行结果

函数 creat 的设计思想是:用 PSN 型指针 head 指向单链表之首。最初,head 为空(NULL),


如图 9-7(a)所示。若要输入数据,则先申请一个 PSN 型节点大小的存储区(p = (PSN
*)malloc(sizeof(PSN));),让指针 p 指向它,填入数据,调整节点的 next 指针,连入链表
末尾。图 9-7(b)是连入第一个节点时的效果。在函数中,总是让指针 p 指向申请到的新节
点,让指针 q 指向链表的尾节点,从而使新节点插入到链表尾非常容易进行。图 9-7(c)是
把第 2 个节点连入末尾时 3 个操作步骤的实际作用。
3.位段
“位段”又称“位域” ,是以二进制位为单位存储信息的一种手段,即以每一位取值 0 或
1,来表示某个状态或特征。
位段以结构为基础,实际上是对结构成员的一种特殊的表示形式。要让结构定义中的一
个成员成为位段,需要做下面两件事:
– 245 –
C 语言程序设计—示例与习题解析

图 9-7 节点连入链表时的实际情形

(1)使该成员具有 unsigned 或 int 型;


(2)在成员名后给出它所需要的二进制位数,并用冒号“:”与成员名隔开。
比如,有如下结构定义:
struct status
{
unsigned x : 1;
unsigned y : 3;
unsigned z : 2;
int tage;
};
它里面有名为 x、y、z 的三个位段(实际就是成员),各占 1、3、2 个二进制位;还有一个名
为 tage 的成员,是 int 型的。若有变量说明:
struct status zgf;
那么变量 zgf 在内存中共占用 3 个字节。具体分配如图 9-8(a)所示。

图 9-8 位段在内存中的安排

如果把上述结构定义成:
struct status
{
unsigned x : 1;
unsigned y : 3;
unsigned : 2;
unsigned z : 2;
int tage;
};

– 246 –
第 9 章 结构、共用、枚举

那里,与上面定义不同之处是有一个位段“unsigned:2;”,它是所谓的“无名位段”。那么说
明变量:“struct status zgf;”后,该变量在内存中的分配情形如图 9-8(b)所示。可以
看出,对于位段,C 语言编译在字节里是顺序分配二进制位的,遇到无名位段时,就分配所
要求位数的空闲位。
由于位段实际上就是结构的一个成员,因此在使用上与结构成员相同。只是不能取它们
的地址,从而与指针有关的操作都不能针对位段进行。

9.2 典型示例分析
示例 1 利用结构数组,编写一个小型帐目系统,它根据菜单的选择,完成输入表项、
删除表项、打印帐目表以及退出等功能。
[解] 由于输入表项、删除表项、打印帐目表等都要用到结构数组,因此结构定义与数
组说明应该放在所有函数之外,使之成为外部变量。整个系统由主函数(main) 、数组初始化
(init)、寻找数组中的空表项(find)
、输入表 项(enter)、删除表项(delete)和打印帐目
表(display)等组成。整个系统程序编写如下:
#include<stdio.h>
struct goods /* 定义结构 goods,并说明结构数组 fruit */
{
char item[20];
int code;
int stock;
}fruit[30];
void init () /* 结构数组初始化函数 init */
{
int i;
for (i=0; i<30; i++)
fruit[i].item[0] ='\0';
}
int find() /* 寻找数组中的空表项函数 find */
{
int j;
for (j=0; j<30; j++)
if (fruit[j].item[0] !='\0')
continue;
else
break;
if (j == 30)
return –1;

– 247 –
C 语言程序设计—示例与习题解析

return j;
}
void enter() /* 输入表项函数 enter */
{
int seat;
seat = find(); /* 调用寻找数组中的空表项函数 find */
if (seat = =−1)
{
printf ("\nThere isn't vacant seat, array full!");
return;
}
printf ("Please enter item:");
scanf ("%s",fruit[seat].item);
printf ("Please enter code:");
scanf("%d", &fruit[seat].code);
printf ("Please enter stock:");
scanf ("%d", &fruit[seat].stock);
}
void delete () /* 删除表项函数 delete */
{
int seat;
printf ("Please enter number of record:");
scanf ("%d", &seat);
if (seat>=0 && seat<30)
fruit[seat].item[0] ='\0';
}
void display () /* 打印帐目表函数 display */
{
int k;
for (k=0; k<30; k++)
if (fruit[k].item[0] != '\0')
{
printf ("item: %s", fruit[k].item);
printf ("code: %d", fruit[k].code);
printf ("stock: %d\n\n", fruit[k].stock);
}
}
main()
{

– 248 –
第 9 章 结构、共用、枚举

int x;
init (); /* 调用初始化函数 init */
for ( ; ; )
{
printf ("\n");
printf ("1. Enter a item\n");
printf ("2. Delete a item\n");
printf ("3. Display list\n");
printf ("4. Quit\n");
do
{
printf ("\nEnter your choice:");
scanf ("%d", &x);
}while(x<0 || x>4);
switch (x)
{
case 1:
enter (); /* 调用输入表项函数 enter */
break;
case 2:
delete (); /* 调用删除表项函数 delete */
break;
case 3:
display (); /* 调用打印帐目表函数 display */
break;
case 4:
exit(0); /* 退出 */
}
}
}
这里,init 是一个初始化函数,它把结构数组 fruit 中每个元素的 item 成员的第 1 个
字节设置成字符串结束符“' \0 '” ,以表示这个元素还没有被使用。enter 是一个完成往 fruit
各个元素中进行输入的函数,它必须先调用函数 find,寻找出一个空闲的元素,即该元素的
item 成员的第 1 个字节为“' \0 '”的元素。只有这样,才能进行输入。调用函数 find 后,
或返回元素的下标,或返回−1(表示没有空闲的位置) 。delete 是删除指定元素内容的函数,
即把该元素的 item 成员的第 1 个字节设置成字符串结束符“' \0 '” ;display 函数对当前
有内容的表目进行显示,即只要一个元素的 item 成员的第 1 个字节不是字符串结束符“' \0
'”,就对它的成员进行显示。主函数 main 先自动对结构数组进行初始化,然后给出菜单供用
户选择,根据选择完成不同的功能。
– 249 –
C 语言程序设计—示例与习题解析

示例 2 利用结构型指针,设计一个软件时钟,完成计时工作。
[解] 程序编写如下:
#include<stdio.h>
struct tm /* 定义名为 tm 的结构型数据类型 */
{
int hours;
int minutes;
int seconds;
};
update (struct tm *t) /* 修改软时钟函数 update 的定义 */
{
(*t).seconds++;
if ((*t).seconds == 60)
{
(*t).seconds=0;
(*t).minutes++;
}
if ((*t).minutes == 60)
{
(*t).minutes=0;
(*t).hours++;
}
if ((*t).hours == 24)
(*t).hours = 0;
delay ();
}
display (struct tm *t) /* 显示软时钟函数 display 定义 */
{
printf ("%d:", (*t).hours);
printf ("%d:", (*t).minutes);
printf ("%d:", (*t).seconds);
}
delay() /* 时间延时函数 delay 的定义 */
{
long int k;
for (k=1; k<400000; ++k)
;
}
main()

– 250 –
第 9 章 结构、共用、枚举

{
struct tm time;
time.hours=0;
time.minutes=0;
time.seconds=0;
for ( ; time.minutes<2; )
{
update(&time); /* 调用 update 函数 */
display(&time); /* 调用 display 函数 */
}
}
整个程序由修改时钟函数 update、显示时钟函数 display、时间延时函数 delay 以及主
函数 main 组成。在所有函数外定义了一个名为 tm 的结构型数据类型,在主函数里说明了一
个名为 time 的 tm 型变量,用以随时记录时间。修改时钟函数 update 接收结构指针,在其基
础上进行时间的累加。显示时钟函数 display 接收结构指针,在其基础上进行时间的显示。
软件时钟的快慢是由时间延时函数 delay 中的循环终止值来确定的。
为了能够演示程序的运行,在主函数的 for ( ; time.minutes<2; )里,设置了循环终
止条件: “time.minutes<2”。否则将是一个无限循环。
程序中所有的“(*t).”都可以用“t−>”代之,这是指针的两种不同的使用方法。
示例 3 有红、黄、蓝、白、黑 5 种颜色的玻璃球若干个,每次取出 3 个。打印取出 3
种不同颜色球的所有取法。
[解] 把 5 种球的颜色定义为枚举型数据类型。通过 3 重 for 循环,每重都是从红到黑
变化,并剔除相同颜色的情况,从而达到取出不同颜色球的目的。程序编写如下:
#include<stdio.h>
main()
{
enum color {red, yellow, blue, white, black}; /* color 是新定义的枚举类型 */
enum color i, j, k, ind;
int num = 0, w;
for (i=red; i<=black; i++)
for (j=red; j<=black; j++)
if (i != j)
{
for (k=red; k<=black; k++)
if ( (k != i) && (k !=j) )
{ /* 进到这里,i、j、k 三者就不相等了 */
num++;
printf ("%−3d", num); /* 打印序号 */
for (w=1; w<=3; w++)

– 251 –
C 语言程序设计—示例与习题解析

{
switch (w)
{
case 1:
ind = i;
break;
case 2:
ind = j;
break;
case 3:
ind = k;
break;
default: break;
}
switch (ind)
{
case red:
printf ("%−7s","red");
break;
case yellow:
printf ("%-7s","yellow");
break;
case blue:
printf ("%−7s", “blue”);
break;
case white:
printf ("%−7s","white");
break;
case black:
printf ("%−7s","black");
break;
default:
break;
}
}
if (num%3 == 0) /* 控制每行打印 3 个输出结果 */
printf ("\n");
}
}

– 252 –
第 9 章 结构、共用、枚举

printf ("\nTotal = %d\n", num);


}
图 9-9 所示的是程序的运行结果。

图 9-9 示例 3 的运行结果

示例 4 有下面的结构定义:
typedef struct
{
float marks[20];
int num;
}STUDENT;
其中 num 是数组 marks 中分数的个数。编写一个名为 mark(xs, zgf)的函数,它通过 zgf 返
回学生 xs 的最高分。在主函数 main 里调用该函数,并加以验证。
[解] 程序编写如下:
#include<stdio.h>
typedef struct
{
float marks[20];
int num;
}STUDENT;
mark (STUDENT *xs, float *zgf)
{
int k;
float temp = 0.0;
for (k=0; k<xs−>num; k++)
if (temp<xs−>marks[k])
temp = xs−>marks[k];

– 253 –
C 语言程序设计—示例与习题解析

*zgf = temp;
}
main()
{
int j;
float *p;
STUDENT stud;
printf ("\nPlease enter the score of student.\n");
for (j=0; j<20; j++)
{
scanf ("%f", &(stud.marks[j]));
if (stud.marks[j]<0)
{
stud.num = j;
break;
}
}
if (j == 20)
stud.num = 20;
mark (&stud, p);
printf ("The highest score is %4.2f.\n",*p);
}
图 9-10 所示的是该程序的一次运行结果。

图 9-10 示例 4 的一次运行结果

9.3 课外习题、答案与解析

9.3.1 课外习题

一、单选题

1.有枚举型定义如下:
enum bt {a1, a2 = 6, a3, a4 = 10} x;

– 254 –
第 9 章 结构、共用、枚举

则枚举变量 x 可取的枚举常量 a2、a3 所对应的整数常量值是( )。


A.1,2 B.6,7 C.6,2 D.2,3
2.有结构型定义如下:
struct
{
int k;
float x;
} s[3] = {{1, 4}, {2, 5}, {3, 7}};
则语句 printf ("%3.1f\n", s[2].k*s[2].x / s[1].x);的输出是( ) 。
A.1.2 B.2.2 C.3.2 D.4.2
3.有共用型、结构型定义如下:
typedef union
{
long m;
int k[5];
char ch;
} NODE;
struct date
{
int bat;
NODE bow;
Double bogey;
};
那么语句 printf ("%d\n", sizeof (NODE) + sizeof (struct date));的输出结果是
( )。
A.10 B.20 C.30 D.40
4.有结构型定义如下:
struct person
{
char name[20];
int age;
int sex;
}w[5], *p = w;
那么下面 scanf 语句中对结构变量成员不正确的引用是( ) 。
A.scanf ("%s", w[0].name); B.scanf ("%d", &w[0].age);
C.scanf ("%d", &(p−>sex)); D.scanf ("%d", p−>age);
5.有结构型定义如下:
struct bty
{

– 255 –
C 语言程序设计—示例与习题解析

int number;
int age
}stu[3]={{10120, 25 },{10124, 21},{10126, 28}}, *ptr = stu;
那么,对指针 ptr 不正确的引用是( ) 。
A.p =&stu.age B.(ptr++)−>num C.ptr++ D.(*ptr).num
6.有结构定义如下:
struct
{
int m;
float x;
} test, *p = &test;
那么对变量 test 的成员 m 的正确引用是( )。
A.(*p).test.m B.(*p).m C.p−>test.m D.p.test.m
7.有结构定义如下:
struct
{
int k : 5;
int : 6;
unsigned int m : 7;
}st;
那么变量 st 所占用的字节数是( )。
A.3 B.2 C.1 D.4
8.有结构定义如下:
struct
{
int k : 2;
int : 0;
unsigned int m : 5;
}st;
那么变量 st 所占用的字节数是( )。
A.3 B.2 C.1 D.4

二、填空题

1.在结构定义中出现的诸变量称为结构的 。
2.结构定义总是以保留字 开始。
3.利用保留字 可以为已有的数据类型起别名。
4.C 语言中使用 结构建立动态存储节点。
5.C 语言中使用 函数来动态分配内存。
6.有结构定义如下:
– 256 –
第 9 章 结构、共用、枚举

struct person
{
int no;
char name[20];
}stu, *ptr = &stu;
用指针 ptr 和指向运算符“−>”
,给变量 stu 成员 no 赋值 1012 的语句是 。
7.有一个结构,一个成员名为 data,int 型;一个成员名为 next,是指向该结构自身
的指针。请完成这个自引用结构的定义:
struct link
{
int data;

};
8.有共用型定义如下:
typedef union
{
long x;
int ary[4];
char str[8];
}NODE;
要得到该型变量所占用的字节数,请完成语句:
printf ("%d\n", sizeof ( ) );
9.有如下结构定义:
struct node
{
char name[20];
float grade;
struct node *ptr;
};
指针 sptr 指向有两个节点的单链表,节点按照字母顺序排列,如图(a)所示。现在要把如
图(b)所示由指针 p 指向的节点插入,使链表成为图(c)。请完成插入操作填空:
p−>ptr = ① ; sptr = ② ;

10.结构定义如题 9。情况如图(a)所示。要将图(b)所示由指针 p 指向的节点插入


到两个节点的中间,使链表成为图(c) 。请完成插入操作填空:

– 257 –
C 语言程序设计—示例与习题解析

p−>ptr = ① ; ② = p;

11.结构定义如题 9。情况如图(a)所示。要将图(b)所示由指针 p 指向的节点插入


到链表的末尾,使链表成为图(c) 。请完成插入操作填空:
① = p; p−>ptr = ② ;

三、是非判断题(在括号内打“√”或“×”)

1.在定义一个结构时,可以没有结构名。 ( )
2.在 C 语言里,可以使用保留字 typedef 定义一种新的数据类型。( )
3.有如下的结构定义:
struct person
{
char name[20];
int age;
};
那么,语句:
person d;
说明 d 是具有 person 型的一个变量。( )
4.有如下的结构定义:
struct card
{
char *name;
char *addr;
}c, *ptr = &c;
那么,语句:
printf ("%s\n", *ptr−>name);
将打印出结构变量 c 的 name 成员内容。
( )
5.一个共用型变量里,不能同时存放其所有成员。( )
6.定义一个结构时,其成员不能是另一个已定义的结构型。( )
7.有如下的枚举定义:
enum weekday
{

– 258 –
第 9 章 结构、共用、枚举

mon, tue, wed, thu, fri, sat, sun


}wd;
那么语句:
wd = 3;
将使枚举变量 wd 取值 thu。( )
8.有程序如下:
main()
{
enum team
{
my, your = 4, his, her = his +5
};
printf ("%d, %d, %d, %d\n", my, your, his, her);
}
运行后,输出的结果是 0,4,2,7。( )
9.语句:
enum w = {one, two, three, four, five};
定义了一个名为 w 的枚举类型,它有 5 个枚举常量。
( )
10.下面给出的名为 wrt 的结构定义是正确的。
( )
struct wrt
{
int x;
float y;
}

四、程序阅读题

1.阅读下面的程序,给出它的输出结果。
#include<stdio.h>
enum months
{
JAN = 1, FEB, MAR, APR, MAY, APR, MAY,
JUN, JUL, AUG, SEP, OCT, NOV, DEC
};
main()
{
enum months yf;
char * yfm[ ] = {"","January","February","March","April","May", "
June",
"July","August","September","October","November","December"};

– 259 –
C 语言程序设计—示例与习题解析

for (yf = JAN; yf<=DEC;yf++)


printf ("%2d%11s\n", yf, yfm[yf]);
}
2.阅读下面的程序,给出它的输出结果。
#include<stdio.h>
main()
{
struct
{
char *name;
int age;
}w[ ] = {"Li wen bing", 24, "Zhang tian qing", 21, "Lu xiao ping", 22};
int k;
for (k=0; k<3; k++)
printf ("%s: %d\n", w[k].name, w[k].age);
}
3.阅读下面的程序,给出它的输出结果。
#include<stdio.h>
main()
{
union ast
{
short x;
char ch;
}W;
W.x = 200;
W.ch = ' B ' ;
printf ("%d, %d, %c\n", sizeof (W), W.x, W.ch);
}
4.字符’0’的 ASCII 码值是 16 进制的 0x30。阅读下面的程序,给出它的输出结果。
#include<stdio.h>
main()
{
union
{
int k[2];
char w[4];
}hit, *p = &hit;
p−>k[0] = 0x39;

– 260 –
第 9 章 结构、共用、枚举

p−>k[1] = 0x38;
printf ("%c\n", p−>w[0]);
}
5.阅读下面的程序,给出它的输出结果。
#include<stdio.h>
struct bm
{
int x;
int *p;
}*ptr;
int fbt[4] = {100, 150, 200, 250};
strunt bm zz[4] = {50, &fbt[0], 60, &fbt[1], 70, &fbt[2], 80, &fbt[3]};
main()
{
ptr = zz;
printf ("%d\n", ++ptr−>x );
printf ("%d\n", (++ptr)−>x );
printf ("%d\n", ++(*ptr−>p) );
}
6.有一个循环单链表(它与单链表的区别是最后一个节点的指针指向了链表的首节点),
它至少有 3 个节点,如下图所示。为其编写一个名为 thn 的函数,它以循环单链表的首指针
为参数。阅读它,并解释其功能。

struct node
{
int data;
struct node *p;
};
int thn (struct node *first)
{
struct node *ptr = first;
int x, y;
y = (ptr−>data) + (ptr−>p−>data) + (ptr−>p−>p−>data);
for (ptr = ptr−>p; ptr != first; ptr = ptr−>p)
{
x = (ptr−>data) + (ptr−>p−>data) + (ptr−>p−>p−>data);
if (x<y)

– 261 –
C 语言程序设计—示例与习题解析

y = x;
}
return y;
}

五、程序设计题

1.定义一个结构,有 3 个成员:姓名、基本工资、岗位工资。说明一个该结构的结构
数组,对其元素按下表初始化。然后打印每个人的姓名和工资总额。

姓 名 基 本 工 资 岗 位 工 资

Li hua qiu 845 1800


Liu ming shang 920 2400

2.在上题结构定义的基础上,于主函数 main 里输入 10 个人员信息,然后输出应发放


的工资总额、工资数最大者和最小者信息。
3.让 13 个人围成一圈,从第 1 个人开始报数,报到 3 的人就退出圈子,然后从下一个
人再开始报数。重复这工作,直到所有的人都出圈。输出人们出圈的次序。
4.利用例 9-7 给出的函数 creat 建立单链表。然后编写一个名为 length 的函数,它以
单链表为参数,计算出其上的节点个数后返回。在 main 里调用 creat 建立单链表,再调用
length,输出单链表的节点个数。

9.3.2 答案与解析

一、单选题

1.B2.D3.C 注意:NODE 型需要 10 个字节,struct date 需要 20 个字节。因此输出


应该是 30。
4.D5.A 注意:stu 不是结构变量,它是结构数组名,因此它没有成员。另外,(*ptr)
相当于 stu[0],因此(*ptr).num 相当 stu[0] .num。
6.B7.A8.B 注意:在位段定义里,出现要求二进制位数为 0 的无名位段时,表示将
当前字节中的剩余位全部空闲,下面的位段放到另一个字节中去。因此,这里总的位数虽然
要求是 7,按说一个字节就能够放下。但因为有一个位数为 0 的无名位段出现,所以第 1 个
字节只用两位,剩余位空闲,位段 m 需要的 5 位在第 2 个字节中。

二、填空题

1.成员 2.Struct 3.typedef 4.自引用


5.Malloc
6.ptr−>no = 1012; 7.struct link *next; 8.NODE
9.① sptr ② p 10.① sptr−>ptr ② sptr−>ptr
11.① sptr−>ptr−>ptr ② NULL

– 262 –
第 9 章 结构、共用、枚举

三、是非判断题

1.√ 2.× 3.× 注意:申明结构型变量时,必须要有保留字 struct。


4.× 注意:应该把“*”去掉。
5.√ 6.× 7.× 注意:要使枚举变量 wd 取值为 thu,必须用语句:wd=thu。
8.× 注意:正确的输出应该是 0,4,5,10。
9.× 注意:枚举定义中不能出现等号。
10.×

四、程序阅读题

1.答:这里先定义了一个名为 months 的枚举类型,它可取的枚举常量有 12 个,对应


的枚举整数值从 1 开始到 12。在 main 里,说明了一个 months 型的变量 yf,还说明一个名为
yfm 的指针数组,它的 yfm[0]元素指向一个空字符串。从 yfm[1]到 yfm[12]元素分别指向一
个月份的英文名字符串。通过 for 循环,打印出月份序号和对应的名称,如下图所示。
从这个习题应该理解,对于枚举类型来说,枚举常量有实
际意义,便于记忆。但使用时却是将每个枚举常量用其对应的
整数值来代替。比如这里的:
for (yf = JAN; yf<=DEC;yf++)
实际的含义执行如下的循环:
for (yf = 1; yf<=12;yf++)
于是,在打印语句:“printf ("%2d%11s\n", yf, yfm[yf]);”
里,打印的 yf 是它对应的整数值。
2.答:w 是一个无名结构数组,在定义结构时就对它的诸
元素进行了赋值。因此程序运行后,输出的结果是:
Li wen bing: 24
Zhang tian qing: 21
Lu xiao ping: 22
3.答:运行后的输出是 2,66,B。这里要注意两点:第一,short x;需要内存 2 个字
节;而 char ch;需要一个字节。因此,系统分配给 ast 型变量 W 的存储区为 2 个字节。第二,
数据在存储区里总是从低字节往高字节存放的。因此,在执行了语句:
W.x = 200;
后,变量 W 所占存储区的情形如下图(a)所示。但在执行了语句:
W.ch = ' B ' ;
后,变量 W 所占存储区的情形如下图(b)所示。正因为如此,输出的结果才是 2,66,B。

– 263 –
C 语言程序设计—示例与习题解析

4.答:这里,变量 hit 是共用型的,占用 4 个字节,指针 p 指向它。在做完:


p−>k[0] = 0x39;
p−>k[1] = 0x38;
后,具体情况如下图(a)或(b)所示。所以 w[0]里放的数值是 57,它是数字字符‘9’的
ASCII 码值。因此,printf 打印出的应该是字符‘9’

5.答:在执行 printf 语句前,内存中的情形如下图所示。执行第 1 条 printf 语句时,


运算符“−>”的优先级高于++。因此应该把 ptr 当前所指的结构数组元素的成员 x 加 1 后打
印出来,即打印 51;执行第 2 条 printf 语句时,圆括号使“++”先作用于指针 ptr,所以使
ptr 下移一个结构数组元素, 然后把该数组的成员 x 打印出来,即打印出 60;执行第 3 条 printf
语句时,是把 ptr 当前所指结构元素(应该是 zz[1])的 p 成员的内容加 1 后打印出来。p
是一个指针,zz[1].p 指向数组 fbt 的元素 fbt[1]。因此,实际上是把 fbt[1]加 1 后打印,
故打印出 151。

6.答:该函数的功能是先求出最先相邻的 3 个节点 data 成员之和,存放在变量 y 里。


然后顺着该循环单链表的链接指针往下走,每走一步就求出相邻的 3 个节点 data 成员之和,
放于变量 x 中。进行 x 与 y 的比较。总是把小者放在 y 里。这一工作一直进行到再往前就是
链表的第一个节点时止。最后返回 3 个一组所求和的最小值。

五、程序设计题

1.答:程序编写如下:
#include<stdio.h>
struct person
{
– 264 –
第 9 章 结构、共用、枚举

char name[20];
int wages;
int subsidy;
};
main()
{
struct person s[4] = {{"Li hua qiu", 845, 1800 },{"Liu ming shang",920,2400}};
int k;
for (k=0; k<2; k++)
printf ("%s: %d\n", s[k].name, s[k].wages + s[k].subsidy);
}
2.答:程序编写如下:
#include<stdio.h>
struct person
{
char name[20];
int wages;
int subsidy;
};
main()
{
int k, pmax, pmin;
long sum;
struct person s[10];
printf ("“Please enter information:\n");
for (k=0; k<10; k++)
scanf ("%s%d%d", s[k].name, &s[k].wages, &s[k].subsidy);
sum = s[0].wages + s[0].subsidy;
pmax = s[0].wages + s[0].subsidy;
pmin = s[0].wages + s[0].subsidy;
for (k=1; k<10; k++)
{
sum += s[k].wages + s[k].subsidy; /* 求累加和 */
if (pmax<s[k].wages + s[k].subsidy)/* 求大者 */
pmax = s[k].wages + s[k].subsidy;
if (pmin>s[k].wages + s[k].subsidy)/* 求小者 */
pmin = s[k].wages + s[k].subsidy;
}
printf ("sum = %ld, max = %d, min = d\n", sum, pmax, pmin);

– 265 –
C 语言程序设计—示例与习题解析

}
3.答:程序编写如下:
#include<stdio.h>
main()
{
struct person
{
int ino;
int next;
}per[14];
int i, j, k;
for (i=1; i<=13; i++) /* 变量 per 元素的初始化 */
{
if (i == 13)
per[i].next = 1; /* 是最后一个人,他的下一个人是第 1 个人 */
else
per[i].next = i + 1; /* 置下一个人的序号 */
per[i].ino = i; /* 置自己的序号 */
}
printf ("The order of person came out:\n");
j = 1; /* 总是从 j 开始报数 */
k = 0; /* 控制出圈的人数,到达 13 时循环停止 */
while (k !=13)
{
i = 0; /* 控制有效的 3 次报数 */
while (1)
{
if (per[j].ino != 0)
i++;
if (i>=3)
break;
j = per[j].next; /* 还没有到 3 个人,于是进到下一个人 */
}
printf ("%4d,", per[j].ino); /* 具有 j 序号的人要出圈 */
per[j].ino = 0;
k++;
if (k%7 == 0) /* 一行打印 7 个数 */
printf ("\n");
}

– 266 –
第 9 章 结构、共用、枚举

printf ("\n");
}
这里先考虑用结构数组来解决。在定义的结构型 person 里有两个成员:自己的序号 ino,
下一个人的序号 next。最后一个人的下一个人定为第 1 个人,从而形成一个圈子。报数时,
报到 3 就把他的序号置为 0,表示它已出圈,然后从他的下一个人重新报数。因此,每报一
个数,都应先检查他的序号是否有效(不为 0) ,为 0 时,就跳过去。下图是执行后的结果。

也可以用结构式的动态链表来解决。程序如下:
#include<stdio.h>
struct node
{
int data;
struct node *next;
};
main()
{
int k;
struct node *p, *q, *head;
head = NULL;
for (k=1; k<=13; k++)
{
p=(struct node *)malloc(sizeof(struct node));
p−>data = k;
if (head == NULL)
{
head = p;
q = p;
}
if (k == 13)
{
q−>next = p;
p−>next = head;
}
else
{
q−>next = p;

– 267 –
C 语言程序设计—示例与习题解析

q = p;
}
}
p = head;
k = 1;
while(k<=13)
{
q = p−>next;
p = p−>next−>next;
q->next = p−>next;
printf ("%d", p−>data);
free(p); /* 释放 p 所指的存储区域 */
p = q−>next;
k++;
}
}
程序中,先通过 for 循环形成一个单循环链表:如果 head 等于 NULL,则表示是第 1 个
节点;如果 k 等于 13,则表示是最后一个节点;其余都是一般节点。然后在 while 循环里,
顺着指针链往下走,总是由 p 指向第 3 个节点(p = p−>next−>next;)
,由 q 指向第 2 个节
,然后把 p 指向的第 3 个节点从链上摘下来(q−>next = p−>next;)
点(q = p−>next;) ,打
印数据后释放。这一工作进行 13 次结束。结果当然和前面的解法是一样的。
4.答:程序编写如下:
#include<stdio.h>
typedef struct person /* 把结构型 person 另起名为 PSN */
{
char name[20];
struct person *next;
}PSN;
PSN * creat (void)
{
/* 函数 creat 的内容同前,在此省略 */
}
int length (PSN *head) /* 函数 length 的定义 */
{
PSN *p = head;
int j = 1;
if (head == NULL)
return (0);
else

– 268 –
第 9 章 结构、共用、枚举

while (p−>next != NULL)


{
p = p−>next;
j++;
}
return (j);
}
main()
{
PSN *t;
int x;
t = creat (); /* 调用函数 creat */
x = length (t); /* 调用函数 length */
if (x>0)
printf ("There are %d elements of this list.\n", x);
else
printf ("This is empty's list.\n");
}

– 269 –
第 10 章 文件、编译预处理

10.1 本章基本概念及知识点
本章涉及以下5个方面的内容:
• 文件的打开和关闭;
• 文件的读和写;
• 无参宏和带参数的宏;
• 文件包含;
• 条件编译。

10.1.1 基本概念
1.文件
按一定规则存储在磁盘上的数据集合,称为“文件” 。
2.文件名
为了区分存放在磁盘上的文件,必须给其一个标识。能惟一标识某个磁盘文件的字符串,
就称为该文件的“文件名” 。一个完整的文件名常表示成如下形式:
盘符:\ 路径 \ 文件名.扩展名(在 DOS 下)
3.文本文件和二进制文件
如果数据以其数字字符的 ASCII 码形式、一个字节一个字节地存储在磁盘上,那么这种文
件称为“文本文件”;如果数据就以二进制形式存储在磁盘上,那么这种文件称为“二进制文件” 。
4.设备文件
计算机系统使用输入/输出设备进行数据的输入和输出,与从文件读取数据和把数据写
入文件是类似的。为了统一管理,操作系统就把输入/输出设备也视为文件来对待,这种特殊
的文件被称为“设备文件” 。
5.标准输入/标准输出文件
按照设备文件的说法,在微机上,把进行数据输入的键盘称为“标准输入文件”;把完
成数据输出和错误信息显示的显示器称为“标准输出文件”和“标准错误输出文件” 。
6.文件型指针
C 语言是通过名为 FILE 的结构型指针来管理文件读写(即存取)的。该结构型的定义在
– 270 –
第 10 章 文件、编译预处理

<stdio.h>头文件里,形式是:
typedef struct
{
int _fd; /* 文件代号 */
int _cleft; /* 文件缓冲区所剩字节数 */
int _mode; /* 文件使用模式 */
char *nextc; /* 下一个等待处理的字节地址,即该文件内部指针 */
char *buff; /* 文件缓冲区首地址 */
} FILE ;
因此,用 FILE 说明的指针变量:
FILE * <变量名>;
就称为“文件型指针” ,这些指针专门用于文件的处理。
7.文件的打开和关闭
一方面,磁盘上文件中的数据,只有读到内存才能进行操作;内存中加工完的数据,只
有写入磁盘文件才能得以有效地保存。另一方面,C 语言是通过文件型指针来管理文件的读
写的。因此,必须事先建立起文件和文件型指针之间的联系,随后才能进行文件的读与写,
建立这种联系的过程,就称为“文件的打开” 。在对一个文件的读或写完成后,就应该终止文
件和文件型指针之间的联系,这被称为“文件的关闭” 。在 C 语言中,文件的打开和关闭,都
是由专门的系统函数(fopen、fclose)来完成的。
8.编译预处理
为了扩展 C 语言的编程环境,提高程序设计者的编程效率,可以在 C 源程序里加入以“#”号开
头的预处理命令,并在编译之前由预处理程序完成预处理命令规定的功能。这就是所谓的“编译预
处理”
。原则上,预处理命令在程序中出现的位置不受约束,但通常总是把它们写在程序的开头。
9.宏和宏替换
用特定标识符代表一个字符序列,这就是所谓的“宏”。在程序编译前,由预处理程序
先行用字符序列取代出现在程序中的标识符,这就是所谓的“宏替换”。
10.条件编译
根据条件的成立与否,来决定源程序中某一部分是否进行编译,是“条件编译”。

10.1.2 本章重点
1.文件的打开与关闭
(1)文件的打开
为了建立起一个文件和一个文件型指针间的联系,即打开一个文件,需要使用 fopen()
函数。调用 fopen()函数的一般方式是:
FILE *fp;
fp = fopen (char *filename, char *made);
其中,filename 是一个字符型指针,指向要被打开的那个文件的文件名(一个字符串);made
也是一个字符型指针,指向代表被打开文件使用模式的字符串。文件使用模式见表 10-1;fp
是一个文件型指针变量。在调用函数 fopen()成功后,返回指向被打开文件 FILE 型结构的地
– 271 –
C 语言程序设计—示例与习题解析

址。将该地址赋予变量 fp,就使 fp 与这个文件建立了联系。于是,系统就可以自动使用该


文件 FILE 型结构中的文件内部指针控制文件的存取工作了。如果打开不成功,则 fopen()函
数将返回一个空指针 NULL。
(2)文件的关闭
为了关闭一个已打开的文件,需要使用 fclose()函数。如果 fp 是指向已打开文件的文
件型指针,那么执行语句:
fclose(fp);
就切断了 fp 与该文件的联系,即该文件被关闭了。下面是程序中关于文件的打开和关
闭两个函数配合使用的框架形式:
#include<stdio.h>
FILE *fp;
…… ……
if ((fp = fopen(“文件名”, “文件使用模式”)) == NULL) /* 打开文件并测试 */
{
printf ("The file can not open!\n");
exit(0);
}
…… …… /* 打开成功,到此执行 */
fclose(fp); /* 关闭文件 */

表 10-1 fopen()函数中文件的使用模式
使用模式字符串 含 义
r 打开一个文本文件供读取数据。如果文件不存在,则返回 NULL
w 建立一个供写入数据的文本文件。若该文件已存在,废弃原有内容
a 打开或建立一个把数据追加到文件尾的文本文件
r+ 打开一个用于更新数据(读和写)的文本文件
w+ 建立一个用于更新数据(读和写)的文本文件,若该文件已存在,废弃原有内容
a+ 打开或建立一个用于更新数据(读和写)的文本文件,写入的数据追加到文件尾

如果打开的对象是一个二进制文件,那么只需在表中所列使用模式字符串的后面增加一
个字符“b”即可。比如“rb”表示打开一个二进制文件供读取数据。
(3)标准输入/输出文件的打开和关闭
标准输入/输出文件是由系统自动打开和关闭的。与这些设备文件对应的文件型指针如
表 10-2 所示。

表 10-2 标准输入/输出文件的文件型指针
文 件 名 文件型指针
标准输入文件 stdin
标准输出文件 stdout
标准错误输出文件 stderr

– 272 –
第 10 章 文件、编译预处理

2.文件的读与写
(1)把一个字符写入文件,或从文件中读取一个字符
• 函数 fputc()把一个字符写入文件。其函数头是:
int fputc (char ch, FILE *fp)
它把 ch 里的字符写入 fp 指向的文件,写入的位置由文件内部指针指定。然后文件内部指针
自动前进一个字节,移到该文件下一个可写入位置。
• 函数 fgets()从文件中读取一个字符。其函数头是:
int fgetc (FILE *fp)
它从 fp 指向的文件的、由文件内部指针指定的当前位置处读取一个字符。然后文件内部指针
自动前进一个字节,移到该文件下一个可读出位置。
(2)把一行数据写入文件,或从文件中读取一行数据
• 函数 fputs()把一行数据写入文件。其函数头是:
int *fputs (char *str, FILE *fp)
它把由 str 指向的字符串(去除字符串结束符‘\0’)写入 fp 指向的文件,写入位置由文件
内部指针指定。然后文件内部指针自动跨越该字符串长度,移到文件下一个可写入的位置。
• 函数 fgets()从文件中读取一行数据。其函数头是:
char *fgets (char *str, int n, FILE *fp)
它从 fp 所指向的文件的、由文件内部指针指定的当前位置处读取 n−1 个字符,在其后补充
字符串结束符‘\0’ ,组成字符串存入由 str 指向的内存区。然后文件内部指针自动跨越该字
符串长度,移到文件下一个可读出的位置。
(3)把指定字节数的若干数据写入文件,或从文件中读取指定字节数的若干数据
• 函数 fwrite()把指定字节数的若干数据写入文件。其函数头是:
int fwrite (char *buf, unsigned size, unsigned n, FILE *fp)
size 是每个数据的字节数,n 是数据的个数。这些数据存放在 buf 所指的内存区里。把它们
写入 fp 指向的文件,然后文件内部指针自动跨越 size∗n 个字节,移到文件下一个可写入
的位置。
• 函数 fread()从文件中读取指定字节数的若干数据。其函数头是:
int fread (char *buf, unsigned size, unsigned n, FILE *fp)
size 是每个数据的字节数,n 是数据的个数。从 fp 所指向的文件的、由文件内部指针指定的
当前位置处读取 n 个数据(每个数据长 size 个字节) ,存放到由 buf 所指的内存区里。然后
文件内部指针自动跨越 size∗n 个字节,移到文件下一个可读取的位置。
(4)把指定格式的数据写入文件,或从文件中读取指定格式的数据
• 函数 fprintf()把指定格式的数据写入文件。其函数头是:
int fprintf (FILE *fp, char *format, e1, e2, ……, en)
它把表达式 e1, e2, ……, en 按照格式控制串(由指针 format 指向)规定的格式,写入 fp
指向的文件,写入位置由文件内部指针指定,然后文件内部指针自动移到文件下一个可写入
的位置。
• 函数 fscanf()从文件中读取指定格式的数据。其函数头是:
int fscanf (FILE *fp, char *format, d1, d2, ……, dn)

– 273 –
C 语言程序设计—示例与习题解析

它从 fp 所指向的文件的、由文件内部指针指定的当前位置处,按照格式控制串(由指针 format
指向)规定的格式,读取 n 个数据,存入内存中 d1, d2, ……, dn 所指地址的单元中,然后
文件内部指针自动移到文件下一个可读取的位置。
3.文件位置的确定与指针管理
(1)文件尾的测试
读取文件中的数据时, 决不能越过文件尾去读取数据。 在 C 语言程序里,是通过函数 feof()
来测试是否遇到文件结束符的。其函数头是:
int feof (FILE *fp)
调用该函数,表示要对 fp 所指向的文件进行测试。若当前已到达文件尾,则函数返回非 0
值,否则返回 0 值。
不同的计算机系统,以不同的键盘组合键形成文件的结束符。对于 IBM PC 及其兼容机,
在输入文件数据的过程中,通过键盘键入:
<ctrl> + z
(即在按住 ctrl 键的同时,按字母键 z)就在文件尾形成了文件结束符。
(2)把文件内部指针重新定位到文件的起始位置
程序中通过调用函数 rewind(),可以把文件内部指针重新定位到文件的起始位置。其函
数头是:
int rewind (FILE *fp)
它表示要把由 fp 所指向的文件的内部指针,重新定位到文件的开头。
(3)把文件内部指针定位到指定的位置
程序中通过调用函数 fseek(),可以把文件内部指针定位到所需要的位置。其函数头是:
int fseek (FILE *fp, long offset, int from)
其中,from 是定位的起始点,只能取表 10-3 所列的某一个值。
表 10-3 from 的取值表
符号常数 相应整数值 含 义
SEEK_SET 0 从文件头开始
SEEK_CUR 1 从文件内部指针当前位置开始
SEEK_END 2 从文件尾开始

offset 是以 from 为起始点的偏移字节数,大于 0 表示向文件尾方向,小于 0 表示向文


件头方向,0 表示不移动。于是,调用函数 fseek(),表示要把由 fp 所指向的文件的内部指
针,从 from 指定的起始点移动 offset 个字节。
(4)文件操作出错测试
可以使用函数 ferror(),来判定文件操作是否出错。其函数头是:
int ferror (FILE *fp)
用于对由 fp 指向的文件最近的一次操作进行正确性测试。如果操作出错,则返回非 0,否则
返回 0。比如,可以编写如下一个通用的出错处理函数供其他函数调用。
void errp (FILE *fp)
{

– 274 –
第 10 章 文件、编译预处理

if (ferror (fp) != 0) /* 操作失败,终止运行 */


{
printf ("file operate be defeated.\n");
exit (0);
}
else
return; /* 操作成功,返回继续运行 */
}
4.文件的删除
可以使用函数 remove()来删除指定的文件。其函数头是:
int remove (char *filename);
参数是指向文件名字符串的指针,或直接是文件名字符串。比如,
int remove ("E:/dahua/cprog/ztest.dat");
就把在路径“E:/dahua/cprog/”下的名为“ztest.dat”的文件给删除掉。
例 10-1 编写一个 main 函数,从键盘上键入 10 个字符,形成一个名为 ztest.dat 的文
件,存于“E:/dahua/cprog”目录下。
[解] 程序编写如下:
#include<stdio.h>
main()
{
FILE *cfptr;
int k;
char ch;
cfptr = fopen ("E:/dahua/cprog/ztest.dat","w");
if (cfptr == NULL)
{
printf ("file can not open!\n");
exit (0);
}
for (k=0; k<10; k++)
{
ch = getchar ();
fputc (ch, cfptr);
}
fclose (cfptr); /* 使用完毕,不要忘记关闭文件 */
}
这里,首先通过调用 fopen()函数,建立起文件型指针 cfptr 与文件 ztest.dat 间的联
系。由于该文件原本并不存在,调用 fopen()函数后,在“E:/dahua/cprog/”路径下,就有
了这个文件,从图 10-1 给出的资源管理器中可以看到它的存在。随之,由“ch = getchar ();”
– 275 –
C 语言程序设计—示例与习题解析

从键盘输入字符,由“fputc (ch, cfptr);”把 ch 里的字符写入 cfptr 指向的文件里,也就


是逐个写入到文件 ztest.dat 里。

图 10-1 在资源管理器中看到文件 ztest.dat 的存在

如果把程序中的语句:
ch = getchar ();
改写为:
ch = fgetc (stdin);
那么就表示是从 stdin 指向的文件(即键盘)读取字符,效果是一样的。
例 10-2 从键盘读取 3 个字符串,存放到内存的二维数组 s1 中,并将它们依次写入
“E:/dahua/cprog/zstr.txt”文件。随之将该文件读至内存数组 s2 里,显示到屏幕上。
[解] 程序编写如下:
#include<stdio.h>
main()
{
FILE *fp;
int k;
char s1[3][40], s2[3][40];
for (k=0; k<3; k++) /* 通过键盘输入 3 个字符串 */
gets (s1[k]);
fp = fopen ("E:/dahua/cprog/zstr.txt","w"); /* 建立文本文件 zstr.txt */
if (fp == NULL)
{
printf ("file can not open!\n");
exit(0);
}
for (k=0; k<3; k++) /* 把内存 s1 里的字符串逐一写入 fp 所指的文件 */
fputs(s1[k], fp);
fclose (fp);
fp = fopen ("E:/dahua/cprog/zstr.txt","r"); /* 换一个使用模式打开文件 */

– 276 –
第 10 章 文件、编译预处理

if (fp == NULL)
{
printf ("file can not open!\n");
exit(0);
}
printf ("output content of s2:\n");
for (k=0; k<3; k++)
{
fgets (s2[k], 40, fp); /* 把 fp 所指文件中的字符串读至内存数组 s2 */
fputs (s2[k], stdout); /* 把内存数组 s2 写入标准输出文件 stdout(显示) */
}
fclose (fp);
}
图 10-2 所示的是该程序的一次运行结果。注意在键盘上键入的文件结束符。另外还要
注意,程序中第一次是以“w”的方式打开文件,因此在对它读以前,必须先关闭它,然后再
以“r”的方式第 2 次打开它,否则无法达到输出 s2 的目的。

图 10-2 例 10-2 的一次运行结果

例 10-3 编写一个能存储 100 个人的工资表,每个记录由工资号、姓名、工资构成。要


求先对该表进行初始化:工资号全为 0,姓名为空字符串,工资为 0.0。根据输入的工资号,
键入人员的姓名和工资。最后把有实际内容的人员记录打印出来。
[解] 程序编写如下:
#include<stdio.h>
struct cltd /* 定义人员结构 */
{
int num; /* 工资号 */
char name[20]; /* 人员姓名 */
float wages; /* 工资额 */
};
main()
{
int k;
struct cltd temp = {0, "", 0.0}; /* 临时结构变量 temp 初始化 */
FILE *fp;

– 277 –
C 语言程序设计—示例与习题解析

if ((fp=fopen("E:/dahua/cprog/cltd.dat","w")) == NULL) /* 建立文件 */


{
printf ("File could not be opened!\n");
exit(0);
}
for (k=0; k<=100; k++) /* 完成对 100 个人员记录的初始化 */
fwrite (&temp, sizeof (struct cltd), 1, fp);
fclose (fp); /* 关闭文件 */
if ((fp=fopen("E:/dahua/cprog/cltd.dat","r+")) == NULL) /* 以“r+”方式打开
文件 */
{
printf ("File could not be opened!\n");
exit(0);
}
printf("Enter number: 1to 100, 0 to end input.\n?");
scanf ("%d", &temp.num); /* 输入人员编号 */
while (temp.num != 0) /* 当编号不为 0 时,继续输入其他信息 */
{
printf ("Enter name, wages.\n?");
scanf ("%s%f", temp.name, &temp.wages);
fseek (fp, (temp.num−1)*sizeof(struct cltd), SEEK_SET); /* 根据编号定位 */
fwrite (&temp, sizeof(struct cltd), 1, fp); /* 根据编号随机写入记录信息 */
printf ("Enter number\n?");
scanf ("%d", temp.num);
}
rewind (fp); /* 把文件内部指针定位于文件头 */
printf ("%−10s%−10s%−8s\n","Number","Name","Wages");
while (!feof(fp)) /* 未到表尾,继续往下 */
{
fread (&temp, sizeof(struct cltd), 1, fp);
if (temp.num != 0) /* 把工资号不为 0 的记录打印输出 */
printf ("%−10d%−10s%−8.2f\n", temp.num, temp.name, temp.wages);
}
fclose (fp); /* 再次关闭文件 */
}
程序中用结构 cltd 表示一个人员的记录,它包括 3 个成员:工资号 num、姓名 name 以
及工资额 wages。程序中利用循环:
for (k=0; k<=100; k++)
fwrite (&temp, sizeof (struct cltd), 1, fp);

– 278 –
第 10 章 文件、编译预处理

完成对 100 个人员记录的初始化。要注意,函数 fwrite 在完成对 fp 指向文件的一次写入后,


其文件内部指针会自动移一个结构的距离。正因为如此,通过循环就能够完成对 100 个人员
记录的初始化工作。
完成初始化后,应该将文件关闭。这是因为原先对它打开的使用模式定为“w”,而下面
将以“r+”(即可写也可读)模式工作,不重新打开是不行的。
重新打开后,由于是根据工资号来写入记录内容的,因此要通过语句:
fseek (fp, (temp.num−1)*sizeof(struct cltd), SEEK_SET);
来对文件的内部指针随机定位,然后才能执行操作:
fwrite (&temp, sizeof(struct cltd), 1, fp);
在输入 0 编号后,整个输入停止。为了打印非 0 工资号的人员信息,程序中通过语句:
rewind (fp);(注意,也可以是 fseek (fp, 0, SEEK_SET);)
把文件内部指针定位于文件头。只要没有到达文件尾(!feof(fp)) ,循环就继续下去,即通
过语句:
fread (&temp, sizeof(struct cltd), 1, fp);
读出一个记录到临时结构变量 temp(在这之后,分配所指文件的文件内部指针自动移动一个
结构的距离),根据它的工资号是否为 0,决定是否打印。图 10-3 所示的是该程序一次运行
后的输出结果。

图 10-3 例 10-3 的一次运行结果

5.宏定义
宏定义就是在符号“#”后跟随保留字 define,组成宏定义命令,定义一个标识符和一
个字符序列,这个标识符叫宏名。编译预处理时,预处理程序把程序中出现的所有宏名都用
字符序列替换,这个过程称为宏替换。宏定义命令的一般形式是:
#define 标识符 字符序列
比如,在程序前给出宏定义命令:
#define PI 3.1415926
那么在编写程序时,就不必书写长长的字符序列 3.1415926,而用简单的标识符 PI 代之。到
编译预处理时,又反过来用 3.1415926 替换掉程序中的标识符 PI。这样,真正编译时,编译
– 279 –
C 语言程序设计—示例与习题解析

程序面对的仍然是 3.1415926。宏的好处一方面体现在编程时输入效率高,另一方面也减少
了输入时的错误。
使用宏定义要注意如下几点。
(1)建议用英文大写字母组成宏名,一是醒目,二是便于与变量名区别。
(2)建议把宏定义安排在程序的开头。
(3)宏定义中的宏名和字符串等各组成部分之间用空格隔开,最后不能有分号。
(4)宏替换时,编译预处理程序只是简单地用定义中的字符序列替换宏名。如果有程序:
#define E_SM "File could not be opened.\n" /* 双引号也是字符序列的一部分 */
…… ……
printf (E_SM); /* 注意,这里没有双引号*/
那么,编译预处理时,就用“File could not be opened.\n”替换 printf()语句中出现的
宏名 E_SM。所以,正式编译时,编译程序面对的 printf()语句的实际形式是:
printf ("File could not be opened.\n");
(5)如果一个宏名出现在字符串(即双引号)里,编译预处理不对它进行宏替换。比如
上面的情形改为
#define E_SM File could not be opened.\n /* 注意,这里没有双引号*/
…… ……
printf ("E_SM"); /* 注意,这里有双引号*/
这时,编译预处理不会把 printf()语句替换成
printf ("File could not be opened.\n");
而是让其保持原样。
(6)宏定义里可以让宏名带有形式参数。在程序中用到时,用实际参数代替形式参数。
比如有程序:
#define MIN(a,b) (a<b) ? a : b
main ()
{
int x=10, y=20;
printf ("the minimum is %d\n", MIN(x, y));
}
这里的宏定义 MIN 就带有两个形式参数 a 和 b。编译预处理时,就把 printf()语句替换

printf ("the minimum is %d\n", (x<y) ? x : y);
由于编译预处理程序只是简单机械地做宏替换,因此在定义带有形式参数的宏名时,要
特别注意保证参数的整体性,否则会引起不必要的麻烦。比如有如下程序:
#define EVEN(x) x %2 == 0 ? 1 : 0
main()
{
if (EVEN(9+1))
printf ("it is even.\n");

– 280 –
第 10 章 文件、编译预处理

else
printf ("it is odd.\n");
}
这时,编译预处理程序只是认为“9+1”是实际参数,机械地用它去替代形式参数 x,因
此 EVEN(9+1)被替换成
9+1%2 == 0 ? 1 : 0
由于“%”的优先级高于“+” ,因此“9+1%2”显然不等于 0,导致整个表达式的值为 0。程序
就打印出“it is odd.”
。但“9+1”应该是偶数,而不是奇数。这种奇怪的结果,是因为没
有保证参数的整体性而出现的。为此,必须在宏定义中,为字符序列里的形式参数括上圆括
号,即改写成
#define EVEN(x) (x) %2 == 0 ? 1 : 0
(7)宏最常见的用法是利用它定义数组的长度。当程序中有多个地方要访问这个数组时,
如果用一个实际的常数说明数组的大小,数组就固定死了。如果在程序开头用宏定义定义数
组的大小,比如,
#define MAX_SIZE 10
float wages [MAX_SIZE]; /* 说明一个 float 型数组 */
那么若要让数组拥有 100 个元素,只需在程序的#define 命令里把 MAX_SIZE 后的字符序列修
改成 100,重新编译即可达到目的。
(8)程序中可以使用命令:
#undef <宏名>
来解除对指定<宏名>的定义,即在程序中遇到“#undef <宏名>”后,前面用“#define <宏
名>”定义的<宏名>不再有效。用此方法,可以控制宏名使用的范围。
6.文件包含
文件包含就是在符号“#”后跟随保留字 include,组成文件包含命令。其作用是告知编
译程序,把命令中所指明的另一个源文件嵌入到当前所在的程序。这样,编程人员可以把现
成的程序拿来就用,减少重复性劳动。文件包含的一般形式是:
#include “文件名” 或 #include <文件名>
这里,如果用双引号括住文件名,则系统先在本程序文件所在路径下搜索指明的包含文件。
若找不到,则再按系统规定的路径去搜索所包含文件;如果用尖括号括住文件名,则系统仅
按系统规定的路径去搜索所包含文件。
使用文件包含命令,要注意如下几点。
(1)一个#include 命令只能包含一个源文件。如果要包含多个文件,就必须编写多条文
件包含命令。
(2)文件包含命令的最后不能有分号。
(3)文件包含命令中指明的只能是源文件,而不能是目标文件。编译预处理把本文件和
包含进来的文件形成一个源程序,然后进行统一编译,产生出目标代码。
7.条件编译
利用若干种条件编译命令,可以有选择地对程序中的某个部分进行或不进行编译,从而
一个程序可以有多种不同的版本。
– 281 –
C 语言程序设计—示例与习题解析

(1)根据常量决定是否编译
这种条件编译的基本形式是:
#if <常量>
<程序段 1>
#else
<程序段 2>
#endif
含义是:如果常量值为非 0(条件成立),则对<程序段 1>进行编译;否则对<程序段 2>
进行编译。
例 10-4 编写一个程序,利用条件编译,使该程序既可以求出输入整数中的最大值,也
可以求出其中的最小值。
[解] 这就是编写一个共用的输入程序,然后把求最大值和最小值的程序段分开编写,
在它们之间安排#if-#else-#endif 即可。整个程序编写如下:
#include<stdio.h>
#define MAX_SIZE 100
#define FLAG 1
main()
{
int array[MAX_SIZE], temp, *ptr = array;
while (ptr<array + MAX_SIZE)
scanf ("%d", ptr++);
ptr = array;
#if FLAG
temp = *ptr;
while (++ptr < array + MAX_SIZE)
if (temp<*ptr)
temp = *ptr;
printf ("The maximum = %d\n", temp);
#else
temp = *ptr;
while (++ptr < array + MAX_SIZE)
if (temp>*ptr)
temp = *ptr;
printf ("The minimum = %d\n", temp);
#endif
}
该程序在预编译时,由于 FLAG 当前取值为 1,因此就会剔除#else-#endif 之间的程序
段,形成求输入数据最大值的程序。如果把源程序里的“#define FLAG 1”改成“#define FLAG
0”,那么在预编译时,就会剔除#if-#else 之间的程序段,形成求输入数据最小值的程序。
– 282 –
第 10 章 文件、编译预处理

可见,利用这种条件编译手段,可以提高编程效率。
(2)根据宏名决定是否编译
这种条件编译有两种基本形式,第一种形式是:
#ifdef <宏名>
<程序段 1>
#else
<程序段 2>
#endif
含义是:如果在此前<宏名>已经由#define 做了定义,则对<程序段 1>进行编译;否则
对<程序段 2>进行编译。
第二种形式是:
#ifndef <宏名>
<程序段 1>
#else
<程序段 2>
#endif
含义是:如果在此前未用#define 对<宏名>做定义,则对<程序段 1>进行编译;否则对<
程序段 2>进行编译。比如有如下程序:
#define CALL 5
main()
{
#ifdef CALL
printf ("i Wang!\n");
#else
printf ("Hi anyone.\n");
#endif
#ifndef HELLO
printf ("HELLO not defined.\n");
#endif
}
该程序编译后运行,将打印出:
Hi Wang!
HELLO not defined.
但如果把程序最前面的“#define CALL 5”删去,则打印出:
Hi anyone!
HELLO not defined.

10.1.3 本章难点
1.选取文件使用模式的重要性
– 283 –
C 语言程序设计—示例与习题解析

程序在内存中运行时,用变量或数组元素等存放数据。比如有变量说明:
int x;
系统就为 x 分配两个字节的存储区。因此,执行赋值语句:
x = 1373;
后,数据 1373 将以如下的形式存放在两个字节里:

这是所谓的用二进制形式存放数据。
如果存储在磁盘的数据文件,也以二进制形式存储数据,那么这种文件就是二进制文件。
由于二进制文件存储数据的形式,与内存中存储数据的形式是一致的,因此从二进制文件中
读出的数据,可以直接参与加工和处理。
还可以以数据每个字符的 ASCII 码值形式存储数据。比如对于数值 1373,由于它是由 4
个字符:‘1’
、‘3’、‘7’、‘3’组成,于是用 4 个字节来存储它,即

如果存储在磁盘的数据文件,以 ASCII 码值形式存储数据,那么这种文件就是文本文件。


由于文本文件存储的数据形式与内存存储数据的形式不同,因此从文本文件中读取数据时,
必须先将数据的 ASCII 码值形式转换成二进制形式,才能参与加工和处理;把数据写入文件
时,又必须将其二进制形式转换成 ASCII 码值形式。
可以看出,用二进制文件来存储数据是合理的,只有像程序清单等文字文件,才采用文
本文件的形式进行存储。
既然数据文件存储在磁盘上有两种不同的形式,所以打开一个文件时,必须明确给出表
10-1 所示的文件使用模式。如果指定的文件使用模式不正确,那就不可能正确地对文件中的
数据进行读写。
2.文件数据的读写方式
如果从文件头开始,依次把数据写入文件;仍从文件头开始,依次读取文件中的数据,
那么这种文件称为顺序存取文件。读写顺序文件数据的程序段,其一般的编写框架可以是如
下形式:
• 用函数 fopen()打开所需文件
• while (!feof (文件型指针) )
{
用有关的读写函数,对数据进行读/写操作
}
• 用函数 fclose()关闭文件
如果根据指定的位置,就能存取文件中的数据,那么这种文件称为随机存取文件。因此,

– 284 –
第 10 章 文件、编译预处理

读写随机文件中的数据,先要定位,然后才能进行读写。读写随机文件数据的程序段,其一
般的编写框架可以是如下形式:
• 用函数 fopen()打开所需文件
• 用函数 freek()确定读写数据的位置
• 用有关的读写函数,对数据进行读/写操作
• 用函数 fclose()关闭文件
例 10-5 利用函数 fprintf()、fscanf(),顺序往文件里存储 5 个 float 型数据,然后
再读出这 5 个数据加以验证。
[解] 程序编写如下:
#include<stdio.h>
#define NUM 5
main()
{
FILE *fp;
float d[NUM];
int k;
char fn[30];
printf ("Enter a file name:");
scanf ("%s", fn);
printf ("Enter 5 float numbers:\n");
for (k=0; k<NUM; k++)
scanf ("%f", (d+k));
if ((fp=fopen(fn,"w")) == NULL) /* 以“w”方式打开文件 */
{
fprintf (stderr,"File could not be opened!\n");
exit(0);
}
for (k=0; k<NUM; k++)
fprintf (fp,"%f", d[k]);
fclose (fp); /* 写入操作完毕,关闭打开的文件 */
if ((fp=fopen(fn,"r")) == NULL) /* 以“r”方式打开文件 */
{
fprintf (stderr,"File could not be opened!\n");
exit(0);
}
printf ("Output 5 float numbers:\n");
for (k=0; k<NUM; k++)
{
fscanf (fp,"%f", (d+k) );

– 285 –
C 语言程序设计—示例与习题解析

printf ("d[%d]=%.3f", k, d[k]);


}
fclose (fp); /* 读取操作完毕,关闭打开的文件 */
printf ("\n");
}
程序中定义了一个名为 NUM 的宏,它出现在多个循环里。要打开的文件名是由用户通过
键盘自己输入的。第 1 次以“w”方式打开文件,利用语句:
fprintf (fp,"%f", d[k]);
把已经输入到数组 d 里的数据,按照“%f”的格式写入到文件型指针 fp 指向的文件中;第 2
次以“r” 方式打开文件,利用语句:
fscanf (fp,"%f", (d+k) );
把由文件型指针 fp 指向的文件中的数据,逐一按照“%f”的格式读到数组 d 中。图 10-4 所
示的是该程序的一次运行结果。

图 10-4 例 10-5 的一次运行结果

3.带参数的宏与函数
从形式上看,带参数的宏与函数有类似的地方。但不能把二者等同起来,它们之间是有
区别的。主要表现在如下几个方面。
(1)对于带参宏,无论是宏名还是形式参数都只是一种符号代表,没有类型的限制;但
函数的参数是有类型限定的。
(2)带参宏的替换是在编译预处理时进行的,且不给参数分配内存单元;函数调用则在
程序运行时发生,要为参数分配临时内存单元。
(3)宏替换只是“机械地”在所定义的字符串基础上,用实际参数替代形式参数,不做
任何运算;但函数调用时,是先求出实际参数的值,然后将其传递给形式参数。
例 10-6 阅读下面程序,分析输出结果。
#include<stdio.h>
#define SQUARE(n) ((n)∗(n))
main()
{
int k = 1;
while ( k<=10 )
printf ("%5d", SQUARE(k++));
printf ("\n");
}

– 286 –
第 10 章 文件、编译预处理

[解] 程序里,定义了一个名为 SQUARE 的带参宏,意欲通过它求出参数的平方。在主


函数 main 里,试图使用它 10 次,输出 1、2、…… 、10 的平方值。但是程序的运行结果却是:
2 12 30 56 90
为什么会是这样呢?这是因为 宏替换把 SQUARE(k++)展开成为(k++)∗(k++),于是
printf()语句变为:
printf ("%5d", (k++)∗(k++));
当 k 为 1 时,第 1 个(k++)使参加求值的 k 为 1,然后 加 1 成为 2;第 2 个(k++)使参加求值的
k 为 2,然后加 1 成为 3。于是,第 1 次打印的值是 1∗2 的结果。第 1 次循环后,回到 while
时,k 是 3。由此可知,第 2 次循环打印的值是 3∗4 的结果。在 k 为 5 的情形下,回到 while
进入第 3 次循环。这样,第 3 次循环打印的值是 5∗6 的结果。在 k 为 7 的情形下,回到 while
进入第 4 次循环。这样,第 4 次循环打印的值是 7∗8 的结果。在 k 为 9 的情形下,回到 while
进入第 5 次循环。这样,第 5 次循环打印的值是 9∗10 的结果。最后,由于 k 为 11 而停止循
环。
下面编写一个输出 1、2、……、10 的平方值的函数,也取名 square,由 main 调用,它
不会出现这种奇怪的现象。
#include<stdio.h>
int square (int n)
{
return n∗n;
}
main()
{
int k = 1;
while ( k<=10 )
printf ("%5d", square(k++));
printf ("\n");
}

10.2 典型示例分析

示例 1 编写一个程序,它逐行读出文本文件”test.txt”的内容,直到遇见 EOF(文
件结束符)时为止。在阅读中随时记录每行的长度和行号,以便在最后能输出最长行的长度
以及行号。如果有相同长度的行,则只取最先碰到的那行。
[解] 程序编写如下:
#include<stdio.h>
main()
{
int ch, len, maxlen;

– 287 –
C 语言程序设计—示例与习题解析

long longest = 0, line = 0;


FILE *fp;
if ((fp=fopen("test.txt","r")) == NULL)
{
printf ("File could not be opened!\n");
exit(0);
}
while ((ch=fgetc(fp)) != EOF)
if (ch == '\n')
{
++line;
if (len>maxlen)
{
maxlen = len;
longest = line;
}
len = 0;
}
else
++len;
fclose (fp);
printf ("\nThe longest line is %ld\n", longest);
printf ("it has %d characters.\n", maxlen);
}
程序中,变量 ch 用于接收由函数 fgetc()读出的一个个字符;变量 len 用于记录一行中
的字符个数;变量 maxlen 记录当前最长行中字符的个数;变量 line 用于记录行数;变量
longest 用于记录当前最长行的行号。如果不是回车换行符(‘\n’),表明是在某一行的里面,
因此就由变量 len 记录该行里的字符个数(++len;) ;如果遇到回车换行符,表示一行结束,
于是比较 len 和 maxlen。如果 len>maxlen,那么就要对 maxlen 和 longest 进行修正,并且
要将变量 len 清 0(len = 0;)
,以便开始下一行的计数工作。
示例 2 编写一个程序,该程序先后从两个文件 test1 和 test2 读出数据,然后写到文
件 test 里,从而形成两个输入文件的连接。
[解] 程序编写如下:
#include<stdio.h>
main()
{
FILE *fp, *fg;
int ch, k = 1;
fg = fopen ("test","w");

– 288 –
第 10 章 文件、编译预处理

while (1)
{
if (k<=2)
{
if (k == 1)
fp = fopen ("test1","r");
else
fp = fopen ("test2","r");
while ((ch=fgetc(fp)) != EOF)
fputc (ch, fg);
if (k == 1)
fclose (fp);
else
{
fputc (ch, fg);
fclose (fp);
}
k++;
}
else
{
fclose (fg);
break;
}
}
}
程序中先用 fp 指向文件 test1,在把 test1 里的内容写入到 test 里去后,再用 fp 指向
文件 test2。至于 fp 是指向 test1 还是 test2,则由变量 k 的取值来控制。另外总是用 fg
指向文件 test。要注意的是,当 k 等于 1 时,遇到 EOF 就停止对文件 test1 的拷贝,将其关
闭后,马上打开 test2,开始对它的拷贝工作。但在遇到文件 test2 的 EOF 时,为了在文件
test 的最后添加上文件结束符,还必须继续做一个操作:fputc (ch, fg);,然后才能停止
整个拷贝工作。
示例 3 编写一个带参数的主函数,它的可执行文件名为 test.exe。在 DOS 提示符下接
收一个如下的命令行:
test zpsy.c
在检查它确实带有扩展名“.c”后,去寻找文件“zpsy.o”,即有无文件名相同、扩展
名为字母‘o’的文件存在。若有,返回“OK”,否则返回“NO OBJECT FILE”。
[解] 程序编写如下:
#include<stdio.h>

– 289 –
C 语言程序设计—示例与习题解析

#include<string.h>
main(int argc, char *argv[ ])
{
char *ax;
FILE *fp;
if (argc != 2)
{
printf ("usage : %s filename\n", argv[0]);
exit(0);
}
if ((ax = strrchr(argv[1], '. ' )) == NULL || (*(ax+1) != ' c ' || *(ax+2) != ' \0 ' ))
printf ("no .c extension.\n");
else
{
*(ax+1) = ' o ' ;
if ((fp=fopen(argv[1],"r")) == NULL)
printf ("NO OBJECT FILE!\n");
else
{
fclose (fp);
printf ("OK!\n");
}
}
}
程序里用到字符串函数 strrchr()。其函数头是:
char *strrchr (char *str, char ch)
功能是在 str 所指向的字符串中,找到最后一次出现的 ch 中给出的字符,然后返回该字符的
位置指针。若没有匹配字符,则返回空指针。因此:
(ax = strrchr(argv[1], '. ' )) == NULL
表示在 DOS 提示符下接收到的命令行里没有字符 '. ' 。而条件:
*(ax+1) != ' c '
表示虽然找到了字符‘.’,但在该字符的后面不是字母 ' c ' 。条件:
*(ax+2) != ' \0 '
表示在字母 ' c ' 的后面字符串没有结束。
排除各种情况后,才表明所键入的命令行:
test zpsy.c
是正确的,因此才由语句:
*(ax+1) = ' o ' ;
去把扩展名修改成为字母 ' o ' ,在 argv[1]里形成新的文件名:zpsy.o。这时,以“r”
– 290 –
第 10 章 文件、编译预处理

模式去打开这个文件。如果打不开,则表示文件不存在。
示例 4 把 PINT 定义为是指向 int 型指针的宏,PFLOAT 定义为是指向 float 型指针的
宏。利用它们编写一个程序,读进一个整数值和一个实数值,分别把存放在 PINT 和 PFLOAT
所指的存储单元中。
[解] 程序编写如下:
#include<stdio.h>
#define PINT int *
#define PFLOAT float *
main()
{
int x;
float y;
PINT p;
PFLOAT q;
p = &x;
q = &y;
printf ("\nEnter a integer value:");
scanf ("%d", p);
printf ("%d %u %d %u\n", x, &x, *p, p);
printf ("\nEnter a real value:");
scanf ("%f", q);
printf ("%f %u %f %u\n", y, &y, *q, q);
}

10.3 课外习题、答案与解析

10.3.1 课外习题

一、单选题

1.在文件使用模式中,字符串“rb”的含义是( )。
A.打开一个文本文件,只能写入数据
B.打开一个已存在的二进制文件,只能读取数据
C.打开一个已存在的文本文件,只能读取数据
D.打开一个二进制文件,只能写入数据
2.若某文件的文件型指针为 fp,其内部指针现在已指向文件尾。那么函数 feof(fp)的
返回值是( ) 。
A.0 B.−1 C.非零值 D.NULL
– 291 –
C 语言程序设计—示例与习题解析

3.以下叙述中,正确的是( ) 。
A.C 语言程序的开始,必需安排一条预处理命令:#include<stdio.h>
B.一但定义了一个宏,在程序中就不能取消它
C.在 C 语言中,任何编译预处理命令必须以符号“#”开头
D.宏定义必须安排在一个程序的开头
4.下面语句中,把变量 fptr 说明为是一个文件型指针的是( ) 。
A.FILE *fptr B.FILE fptr C.file *fptr D.file
fptr
5.有宏定义如下:
#define F(x) 2∗x
那么表达式 F(2+3)的值是( ) 。
A.10 B.9 C.8 D.7
6.下面的预处理命令中,正确的是( ) 。
A.#define STR "string" B.#include stdio.h
C.#include "string.h" D.#define PI 3.1415926;
7.有宏定义及语句如下:
#define N 3
#define Y(x) ((N+1)∗x)
z = 2∗(N+Y(5+1));
那么,变量 z 的最终取值是( ) 。
A.38 B.42 C.48 D.52
8.读取文件中的单个字符,应该使用函数( ) 。
A.getc() B.fgetc() C.gets() D.fgets()
9.如果要把文件中的一个学生记录(包括学号、姓名、年龄)读到内存中相应的结构
型变量里,那么最好使用函数( ) 。
A.fgetc() B.fgets() C.fscanf() D.fread()
10.以下程序中 for 循环体执行的次数是( )。
#include<stdio.h>
#define N 2
#define M N+1
#define SUM (M+1)*M/2
main()
{
int k, n = 0 ;
for (k=1; k<=SUM; k++)
{
n++;
printf ("%d", n);
}

– 292 –
第 10 章 文件、编译预处理

printf ("\n");
}
A.6 B.8 C.7 D.9

– 293 –
C 语言程序设计—示例与习题解析

二、填空题

1.程序开始时,会自动打开标准输入设备文件。该文件的文件型指针是 。
2.每一条编译预处理命令,都必须以 符号开头。
3. 编译预处理命令用于取消一个宏名的作用。
4. 编译预处理命令把一个源文件嵌入到另一个源文件中。
5.利用宏来计算正方体体积。若宏名为 VOLUME,形式参数为 x,那么该宏的定义应该
写为 。
6.FILE *p 把变量 p 说明为是一个文件型指针。这里用到的“FILE” ,是 在 头
文件里定义的。
7.请完成下面的程序填空, 程序的功能是把名为 file1.txt 的文件拷贝到名为 file2.txt
的文件中。
#include<stdio.h>
main()
{
char ch;
FILE *fp1, *fp2;
fp1 = fopen ("file1.txt", ① );
fp2 = fopen ("file2.txt", ② );
ch = fgetc (fp1);
while (ch != EOF)
{
fputc (ch, ③ );
ch = fgetc ( ④ );
}
}
8.下面给出的程序,把从键盘读入的字符(用‘#’作为输入结束标志)复制到名为
ztest.dat 的文件里。请完成程序的填空:
#include<stdio.h>
main()
{
FILE *fptr;
char ch;
if ((fptr = fopen( ① , "w")) == ② )
exit(0);
while ((ch = getchar() ) != ③ )
fputc (ch, fptr);
fclose ( ④ );
}

– 294 –
第 10 章 文件、编译预处理

三、是非判断题(在括号内打“√”或“×”)

1.程序中必须用函数 fopen()去打开标准输入/输出设备文件。( )
2.如果文件内部指针当前没有指向文件的起始位置,那么要从文件的起始位置读取数
据,就必须先关闭文件再打开它。 ( )
3.函数 freek()能根据文件内部指针,从文件起始点、文件尾以及当前位置完成指针的
定位工作。( )
4.可以用如下的办法:
if ((fp = fopen("test.dat","w")) != NULL)
打开文件 test.dat,并在保留文件原有内容的情况下,把数据添加到文件。
( )
5.语句:
fp = fopen ("tran.dat","r");
的功能是打开供读取数据的文件 tran.dat,把返回的文件指针赋予 FILE 型指针变量 fp。
( )
6.在一个包含命令里,可以分列多个源文件,达到包含多个文件的目的。 ( )
7.下面的程序段:
#ifdef TRUE
#undef TRUE
#define TRUE 1
#endif
表示如果定义了宏名 TRUE,则取消其定义,并重新定义 TRUE 为 1。 ( )
8.C 语言程序中,对宏定义的替换是在源程序正式编译前完成的。 ( )
9.利用#include 命令,可以把任何性质的文件包含到现文件中。 ( )
10.函数 fgets (ptr, n, fp)的功能是:从 fp 指向的文件中连续读取 n−1 个字符,构
成字符串后存入由指针 ptr 所指向的内存区域。 ( )

四、程序阅读题

1.阅读如下程序,给出程序的运行结果。
#include<stdio.h>
#define MIN(x, y) (x)< (y) ? (x) : (y)
main()
{
int m = 10, n = 15, x;
x = 10∗MIN(m, n);
printf ("%d\n", x);
}
2.阅读如下程序,给出程序的运行结果。
#include<stdio.h>
#define LOW (−2)
– 295 –
C 语言程序设计—示例与习题解析

#define HIGH (LOW + 5)


#define PR(x) printf ("%d\n", (x))
#define FOR(x) for ( ; (x); (x)− − )
main()
{
int j = LOW, k = HIGH;
FOR(k)
switch (k)
{
case 1:
PR( j++ );
case 2:
PR(k);
break;
default :
PR(j);
}
}
3.有程序如下:
#include<stdio.h>
void func (FILE *); /* 函数 func 的原型 */
main(int argc, char *argv[])
{
FILE *fp;
int k = 1;
while (− −argc>0)
{
if ((fp=fopen(argv[k++],"r")) == NULL)
{
printf ("File could not be opened!\n");
exit (0);
}
func (fp);
fclose (fp);
}
}
void func (FILE *fptr)
{
char ch;

– 296 –
第 10 章 文件、编译预处理

while ((ch=fgetc(fptr)) != ' # ' )


putchar (ch − 32);
}
该程序的可执行文件名为:test1.exe。现在磁盘上有 3 个文本文件,文件名和文件内
容如下所示:
文件名 文件内容
amt1 aaaaaa#
bmt2 bbbbbb#
cmt3 cccccc#
在 DOS 提示符下键入命令行:
test1 amt1 bmt2 cmt3<CR> (<CR>表示回车换行)
阅读它,并给出程序的输出结果。
4.分析下面程序的输出结果。
#include<stdio.h>
#define X1 a+b
#define X2 X1∗X1
main()
{
int a = 2, b = 3;
printf ("a=%d b=%d X2=%d\n", a, b, X2);
}
5.阅读下面的程序,其中涉及的文件 wps.c 是一个 C 语言源程序。分析其功能。
#include<stdio.h>
main()
{
FILE *fp;
char ch;
int m=0, n=0;
if ((fp=fopen("wps.c","r")) == NULL)
{
printf ("File could not be opened!\n");
exit(0);
}
while (!feof(fp))
{
ch = fgetc(fp);
if ( ch == ' [ ' ]
m++;
if (ch == ' ) ' )

– 297 –
C 语言程序设计—示例与习题解析

n++;
}
fclose(fp);
if (m == n)
printf ("' [ ' and ' ] ' is mate!\n");
else
printf ("' [ ' and ' ] ' is not mate!\n");
}

五、程序设计题

1.编写一个程序,从键盘输入文件名,将该文件打开。然后把键盘输入的字符存入文
件,遇到字符“#”时输入结束。
2.有结构定义如下:
typedef struct
{
char name[30];
int age;
float gpa;
}STUDENT;
编写一个函数 copy(file1, file2, limt),它从文件 file1 中读取具有上述结构的数据,
把成员 age 的值小于 limt 的结构数据复制到文件 file2 里。
3.编写一个函数,它接收一个文件名字符串,判别该文件是否存在。若存在则返回 1,
否则返回 0。在 main()里,由用户输入文件名,调用所编函数,验证其正确性。
4.定义一个带参数的宏,使两个参数的值互换。在主程序 main()里输入数据做为宏的
实际参数,然后输出调换后的结果。
5.定义一个带参数的宏,在 2 个整数中得到最大者。在主程序 main()里嵌套使用该宏,
以便从输入的 3 个数中得到最大者。

10.3.2 答案与解析

一、单选题

1.B 2.C 3.C 4.A 5.D 6.C


7.C 注意:Y(5+1)的宏替换应该是(N+1)∗5+1。再对 N 进行宏替换,结果成为(3+1)∗5+1。
因此变量 z 应该等于 2∗(3+(3+1)∗5+1)。计算后得到 48。
8.B 9.D
10.B 注意:for 循环体执行的次数取决于 SUM。第 1 步用 N+1“机械地”对 SUM 中的
M 实行宏替换,结果成为(N+1+1)∗N+1/2。再用 2“机械地”对结果中的 N 实行宏替换,结果
成为(2+1+1)∗2+1/2。由于 1/2 是整除,结果为 0,故最终结果为 8。

– 298 –
第 10 章 文件、编译预处理

二、填空题

1.stdin 2.# 3.#undef 4.#include


5.#define VOLUME(x) (x)∗(x)∗(x)
6.stdio.h 7.① "r" ② "w" ③ fp2 ④ fp1
8.① "ztest.dat" ② NULL ③ ' # ' ④ fptr

三、是非判断题

1.×
2.× 注意:可以用函数 rewind 把文件内部指针位置重新定位到文件的起始点。
3.√ 4.× 5.√ 6.× 7.√
8.√
9.× 注意:#include 命令只能把源文件包含到现文件中。
10.√

四、程序阅读题

1.答:MIN(m, n)实际上是 MIN(10, 15)。宏替换后,语句:


x = 10∗MIN(m, n);
成为
x = 10∗10<15 ? 10:15;
由于运算符“∗”的优先级高于“? :”的优先级。因此右边的表达式成为
100<15 ? 10 : 15
它的取值为 15。所以,程序运行后的输出结果是 15。
2.答:在编译预处理时,main()通过宏替换过程,成为下面的程序:
main()
{
int j =−2, k = 3;
for (; k; k− −)
switch (k)
{
case 1:
printf ("%d\n", j++ );
case 2:
printf ("%d\n", k);
break;
default :
printf ("%d\n", j);
}
}
– 299 –
C 语言程序设计—示例与习题解析

所以,最终的输出结果是:
−2
2
−2
1
3.答:main()函数在 argc 的控制下,逐一把命令行中给出的 3 个文件 amt1、bmt2、cmt3
打开。打开一个文件,就去调用一次函数 func()。函数 func()的功能是利用函数 fgetc(),
把当前打开文件的一个个字符读出,转换成大写后在屏幕上显示出来。这个工作在遇到“#”
符时停止。因此,整个程序运行后的输出结果是:
AAAAAABBBBBBCCCCCC
4.答:宏替换把 X2 展开成为 a+b∗a+b。因此程序的输出结果是:
a=2 b=3 X2=11
5.答:该程序统计源程序 wps.c 中出现的方括号是否匹配。由函数 fgetc()从打开的文
件里,逐一读出字符。如果是左方括号,变量 m 就加 1;如果是右方括号,变量 n 就加 1。最
终看这两个变量的计数是否相等,由此得出匹配或不匹配的结论。

五、程序设计题

1.答:程序编写如下:
#include<stdio.h>
main()
{
FILE *fp;
char ch, fname[30];
printf ("Enter file name:");
gets (fname);
if ((fp=fopen(fname,"w")) == NULL)
{
printf ("File could not be opened!\n");
exit(0);
}
while ((ch=getchar()) != ' # ' )
fputc (ch, fp);
fclose (fp);
}
2.答:函数 copy()编写如下:
void copy (FILE *fp1, FILE *fp2, int limt)
{
STUDENT x;
rewind (fp1);

– 300 –
第 10 章 文件、编译预处理

fread ((char *) &x, sizeof(STUDENT), 1, fp1);


while (!feof(fp1))
{
if (x.age<limt)
fwrite ((char *) &x, sizeof(STUDENT), 1, fp2);
fread ((char *) &x, sizeof(STUDENT), 1, fp1);
}
}
这里,copy 函数接收两个 FILE 型指针,因此无需调用 fopen()函数来打开文件。由于
是读写结构式数据,因此要使用函数 fread()和 fwrite()。由 fread()读出的数据先放在临
时结构变量 x 里。如果它的成员 age 小于 limt,那么就用 fwrite()把 x 里的数据写入另一个
文件。这一工作一直进行到遇见文件结束符时止。
3.答:程序编写如下:
#include<stdio.h>
#define TRUE 1
#define FALSE 0
int isexist(char *str)
{
FILE *fp;
if ((fp=fopen(str,"r")) != NULL)
return TRUE;
else
return FALSE;
}
main()
{
char fname[30];
printf ("Enter a file name:");
fscanf (stdin,"%s", fname);
if (isexist (fname))
printf ("File %s exist.\n", fname);
else
printf ("File %s do not exist.\n", fname);
}
下图是该程序的两次运行结果。表明文件:
E:/dahua/cprog/wages.dat
是存在的,而文件:
E:/dahua/cprog/zstr.dat
是不存在的。
– 301 –
C 语言程序设计—示例与习题解析

4.答:程序编写如下:
#include<stdio.h>
#define SWAP(x,y) temp=x;x=y;y=temp
main()
{
int a1, a2, temp;
printf ("Enter two integers:");
scanf ("%d%d", &a1, &a2);
SWAP(a1,a2);
printf ("Now, a1=%d a2=%d\n", a1, a2);
}
5.答:程序编写如下:
#include<stdio.h>
#define MAX(x,y) ((x)>(y) ? (x) : (y))
main()
{
int a1, a2, a3;
printf ("Enter three integers:");
scanf ("%d%d%d", &a1, &a2, &a3);
printf ("maximum = %d\n", MAX(MAX(a1,a2),a3));
}

– 302 –

You might also like