Professional Documents
Culture Documents
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 号
网址 http://www. ptpress.com.cn
读者热线:010-67180876
北京汉魂图文设计有限公司制作
印刷厂印刷
新华书店总店北京发行所经销
开本:787×1092 1/16
印张:19.5
字数:471 千字 2003 年 7 月第 1 版
印数:1 – 0 000 册 2003 年 7 月北京第 1 次印刷
定价:25.00 元
本书如有印装质量问题,请与本社联系 电话:(010)67129223
编者的话
编者
2003 年 4 月于北京
目 录
–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 终止
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 在键盘上输入两个整数,输出其中的大者。
[解]
(1)编辑阶段:通过使用编辑器,把 C 语言程序录入计算机,并以文件的形式存放到磁
盘上,这个过程称为编辑。它将产生出以“.c”为扩展名的源程序文件。
(2)编译阶段:源程序不能直接执行,必须通过 C 编译程序将它翻译成计算机能识别的
二进制目标代码,这个过程称为编译。它产生出以“.obj”为扩展名的目标程序文件。
(3)连接阶段:目标程序仍不能立即在机器上执行,因为程序中还会用到系统提供的库
函数(如 printf),需要把它们与产生的目标程序连接在一起,形成一个整体,这个过程称
为连接。它将产生出以“.exe”为扩展名的可执行程序文件。
(4)执行阶段:运行可执行文件,以获取所需要的结果。整个过程如图 1-1 所示。
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 语言程序设计—示例与习题解析
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 语言程序设计—示例与习题解析
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
二、填空题
三、是非判断题(在括号内打“√”或“×”)
–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
二、填空题
三、是非判断题
– 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 语言程序设计—示例与习题解析
表 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 章 基本数据类型
图 2-1 数据类型说明与内存分配示例
– 17 –
C 语言程序设计—示例与习题解析
– 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.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 数值、字符常量、字符串常量在内存中的存放情况
4.注意区分转义字符\ddd、\xhh 与八进制、十六进制整型常数表示法
在转义符“\”后跟 1~3 个数字时,C 语言就认为这些数字是以八进制数表示的某个字
符的 ASCII 码值,它与以前缀数字 0 打头的八进制整型常数的表示法是不同的。同样地,在
转义符“\”后先跟小写字母 x、然后跟 1~2 个数字时,C 语言就认为这些数字是以十六进制
数表示的某个字符的 ASCII 码值, 它与以前缀 0x 打头的十六进制整型常数的表示法是不同的。
即\0x4D 不是正确的转义字符,\x4D 才是正确的转义字符写法。
5.正确使用 printf 和 scanf 函数
– 20 –
第 2 章 基本数据类型
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 章 基本数据类型
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 函数中附加格式字符的含义。
− 输出的数字或字符左对齐,不足时右边补空格
示例 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 语言程序设计—示例与习题解析
二、填空题
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 在一个字节的二进制表示是 。
三、是非判断题(在括号内打“√”或“×”)
四、程序阅读题
{
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.答: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 语言程序设计—示例与习题解析
– 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 章 运算符与表达式
– 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 章 运算符与表达式
例 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位
– 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 章 运算符与表达式
图 3-2 数据类型转换示意
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 的值是多少?
五、程序设计题
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 语言程序设计—示例与习题解析
二、填空题
图 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.答:输出结果为
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 语言程序设计—示例与习题解析
– 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 语言程序设计—示例与习题解析
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 编写一个程序,接收键盘输入的两个整型数。根据两数的关系,打印出相等或
不等的信息。
[解] 由于题目中要求“接收键盘输入的两个整型数”,因此需要说明两个 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 语言程序设计—示例与习题解析
图 4-3 if 语句的嵌套结构
4.多分支选择语句 switch
在 C 语言中,多分支选择语句 switch 的一般形式和执行流程如图 4-4 所示。
关于 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.1.3 本章难点
1.if 语句与 switch 语句的关系
由 if 语句的嵌套形式,构造出了阶梯式的多分支选择结构。但因为是嵌套式的,所以
使用起来缺少灵巧性,在编程时容易出错。当层次太多时,编程者甚至自己也会对选择的关
系混淆不清了。
基于这个原因,C 语言又提供了实现多分支选择的 switch 语句,即通常所说的开关语句。
它通过将变量的当前值逐个与整型常量或字符常量比较,找出匹配者,从而决定执行哪个语
句段。
虽然 if 语句的嵌套形式和 switch 语句都能实现多分支选择,在某种场合也可以互换替
代,但 if 语句适应于各种条件的选择,能够计算关系或逻辑表达式;switch 语句只适用于
检验表达式与哪个值相等的情形。它们之间仍然是有区别的。
– 61 –
C 语言程序设计—示例与习题解析
– 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 语言程序设计—示例与习题解析
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 语言程序设计—示例与习题解析
– 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 语言程序设计—示例与习题解析
一、单选题
– 72 –
第 4 章 顺序结构与选择结构的程序设计
执行后,变量 x 的值是( )。
A.34 B.4 C.35 D.3
– 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.阅读下面的程序,写出其执行结果。
#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 语句打印出什么信息?
五、程序设计题
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
二、填空题
三、是非判断题
四、程序阅读题
# # # # #
$ $ $ $ $
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 语言程序设计—示例与习题解析
– 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 语言程序设计—示例与习题解析
– 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(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 语言程序设计—示例与习题解析
– 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 所示。
#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 循环结构中的作用。注意,图中虚线范围内是循环体语句。
– 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 章 循环结构的程序设计
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 语言程序设计—示例与习题解析
– 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
立即进入下一次循环。程序编写如下:
分,执行条件检验,进入下一次循环。
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 之间所有偶数之和。
[解] 程序编写如下:
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 语言程序设计—示例与习题解析
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 语言程序设计—示例与习题解析
– 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 课外习题
一、单选题
– 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 语言程序设计—示例与习题解析
四、程序阅读题
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 章 循环结构的程序设计
五、程序设计题
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 语言程序设计—示例与习题解析
二、填空题
三、是非判断题
四、程序阅读题
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 章 函数与变量存储类型
– 121 –
C 语言程序设计—示例与习题解析
– 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 章 函数与变量存储类型
– 125 –
C 语言程序设计—示例与习题解析
图 6-2 各种变量作用域的演示结果
– 127 –
C 语言程序设计—示例与习题解析
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 章 函数与变量存储类型
– 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.2 典型示例分析
图 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 章 函数与变量存储类型
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 数
公倍数。
[解] 程序编写如下:
#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 示例 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 ∗ ② );
}
请完成程序填空。
三、是非判断题(在括号内打“√”或“×”)
不起作用。( )
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 章 函数与变量存储类型
– 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,运行的结果各是什么?
五、程序设计题
6.3.2 答案与解析
一、单选题
– 143 –
C 语言程序设计—示例与习题解析
二、填空题
三、是非判断题
四、程序阅读题
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
五、程序设计题
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 章 函数与变量存储类型
– 147 –
C 语言程序设计—示例与习题解析
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 章 函数与变量存储类型
– 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 章 函数与变量存储类型
– 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-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 指针运算与数据类型
– 159 –
C 语言程序设计—示例与习题解析
图 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");
}
可以看出,用指针代替数组名,按照下标来访问数组元素是对的;用数组名代替指针,
借助间接访问运算符“*”来访问数组元素也是对的。不过仍要再次强调,数组名是一个地址
常量,不能认为它与指针完全等同。
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 把数组做为函数的参数
– 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 “值传递”和“地址传递”的比较
(*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 次运行的结果。
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 章 指针与一维数组
– 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.2 典型示例分析
– 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 的两次运行结果
– 171 –
C 语言程序设计—示例与习题解析
图 7-11 示例 2 的两次运行结果
– 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 语言程序设计—示例与习题解析
图 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 章 指针与一维数组
图 7-15 升序冒泡排序的运行结果
– 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 选择排序的运行结果
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 语言程序设计—示例与习题解析
二、填空题
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 章 指针与一维数组
三、是非判断题(在括号内打“√”或“×”)
– 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");
}
五、程序设计题
7.3.2 答案与解析
一、单选题
1.B 注意:运算符“*”不能作用在一般变量上,只能对指针变量起作用。
2.D 注意:++与*的优先级相同,且自右向左结合。因此先做*,得到 25,再做++,得
到 26。故打印结果为 26。
3.C
– 187 –
C 语言程序设计—示例与习题解析
二、填空题
三、是非判断题
四、程序阅读题
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 章 指针与一维数组
五、程序设计题
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 语言程序设计—示例与习题解析
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 语言程序设计—示例与习题解析
(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 章 多维数组与字符串
这里要注意,在给出函数原型时,写法:
– 197 –
C 语言程序设计—示例与习题解析
– 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 语言程序设计—示例与习题解析
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 章 多维数组与字符串
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 章 多维数组与字符串
3 to end。
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.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 章 多维数组与字符串
图 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 的输出
– 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 课外习题
一、单选题
法应该是( )。
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 语言程序设计—示例与习题解析
二、填空题
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 语言程序设计—示例与习题解析
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 语言里提供的求字符串长度的函数。
三、是非判断题(在括号内打“√”或“×”)
– 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 章 多维数组与字符串
五、程序设计题
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
三、是非判断题
– 223 –
C 语言程序设计—示例与习题解析
四、程序阅读题
五、程序设计题
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 章 多维数组与字符串
整个程序编写如下:
#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 语言程序设计—示例与习题解析
– 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 所示的是该程序的一次运行结果。
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 章 结构、共用、枚举
– 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 时的一次运行结果。
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 章 结构、共用、枚举
与结构的用法相同,共用型的定义和变量说明也有 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 章 结构、共用、枚举
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 章 结构、共用、枚举
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 章 结构、共用、枚举
图 9-6 链表程序的运行结果
图 9-7 节点连入链表时的实际情形
图 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 章 结构、共用、枚举
图 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 章 结构、共用、枚举
– 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 = ② ;
– 257 –
C 语言程序设计—示例与习题解析
p−>ptr = ① ; ② = p;
三、是非判断题(在括号内打“√”或“×”)
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 章 结构、共用、枚举
四、程序阅读题
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 语言程序设计—示例与习题解析
– 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 个成员:姓名、基本工资、岗位工资。说明一个该结构的结构
数组,对其元素按下表初始化。然后打印每个人的姓名和工资总额。
姓 名 基 本 工 资 岗 位 工 资
9.3.2 答案与解析
一、单选题
二、填空题
– 262 –
第 9 章 结构、共用、枚举
三、是非判断题
四、程序阅读题
– 263 –
C 语言程序设计—示例与习题解析
五、程序设计题
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 章 结构、共用、枚举
– 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 语言程序设计—示例与习题解析
表 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 从文件尾开始
– 274 –
第 10 章 文件、编译预处理
如果把程序中的语句:
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 的目的。
– 277 –
C 语言程序设计—示例与习题解析
– 278 –
第 10 章 文件、编译预处理
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 个字节来存储它,即
– 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 语言程序设计—示例与习题解析
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 章 文件、编译预处理
10.2 典型示例分析
示例 1 编写一个程序,它逐行读出文本文件”test.txt”的内容,直到遇见 EOF(文
件结束符)时为止。在阅读中随时记录每行的长度和行号,以便在最后能输出最长行的长度
以及行号。如果有相同长度的行,则只取最先碰到的那行。
[解] 程序编写如下:
#include<stdio.h>
main()
{
int ch, len, maxlen;
– 287 –
C 语言程序设计—示例与习题解析
– 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 语言程序设计—示例与习题解析
– 296 –
第 10 章 文件、编译预处理
– 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 答案与解析
一、单选题
– 298 –
第 10 章 文件、编译预处理
二、填空题
三、是非判断题
1.×
2.× 注意:可以用函数 rewind 把文件内部指针位置重新定位到文件的起始点。
3.√ 4.× 5.√ 6.× 7.√
8.√
9.× 注意:#include 命令只能把源文件包含到现文件中。
10.√
四、程序阅读题
所以,最终的输出结果是:
−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 章 文件、编译预处理
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 –