Professional Documents
Culture Documents
第1章 程序设计与算法
1.1 程序设计语言的发展
自1 9 4 6年世界上第一台电子计算机问世以来,计算机科学及其应用的发展十分迅猛,计
算机被广泛地应用于人类生产、生活的各个领域,推动了社会的进步与发展。特别是随着国
际互联网( I n t e r n e t)日益深入千家万户,传统的信息收集、传输及交换方式正被革命性地改
变,我们已经难以摆脱对计算机的依赖,计算机已将人类带入了一个新的时代 — 信息时代。
新的时代对于我们的基本要求之一是:自觉地、主动地学习和掌握计算机的基本知识和
基本技能,并把它作为自己应该具备的基本素质。要充分认识到,缺乏计算机知识,就是信
息时代的“文盲”。
对于理工科的大学生而言,掌握一门高级语言及其基本的编程技能是必需的。大学学习,
除了掌握本专业系统的基础知识外,科学精神的培养、思维方法的锻炼、严谨踏实的科研作
风养成,以及分析问题、解决问题的能力的训练,都是日后工作的基础。学习计算机语言,
正是一种十分有益的训练方式,而语言本身又是与计算机进行交互的有力的工具。
一台计算机是由硬件系统和软件系统两大部分构成的,硬件是物质基础,而软件可以说
是计算机的灵魂,没有软件,计算机是一台“裸机”,是什么也不能干的,有了软件,才能灵
动起来,成为一台真正的“电脑”。所有的软件,都是用计算机语言编写的。
计算机程序设计语言的发展,经历了从机器语言、汇编语言到高级语言的历程。
1. 机器语言
电子计算机所使用的是由“ 0”和“1”组成的二进制数,二进制是计算机的语言的基础。
计算机发明之初,人们只能降贵纡尊,用计算机的语言去命令计算机干这干那,一句话,就
是写出一串串由“ 0”和“ 1”组成的指令序列交由计算机执行,这种语言,就是机器语言。
使用机器语言是十分痛苦的,特别是在程序有错需要修改时,更是如此。而且,由于每台计
算机的指令系统往往各不相同,所以,在一台计算机上执行的程序,要想在另一台计算机上
执行,必须另编程序,造成了重复工作。但由于使用的是针对特定型号计算机的语言,故而
运算效率是所有语言中最高的。机器语言,是第一代计算机语言。
2. 汇编语言
为了减轻使用机器语言编程的痛苦,人们进行了一种有益的改进:用一些简洁的英文字
母、符号串来替代一个特定的指令的二进制串,比如,用“ A D D”代表加法,“M O V”代表
数据传递等等,这样一来,人们很容易读懂并理解程序在干什么,纠错及维护都变得方便了,
这种程序设计语言就称为汇编语言,即第二代计算机语言。然而计算机是不认识这些符号的,
这就需要一个专门的程序,专门负责将这些符号翻译成二进制数的机器语言,这种翻译程序
被称为汇编程序。
汇编语言同样十分依赖于机器硬件,移植性不好,但效率仍十分高,针对计算机特定硬
件而编制的汇编语言程序,能准确发挥计算机硬件的功能和特长,程序精炼而质量高,所以
2 C语言程序设计
下载
至今仍是一种常用而强有力的软件开发工具。
3. 高级语言
从最初与计算机交流的痛苦经历中,人们意识到,应该设计一种这样的语言,这种语言
接近于数学语言或人的自然语言,同时又不依赖于计算机硬件,编出的程序能在所有机器上
通用。经过努力, 1 9 5 4年,第一个完全脱离机器硬件的高级语言 — FORTRAN问世了, 4 0多
年来,共有几百种高级语言出现,有重要意义的有几十种,影响较大、使用较普遍的有
F O RT R A N、A L G O L、C O B O L、B A S I C、L I S P、S N O B O L、P L / 1、P a s c a l、C、P R O L O G、
Ada、C++、VC、VB、Delphi、JAVA等。
高级语言的发展也经历了从早期语言到结构化程序设计语言,从面向过程到非过程化程
序语言的过程。相应地,软件的开发也由最初的个体手工作坊式的封闭式生产,发展为产业
化、流水线式的工业化生产。
6 0年代中后期,软件越来越多,规模越来越大,而软件的生产基本上是人自为战,缺乏
科学规范的系统规划与测试、评估标准,其恶果是大批耗费巨资建立起来的软件系统,由于
含有错误而无法使用,甚至带来巨大损失,软件给人的感觉是越来越不可靠,以致几乎没有
不出错的软件。这一切,极大地震动了计算机界,史称“软件危机”。人们认识到:大型程序
的编制不同于写小程序,它应该是一项新的技术,应该像处理工程一样处理软件研制的全过
程。程序的设计应易于保证正确性,也便于验证正确性。 1 9 6 9年,提出了结构化程序设计方
法,1 9 7 0年,第一个结构化程序设计语言 — P a s c a l语言出现,标志着结构化程序设计时期的
开始。
8 0年代初开始,在软件设计思想上,又产生了一次革命,其成果就是面向对象的程序设
计。在此之前的高级语言,几乎都是面向过程的,程序的执行是流水线似的,在一个模块被
执行完成前,人们不能干别的事,也无法动态地改变程序的执行方向。这和人们日常处理事
物的方式是不一致的,对人而言是希望发生一件事就处理一件事,也就是说,不能面向过程,
而应是面向具体的应用功能,也就是对象( o b j e c t)。其方法就是软件的集成化,如同硬件的
集成电路一样,生产一些通用的、封装紧密的功能模块,称之为软件集成块,它与具体应用
无关,但能相互组合,完成具体的应用功能,同时又能重复使用。对使用者来说,只关心它
的接口(输入量、输出量)及能实现的功能,至于如何实现的,那是它内部的事,使用者完
全不用关心,C++、VB、Delphi就是典型代表。
高级语言的下一个发展目标是面向应用,也就是说:只需要告诉程序你要干什么,程序
就能自动生成算法,自动进行处理,这就是非过程化的程序语言。
1.2 C语言的特点
1.2.1 C语言是中级语言
C语言通常称为中级计算机语言。中级语言并没有贬义,不意味着它功能差、难以使用、
或者比BASIC、Pascal那样的高级语言原始,也不意味着它与汇编语言相似,会给使用者带来
类似的麻烦。 C语言之所以被称为中级语言,是因为它把高级语言的成分同汇编语言的功能结
合起来了。表1-1表明了C语言在计算机语言中所处的地位。
下载
第1章 程序设计与算法 3
表1-1 C语言在计算机语言中的地位
高级 Ada、Modula-2、Pascal、COBOL、FORTRAN、BASIC
中级 C、FORTH、Macro-assembler
低级 Assembler
1.2.2 C语言是结构化语言
1.2.3 C语言是程序员的语言
也许你会问“所有的计算机语言不都是程序员使用的吗?”,回答是断然的“否”。我们
考虑典型的非程序员的语言 C O B O L和B A S I C。C O B O L的设计使程序员难以改变所编写代码
的可靠性,甚至不能提高代码的编写速度。
然而 C O B O L设计者的本意却是打算使非程序员能读程序(这是不大可能的事)。注意,
4 C语言程序设计
下载
这并不是攻击COBOL的优点,而是想指出,它没有被设计成为程序员的理想语言。 BASIC 的
主要目的是允许非专业程序员在计算机上编程解决比较简单的问题。与其形成鲜明对照的是 C
语言,由于程序生成、修改和现场测试自始至终均由真正的程序员进行,因而它实现了程序
员的期望:很少限制、很少强求、块结构、独立的函数以及紧凑的关键字集合。用 C语言编程,
程序员可以获得高效机器代码,其效率几乎接近汇编语言代码。
C语言被程序员广泛使用的另一个原因是可以用它代替汇编语言。汇编语言使用的汇编指
令,是能够在计算机上直接执行的二进制机器码的符号表示。汇编语言的每个操作都对应为
计算机执行的单一指令。虽然汇编语言给予程序员达到最大灵活性和最高效率的潜力,但开
发和调试汇编语言程序的困难是难以忍受的。非结构性使得汇编语言程序难于阅读、改进和
维护。也许更重要的是,汇编语言程序不能在使用不同 CPU的机器间移植。
最初,C语言被用于系统程序设计。一个“系统程序”是一大类程序的一部分,这一大类
构成了计算机操作系统及实用程序。通常被称为系统程序的有:
• 操作系统。
• 翻译程序。
• 编辑程序。
• 汇编程序。
• 编译程序。
• 数据库管理程序。
随着C语言的普及,加之其可移植性和高效率,许多程序员用它设计各类程序。几乎所有
的计算机上都有 C语言编译程序,这使我们可以很少改动甚至不加改动地将为一种机器写的 C
语言源程序在另一种机器上编译执行。可移植性节省了时间和财力。
C语言不仅在速度和结构上有它的优势,而且每个 C语言系统都提供了专门的函数库,程
序员可以根据不同需要对其进行剪裁,以适应各种程序的设计。由于它允许(更准确地说是
鼓励)分别编译,所以 C语言可使程序员方便地管理大型项目,最大限度地减少重复劳动。
1.3 C语言的程序结构
1.3.1 基本程序结构
任何一种程序设计语言都具有特定的语法规则和规定的表达方法。一个程序只有严格按
照语言规定的语法和表达方式编写,才能保证编写的程序在计算机中能正确地执行,同时也
便于阅读和理解。
为了了解C语言的基本程序结构,我们先介绍几个简单的 C程序。
[例1-1]
# include <stdio.h>
main() /* 主函数*/
{
printf("This is a sample of c program. \n"); 调用标准函数,
/*
显示引号中的内容 */
}
下载
第1章 程序设计与算法 5
这是一个最简单的 C程序,其执行结果是在屏幕上显示一行信息:
RUN ↵
This is a sample of c program.
[例1-2]
main() /*主函数*/
{
void proc(); /* 函数声明 */
int a=3; /*指定a为整数,初始值为 3*/
proc(); /* 调用函数proc, 无返回*/
a=func(); /*调用函数func, 结果返回给a*/
printf("This is a sample of c program. \n");
}
本程序的执行过程是:
• 程序从main()处开始。
• 变量a代表一个整数,并且初始值为 3。
• 执行程序(函数) proc();屏幕上显示Hello,\n为转义字符,代表换行的意思。
• 执行程序(函数) func();并将结果赋予 a,此时,a的值为2。
• 屏幕上显示“This is a sample of c program.”。
程序执行的结果是在屏幕显示两行信息:
RUN ↵
Hello.
This is a sample of c program.
程序中/ * . . . . . * /表示对程序的说明(称为注释),不参与程序的运行。注释文字可以是任意
字符,如汉字、拼音、英文等。
[例1-3]
/*输入长方体的长、宽、高,计算长方体体积 */
main()
{
int x,y,z,v; /* 定义整型变量 */
scanf("%d,%d,%d",&x,&y,&z); /*调用标准函数,从键盘输入x,y,z 的值*/
v = volume(x,y,z); /* 调用volume 函数,计算体积 */
prinf("v = %d\n",v);
}
6 C语言程序设计
下载
int volume(a,b,c) /* 定义volume 函数*/
int a,b,c; /* 对形参a,b,c 作类型定义*/
{
int p; /* 定义函数内部使用的变量p*/
p = a*b*c; /* 计算体积p的值*/
return(p); /* 将p值返回调用处*/
}
本程序的功能是对从键盘输入的长方体的长、宽、高三个整型量求其体积的值。程序运
行的情况如下:
RUN ↵
5,8,6 ↵
v = 240
其中:
函数头 包括函数说明、函数名和圆括号中的形式参数(如 int volume(a,b,c)),如果函数
调用无参数传递,圆括号中形式参数为空(如 void proc()函数)。
形式参数说明 指定函数调用传递参数的数据类型(如例 1.3中语句int a,b,c;)。
函数体 包括函数体内使用的数据说明和执行函数功能的语句,花括号 {和}表示函数体的
开始和结束。
1.3.2 函数库和链接
从技术上讲,纯粹由程序员自己编写的语句构成 C语言程序是可能的,但这却是罕见的。
因为所有的 C编译程序都提供能完成各种常用任务的函数 — 函数库(如 printf、scanf等)。
C编译程序的实现者已经编写了大部分常见的通用函数。当我们调用一个别人编写的函数
下载
第1章 程序设计与算法 7
时编译程序“记忆”它的名字。随后,“链接程序”把我们编写的程序同标准函数库中找到的
目标码结合起来,这个过程称为“链接”。
保存在函数库中的函数是可重定位的。这意味着其中机器码指令的内存地址并未绝对地
确定, 只有偏移量是确定的。当把程序与标准函数库中的函数相链接时,内存偏移量被用来产
生实际地址。有关重定位的详细内容,请查阅其他技术书籍。
编写程序时用到的函数,许多都可以在标准函数库中找到。它们是可以简单地组合起来
的程序构件。编写了一个经常要用的函数之后,可将其放入库中备用。
1.3.3 开发一个C程序
开发一个C程序,包括以下四步:
1) 程序设计 程序设计亦称程序编辑。程序员用任一编辑软件(编辑器)将编写好的 C程
序输入计算机,并以文本文件的形式保存在计算机的磁盘上。编辑的结果是建立 C源程序文件。
C程序习惯上使用小写英文字母,常量和其他用途的符号可用大写字母。 C语言对大、小写字
母是有区别的。关键字必须小写。
2) 程序编译 编译是指将编辑好的源文件翻译成二进制目标代码的过程。编译过程是使用
C语言提供的编译程序(编译器)完成的。不同操作系统下的各种编译器的使用命令不完全相
同,使用时应注意计算机环境。编译时,编译器首先要对源程序中的每一个语句检查语法错
误,当发现错误时,就在屏幕上显示错误的位置和错误类型的信息。此时,要再次调用编辑
器进行查错修改。然后,再进行编译,直至排除所有语法和语义错误。正确的源程序文件经
过编译后在磁盘上生成目标文件。
3) 链接程序 编译后产生的目标文件是可重定位的程序模块,不能直接运行。链接就是把
目标文件和其他分别进行编译生成的目标程序模块(如果有的话)及系统提供的标准库函数
链接在一起,生成可以运行的可执行文件的过程。链接过程使用 C语言提供的链接程序(链接
器)完成,生成的可执行文件存在磁盘中。
4) 程序运行 生成可执行文件后,就可以在操作系统控制下运行。若执行程序后达到预期
目的,则 C程序的开发工作到此完成。否则,要进一步检查修改源程序,重复编辑 — 编译
— 链接 — 运行的过程,直到取得预期结果为止。
大部分C语言都提供一个独立的开发集成环境,它可将上述四步连贯在一个程序之中。本
书所涉及的程序全部在 Turbo C环境中进行。
1.3.4 C语言的关键字
表1-2列举了32个关键字,它们与标准 C句法结合,形成了程序设计语言 C。
表1-2 关 键 字
1.4 算法
1.4.1 流程图与算法的结构化描述
1. 流程图
流程图是一种传统的算法表示法,它利用几何图形的框来代表各种不同性质的操作,用
流程线来指示算法的执行方向。由于它简单直观,所以应用广泛,特别是在早期语言阶段,
只有通过流程图才能简明地表述算法,流程图成为程序员们交流的重要手段,直到结构化的
程序设计语言出现,对流程图的依赖才有所降低。
下面介绍常见的流程图符号及流程图的例子。
10 C语言程序设计
下载
本章例1-1的算法的流程图如图 1-2所示。本章例1-2的算法的流程图如图 1-3所示。
在流程图中,判断框左边的流程线表示判断条件为真时的流程,右边的流程线表示条件为假
时的流程,有时就在其左、右流程线的上方分别标注“真”
、“假”或“T”
、“F”或“Y”
、“N”。
图1-1 常见的流程图符号
开始
输入A,B,C
开始
T F
A>B i=9
a1=1
MAX<=A MAX<=B
i=i-1
a0=2*(a1+1)
T F i>1
C>MAX a1=a0
MAX<=C
输出a0
输出MAX
结束 结束
另外还规定,流程线是从下往上或从右向左时,必须带箭头,除此以外,都不画箭头,
流程线的走向总是从上向下或从左向右。
2. 算法的结构化描述
早期的非结构化语言中都有 g o t o语句,它允许程序从一个地方直接跳转到另一个地方去。
执行这样做的好处是程序设计十分方便灵活,减少了人工复杂度,但其缺点也是十分突出的,
下载
第1章 程序设计与算法 11
一大堆跳转语句使得程序的流程十分复杂紊乱,难以看懂也难以验证程序的正确性,如果有
错,排起错来更是十分困难。这种转来转去的流程图所表达的混乱与复杂,正是软件危机中
程序人员处境的一个生动写照。而结构化程序设计,就是要把这团乱麻理清。
经过研究,人们发现,任何复杂的算法,都可以由顺序结构、选择(分支)结构和循环
结构这三种基本结构组成,因此,我们构造一个算法的时候,也仅以这三种基本结构作为
“建筑单元”,遵守三种基本结构的规范,基本结构之间可以并列、可以相互包含,但不允许
交叉,不允许从一个结构直接转到另一个结构的内部去。正因为整个算法都是由三种基本结
构组成的,就像用模块构建的一样,所以结构清晰,易于正确性验证,易于纠错,这种方法,
就是结构化方法。遵循这种方法的程序设计,就是结构化程序设计。
相应地,只要规定好三种基本结构的流程图的画法,就可以画出任何算法的流程图。
(1) 顺序结构
顺序结构是简单的线性结构,各框按顺序执行。其流程图的基本形态如图 1 - 4所示,语句
的执行顺序为: A→B→C。
(2) 选择(分支)结构
这种结构是对某个给定条件进行判断,条件为真或假时分别执行不同的框的内容。其基
本形状有两种,如图 1-5 a)、b)所示。图 1-5 a )的执行序列为:当条件为真时执行 A,否则
执行B;图1-5b)的执行序列为:当条件为真时执行 A,否则什么也不做。
T F T F
条件 条件
A
A B A
B
C
a) b)
(3) 循环结构
循环结构有两种基本形态: while型循环和do-while型循环。
a. while 型循环
如图1-6所示。
其执行序列为:当条件为真时,反复执行 A,一旦条件为假,跳出循环,执行循环紧后的
语句。
b. do-while型循环
如图1-7所示。
A
A
A
A
T T
条件 条件 条件
T
F F
1.4.2 用N-S图描述算法
a1=1
输入A,B,C
T A>B i=9
F
max<=A max<=B 当i>=1时,循环
C>max a0=2*(a1+1)
T F
a1=a0
max<=C i=i-1
输出 MAX 输出a0
A A A i1 A
条件 条件
I= i2 B
B
B
a) b) i3 C
C i4 D
3. 循环结构
如图1-17所示。图1-17 a)为while型循环,图 1-17 b)为do-while型循环。
WHILE<条件> A UNTIL<条件> A
a) b)
图1-17 循环结构的PAD
本章例1.1的PAD图如图1-18,例1-2的PAD图如图1-19。
输入A,B,C a1=1
MAX<=A
A>B i=9
MAX<=B a0=2*a1+1
MAX<=C
while i>=1 a1=a0
C>MAX
i=i-1
输出MAX
输出 a0
第2章 数据类型、运算符和表达式
2.1 C语言的数据类型
C语言有五种基本数据类型:字符、整型、单精度实型、双精度实型和空类型。尽管这几
种类型数据的长度和范围随处理器的类型和 C语言编译程序的实现而异,但以 b i t为例,整数
与C P U字长相等,一个字符通常为一个字节,浮点值的确切格式则根据实现而定。对于多数
微机,表2-1给出了五种数据的长度和范围。
表2-1 基本类型的字长和范围
类 型 长 度(bit) 范 围
char(字符型) 8 0~255
int(整型) 16 -32768~32767
float(单精度型) 32 约精确到 6位数
double(双精度型) 64 约精确到 12位数
void(空值型) 0 无值
表中的长度和范围的取值是假定 CPU的字长为16bit。
C语言还提供了几种聚合类型( aggregate types),包括数组、指针、结构、共用体(联合)
、
位域和枚举。这些复杂类型在以后的章节中讨论。
除v o i d类型外,基本类型的前面可以有各种修饰符。修饰符用来改变基本类型的意义,
以便更准确地适应各种情况的需求。修饰符如下:
• signed(有符号)。
• unsigned(无符号)。
• long(长型符)。
• short(短型符)。
修饰符s i g n e d、s h o r t、l o n g和u n s i g n e d适用于字符和整数两种基本类型,而 l o n g还可用于
double(注意,由于long float与double意思相同,所以 ANSI标准删除了多余的 long float)。
表2-2给出所有根据 ANSI标准而组合的类型、字宽和范围。切记,在计算机字长大于 16位
的系统中, short int与signed char可能不等。
表2-2 ANSI标准中的数据类型
类 型 长 度(bit) 范 围
char(字符型) 8 ASCII字符
unsigned char(无符号字符型) 8 0~255
signed char(有符号字符型 ) 8 -128~127
int(整型) 16 32768~32767
unsigned int(无符号整型 ) 16 0~65535
signed int(有符号整型 ) 16 同int
下载
第2章 数据类型、运算符和表达式 15
(续)
类 型 长 度(bit) 范 围
*表中的长度和范围的取值是假定 CPU的字长为16bit。
因为整数的缺省定义是有符号数,所以 singed这一用法是多余的,但仍允许使用。
某些实现允许将 unsigned用于浮点型,如 unsigned double。但这一用法降低了程序的可移
植性,故建议一般不要采用。
为了使用方便, C编译程序允许使用整型的简写形式:
• short int 简写为short。
• long int 简写为long。
• unsigned short int 简写为unsigned short。
• unsigned int 简写为unsigned。
• unsigned long int 简写为unsigned long。
即,int可缺省。
2.2 常量与变量
2.2.1 标识符命名
在C语言中,标识符是对变量、函数标号和其它各种用户定义对象的命名。标识符的长度
可以是一个或多个字符。绝大多数情况下,标识符的第一个字符必须是字母或下划线,随后
的字符必须是字母、数字或下划线(某些 C语言编译器可能不允许下划线作为标识符的起始字
符)。下面是一些正确或错误标识符命名的实例。
正确形式 错误形式
count 2count
test23 hi! there
high_balance high..balance
A N S I标准规定,标识符可以为任意长度,但外部名必须至少能由前 8个字符唯一地区分。
这里外部名指的是在链接过程中所涉及的标识符,其中包括文件间共享的函数名和全局变量
名。这是因为对某些仅能识别前 8个字符的编译程序而言,下面的外部名将被当作同一个标识
符处理。
counters counters1 counters2
A N S I标准还规定内部名必须至少能由前 3 1个字符唯一地区分。内部名指的是仅出现于定
16 C语言程序设计
下载
义该标识符的文件中的那些标识符。
C语言中的字母是有大小写区别的,因此 count Count COUNT是三个不同的标识符。
标识符不能和C语言的关键字相同,也不能和用户已编制的函数或 C语言库函数同名。
2.2.2 常量
C语言中的常量是不接受程序修改的固定值,常量可为任意数据类型,如下例所示 :
数据类型 常量举例
char 'a'、'\n'、'9'
int 21、 123 、2100 、-234
long int 35000、 -34
short int 10、-12、90
unsigned int 10000、 987、 40000
float 123.23、 4.34e-3
double 123.23、 12312333、 -0.9876234
C语言还支持另一种预定义数据类型的常量,这就是串。所有串常量括在双撇号之间,例
如"This is a test"。切记,不要把字符和串相混淆,单个字符常量是由单撇号括起来的,如 'a'。
2.2.3 变量
其值可以改变的量称为变量。一个变量应该有一个名字 (标识符),在内存中占据一定的存
储单元,在该存储单元中存放变量的值。请注意区分变量名和变量值这两个不同的概念。
所有的C变量必须在使用之前定义。定义变量的一般形式是:
type variable_list;
注意 C语言中变量名与其类型无关。
2.3 整型数据
2.3.1 整型常量
整型常量及整常数。它可以是十进制、八进制、十六进制数字表示的整数值。
十进制常数的形式是:
digits
这里digits可以是从0到9的一个或多个十进制数位,第一位不能是 0。
八进制常数的形式是:
下载
第2章 数据类型、运算符和表达式 17
0digits
在此,digits可以是一个或多个八进制数( 0~7之间),起始0是必须的引导符。
十六进制常数是下述形式:
0xhdigits
0Xhdigits
十 进 制 八 进 制 十六进制
10 012 0Xa或0XA
132 0204 0X84
32179 076663 0X7db3或0X7DB3
整常数在不加特别说明时总是正值。如果需要的是负值,则负号“ -”必须放置于常数表
达式的前面。
每个常数依其值要给出一种类型。当整常数应用于一表达式时,或出现有负号时,常数
类型自动执行相应的转换,十进制常数可等价于带符号的整型或长整型,这取决于所需的常
数的尺寸。
八进制和十六进制常数可对应整型、无符号整型、长整型或无符号长整型,具体类型也
取决于常数的大小。如果常数可用整型表示,则使用整型。如果常数值大于一个整型所能表
示的最大值,但又小于整型位数所能表示的最大数,则使用无符号整型。同理,如果一个常
数比无符号整型所表示的值还大,则它为长整型。如果需要,当然也可用无符号长整型。
在一个常数后面加一个字母 l或L,则认为是长整型。如10L、79L、012L、0115L、0XAL、
0x4fL等。
2.3.2 整型变量
前面已提到, C规定在程序中所有用到的变量都必须在程序中指定其类型,即“定义”。
这是和BASIC、FORTRAN不同的,而与Pascal相似。
[例2-1]
main()
{
int a,b,c,d; /* 指定a,b,c,d 为整型变量*/
unsigned u; /*指定u为无符号整型变量 */
a=12; b=-24; u=10;
c=a+u; d=b+u;
printf("a+u=%d, b+u=%d\n",c,d);
}
运行结果为 :
RUN ↵
a+u=22, b+u=-14
18 C语言程序设计
下载
可以看到不同类型的整型数据可以进行算术运算。在本例中是 int型数据与unsingned int型
数据进行相加减运算。
2.4 实型数据
2.4.1 实型常量
实型常量又称浮点常量,是一个十进制表示的符号实数。符号实数的值包括整数部分、
尾数部分和指数部分。实型常量的形式如下:
[digits][.digits][E|e[+|-]digits]
所有的实型常量均视为双精度类型。
实型常量的整数部分为 0时可以省略,如下形式是允许的:
。
.57, .0075e2, -.125, -.175E-2
注意 字母E或e之前必须有数字,且E或e后面指数必须为整数,如e3、2.1e3.5、.e3、e
等都是不合法的指数形式。
2.4.2 实型变量
2.5.1 字符常量
字符常量是指用一对单引号括起来的一个字符。如‘ a’,‘9’,‘!’。字符常量中的单引
号只起定界作用并不表示字符本身。单引号中的字符不能是单引号(’)和反斜杠( \),它们
特有的表示法在转义字符中介绍。
在C语言中,字符是按其所对应的 ASCII码值来存储的,一个字符占一个字节。例如:
字符 ASCII码值(十进制)
! 33
0 48
1 49
9 57
A 65
B 66
a 97
b 98
注意 字符'9'和数字9的区别,前者是字符常量,后者是整型常量,它们的含义和在计
算机中的存储方式都截然不同。
由于C语言中字符常量是按整数( short型)存储的,所以字符常量可以像整数一样在程序
中参与相关的运算。例如:
'a'-32; /* 执行结果97-32 = 65 */
'A' + 32; /* 执行结果65+32 = 97 */
'9'-9; /* 执行结果57-9 = 48 */
2.5.2 字符串常量
字符串常量是指用一对双引号括起来的一串字符。双引号只起定界作用,双引号括起的
字符串中不能是双引号( ")和反斜杠(\),它们特有的表示法在转义字符中介绍。例如:
"China" ,"C program", "YES&NO", "33312-2341", 等。
"A"
C语言中,字符串常量在内存中存储时,系统自动在字符串的末尾加一个“串结束标志”,
即A S C I I码值为 0的字符 N U L L,常用 \ 0表示。因此在程序中,长度为 n个字符的字符串常量,
在内存中占有n+1个字节的存储空间。
例如,字符串 C h i n a有5个字符,作为字符串常量 " C h i n a "存储于内存中时,共占 6个字节,
系统自动在后面加上 NULL字符,其存储形式为:
C h i n a NULL
要特别注意字符串与字符串常量的区别,除了表示形式不同外,其存储性质也不相同,
字符'A'只占1个字节,而字符串常量 "A"占2个字节。
20 C语言程序设计
下载
2.5.3 转义字符
转义字符 意 义 ASCII码值(十进制)
\a 响铃(BEL) 007
\b 退格(BS) 008
\f 换页(FF) 012
\n 换行(LF) 010
\r 回车(CR) 013
\t 水平制表(HT) 009
\v 垂直制表(VT) 011
\\ 反斜杠 092
\? 问号字符 063
\' 单引号字符 039
\" 双引号字符 034
\0 空字符(NULL) 000
\ddd 任意字符 三位八进制
\xhh 任意字符 二位十六进制
字符常量中使用单引号和反斜杠以及字符常量中使用双引号和反斜杠时,都必须使用转
义字符表示,即在这些字符前加上反斜杠。
在C程序中使用转义字符 \ d d d或者\ x h h可以方便灵活地表示任意字符。 \ d d d为斜杠后面跟
三位八进制数,该三位八进制数的值即为对应的八进制 ASCII码值。\x后面跟两位十六进制数,
该两位十六进制数为对应字符的十六进制 ASCII码值。
使用转义字符时需要注意以下问题:
1) 转义字符中只能使用小写字母,每个转义字符只能看作一个字符。
2) \v 垂直制表和 \f 换页符对屏幕没有任何影响,但会影响打印机执行响应操作。
3) 在C程序中,使用不可打印字符时,通常用转义字符表示。
2.5.4 符号常量
C语言允许将程序中的常量定义为一个标识符,称为符号常量。符号常量一般使用大写英
文字母表示,以区别于一般用小写字母表示的变量。符号常量在使用前必须先定义,定义的
形式是:
#define <符号常量名> <常量>
例如:
#define PI 3.1415926
#define TRUE 1
#definr FALSE 0
#define STAR '*'
这里定义 P I、T R U E、F L A S E、S TA R为符号常量,其值分别为 3 . 1 4 1 5 9 2 6,1,0,' * '。
下载
第2章 数据类型、运算符和表达式 21
#define是C语言的预处理命令,它表示经定义的符号常量在程序运行前将由其对应的常量替换。
定义符号常量的目的是为了提高程序的可读性,便于程序的调试和修改。因此在定义符
号常量名时,应使其尽可能地表达它所代表的常量的含义,例如前面所定义的符号常量名
P I (π),表示圆周率 3 . 1 4 1 5 9 2 6。此外,若要对一个程序中多次使用的符号常量的值进行修改,
只须对预处理命令中定义的常量值进行修改即可。
2.5.5 字符变量
字符变量用来存放字符常量,注意只能存放一个字符,不要以为在一个字符变量中可以
放字符串。
字符变量的定义形式如下:
char c1, c2;
它表示c1和c2为字符变量,各放一个字符。因此可以用下面语句对 c1、c2赋值:
c1 = 'a'; c2 = 'b';
[例2-2]
main()
{
char c1,c2;
c1=97; c2=98;
printf("%c %c",c1,c2);
}
[例2-3]
main()
{ char c1,c2;
c1='a' ;c2='b';
c1 = c1 - 32; c2 =c2 - 32;
printf("%c %c",c1,c2);
}
运行结果为:
RUN ↵
A B
它的作用是将两个小写字母转换为大写字母。因为 'a'的ASCII码为97,而'A'为65,'b'为98,
' B '为6 6。从A S C I I代码表中可以看到每一个小写字母比大写字母的 A S C I I码大3 2。即'a'='A' +
32。
22 C语言程序设计
下载
2.6 运算符
C语言的内部运算符很丰富,运算符是告诉编译程序执行特定算术或逻辑操作的符号。 C
语言有三大运算符:算术、关系与逻辑、位操作。另外, C还有一些特殊的运算符,用于完成
一些特殊的任务。
2.6.1 算术运算符
运 算 符 作 用 运 算 符 作 用
- 减法,也是一元减法 % 模运算
+ 加法 -- 自减(减 1)
* 乘法 ++ 自增(增 1)
/ 除法
下面是说明 %用法的程序段。
int x,y;
x=10;
y=3;
printf("%d",x/y); /* 显示 3 */
printf("%d",x%y); /* 显示 1,整数除法的余数 */
x=1;
y=2;
printf("%d,%d",x/y,x%y); /* 显示 0,1 */
最后一行打印一个 0和一个1,因为1/2整除时为0,余数为1,故1%2取余数1。
2.6.2 自增和自减
C语言中有两个很有用的运算符,通常在其它计算机语言中是找不到它们的 — 自增和自
减运算符, ++和--。运算符“ ++”是操作数加1,而“--”是操作数减1,换句话说:
x=x+1; 同++x;
x=x-1; 同--x;
此时,y=11。如果程序改为:
x=10;
y=x++;
2.6.3 关系和逻辑运算符
关系运算符中的“关系”二字指的是一个值与另一个值之间的关系,逻辑运算符中的
“逻辑”二字指的是连接关系的方式。因为关系和逻辑运算符常在一起使用,所以将它们放在
一起讨论。
关系和逻辑运算符概念中的关键是 Tr u e(真)和 F l a s e(假)。C语言中,非 0为Tr u e,0为
Flase。使用关系或逻辑运算符的表达式对 Flase和Ture分别返回值0或1(见表2-6)。
表2-6 关系和逻辑运算符
关系运算符 含 义 关系运算符 含 义
逻辑运算符 含 义
&& 与
|| 或
! 非
表2-6给出于关系和逻辑运算符,下面用 1和0给出逻辑真值表。
关系和逻辑运算符的优先级比算术运算符低,即像表达式 10>1+12的计算可以假定是对表
达式10>(1+12)的计算,当然,该表达式的结果为 Flase。
在一个表达式中允许运算的组合。例如:
10>5&&!(10<9)||3<=4
24 C语言程序设计
下载
p q p&&q p||q !p
0 0 0 0 1
0 1 0 1 1
1 1 1 1 0
1 0 0 1 0
这一表达式的结果为 True。
下表给出了关系和逻辑运算符的相对优先级:
最高 !
>= <=
== !=
&&
最低 ||
同算术表达式一样,在关系或逻辑表达式中也使用括号来修改原计算顺序。
切记,所有关系和逻辑表达式产生的结果不是 0就是1,所以下面的程序段不仅正确而且
将在屏幕上打印数值 1。
int x;
x=100;
printf("%d",x>10);
2.6.4 位操作符
操 作 符 含 义 操 作 符 含 义
下面是异或的真值表。
表2-8 异或的真值表
P q p^q
0 0 0
1 0 1
1 1 0
0 1 1
下载
第2章 数据类型、运算符和表达式 25
如表2-8所示,当且仅当一个操作数为 True时,异或的输出为 True,否则为Flase。
位操作通常用于设备驱动程序,例如调制解调器程序、磁盘文件管理程序和打印机驱动
程序。这是因为位操作可屏蔽掉某些位,如奇偶校验位(奇偶校验位用于确保字节中的其它
位不会发生错误通常奇偶校验位是字节的最高位)。
通常我们可把位操作 AND作为关闭位的手段,这就是说两个操作数中任一为 0的位,其结
果中对应位置为 0。例如,下面的函数通过调用函数 read_modem(),从调制解调器端口读入一
个字符,并将奇偶校验位置成 0。
[例2-4]
Char get_char_from_modem()
{
char ch;
从调制解调器端口中得到一个字符 */
ch=read_modem(); /*
return(ch&127);
}
字 符 x 每个语句执行后的 x x 的 值
x=7 00000111 7
x<<1 00001110 14
x<<3 01110000 112
x<<2 11000000 192
x>>1 01100000 96
x>>2 00011000 24
2.6.5 ?操作符
C语言提供了一个可以代替某些 i f - t h e n - e l s e语句的简便易用的操作符?。该操作符是三元
下载
第2章 数据类型、运算符和表达式 27
的,其一般形式为:
EXP1?EXE2:EXP3
EXP1,EXP2和EXP3是表达式,注意冒号的用法和位置。
操作符“ ? ”作用是这样的,在计算 EXP1之后,如果数值为 True,则计算EXP2,并将结
果作为整个表达式的数值;如果 E X P 1的值为 Fl a s e,则计算 E X P 3,并以它的结果作为整个表
达式的值,请看下例:
x=10;
y=x>9?100:200;
有关C语言中的其它条件语句将在第 3章进行讨论。
2.6.6 逗号操作符
作为一个操作符,逗号把几个表达式串在一起。逗号操作符的左侧总是作为 v o i d (无值),
这意味着其右边表达式的值变为以逗号分开的整个表达式的值。例如:
x=(y=3,y+1);
2.6.7 关于优先级的小结
表2 - 1 0列出了C语言所有操作符的优先级,其中包括将在本书后面讨论的某些操作符。注
意,所有操作符(除一元操作符和?之外)都是左结合的。一元操作符( *,&和-)及操作符
“?”则为右结合。
表2-10 C语言操作符的优先级
最 高 级 ()[] →
!~ ++ -- -(type) * & sizeof
*/%
+-
<< >>
<= >=
== !=
28 C语言程序设计
下载
(续)
&
^
|
&&
||
?
= += -= *= /=
最低级 ,
2.7 表达式
表达式由运算符、常量及变量构成。 C语言的表达式基本遵循一般代数规则,有几点却是
与C语言紧密相关的,以下将分别加以讨论。
2.7.1 表达式中的类型转换
混合于同一表达式中的不同类型常量及变量,应均变换为同一类型的量。 C语言的编译程
序将所有操作数变换为与最大类型操作数同类型。变换以一次一操作的方式进行。具体规则
如下:
char ch;
int i;
float f;
double d;
result=(ch / i) + ( f * d ) - ( f + i );
int double double
double
double
图2-1 类型转换实例
2.7.2 构成符cast
可以通过称为cast的构成符强迫一表达式变为特定类型。其一般形式为:
(type )expression
通常认为cast是操作符。作为操作符, cast是一元的,并且同其它一元操作符优先级相同。
虽然c a s t在程序中用得不多,但有时它的使用的确很有价值。例如,假设希望用一整数控
制循环,但在执行计算时又要有小数部分。
[例2-6]
main()
{
int i ;
for (i+1;i<=100;++i)
printf("%d/2 is :%f",i,(float)i/2);
}
2.7.3 空格与括号
为了增加可读性,可以随意在表达式中插入tab和空格符。例如,下面两个表达式是相同的。
x=10/y*(127/x);
x=10/y*(127/x);
冗余的括号并不导致错误或减慢表达式的执行速度。我们鼓励使用括号,它可使执行顺
序更清楚一些。例如,下面两个表达式中哪个更易读一些呢?
x=y/2-34*temp&127;
x=(y/2)-((34*temp)&127);
2.7.4 C语言中的简写形式
C语言提供了某些赋值语句的简写形式。例如语句:
x=x+10;
在C语言中简写形式是:
x+=10 ;
其等价语句是
x-=100;
简写形式广泛应用于专业 C语言程序中,希望读者能熟悉它。
下载
第3章 程序控制语句
3.1 程序的三种基本结构
通常的计算机程序总是由若干条语句组成,从执行方式上看,从第一条语句到最后一条
语句完全按顺序执行,是简单的顺序结构;若在程序执行过程当中,根据用户的输入或中间
结果去执行若干不同的任务则为选择结构;如果在程序的某处,需要根据某项条件重复地执
行某项任务若干次或直到满足或不满足某条件为止,这就构成循环结构。大多数情况下,程
序都不会是简单的顺序结构,而是顺序、选择、循环三种结构的复杂组合。
三种基本结构的流程图、 N-S图以及PAD图可以参看本书第 1章1.4节“算法”相关内容。
C语言中,有一组相关的控制语句,用以实现选择结构与循环结构:
选择控制语句: if;
switch、case
循环控制语句: for、while、do…while
转移控制语句: break、continue、goto
我们将在后面几节中详细介绍。
3.2 数据的输入与输出
在程序的运行过程中,往往需要由用户输入一些数据,而程序运算所得到的计算结果等
又需要输出给用户,由此实现人与计算机之间的交互,所以在程序设计中,输入输出语句是
一类必不可少的重要语句,在 C语言中,没有专门的输入输出语句,所有的输入输出操作都是
通过对标准I / O库函数的调用实现。最常用的输入输出函数有 s c a n f ( )、p r i n t f ( )、g e t c h a r ( )和
putchar(),以下分别介绍。
3.2.1 scanf()函数
格式化输入函数 s c a n f ( )的功能是从键盘上输入数据,该输入数据按指定的输入格式被赋
给相应的输入项。函数一般格式为:
scanf("控制字符串 ",输入项列表);
其中控制字符串规定数据的输入格式,必须用双引号括起,其内容是由格式说明和普通
字符两部分组成。输入项列表则由一个或多个变量地址组成,当变量地址有多个时,各变量
地址之间用逗号“,”分隔。
s c a n f ( )中各变量要加地址操作符,就是变量名前加“ &”,这是初学者容易忽略的一个问
题。应注意输入类型与变量类型一致。
下面探讨控制字符串的两个组成部分:格式说明和普通字符。
1. 格式说明
格式说明规定了输入项中的变量以何种类型的数据格式被输入,形式是:
32 C语言程序设计
下载
%[<修饰符>]<格式字>
各个格式字符及其意义见表 3-1。
表3-1 输入格式字符
格式字符 意 义
d 输入一个十进制整数
o 输入一个八进制整数
x 输入一个十六进制整数
f 输入一个小数形式的浮点数
e 输入一个指数形式的浮点数
c 输入一个字符
s 输入一个字符串
各修饰符是可选的,可以没有,这些修饰符是:
⑴ 字段宽度
例如:scanf("%3d",&a)
按宽度3输入一个整数赋给变量 a。
⑵ l和h
可以和 d、o、x一起使用,加 l表示输入数据为长整数,加 h表示输入数据为短整数,例
如:
scanf("%10ld%hd",&x,&i)
则x按宽度为10的长整型读入,而 i按短整数读入。
⑶ 字符*
*表示按规定格式输入但不赋予相应变量,作用是跳过相应的数据。
例如:
scanf("%4d%*d%4d",&x,&y,&z)
执行该语句,若输入为“1 2 3↵”
结果为x=1,z=3,y未赋值,2被跳过。
2. 普通字符
普通字符包括空格、转义字符和可打印字符。
(1) 空格
在有多个输入项时,一般用空格或回车作为分隔符,若以空格作分隔符,则当输入项中
包含字符类型时,可能产生非预期的结果,例如:
scanf("%d%c",&a,&ch)
输入32 q
期望a=32,ch=q,但实际上,分隔符空格被读入并赋给 ch。
为避免这种情况,可使用如下语句:
scanf("%d %c",&a,&ch)
此处%d后的空格,就可跳过字符“ q”前的所有空格,保证非空格数据的正确录入。
(2) 转义字符: \n、\t
先看下面的例子:
下载
第3章 程序控制语句 33
scanf("%d%d",&a,&b);
scanf("%d%d%d",&x,&y,&z);
输入为 1 2 3↵
4 5 6↵
结果为:a=1, b=2, x=3, y=4, z=5
若将上述语句改为:
scanf("%d%d\n",&a,&b);
scanf("%d%d%d",&x,&y,&z);
程序如下:
main()
{
float a,b,h,s;
printf("please input a,b,h:");
scanf("%f%f%f",&a,&b,&h);
s=0.5*(a+b)*h;
printf("a=%5.2f b=%5.2f h=%5.2f",a,b,h);
printf("s=%7.4f",s);
}
运行结果如下:
RUN ↵
3.5 4.2 2.8↵
please input a,b,h:
a=3.50 b=4.20 h=2.80
s=10.7800
格式字符 意 义 格式字符 意 义
c 按字符型输出 o 按八进制整数输出
d 按十进制整数输出 x 按十六进制整数输出
u 按无符号整数输出 s 按字符串输出
f 按浮点型小数输出 g 按e和f格式中较短的一种输出
e 按科学计数法输出
修饰符是可选的,用于确定数据输出的宽度、精度、小数位数、对齐方式等,用于产生
更规范整齐的输出,当没有修饰符时,以上各项按系统缺省设定显示。
(1) 字段宽度修饰符
表3-3列出了字段宽度修饰符。
表3-3 字段宽度修饰符
修 饰 符 格式说明 意 义
例如:设i=123,a=12.34567,
则:
printf("%4d+++%5.2f",i,a);
输出: 123+++12.35
printf("%2d+++%2.1f",i,a);
输出:
123+++12.3
可以看出,当指定场宽小于数据的实际宽度时,对整数,按该数的实际场宽输出,对浮
点数,相应小数位的数四舍五入。例如: 1 2 . 3 4 5 6 7按%5.2f 输出,输出 1 2 . 3 5。若场宽小于等
于浮点数整数部分的宽度,则该浮点数按实际位数输出,但小数位数仍遵守宽度修饰符给出
的值。如上面的 12.34567按%2.1f 输出,结果为: 12.3。
在实际应用中,还有一种更灵活的场宽控制方法,用常量或变量的值作为输出场宽,方
法是以一个 "*"作为修饰符,插入到 %之后。
例如:i=123;
printf("%*d",5,i);
下载
第3章 程序控制语句 35
此处,5为场宽,输出为
123
在程序中,可以用一个整形变量K来指示场宽:
printf("%*d",k,i);
可以根据k的值动态地决定 i的显示场宽,这在解某些问题时是相当有用的。
(2) 对齐方式修饰符
负号“-”为“左对齐”控制符,一般所有输出数据为右对齐格式,加一个“ -”号,则
变为“左对齐”方式。
例如: i=123,a=12.34567
printf(“%4d%10.4f”,i,a);
输出为: 123 12.3457
printf(“%-4d%10.4f”,i,a);
输出为: 123 12.3457
printf(“%4d%-10.4f”,i,a);
输出为: 12312.3457
(3) l和h
可以与输出格式字符 d、f、u等连用,以说明是用 long型或short型格式输出数据,如:
%hd 短整型
%lf 精度型
%ld 长整型
%hu 无符号短整型
2. 普通字符
普通字符包括可打印字符和转义字符,可打印字符主要是一些说明字符,这些字符按原
样显示在屏幕上,如果有汉字系统支持,也可以输出汉字。
转义字符是不可打印的字符,它们其实是一些控制字符,控制产生特殊的输出效果。
例如:i=123,n=456,a=12.34567,且i为整型,n为长整型。
printf("%4d\t%7.4f\n\t%lu\n",i,a,n);
输出为:
123 12.3457
456
其中\ t为水平制表符,作用是跳到下一个水平制表位,在各个机器中,水平制表位的宽度
是不一样的,这里设为8个字符宽度,那么“ \t”跳到下一个8的倍数的列上。
“\n”为回车换行符,遇到“ \n”,显示自动换到新的一行。
在c语言中,如果要输出%,则在控制字符中用两个%表示,即%%。
[例3-2] 输出格式控制符的使用。
# include<stdio.h>
main()
{
int a;
36 C语言程序设计
下载
long int b;
short int c;
unsigned int d;
char e;
float f;
double g;
a=1023;
b=2222;
c=123;
d=1234;
e='x';
f=3.1415926535898 ;
g=3.1415926535898;
printf("a=%d\n",a);
printf("a=%0\n",a);
printf("a=%x\n",a);
printf("b=%ld\n",b);
printf("c=%d\n",c);
printf("d=%u\n",d);
printf("e=%c\n",e);
printf("f=%f\n",f);
printf("g=%f\n",g);
printf("\n");
}
执行程序,输出为:
RUN ↵
a=1023
a=1777
a=3ff
b=2222
c=123
d=1234
e=x
f=3.141593
g=3.141593
3.2.3 getchar()函数与putchar()函数
putchar() 与getchar()是对单个字符进行输入输出的函数。
getchar()的功能是返回键盘输入的一个字符,它不带任何参数,其通常格式如下:
ch=getchar()
ch为字符型变量,上述语句接收从键盘输入的一个字符并将它赋给 ch。
putchar()的作用是向屏幕上输出一个字符,它的功能与 printf函数中的%c相当。putchar()必
须带输出项,输出项可以是字符型常量、变量、表达式,但只能是单个字符而不能是字符串。
[例3-3] 输入一个字符,回显该字符并输出其 ASCII码值。
#include<stdio.h>
main()
下载
第3章 程序控制语句 37
{
char ch;
ch=getchar();
putchar(ch);
printf("%d\n",ch);
}
运行程序:
RUN ↵
g↵
g 103
需要注意的是,字符输入输出函数定义在头文件 s t d i o . h中,故当程序中使用 p u t c h a r ( )或
getchar()函数时,必须在 main()之前用语句:
#include"stdio.h"
将stdio.h包含进来。
3.2.4 程序应用举例
[例3-4] 下面的程序是一个复数加法的例子。
#include <stdio.h>
main()
{
float a1,b1,a2,b2;
char ch;
printf("\t\t\tcomplexs Addition\n");
printf("please input the first complex:\n");
printf("\t realpart:");
scanf("%f",&a1);
printf("\t virtualpart:");
scanf("%f",&b1);
printf("%5.2f +i %5.2f\n",a1,b1);
printf("\n please input the second complex:\n");
printf("\t realpart:");
scanf("%f",&a2);
printf("\t virtualpart :");
scanf("%f",&b2);
printf("%5.2f +i %5.2f\n",a2,b2);
printf("\n The addition is :");
printf("%6.3f +i %6.3f\n",a1+a2,b1+b2);
printf(" program normal terminated,press enter...");
ch=getchar();
ch=getchar();
}
运行结果如下:
RUN ↵
complexs addition
please input the first complex :
realpart :1.2 ↵
virtualpart :3.4 ↵
38 C语言程序设计
下载
1.20 +i 3.40
please input the second complex :
realpart :5.6 ↵
virtualpart :7.8 ↵
5.60 +i 7.80
The addition is:6.800 +i 11.200
program normal terminated, press enter....
3.3 条件控制语句
在程序的三种基本结构中,第二种即为选择结构,其基本特点是 :程序的流程由多路分支
组成,在程序的一次执行过程中,根据不同的情况,只有一条支路被选中执行,而其他分支
上的语句被直接跳过。
C语言中,提供 i f语句和s w i t c h语句选择结构, i f语句用于两者选一的情况,而 s w i t c h用于
多分支选一的情形。
3.3.1 if语句
1. if语句的两种基本形式
首先,我们看一个例子,由此了解选择结构的意义及设计方法。
[例3-5] 输入三个数,找出并打印其最小数。
分析:设三个数为 A、B、C,由键盘读入,我们用一个变量 M I N来标识最小数, A、B、
C与MIN皆定义为int型变量。
每次比较两个数,首先比较 A和B,将小的一个赋给 M I N,再把第三个数 C与M I N比较,
再将小的一个赋给 MIN,则最后MIN即为A、B、C中最小数。
算法如下:
1) 输入A、B、C。
2) 将A与B中小的一个赋给 MIN。
3) 将MIN与C中小的一个赋给 MIN。
4) 输出MIN。
将第2)步细化为:若 A<B,则MIN <==A,否则:MIN <==B;其流程图见图3 -1。
第3)步细化为:若 C<MIN,则MIN <==C;其流程图见图 3-2。
真 假 真
A<B C<MIN
对应图3-1和图3-2,正是if语句的两种基本形式,与图 3-2对应的if语句的格式为:
if <表达式> 语句
当表达式为真时,执行语句,表达式为假时跳过语句。
下载
第3章 程序控制语句 39
与图3-1对应的if语句的格式为:
if 〈表达式〉
语句1
else
语句2
当表达式为真时,执行语句1,表达式为假时执行语句 2。无论如何,语句1与语句2每
次只能有一个被执行。
要注意的是: i f或i f . . . e l s e,包括后面要讲到的嵌套 i f,即if...else if...被看成是一条语句,
即使其中的语句是包含多条语句的复合语句,仍然如此。
下面是例3-5的源程序:
main()
{
int a,b,c,min;
printf(" input a,b,c :");
scanf("%d%d%d",&a,&b,&c);
if (a<b)
min = a;
else
min = b;
if (c<min)
min = c;
printf("The result is %d\n",min);
}
执行情况如下:
RUN ↵
input a,b,c: 3 5 2↵
The result is : 2
这里顺便提一下程序书写的缩排问题,所谓缩排,就是下一行与上一行相比,行首向右
缩进若字符,如上例的 min = a 、min = b等。适当的缩排能使程序的结构、层次清晰、一目了
然,增加程序的易读性。应该从一开始就养成一个比较好的书写习惯,包括必要的注释、适
当的空行以及缩排。
2. 复合语句
if语句中,有时需要执行的语句不止一条,这就要用到复合语句。
复合语句,就是用一对花括号括起来的一条或多条语句,形式如下:
{
语句1;
语句2;
……
语句n;
}
无论包括多少条语句,复合语句从逻辑上讲,被看成是一条语句。
复合语句在分支结构、循环结构中,使用十分广泛。
[例3-6] 读入两个数 x、y,将大数存入x,小数存入 y。
40 C语言程序设计
下载
分析: x、y从键盘读入,若 x > = y,只需顺序打出,否则,应将 x,y中的数进行交换,然
后输出。两数交换必须使用一个中间变量 t, 开始
定义三个浮点数 x、y、t。
输入x,y
算法:
1) 读入x、y; 真
x<y
2) 大数存入x,小数存入 y;
t<==x
3) 输出x、y。 x<==y
第2)步求精: y<==t
若x<y,则交换x与y;
再求精,x与y交换; 输入x,y
① t <== x
② x <== y 结束
执行结果:
input x,y :43.2 56.7↵
result : 56.700 43.200
3. if...else if 语句
实际应用中常常面对更多的选择,这时,将 i f . . . e l s e扩展一下,就得到 if...else if结构,其
一般形式为:
if <表达式1>
语句1
else if<表达式2>
语句2
else if <表达式3>
语句3
else 语句4
对应的流程图见图 3-4。
下载
第3章 程序控制语句 41
假
表达式1
假
表达式2
真 假
表达式3
4. if 语句嵌套
在一个if 语句中可以又出现另一个 if语句,这称为if语句的嵌套或多重 if语句:
if <表达式1>
if< 表达式11>
……
else
语句2;
[例3-8] 计算函数
1 x>0
y= 0 x=0
-1 x<0
流程图见图 3-5。
BEGIN
输入x
假
x>=0
真
真 x>0 假
y=1 y=0
y=-1
输出 y
END
图3-5 例3-8的流程图
源程序如下:
main()
{
float x,y;
printf("input x,y:");
scanf("%f",&x);
if (x>=0)
if (x>0)
y=1;
else
下载
第3章 程序控制语句 43
y=0;
else
y=-1; y=0
printf("y=%4.0f\n",y);
假
} x>=0
3.3.2 switch 语句
其中常量表达式的值必须是整型,字符型或者枚举类型,各语句序列允许有多条语句,
不需要按复合语句处理,若语句序列 i为空,则对应的 break语句可去掉。图 3-7是switch语句的
流程图。
特殊情况下,如果switch表达式的多个值都需要执行相同的语句,可以采用下面的格式:
44 C语言程序设计
下载
switch (i)
{
case 1:
case 2:
case 3: 语句1;
break;
case 4:
case 5: 语句2;
break;
default: 语句3;
}
表达式=
语句序列1 BREAK
常量表达式1
表达式=
语句序列2 BREAK
常量表达式2
表达式=
常量表达式i
表达式=常
语句序列i+1 BREAK
量表达式i+1
表达式=
语句序列n BREAK
常量表达式n
语句序列n+1
3.3.3 程序应用举例
运行结果如下:
RUN ↵
please input a,b,c :1 2 3↵
There are two virtual roots:
-1.000000 + i 1.000000 -1.000000 - i 1.000000
RNU ↵
please input a,b,c :2 5 3↵
There are two different roots : -1.500000 and -1.000000
RNU ↵
please input a,b,c :0 0 3↵
No root!
3.4 循环控制语句
循环控制结构(又称重复结构)是程序中的另一个基本结构。在实际问题中,常常需要
进行大量的重复处理,循环结构可以使我们只写很少的语句,而让计算机反复执行,从而完
下载
第3章 程序控制语句 47
成大量类同的计算。
C语言提供了 while语句、do...while语句和for语句实现循环结构。
3.4.1 while语句
while语句是当型循环控制语句,一般形式为 :
while <表达式> 语句;
语句部分称为循环体,当需要执行多条语句时,应使用复合语 语句
句。 真 表达式
w h i l e语句的流程图见图 3 - 8,其特点是先判断,后执行,若条 假
件不成立,有可能一次也不执行。
图3-8 while语句的流程图
[例3-11] 求n!
分析: n!= n* (n-1)*(n-2)* .. 2*1, 0!=1。即S0=1,Sn=Sn-1*n。可以从S0开始,依次
求出S1、S2、...Sn。
统一令 S等于阶乘值, S的初值为 0!= 1;变量 i为计数器, i从1变到n,每一步令 S = S * i,
则最终S中的值就是 n!。
流程图见图 3-9,程序如下:
main()
{
int n,i;
long int s;
printf(" please input n (n>=0) :");
scanf("%d",&n);
if (n>=0)
{
s=1;
if (n>0)
{
i=1;
while (i<=n)
{
s*=i;
i=i+1;
}
}
printf("%d! = %ld \n",n,s);
}
else
printf("Invalid input! \n");
}
运行结果如下:
RUN ↵
please input n(n>=0):0↵
0!= 1
48 C语言程序设计
下载
RUN ↵
please input n(n>=0):6↵
6!= 720
RUN ↵
please input n(n>=0):-2 ↵
Invalid input!
BEGIN
读入 n
假
真
N>=0
S=1
真 假
N>=0
i=1
i=i+1 报错
s=s*i
真 i<=n
输出s
END
图3-9 例3-11的算法流程图
[例3-12] 利用格里高利公式求 π:
π/4 = 1 - 1/3 + 1/5 - 1/7 + ...
直到最后一项的绝对值小于等于 10-6为止。
程序如下:
# include <stdio.h>
# include <math.h>
{
main()
{
double e,pi;
long int n,s;
t=1.0;
n=1;
s=1;
pi=0.0;
下载
第3章 程序控制语句 49
while (fabs(t)>=1e-6)
{
pi=pi+t;
n=n+2;
s=-s;
t=(float)(s)/(float)(n);
}
pi=pi*4;
printf(" pi = %lf\n" ,pi);
}
运行结果为:
RUN ↵
pi = 3.141591
其中语句通常为复合语句,称为循环体。 表达式
d o. . .while 语句的流程图见图 3 - 1 0,其基本特点是:先执行后判断, 真
因此,循环体至少被执行一次。
但需要注意的是, do...while与标准的直到型循环有一个极为重要的区 图3-10 do...while语
别,直到型循环是当条件为真时结束循环,而 d o. . .w h i l e语句恰恰相反, 句的流程图
当条件为真时循环,一旦条件为假,立即结束循环,请注意 do...while语句的这一特点。
例[3-13] 计算sin(x) = x- x 3/3! + x 5/5! - x 7/7! + ...
直到最后一项的绝对值小于 1e-7时为止。
分析:这道题使用递推方法来做。
让多项式的每一项与一个变量 n对应, n的值依次为 1,3,5,7,. . .,从多项式的前一项
算后一项,只需将前一项乘一个因子:
(-x 2)/((n-1)*n)
用s表示多项式的值,用t表示每一项的值,程序如下 :
#include <math.h>
# include <stdio.h>
main()
{
double s,t,x;
int n ;
printf("please input x :");
50 C语言程序设计
下载
scanf("%lf" ,&x);
t=x;
n=1;
s=x;
do
{
n=n+2;
t=t*(-x*x)/((float)(n)-1)/(float)(n);
s=s+t;
} while (fabs(t)>=1e-7);
printf("sin(%f )=%lf" ,x,s);
}
运行结果如下:
RUN ↵
please input x:1.5753 ↵
sin(1.575300)=0.999990
RUN ↵
please input x:-0.65 ↵
sin(-0.650000)=-0.605186
3.4.3 for 语句
f o r语句是循环控制结构中使用最广泛的一种循环控制语句,特别适合已知循环次数的情
况。它的一般形式为:
for (<表达式1> ;<表达式2> ;<表达式3>) 语句
for语句很好地体现了正确表达循环结构应注意的三个问题:
1) 控制变量的初始化。 表达式1
2) 循环的条件。
3) 循环控制变量的更新。 表达式 3
表达式1:一般为赋值表达式,给控制变量赋初值; 循环体
表达式2:关系表达式或逻辑表达式,循环控制条件;
真 表达式 2
表达式3:一般为赋值表达式,给控制变量增量或减量。
假
语句:循环体,当有多条语句时,必须使用复合语句。
图3-11 for 循环的流程图
for循环的流程图如图 3-11,其执行过程如下:
首先计算表达式 1,然后计算表达式 2,若表达式 2为真,则执行循环体;否则,退出 for循
环,执行 for循环后的语句。如果执行了循环体,则循环体每执行一次,都计算表达式 3,然后
重新计算表达式 2,依此循环,直至表达式 2的值为假,退出循环。
[例3-14] 计算自然数 1到n的平方和。
# include <stdio.h>
# include <math.h>
main()
{
int i;
float s;
下载
第3章 程序控制语句 51
printf("please input n :");
scanf("%d" ,&n);
s=0.0;
for(i=1;i<=n;i++)
s=s+(float)(i)*(float)(i);
printf("1*1 + 2*2 +...+%d*%d = %f\n" ,n,n,s);
}
运行结果如下:
RUN ↵
please input n : 5↵
1*1 + 2*2 + ... + 5* 5 = 55.000000
for语句的几种格式
for语句的三个表达式都是可以省略的,但分号“;”绝对不能省略。
a. for(; ;)语句;
这是一个死循环,一般用条件表达式加 break语句在循环体内适当位置,一旦条件满足时,
用break语句跳出for循环。
例如,在编制菜单控制程序时,可以如下:
for(; ;)
{
printf("please input choice( Q=Exit):"); 显示菜单语句块:
/* */
scanf("%c" ,&ch);
if (ch=='Q') or (ch=='q') break; 语句段
/* */
}
b. for(;表达式2;表达式3)
使用条件是:循环控制变量的初值不是已知常量,而是在前面通过计算得到,例如:
i=m-n;
……
for( ;i<k ;i++) 语句;
c. for(表达式1;表达式2;)语句
一般当循环控制变量非规则变化,而且循环体中有更新控制变量的语句时使用。
例如:
for(i=1 ;i<=100 ;)
{
……
i=i*2+1;
……
}
d. for(i=1,j=n;i<j;i++,j--)语句;
在f o r语句中,表达式 1、表达式 3都可以有一项或多项,如本例中,表达式 1同时为 i和j赋
初值,表达式3同时改变i和j的值。当有不止一项时,各项之间用逗号“,”分隔。
另外,C语言还允许在循环体内改变循环变量的值,这在某些程序的设计中是很有用的。
到此,我们已经学习了 C语言中三种循环控制语句 w h i l e、d o. . .w h i l e和f o r语句,下面再讨
52 C语言程序设计
下载
论两个问题:
三种语句的选用
同一个问题,往往既可以用 while语句解决,也可以用 do...while或者for语句来解决,但在
实际应用中,应根据具体情况来选用不同的循环语句,选用的一般原则是:
1) 如果循环次数在执行循环体之前就已确定,一般用 f o r语句;如果循环次数是由循环体
的执行情况确定的,一般用 while语句或者do... while语句。
2) 当循环体至少执行一次时,用 d o. . .w h i l e语句,反之,如果循环体可能一次也不执行,
选用while语句。
循环的嵌套
一个循环的循环体中有另一个循环叫循环嵌套。这种嵌套过程可以有很多重。一个循环
外面仅包围一层循环叫二重循环;一个循环外面包围两层循环叫三重循环;一个循环外面包
围多层循环叫多重循环。
三种循环语句 for、while、do...while可以互相嵌套自由组合。但要注意的是,各循环必须
完整,相互之间绝不允许交叉。如下面这种形式是不允许的:
do
{ ……
for (;;)
{
……
}while( );
}
[例3-15] 打印8行7列的星形矩阵。
流程图见图 3-12,程序如下:
BEGIN
i=0 外循环
i<8
k=0 内循环
k<7
'*'
k=k+1
换行
i=i+1
END
图3-12 例3-15的算法流程图
# include<stdio.h>
下载
第3章 程序控制语句 53
main( )
{
int i,j;
for(i=0;i<8 ,i++) /*控制行*/
{
for(j=0;j<7>;j++) /* 控制列*/
printf("*") ;
printf("\n") ; /*换行*/
}
}
打印结果如下:
RUN ↵
*******
*******
*******
*******
*******
*******
*******
*******
3.4.4 break与continue语句
有时,我们需要在循环体中提前跳出循环,或者在满足某种条件下,不执行循环中剩下
的语句而立即从头开始新的一轮循环,这时就要用到 break和continue语句。
1. break语句
在前面学习 s w i t c h语句时,我们已经接触到 b r e a k语句,在 c a s e子句执行完后,通过 b r e a k
语句使控制立即跳出 s w i t c h结构。在循环语句中, b r e a k语句的作用是在循环体中测试到应立
即结束循环时,使控制立即跳出循环结构,转而执行循环语句后的语句。
[例3-16] 打印半径为 1到10的圆的面积,若面积超过 100,则不予打印。
# include <stdio.h>
main( )
{
int r;
float area;
for(r=1;r<=10;r++)
{
area=3.141593*r*r;
if(area>100.0)
break;
printf("square=%f\n" ,area);
}
printf("now r=%d\n" ,r);
}
54 C语言程序设计
下载
运行程序:
RUN↵
square=3.141593
square=12.566373
square=28.274338
square=50.265488
square=78.539825
now r=6
当break处于嵌套结构中时,它将只跳出最内层结构,而对外层结构无影响。
2. continue语句
c o n t i n u e语句只能用于循环结构中,一旦执行了 c o n t i n u e语句,程序就跳过循环体中位于
该语句后的所有语句,提前结束本次循环周期并开始新一轮循环。
[例3-17] 计算半径为 1到15的圆的面积,仅打印出超过 50的圆面积。
# include <stdio.h>
main()
{
int r;
float area;
for (r=1;r<=5;r++)
{
area=3.141593*r*r;
if(area<50.0)
continue;
printf("square =%f",area);
}
}
结果为:
RUN ↵
square=50.265488
square=78.539825
同b r e a k一样, c o n t i n u e语句也仅仅影响该语句本身所处的循环层,而对外层循环没有影
响。
3.4.5 程序应用举例
[例3-18] 验证哥德巴赫猜想:任一充分大的偶数,可以用两个素数之和表示,例如:
4=2+2
6=3+3
……
98=19+79
哥德巴赫猜想是世界著名的数学难题,至今未能在理论上得到证明,自从计算机出现后,
人们就开始用计算机去尝试解各种各样的数学难题,包括费马大定理、四色问题、哥德巴赫
猜想等,虽然计算机无法从理论上严密地证明它们,而只能在很有限的范围内对其进行检验,
但也不失其意义。费马大定理已于 1 9 9 4年得到证明,而哥德巴赫猜想这枚数学王冠上的宝石,
下载
第3章 程序控制语句 55
至今无人能及。
分析:我们先不考虑怎样判断一个数是否为素数,而从整体上对这个问题进行考虑,可
以这样做:读入一个偶数 n,将它分成p和q,使n=p+q。怎样分呢?可以令 p从2开始,每次加1,
而令q=n-p,如果p、q均为素数,则正为所求,否则令 p=p+q再试。
其基本算法如下:
1) 读入大于3的偶数n。
2) P=1
3) do {
4) p=p+1;q=n-p;
5) p是素数吗?
6) q是素数吗?
7) } while p、q有一个不是素数。
8) 输出n=p+q。
为了判明 p、q是否是素数,我们设置两个标志量 f l a g p和f l a g q,初始值为 0,若p是素数,
令flagp=1,若q是素数,令 flagq=1,于是第7步变成:
7) } while (flagp*flagq==0);
再来分析第 5、第6步,怎样判断一个数是不是素数呢?
素数就是除了1和它自身外,不能被任何数整除的整数,由定义可知:
2、3、5、7、11、13、17、19等是素数;
1、4、6、8、9、10、12、14等不是素数;
要判断i是否是素数,最简单的办法是用 2、3、4、……i-1这些数依次去除 i,看能否除尽,
若被其中之一除尽,则 i不是素数,反之, i是素数。
但其实,没必要用那么多的数去除,实际上,用反证法很容易证明,如果小于等于 i的平
方根的数都除不尽,则 i必是素数。于是,上述算法中的第 5步、第6步可以细化为:
第5)步 p是素数吗?
flagp=1;
for (j=2;j<=[sqrt(p)];j++)
if p除以j的余数 = 0
{ flagp=0;
break; }
第6)步 q是素数吗?
flagq=1;
for (j=2;j<=[sqrt(q)];j++)
if q除以j的余数 = 0
{ flagq=0;
break; }
程序如下:
#include <math.h>
# include <stdio.h>
main()
{
56 C语言程序设计
下载
int j,n,p,q,flagp ,flagq;
printf("please input n :");
scanf(" %d",&n);
if (((n%2)!=0)||(n<=4))
printf("input data error!\n");
else
{
p=1;
do {
p=p+1;
q=n-p;
flagp=1;
for(j=2;j<=(int)(floor(sqrt((double)(p))));j++)
{
if ((p%j)==0)
{
flagp=0;
break;
}
}
flagq=1;
for (j=2;j<=(int)(floor(sqrt((double)(q))));j++)
{
if ((q%j)==0)
{
flagq=0;
break;
}
}
} while (flagp*flagq==0);
printf("%d = %d + %d \n" ,n,p,q);
}
}
程序运行结果如下:
RUN ↵
please input n : 8↵
8 =3+5
RUN ↵
please input n : 98↵
98 =19+79
RUN ↵
please input n : 9↵
input data error!
下载
第4章 函 数
在学习C语言函数以前,我们需要了解什么是模块化程序设计方法。
人们在求解一个复杂问题时,通常采用的是逐步分解、分而治之的方法,也就是把一个
大问题分解成若干个比较容易求解的小问题,然后分别求解。程序员在设计一个复杂的应用
程序时,往往也是把整个程序划分为若干功能较为单一的程序模块,然后分别予以实现,最
后再把所有的程序模块像搭积木一样装配起来,这种在程序设计中分而治之的策略,被称为
模块化程序设计方法。
在C语言中,函数是程序的基本组成单位,因此可以很方便地用函数作为程序模块来实现
C语言程序。
利用函数,不仅可以实现程序的模块化,程序设计得简单和直观,提高了程序的易读性
和可维护性,而且还可以把程序中普通用到的一些计算或操作编成通用的函数,以供随时调
用,这样可以大大地减轻程序员的代码工作量。
函数是C语言的基本构件,是所有程序活动的舞台。函数的一般形式是 :
type-specifier function_name(parameter list)
parameter declarations
{
body of the function
}
类型说明符定义了函数中 r e t u r n语句返回值的类型,该返回值可以是任何有效类型。如果
没有类型说明符出现,函数返回一个整型值。参数表是一个用逗号分隔的变量表,当函数被
调用时这些变量接收调用参数的值。一个函数可以没有参数,这时函数表是空的。但即使没
有参数,括号仍然是必须要有的。参数说明段定义了其中参数的类型。
4.1 函数说明与返回值
4.1.1 函数的类型说明
可将函数说明为返回任何一种合法的 C语言数据类型。
类型说明符告诉编译程序它返回什么类型的数据。这个信息对于程序能否正确运行关系
极大,因为不同的数据有不同的长度和内部表示。
返回非整型数据的函数被使用之前,必须把它的类型向程序的其余部分说明。若不这样
做,C语言的编译程序就认为函数是返回整型数据的函数,调用点又在函数类型说明之前,编
译程序就会对调用生成错误代码。为了防止上述问题的出现,必须使用一个特别的说明语句,
58 C语言程序设计
下载
通知编译程序这个函数返回什么值。下例示出了这种方法。
[例4-1]
float sum( ); /* 函数说明 */
main ( )
{
float first,second;
first =123.23;
second=99.09 ;
printf ("%f",sum (first,second)) ;
}
float sum (a,b) /* 函数定义*/
float a,b;
{
return a+b;
}
即使函数使用形参,也不要将其写入说明句。若未使用类型说明语句,函数返回的数据类
型可能与调用者所要求的不一致,其结果是难以预料的。如果两者同处于一个文件中,编译程
序可以发现该错误并停止编译。如果不在同一个文件中,编译程序无法发现这种错误。类型检
查仅在编译中进行,链接和运行时均不检查。因此,必须十分细心以确保绝不发生上述错误。
当被说明为整型的函数返回字符时,这个字符值被转换为整数。因为 C语言以不加说明的
方式进行字符型与整型之间的数据转换,因而多数情况下,返回字符值的函数并不是说明为
返回字符值,而是由函数的这种字符型向整型的缺省类型转换隐含实现的。
4.1.2 返回语句
返回语句 r e t u r n有两个重要用途。第一,它使得内含它的那个函数立即退出,也就是使程
序返回到调用语句处继续进行。第二,它可以用来回送一个数值。本章将说明这两个用途。
1. 从函数返回
函数可以用两种方法停止运行并返回到调用程序。第一种是在执行完函数的最后一个语
句之后,从概念上讲,是遇到了函数的结束符“ }”(当然这个花括号实际上并不会出现在目
标码中,但我们可以这样理解)。例如,下面的函数在屏幕上显示一个字符串。
[例4-2]
pr_reverse ()
{
char s[80]; /*定义一个字符数组 */
scanf("%s" ,s); /* 输入一个字符串,其长度不超过 79 个字符*/
printf("%s\n" ,s) ;
}
下载
第4章 函 数 59
一旦字串显示完毕,函数就没事可做了,这时它返回到被调用处。
在实际情况中,没有多少函数是以这种缺省方式终止运行的。因为有时必须送回一个值,
大多数函数用return语句终止运行,有时在函数中设立了多个终止点以简化函数、提高效率。切
记,一个函数可以有多个返回语句。如下所示,函数在s1、s2相等时返回1,不相等时返回-1。
[例4-3]
find_char(s1 ,s2)
char s1,s2 ;
{
if(s1==s2)
return 1;
else
return -1;
}
2. 返回值
所有的函数,除了空值类型外,都返回一个数值(切记,空值是 A N S I建议标准所做的扩
展,也许并不适合读者手头的 C编译程序)。该数值由返回语句确定。无返回语句时,返回值
是0。这就意味着,只要函数没有被说明为空值,它就可以用在任何有效的 C语言表达式中作
为操作数。这样下面的表达式都是合法的 C语言表达式。
x = power (y);
if (max (x,y) >100) printf("greater");
; isdigit (ch);)... ;
for (ch=getchar( )
可是,函数不能作为赋值对象,下列语句是错误的:
swap(x ,y) =100;
C编译程序将认为这个语句是错误的,而且对含有这种错误语句的程序不予编译。
所有非空值的函数都会返回一个值。我们编写的程序中大部分函数属于三种类型。第一
种类型是简单计算型 — 函数设计成对变量进行运算,并且返回计算值。计算型函数实际上
是一个“纯”函数,例如 sqr( )和sin( )。第二类函数处理信息,并且返回一个值,仅以此表示
处理的成功或失败。例如 write( ),用于向磁盘文件写信息。如果写操作成功了, write( )返回
写入的字节数,当函数返回- 1时,标志写操作失败。最后一类函数没有明确的返回值。实际
上这类函数是严格的过程型函数,不产生值。如果读者用的是符合 A N S I建议标准的 C编译程
序,那么所有这一类函数应当被说明为空值类型。奇怪的是,那些并不产生令人感兴趣的结
果的函数却无论如何也要返回某些东西。例如 printf( )返回被写字符的个数。然而,很难找出
一个真正检查这个返回值的程序。因此,虽然除了空值函数以外的所有函数都返回一个值,
我们却不必非得去使用这个返回值。有关函数返回值的一个常见问题是:既然这个值是被返
回的,我是不是必须把它赋给某个变量?回答是:不必。如果没有用它赋值,那它就被丢弃
了。请看下面的程序,它使用了 mul( )函数。mul( )函数定义为:int mul(int x, int y){......}
[例4-4]
main( )
{
int x,y,z;
x=10 ; y=20 ;
60 C语言程序设计
下载
z=mul(x ,y) ; /* 1 */
printf("%d" ,mul(x ,y)) ; /* 2 */
mul(x ,y) ; /* 3 */
}
4.2 函数的作用域规则
“语言的作用域规则”是一组确定一部分代码是否“可见”或可访问另一部分代码和数据
的规则。
C语言中的每一个函数都是一个独立的代码块。一个函数的代码块是隐藏于函数内部的,
不能被任何其它函数中的任何语句(除调用它的语句之外)所访问(例如,用 g o t o语句跳转
到另一个函数内部是不可能的)。构成一个函数体的代码对程序的其它部分来说是隐蔽的,它
既不能影响程序其它部分,也不受其它部分的影响。换言之,由于两个函数有不同的作用域,
定义在一个函数内部的代码数据无法与定义在另一个函数内部的代码和数据相互作用。
C语言中所有的函数都处于同一作用域级别上。这就是说,把一个函数定义于另一个函数
内部是不可能的。
4.2.1 局部变量
在函数内部定义的变量成为局部变量。在某些 C语言教材中,局部变量称为自动变量,这
就与使用可选关键字 a u t o定义局部变量这一作法保持一致。局部变量仅由其被定义的模块内
部的语句所访问。换言之,局部变量在自己的代码模块之外是不可知的。切记:模块以左花
括号开始,以右花括号结束。
对于局部变量,要了解的最重要的东西是:它们仅存在于被定义的当前执行代码块中,
即局部变量在进入模块时生成,在退出模块时消亡。
定义局部变量的最常见的代码块是函数。例如,考虑下面两个函数。
[例4-5]
func1()
{
int x; /* 可定义为 auto int x; */
x=10;
}
func2()
{
int x; /* 可定义为 auto int x; */
x=-1999;
}
4.2.2 全局变量
与局部变量不同,全局变量贯穿整个程序,并且可被任何一个模块使用。它们在整个程
序执行期间保持有效。全局变量定义在所有函数之外,可由函数内的任何表达式访问。在下
面的程序中可以看到,变量 c o u n t定义在所有函数之外,函数 m a i n ( )之前。但其实它可以放置
在任何第一次被使用之前的地方,只要不在函数内就可以。实践表明,定义全局变量的最佳
位置是在程序的顶部。
[例4-7]
int count; /*count 是全局变量 */
main()
{
count = 100;
func1();
}
func1()
62 C语言程序设计
下载
{
int temp;
temp = count;
func2();
printf("count is %d",count); /* 打印 100 */
}
func2()
{
int count;
for(count = 1; count < 10; count++)
putchar('.'); /* 打印出"。" */
}
4.2.3 动态存储变量
从变量的作用域原则出发,我们可以将变量分为全局变量和局部变量;换一个方式,从
变量的生存期来分,可将变量分为动态存储变量及静态存储变量。
动态存储变量可以是函数的形式参数、局部变量、函数调用时的现场保护和返回地址。
这些动态存储变量在函数调用时分配存储空间,函数结束时释放存储空间。动态存储变量的
定义形式为在变量定义的前面加上关键字“ auto”,例如:
下载
第4章 函 数 63
auto int a, b, c;
“a u t o”也可以省略不写。事实上,我们已经使用的变量均为省略了关键字“ a u t o”的动
态存储变量。有时我们甚至为了提高速度,将局部的动态存储变量定义为寄存器型的变量,
定义的形式为在变量的前面加关键字“ register”,例如:
register int x, y, z;
这样一来的好处是:将变量的值无需存入内存,而只需保存在 C P U内的寄存器中,以使
速度大大提高。由于 C P U内的寄存器数量是有限的,不可能为某个变量长期占用。因此,一
些操作系统对寄存器的使用做了数量的限制。或多或少,或根本不提供,用自动变量来替代。
4.2.4 静态存储变量
在编译时分配存储空间的变量称为静态存储变量,其定义形式为在变量定义的前面加上
关键字“static”,例如:
static int a=8;
定义的静态存储变量无论是做全程量或是局部变量,其定义和初始化在程序编译时进行。
作为局部变量,调用函数结束时,静态存储变量不消失并且保留原值。
[例 4-8]
main( )
{
inf f( ); /*函数声明*/
int j;
for (j=0; j<3; j++)
printf ("%d\n",f( ));
}
int f( ) /*无参函数*/
{
static int x=1;
x++;
return x;
}
运行程序:
RUN ↵
2
3
4
4.3 函数的调用与参数
如果一个函数要使用参数,它就必须定义接受参数值的变量。
64 C语言程序设计
下载
4.3.1 形式参数与实际参数
函数定义时填入的参数我们称之为形式参数,简称形参,它们同函数内部的局部变量作
用相同。形参的定义是在函数名之后和函数开始的花括号之前。
调用时填入的参数,我们称之为实际参数,简称实参。
必须确认所定义的形参与调用函数的实际参数类型一致,同时还要保证在调用时形参与
实参的个数出现的次序也要一一对应。如果不一致,将产生意料不到的结果。与许多其它高
级语言不同,(是健壮的,它总要做一些甚至你不希望的事情,几乎没有运行时错误检查,完
全没有范围检测。作为程序员,必须小心行事以保证不发生错误,安全运行。
4.3.2 赋值调用与引用调用
一般说来,有两种方法可以把参数传递给函数。第一种叫做“赋值调用”(call by value),
这种方法是把参数的值复制到函数的形式参数中。这样,函数中的形式参数的任何变化不会
影响到调用时所使用的变量。
把参数传递给函数的第二种方法是“引用调用”(call by reference)。这种方法是把参数
的地址复制给形式参数,在函数中,这个地址用来访问调用中所使用的实际参数。这意味着,
形式参数的变化会影响调用时所使用的那个变量 (详细内容请参见后续章节 )。
除少数情况外, C语言使用赋值调用来传递参数。这意味着,一般不能改变调用时所用变
量的值。请看例 4-9。
[例4-9]
main ( )
{
int t =10;
printf("%d %d ",sqr(t) ,t) ; /* sqr(t)是函数调用,t是实参*/
}
int sqr(x) /* 函数定义,x是形式参数*/
int x;
{
x=x*x ;
return (x);
}
切记,传给函数的只是参数值的复制品。所有发生在函数内部的变化均无法影响调用时
使用的变量。
4.4 递归
C语言函数可以自我调用。如果函数内部一个语句调用了函数自己,则称这个函数是“递
下载
第4章 函 数 65
归”。递归是以自身定义的过程。也可称为“循环定义”。
递归的例子很多。例如定义整数的递归方法是用数字 1,2,3,4,5,6,7,8,9加上或
减去一个整数。例如,数字 15是7+8;数字21是9+12; 数字12是9+3。
一种可递归的计算机语言,它的函数能够自己调用自己。一个简单的例子就是计算整数
阶乘的函数 factor( )数N的阶乘是1到N之间所有数字的乘积。例如 3的阶乘是1×2×3,即是6。
factor( )和其等效函数fact( )如例4-10所示。
[例4-10]
factor(n) /* 递归调用方法 */
int n;
{
int answer;
if (n==1)
return (1);
answer=factor(n-1) * ;
n /* 函数自身调用 */
return(answer) ;
}
[例4-11]
fact(n) /* 非递归方法 */
int n;
{
int t,answer ;
answer=1 ;
for (t=1; t<=n ; t++)
;
answer = answer * t
return(answer) ;
}
4.5 实现问题
在编写 C语言的函数时,有几个要点需要我们牢记,因为它们影响到函数的效率和可用
性。
4.5.1 参数和通用函数
通用函数是指能够被用在各种情况下,或者是可被许多不同程序员使用的函数。我们不
应该把通用函数建立在全局变量上(不应该在通用函数中使用全局变量)。函数所需要的所有
数据都应该用参数传递(在个别难以这样做的情况下,可以使用静态变量)。使用参数传递,
除了有助于函数能用在多种情况下之外,还能提高函数代码的可读性。不用全局变量,可以
使得函数减少因副作用而导致错误的可能性。
4.5.2 效率
函数是 C语言的基本构件。对于编写简单程序之外的所有程序来说,函数是必不可少的。
但在一些特定的应用中,应当消除函数,而采用内嵌代码。内嵌代码是指一个函数的语句中
不含函数调用语句。仅当执行速度是很关键的场合下,才用内嵌代码而不用函数。
有两个原因使得内嵌代码的执行速度比函数快。首先,调用需要花费时间;其次,如果
有参数需要传递,就要把它们放在堆栈中,这也要用时间。在几乎所有的应用中,执行时间
上的这些微小开销是微不足道的。不过当时间开销至关重要时,使用内嵌代码消除函数调用,
可以把每次函数调用的开销节省下来。下面的两个程序都是打印从 1到1 0的数的平方。由于函
数调用需要花费时间,所以内嵌代码版本运行的比另一个要快。
内嵌 函数调用
main ( ) main ( )
{ {
int x; int x;
for (x=1,x<11 ;++x) for (xx=1;x<11 ;++x)
printf ("%d",x*x) ; printf ("%d",sqr(x)) ;
} }
sqr(a) ;
int a;
{
return a*a;
}
下载
第4章 函 数 67
4.6 函数库和文件
4.6.1 程序文件的大小
因为C语言允许分别编译,很自然就会提出这样的问题:一个文件的最适宜的规模是多
大?这规模很重要,因为编译时间与被编译文件的大小直接相关。一般说来,链接处理的时
间比编译处理的时间短得多,且不需要经常去重新编译已经运行过的代码;另一方面,不得
不同时处理多个文件也确实是件厌烦的事。
问题的答案是,每个用户、每个编译程序、每个操作系统环境都是不同的。可是对大部
分微型机和一般的 C编译程序来说。源程序文件不应长于 1 0 0 0 0个字节,建立短于 5 0 0 0个字节
的文件,可以避免不少麻烦。
4.6.2 分类组织文件
在开发一个大型程序时,最令人烦恼的而又是最常遇到的工作之一就是需要检查每个文
件,以确定某个函数的存放。在程序开发的早期做一点文件组织工作就可以避免这一问题。
首先可以把概念上有关的函数组织到一个文件中。如果在编写正文编辑程序时,把删除
正文所用的所有函数放进另一个文件,等等。
第二,把所有的通用函数放在一起。例如,在数据库程序中,输入/输出格式编排函数
是被其它函数调用的通用函数,应把它们放进一个单独的文件里。
第三,把最高层函数放进一个单独的文件中,如果空间允许,就和 main ( ) 放在一起。最
高层函数被用来启动程序的总体活动。这些例程从本质上定义了程序的操作。
4.6.3 函数库
从技术上讲,函数库与分别编译的函数文件不同。当库中例程被链接到程序中,或当使
用一个分别编译的文件时,文件中的所有函数都被装入和链接到程序中去。对自己创建的函
数文件中的大多数文件来说,文件中所有的函数都是要用到的。而对 C的标准函数库,永远也
无法把所有的函数都连接到自己的程序中去,因为目的码会大得吓人!
有时候我们需要建立一个函数库,例如,假定已经完成了一套专门的统计函数,如果当
前开发的某个程序仅仅需要求出一批数值的均值,我们就不必把这些函数全部装入。在这种
情况下,函数库是很有用的。
大部分C语言的编译程序都有建立函数库的指令。操作过程因编译程序不同而异,可从用
户手册中寻找建库的具体步骤。
4.7 C语言的预处理程序与注释
C程序的源代码中可包括各种编译指令,这些指令称为预处理命令。虽然它们实际上不是
C语言的一部分,但却扩展了 C程序设计的环境。本节将介绍如何应用预处理程序和注释简化
68 C语言程序设计
下载
程序开发过程,并提高程序的可读性。
4.7.1 C语言的预处理程序
非常明显,所有预处理命令均以符号#开头,下面分别加以介绍。
4.7.2 #define
命令#d e f i n e定义了一个标识符及一个串。在源程序中每次遇到该标识符时,均以定义的
串代换它。 ANSI标准将标识符定义为宏名,将替换过程称为宏替换。命令的一般形式为:
#define identifier string
注意,该语句没有分号。在标识符和串之间可以有任意个空格,串一旦开始,仅由一新
行结束。
例如,如希望TURE取值1,FALSE 取值0,可说明两个宏 #define
#define TURE 1
#define FALSE 0
这使得在源程序中每次遇到 TURE或FALSE就用0或1代替。
例如,在屏幕上打印“ 0 1 2 ”:
printf("%d %d %d",FALSE ,TRUE ,TRUE+1) ;
宏名定义后,即可成为其它宏名定义中的一部分。例如,下面代码定义了 O N E、T W O及
THREE的值。
#define ONE 1
#define TWO ONE+ONE
#define THREE ONE+TWO
懂得宏替换仅仅是以串代替标识符这点很重要。因此,如果希望定义一个标准错误信息,
可编写如下代码:
#define E_MS "standard error on input\n"
printf(E_MS) ;
如果在串中含有标识符,则不进行替换。例如:
#define XYZ this is a test
.
.
.
printf("XYZ") ;
C语言程序普遍使用大写字母定义标识符。这种约定可使人读程序时很快发现哪里有宏替
换。最好是将所有的 # d e f i n e放到文件的开始处或独立的文件中 (用# i n c l u d e访问),而不是将它
们分散到整个程序中。
宏代换的最一般用途是定义常量的名字和程序中的“游戏数”。例如,某一程序定义了一
个数组,而它的几个子程序要访问该数组,不应直接以常量定数组大小,最好是用名字定义
之(需改变数组大小时)。
#define MAX_SIZE 100
;
float balance[MAX_SIZE]
# d e f i n e命令的另一个有用特性是,宏名可以取参量。每次遇到宏名时,与之相连的形参
均由程序中的实参代替。例如:
[例4-12]
#define MIN(a,b) (a<b) ? a : b
main()
{
int x, y;
x = 10;
y = 20;
,MIN(x ,y)) ;
printf("the minimum is: %d"
}
用宏代换代替实在的函数的一大好处是宏替换增加了代码的速度,因为不存在函数调用
的开销。但增加速度也有代价:由于重复编码而增加了程序长度。
4.7.3 #error
处理器命令 #error强迫编译程序停止编译,主要用于程序调试。
4.7.4 # include
4.7.5 条件编译命令
有几个命令可对程序源代码的各部分有选择地进行编译,该过程称为条件编译。商业软
件公司广泛应用条件编译来提供和维护某一程序的许多顾客版本。
1. #if、#else,#elif及#endif
#if 的一般含义是如果 #if 后面的常量表达式为 t r u e,则编译它与 # e n d i f之间的代码,否则
跳过这些代码。命令 #endif 标识一个#if 块的结束,参见例 4-13。
#if constant-expression
statement sequence
#endif
[例4-13]
# define MAX 100
main( )
{
# if MAX>99
;
printf("compiled for array greater than 99\n")
# endif
}
例如:下面程序利用 ACTIVE_COUNTRY定义货币符号。
#define US 0
#define ENGLAND1
#define FRANCE 2
# define ACTIVE_COUNTRY US
#if ACTIVE_COUNTRY = = US
char currency[ ]="dollar;"
#elif ACTIVE_COUNTRY= =ENGLAND
char currency[ ]="pound;"
#else
;
char currency[ ]="franc"
#endif
#if与#elif命令可能一直嵌套到实现规定的权限,其中 #endif、#else或#elif与最近#if或#elif
关联。例如,下面程序是完全有效的。
#if MAX>100
#if SERIAL_VERSION
int port=198;
#elif
int port=200;
#elif
#else
char out_buffer[100];
#endif
72 C语言程序设计
下载
2. # ifdef 和# ifndef
条件编译的另一种方法是用 # i f d e f与# i f n d e f命令,它们分别表示“如果有定义”及“如果
无定义”。
# ifdef的一般形式是:
# ifdef macroname
statement sequence
#endif
如果宏名在前面 #define语句中已定义过,则该语句后的代码块被编译。
#ifndef的一般形式是:
#ifndef macroname
statement sequence
#endif
4.7.6 #undef
命令 #undef 取消其后那个前面已定义过有宏名定义。一般形式为:
#undef macroname
例如:
# define LEN 100
#difine WIDTH 100
;
char array[LEN][WIDTH]
# undef LEN
# undef WIDTH
/ *at this point both LEN and WIDTH are undefined * /
直到遇到#undef 语句之前, LEN与WIDTH均有定义。
# undef 的主要目的是将宏名局限在仅需要它们的代码段中。
下载
第4章 函 数 73
4.7.7 #line
命令#line改变_LINE_ 与_FILE_的内容,它们是在编译程序中预先定义的标识符。
命令的基本形式如下:
# line number["filename"]
其中的数字为任何正整数,可选的文件名为任意有效文件标识符。行号为源程序中当前
行号,文件名为源文件的名字。命令 #line主要用于调试及其它特殊应用。
例如,下面说明行计数从 100开始;printf( ) 语句显示数 102,因为它是语句 #line 100后的
第3行。
#line 100 /* 初始化行计数器 */
main ( ) /* 行号 100 */
{ /* 行号101 */
printf("%d\n" ,_LINE_) ; /* 行号 102 */
}
4.7.8 #pragma
命令#pragma 为实现时定义的命令,它允许向编译程序传送各种指令。例如,编译程序
可能有一种选择,它支持对程序执行的跟踪。可用 #pragma语句指定一个跟踪选择。
4.7.9 预定义的宏名
ANSI标准说明了五个预定义的宏名。它们是:
_LINE_
_FILE_
_DATE_
_TIME_
_STDC_
如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序
也许还提供其它预定义的宏名。
_LINE_及_FILE_宏指令在有关#line的部分中已讨论,这里讨论其余的宏名。
_DATE_宏指令含有形式为月 /日/年的串,表示源文件被翻译到代码时的日期。
源代码翻译到目标代码的时间作为串包含在 _TIME_中。串形式为时:分:秒。
如果实现是标准的,则宏 _ S T D C _含有十进制常量 1。如果它含有任何其它数,则实现是
非标准的。
注意:宏名的书写由标识符与两边各二条下划线构成。
4.7.10 注释
注释可出现在程序的任何位置,但它不能出现在关键字或标识符中间。
即,注释x=10+ /*add the numbers */ 5;是有效的,但 swi/* this will not work */tch(c){...
是不正确的,因为 C的关键字不能含有注释。通常也不希望表达式中间出现注释,因为这会使
意义含混不清。
注释不可嵌套,即一个注释内不可含有另一个注释。例如,下面代码段在编译时出错:
/*this is an outer comment
x=y/a ;
/*this is an inner comment -and causes an error */
*/
当需要解释程序的行为时,注释应简明扼要。除了最简单和最直观的函数外,都应有注
释,在函数开始处说明其功能,如何调用以及返回何处。
4.8 程序应用举例
[例4-16] 字符串的显示及反向显示。
#include <stdio.h>
#include <string.h> 包含字符串库函数说明的头文件
/* */
# include <stdio.h>
,int index);
void forward_and_backwards(char line_of_char[] /* 函数声明*/
void main()
{
;
char line_of_char[80] /*定义字符数组 */
int index = 0;
这是一个递归函数调用的例子。程序中函数 f o r w a r d _ a n d _ b a c k w a r d s ( )的功能是显示一个
字符串后反向显示该字符串。
[例4-17] 计算1~7的平方及平方和。
#include <stdio.h>
下载
第4章 函 数 75
# include<math.h>
void header(); /*函数声明*/
void square(int number) ;
void ending();
int sum; /* 全局变量 */
main()
{
int index;
header() ; /* 函数调用*/
for (index = 1;index <= 7;index++)
square(index) ;
ending() ; /*结束*/
}
void header()
{
sum = 0; /* 初始化变量"sum" */
;
printf("This is the header for the square program\n\n")
}
;
numsq = number * number
sum += numsq;
,number ,numsq) ;
printf("The square of %d is %d\n"
}
void ending()
{
,sum) ;
printf("\nThe sum of the squares is %d\n"
}
运行程序:
RUN ↵
This is the header for the square program
The square of 1 is 1
The square of 2 is 4
The square of 3 is 9
The square of 4 is 16
The square of 5 is 25
The square of 6 is 36
The square of 7 is 49
The sum of the squares is 140
void head1(void);
void head2(void);
void head3(void);
int count; /* 全局变量 */
main()
{
;
register int index /* 定义为主函数寄存器变量 */
head1() ;
head2() ;
head3() ;
index = 23;
,index) ;
printf("The header1 value is %d\n"
}
void head2(void)
{
int count; /* 此变量是函数 head2() 的局部变量 */
/* 此变量名与全局变量 count 重名 */
/* 故全局变量count 不能在函数head2() 中使用*/
count = 53;
,count) ;
printf("The header2 value is %d\n"
counter = 77;
}
下载
第4章 函 数 77
void head3(void)
{
,counter) ;
printf("The header3 value is %d\n"
}
运行程序:
RUN ↵
The headerl value is 23
The header2 value is 53
The header3 value is 77
0 1 2 3 4 5 6 index is now 8
0 1 2 3 4 5 6 index is now 7
0 1 2 3 4 5 6 index is now 6
0 1 2 3 4 5 6 index is now 5
0 1 2 3 4 5 6 index is now 4
0 1 2 3 4 5 6 index is now 3
0 1 2 3 4 5 6 index is now 2
0 1 2 3 4 5 6 index is now 1
该程序的演示帮助读者来了解全局变量、局部变量的作用域,请仔细理解体会。
下载
第5章 数 组
数组是一个由若干同类型变量组成的集合,引用这些变量时可用同一名字。数组均由连
续的存储单元组成,最低地址对应于数组的第一个元素,最高地址对应于最后一个元素,数
组可以是一维的,也可以是多维的。
5.1 一维数组
一维数组的一般说明形式如下 :
type-specifier var_name [size];
在C语言中,数组必须显示地说明,以便编译程序为它们分配内存空间。在上式中,类型
说明符指明数组的类型,也就是数组中每一个元素个数,一维数组的总字节数可按下式计算 :
sizeof( 类型)* 数组长度 = 总字节数
[例5-1] 将数字0到9装入一个整型数组。
main( )
{
int x[10]; /* 定义包含10个整型数的数组,引用为 x[0] ,x[1]...x[9]*/
int t ;
for (t=0; t<10;++t) x[t]=t;
}
C语言并不检验数组边界,因此,数组的两端都有可能越界而使其它变量的数组甚至程序
代码被破坏。在需要的时候,数组的边界检验便是程序员的职责。例如,当使用 gets( )接收字
符输入时,必须确认字符数组的长度足以存放最长的字符串。
一维数组在本质上是由同类数据构成的表,例如,对下列数组 a:
char a[7]
元素 0 1 2 3 4 5 6
地址 1000 1001 1002 1003 1004 1005 1006
图5-1 起始地址为1000的7元素字符数组
5.1.1 向函数传递一维数组
将一维数组传递给函数时,把数组名作为参数直接调用函数即可,无需任何下标。这样,
数组的第一个元素的地址将传递给该函数。 C语言并不是将整个数组作为实参来传递,而是用
指针来代替它。例如,下面的程序将数组 i的第一个元素的地址传递给函数 func1( )。
main( )
下载
第5章 数 组 79
{
int i[10];
func1(i); /*函数调用,实参是数组名*/
.
.
.
}
函数若要接收一维数组的传递,则可以用下面的二种方法之一来说明形式参数; 1) 有界
数组;2) 无界数组。例如,函数 func1 ( )要接收数组 i可如下说明 :
func1(str)
char str[10]; /* 有界数组,数组的下标只能小于或等于传递数组的大小。 */
{
.
.
.
}
也可说明为:
func1(str)
char str[ ]; / * 无界数组 * /
{
.
.
.
}
这二种说明方法的效果是等价的,它们都通知编译程序建立一个字符指针。第一种说明
使用的是标准的数组说明;后一种说明使用了改进型的数组说明,它只是说明函数将要接收
一个具有一定长度的整型数组。细想就会发现,就函数而言,数组究竟有多长并无关紧要,
因为C语言并不进行数组的边界检验。事实上,就编译程序而言,下面的说明也是可行的。
func1 (str);
int str[32];
{
.
.
.
}
因为编译程序只是产生代码使函数 func1( )接收一个指针,并非真正产生一个包含 3 2个元
素的数组。
5.1.2 字符串使用的一维数组
显然,一维数组的最普通的用法是作为字符串。在 C语言中,字符串被定义为一个以空字
符终结的字符数组。空字符以‘ \ 0’来标识,它通常是不显示的。因此,在说明字符数组时,
必须比它要存放的最长字符串多一个字符。例如,假如要定义一个存放长度为 1 0的字符串的
数组s,可以写成 :
char s[11];
80 C语言程序设计
下载
这样就给字符串末尾的空字符保留了空间。
尽管C语言并不把字符串定义为一种数据类型,但却允许使用字符串常量。字符串常量是
由双引号括起来的字符表。例如,下面两个短语均为字符串常量 :
"hello there"
"this is a test"
不必向字符串的末尾加空字符, C编译程序会自动完成这一工作。
C语言支持多串操作函数,最常用的有 :
名字 功能
strcpy(s1 s2) 将s2拷贝到s1
strcat(s1 s2) 将s2连接到s1的末尾
strlen(s1) 返回s1的长度
strcmp(s1,s2) 若s1与s2相等,返回值为0
若s1<s2,返回值小于0
若s1>s2,返回值大于0
例5-2说明了这些函数的用法。
[例5-2]
# include <stdio.h>
main ( )
{
char s1[80],s2[80]; /*定义字符数组*/
gets (s1); /*输入字符串*/
gets (s2);
,strlen(s1) ,strlen(s2));
printf ("lengthsf: %d %d \n"
if (!strcmp(s1,s2))
printf ("the strings are equal \n");
strcat(s1 ,s2);
printf ("%s\n",s1);
}
5.2 二维数组
5.2.1 二维数组的一般形式
C语言允许使用多维数组,最简单的多维数组是二维数组。实际上,二维数组是以一维数
下载
第5章 数 组 81
组为元素构成的数组,要将 d说明成大小为( 10,20)的二维整型数组,可以写成 :
int d[10][20]
请留心上面的说明语句, C不像其它大多数计算机语言那样使用逗号区分下标,而是用方
括号将各维下标括起,并且,数组的二维下标均从 0计算。
与此相似,要存取数组 d中下标为( 3,5)的元素可以写成 :
d[3][5]
在例5-3中,整数1到12被装入一个二维数组。
[例5-3]
main ( )
{
int t,i,num[3][4]
for (t=0; t<3; ++t)
for (i=0;i<4;++i)
num[t][i]=(t*4)+i+1;
}
二维数组以行—列矩阵的形式存储。第一个下标代表行,第二个下标代表列,这意味着
按照在内存中的实际存储顺序访问数组元素时,右边的下标比左边的下标的变化快一些。图
5-2是一个二维数组在内存中的情形,实际上,第一下标可以认为是行的指针。
记住,一旦数组被证明,所有的数组元素都将分配相应的存储空间。对于二维数组可用
下列公式计算所需的内存字节数:
行数×列数×类型字节数=总字节数
图5-2 内存中的二维数组
第一维的长度也可指明,但没有必要。
C编译程序对函数中的如下语句:
X[2][4]
处理时,需要知道二维的长度。若行长度没定义,那么它就不可能知道第三行从哪儿开
始。
[例5-4] 用一个二维数组存放某一教师任教的各班学生的分数。假定教师有三个班,每班
最多有三十名学生。注意各函数存取数组的方法。
#define classes 3
#define grades 30
#include <stdio.h>
main( )
{
void enter_grades() ;
void disp_grades( );
int get_grade( );
定义二维数组,每行存放一个班学生成绩 */
int a[classes] [grades];/*
char ch;
for( ; ;)
{
do { /*菜单显示*/
printf("(E)nter grades\n");
printf("(R)eport grades\n");
printf("(Q)uit\n");
将键盘输入字符转换为大写 */
ch=toupper(getchar()); /*
} while(ch!='E' && ch!='R' && ch!='Q');
switch(ch)
{
case 'E':
enter_grades( );
break;
case 'R':
下载
第5章 数 组 83
disp_grades(grade);
break;
case 'Q':
exit(0);
}
}
}
void enter_grades(a)
int a[][grades];
{
int t, i;
for (t=0;t<classes;t++)
{
printf (" class #%d:\n",t+1);
for (i=0; i<grades; i++)
a[t][i]=get_grade(i);
}
}
int get_grades(num)
int num;
{
char s[80];
,num+1);
printf("enter grade for student # %d:\n"
gets(s) ;/* 输入成绩*/
return(atoi(s));
}
void disp_grades(g) /* 显示学生成绩*/
int g[ ][grades];
{
int t,i;
for(t=0; t<classes; ++t) {
printf("class # %d:\n" ,t+1);
for(i=0;i<grades;++i)
,i+1 ,g[t][i]);
printf("grade for student #%d is %d\n"
}
}
我们将实际问题简化为共有 2个班,每班两个学生,即将程序中的常量定义修改如下:
#define classes 2
#define grades 2
运行程序:
RUN ↵
(E)nter grades
(R)eport grades
(Q)uit: e↵
class #1:
78 ↵
enter grade for student #1:
84 C语言程序设计
下载
89 ↵
enter grade for student #2:
class #2
enter grade for student #1:98 ↵
enter grade for student #2:90 ↵
(E)nter grades
(R)eport grades
(Q)uit: r↵
class #1
grade for student #1 is 78
grade for student #2 is 89
class #2
grade for student #1 is 98
grade for student #2 is 90
(E)nter grades
(R)eport grades
(Q)uit :q↵
5.2.2 字符串数组
程序设计中经常要用到字符串数组。例如,数据库的输入处理程序就要将用户输入的命
令与存在字符串数组中的有效命令相比较,检验其有效性。可用二维字符数组的形式建立字
符串数组,左下标决定字符串的个数,右下标说明串的最大长度。例如,下面的语句定义了
一个字符串数组,它可存放 30个字符串,串的最大长度为 80个字符:
char str_array[30][80];
要访问单独的字符串是很容易的,只需标明左下标就可以了。例如,下面的语句以数组
str_array中的第三个字符串为参数调用函数 gets( )。
gets(str_array[2]);
该语句在功能上等价于:
gets(&str_array[2][0]);
但第一种形式在专业程序员编制的 C语言程序中更为常见。
为帮助理解字符串数组的用法,研究例5-5。它以一个字符串数组为基础做简单的文本编辑。
[例5-5]
#include <stdio.h >
#define MAX 100
#define LEN 80
char text [MAX][LEN]
/* 一个非常简单的文本编辑器 */
main( )
{
register int t,i,j;
for(t=0;t<MAX; t++) 逐行输入字符串 */
/*
{
下载
第5章 数 组 85
printf("%d:" ,t);
gets(text[t]);
if(! text[t][0])
break; /* 空行退出 */
}
for(i=0;i<t ,i++) /*按行,逐个字符输出字符串 */
{
for(j=0; text [i][j];j++)
putchar(text [i][j]);
putchar( '\n');
}
}
该程序输入文本行直至遇到一个空行为止,而后每次一个字符地重新显示各行。
5.3 多维数组
C语言允许有大于二维的数组,维数的限制(如果有的话)是由具体编译程序决定的。多
维数组的一般说明形式为:
Type-specifier name [a][b][c]...[z];
由于大量占有内存的关系,二维或更多维数组较少使用。如前所述,当数组定义之后,
所有的数组元素都将分配到地址空间。例如,大小为( 10,6,9,4)的四维字符数组需要 10
×6×9×4即2160字节。
如果上面的数组是两字节整型的,则需要 4 3 2 0字节,若该数组是双字型的(假定每个双
字为8字节)则需要34560字节,存储量随着维数的增加呈指数增长。
关于多维数组,需要注意一点:计算机要花大量时间计算数组下标,这意味着存取多维
数组中的元素要比存取一维数组的元素花更多的时间。由于这些和其它原因,大量的多维数
组一般采用 C语言动态分配函数及指针的方法,每次对数组的一部分动态地分配存储空间。
多维数组传递给函数时,除第一维外,其它各维都必须说明。例如,将数组 m定义成:
int m[4][3][6][5];
那么接收m的函数应写成:
func1 (d)
int d[][3][6][5];
当然,如果愿意,也可加上第一维的说明。
5.4 数组的初始化
5.4.1 数组初始化
C语言允许在说明时对全局数组和静态局部数组初始化,但不能对非静态局部数组初始化。
与其它变量相似,数组初始化的一般形式如下:
type-specifier array_name[size1]...[sizen]={value-list};
数值表是一个由逗号分隔的常量表。这些常量的类型与类型说明相容,第一个常量存入
86 C语言程序设计
下载
数组的第一个单元,第二个常量存入第二个单元,等等,注意在括号“ }”后要加上分号。
下列中一个 10元素整型数组被初始化装入数字 1到10:
int i[10]={1,2,3,4,5,6,7,8,9,10};
这意味着i[0]的值为1,而i[9]的值为10。
存放字符串的字符数组的初始化可采用如下简化的形式:
char array_name[size] = "string";
上面代码产生和下面代码相同的结果:
char str[6]={'h','e' ,'l' ,'l' ,'o' ,'\o'};
因为C语言中的字符串都以空( N U L L)字符为终结,故要确认定义的数组足够长以存放
空字符。这就是为什么 h e l l o只有5个字符,而 str 要有6个字符长的原因。使用字符串常量时,
编译程序自动地在末尾加上空字符。
多维数组初始化的方法与一维数组相同,例如,下式将 s q r s初始化为从 1到1 0及它们各自
的平方数。
int sqrs[10][2]={
1,1,
2,4,
3,9,
4,16 ,
5,25 ,
6,36 ,
7,49 ,
8,64 ,
9,81 ,
10 ,100 ,
};
5.4.2 变长数组的初始化
设想用数组初始化的方法建立一个如下错误信息表:
char e1[12] = "read error\n";
char e2[13] = "write error\n";
char e3[18] = "cannot open file\n";
可以想象,如果用手工去计算每一条信息的字符数以确定数组的长度是何等的麻烦。利
用变长数组初始化的方法可以使 C自动地计算数组的长度。变长数组初始化就是使 C编译程序
自动建立一个不指明长度的足够大的数组以存放初始化数据。使用这种方法,以上信息表变
为:
char e1[] = "read error\n";
char e2[] = "write error\n";
char e3[] = "cannot open file\n";
给定上面的初始化,下面的语句
下载
第5章 数 组 87
,e2,sizeof(e2));
printf("%s has length %d\n"
将打印出:
write error
has length 13
除了减少麻烦外,应用变长数组初始化使程序员可以修改任何信息,而不必担心随时可
能发生的计算错误。
变长数组初始化的方法不仅仅限于一维数组。但在对多维数组初始化时,必须指明除了
第一维以外其它各维的长度,以使编译程序能够正确地检索数组。其方法与数组形式参数的
说明类似。这样就可以建立变长表,而编译程序自动地为它们分配存储空间。例如,下面用
变长数组初始化的方法定义数组 sqrs:
int sqrs[ ][2]={
1,1,
2,4,
3,9,
4,16,
5,25 ,
6,36 ,
7,49,
8,64 ,
9,81 ,
10 ,100
};
相对定长数组的初始化而言,这种说明的优点在于可以在不改变数组各维长度的情况下,
随时增加或缩短表的长度。
5.5 应用程序举例
[例5-6] 为比赛选手评分。
计算方法:从 1 0名评委的评分中扣除一个最高分,扣除一个最低分,然后统计总分,并
除以8,最后得到这个选手的最后得分 (打分采用百分制 )。
#include<stdio.h>
main()
{
int score[10]; /*10 个评委的成绩*/
float mark; 最后得分*/
/*
int i;
int max = -1; /* 最高分*/
int min = 101; /*最低分*/
int sum = 0; 个评委的总和*/
/*10
for(i=0;i<10;i++)
{
printf("Please Enter the Score of No. ,i
%d"+1);
scanf("%d\n" ,&score[i]);
88 C语言程序设计
下载
sum=sum+score[i];
}
for(i=0;i<10;i++)
{
if(score[i]>max)
max=score[i];
}
for(i=0;i<10;i++)
{
if(score[i]<min)
min=score[i];
}
mark=(sum-min-max)/8.0;
,mark);
printf("The mark of the player is %.1f\n"
}
一趟排序: 1次比较:76 44 82 63 71
2次比较:82 44 76 63 71
3次比较:82 44 76 63 71
4次比较:82 44 76 63 71
最大
#include <stdio.h>
main()
{
int num[5];
int i,j;
int temp;
这是一个非常简单的排序程序,我们只需稍加扩展就可以编制出很多功能强大的管理程
序,如学生统计总分、平均排列年级名次等。
[例5-8] 简易学生成绩查询系统。
图5-3为学生成绩登记表,下例程序完成如下功能:
1) 根据输入的学生学号,给出各次考试成绩及平均成绩;
2) 根据输入考试的次数,打印出该次考试中每个学生的成绩,并给出平均分;
3) 根据学号查出学生某次考试成绩;
4) 录入考试成绩。
考试
1 2 3 4 5 6
学号 成绩
1 80 60 70 80 50 90
2 80 70 82 50 90 60
3 75 86 74 81 92 61
4 55 61 70 72 74 81
图5-3 学生成绩表
#include <stdio.h>
mian()
{
int select;
int i,j;
int score[5][7];
int average=0;
int sum=0;
do{
printf(" 本程序有4项功能\n");
printf("1 、根据学号查询学生成绩 \n");
printf("2 、根据考试号统计成绩\n");
printf("3 、根据考试号和学号查询成绩 \n");
printf("4 、成绩录入\n");
printf("0 、退出\n");
printf(" 请输入选择(0-4 ):");
scanf("%d\n" ,&select);
90 C语言程序设计
下载
switch(select)
{
case 0:
printf("OK\n");
exit(0)
break;
case 1:
printf(" 输入学号:");
scanf("%d\n" ,&i);
for(j=1; j<7; j++)
{
printf(" 第%d科成绩是%d\n" ,j,score[i][j]);
sum += score[i][j];
}
average=sum/6;
printf(" 学生的平均成绩是 %d\n" ,average);
break;
case 2:
printf(" 输入考试号:");
scanf("%d\n" ,&j);
for(i=1; i<5; i++)
{
printf(" 第%d 号学生本科成绩是 %d\n" ,i,score[i][j]);
sum += score[i][j];
}
average=sum/4;
printf(" 本科平均成绩是 %d\n" ,average);
break;
case 3:
printf(" 输入学号和考试号 :");
scanf("%d %d\n",&i ,&j);
printf(" 第 %d 号学生的第 %d 科考试成绩是 %d\n" , i , j ,
score[i][j]);
break;
case 4:
printf(" 请输入成绩\n");
for(i=1; i<5; i++)
for(j=1; j<7; j++)
scanf("%d\n" ,&score[i][j]);
break;
default:
break;
}while(1);
}
从本例中可以看出,当涉及到二维数组时,通常用两重 for循环来存取元素。
下载
第6章 指 针
指针是C语言的精华部分,通过利用指针,我们能很好地利用内存资源,使其发挥最大的
效率。有了指针技术,我们可以描述复杂的数据结构,对字符串的处理可以更灵活,对数组
的处理更方便,使程序的书写简洁,高效,清爽。但由于指针对初学者来说,难于理解和掌
握,需要一定的计算机硬件的知识做基础,这就需要多做多练,多上机动手,才能在实践中
尽快掌握,成为 C的高手。
6.1 指针与指针变量
2000 1 变量a
2000 pa
1000
2002 2 变量b
1002 2002 pb
2020 a 变量ch1
2021 b 变量ch2
图6-2 指针变量与变量在内存中的关系
6.2 指针变量的定义与引用
6.2.1 指针变量的定义
在C程序中,存放地址的指针变量需专门定义;
int *ptr1;
float *ptr2;
char *ptr3;
图6-3 赋值语句的效果
需要说明的是,指针变量可以指向任何类型的变量,当定义指针变量时,指针变量的值
是随机的,不能确定它具体的指向,必须为其赋值,才有意义。
6.2.2 指针变量的引用
利用指针变量,是提供对变量的一种间接访问形式。对指针变量的引用形式为:
*指针变量
其含义是指针变量所指向的值。
[例6-1] 用指针变量进行输入、输出。
main()
{
int *p,m;
scanf("%d",&m);
p=&m; /* 指针p指向变量m*/
printf("%d",*p);
/* p是对指针所指的变量的引用形式 ,与此m意义相同*/
}
运行程序:
RUN ↵
3↵
3
上述程序可修改为:
main()
{
int *p,m;
p=&m;
scanf("%d",p); /* p是变量m的地址,可以替换&m*/
printf("%d", m);
}
运行效果完全相同。请思考一下若将程序修改为如下形式:
main()
{
int *p,m;
scanf("%d",p);
p=&m;
printf("%d", m);
}
会产生什么样的结果呢?事实上,若定义了变量以及指向该变量的指针为:
int a,*p;
94 C语言程序设计
下载
若p=&a; 则称p指向变量 a,或者说 p具有了变量 a的地址。在以后的程序处理中,凡是可
以写&a的地方,就可以替换成指针的表示 p,a就可以替换成为 *p。
6.3 指针运算符与指针表达式
6.3.1 指针运算符与指针表达式
在C中有两个关于指针的运算符:
• &运算符: 取地址运算符, &m即是变量m的地址。
• *运算符:指针运算符, *ptr表示其所指向的变量。
[例6-2] 从键盘输入两个整数,按由大到小的顺序输出。
main()
{
int *p1,*p2,a,b,t; /* 定义指针变量与整型变量*/
scanf("%d,%d",&a,&b);
p1=&a; /* 使指针变量指向整型变量*/
p2=&b;
if(*p1<*p2)
{ /*交换指针变量指向的整型变量 */
t=*p1;
*p1=*p2;
*p2=t;
}
printf("%d,%d\n",a,b);
}
在程序运行过程中,指针与所指的变量之间的关系如图 6-4所示:
p1 a *p1 p1 a *p1
&a 3 &a 4
p2 b *p2
&b 4 p2 b *p2
a) &b 3
b)
图6-4 程序运行中指针与变量之间的关系
当指针被赋值后,其在内存的安放如 a),当数据比较后进行交换,这时,指针变量与所指
向的变量的关系如 b)所示,在程序的运行过程中,指针变量与所指向的变量其指向始终没变。
下面对程序做修改。
下载
第6章 指 针 95
[例6-3]
main()
{
int *p1,*p2,a,b,*t;
scanf("%d,%d",&a,&b);
p1=&a;
p2=&b;
if(*p1<*p2)
{ /* 指针交换指向 */
t=p1;
p1=p2;
p2=t;
}
printf("%d,%d\n",*p1,*p2);
}
程序的运行结果完全相同,但程序在运行过程中,实际存放在内存中的数据没有移动,
而是将指向该变量的指针交换了指向。其示意如图 6-5:
p1 a *p1 p1 a *p2
&a 3 &b 3
p2 b *p2 p2 b p1
&b 4 &a 4
a) b)
图6-5 修改后的程序在运行中指针与变量之间的关系
6.3.2 指针变量作函数的参数
函数的参数可以是我们在前面学过的简单数据类型,也可以是指针类型。使用指针类型
做函数的参数,实际向函数传递的是变量的地址。由于子程序中获得了所传递变量的地址,
在该地址空间的数据当子程序调用结束后被物理地保留下来。
[例6-4] 利用指针变量作为函数的参数,用子程序的方法再次实现上述功能。
main()
{
void chang(); /* 函数声明*/
int *p1,*p2,a,b,*t;
scanf("%d,%d",&a,&b);
p1=&a;
p2=&b;
chang(p1,p2); /*子程序调用*/
printf("%d,%d\n",*p1,*p2);
96 C语言程序设计
下载
return 0;
}
void chang(int *pt1,int *pt2)
{ /* 子程序实现将两数值调整为由大到小 */
int t;
if (*pt1<*pt2) /* 交换内存变量的值 */
{
t=*pt1; *pt1=*pt2; *pt2=t;}
return;
}
由于在调用子程序时,实际参数是指针变量,形式参数也是指针变量,实参与形参相结
合,传值调用将指针变量传递给形式参数 pt1和pt2。但此时传值传递的是变量地址,使得在子
程序中pt1和pt2具有了p1和p2的值,指向了与调用程序相同的内存变量,并对其在内存存放的
数据进行了交换,其效果与 [例6-2]相同。
思考下面的程序,是否也能达到相同的效果呢?
main()
{
void chang();
int *p1,*p2,a,b,*t;
scanf("%d,%d",&a,&b);
p1=&a;
p2=&b;
chang(p1,p2);
printf("%d,%d\n",*p1,*p2);
}
void chang(int *pt1,int *pt2)
{
int *t;
if (*pt1<*pt2)
{
t=pt1; pt1=pt2; pt2=t;
}
return;
}
程序运行结束,并未达到预期的结果,输出与输入完全相同。其原因是对子程序来说,
函数内部进行指针相互交换指向,而在内存存放的数据并未移动,子程序调用结束后,
main()函数中p1和p2保持原指向,结果与输入相同。
6.4 指针与数组
变量在内存存放是有地址的,数组在内存存放也同样具有地址。对数组来说,数组名就
是数组在内存安放的首地址。指针变量是用于存放变量的地址,可以指向变量,当然也可存
放数组的首址或数组元素的地址,这就是说,指针变量可以指向数组或数组元素,对数组而
言,数组和数组元素的引用,也同样可以使用指针变量。下面就分别介绍指针与不同类型的
数组。
下载
第6章 指 针 97
6.4.1 指针与一维数组
假设我们定义一个一维数组,该数组在内存会有系统分配的一个存储空间,其数组的名
字就是数组在内存的首地址。若再定义一个指针变量,并将数组的首址传给指针变量,则该
指针就指向了这个一维数组。我们说数组名是数组的首地址,也就是数组的指针。而定义的
指针变量就是指向该数组的指针变量。对一维数组的引用,既可以用传统的数组元素的下标
法,也可使用指针的表示方法。
int a[10] , *ptr; /* 定义数组与指针变量 */
做赋值操作:ptr=a; 或 ptr=&a[0];
则ptr就得到了数组的首址。其中, a是数组的首地址, &a[0]是数组元素 a[0]的地址,由于
a [ 0 ]的地址就是数组的首地址,所以,两条赋值操作效果完全相同。指针变量 p t r就是指向数
组a的指针变量。
若ptr指向了一维数组,现在看一下 C规定指针对数组的表示方法:
1) ptr+n与a + n表示数组元素 a [ n ]的地址,即&a[n] 。对整个 a数组来说,共有 1 0个元素, n
的取值为 0~9,则数组元素的地址就可以表示为 p t r + 0~p t r + 9或a + 0~a + 9,与&a[0] ~& a [ 9 ]
保持一致。
2) 知道了数组元素的地址表示方法, * ( p t r + n )和* ( a + n)就表示为数组的各元素即等效于
a[n]。
3) 指向数组的指针变量也可用数组的下标形式表示为 ptr[n],其效果相当于 *(ptr+n)。
[例6-5] /*以下标法输入输出数组各元素。
下面从键盘输入 10个数,以数组的不同引用形式输出数组各元素的值。
# include <stdio.h>
main()
{
int n,a[10],*ptr=a;
for(n=0;n<=9;n++)
scanf("%d",&a[n]);
printf("1------output! \n");
for(n=0;n<=9;n++)
printf("%4d",a[n]);
printf("\n");
}
运行程序:
RUN ↵
↵
1 2 3 4 5 6 7 8 9 0
1------output!
1 2 3 4 5 6 7 8 9 0
[例6-6] 采用指针变量表示的地址法输入输出数组各元素。
# include<stdio.h>
main()
{
int n,a[10],*ptr=a; /* 定义时对指针变量初始化*/
98 C语言程序设计
下载
for(n=0;n<=9;n++)
scanf("%d",ptr+n);
printf("2------output! \n");
for(n=0;n<=9;n++)
printf("%4d",*(ptr+n));
printf("\n");
}
运行程序:
RUN ↵
↵
1 2 3 4 5 6 7 8 9 0
2------output!
1 2 3 4 5 6 7 8 9 0
[例6-7] 采用数组名表示的地址法输入输出数组各元素。
main()
{
int n,a[10],*ptr=a;
for(n=0;n<=9;n++)
scanf("%d",a+n);
printf("3------output! \n");
for(n=0;n<=9;n++)
printf("%4d",*(a+n));
printf("\n");
}
运行程序:
RUN ↵
↵
1 2 3 4 5 6 7 8 9 0
3------output!
1 2 3 4 5 6 7 8 9 0
[例6-8] 用指针表示的下标法输入输出数组各元素。
main()
{
int n,a[10],*ptr=a;
for(n=0;n<=9;n++)
scanf("%d",&ptr[n]);
printf("4------output! \n");
for(n=0;n<=9;n++)
printf("%4d",ptr[n]);
printf("\n");
}
运行程序:
RUN ↵
↵
1 2 3 4 5 6 7 8 9 0
4----output!
下载
第6章 指 针 99
1 2 3 4 5 6 7 8 9 0
[例6-9] 利用指针法输入输出数组各元素。
main()
{
int n,a[10],*ptr=a;
for(n=0;n<=9;n++)
scanf("%d",ptr++);
printf("5------output! \n");
ptr=a; /*指针变量重新指向数组首址 */
for(n=0;n<=9;n++)
printf("%4d",*ptr++);
printf("\n");
}
运行程序:
RUN ↵
↵
1 2 3 4 5 6 7 8 9 0
5-----output!
1 2 3 4 5 6 7 8 9 0
ptr
程序与例6-9相比,只少了赋值语句 ptr=a;程序的运行结果还相同吗?
6.4.2 指针与二维数组
定义一个二维数组:
100 C语言程序设计
下载
int a[3][4];
图6-7 二维数组在内存中的存放
图6-8 指针与二维数组的关系
我们定义的二维数组其元素类型为整型,每个元素在内存占两个字节,若假定二维数组
从1000单元开始存放,则以按行存放的原则,数组元素在内存的存放地址为 1000~1022。
用地址法来表示数组各元素的地址。对元素 a [ 1 ] [ 2 ],& a [ 1 ] [ 2 ]是其地址, a [ 1 ] + 2也是其地
址。分析 a [ 1 ] + 1与a [ 1 ] + 2的地址关系,它们地址的差并非整数 1,而是一个数组元素的所占位
置2,原因是每个数组元素占两个字节。
对0行首地址与 1行首地址 a与a + 1来说,地址的差同样也并非整数 1,是一行,四个元素占
的字节数8。
由于数组元素在内存的连续存放。给指向整型变量的指针传递数组的首地址,则该指针
指向二维数组。
int *ptr, a[3][4];
下载
第6章 指 针 101
若赋值: ptr=a;则用ptr++ 就能访问数组的各元素。
[例6-10] 用地址法输入输出二维数组各元素。
# include <stdio.h>
main()
{
int a[3][4];
int i,j;
for(i=0;i<3;i++)
for(j=0;j<4;j++)
scanf("%d",a[i]+j); /*地址法*/
for(i=0;i<3;i++)
{
for(j=0;j<4;j++)
是地址法所表示的数组元素*/
printf("%4d",*(a[i]+j)); /* *(a[i]+j)
printf("\n");
}
}
运行程序:
RUN ↵
1 2 3 4 5 6 7 8 9 10 11 ↵
12
1 2 3 4
5 6 7 8
9 10 11 12
[例6-11] 用指针法输入输出二维数组各元素。
# include<stdio.h>
main()
{
int a[3][4],*ptr;
int i,j;
ptr=a[0];
for(i=0;i<3;i++)
for(j=0;j<4;j++)
scanf("%d",ptr++); /* 指针的表示方法 */
ptr=a[0];
for(i=0;i<3;i++)
{
for(j=0;j<4;j++)
printf("%4d",*ptr++);
printf("\n");
}
}
运行程序:
RUN ↵
1 2 3 4 5 6 7 8 9 10 11 ↵
12
1 2 3 4
102 C语言程序设计
下载
5 6 7 8
9 10 11 12
对指针法而言,程序可以把二维数组看作展开的一维数组:
main()
{
int a[3][4],*ptr;
int i,j;
ptr=a[0];
for(i=0;i<3;i++)
for(j=0;j<4;j++)
scanf("%d",ptr++); /* 指针的表示方法*/
ptr=a[0];
for(i=0;i<12 ;i++)
printf("%4d",*ptr++);
printf("\n");
}
运行程序:
RUN ↵
1 2 3 4 5 6 7 8 9 10 11 ↵
12
1 2 3 4 5 6 7 8 9 10 11 12
6.4.3 数组指针作函数的参数
学习了指向一维和二维数组指针变量的定义和正确引用后,我们现在学习用指针变量作
函数的参数。
[例6-12] 调用子程序,实现求解一维数组中的最大元素。
我们首先假设一维数组中下标为 0的元素是最大和用指针变量指向该元素。后续元素与该
元素一一比较,若找到更大的元素,就替换。子程序的形式参数为一维数组,实际参数是指
向一维数组的指针。
# include <stdio.h>
main()
{
int sub_max(); /* 函数声明*/
int n,a[10],*ptr=a; /* 定义变量,并使指针指向数组 */
int max;
for(n=0;n<=i-1;n++) /* 输入数据*/
scanf("%d",&a[n]);
max=sub_max(ptr,10); /* 函数调用,其实参是指针*/
printf("max=%d\n",max);
}
int sub_max(b,i) /* 函数定义,其形参为数组*/
int b[],i;
{
int temp,j;
temp=b[0];
下载
第6章 指 针 103
for(j=1;j<=9;j++)
if(temp<b[j]) temp=b[j];
return temp;
}
[例6-14] 上述程序的子程序中,数组元素还可以用指针表示。
# include <stdio.h>
main()
104 C语言程序设计
下载
{
int sub_max();
int n,a[10],*ptr=a;
int max;
for(n=0;n<=9;n++)
scanf("%d",&a[n]);
max=sub_max(ptr,10);
printf("max=%d\n",max);
}
int sub_max(b,i)/* 子程序定义*/
int *b,i;
{
int temp,j;
temp=*b++;
for(j=1;j<=i-1;j++)
if(temp<*b) temp=*b++;
return temp;
}
主程序 主程序
子程序
子程序
a a
a[0] b[0] a[0] b
ptr ptr
a[8] a[8]
b[8]
a[9] a[9]
b[9]
[例6-15] 用指向数组的指针变量实现一维数组的由小到大的冒泡排序。编写三个函数用
于输入数据、数据排序、数据输出。
在第5章的例题中,我们介绍过选择法排序及算法,此例再介绍冒泡排序算法。为了将一
组n个无序的数整理成由小到大的顺序,将其放入一维数组 a[0]、a[1]...a[n-1]。冒泡算法如下:
(开序)
① 相邻的数组元素依次进行两两比较,即 a [ 0 ]与a [ 1 ]比、a [ 1 ]与a [ 2 ]比. . . a [ n - 2 ]与a [ n - 1 ]比,
通过交换保证数组的相邻两个元素前者小,后者大。此次完全的两两比较,能免实现 a [ n - 1 ]成
为数组中最大。
② 余下n - 1个元素,按照上述原则进行完全两两比较,使 a [ n - 2 ]成为余下 n - 1个元素中最
大。
③ 进行共计n-1趟完全的两两比较,使全部数据整理有序。
下面给出一趟排序的处理过程:
原始数据 3 8 2 5
第一次相邻元素比: 3 8 2 5
第二次相邻元素比: 3 2 8 5
第三次相邻元素比: 3 2 5 8
4个元素进行 3次两两比较,得到一个最大元素。若相邻元素表示为 a [ j ]和a [ j + 1 ],用指针
变量P指向数组,则相邻元素表示为 *(P+j)和*(P+j+1)程序实现如下:
# include<stdio.h>
#define N 10
main()
{
void input(); /* 函数声明*/
106 C语言程序设计
下载
void sort();
void output();
int a[N],*p; /* 定义一维数组和指针变量*/
input(a,N); /*数据输入函数调用,实参a是数组名*/
p=a; /* 指针变量指向数组的首地址 */
sort(p,N); /* 排序,实参p是指针变量*/
output(p,N); /*输出,实参p是指针变量*/
}
void input(arr,n) /* 无需返回值的输入数据函数定义 ,形参arr 是数组*/
int arr[],n;
{
int i;
printf("input data:\n");
for(i=0;i<n;i++) /* 采用传统的下标法 */
scanf("%d",&arr[i]);
}
运行程序:
RUN ↵
3 5 7 9 3 23 43 2 1 ↵10
1 2 3 3 5 7 9 10 23 43
由于C程序的函数调用是采用传值调用,即实际参数与形式参数相结合时,实参将值传给
形式参数,所以当我们利用函数来处理数组时,如果需要对数组在子程序中修改,只能传递
数组的地址,进行传地址的调用,在内存相同的地址区间进行数据的修改。在实际的应用中,
如果需要利用子程序对数组进行处理,函数的调用利用指向数组(一维或多维)的指针作参
数,无论是实参还是形参共有下面四种情况:
下载
第6章 指 针 107
实 参 形 参
1 数组名 数组名
2 数组名 指针变量
3 指针变量 数组名
4 指针变量 指针变量
在函数的调用时,实参与形参的结合要注意所传递的地址具体指向什么对象,是数组的
首址,还是数组元素的地址,这一点很重要。
[例6-16] 用指向二维数组的指针作函数的参数,实现对二维数组的按行相加。
# include <stdio.h>
#define M 3
#define N 4
main()
{
float a[M][N];
float score1,score2,score3, *pa=a[0];/* 指针变量pa指向二维数组*/
/* score1,score2,score3 分别记录三行的数据相加*/
int i,j;
void fun();
for(i=0;i<M;i++)
for(j=0;j<N; j++) /* 二维数组的数据输入 */
scanf("%f",&a[i][j]);
fun(pa,&score1,&score2,&score3);
/* 函数调用,不仅传递数组首地址,还要传递变量的地址 */
printf("%.2f,%.2f,%.2f\n",score1,score2,score3);
}
void fun(b,p1,p2,p3)
float b[ ][N],*p1,*p2,*p3;
{int i,j;
*p1=*p2=*p3=0;
for(i=0;i<M;i++)
for(j=0;j<N;j++)
{
if(i==0) *p1=*p1+b[i][j]; /*第0行的数据相加*/
if(i==1) *p2=*p2+b[i][j]; /* 第1行的数据相加*/
if(i==2) *p3=*p3+b[i][j]; /* 第2行的数据相加*/
}
}
[例6-17] 求解二维数组中的最大值及该值在二维数组中的位置。
108 C语言程序设计
下载
我们知道,二维数组在内存中是按行存放,假定我们定义二维数组和指针如下:
int a[3][4],*p=a[0];
P[0] p[1] p[2] p[3] p[4] p[5] p[6] p[7] p[8] p[9] p[10] p[11]
图6-11 例6-17中二维数组在内存中的存放
从上述存放情况来看,若把二维数组的首地址传递给指针 p,则映射过程如图 6 - 11
所示。我们只要找到用 p所表示的一维数组中最大的元素及下标,就可转换为在二维数组中的
行列数。
# include<stdio.h>
main()
{
int a[3][4],*ptr,i,j,max,maxi,maxj;
/*max 是数组的最大, maxi 是最大元素所在行, maxj 是最大元素所在列 */
for(i=0;i<3;i++)
for(j=0;j<4;j++)
scanf("%d",&a[i][j]);
ptr=a[0]; /* 将二维数组的首地址传递给指针变量 */
max_arr(ptr,&max,&maxi,12);
maxj=maxi%4; /* 每行有四个元素,求该元素所在列 */
maxi=maxi/4; /* 求该元素所在行*/
printf("max=%d,maxi=%d,maxj=%d",max,maxi,maxj);
}
int max_arr(b,p1,p2,n)
int *b,*p1,*p2,n;
/*b 指向二维数组的指针, p1指向最大值,p2 指向最大值在一维数组中的位置, */
/*n 是数组的大小*/
{
int i;
*p1=b[0]; *p1=0;
for(i=1;i<n;i++)/* 找最大*/
if (b[i]>*p1) {*p1=b[i]; *p2=i;}
}
运行程序:
RUN ↵
4 7 8 9↵
3 7 9 3↵
1 5 2 6↵
max=9,maxi=0,maxj=3
6.4.4 指针与字符数组
在前面的课程中,我们用过了字符数组,即通过数组名来表示字符串,数组名就是数组
的首地址,是字符串的起始地址。下面的例子用于简单字符串的输入和输出。
下载
第6章 指 针 109
#include <stdio.h>
main()
{
char str[20];
gets(str);
printf("%s\n",str);
}
RUN ↵
good morning!↵
good morning!↵
现在,我们将字符数组的名赋予一个指向字符类型的指针变量,让字符类型指针指向字
符串在内存的首地址,对字符串的表示就可以用指针实现。其定义的方法为: char str[20] ,
*P=str;这样一来,字符串 str就可以用指针变量 P来表示了。
#include <stdio.h>
main()
{
char str[20],*p=str ; /* p=str则表示将字符数组的首地址传递给指针变量 p */
gets(str);
printf("%s\n",p);
}
RUN ↵
good morning!↵
good morning!↵
需要说明的是,字符数组与字符串是有区别的,字符串是字符数组的一种特殊形式,存
储时以“ \ 0”结束,所以,存放字符串的字符数组其长度应比字符串大 1。对于存放字符的字
符数组,若未加“ \0”结束标志,只能按逐个字符输入输出。
[例6-18] 字符数组的正确使用方法。
# include<stdio.h>
main()
{
char str[10],*p=str;
int i;
scanf("%s",str); /* 输入的字符串长度超过 10*/
for( i=0;i<10;i++)
printf("%c",*p++); /*正确输出*/
printf("\n");
p=str;
printf("%s",p); /* 字符数组无'\0' 标志,输出出错*/
puts(str); /* 字符数组无'\0' 标志,输出出错*/
}
对上述程序中字符数组以字符串形式输出,若无“ \0”标志,则找不到结束标志,输出出
错。
[例6-19] 用指向字符串的指针变量处理两个字符串的复制。
110 C语言程序设计
下载
字符串的复制要注意的是:若将串 1复制到串2,一定要保证串 2的长度大于或等于串 1。
#include<stdio.h>
main()
{
char str1[30],str2[20],*ptr1=str1,*ptr2=str2;
printf("input str1:");
gets(str1); /* 输入str1*/
printf("input str2:");
gets(str2); /* 输入str2*/
printf("str1------------str2\n");
printf("%s.......%s\n",ptr1,ptr2);
while(*ptr2) *ptr1++=*ptr2++; /* 字符串复制*/
*ptr1='\0'; /* 写入串的结束标志 */
printf("str1------------str2\n");
printf("%s.......%s\n",str1,str2);
}
RUN ↵
input str1: I love China! ↵
input str2: I love Chengdu! ↵
str1--------------------str2
I love China! ....... I love Chengdu!
str1------------------------------------------str2
I love China! I love Chengdu! ...... I love Chengdu!.
6.5 指针的地址分配
我们可以定义指针变量指向任何类型的变量。在上述的处理过程中,指针变量指向的变
量通过传递变量的地址来实现。指针变量的取值是内存的地址,这个地址应当是安全的,不
可以是随意的,否则,写入内存单元的值将会使得已存放的数据或程序丢失。应使用编译系
统提供的标准函数来实现地址分配。
A N S I标准建议设置了两个最常用的动态分配内存的函数 malloc() 和f r e e ( ),并包含在
stdlib.h中,但有些 C编译却使用 malloc.h包含。使用时请参照具体的 C编译版本。
我们这里所指的动态内存分配其含义是指:当定义指针变量时,其变量的取值是随机的,
可能指向内存的任一单元。若指针的指向是不安全的内存地址,在该地址空间上的数据交换
就会产生意料不到的效果。为此,在程序的执行过程中,要保证指针操作的安全性,就要为
指针变量分配安全地址。在程序执行时为指针变量所做的地址分配就称之为动态内存分配。
当无需指针变量操作时,可以将其所分配的内存归还系统,此过程我们称之为内存单元的释
放。
malloc( )用以向编译系统申请分配内存; free( )用以在使用完毕释放掉所占内存。
[例6-21] 两个字符串的交换。
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
main()
{
char *ptr1,*ptr2,*temp;
ptr1=malloc(30); /* 动态为指针变量分配长度为30 字节的存储空间 */
ptr2=malloc(20);
temp=malloc(30);
printf("input str1:");
gets(ptr1); /* 输入字符串*/
printf("input str2:");
gets(ptr2);
printf("str1------------str2\n");
printf("%s.......%s\n",ptr1,ptr2);
strcpy(temp,ptr1);/* 串复制*/
112 C语言程序设计
下载
strcpy(ptr1,ptr2);
strcpy(ptr2,temp);
printf("str1------------str2\n");
printf("%s.......%s\n",ptr1,ptr2);
free(ptr1);
free(ptr2);
}
为指针变量分配的存储空间长度取决于存放字符的多少。在上述的程序中,两个串的交
换可以通过标准函数 strcpy() 来完成,也可以通过串指针交换指向完成,用 t e m p = p t r 1;
p t r 1 = p t r 2;p t r 2 = t e m p;三条赋值语句实现。但是,利用指针交换指向,其物理意义与串通过
函数进行的复制完全不同。前者是存放串地址的指针变量数据交换,后者是串在内存物理空
间的数据交换。指针变量用完后,将指针变量所占的存储空间释放。
运行程序: run↵
input str1: China↵
input str2: Chengdu ↵
str1------------str2
China----------Chengdu
str1------------str2
Chengdu----- China
6.6 指针数组
前面介绍了指向不同类型变量的指针的定义和使用,我们可以让指针指向某类变量,并
替代该变量在程序中使用;我们也可以让指针指向一维、二维数组或字符数组,来替代这些
数组在程序中使用,给我们在编程时带来许多方便。
下面我们定义一种特殊的数组,这类数组存放的全部是指针,分别用于指向某类的变量,
以替代这些变量在程序中的使用,增加灵活性。指针数组定义形式:
类型标识 *数组名[数组长度]
例如: char *str[4];
由于[ ] 比*优先权高,所以首先是数组形式 str[4 ],然后才是与“ *”的结合。这样一来指
针数组包含 4个指针 s t r [ 0 ]、s t r [ 1 ]、s t r [ 2 ]、s t r [ 3 ],各自指向字符类型的变量。例如: int *
ptr[5];
该指针数组包含 5个指针 p t r [ 0 ]、p t r [ 1 ]、p t r [ 2 ]、p t r [ 3 ]、p t r [ 4 ],各自指向整型类型的变
量。
[例6-22] 针对指针数组的应用,我们分别用指针数组的各指针指向字符串数组、指向一
维整型数组、指向二维整型数组。
#include <stdlib.h>
#include <stdio.h>
main()
{
char *ptr1[4]={"china","chengdu","sichuang","chongqin"};
/* 指针数组ptr1 的4个指针分别依此指向4个字符串*/
int i,*ptr2[3],a[3]={1,2,3},b[3][2]={1,2,3,4,5,6};
下载
第6章 指 针 113
for (i=0;i<4;i++)
printf("\n%s",ptr1[i]); /* 依此输出 ptr1 数组4个指针指向的 4个字符串*/
printf("\n");
for(i=0;i<3;i++)
ptr2[i]=&a[i]; /*将整型一维数组 a的3个元素的地址传递给指针数组 ptr2*/
for(i=0;i<3;i++)/* 依此输出ptr2 所指向的3个整型变量的值 */
printf("%4d",*ptr2[i]);
printf("\n");
for(i=0;i<3;i++)
ptr2[i]=b[i]; /*传递二维数组b的每行首地址给指针数组的 4 个指针*/
for(i=0;i<3;i++)/* 按行输出*/
printf("%4d%4d\n",*ptr2[i],*ptr2[i]+1);
}
程序中指针数组与所指对象的关系如图 6-12所示。
图6-12 例6-22程序中指针数组与所指对象的关系
china
chengdu
sichuang
chongqin
1 2 3
1 2
2 4
5 6
在处理二维字符数组时,我们可以把二维字符数组看成是由多个一维字符数组构成,也
就是说看成是多个字符串构成的二维字符数组,或称为字符串数组。
指针数组对于解决这类问题(当然也可以解决其它问题)提供了更加灵活方便的操作。
有一点需要说明,若定义一个指针数组后,指针数组各元素的取值(即地址)要注意安全性。
114 C语言程序设计
下载
如定义指针数组:
char *ptr[3];
我们说该数组包含三个指针,但指针的指向是不确定的,指针现在可能指向内存的任一
地址。假定现在作语句: scanf("%s", ptr[ i ] ), 则输入的字符串在内存的存放其地址由 ptr[ i ]
决定。除非给指针数组元素赋值安全的地址。
[例6-23] 定义字符指针数组,包含 5个数组元素。同时再定义一个二维字符数组其数组
大小为5*10,即5行10列,可存放5个字符串。若将各字符串的首地址传递给指针数组各元素,
那么指针数组就成为名副其实的字符串数组。下面对各字符串进行按字典排序。
在字符串的处理函数中, s t r c m p ( s t r 1 , s t r 2 )函数就可以对两个字符串进行比较,函数的返
回值> 0、= 0、< 0分别表示串 s t r 1大于s t r 2、s t r 1等于s t r 2、s t r 1小于s t r 2。再利用 s t r c p y ( )函数实
现两个串的复制。下面选用冒泡排序法。
#include <stdlib.h>
#include <string.h>
#include<stdio.h>
main()
{
char *ptr1[4],str[4][20],temp[20];
/* 定义指针数组、二维字符数组、用于交换的一维字符数组 */
int i,j;
for (i=0;i<4;i++)
gets(str[i]); /* 输入4个字符串*/
printf("\n");
for(i=0;i<4;i++)
ptr1[i]=str[i]; /* 将二维字符数组各行的首地址传递给指针数组的各指针 */
printf("original string:\n");
for(i=0;i<4;i++) /* 按行输出原始各字符串 */
printf("%s\n",ptr1[i]);
printf("ordinal string:\n");
for(i=0;i<3;i++) /* 冒泡排序*/
for(j=0;j<4-i-1;j++)
if(strcmp(ptr1[j],ptr1[j+1])>0)
{ strcpy(temp,ptr1[j]);
strcpy(ptr1[j],ptr1[j+1]);
strcpy(ptr1[j+1],temp);
}
for( i=0;i<4;i++) /* 输出排序后的字符串 */
printf("%s\n" , ptr1[i]);
}
运行程序:
RUN ↵
jkjkdkddfs ↵
fhfgkjkfgkf ↵
hkfgkgfkklg ↵
jjkdjdk ↵
original string:
下载
第6章 指 针 115
jkjkdkddfs
fhfgkjkfgkf
hkfgkgfkklg
jjkdjdk
ordinal string:
fhfgkjkfgkf
hkfgkgfkklg
jjkdjdk
jkjkdkddfs
程序中一定要注意指针的正确使用。一旦将二维字符数组的各行首地址传递给指针数组
的各指针,则相当于给指针分配了安全可操作的地址,地址空间大小由二维字符数组来决定。
当然也可由编译系统为指针分配地址用于字符串的存放。
[例6-24] 利用 malloc()函数为指针分配存储空间,实现字符串的排序。
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
main()
{
char *ptr1[4],*temp;
int i,j;
for (i=0;i<4;i++)
{
ptr1[i]=malloc(20); /* 为指针数组各指针分配 20字节的存储空间 */
gets(ptr1[i]);
}
printf("\n");
printf("original string:\n");
for(i=0;i<4;i++)
printf("%s\n",ptr1[i]);
printf("ordinal string:\n");
for(i=0;i<3;i++)
for(j=0;j<4-i-1;j++)
if(strcmp(ptr1[j],ptr1[j+1])>0)
{
temp=ptr1[j]; /* 利用指向字符串的指针,进行指针地址的交换 */
ptr1[j]=ptr1[j+1];
ptr1[j+1]=temp;
}
for( i=0;i<4;i++) /* 字符串输出*/
printf("%s\n" , ptr1[i]);
}
运行程序,其结果与上述例 6-23完全相同。
[例6-25] 对已排好序的字符指针数组进行指定字符串的查找。字符串按字典顺序排列,
查找算法采用二分法,或称为折半查找。
折半查找算法描述:
116 C语言程序设计
下载
1. 设按开序(或降序)输入 n 个字符串到一个指针数组。
2. 设low 指向指针数组的低端, high 指向指针数组的高端, mid=(low+high)/2
3. 测试mid所指的字符串,是否为要找的字符串。
4. 若按字典顺序, m i d所指的字符串大于要查找的串,表示被查字符串在 l o w和m i d之间,
否则,表示被查字符串在 mid和high之间。
5. 修改low式high的值,重新计算 mid,继续寻找。
#include <stdlib.h>
#include <alloc.h>
#include <string.h>
#include <stdio.h>
main()
{
char *binary(); /* 函数声明*/
char *ptr1[5],*temp;
int i,j;
for (i=0;i<5;i++)
{
ptr1[i]=malloc(20); /* 按字典顺序输入字符串 */
gets(ptr1[i]);
}
printf("\n");
printf("original string:\n");
for(i=0;i<5;i++)
printf("%s\n",ptr1[i]);
printf("input search string:\n");
temp=malloc(20);
gets(temp); 输入被查找字符串 */
/*
i=5;
temp=binary(ptr1,temp,i ); /* 调用查找函数*/
运行程序:
RUN ↵
chengdu ↵
chongqin ↵
beijing ↵
tianjin ↵
shanghai ↵
original string:
chengdu
chongqin
beijing
tianjin
shanghai
input search string:
beijing ↵
succesful----- beijing
[例6-26] 在一个已排好序的字符串数组中,插入一个键盘输入的字符串,使其继续保持
有序。
在上述程序查找成功的基础上,我们将该字符串插入到字符数组中。插入的位置可以是
数组头、中间或数组尾。查找的算法采用折半算法,找到插入位置后,将字符串插入。
#include <stdlib.h>
#include <alloc.h>
#include <string.h>
#include <stdio.h>
main()
{
int binary(); /*查找函数声明 */
void insert(); /* 插入函数声明 */
char *temp,*ptr1[6];
int i,j;
for (i=0;i<5;i++)
{
ptr1[i]=malloc(20); /* 为指针分配地址后 */
gets(ptr1[i]); /*输入字符串*/
}
ptr1[5]=malloc(20);
printf("\n");
printf("original string:\n");
for(i=0;i<5;i++) /*输出指针数组各字符串 */
printf("%s\n",ptr1[i]);
printf("input search string:\n");
temp=malloc(20);
gets(temp); /*输入被插字符串 */
118 C语言程序设计
下载
i=binary(ptr1,temp,5 ); /* 寻找插入位置i*/
printf("i=%d\n",i);
insert(ptr1,temp,5,i); /* 在插入位置 i处插入字符串 */
printf("output strings:\n");
for(i=0;i<6;i++) /* 输出指针数组的全部字符串 */
printf("%s\n",ptr1[i]);
return;
}
int j;
for (j=n;j>i;j--) /* 将插入位置之后的字符串后移 */
strcpy(ptr[j],ptr[j-1]);
strcpy(ptr[i],str); 将被插字符串按字典顺序插入字符串数组 */
}
( 2 0 )保证插入字符串后,也具有安全的存储空 p j(实型变量)
&j 5.3
间,字符串的长度以串中最长的为基准向系
p ch(字符变量)
统申请存储空间,以保证在串的移动中有足
&ch ‘a’
够的存储空间。
p1(双重指针) p2(指针变量) x(整型变量)
6.7 指向指针的指针 &p2 &x 4
图6-13 双重指针
一个指针变量可以指向整型变量、实型
下载
第6章 指 针 119
变量、字符类型变量,当然也可以指向指针类型变量。当这种指针变量用于指向指针类型变
量时,我们称之为指向指针的指针变量,这话可能会感到有些绕口,但你想到一个指针变量
的地址就是指向该变量的指针时;这种双重指针的含义就容易理解了。下面用一些图来描述
这种双重指针,见图 6-13。
在图中,整型变量 i的地址是 & i,将其传递给指针变量 p,则 p指向 i;实型变量 j的地址
是&j,将其传递给指针变量 p,则p指向j; 字符型变量 ch的地址是&ch,将其传递给指针变量 p,
则p指向ch; 整型变量 x的地址是 &x,将其传递给指针变量 p2,则p2指向x,p2是指针变量,同
时,将p 2的地址 & p 2传递给p 1,则p 1指向p 2。这里的 p 1就是我们谈到的指向指针变量的指针
变量,即指针的指针。
指向指针的指针变量定义如下:
类型标识符 **指针变量名
例如: float **ptr;
其含义为定义一个指针变量 p t r,它指向另一个指针变量(该指针变量又指向一个实型变
量)。由于指针运算符“ *”是自右至左结合,所以上述定义相当于:
float *(*ptr);
下面看一下指向指针变量的指针变量怎样正确引用。
[例6-27] 用指向指针的指针变量访问一维和二维数组。
#include <stdio.h>
#include <stdlib.h>
main()
{
是指向指针的指针变量 */
int a[10],b[3][4],*p1,*p2,**p3,i,j; /*p3
for(i=0;i<10;i++)
scanf("%d",&a[i]); /* 一维数组的输入 */
for (i=0;i<3;i++)
for(j=0;j<4;j++)
scanf("%d",&b[i][j]); /* 二维数组输入 */
for (p1=a,p3=&p1,i=0;i<10;i++)
printf("%4d",*(*p3+i)); /* 用指向指针的指针变量输出一维数组 */
printf("\n");
for (p1=a;p1-a<10;p1++) /* 用指向指针的指针变量输出一维数组 */
{
p3=&p1;
printf("%4d",**p3);
}
printf("\n");
for(i=0;i<3;i++) /* 用指向指针的指针变量输出二维数组 */
{
p2=b[i];
p3=&p2;
for (j=0;j<4;j++)
printf("%4d",*(*p3+j));
printf("\n");
}
for(i=0;i<3;i++) /* 用指向指针的指针变量输出二维数组 */
120 C语言程序设计
下载
{
p2=b[i];
for(p2=b[i];p2-b[i]<4;p2++)
{
p3=&p2;
printf("%4d",**p3);
}
printf("\n");
}
}
图6-14 例6-27程序的存储示意图
运行程序:
RUN ↵
c language
fox
computer
home page
----------
c language
fox
computer
home page
程序中需要注意的是,执行 c p p = c p 是将指针数组的首地址传递给双重指针,所以 *
(cpp+i)表示第i行的首地址,而不是 cpp+i。在程序设计时一定分清。
6.8 main函数的参数
C程序最大的特点就是所有的程序都是用函数来装配的。 m a i n ( )称之为主函数,是所有程
序运行的入口。其余函数分为有参或无参两种,均由 main()函数或其它一般函数调用,若调用
的是有参函数,则参数在调用时传递。
main()
{
...
y1=f1(x1,x2);
...
}
f1(int a,int b)
122 C语言程序设计
下载
{
....
Y2=f2(x3,x4);
....
}
f2( int m,int n)
{
....
.....
}
从函数参数的形式上看,包含一个整型和一个指针数组。当一个 C的源程序经过编译、链
接后,会生成扩展名为 . E X E的可执行文件,这是可以在操作系统下直接运行的文件,换句话
说,就是由系统来启动运行的。对 main()函数既然不能由其它函数调用和传递参数,就只能由
系统在启动运行时传递参数了。
在操作系统环境下,一条完整的运行命令应包括两部分:命令与相应的参数。其格式为:
命令 参数1 参数2 . . . .参数n↵
此格式也称为命令行。命令行中的命令就是可执行文件的文件名,其后所跟参数需用空
格分隔,并为对命令的进一步补充,也即是传递给 main()函数的参数。
命令行与main()函数的参数存在如下的关系:
设命令行为: program str1 str2 str3 str4 str5↵
其中 p r o g r a m 为文 件 名, 也 就是 一 个由
p r o g r a m . c 经编译、链接后生成的可执行文件
p r o g r a m . e x e,其后各跟 5个参数。对 main( ) 函数
来说,它的参数 a rg c记录了命令行中命令与参数的
个数,共 6个,指针数组的大小由参数 a rg c的值决
定,即为 char *arg v [ 6 ],指针数组的取值情况如图
6-15所示。
图6-15 指针数组的取值情况
数组的各指针分别指向一个字符串。应当引起
注意的是接收到的指针数组的各指针是从命令行的开始接收的,首先接收到的是命令,其后
才是参数。
下面用实例来说明带参数的 main()函数的正确使用。
下载
第6章 指 针 123
[例6-29] 利用图形库函数绘制一个变化的环。它是把一个半径为 R 1的圆周分成 n份,然
后以每个等分点为圆心,以 Rs为半径画 n个圆(关于作图的详细理论本教材第 9章第1节作了专
门介绍,这里只作简单分析)。利用 m a i n ( )函数的带参数形式,我们可以从键盘以命令行的方
式输入R1和Rs及屏幕的背景色。
#include <graphics.h> /*包含图形库函数的头文件 */
#include <math.h>
#define pi 4.1415926
main(argc,argv)
int argc;char *argv[]; /* 定义带参数的main()*/
{
int x,y,r1,rs,color;
double a;
int gdriver=DETECT,gmode;
启动图形工作方式 */
initgraph(&gdriver,&gmode,"..\\bgi ");/*
r1=atoi(argv[1]); /*计算基础圆半径 */
rs=atoi(argv[2]); /* 计算同心圆半径 */
color=atoi(argv[3]); /* 背景色*/
cleardevice(); /*清除图形屏幕 */
setbkcolor(color); /* 设置背景色*/
setcolor(4); /*设置图形显示颜色 */
for(a=0; a<=2*pi;a+=pi/18) /* 绘制同心圆*/
{
x=r1*cos(a)+320;
y=r1*sin(a)+240;
circle(x,y,rs); /* 以圆心坐标为 x、y,半径为rs画圆*/
}
getch(); /* 等待按键继续 */
closegraph(); /*关闭图形工作方式 */
}
色彩通过atoi()函数转换为整型。
通过带参数的main()函数,我们可以为自己的程序设置口令,在运行程序的命令行中给出
所需的口令,正确则继续,否则退出。程序图形输出如图 6-17所示。
[例6-30] 将上述程序作修改,在程序的入口处添置密码,若给定密码正确,则显示图
形。
#include <graphics.h>
124 C语言程序设计
下载
#include <math.h>
#define pi 4.1415926
main(argc,argv)
int argc;char *argv[];
{
int x,y,r1,rs,color;
double a;
int gdriver=DETECT,gmode;
if (strcmp(argv[1],"pass")!=0) 设置口令的比较
/* */
{ printf("password error!\n");
exit(0);
}
initgraph(&gdriver,&gmode,"..\\bgi ");
r1=atoi(argv[2]);
rs=atoi(argv[3]);
color=atoi(argv[4]);
cleardevice();
setbkcolor(color);
setcolor(4);
for(a=0; a<=2*pi;a+=pi/18)
{
x=r1*cos(a)+320;
y=r1*sin(a)+240;
circle(x,y,rs);
}
getch();
closegraph();
}
图6-17 例6-29程序输出的图形
在操作系统的环境下运行程序, 命令行
中增加口令“pass”,命令行方式为:
l6-30 pass 20 40 3↵
指针数组的存储字符串如图 6-18所示。
若给定字符串a rg v [ 1 ]的值是pass,则程序
正确运行,否则程序退出。口令正确的情况
下,显示的图形为图 6-17中的一个。
图6-18 例6-30程序中指针数组的存储字符串
下载
第7章 结构体与共用体
前面的课程我们学习了一些简单数据类型(整型、实型、字符型)的定义和应用,还学
习了数组(一维、二维)的定义和应用,这些数据类型的特点是:当定义某一特定数据类型,
就限定该类型变量的存储特性和取值范围。对简单数据类型来说,既可以定义单个的变量,
也可以定义数组。而数组的全部元素都具有相同的数据类型,或者说是相同数据类型的一个
集合。
在日常生活中,我们常会遇到一些需要填写的登记表,如住宿表、成绩表、通讯地址等。
在这些表中,填写的数据是不能用同一种数据类型描述的,在住宿表中我们通常会登记上姓
名、性别、身份证号码等项目;在通讯地址表中我们会写下姓名、邮编、邮箱地址、电话号
码、E - m a i l等项目。这些表中集合了各种数据,无法用前面学过的任一种数据类型完全描述,
因此C引入一种能集中不同数据类型于一体的数据类型 — 结构体类型。结构体类型的变量可
以拥有不同数据类型的成员,是不同数据类型成员的集合。
7.1 结构体类型变量的定义和引用
在上面描述的各种登记表中,让我们仔细观察一下住宿表、成绩表、通讯地址等。
住宿表由下面的项目构成:
姓 名 性 别 职 业 年 龄 身份证号码
成绩表由下面的项目构成:
通讯地址表由下面的项目构成:
这些登记表用C提供的结构体类型描述如下:
住宿表:
struct accommod
{
char name[20]; /*姓名*/
char sex; /* 性别*/
char job[40]; /* 职业*/
int age; /*年龄*/
long number; /* 身份证号码*/
126 C语言程序设计
下载
};
成绩表:
struct score
{
char grade[20]; /* 班级*/
long number; /* 学号*/
char name[20]; /* 姓名*/
float os; /* 操作系统*/
float datastru; /* 数据结构*/
float compnet; /* 计算机网络*/
};
通讯地址表 :
struct addr
{
char name[20];
char department[30];/* 部门*/
char address[30]; /* 住址*/
long box; /* 邮编*/
long phone; /* 电话号码*/
char email[30]; /*Email*/
};
这一系列对不同登记表的数据结构的描述类型称为结构体类型。由于不同的问题有不同
的数据成员,也就是说有不同描述的结构体类型。我们也可以理解为结构体类型根据所针对
的问题其成员是不同的,可以有任意多的结构体类型描述。
下面给出C对结构体类型的定义形式:
struct 结构体名
{
成员项表列
};
有了结构体类型,我们就可以定义结构体类型变量,以对不同变量的各成员进行引用。
7.1.1 结构体类型变量的定义
结构体类型变量的定义与其它类型的变量的定义是一样的,但由于结构体类型需要针对
问题事先自行定义,所以结构体类型变量的定义形式就增加了灵活性,共计有三种形式,分
别介绍如下:
1) 先定义结构体类型,再定义结构体类型变量:
struct stu /* 定义学生结构体类型 */
{
char name[20]; /* 学生姓名*/
char sex; /* 性别*/
long num; /* 学号*/
float score[3]; /* 三科考试成绩*/
};
/* 定义结构体类型变量 */
struct stu student1,student2;
下载
第7章 结构体与共用体 127
struct stu student3,student4;
用此结构体类型,可以定义更多的该结构体类型变量。
2) 定义结构体类型同时定义结构体类型变量:
struct data
{
int day;
int month;
int year;
} time1,time2;
也可以再定义如下变量:
struct data time3,time4;
用此结构体类型,同样可以定义更多的该结构体类型变量。
3) 直接定义结构体类型变量:
struct
{
char name[20]; /* 学生姓名*/
char sex; /* 性别*/
long num; /*学号*/
float score[3]; /* 三科考试成绩 */
} person1,person2; /* 定义该结构体类型变量 */
该定义方法由于无法记录该结构体类型,所以除直接定义外,不能再定义该结构体类型
变量。
7.1.2 结构体类型变量的引用
学习了怎样定义结构体类型和结构体类型变量,怎样正确地引用该结构体类型变量的成
员呢?C 规定引用的形式为:
<结构体类型变量名 >.<成员名>
若我们定义的结构体类型及变量如下:
struct data
{ time 1
int day; day month year
int month;
timel.day timel.month timel.year
int year;
} time1,time2; time2
7.1.3 结构体类型变量的初始化
由于结构体类型变量汇集了各类不同数据类型的成员,所以结构体类型变量的初始化就
128 C语言程序设计
下载
略显复杂。
结构体类型变量的定义和初始化为:
struct stu /* 定义学生结构体类型 */
{
char name[20]; /* 学生姓名*/
char sex; /* 性别*/
long num; /* 学号*/
float score[3]; /* 三科考试成绩*/
};
struct stu student={"liping",'f',970541,98.5,97.4,95};
上述对结构体类型变量的三种定义形式均可在定义时初始化。结构体类型变量完成初始
化后,即各成员的值分别为: s t u d e n t . n a m e = " l i p i n g "、s t u d e n t . s e x = ' f '、s t u d e n t . n u m = 9 7 0 5 4 1、
s t u d e n t . s c o r e [ 0 ] = 9 8 . 5、s t u d e n t . s c o r e [ 1 ] = 9 7 . 4、s t u d e n t . s c o r e [ 2 ] = 9 5。其存储在内存的情况如图
7-2所示。
图7-2 结构体类型变量在内存中的存储
我们也可以通过 C提供的输入输出函数完成对结构体类型变量成员的输入输出。由于结构
体类型变量成员的数据类型通常是不一样的,所以要将结构体类型变量成员以字符串的形式
输入,利用 C的类型转换函数将其转换为所需类型。类型转换的函数是:
int atoi( char *str);转换str所指向的字符串为整型,其函数的返回值为整型。
double atof(char *str);转换str所指向的字符串为实型,其函数的返回值为双精度的实型。
long atol(char *str);转换str所指向的字符串为长整型,其函数的返回值为长整型。
使用上述函数,要包含头文件 "stdlib.h"。
对上述的结构体类型变量成员输入采用的一般形式:
char temp[20];
gets(student.name); /* 输入姓名*/
student.sex=getchar(); /* 输入性别*/
gets(temp); /* 输入学号*/
student.num=atol(temp); /* 转换为长整型*/
for(i=0;i<3;i++) /* 输入三科成绩*/
{
gets(temp);
student.score[i]=atoi(temp);
}
对该结构体类型变量成员的输出也必须采用各成员独立输出,而不能将结构体类型变量
以整体的形式输入输出。
C允许针对具体问题定义各种各样的结构体类型,甚至是嵌套的结构体类型。
struct data
{
int day;
下载
第7章 结构体与共用体 129
int mouth;
int year;
};
struct stu
{
char name[20];
struct data birthday; 出生年月,嵌套的结构体类型
/* */
long num;
} person;
7.2 结构体数组的定义和引用
单个的结构体类型变量在解决实际问题时作用不大,一般是以结构体类型数组的形式出
现。结构体类型数组的定义形式为:
struct stu /* 定义学生结构体类型*/
{
char name[20]; /* 学生姓名*/
char sex; /* 性别*/
long num; /*学号*/
float score[3]; /* 三科考试成绩 */
};
struct stu stud[20]; 定义结构体类型数组stud
/* ,*/
/* 该数组有20个结构体类型元素 */
其数组元素各成员的引用形式为:
stud[0].name 、stud[0].sex 、stud[0].score[i];
stud[1].name 、stud[1].sex 、stud[1].score[i];
...
...
stud[19].name 、stud[19].sex 、stud[19].score[i];
3 Jiangbo 89 70 76
4 Yangming 90 100 67
题目要求的问题多,采用模块化编程方式,将问题进行分解如下:
1) 结构体类型数组的输入。
2) 求解各学生的三科平均成绩。
3) 按学生的平均成绩排序。
130 C语言程序设计
下载
4) 按表格要求输出。
5) 求解组内学生单科平均成绩并输出。
6) 定义 main()函数,调用各子程序。
第一步,根据具体情况定义结构体类型。
struct stu
{
char name[20]; /*姓名*/
long number; /*学号*/
float score[4]; /* 数组依此存放English 、Mathema 、Physics ,及Average*/
};
由于该结构体类型会提供给每个子程序使用,是共用的,所以将其定义为外部的结构体
类型,放在程序的最前面。
第二步,定义结构体类型数组的输入模块。
void input(arr,n) /* 输入结构体类型数组 arr 的n个元素*/
struct stu arr[];
int n;
{ int i,j;
char temp[30];
for (i=0;i<n;i++)
{
printf("\ninput name,number,English,mathema,physic\n"); /*打印提示信息*/
gets(arr[i].name); /* 输入姓名*/
gets(temp); 输入学号*/
/*
arr[i].number=atol(temp);
for(j=0;j<3;j++)
{
gets(temp); /*输入三科成绩*/
arr[i].score[j]=atoi(temp);
};
}
}
第三步,求解各学生的三科平均成绩。
在结构体类型数组中第 i个元素 a r r [ i ]的成员 s c o r e的前三个元素为已知,第四个 Av e r a g e需
计算得到。
void aver(arr,n)
struct stu arr[];
int n;
{
int i,j;
for(i=0;i<n;i++) /*n 个学生*/
{
arr[i].score[3]=0;
for(j=0;j<3;j++)
arr[i].score[3]=arr[i].score[3]+arr[i].score[j];求和*
/*/
下载
第7章 结构体与共用体 131
arr[i].score[3]=arr[i].score[3] /3; 平均成绩*
/* /
}
}
第四步,按平均成绩排序,排序算法采用冒泡法。
void order(arr,n)
struct stu arr[];
int n;
{ struct stu temp;
int i,j,x,y;
for(i=0;i<n-1;i++)
for(j=0;j<n-1-i;j++)
if (arr[j].score[3]>arr[j+1].score[3])
{ temp=arr[j]; 结构体类型变量不允许以整体输入或输出,但允许相互赋值 */
/*
进行交换*/
arr[j]=arr[j+1]; /*
arr[j+1]=temp;
}
}
第五步,按表格要求输出。
void output(arr,n) /* 以表格形式输出有 n个元素的结构体类型数组各成员 */
int n;
struct stu arr[];
{int i,j;
printf("********************TABLE********************\n"); 打印表头*
/* /
printf("----------------------------------------------------\n");
/* 输出一条水平线*/
printf("|%10s|%8s|%7s|%7s|%7s|%7s|\n","Name","Number","English","Mathema",
"physics","average");
/* 输出效果为:| Name| Number|English|Mathema|Physics|Average|*/
printf("----------------------------------------------------\n");
for (i=0;i<n;i++)
{
printf("|%10s|%8ld|",arr[i].name,arr[i].number);/* 输出姓名、学号 */
for(j=0;j<4;j++)
printf("%7.2f|",arr[i].score[j]);/* 输出三科成绩及三科的平均 */
printf("\n");
printf("---------------------------------------------------\n");
}
}
第六步,求解组内学生单科平均成绩并输出。在输出表格的最后一行,输出单科平均成
绩及总平均。
void out_row(arr,n) /* 对n个元素的结构体类型数组求单项平均 */
int n;
struct stu arr[];
{
/*定义存放单项平均的一维数组 */
float row[4]={0,0,0,0};
int i,j;
132 C语言程序设计
下载
for(i=0;i<4;i++)
{
for(j=0;j<n;j++)
row[i]=row[i]+arr[j].score[i];/* 计算单项总和*/
row[i]=row[i]/n; 计算单项平均*
/* /
}
printf("|%19c|",' '); 按表格形式输出
/* */
for (i=0;i<4;i++)
printf("%7.2f|",row[i]);
printf("\n------------------------------------------\n");
}
第七步,定义main()函数,列出完整的程序清单。
#include <stdlib.h>
#include <stdio.h>
struct stu
{
char name[20];
long number;
float score[4];
};
main()
{
void input(); /* 函数声明*/
void aver();
void order();
void output();
void out_row();
struct stu stud[4]; /* 定义结构体数组*/
float row[3];
input(stud,4); /* 依此调用自定义函数 */
aver(stud,4);
order(stud,4);
output(stud,4);
out_row(stud,4);
}
/****************************/
void input(arr,n)
struct stu arr[];
int n;
{ int i,j;
char temp[30];
for (i=0;i<n;i++)
{
printf("\nInput Name,Number,English,Mathema,Physic\n");
gets(arr[i].name);
gets(temp);
arr[i].number=atol(temp);
下载
第7章 结构体与共用体 133
for(j=0;j<3;j++)
{
gets(temp);
arr[i].score[j]=atoi(temp);
};
}
}
/***********************/
void aver(arr,n)
struct stu arr[];
int n;
{
int i,j;
for(i=0;i<n;i++)
{
arr[i].score[3]=0;
for(j=0;j<3;j++)
arr[i].score[3]=arr[i].score[3]+arr[i].score[j];
arr[i].score[3]=arr[i].score[3] /3;
}
}
/***********************/
void order(arr,n)
struct stu arr[];
int n;
{ struct stu temp;
int i,j,x,y;
for(i=0;i<n-1;i++)
for(j=0;j<n-1-i;j++)
if (arr[j].score[3]>arr[j+1].score[3])
{ temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
/******************/
void output(arr,n)
int n;
struct stu arr[];
{int i,j;
printf("********************TABLE********************\n");
printf("----------------------------------------------------\n");
printf("|%10s|%8s|%7s|%7s|%7s|%7s|\n","Name","Number","English","mathema",
"physics","average");
printf("----------------------------------------------------\n");
for (i=0;i<n;i++)
{
printf("|%10s|%8ld|",arr[i].name,arr[i].number);
134 C语言程序设计
下载
for(j=0;j<4;j++)
printf("%7.2f|",arr[i].score[j]);
printf("\n");
printf("---------------------------------------------------\n");
}
}
/*************************************/
void out_row(arr,n)
int n;
struct stu arr[];
{
float row[4]={0,0,0,0};
int i,j;
for(i=0;i<4;i++)
{
for(j=0;j<n;j++)
row[i]=row[i]+arr[j].score[i];
row[i]=row[i]/n;
}
printf("|%19c|",' ');
for (i=0;i<4;i++)
printf("%7.2f|",row[i]);
printf("\n------------------------------------------\n");
}
运行程序:
RUN ↵
Input Name,Number,English,Mathema,Physic
Liping ↵
1↵
78 ↵
98 ↵
76 ↵
Input Name,Number,English,Mathema,Physic
Wangling ↵
2↵
66 ↵
90 ↵
86 ↵
Input Name,Number,English,Mathema,Physic
Jiangbo ↵
3↵
89 ↵
70 ↵
76 ↵
Input Name,Number,English,Mathema,Physic
Yangming ↵
4↵
90 ↵
100
下载
第7章 结构体与共用体 135
67 ↵
*******************TABLE*********************
-------------------------------------------------------
| Number| Name|English| Mathema| Physics| Average|
-------------------------------------------------------
| Yangming| 4| 90.00| 100.00| 67.00| 85.67|
-------------------------------------------------------
| Liping| 1| 78.00| 98.00| 76.00| 84.00|
-------------------------------------------------------
| Wangling| 2| 66.00| 90.00| 86.00| 80.72|
-------------------------------------------------------
| Jiangbo| 3| 89.00| 70.00| 76.00| 78.33|
-------------------------------------------------------
| | 80.75| 89.50| 76.25| 82.18|
-------------------------------------------------------
程序中要谨慎处理以数组名作函数的参数。由于数组名作为数组的首地址,在形参和实
参结合时,传递给子程序的就是数组的首地址。形参数组的大小最好不定义,以表示与调用
函数的数组保持一致。在定义的结构体内,成员 s c o r e [ 3 ]用于表示计算的平均成绩,也是我们
用于排序的依据。我们无法用数组元素进行相互比较,而只能用数组元素的成员 s c o r e [ 3 ]进行
比较。在需要交换的时候,用数组元素的整体包括姓名、学号、三科成绩及平均成绩进行交
换。在程序 o r d e r()函数中,比较采用: a r r [ j ] . s c o r e [ 3 ] > a r r [ j + 1 ] . s c o r e [ 3 ],而交换则采用:
arr[j] ←
→ arr[j+1]
7.3 结构体指针的定义和引用
指针变量非常灵活方便,可以指向任一类型的变量,若定义指针变量指向结构体类型变
量,则可以通过指针来引用结构体类型变量。
7.3.1 指向结构体类型变量的使用
首先让我们定义结构体:
struct stu
{
char name[20];
long number;
float score[4];
};
再定义指向结构体类型变量的指针变量:
struct stu *p1, *p2 ;
定义指针变量p1、p2,分别指向结构体类型变量。引用形式为:指针变量→成员;
[例7-2] 对指向结构体类型变量的正确使用。输入一个结构体类型变量的成员,并输出。
#include <stdlib.h> 使用malloc() 需要*/
/*
struct data /* 定义结构体*/
{
136 C语言程序设计
下载
int day,month,year;
};
struct stu /*定义结构体*/
{
char name[20];
long num;
struct data birthday; 嵌套的结构体类型成员*/
/*
} ;
main() /*定义main() 函数*/
{
struct stu *student; 定义结构体类型指针*
/* /
student=malloc(sizeof(struct stu)); 为指针变量分配安全的地址*
/* /
printf("Input name,number,year,month,day:\n");
scanf("%s",student->name); 输入学生姓名、学号、出生年月日
/* */
scanf("%ld",&student->num);
scanf("%d%d%d",&student->birthday.year,&student->birthday.month,
&student->birthday.day);
printf("\nOutput name,number,year,month,day\n" );
/* 打印输出各成员项的值 */
printf("%20s%10ld%10d//%d//%d\n",student->name,student->num,
student->birthday.year,student->birthday.month,
student->birthday.day);
}
运行程序:
RUN ↵
Input name,number,year,month,day:
Wangjian 34 1987 5 23↵
Wangjian 34 1987//5//23
7.3.2 指向结构体类型数组的指针的使用
定义一个结构体类型数组,其数组名是数组的首地址,这一点前面的课程介绍得很清楚。
定义结构体类型的指针,既可以指向数组的元素,也可以指向数组,在使用时要加以区分。
[例7-3] 在例7 - 2中定义了结构体类型,根据此类型再定义结构体数组及指向结构体类型
的指针。
struct data
下载
第7章 结构体与共用体 137
{
int day,month,year;
};
struct stu /*定义结构体*/
{
char name[20];
long num;
struct data birthday; 嵌套的结构体类型成员*/
/*
} ;
struct stu student[4],*p; 定义结构体数组及指向结构体类型的指针
/* */
printf("\n3-----Output name,number,year,month,day\n" );
for(i=0;i<4;i++) /* 采用地址法输出数组元素的各成员 */
printf("%20s%10ld%10d//%d//%d\n",(student+i)->name,(student+i)->num,
(student+i)->birthday.year,(student+i)->birthday.month,
(student+i)->birthday.day);
p=student;
printf("\n4-----Output name,number,year,month,day\n" );
for(i=0;i<4;i++) /* 采用指针的数组描述法输出数组元素的各成员 */
printf("%20s%10ld%10d//%d//%d\n",p[i].name,p[i].num,
p[i].birthday.year,p[i].birthday.month,
p[i].birthday.day);
}
运行程序:
RUN ↵
1----Output name,number,year,month,day
liying 1 1978//5//23
wangping 2 1979//3//14
libo 3 1980//5//6
xuyan 4 1980//4//21
2----Output name,number,year,month,day
liying 1 1978//5//23
wangping 2 1979//3//14
libo 3 1980//5//6
xuyan 4 1980//4//21
3----Output name,number,year,month,day
liying 1 1978//5//23
wangping 2 1979//3//14
libo 3 1980//5//6
xuyan 4 1980//4//21
4----Output name,number,year,month,day
liying 1 1978//5//23
wangping 2 1979//3//14
libo 3 1980//5//6
xuyan 4 1980//4//21
对二维或多维数组的指针,有兴趣的同学可课后讨论,总结出来。
7.4 链表的建立、插入和删除
数组作为存放同类数据的集合,给我们在程序设计时带来很多的方便,增加了灵活性。
下载
第7章 结构体与共用体 139
但数组也同样存在一些弊病。如数组的大小在定义时要事先规定,不能在程序中进行调整,
这样一来,在程序设计中针对不同问题有时需要 3 0个大小的数组,有时需要 5 0个数组的大小,
难于统一。我们只能够根据可能的最大需求来定义数组,常常会造成一定存储空间的浪费。
我们希望构造动态的数组,随时可以调整数组的大小,以满足不同问题的需要。链表就
是我们需要的动态数组。它是在程序的执行过程中根据需要有数据存储就向系统要求申请存
储空间,决不构成对存储区的浪费。
链表是一种复杂的数据结构,其数据之间的相互关系使链表分成三种:单链表、循环链
表、双向链表,下面将逐一介绍。
7.4.1 单链表
图7-3是单链表的结构。
1200 12 34 56 78
2000 1800 2400 NULL
图7-3 单链表
单链表有一个头节点 h e a d,指向链表在内存的首地址。链表中的每一个节点的数据类型
为结构体类型,节点有两个成员:整型成员(实际需要保存的数据)和指向下一个结构体类
型节点的指针即下一个节点的地址(事实上,此单链表是用于存放整型数据的动态数组)。链
表按此结构对各节点的访问需从链表的头找起,后续节点的地址由当前节点给出。无论在表
中访问那一个节点,都需要从链表的头开始,顺序向后查找。链表的尾节点由于无后续节点,
其指针域为空,写作为 NULL。
图7 - 3还给出这样一层含义,链表中的各节点在内存的存储地址不是连续的,其各节点的
地址是在需要时向系统申请分配的,系统根据内存的当前情况,既可以连续分配地址,也可
以跳跃式分配地址。
看一下链表节点的数据结构定义:
struct node
{
int num;
struct node *p;
};
在链表节点的定义中,除一个整型的成员外,成员 p是指向与节点类型完全相同的指针。
在链表节点的数据结构中,非常特殊的一点就是结构体内的指针域的数据类型使用了未定义
成功的数据类型。这是在 C中唯一规定可以先使用后定义的数据结构。
• 单链表的创建过程有以下几步:
1) 定义链表的数据结构。
2) 创建一个空表。
3) 利用malloc()函数向系统申请分配一个节点。
140 C语言程序设计
下载
4 ) 将新节点的指针成员赋值为空。若是空表,将新节点连接到表头;若是非空表,将新
节点接到表尾。
5) 判断一下是否有后续节点要接入链表,若有转到3),否则结束。
• 单链表的输出过程有以下几步
1) 找到表头。
2) 若是非空表,输出节点的值成员,是空表则退出。
3) 跟踪链表的增长,即找到下一个节点的地址。
4) 转到 2)。
[例7-5] 创建一个存放正整数(输入 -999做结束标志)的单链表,并打印输出。
在链表的创建过程中,链表的头指针是非常重要的参数。因为对链表的输出和查找都要
从链表的头开始,所以链表创建成功后,要返回一个链表头节点的地址,即头指针。
运行程序:RUN ↵
1 2 3 4 5 6 7 ↵
-999
1 2 3 4 5 6 7
链表的创建过程用图示如下:
第一步,创建空表: head NULL
第二步,申请新节点:
p1
1 NULL
p2
第三步,若是空表,将新节点接到表头:
head
P1 1 NULL
P2
若是非空表,head ...
NULL
p2 p1
P2->next=p1。
第四步,p2=p1: head NULL
第五步,申请新节点: p1 p2
NULL
若数值为负,则结束;否则转到第三步。
7.4.2 单链表的插入与删除
在链表这种特殊的数据结构中,链表的长短需要根据具体情况来设定,当需要保存数据
时向系统申请存储空间,并将数据接入链表中。对链表而言,表中的数据可以依此接到表尾
或连结到表头,也可以视情况插入表中;对不再需要的数据,将其从表中删除并释放其所占
空间,但不能破坏链表的结构。这就是下面将介绍的链表的插入与删除。
1. 链表的删除
在链表中删除一个节点,用图 7-4描述如下:
[例7-6] 创建一个学生学号及姓名的单链表,即节点包括学生学号、姓名及指向下一个
节点的指针,链表按学生的学号排列。再从键盘输入某一学生姓名,将其从链表中删除。
首先定义链表的结构:
struct
142 C语言程序设计
下载
{
int num;/* 学生学号*/
char str[20]; /* 姓名*/
struct node *next;
};
删除表中节点s'p->next=s->next
p s
head
NULL
删除表头节点head=head->next
head
NULL
删除表尾节点s,p->next=NULL
p s
head
NULL
NULL
图7-4 链表中节点的删除
从图7 - 4中看到,从链表中删除一个节点有三种情况,即删除链表头节点、删除链表的中
间节点、删除链表的尾节点。题目给出的是学生姓名,则应在链表中从头到尾依此查找各节
点,并与各节点的学生姓名比较,若相同,则查找成功,否则,找不到节点。由于删除的节
点可能在链表的头,会对链表的头指针造成丢失,所以定义删除节点的函数的返回值定义为
返回结构体类型的指针。
struct node *delet(head,pstr)/* 以head 为头指针,删除 pstr 所在节点*/
struct node *head;
char *pstr;
{
struct node *temp,*p;
temp=head; /* 链表的头指针*/
if (head==NULL) /* 链表为空*/
printf("\nList is null!\n");
else /*非空表*/
{
temp=head;
while (strcmp(temp->str,pstr)!=0&&temp->next!=NULL)
/* 若节点的字符串与输入字符串不同,并且未到链表尾 */
{
p=temp;
temp=temp->next; /* 跟踪链表的增长,即指针后移 */
}
if(strcmp(temp->str,pstr)==0 ) /* 找到字符串*/
{
if(temp==head) { /* 表头节点*/
printf("delete string :%s\n",temp->str);
head=head->next;
free(temp); /* 释放被删节点*/
下载
第7章 结构体与共用体 143
}
else
{
p->next=temp->next; 表中节点*/
/*
printf("delete string :%s\n",temp->str);
free(temp);
}
}
没找到要删除的字符串*/
else printf("\nno find string!\n");/*
}
return(head); /* 返回表头指针 */
}
2. 链表的插入
首先定义链表的结构:
struct
{
int num; /*学生学号*/
char str[20]; /*姓名*/
struct node *next;
};
在建立的单链表中,插入节点有三种情况,如图 7-5所示。
在表头插入节点p1,head=p1
head
NULL
p1
在表中插入节点p1,p3->next=p1;p1->next=p2;
p3 p2
head
NULL
p1
在表尾插入节点p1,p2->next=p1;p1->next=NULL
p2
head
NULL
p1
图7-5 单链表中插入节点
插入的节点可以在表头、表中或表尾。假定我们按照以学号为顺序建立链表,则插入的
节点依次与表中节点相比较,找到插入位置。由于插入的节点可能在链表的头,会对链表的
头指针造成修改,所以定义插入节点的函数的返回值定义为返回结构体类型的指针。节点的
插入函数如下:
struct node *insert(head,pstr,n) /* 插入学号为n、姓名为pstr 的节点*/
struct node *head; /* 链表的头指针 */
char *pstr;
144 C语言程序设计
下载
int n;
{
struct node *p1,*p2,*p3;
分配一个新节点 */
p1=(struct node*)malloc(sizeof(struct node));/*
strcpy(p1->str,pstr); /* 写入节点的姓名字串 */
p1->num=n; /* 学号*/
p2=head;
if (head==NULL) /* 空表*/
{
head=p1; p1->next=NULL;/* 新节点插入表头*/
}
else
{ /*非空表*/
while(n>p2->num&&p2->next!=NULL)
/*输入的学号小于节点的学号,并且未到表尾 */
{
p3=p2;
p2=p2->next; /* 跟踪链表增长*/
}
if (n<=p2->num) /* 找到插入位置*/
if (head==p2) /* 插入位置在表头*/
{
head=p1;
p1->next=p2;
}
else
{ /*插入位置在表中 */
p3->next=p1;
p1->next=p2;
}
else
{ /*插入位置在表尾 */
p2->next=p1;
p1->next=NULL;
}
}
return(head); /* 返回链表的头指针 */
}
3. 实例[例7-7]
创建包含学号、姓名节点的单链表。其节点数任意个,表以学号为序,低学号的在前,
高学号的在后,以输入姓名为空作结束。在此链表中,要求删除一个给定姓名的节点,并插
入一个给定学号和姓名的节点。
# include "stdlib.h"
# include "malloc. h"
下载
第7章 结构体与共用体 145
struct node /*节点的数据结构 */
{
int num;
char str[20];
struct node *next;
};
/****************************/
main( )
{
/* 函数声明*/
struct node *creat();
struct node *insert();
struct node *delet();
void print( );
struct node *head;
char str[20];
int n;
head=NULL; /*做空表*/
head=creat (head); /* 调用函数创建以 head 为头的链表*/
print(head) ;/* 调用函数输出节点 */
printf("\n input inserted num,name:\n");
gets(str); /*输入学号*/
n=atoi (str);
gets(str); /*输入姓名*/
head=insert (head, str, n); 将节点插入链表
/* */
print (head); /* 调用函数输出节点 */
printf("\n input deleted name:\n");
gets(str); /*输入被删姓名*/
head=delet(head,str); 调用函数删除节点 */
/*
print (head); /*调用函数输出节点 */
return;
}
/**********************/
/*** 创建链表************/
struct node *creat(struct node *head)
{
char temp[30];
struct node *pl,*p2;
pl=p2=(struct node*) malloc(sizeof(struct node));
printf ("input num, name: \n");
printf("exit:double times Enter!\n");
gets(temp);
gets (p1->str);
pl->num=atoi (temp);
pl->next=NULL;
while (strlen (pl->str)>0
{
;
if (head==NULL) head=pl
else p2->next=p1;
146 C语言程序设计
下载
P2=pl ;
pl=(struct node *)malloc(sizeof(struct node));
printf ("input num, name: \n");
printf("exit:double times Enter!\n");
gets(temp);
gets(pl ->str);
p1->num=atoi (temp);
P1->next=NULL;
}
return head;
}
/********************/
/********** 插入节点**********/
struct node *insert (head, pstr,n);
struct node *head;
char *pstr;
int n;
{
struct node *pl,*p2,*p3;
p1=(struct node*)malloc(sizeof(struct node));
strcpy (p1->str, pstr);
p1->num=n;
p2=head;
if(head==NULL)
{
head=pl;pl->next=NULL;
}
else
{
while (n>p2->num&&p2->next!=NULL)
{
p3=P2
p2=p2->next;
}
if (n<=p2->num)
if (head==p2)
{
head=pl;
pl->next=p2;
}
else
{
p3->next=pl;
pl->next=p2;
}
else
{
p2->next=pl;
pl->next=NULL;
下载
第7章 结构体与共用体 147
}
}
return(head);
}
/*************************/
/***** 删除节点*************/
struct node *delet (head, pstr)
struct node *head;
char *pstr;
{
struct node *temp,*p;
temp=head;
if (head==NULL)
printf("\nList is null!\n");
else
{
temp=head;
while (strcmp(temp->str,pstr)!=O&&temp->next!=NULL)
{
p=temp;
temp=temp->next,
}
if(strcmp(temp->str,pstr)==0)
{
if (temp== head)
{
head=head->next;
free(temp);
}
else
{
p->next =temp->next;
printf("delete string :%s\n",temp->str);
free(temp);
}
}
else printf("\nno find string!\n");
}
return(head);
}
/**********************************/
/********** 链表各节点的输出 **********/
void print (struct node *head)
{
struct node *temp;
temp=head;
printf("\n output strings:\n");
while (temp!=NULL)
{
printf("\n%d----%s\n",temp->num ,temp->str) ;
148 C语言程序设计
下载
temp=temp->next ;
}
return;
}
运行程序:
RUN ↵
input num,name:
exit:double times Enter!
1↵
Huangping ↵
input num,name:
exit:double times Enter!
3↵
Lixiaobo ↵
input num,name:
exit:double times Enter!
4↵
Yangjinhua ↵
input num,name:
exit:double times Enter!
7↵
xuehong ↵
input num,name:
exit:double times Enter!
↵
↵
output strings:
1------- Huangping
3--------Lixiaobo
4--------Yangjinhua
7--------xuehong
input inserted num,name:
5↵
Liling ↵
output strings:
1------- Huangping
3--------Lixiaobo
4--------Yangjinhua
5--------Liling
7--------xuehong
input deleted name:
Lixiaobo ↵
delete string : Lixiaobo
1------- Huangping
4--------Yangjinhua
5--------Liling
7--------xuehong
下载
第7章 结构体与共用体 149
7.5 共用体
所谓共用体类型是指将不同的数据项组织成一个整体,它们在内存中占用同一段存储单
元。其定义形式为:
union 共用体名
{成员表列};
7.5.1 共用体的定义
union data
{
int a;
float b;
double c;
char d;
} obj;
该形式定义了一个共用体数据类型 union data ,定义了共用体数据类型变量 o b j。共用体
数据类型与结构体在形式上非常相似,但其表示的含义及存储是完全不同的。先让我们看一
个小例子。
[例7-8]
union data /*共用体*/
{
int a;
float b;
double c;
char d;
}mm;
struct stud /*结构体*/
{
int a;
float b;
double c;
char d;
};
main()
{
struct stud student
printf("%d,%d",sizeof(struct stud),sizeof(union data));
}
运行程序输出:
RUM ↵
15 ,8
程序的输出说明结构体类型所占的内存空间为其各成员所占存储空间之和。而形同结构
体的共用体类型实际占用存储空间为其最长的成员所占的存储空间。详细说明如图 7-6所示。
150 C语言程序设计
下载
结构体
共用体
a 2
d
4 a
b
15 b 8
c 8 c
d 1
图7-6 共用体类型与结构体类型占用存储空间的比较
对共用体的成员的引用与结构体成员的引用相同。但由于共用体各成员共用同一段内存
空间,使用时,根据需要使用其中的某一个成员。从图中特别说明了共用体的特点,方便程
序设计人员在同一内存区对不同数据类型的交替使用,增加灵活性,节省内存。
7.5.2 共用体变量的引用
可以引用共用体变量的成员,其用法与结构体完全相同。若定义共用体类型为:
union data /*共用体*/
{
int a;
float b;
double c;
char d;
}mm;
运行程序输出为:
6
下载
第7章 结构体与共用体 151
67 .2
34.2,=
假定共用体的成员在内存的存储是从地址 1 0 0 0单元开始存放,整个共用体类型需占存储
空间6个字节,即共用体 d i g的成员 d a t a与b y t e共用这 6个字节的存储空间,存储空间分配示意
如图7-7所示。
存储器
1000
byte[0]
data.year
1001 byte[1] 共
用
1002 byte[2]
data.month 体
1003 byte[3] 类
byte[4] 型
1004 data.day
1005 byte[5]
图7-7 共用体dig成员在存储空间中的分配示意图
main()
{
union dig unit;
int i;
printf("enter year:\n");
scanf("%d",&unit.data.year); /* 输入年*/
printf("enter month:\n");
scanf("%d",&unit.data.month); /* 输入月*/
printf("enter day:\n");
scanf("%d",&unit.data.day); /* 输入日*/
printf("year=%d month=%d day=%d\n", unit.data.year,unit. data. month, un
data.day); /* 打印输出*/
for(i=0;i<6;i++)
printf("%d,",unit.byte[i]); /* 按字节以十进制输出 */
printf("\n");
}
运行程序:
RUN ↵
enter year:
1976 ↵
enter month:
4↵
enter day:
23 ↵
year=1976 month=4 day=23
184,7,4,0,23,0
第8章 输入、输出和文件系统
在前面的程序设计中,我们介绍了输入和输出,即从标准输入设备 — 键盘输入,由标准
输出设备 — 显示器或打印机输出。不仅如此,我们也常把磁盘作为信息载体,用于保存中
间结果或最终数据。在使用一些字处理工具时,会利用打开一个文件来将磁盘的信息输入到
内存,通过关闭一个文件来实现将内存数据输出到磁盘。这时的输入和输出是针对文件系统,
故文件系统也是输入和输出的对象,谈到输入和输出,自然也离不开文件系统。
文件可以从不同的角度来分类:
1) 按文件所依附的介质来分:有卡片文件、纸带文件、磁带文件、磁盘文件等。
2) 按文件内容来分:有源文件、目标文件、数据文件等。
3) 按文件中数据组织形式分:有字符文件和二进制文件。
字符文件通常又称为 ASCII码文件或正文文件,按字符存储,具有可读性;而二进制文件
是以二进制存储,不具备可读性,但从存储空间的利用来看,实型数无论位数大小均占 4位,
字符确需按位数来存放,这样的话,二进制文件相对就节省了空间。
目前C语言使用的文件系统分为缓冲文件系统(标准 I/O)和非缓冲文件系统(系统 I/O)。
8.1 缓冲文件系统
缓冲文件系统的特点是:在内存开辟一个“缓冲区”,为程序中的每一个文件使用,当执
行读文件的操作时,从磁盘文件将数据先读入内存“缓冲区”,装满后再从内存“缓冲区”依
此读入接收的变量。执行写文件的操作时,先将数据写入内存“缓冲区”,待内存“缓冲区”
装满后再写入文件。由此可以看出,内存“缓冲区”的大小,影响着实际操作外存的次数,
内存“缓冲区”越大,则操作外存的次数就少,执行速度就快、效率高。一般来说,文件
“缓冲区”的大小随机器而定。
8.1.1 文件的打开与关闭
任何关于文件的操作都要先打开文件,再对文件进行读写,操作完毕后,要关闭文件。
1. 文件类型指针
人们在操作文件时,通常都关心文件的属性,如文件的名字、文件的性质、文件的当前
状态等。对缓冲文件系统来说,上述特性都是要仔细考虑的。 ANSI C 为每个被使用的文件在
内存开辟一块用于存放上述信息的小区,利用一个结构体类型的变量存放。该变量的结构体
类型由系统取名为 FILE,在头文件 stdio.h中定义如下:
typedef struct{
int_fd; /* 文件号*/
int_cleft; /* 缓冲区中的剩余字符*/
int_mode; /* 文件的操作模式 */
char*_next; /*下一个字符的位置 */
char *_buff; /* 文件缓冲区的位置 */
154 C语言程序设计
下载
} FILE;
在操作文件以前,应先定义文件变量指针:
FILE *fp1,fp2;
按照上面的定义,fp1和fp2均为指向结构体类型的指针变量,分别指向一个可操作的文件,
换句话说,一个文件有一个文件变量指针,今后对文件的访问,会转化为针对文件变量指针
的操作。
2. 文件的打开
ANSI C 提供了打开文件的函数:
FILE *fopen(char *fname,char *mode)
函数原型在 stdio.h文件中,fopen()打开一个fname指向的外部文件,返回与它相连接的流。
f n a m e是字符串,应是一个合法的文件名,还可以指明文件路经。对文件的操作模式由 m o d e
决定,mode也是字符串,由表 8-1给出mode的取值表。
表8-1 mode的取值表
Mode 含 义
r 打开一个文本文件只读
w 打开一个文本文件只写
a 打开一个文本文件在尾部追加
rb 打开一个只读的二进制文件
wb 打开一个只写的二进制文件
ab 对二进制文件追加
r+ 打开一个可读 /写的文本文件
w+ 创建一个新的可读 /写的文本文件
a+ 打开一个可读 /写的文本文件
rb+ 打开一个可读 /写的二进制文件
wb+ 创建一个新的可读 /写的二进制文件
ab 打开一个可读 /写的二进制文件
如表8 - 1所示,文件的操作方式有文本文件和二进制文件两种,打开文件的正确方法如下
例所示:
#include <stdio.h>
FILE *fp;
If ((fp=fopen("test.txt","w"))==NULL)
{ /*创建一个只写的新文本文件*/
printf("cannot open file \n");
exit(0);
}
这种方法能发现打开文件时的错误。在开始写文件之前检查诸如文件是否有写保护,磁
盘是否已写满等,因为函数会返回一个空指针 NULL,NULL值在stdio.h中定义为0。事实上打
开文件是要向编译系统说明三个信息:①需要访问的外部文件是哪一个。②打开文件后要执
行读或写即选择操作方式。③确定哪一个文件指针指向该文件。对打开文件所选择的操作方
式来说,一经说明不能改变,除非关闭文件后重新打开。是只读就不能对其写操作,对已存
下载
第8章 输入、输出和文件系统 155
文件如以新文件方式打开,则信息必丢失。
3. 文件的关闭
ANSI C 提供了关闭文件的函数:
int fclose(FILE *stream)
main()
{
FILE *fp;
If ((fp=fopen("test.dat","rb"))==NULL)
{
printf("cannot open file\n");
exit(0);
}
/*写入对文件执行读写的代码
…… */
if (fclose(fp)) printf("file close error!\n");
}
8.1.2 文件的读写
当文件按指定的工作方式打开以后,就可以执行对文件的读和写。下面按文件的性质分
类进行操作。针对文本文件和二进制文件的不同性质,对文本文件来说,可按字符读写或按
字符串读写;对二进制文件来说,可进行成块的读写或格式化的读写。
1. 读写字符
C提供f g e t c和f p u t c函数对文本文件进行字符的读写,其函数的原型存于 s t d i o . h头文件中,
格式为:
int fgetc(FILE *stream)
f g e t c ( )函数从输入流的当前位置返回一个字符,并将文件指针指示器移到下一个字符处,
如果已到文件尾,函数返回 EOF,此时表示本次操作结束,若读写文件完成,则应关闭文件。
int fputc(int ch,FILE *stream)
f p u t c()函数完成将字符 c h的值写入所指定的流文件的当前位置处,并将文件指针后移
一位。fputc ()函数的返回值是所写入字符的值,出错时返回 EOF。
[例8-2] 将存放于磁盘的指定文本文件按读写字符方式逐个地从文件读出,然后再将其
显示到屏幕上。采用带参数的 main(),指定的磁盘文件名由命令行方式通过键盘给定。
#include <stdio.h>
main( argc,argv)
int argc;
156 C语言程序设计
下载
char *argv[];
{
char ch;
FILE *fp;
int i;
if((fp=fopen(argv[1],"r"))==NULL) /* 打开一个由argv[1] 所指的文件*/
{
printf("not open");
exit(0);
}
while ((ch=fgetc(fp))!=EOF) /* 从文件读一字符,显示到屏幕 */
putchar(ch);
fclose(fp);
}
#include <stdio.h>
main( argc,argv)
int argc;
char *argv[];
{
char ch;
FILE *fp;
int i;
if((fp=fopen(argv[1],"r"))==NULL) /* 打开一个由argv[1] 所指的文件*/
{
printf("not open");
exit(0);
}
while ((ch=fgetc(fp))!=EOF) /* 从文件读一字符,显示到屏幕 */
putchar(ch);
fclose(fp);
}
程序通过从键盘输入一以回车结束的字符串,写入指定的流文件 test.txt,文件以文本只写
方式打开,所以流文件具有可读性,能支持各种字符处理工具访问。简单地说,我们可以通
过DOS提供的type命令来列表显示文件内容。
运行程序:
RUN ↵
I love china!↵
在DOS操作系统环境下,利用 type 命令显示test.txt文件如下:
c:\tc> type test.txt↵
I love china!
2. 读写字符串
C提供读写字符串的函数原型在 stdio.h头文件中,其函数形式为:
Char *fgets(char *str,int num,FILE *stream)
fputs()函数将str指向的字符串写入流文件。操作成功时,函数返回 0值,失败返回非零值。
[例8-4] 向磁盘写入字符串,并写入文本文件 test.txt:
#include <stdio.h>
#include <string.h>
main()
{
FILE *fp;
char str[128];
/* 打开只写的文本文件*/
if ((fp=fopen("test.txt","w"))==NULL)
{
printf("cannot open file!");
exit(0);
}
while((strlen(gets(str)))!=0)
{ /*若串长度为零,则结束 */
fputs(str,fp); /*写入串*/
fputs("\n",fp); /* 写入回车符*/
158 C语言程序设计
下载
}
fclose(fp); /*关文件*/
}
运行结束后,我们利用 dos的type命令列表文件:
c:\tc> type test.txt↵
Hello!
How do you do
Good-bye!
这里所输入的空串,实际为一单独的回车符,其原因是 gets函数判断串的结束是以回车作
标志的。
[例8-5] 从一个文本文件 test1.txt中读出字符串,再写入令一个文件 test2.txt。
#include <stdio.h>
#include <string.h>
main()
{
FILE *fp1,*fp2;
char str[128];
if ((fp1=fopen("test1.txt","r"))==NULL)
{ /* 以只读方式打开文件 1*/
printf("cannot open file\n");
exit(0);
}
if ((fp2=fopen("test2.txt","w"))==NULL)
{ /*以只写方式打开文件 2*/
printf("cannot open file\n");
exit(0);
}
while ((strlen(fgets(str,128,fp1)))>0)
/*从文件中读回的字符串长度大于 0 */
{
fputs(str,fp2 ); /* 从文件1读字符串并写入文件 2*/
printf("%s",str); /* 在屏幕显示*/
}
fclose(fp1);
fclose(fp2);
}
程序共操作两个文件,需定义两个文件变量指针,因此在操作文件以前,应将两个文件
以需要的工作方式同时打开(不分先后),读写完成后,再关闭文件。设计过程是按写入文件
下载
第8章 输入、输出和文件系统 159
的同时显示在屏幕上,故程序运行结束后,应看到增加了与原文件相同的文本文件并显示文
件内容在屏幕上。
3. 格式化的读写
前面的程序设计中,我们介绍过利用 s c a n f ( )和p r i n t f ( )函数从键盘格式化输入及在显示器
上进行格式化输出。对文件的格式化读写就是在上述函数的前面加一个字母 f成为f s c a n f ( )和
fprintf()。其函数调用方式:
int fscanf(FILE *stream,char *format,arg_list)
int fprintf(FILE *stream,char *format,arg_list)
其中,stream为流文件指针,其余两个参数与 scanf()和printf()用法完全相同。
[例8-6] 将一些格式化的数据写入文本文件,再从该文件中以格式化方法读出显示到屏
幕上,其格式化数据是两个学生记录,包括姓名、学号、两科成绩。
#include <stdio.h>
main()
{
FILE *fp;
int i;
struct stu{ /* 定义结构体类型 */
char name[15];
char num[6];
float score[2];
}student; /* 说明结构体变量 */
if ((fp=fopen("test1.txt","w"))==NULL)
{ /* 以文本只写方式打开文件*/
printf("cannot open file");
exit(0);
}
printf("input data:\n");
for( i=0;i<2;i++)
{
scanf("%s %s %f %f",student.name,student.num,&student.score[0],
&student.score[1]); /* 从键盘输入*/
fprintf(fp,"%s %s %7.2f %7.2f\n",student.name,student.num,
student.score[0],student.score[1]);/* 写入文件*/
}
fclose(fp); /* 关闭文件*/
if ((fp=fopen("test.txt","r"))==NULL)
{ /*以文本只读方式重新打开文件 */
printf("cannot open file");
exit(0);
}
printf("output from file:\n");
while (fscanf(fp,"%s %s %f %f\n",student.name,student.num,
&student.score[0],student.score[1])!=EOF )
/* 从文件读入*/
printf("%s %s %7.2f %7.2f\n",student.name,student.num,
160 C语言程序设计
下载
student.score[0],student.score[1]); 显示到屏幕*
/* /
fclose(fp); /*关闭文件*/
}
程序设计一个文件变量指针,两次以不同方式打开同一文件,写入和读出格式化数据,
有一点很重要,那就是用什么格式写入文件,就一定用什么格式从文件读,否则,读出的数
据与格式控制符不一致,就造成数据出错。上述程序运行如下:
input data:
↵
xiaowan j001 87.5 98.4
↵
xiaoli j002 99.5 89.6
output from file:
xiaowan j001 87.50 98.40
xiaoli j002 99.50 89.60
列表文件的内容显示为:
c:\> type test.txt↵
xiaowan j001 87.50 98.40
xiaoli j002 99.50 89.60
此程序所访问的文件也可以定为二进制文件,若打开文件的方式为:
if ((fp=fopen("test1.txt","wb"))==NULL)
{ /* 以二进制只写方式打开文件 */
printf("cannot open file");
exit(0);
}
其效果完全相同。
4. 成块读写
前面介绍的几种读写文件的方法,对其复杂的数据类型无法以整体形式向文件写入或从
文件读出。C语言提供成块的读写方式来操作文件,使其数组或结构体等类型可以进行一次性
读写。成块读写文件函数的调用形式为:
int fread(void *buf,int size,int count,FILE *stream)
int fwrite(void *buf,int size,int count,FILE *stream)
fread ()函数从 stream 指向的流文件读取 count (字段数)个字段,每个字段为 s i z e (字
段长度)个字符长,并把它们放到 buf(缓冲区)指向的字符数组中。
fread ()函数返回实际已读取的字段数。若函数调用时要求读取的字段数超过文件存放
的字段数,则出错或已到文件尾,实际在操作时应注意检测。
f w r i t e ( )函数从 b u f (缓冲区)指向的字符数组中,把 c o u n t (字段数 )个字段写到 s t r e a m所指向
的流中,每个字段为 size个字符长,函数操作成功时返回所写字段数。
关于成块的文件读写,在创建文件时只能以二进制文件格式创建。
[例8-7] 向磁盘写入格式化数据,再从该文件读出显示到屏幕。
#include "stdio.h"
#include "stdlib.h"
main()
{
FILE *fp1;
下载
第8章 输入、输出和文件系统 161
int i;
struct stu{ /*定义结构体*/
char name[15];
char num[6];
float score[2];
}student;
if ((fp1=fopen("test.txt","wb"))==NULL)
{ /*以二进制只写方式打开文件 */
printf("cannot open file");
exit(0);
}
printf("input data:\n");
for( i=0;i<2;i++) {
scanf("%s %s %f %f",student.name,student.num,
&student.score[0],&student.score[1]);/* 输入一记录*/
fwrite(&student,sizeof(student),1,fp1);成块写入文件*
/* /
}
fclose(fp1);
if ((fp1=fopen("test.txt","rb"))==NULL)
{ /*重新以二进制只写打开文件*/
printf("cannot open file");
exit(0);
}
printf("output from file:\n");
for (i=0;i<2;i++)
{
fread(&student,sizeof(student),1,fp1);/* 从文件成块读*/
printf("%s %s %7.2f %7.2f\n",student.name,student.num,
student.score[0],student.score[1]);/* 显示到屏幕*/
}
fclose(fp1);
}
运行程序:
input data:
↵
xiaowan j001 87.5 98.4
↵
xiaoli j002 99.5 89.6
output from file:
xiaowan j001 87.50 98.40
xiaoli j002 99.50 89.60
通常,对于输入数据的格式较为复杂的话,我们可采取将各种格式的数据当做字符串输
入,然后将字符串转换为所需的格式。 C提供函数:
int atoi(char *ptr)
float atof(char *ptr)
long int atol(char *ptr)
它们分别将字符串转换为整型、实型和长整型。使用时请将其包含的头文件 m a t h . h或
162 C语言程序设计
下载
stdlib.h写在程序的前面。
[例8-8] 将输入的不同格式数据以字符串输入,然后将其转换进行文件的成块读写。
#include <stdio.h>
#include <stdlib.h>
main()
{
FILE *fp1;
char *temp;
int i;
struct stu{ /* 定义结构体类型*/
char name[15]; /* 姓名*/
char num[6]; /* 学号*/
float score[2]; /* 二科成绩*/
}student;
if ((fp1=fopen("test.txt","wb"))==NULL) /* 打开文件*/
{
printf("cannot open file");
exit(0);
}
for( i=0;i<2;i++) {
printf("input name:");
gets(student.name); /* 输入姓名*/
printf("input num:");
gets(student.num); /* 输入学号*/
printf("input score1:");
gets(temp); /* 输入成绩*/
student.score[0]=atof(temp);
printf("input score2:");
gets(temp);
student.score[1]=atof(temp);
fwrite(&student,sizeof(student),1,fp1); /* 成块写入到文件*/
}
fclose(fp1);
if ((fp1=fopen("test.txt","rb"))==NULL)
{
printf("cannot open file");
exit(0);
}
printf("---------------------\n");
printf("%-15s%-7s%-7s%-7s\n","name","num","score1","score2");
printf("---------------------\n");
for (i=0;i<2;i++)
{
fread(&student,sizeof(student),1,fp1);
printf("%-15s%-7s%7.2f%7.2f\n",student.name,student.num,
student.score[0],student.score[1]);
下载
第8章 输入、输出和文件系统 163
}
fclose(fp1);
}
运行程序如下:RUN↵
input name:li-ying
input num: j0123
input score1:98.65
input score2:89.6
input name:li-li
input num: j0124
input score1:68.65
input score2:86.6
----------------------------------
name num score1 score2
----------------------------------
li-ying j0123 98.65 89.60
li-li j124 68.64 86.60
8.1.3 随机读写文件
随机对文件的读写是指在文件内部任意对文件内容进行访问,这也就需要对文件进行详
细的定位,只有定位准确,才有可能对文件随机访问。
C语言提供了用于文件定位的函数,它的作用是使文件指针移动到所需要的位置。
int fseek(FILE *fp,long d,int pos)
fp是文件指针,d是位移量, pos是起始点。
Pos的取值为:
0 :文件开始处
1 :文件的当前位置
2 :文件的尾部
位移量d是l o n g型的数据,可以为正或负值。表示从起始点向下或向上的指针移动。函数
的返回值若操作成功为 0,操作失败为非零。
例如:fseek(fp,5L,0);将文件指针从文件头向下移动 5个字节。
fseek(fp,-10L,2);将文件指针从当前位置向上移动 10个字节。
rewind() 将文件指针移动到文件头。
ftell(FILE *fp) 返回文件指针的当前位置。
[例8-9] 写入5个学生记录,记录内容为学生姓名、学号、两科成绩。写入成功后,随机
读取第三条记录,并用第二条记录替换。
#include <stdio.h>
#include <stdlib.h>
#define n 5
main()
{
FILE *fp1; /* 定义文件指针 */
164 C语言程序设计
下载
char *temp;
int i,j;
struct stu{ /* 定义学生记录结构 */
char name[15];
char num[6];
float score[2];
}student[n];
if ((fp1=fopen("test.txt","wb"))==NULL) /* 以二进制只写方式打开文件 */
{
printf("cannot open file");
exit(0);
}
for( i=0;i<n;i++)
{
fwrite(&student[i],sizeof(struct stu),1,fp1);成块写入*
/* /
}
fclose(fp1); /*关闭*/
if ((fp1=fopen("test.txt","rb+"))==NULL)
{ /*以可读写方式打开文件*/
printf("cannot open file");
exit(0);
}
printf("---------------------\n");
printf("%-15s%-7s%-7s%-7s\n","name","num","score1","score2");
printf("---------------------\n");
for (i=0;i<n;i++)
{ /*显示全部文件内容 */
fread(&student[i],sizeof(struct stu),1,fp1);
printf("%-15s%-7s%7.2f%7.2f\n",student[i].name,student[i].num,
student[i].score[0],student[i].score[1]);
}
/* 以下进行文件的随机读写 */
fseek(fp1,3*sizeof(struct stu),0); /* 定位文件指针指向第三条记录 */
fwrite(&student[1],sizeof(struct stu),1,fp1);
/* 在第三条记录处写入第二条记录 */
rewind(fp1); /*移动文件指针到文件头 */
printf("---------------------\n");
下载
第8章 输入、输出和文件系统 165
printf("%-15s%-7s%-7s%-7s\n","name","num","score1","score2");
printf("---------------------\n");
for (i=0;i<n;i++)
{ /*重新输出文件内容 */
fread(&student[i],sizeof(struct stu),1,fp1);
printf("%-15s%-7s%7.2f%7.2f\n",student[i].name,student[i].num,
student[i].score[0],student[i].score[1]);
}
fclose(fp1); /*关闭文件*/
}
运行程序:
RUN ↵
input name:li-ying
input num: j0123
input score1:98.65
input score2:89.6
input name:li-li
input num: j0124
input score1:68.65
input score2:86.6
input name:li-ping
input num: j0125
input score1:88.5
input score2:84.6
input name:Wang-xian
input num: j0126
input score1:98
input score2:94
input name:Ma-ling
input num: j0127
input score1:66.5
input score2:80.6
----------------------------------
name num score1 score2
----------------------------------
li-ying j0123 98.65 89.60
li-li j0124 68.64 86.60
li-ping j0125 88.50 84.60
Wang-xian j0126 98.0094.00
Ma-ling j012766.50 80.60
----------------------------------
name num score1 score2
----------------------------------
li-ying j0123 98.65 89.60
li-li j0124 68.64 86.60
li-li j0124 68.64 86.60
Wang-xian j0126 98.0094.00
166 C语言程序设计
下载
Ma-ling j0127 66.50 80.60
程序的第二次输出,即随机访问后,文件中会有两条相同的记录。
8.2 非缓冲文件系统
前面介绍的缓冲文件系统是借助文件结构体指针来对文件进行管理,通过文件指针来对
文件进行访问,既可以读写字符、字符串、格式化数据,也可以读写二进制数据。非缓冲文
件系统依赖于操作系统,通过操作系统的功能对文件进行读写,是系统级的输入输出,它不
设文件结构体指针,只能读写二进制文件,但效率高、速度快,由于 A N S I标准不再包括非缓
冲文件系统,因此建议大家最好不要选择它。本书只作简单介绍。
1. 文件的打开与关闭
非缓冲文件系统不是 A N S I标准定义的,是 U N I X型I / O系统的一员,所以,其原型位于
io.h文件中。
打开文件:
;
int open(char *fname,int access)
打开文件名为fname,以access方式访问:
access的值为:O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
关闭文件:
close(int fd);
下述程序用 UNIX系统打开和关闭一个文件:
#include "io.h"
#include "fcntl.h"
#include "sys\stat.h"
main(argc,argv)
int argc;
char *argv[]
{
int fd;
if((fd=open(argv[1],O_RDONLY))==-1) 以只读方式打开文件
/* */
{
printf("cannt open file!");
exit(0);
}
printf("file existent!");
if (close(fd)) printf("error in closing file\n");
}
2. 文件的读写
对非缓冲文件系统的读写函数的原型在 io.h头文件中,其调用形式为:
int read(int fd,void *buf,int count)
main()
{
int fd;
char buffer[100];
if ((fd=open("TEST.TST",O_RDONLY))==-1)打开文件*
/* /
{
printf("cannot open file !\n");
exit(0);
}
if (read(fd,buffer,100)!=100) /* 判断读写的字节数是否正确 */
printf("Possible read error.");
}
8.3 文件系统应用举例
文件操作在程序设计中是非常重要的技术,文件的数据格式不同,决定了对文件操作方
式的不同。
[例8-10] 我们需要同时处理三个文件。文件 a d d r. t x t记录了某些人的姓名和地址;文件
tel.txt记录了顺序不同的上述人的姓名与电话号码。希望通过对比两个文件,将同一人的姓名、
地址和电话号码记录到第三个文件 addrtel.txt。首先看一下前两个文件的内容:
type addr.txt↵
hejie tianjing
liying shanghai
liming chengdu
wangpin chongqing
type tel.txt↵
liying 12345
hejie 8764
wangpin 87643
liming 7654322
这两个文件格式基本一致,姓名字段占 1 4个字符,家庭住址或电话号码长度不超过 1 4个
字符,并以回车结束。文件结束的最后一行只有回车符,也可以说是长度为 0的串。在两个文
件中,由于存放的是同一批人的资料,则文件的记录数是相等的,但存放顺序不同。我们可
以任一文件记录为基准,在另一文件中顺序查找相同姓名的记录,若找到,则合并记录存入
168 C语言程序设计
下载
第三个文件,将查找文件的指针移到文件头,以备下一次顺序查找。
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
main()
{
FILE *fptr1,*fptr2,*fptr3; /* 定义文件指针*/
char temp[15],temp1[15],temp2[15];
if ((fptr1=fopen("addr.txt","r"))==NULL)/* 打开文件*/
{
printf("cannot open file");
exit(0);
}
if ((fptr2=fopen("tel.txt","r"))==NULL)
{
printf("cannot open file");
exit(0);
}
if ((fptr3=fopen("addrtel.txt","w"))==NULL)
{
printf("cannot open file");
exit(0);
}
clrscr(); /* 清屏幕*/
while(strlen(fgets(temp1,15,fptr1))>1) 读回的姓名字段长度大于1
/* */
{
fgets(temp2,15,fptr1); /* 读地址*/
fputs(temp1,fptr3); /* 写入姓名到合并文件 */
fputs(temp2,fptr3); /* 写入地址到合并文件 */
strcpy(temp,temp1); /* 保存姓名字段*/
do /*查找姓名相同的记录 */
{
fgets(temp1,15,fptr2);
fgets(temp2,15,fptr2);
} while (strcmp(temp,temp1)!=0);
rewind(fptr2); /* 将文件指针移到文件头,以备下次查找 */
fputs(temp2,fptr3); /* 将电话号码写入合并文件*/
}
fclose(fptr1); /* 关闭文件*/
fclose(fptr2);
fclose(fptr3);
}
下载
第8章 输入、输出和文件系统 169
程序运行后,我们来看一下合并后的文件 addrtel.txt的内容:
type addrtel.txt↵
hejie tianjing
8764
liying shanghai
12345
liming chengdu
7654322
wangpin chongqing
87643
下载
第9章 实用编程技巧
9.1 图形应用技巧
9.1.1 显示适配器类型的自动测试
目前P C机及兼容机的显示器及其适配器的类型非常多,有单色的,也有彩色的。这些显
示器及适配器的模式对应用程序来说是非常重要的。如何在程序中自动识别显示器的模式,
以便更好地使用当前的显示模式是每个微机应用程序开发者的一个重要课题。下面程序可以
方便测出当前显示器适配器的模式(有关具体知识,请参见其它相关的技术书籍)。
[例9-1] 测试显示适配器类型。
#include <stdio.h>
#include <graphics.h>
#define P(note) printf(note)
#define ,value)
PV(format ,value)
printf(format
#define PM printf("mode is ")
#define PD printf("\n\tdetected graphics drive is")
void main( )
{
int gdrive ,gerror ,gmode ;
开发图形软件的基本方法
大家都知道,Turbo C 具有汇编语言那样直接控制系统硬件以及调用操作系统资源的功能,
同时又具有一般高级语言完成复杂运算的能力。因此, C语言已成为开发图形软件最理想的程
序语言。下面主要介绍几个生成基本图形的函数,它们是开发复杂图形软件的基础。
显示方式与色调函数
若要在屏幕上显示图形,首先要把屏幕设置为彩色图形显示方式,常用的方式是:
mode 4 320×200 4色
在这种显示方式下,可以使用两种不同的配色器来配置色调,见表 9-1。
表9-1 屏幕色调
配色器号 颜 色 0 颜 色 1 颜 色 2 颜色 3
0 同底色 绿 红 黄
1 同底色 青 淡红 白
使用画点函数可以编写画直线的函数 l i n e ( ),其原理是已知直线的两个端点坐标时,用迭
代过程确定组成直线各点的位置。函数 line()的参数为 x1,y1,x2,y2,color。其中x1、y1和
x 2、y 2分别是直线的起点和终点坐标。 C o l o r是直线的颜色,取值 0 ~ 3。例如在屏幕的右斜对
角上画一条绿色直线,使用的格式为:
174 C语言程序设计
下载
line(0 ,0,119 ,319 ,1);
矩形与填充函数
矩形是由四条直线组成的。使用直线函数可以矩形。绘制时,只需知道它左上角和右下
角的坐标,就可以用直线函数绘出它的四条边,矩形函数 b o x ( )的参数为 x 1、y 1、x 2、y 2、
color。其中x1、y1和x2、y2分别是矩形左上角和右下角的坐标, color是四条边的颜色。
矩形填充块,实际是在指定位置上画出具有相同长度和颜色的直线,其填充函数 f i l l b o x ( )
中的参数与 box()中的参数相同。
绘制图形
使用上述几个基本图形的函数,可以编写出在屏幕上绘制任意图形的程序。它使用键盘
上的箭头等功能键,实现显示位置和颜色的控制。为了获取键盘扫描代码和显示当前绘图位
置,要使用十字函数 xhair()和获取键盘扫描代码函数 getkey()。
下面给出一个简单的绘图程序 t x . c。它相当于用画笔在屏幕上绘制图形,画笔的位置用十
字光标显示。画笔的移动由↓、→、↑、←四个键控制。用 H o m e、P g U p、P g D n和E n d键分
别控制十字光标向 4 5°方向移动。画笔的抬起落下由字母键 O控制,颜色由数字键 0~3控制。
功能键F1用于设定单步前进, F2用于设定5步前进。
该程序还可以画出矩形、填充矩形和直线。这时需要设置它们的坐标位置,直线需要两
个端点坐标,矩形需要两个对角的坐标。例如,在抬笔状态下,把十字光标移至第一个位置
后按回车键,然后再移至第二个位置按回车键。之后按下 L键时,则在设定的两个端点的位置
上画出一条直线;如果按 B键,则以两个位置为对角画出矩形;如果按 F键,则画出填充矩形。
另外,用P键可改变色调,按 Q键结束运行,返回到 DOS状态。
[例9-2] 绘图程序tx.c
#include<stdio.h>
#include<dos.h>
#include<ctype.h>
#include<conio.h>
#include<math.h>
void setmode(int);
void palet(int);
void point(int,int ,int) ;
void line(int,int ,int ,int ,int) ;
void box(int,int ,int ,int ,int) ;
void fillboX(int,int ,int ,int ,int) ;
void Xhair(int,int) ;
int getkey(void);
void main()
{
union{
char c[2];
int i;
}key ;
int X=10,y=10 ,cc=2 ,onflag=1 ,palnum=1 ;
下载
第9章 实用编程技巧 175
intX1=0 ,y1=0 ,X2=0 ,y2=0 ,firstpoint=1 ;
int d=1;
setmode(4) ;
palet(0) ;
Xhair(X ,y);
do {
key.i=getkey() ;
Xhair(X ,y);
if(!key.c[0])
switch(key.c[1]){
case 75: if(onflag) /*left*/
line(X ,y,X,y-d ,cc) ;
y-=d ;
break ;
case 77: if(onflag) /* right */
line(X ,y,X,y+d ,cc) ;
y+=d ;
break ;
case 72:if(onflag) /* up */
line(X ,y,X-d ,y,cc) ;
X-=d ;
break ;
case 80: if(onflag) /* down */
line(X ,y,X+d ,y,cc) ;
X+=d ;
break ;
case 71:if(onflag) /* Home-up left */
line(X ,y,X-d ,y+d ,cc) ;
X-=d ;
X-=d ;
break ;
case 73:if(onflag) /*PgUp-up right */
line(X ,y,X-d ,y+d ,cc) ;
X-=d ;
y+=d ;
break ;
case 79:if(onflag) /*End-down left */
line(X ,y,X+d ,y-d ,cc) ;
X+=d ;
y-=d ;
break ;
case 81:if(onflag) /* PgUp-down right */
line(X ,y,X+d ,y+d ,cc) ;
X+=d ;
y+=d ;
break ;
case 59: /*F1*/
d=1 ;
break ;
176 C语言程序设计
下载
case 60: /*F2*/
d=5 ;
break ;
}
else
switch(tolower(key.c[0])){
case 'o': /* brush on-off */
onflag=!onflag ;
break ;
case '1': /* color 1*/
cc=1 ;
break ;
case '2': /* color2 */
cc=2 ;
break ;
case '3': /* color 3*/
cc=3 ;
break ;
case '0': /* color 0*/
cc=0 ;
break ;
case 'b': /* set boX */
boX(X1 ,y1,X2 ,y2 ,cc) ;
break ;
case 'f': /*set fill boX */
fillboX(X1 ,y1 ,X2 ,y2,cc) ;
break ;
case 'l': /* set line */
line(X1 ,y1 ,X2,y2 ,cc) ;
break ;
case 'r': /*set endpoint */
if(firstpoint){
X1=X ;
y1=y ;
}
else {
X2=X ;
y2=y ;
}
firstpoint = !firstpoint ;
break ;
case 'p': /* set color */
palnum = palnum==1 ? 2 :;1
palet(palnum) ;
break ;
}
Xhair(X ,y) ;
}while(key.c[0]!='q') ;
getch() ;
下载
第9章 实用编程技巧 177
setmode(2) ;
}
/* 设置显示方式 */
void setmode(mode)
int mode;
{
union REGS regs;
regs.h.al=mode ;
regs.h.ah=0 ;
int86(0X10 ,®s ,®s) ;
}
/* 设置色调*/
void palet(pn)
int pn;
{
union REGS regs;
regs.h.bh=1 ;
regs.h.bl=pn ;
regs.h.ah=11 ;
int86(0X10 ,®s ,®s) ;
}
/*画点函数*/
void point(X,y,color)
int X,y,color ;
{
union {
char cc[2];
int i;
}mask ;
int i,indeX ,posit ;
unsigned char t;
char Xor;
;
char far *ptr=(char far *)0Xb8000000
mask.i=0Xff3f ;
if(X<0||X>199||y<0||y>319)
return ;
Xor=color&128 ;
color=color&127 ;
posit=y%4 ;
color<<=2*(3-posit) ;
mask.i>>=2*posit ;
indeX=X*40+(y/4) ;
if(X%2)indeX+=8152 ;
if(! Xor){
t=*(ptr+indeX)&mask.cc[0] ;
*(ptr+indeX)=t|color ;
}
else{
t=*(ptr+indeX)|(char)0 ;
178 C语言程序设计
下载
*(ptr+indeX)=t^color ;
}
}
/*直线函数*/
void line (X1,y1,X2 ,y2 ,color)
int X1,y1 ,X2,y2 ,color ;
{
register int t,dis ;
int Xerr=0,yerr=0 ,dX ,dy;
int incX,incy ;
dX=X2-X1 ;
dy=y2-y1 ;
if (dX>0)incX=1;
else if(dX==0)incX=0 ;
else incX=-1;
if(dy>0)incy=1 ;
else if(dy==0)incy=0 ;
else incy=-1;
dX=abs(dy) ;
dy=abs(dy) ;
for (t=0;t<=dis+1 ;t++){
point(X1 ,y1 ,color) ;
Xerr+=dX ;
yerr+=dy ;
if(Xerr>dis){
Xerr-=dis ;
X1+=incX ;
}
if(yerr>dis){
yerr-=dis ;
y1+=incy ;
}
}
}
/*矩形函数*/
void boX(X1,y1 ,X2,y2 ,color)
int X1,y1 ,X2 ,y2,color ;
{
line (X1,y1,X2 ,y1 ,color) ;
line(X1 ,y1 ,X2,y2 ,color) ;
line(X1 ,y2 ,X2,y2 ,color) ;
line(X2 ,y1 ,X2,y2 ,color) ;
}
/* 矩形填充函数*/
void fillboX(X1,y1,X2 ,y2 ,color)
int X1,y1 ,X2,y2 ,color ;
{
register int i,begin ,end ;
begin=X1<X2? X1:X2;
下载
第9章 实用编程技巧 179
end=X1>X2? X1:X2;
for (i=begin;i<=end ;i++)
line(i ,y1 ,i,y2 ,color) ;
}
/* 十字光标定位函数 */
void Xhair(X,y)
int X,y;
{
line(X-4 ,y,X+3 ,y,1|128) ;
line(X ,y+4 ,X,y-3 ,1|128) ;
}
/* 获取键盘扫描码函数 */
int getkey()
{
union REGS regs;
regs.h.ah=0 ;
return int86(0X16,®s ,®s) ;
}
9.1.2 屏幕图像的存取技巧
Turbo C提供了丰富的图形操作函数,利用这些函数可以很容易编写图形和图像处理程序,
但是Turbo C没有提供屏幕图像存储和恢复的函数,而在许多情况下需要将屏幕上的图像全部
或部分的以文件的形式保存在磁盘上,在需要时快速地从磁盘调入内存并重现在屏幕上。下
面介绍的程序就是用来对屏幕上任一块矩形区域进行存取的程序 t x 1 . c。saveimage( )函数首先
将屏幕上的一块矩形区域图像的数据写入内存某地址,然后创建一个二进制文件,并把该地
址的数据写入此文件中。这样屏幕上的图像就以文件的形式存储在磁盘上了。 loadimage( )函
数首先将磁盘上的图像数据文件打开并读入到内存某地址,然后从该地址将这些数据读到视
屏缓冲区。这样,原先保存的图像就重现在屏幕上了。
t x 1 . c程序首先测试硬件的图形适配卡类型,并根据其值进入相应的图形方式,然后在屏
幕上画一个彩色饼形统计图,并将该图像存入文件 graph.dat中,最后打开graph.dat文件并读入
内存,将这幅图像重现在屏幕上。
[例9-3] 存取任意屏幕图像程序 tx1.c
/* 存取任意屏幕图像头文件 tX1.h */
#include<stdio.h>
#include<graphics.h>
#include<conio.h>
#include<fcntl.h>
#include<alloc.h>
#include<stdlib.h>
#include<io.h>
void main(void)
{
initialize() ;
outteXtXy(X/2 ,-10 ,"Saving Image");
下载
第9章 实用编程技巧 181
saveimage(0 ,0,X,y,f) ;
outteXtXy(X+50 ,y+y/2+10 ,"Loading Image");
loadimage(X ,y/2 ,f) ;
quit( );
}
void initialize( )
{
int gdrive=DETECT,gmode ,errorcode ;
int angle=360/MAXCOLORS ,color ;
initgraph(&gdrive ,&gmode , "") ;
errorcode=graphresult( ;)
if (errorcode!=grOk)
{
printf("graphics error:%s\n" ,grapherrormsg(errorcode)) ;
printf("press any key to halt:") ;
getch( );
eXit(1) ;
}
X=getmaXX() *2/5;
y=getmaXy( ) *2/5;
setviewport(X/2 ,y/2 ,getmaXX( )-X/2,getmaXy( )-y/2,0) ;
setteXtjustify( CENTER_TEXT ,CENTER_TEXT) ;
rectangle(0 ,0,X,y) ;
for (color=0;color<MAXCOLORS ;color++){
setfillstyle(SOLID_FILL ,color) ;
pieslice(X/2 ,y/2 ,color*angle ,(color+1)*angle ,y/3) ;
}
}
void quit( )
{
getch( );
closegraph( );
}
9.1.3 屏幕显示格式的控制方法
一个良好的屏幕格式能给操作者提供很大的方便,也给人们一种赏心悦目的感觉。
为了控制屏幕显示格式,需要编写两个屏幕控制。我们可以借助这两个函数,设计出用
户所需要的屏幕显示格式。
下面是一个简单的演示程序。程序中借助这两个函数,在屏幕中间显示变动的数字。
[例9-4] 控制显示格式 tx3.c
# include<stdio.h>
# include<bios.h>
#include<dos.h>
182 C语言程序设计
下载
void cls(int);
void gotoXy(int,int) ;
void cls(line)
int line;
{
union REGS in,out ;
in.X.aX=0600 ;
in.X.cX=0000 ;
in.h.dh=line-1 ;
in.h.dl=79 ;
in.h.bh=07 ;
int86(0X10 ,&in ,&out) ;
}
void gotoXy (X,y)
int X,y;
{
union REGS in,out ;
in.h.dh=X ;
in.h.dl=y ;
in.h.ah=02 ;
in.h.bh=0 ;
int86 (0X10,&in ,&out) ;
}
void main( )
{
int i;
gotoXy(0 ,0) ;
cls(11) ;
gotoXy(4 ,20) ;
printf("------------------------") ;
gotoXy(5 ,20) ;
printf("|proceeding record No: |") ;
gotoXy(6 ,20) ;
printf("------------------------") ;
for (i=1;i<=1000 ;i++)
{
gotoXy(5 ,45) ;
printf("%4d" ,i) ;
}
}
9.1.4 使图形软件脱离BGI的方法
9.1.5 拷贝屏幕图形的方法
在图形方式下,有时需将屏幕信息在打印机上输出。为了输出屏幕图形,要有一个内存
驻留程序。这时要考虑内存驻留程序的激活问题,激活时机的控制以及 TSR的初始化问题。
所有 T S R程序都靠键来激活,因此需用自己的键盘中断程序代替 D O S的键盘中断程序,
激活T S R程序是在 D O S不忙时进行的。当 D O S正在使用时,有一个字节被置 1,当它未被使用
时,该字节为 0,这个地址可用 3 4 H号中断获取,该中断返回后, E S寄存器存放段地址, B X
寄存器放位移,因而,当该字节为 0时,TSR程序才允许激活。
实现时,使用了 interrupt类型说明、寄存器伪变量和程序终止并驻留的技术。
首先由 main( )函数完成初始化工作,它先取得中断 5子程序的地址,然后设置新的 5号中
断程序,这由 g e t v e c t和s e t v e c t来实现的。最后用 k e e p ( 0,s i z e )使程序驻留,并保存 1 6 * s i z e大
小的数据空间。
i n t e r r u p t类型说明符允许我们编写中断处理程序,说明该类型的函数进入时,会自动保存
各寄存器的值,退出时恢复各寄存器的值,新的中断处理程序先判断当前的显示方式,若是
文本方式,则执行老中断程序,若是图形方式,就执行新的图形拷贝程序。
每当发生中断调用时, D O S转移到一个内部很小的数据栈上工作,为了确保程序能正常
工作,必须建立起自己的数据栈,用寄存器伪变量 S P和S S实现。打印完屏幕后,恢复原环境
(此程序针对 EPSON LQ系列打印机)。
[例9-5] 屏幕图形硬拷贝程序 tx4.c
#include <dos.h>
#include<stdio.h>
#include<graphics.h>
#include<bios.h>
×1000
#define STK_SIZE 0
184 C语言程序设计
下载
#define print(ch) ,ch ,0)
biosprint(0
9.1.6 随意改变VGA显示器显示颜色的技巧
回车后,屏幕上将出现寄存器号、红、绿、蓝的颜色值。
[例9-6] 设置新色彩 setcolor.c。
/* 格式:setcolor< 寄存器中 >< 红 > < 绿 > < 蓝 >*/
#include<dos.h>
#include<stdlib.h>
#include<stdio.h>
/* 检查颜色设置 getcolor.c*/
/* 格式:getcolor< 寄存器号>*/
#include<dos.h>
#include<stdio.h>
#include<stdlib.h>
9.1.7 用随机函数实现动画的技巧
int main( )
{
initialize( ); /*初始化图形系统 */
/* 显示放大字体*/
setcolor(YELLOW) ;
settextstyle(TRIPLEX_FONT ,HORIZ_DIR ,4) ;
settextjustify(CENTER_TEXT ,CENTER_TEXT) ;
outtextxy((getmaXX( )/2-17) ,360 ,"COMPUTER") ;
rbars( ); /* 主程序*/
closegraph( );/* 关闭图形系统 */
return 1;
}
void initialize(void)
{
gdrive=DETECT ;
initgraph (&gdrive ,&gmode ,"") ;
ecode=graphresult( ) ;
if (ecode!=0)
{
printf("Graphice Error : %d\n,g
" rapherrormsg(ecode)) ;
eXit(1) ;
}
getpalette(&palette) ;
mcolor=getmaXcolor( )+1 ;
}
void rbars(void)
{
int color ; /* 画计算机图形*/
setcolor(DARKGRAY) ;
下载
第9章 实用编程技巧 189
bar3d(X1-20 ,y1-20 ,X2+56 ,y2+70 ,0,3) ;
setfillstyle(CLOSE_DOT_FILL ,BLUE) ;
setfillstyle(SOLID_FILL ,RED) ;
circle(X2+28 ,y2+60 ,4) ;
bar(X1+4 ,y1+78 ,X1+20 ,y1+83) ;
setcolor(MAGENTA) ;
circle(X2+28 ,y2+60 ,4) ;
circle(X2+16 ,y2+60 ,4) ;
circle(X2+4 ,y2+60 ,4);
setcolor(WHITE) ;
setfillstyle(SOLID_FILL ,DARKGRAY) ;
bar3d(X1-60 ,y1+120 ,X1+154 ,y1+170 ,0,2) ;
bar3d(X1+120 ,y1+126 ,X1+100 ,y1+164 ,0,2) ;
line (X1+20,y1+145 ,X1+100 ,y1+145) ;
setfillstyle(SOLID_FILL ,GREEN) ;
bar(X1+26 ,y1+130 ,X1+34 ,y1+132) ;
bar(X1+26 ,y1+150 ,X1+34 ,y1+152) ;
setfillstyle(WIDE_DOT_FILL ,RED) ;
bar(X1-24 ,y1+128 ,X1-44 ,y1+142) ;
/*利用随机函数实现矩形画面互相覆盖,产生动感 */
while(!kbhit( ))
{
color=random(mcolor-1)+1 ;
setcolor(color) ;
setfillstyle(random(11)+1 ,color) ;
bar3d(X1+random(getmaXX( )/Xy) ,y1+random(getmaXy( )/Xy)
,
X2+getmaXX( )/Xy,y2+ getmaXy( )/Xy,0,5) ;
}
}
计算机图形动画显示的是由一系列静止图像在不同位置上的重现。计算机图形动画技术
一般分为画擦法和覆盖刷新法两大类。画擦法是先画 T时刻的图形,然后在 T +△T时刻把它擦
掉,改画新时刻的图形是由点、线、圆等基本图元组成。这种一画一擦的方法对于实现简单
图形的动态显示是比较有效的。而当需要显示比较复杂的图形时,由于画擦图形时间相对较
长,致使画面在移动时出现局部闪烁现象,使得动画视觉效果变差。所以,为提高图形的动
态显示效果,在显示比较复杂的图形时多采用覆盖刷新的方法。
在Turbo C 的图形函数中,有几个函数可完成动画的显示:
getimage(int left,int top,int right,int bottom,void far*buf) 函数把屏幕图形部分拷贝
到由buf所指向的内存区域。
imagesize() 函数用来确定存储图形所需的字节数,所定义的字节数根据实际需要可以定
义得多一些。
p u t i m a g e ( )函数可以把 g e t i m a g e ( )存储的图形重写在屏幕上。利用 p u t i m a g e ( )函数中的
C O P Y _ P U T项,在下一个要显示的位置上于屏幕中重写图像,如此重复、交替地显示下去,
190 C语言程序设计
下载
即可达到覆盖刷新的目的,从而实现动画显示。由于图形是一次性覆盖到显示区的,并在瞬
间完成,其动态特性十分平滑,动画效果较好。
程序d h 2 . c就是根据上述思路而实现的。程序运行时,将在屏幕上出现一个跳动的红色小
球。
[例9-8] 动画显示程序dh2.c
#include <stdio.h>
#include<graphics.h>
#include<alloc.h>
#include<conio.h>
void main(void)
{
int driver=DETECT,mode ;
int k=0,i,m,m1 ;
int maXX,mayy ,size ;
char *buf;
9.2 菜单设计技术
菜单在用户编写的程序中占据相当一部分内容。设计一个高质量的菜单,不仅能使系统
美观,更主要的是能够使操作者使用方便,避免一些误操作带来的严重后果。
9.2.1 下拉式菜单的设计
下拉式菜单是一个窗口菜单,它具有一个主菜单,其中包括几个选择项,主菜单的每一
项又可以分为下一级菜单,这样逐级下分,用一个个窗口的形式弹出在屏幕上,一旦操作完
毕又可以从屏幕上消失,并恢复原来的屏幕状态。
设计下拉式菜单的关键就是在下级菜单窗口弹出之前,要将被该窗口占用的屏幕区域保
存起来,然后产生这一级菜单窗口,并可用光标键选择菜单中各项,用回车键来确认。如果
某选择项还有下级菜单,则按同样的方法再产生下一级菜单窗口。
用Turbo C 在文本方式时提供的函数 gettext( ) 来放屏幕规定区域的内容,当需要时用
puttext( )函数释放出来,再加上键盘管理函数 bioskey( ),就可以完成下拉式菜单的设计。
程序m e n u 1 . c是一个简单拉式菜单。运行时在屏幕上一行显示主菜单的内容,当按 ALT+F
则进入 F i l e子菜单,然后可用光标键移动色棒选择操作,用回车确认。用 E s c键退出主菜单,
并可用ALT+X退出菜单系统。
[例9-9] 下拉式菜单 menu1.c
/* 下拉式菜单 menu1.c*/
#include<conio.h>
#include<stdio.h>
#include<stdlib.h>
#include<bios.h>
void main(void)
{
192 C语言程序设计
下载
int i,key ,key0 ,key1 ,y,test ;
char *m[ ]={"File " ,"Edit ","Run ","Compile ","Projsct ",
"Options ","Debug ","Break/watch "}; /*定义主菜单的内容 */
char *f[ ]={"Load F3", /* 定义FILE 子菜单的内容*/
"Pick ALT+F3",
"New ",
"Save F2",
"Write to ",
"Directory ",
"Change dir ",
"Os shell ",
"Quit ALT+X"};
char buf[16*10*2],buf1[16*2] ; /* 定义保存屏幕区域的数组变量 */
textbackground(BLUE) ; /* 设置文本屏幕背景色 */
clrscr( ); /* 屏幕背径着色*/
window(1 ,1,80 ,1); /* 定义一个文本窗口 */
textbackground(WHITE) ; /* 设置窗口背景色*/
textcolor(BLACK) ;
clrscr( );
window(1 ,1,80 ,2) ;
for (i=0;i<8 ;i++)
cprintf("%s" ,m[i]) ; /*显示主菜单的内容 */
while(1)
{
key=0 ;
while(bioskey(1) == 0) ; /* 等待键盘输入 */
key = bioskey(0); /* 取键盘输入码 */
key = key&0Xff? 0:key>>8 ; /*只取扩充键码 */
if(key == 45) eXit (0) ; /* 如果按ALT+X 键则退出*/
if(key == 33) /* 如果按ALT+F 则显示子菜单*/
{
textbackground(BLACK) ;
textcolor (WHITE);
gotoxy(4 ,1);
cprintf("%s" ,m[0]) ;
gettext(4 ,2,19 ,11,buf) ;/* 保存窗口区域的在原有内容*/
window(4 ,2,19 ,11) ;
textbackground(WHITE) ;
textcolor(BLACK) ;
clrscr( );
window(4 ,2,19,12) ;
gotoxy(1 ,1) ; /*作一个单线形边框 */
putch(0xff) ;
for (i=2;i<10 ;i++)
{
gotoxy(1 ,i) ; putch(0 ×b3) ;
gotoxy(16 ,i) ; putch(0 ×b3) ;
}
gotoxy(1 ,10) ;
下载
第9章 实用编程技巧 193
putch(0Xc0) ;
for (i=2;i<16 ;i++)
putch(0Xc4) ;
putch(0Xd9) ;
for (i=2;i<10 ;i++)
{
gotoxy(2 ,i) ;
cprintf("%s" ,f[i-1]) ;
}
gettext(2 ,2,18 ,3,buf1) ;
textbackground(BLACK) ;
textcolor(WHITE) ;
gotoxy(2 ,2);
cprintf("%s" ,f[0]) ;
y=2 ;
key1=0 ;
while((key0!=27)&&(key1!=45)&&(key0!=13))
{/* 输入为ALT+X ,回车或ESC 键退出循环*/
while(bioskey(1)==0) ; /* 等待键盘输入 */
key0=key1=bioskey(0) ; /*取键盘输入码 */
key0=key0&0Xff ; /* 只取扩充码*/
key1=key1&0Xff? 0:key1>>8 ;
if (key1==72||key1==80) /*如果为上下箭头键 */
{
puttext(2 ,y,18 ,y+1 ,buf1) ; /*恢复原来的信息 */
if (key1==72) y= y==2? 9:y-1 ; /* 上箭头处理*/
if (key1==80) y= y==9? 2:y+1 ; /* 下箭头处理*/
getteXt(2 ,y,18 ,y+1 ,buf1) ; /* 保存新色棒前产生这一位置屏幕内容 */
textbackground(BLACK) ; /*产生新色棒*/
textcolor(WHITE) ;
gotoxy(2 ,y) ;
cprintf("%s" ,f[y-1]) ;
}
}
if(key1 == 45) eXit(0) ; /* 按ALT+X 退出*/
if(key0 == 13) /* 回车按所选菜单项进行处理 */
{
switch(y)
{
case 1:
break ;
case 2:
break ;
case 9:
eXit(0) ;
default:
break ;
}
}
194 C语言程序设计
下载
else /*按ESC 键返回主菜单*/
{
window(1 ,1,80 ,2) ;
puttext(4 ,2,19 ,11 ,buf) ; /*释放子菜单窗口占据的屏幕原来内容 */
textbackground(WHITE) ;
textcolor(BLACK) ;
gotoxy(4 ,1) ;
cprintf("%s" ,m[0]) ;
}
}
}
}
9.2.2 选择式菜单的设计
所谓选择式菜单,就是在屏幕上出现一个菜单,操作者可根据菜单上所提供的数字或字
母按相应的键去执行特定的程序,当程序执行完后又回到主菜单上。
这种菜单编制简单,操作方便,使用灵活,尤其适用于大型管理程序。如果在自动批处
理文件上加入这种菜单后,操作者可根据菜单上的提示,进行相应的操作,这样可以简化许
多步骤,对一般微机用户来说是比较适合的。
[例9-10] 选择式菜单程序 menu2.c
#include<stdio.h>
#include<stdlib.h>
#include<conio.h>
main( )
{
char ch;
int i;
do {
system("cls") ;
printf("\n\t1. into Turbo C ;")
printf("\n\t2. into Windows "; )
printf("\n\t3. into Wps ;")
printf("\n\t4. into Dbase ; ")
printf("\n\t0. Quit \n\n ;")
printf("\t ;
Please select:")
ch=getch( );
switch(ch)
{
;break ;
case '1' : system("tc")
;break ;
case '2' : system("win")
;break ;
case '3' : system("wps")
;break ;
case '4' : system("dbase")
;eXit(1) ;
case '0' : system("cls")
default:
;
printf("\n\t*** wrong !!! ***\n")
下载
第9章 实用编程技巧 195
for (i=0;i<600 ;i++) ;
{ ; }
}
}
while (1);
}
9.2.3 实现阴影窗口的技巧
目前,许多应用软件都采用了输出窗口技术,有的甚至使用了带有阴影的输出窗口。这
种技术给人们以新鲜醒目的感觉,可达到事半功倍的作用。
程序 m e n u 3 . c是一个阴影窗口的例子。其中用到两个自编函数,一个是建立窗口函数
set_win( ),另一个是建立带有阴影部分的窗口函数 set_bkwin( )。这两个函数需要传递以下几
个参数:
int X1,y1 ,X2,y2 ,b,bc ,tc
char *head
void main(void )
{
set_bkwin(1 ,2,25 ,18,2,2,1,"window") ;
getch() ;
196 C语言程序设计
下载
}
9.3 音响技巧
9.3.1 音乐程序设计
我们知道,音乐是音高和音长的有序组合,设计微机音乐最重要的就是如何定义音高和
音长,以及如何让扬声器发出指定的音符。下面给出音符与频率的关系表。 C语言提供的三个
函数s o u n d ( )、n o s o u n d ( )和c l o c k ( )可以很方便地解决上述的问题。 s o u n d ( )函数可以用指定频率
打开P C机扬声器直到用 n o s o u n d ( )函数来关闭它; c l o c k ( )函数正好用来控制发声时间,而且它
不受PC机主频高低的影响。下面这段程序可使微机发出 c调1的声音。
表9-2 音符与频率关系表
c d e f g a b
音符
1 2 3 4 5 6 7
频率 262 294 330 349 392 440 494
c d e f g a b
单符
1 2 3 4 5 6 7
频率 523 587 659 698 784 880 988
c d e f g a b
音符
1 2 3 4 5 6 7
频率 1047 1175 1319 1397 2568 1760 1976
[例9-12] 音乐程序music1.c
#include<stdio.h>
#include<dos.h>
void pause(int);
void sound1(int,int) ;
void main(void)
198 C语言程序设计
下载
{
int i,freq ,speed=5 ;
int time=4*speed;
char *qm="iddgwwwqqgfff dddfghhhggg ddgwwwqqgfff\
ddffhjqqqqq wpggjhgddgqq hhqwwqjjjggg\
ddgwwwqqqgfff ddffhjqqqqqq" ;/* 定义歌曲*/
while (*qm++ !='\0'){
i=1 ;
switch(*qm){
case 'k':
time=1*speed ; i=0 ;
break ;
case 'i':
time=6*speed ; i=0 ;
break ;
case 'o':
time=10*speed ; i=0 ;
break ;
case 'p':
pause(time) ; i=0 ;
break ;
case 'a':
freq=523 ;
break ;
case 's':
freq=587 ;
break ;
case 'd':
freq=659 ;
break ;
case 'f':
freq=698 ;
break ;
case 'g':
freq=784 ;
break ;
case 'h':
freq=880 ;
break ;
case 'j':
freq=988 ;
break ;
case 'z':
freq=262 ;
break ;
case 'X':
freq=294 ;
break ;
case 'c':
下载
第9章 实用编程技巧 199
freq=330 ;
break ;
case 'v':
freq=349 ;
break ;
case 'b':
freq=392 ;
break ;
case 'n':
freq=440 ;
break ;
case 'm':
freq=494 ;
break ;
case 'q':
freq=1047 ;
break ;
case 'w':
freq=1175 ;
break ;
case 'e':
freq=1319 ;
break ;
case 'r':
freq=1397 ;
break ;
case 't':
freq=2568 ;
break ;
case 'y':
freq=1760 ;
break ;
case 'u':
freq=1976 ;
break ;
default:
i =0;
break ;
}
if(i)
sound1(freq ,time) ;
}
}
,int time) /*freq为频率,time为持续时间 */
void sound1(int freq
{
union {
long divisor;
unsigned char c[2];
} count;
200 C语言程序设计
下载
unsigned char ch;
9.3.2 自动识谱音乐程序
音乐的简谱是由各种音符构成的,将这些音符按不同的频率、持续时间连续发出声音,
就形成了旋律。因此音乐演奏的关键是曲调的定义与识别及发音时间的控制。
为了实现计算机自动识谱,可定义一套曲调的编码,其中“ 1 2 3 4 5 6 7”表示中音的 1、2、
3、5、6、7;高音可在中音之后加“ *”;低音在中音之后加“;”号;减号“ -”表示两
拍;“.”表示一拍半;“ =”表示四分之一拍;下画线“ _”表示1/2拍。
我们可用文本编辑软件(如 E D I T)按上述编码将一首曲子的乐谱输到计算机中,得到乐
谱文件供程序调用。其中乐谱文件的第一节的数字分别为:节拍基数和速度,输入时用空格
分开。乐谱文件的第二行到最后一行为歌曲的内容,每小节之间用“ /”分开。
下面根据“世上只有妈妈好”编制曲谱文件如下Ma.txt:
8 50
6.5_3 5 /1* 6_5_6-/3 5_6_5 3 /1_6;5_
3_2-/2_3_5 5_6_/3 2 1-/5.3 2_/_/6.;/_5-/
9.3.3 实现后台演奏音乐的技巧
即L1为1拍,L2为1/2拍,L4为1/4拍。
后台演奏,可通过修改 1 C向量来实现。计算机每秒发出 1 8 . 2次中断调用 1 C,因此,就可
以通过它来计算,实现后台演奏。
程序P L AY. C只是一个简单的后台演奏音乐的例子,将其编译后,就可在 D O S提示符后直
接执行,演奏过程中,按任一键都将停止演奏。
[例9-13] 后台演奏程序PLAY.C
#include<stdio.h>
#include<dos.h>
#include<bios.h>
#include<conio.h>
#define L1 1000
#define L2 L2/2
#define L4 L1/4
int HZ[4][7]={
{131 ,147 ,165 ,175 ,196 ,220 ,247} ,
{262 ,294 ,330 ,349 ,392 ,440 ,494} ,
{523 ,587 ,659 ,698 ,784 ,880 ,980}
};
int *s;
int buf[100]={11 ,12 ,12 ,12 ,13 ,12 ,14 ,12 ,16 ,12 ,17 ,12 ,21 ,11 ,22 ,12 ,
204 C语言程序设计
下载
23 ,12 ,24 ,12,25 ,12 ,26,12 ,27 ,12,31 ,12 ,0,0,0};
void main(void)
{
play(buf) ;
; /* 判断结束条件 */
while (*s && ! bioskey(0))
nosound() ;
setvect(0X1c ,old_int9) ;
}
第10章 C++入门
90年代以来,面向对象的程序设计( Object-Oriented Programming,简称OOP)异军突起,
迅速在全世界流行,一跃成为主流的程序设计技术。在软件市场中,覆盖面大、垄断市场的
新一代程序设计语言、软件开发工具和环境以及操作系统大多是面向对象的。
10.1 面向对象的概念
10.1.1 面向对象的程序结构
面向对象的程序设计是一种基于结构分析的、以数据为中心的程序设计方法。在面向对
象的程序中,活动的基本单位是对象,向对象发送消息可以激活对象的行为。为此,许多人
把面向对象的程序描述为:
程序=对象+消息传递
1. 对象
对象类似于 C语言中的变量,可以泛指自然界中的任何事务,包括具体实物和抽象概念。
对象具有一些属性、状态和行为。例如,每个人都有姓名、性别、年龄、身高、体重等属性,
有工作、学习、吃饭、睡觉等行为。所以,对象一般可以表示为:属性 +行为。在面向对象的
程序设计中,对象被表示为:数据 +操作,操作也称为方法。这就是说,面向对象程序设计中
的对象是指由一组数据和作用于其上的一组方法组成的实体。
2. 类
在面向对象的程序设计中,会涉及到许多对象,我们无法将所有的对象都描述清楚,如
果那样做的话,程序将无限长或无法描述。因此在面向对象的程序设计中引入了类的概念,
将同类的对象归于一类,同类对象具有相同的属性和行为,如各种花同属一类,各种草、植
物等也分别属于不同的类。
3. 消息
消息就是对对象进行某种操作的信息。当要求对象执行某种特定操作时,就向该对象发
送操作消息,对象接收到指定操作的消息后,就调用相应的操作方法,完成有关操作。
消息及其传递机制是面向对象程序设计的一个重要角色。对象的一切活动,都要通过消
息来驱动,消息传递是对象间通信的唯一途径。
4. 方法
方法就是对对象进行的某种操作。当对象接收到相应的消息时,就调用对应的方法完成
指定的操作,有了消息,就驱动对象工作;有了方法,就能实现消息所要求的操作。
5. 继承
继承是面向对象语言的另一个重要概念。在客观世界中,存在着整体与个体的关系、一
般与特殊的关系,继承将后者模型化。
206 C语言程序设计
下载
例如,对人的分类我们用图 10-1描述如下。
在类的层次结构图中,下层节点都具有上层节点的特性,都具备人的共同特点。但下层
节点较之上层节点而言,又具有新的特性,是上层节点 人
所不具有的。这种下层节点对上层节点的特性的保持,
就是我们所说的继承。
在面向对象语言中,类功能支持这种层次结构。除 工人 农民 解放军
了根结点外,每个类都有它的超类,又称为父类或基类。
除了叶结点外,每个类都有它的子类,又称为派生类。
一个子类可以从它的基类继承所有的数据和操作,并扩
海军 空军 陆军
充自己的特殊数据和操作。基类抽象出共同的属性和操
作,子类体现其差别。有了类的层次结构和继承性,不 图10-1 对人的分类
同对象的共同特性只需定义一次,用户就可以充分利用已有的类,进行完善和扩充,达到软
件可重用的目的。
10.1.2 C++的类
C + +语言是一种面向对象的程序设计语言,是对传统 C语言的完善和扩充,并支持面向对
象的概念:对象、类、方法、消息和继承,下面给出一个 C++关于类的结构:
[例10-1] 栈操作。栈是一种后进先出的数据结构,我们利用数组这个静态的存储来实现
栈、充当栈,完成数据的压栈和出栈。
#include "iostream.h"
#define SIZE 100
// 定义栈类型
class stack /* 定义类*/
{
int stck[SIZE]; /* 数据成员,整型数组做堆栈 */
int top; /* 栈顶指针*/
public:
void init(void); /* 初始化成员函数*/
void push(int i); /* 压栈*/
int pop(void); /* 出栈*/
};
// 栈的初始化
void stack:::init(void) /* 类成员函数的定义 */
{
top=0; /* 定义栈顶指针指向数组头*/
}
//进栈操作
void stack::push(int i)
{
if (top= =SIZE)
{
cout<<"The stack is full!"; /* 栈满*/
return;
下载
第10章 C++入门 207
}
stck[top]=i; /* 压入数据到栈顶 */
top++; /* 指针加1*/
}
// 出栈操作
int stack::pop(void)
{
if (top= =0)
{
cout<<"The stack is underflow!"; /* 栈空,无数据 */
return 0;
}
top- -; /* 指针减1*/
return stck[top]; /* 返回栈顶元素 */
}
void main(void)
{
stack stack1, stack2; //创建对象,
stack1.init(); /*调用成员函数,对栈初始化 */
stack2.init();
stack1.push(1); //在stack1 栈中,压栈1。
stack2.push(2); //在stack2 栈中,压栈2。
stack1.push(3);
stack2.push(4);
cout<<stack1.pop()<<" "; 输出s // tack1 栈顶数据,即弹出
cout<<stack2.pop()<<" ";
cout<<stack1.pop()<<" ";
cout<<stack2.pop()<<"\n ";
}
10.2 C++的输入与输出
C++标准流的输入输出可以在 iostream.h文件中找到。cout是C++中与标准输出设备相关的
输出流,“< <” 是运算符,该运算符完成将引号内的字符串写到标准输出流 c o u t,简单地说,
就是将字符串“ H e l l o,Wo r l d !”写到标准输出设备—显示器上,为此,运行程序,我们将
在屏幕上看到:
Hello ,World!
[例10-3] 利用海伦公式,输入三角形的三条边,若满足任意两边之和大于第三边,则计
算出给定三角形的面积。
#include <iostream.h>
#include <math.h>
main()
{
float s,s1 ,a1 ,a2 ,a3; // a1,a2 ,a3是三角形的三条边; s 是三角形的面积;
// s1是二分之一的周长
cout<<"a1=";
cin>>a1; /* 键盘输入*/
cout<<"a2=";
cin>>a2;
cout<<"a3=";
cin>>a3;
if (((a1+a2)>a3)&&((a1+a3)>a2)&&((a2+a3)>a1))
//任意两边之和应大于第三边
{
s1=(a1+a2+a3)/2;
s=sqrt(s1*(s1-a1)*(s1-a2)*(s1-a3)); // 计算面积
cout<<"area="<<s;
cout<<"\n";
}
else
cout<<"error!";
return 0;
}
10.3 类与对象
客观世界中的事物都包含属性和行为两个方面。在C++程序设计中,对事物的描述分别用数
据成员和成员函数来表现,并把它们封装起来,形成一个抽象的数据类型—类。这就是说,类
下载
第10章 C++入门 209
具有两种成员:数据成员和成员函数,按照面向对象的概念,成员函数又称为方法函数或方法。
10.3.1 类的定义与对象的引用
1. 类的定义
类定义的基本格式如下所示:
class 类型名
{
private:
私有成员声明;
protected:
保护成员声明;
public:
公有成员声明;
}
类成员分为私有成员和公有成员两部分。外界不能访问一个对象的私有成员,只能与对
象的公有成员之间进行信息交换。定义类即是确定选择成员并区分它们的访问权限。
[例10-4] C++程序结构示例。
#include <stdio.h>
class exam1 // 定义类
{
private: // 类的私有成员
int x,y; //数据成员
public: // 类的公有成员 类的声明部分
void init(); // 成员函数
float average();
void print();
};
void exam1::init() // 类成员函数的定义
{
x=3;
y=4;
}
float exam1::average() //类成员函数的定义 类的实现部分
{return (x+y)/2.0;
}
void exam1::print() // 类成员函数的定义
{
printf("\nx=%d ,y=%d ,aver=%7.2f\n" ,x,y,average());
}
main() // 主函数
{
exam1 obj; //声明并创建一个对象
obj.init(); // 调用成员函数初始化 类的使用
obj.print(); //输出运算结果
return 0;
}
210 C语言程序设计
下载
1) 类在 C++中用关键字 class来说明,紧跟其后的是类名 exam1,类中包含两个私有数据成
员x、y和三个公有的成员函数 init()、average()、print()。
C++允许隐藏内部状态,由 private开始的私有段成员就满足这一特性,它们只能允许该类
对象的成员函数来访问。类中的公有段既可以是数据成员,也可以是成员函数,这是类提供
给外部的接口。当然, C++还提供另一种保护段符号: protected ,下面会介绍到。
运行该程序,得到的输出显示为:
x=3 , y=4 , aver= 3.50
2) 类的成员在类的定义中出现的顺序可以任意,并且类的实现既可以放在类的外面,又
可以内嵌在类内,下面调整类成员的顺序为:
class exam1 // 定义类
{
public:
float average();
void print();
private:
int x,y;
public:
void init();
};
3) 若类的实现定义在类的外面,在成员函数的函数头中函数名前,应使用作用域限定符::
指明该函数是哪一个类中的成员函数,即有如下格式。
类型 类名::成员函数名(参数表)
{
函数体
}
4) 除特殊指明外,成员函数操作的是同一个对象的数据成员。下面的示例将类成员函数
的实现内嵌在类中:
class exam1 //定义类
{
public:
float average()
{
return (x+y)/2.0;
}
void print()
{
printf("\nx=%d ,y=%d ,aver=%7.2f\n" ,x,y,average());
}
private:
int x,y;
public:
void init()
{
x=3;
y=4;
}
};
下载
第10章 C++入门 211
5) 类定义的最后一个花括号的外面一定要有分号结束。程序中出现的“ //”符号是作为注
释开始的标志,与 C语言中“/* ...... */”用法完全相同。
6) 使用public、private和protected关键字
public、private和protected关键字称为访问说明符。
说明为p u b l i c的类成员可以被任何函数所使用(当它们是数据成员时)或调用(当它们是
成员函数时)。调用者不必属于这个类或任何类。
说明为 p r i v a t e的类成员只能被同类中的成员函数所使用或调用,也可以被同类的友元使
用或调用。在类的成员中,若没有额外声明访问权限,则表明为 private的类成员。
说明为 p r o t e c t e d的类成员只能被同类中的成员函数或同类的友元类,或派生类的成员函
数及友元类所使用或调用。
2. 类与对象
上述类e x a m 1提出了两个概念:类与对象。从形式上看,类与对象的关系类似于 C语言中
的数据类型与变量的关系,类是将具有相同属性和行为的事物做一个概括,它是普遍意义上
的一般概念,而对象是具有类特征的具体事物。一旦定义了类,那么就有无数的具有该属性
和行为的对象与之对应。
类在概念上是一种抽象机制,它抽象了一类对象的存储和操作特性;在系统实现中,类
是一种共享机制,它提供了一类对象共享其类的操作实现。
类是对象的模板,对象承袭了类中的数据和方法,只是各对象具有的初始化数据不同,
所表示的对象状态也不同。
一个C + +文件可以作为一个文件存储,其文件的扩展名为“ . c p p”,也可以作为几个文件
存储。若作为几个文件存储,一般说来应把类的声明部分存于“ . h”的头文件中,而把类的
实现部分和类的使用部分分别存于扩展名为“ . c p p”的文件中。包含主函数的 . c p p文件中应包
含.h和其它.cpp文件。规模较大的程序,应采用模块化的程序设计技术。
C + +程序的编辑、编译、连接及运行的方法和过程,在 D O S 下,与 C语言基本一样。
Borland C++或是Turbo C++,均有一个集成开发环境,易学易用(与 Turbo C大同小异),操
作非常方便。
10.3.2 构造函数与析构函数
C + +中,类是一种数据类型,这样的类型总与存储空间相关,即要占用一定的内存资源。
当我们定义了对象时,编译系统就会为它分配存储,进行一定的初始化,由于类的结构各不
相同,所需的工作量也各不相同,为此, C + +提供构造函数来完成上述工作。构造函数是属
于某一特定类,可由用户设置,也可用系统缺省设置。与之相对应的是类的析构函数,当类
的对象退出作用域时,析构函数负责回收存储空间,并做一些必要的善后处理。析构函数也
是属于某一特定类,可由用户设置,也可用系统缺省。
1. 构造函数
当定义一个对象时,我们需要给对象开辟一个存储空间,将对象的数据成员初始化。在
使用构造函数以前,首先对构造函数作如下说明:
1) 构造函数具有与类名相同的函数名。
2) 构造函数没有返回类型,即使void也不可以。它的返回值是隐含的,是指向类本身的指针。
212 C语言程序设计
下载
3) 构造函数在对象被定义时自动调用,作相应的初始化。
4) 构造函数可以有参数,也可无参数。
5) 构造函数名可以重载。
6) 当类中无与类名相同的构造函数时, C++编译系统为其设置缺省的构造函数。
[例10-5] 构造函数应用举例
#include <stdio.h>
class A{
int a,b,c; // 缺省访问权限,为私有数据成员
public: // 公有段
A(int=1 ,int=2 ,int=3); // 构造函数1
A(double ,double ,double); // 构造函数2
A(long); // 构造函数3
A(A&); // 构造函数4(拷贝构造函数)
void show() // 公有成员函数
{
printf("%d , %d , %d\n" ,a,b,c);
}
};
A::A(int I1,int I2,int I3) // 构造函数1的实现
{
a=I1; b=I2; c=I3;
}
A::A(double f1,double f2,double f3) // 构造函数2的实现
{
a=(int)f1; b=(int)f2; c=(int)f3;
}
A::A(long n) // 构造函数3的实现
{
a=b=c=(int)n;
}
A::A(A& other) // 构造函数4的实现
{
a=other.a;
b=other.b;
c=other.c;
}
main()
{
A x1; // 定义对象x1,调用缺省参数的构造函数1
x1.show(); // 调用公有段成员函数 show()
A x2(3); // 定义对象x2,调用构造函数 1
x2.show(); // 调用公有段成员函数 show()
A x3(3,1); // 定义对象x3 ,调用构造函数 1
x3.show(); // 调用公有段成员函数 show()
A x4(3.14,2.414 ,6.28); // 定义对象x4 ,调用构造函数 2
x4.show(); // 调用公有段成员函数 show()
A x5(53L); // 定义对象x5 ,调用构造函数 3
x5.show(); // 调用公有段成员函数 show()
下载
第10章 C++入门 213
A x6=x5; // 定义对象x6,调用拷贝构造函数 4
x6.show(); // 调用公有段成员函数show()
return 0;
}
运行上述程序,得如下输出:
1,2,3
3,2,3
3,1,3
3,2,6
53 ,53,53
53 ,53,53
可以提供不带参数的构造函数,即是缺省的构造函数。例如:
class A{
...
A();
...
};
A::A ()
{
a=0; b=0; c=0;
}
但要注意的是,不能将可缺省参数的构造函数与缺省的构造函数一起使用,以免编译系
统混淆。例如:
class A{
...
A( );
A(int=1 , int=2 , int=3);
...
};
void main( )
{
A obj; // 编译系统无法区分应调用哪一个构造函数
...
}
2. 析构函数
与构造函数对应的是析构函数。 C++用析构函数来处理对象的善后工作,在对象撤销时自
动调用,并可能要释放一些动态的存储空间等。析构函数具有如下的一些特点:
1) 与类同名,之前冠以波浪线,以区别构造函数。
2) 不指定返回类型。
3) 不能指定参数。
4) 一个类只能有一个析构函数。
析构函数可以这样写:
class A{
...
214 C语言程序设计
下载
public:
...
~
A();
};
A::A(){...} // 析构函数定义
[例10-6] 我们将构造函数进行编号,在程序的运行中去发现对象被定义后,调用的构造
函数及对象被撤销时,调用的析构函数的处理过程。
#include <stdio.h>
class A{
int a,b,c,number; // 类的私有成员,number 用于标志构造函数
public:
A(int=1 ,int=2 ,int=3); // 类的构造函数
A(double ,double ,double);
A(long);
A(A&);
~A(){ // 类的析构函数
,number);
printf("object x destroyed by constr %d created\r\n"
}
void show()
{
printf("%d , %d, %d\n" ,a,b,c);
}
};
A::A(int i1,int i2,int i3) // 构造函数1
{
a=i1; b=i2; c=i3; number=1;
}
A::A(double f1,double f2,double f3) // 构造函数2
{
a=(int)f1; b=(int)f2; c=(int)f3; number=2;
}
A::A(long n) // 构造函数3
{
a=b=c=(int)n; number= 3;
}
A::A(A& other) // 拷贝的构造函数4
{
a=other.a;
b=other.b;
c=other.c;
number=4;
}
main()
{
A x1; // 定义对象X1 调用构造1
x1.show();
A x2(3); // 定义对象X2 调用构造1
x2.show();
A x3(3,1); // 定义对象X3调用构造1
x3.show();
下载
第10章 C++入门 215
A x4(3.14,2.414 ,6.28); //定义对象X4 调用构造2
x4.show();
A x5(53L); //定义对象X5 调用构造3
x5.show();
A x6=x5; // 定义对象X 6调用构造4
x6.show();
return 0; //各对象调用相应的析构函数。
}
程序的输出:
1,2,3
3,2,3
3,1,3
3,2,6
53 ,53,53
53 ,53,53
object x destroyed by constr 4 created
object x destroyed by constr 3 created
object x destroyed by constr 2 created
object x destroyed by constr 1 created
object x destroyed by constr 1 created
object x destroyed by constr 1 created
从上述输出结果来看,对象一旦定义,就必须要调用构造函数,退出时要调用析构函数。
先定义的对象最后被毁灭或后定义的对象最先使用析构函数。
10.3.3 函数重载
上述程序中出现的构造函数,在形式上看,参数各不相同。正是由此,被定义的对象根
据参数形式的不同,调用不同的构造函数,这个过程我们称为函数的重载。当然,不仅构造
函数可以重载,其它的类成员函数也同样可以重载。
[例10-7] 类的成员函数的重载。
#include <stdlib.h>
#include <string.h>
#include <iostream.h.>
class string{
int length;
char str[256];
public:
string(){ length=0; ,"");}
strcpy(str
// 类的构造函数 1,完成字符串的初始化
string(char *); //重载的构造函数 2
char * search(char); // 返回字符指针的成员函数1
char * search(char *); // 返回字符指针的重载成员函数 2
};
string::string(char *text) // 构造函数2的定义
{
if (strlen(text)<256) // 若串text 的长度小于256
strcpy(str ,text); // 将串text 复制给串str
216 C语言程序设计
下载
else
strncpy(str ,text ,255); // 若串text 的长度超过255
// 则将串text 的255 个字符复制给串 str
length=strlen(str);
}
char *string::search(char arg)// 成员函数1的定义
{
return strchr(str,arg); // 返回串str 中第一次出现字符 arg 的位置
}
char *string::search(char *arg)// 重载成员函数2的定义
{
return strstr(str,arg); // 返回串str 在串arg 中第一次出现的位置
}
void main()
{
string hellomsg='Hello ,there ,I'm a string!';
// 定义串string 的对象,自动调用构造函数1
char *found; // 定义字符串found
cout<<hellomsg.search('t')<<"\r\n";
// 输出对象的成员函数 1的返回值
cout<<hellomsg.search("string")<<"\r\n";
// 输出对象的成员函数 2的返回值
}
运行程序,输出为 :
Hello, there,I'm a string!
string!
10.3.4 友元
在类成员的介绍中,我们对类的三种成员做过详细的说明,特别是强调了类的私有成员
和类保护段成员的隐蔽性,它们只能被类对象的成员函数所访问,这也同样表明,类的成员
函数可以访问类中的所有成员。友元是 C + +提供给外部的类或函数访问类的私有成员和保护
成员的一种途径。
在一个类中,将friend加在某个函数或某个类的前面,则该函数或类将成为所在类的友元。
友元不受它们在类中出现次序的影响,而仅表明其是类的友元。
[例10-8] 说明类的友元函数的作用及与成员函数的区别。
#include <iostream.h>
#include <string.h>
class stud{
char *name,*num ,*tel; // 类的私有成员:姓名,学号,电话号码
public:
stud(char *na,char *nu, char *pho ) // 类的构造函数
{
name=new char[strlen(na)+1];
//使用运算符 new 向系统申请 name 所需的存
储空间
strcpy(name ,na); // 将参数na的值复制给姓名 name
下载
第10章 C++入门 217
num=new char[strlen(nu)+1];
// 使用运算符new 向系统申请num 所需的存储空间
strcpy(num ,nu); // 将参数nu的值复制给学号 num
tel=new char[strlen(pho)+1];
// 使用运算符new 向系统申请tel 所需的存储空间
strcpy(tel ,pho); // 将参数pho 的值复制给电话号码 tel
}
void show(stud&); // 类的公有成员函数
friend void show(stud&); // 类的友元函数
~stud() // 类的析构函数
{ delete name; delete num; delete tel;}
// 使用运算符delete 释放类的各数据成员所占存储空间
};
void show(stud &student) //友元函数的定义
{
cout<<" 类的友元函数的调用:\n";
cout<<"student\"s name is:"<<student.name
<<"\nstudent\"s number:"<<student.num
<<"\nstudent\"s telephone:"<<student.tel<<"\n";
cout<<" 友元函数调用类的成员函数: \n";
student.show(student); //调用类的成员函数
}
void stud::show(stud &student) // 类的成员函数的定义
{
cout<<" 类的成员函数的调用:\n";
cout<<"student\"s name is:"<<student.name
<<"\nstudent\"s number:"<<student.num
<<"\nstudent\"s telephone:"<<student.tel<<"\n";
}
void main()
{
stud x("li-ling" ,"j98-10323" ,"0285533123"); // 定义类的对象 x
show(x); //调用类的友元函数
x.show(x); // 调用类的成员函数
}
上述程序中,类的友元函数和类的成员函数完成相同的功能,并且函数名也完全相同,
不同的只是说明和定义的形式。让我们先看一下程序运行后的输出:
类的友元函数的调用:
student\'s name is: li-ling
student\'s number: j98-10323
student\'s telephone: 0285533123
友元函数调用类的成员函数:
student\'s name is: li-ling
student\'s number: j98-10323
student\'s telephone: 0285533123
类的成员函数的调用:
218 C语言程序设计
下载
student\'s name is: li-ling
student\'s number: j98-10323
student\'s telephone: 0285533123
顺便需要说明的是:类的友元函数的定义既可放在类内,也可在类外,与成员函数是一
致的。如放在类内,则不受段访问特性的影响。
友元可以是多个类的友元,即可以跨类访问。
[例10-9] 关于跨类友元的访问,程序能验证其各位数的立方和等于其数本身的任一个三
位数,如: 13+53+33=150等。
#include <iostream.h>
class hundreds; // 类的声明。在类未定义前需使用时,必须先声明。
class cubic // 类cubic 的定义
{
int sum;
public:
void set_sum(int a,int b,int c){ sum=a*a*a+b*b*b+c*c*c;}
// 类的成员函数用于求其各位数的立方和。
friend int equal(cubic ,hundreds
c h); // 类的友元函数
} cub; // 在类定义的同时定义对象cub
class hundreds{// 类hundreds 的定义
int num;
public:
void set_num(int a,int b,int c){
num=100*a+10*b+c;
}
// 类的成员函数用于求其三个数构成的三位数
friend int equal(cubic ,hundreds
c h);//类的友元函数
void display(void){cout<<num<<endl;}类的成员函数 //
}hun; // 在类定义的同时定义对象hun
int equal(cubic c,hundreds h){ return !(c.sum-h.num);}
// 类cubic 和类hundreds 的友元函数equal 的定义,用于验证由三个数构成的
main(void)
{
int d3,d2,d1;
for (d3=1;d3<10;d3++)
for (d2=0;d2<10;d2++)
for (d1=0;d1<10;d1++)
{
cub.set_sum(d3 ,d2 ,d1);
// 对象cub 调用其成员函数求得三个数的立方和
hun.set_num(d3 ,d2 ,d1);
// 对象hun 调用其成员函数求得构成的三位数
if (equal(cub,hun)) hun.display(); // 相等则显示出三位数
}
return 0;
}
该程序的输出是:
150
370
371
407
下载
第10章 C++入门 219
一定请注意,友元函数在使用时,与成员函数的使用有所区别,它不能在函数名前加上
类名作函数调用的限定,即不能写成 cubic::equal或hundreds::equal。同时,我们也可以将类声
明为友元。
class Y; //类的声明
class X{
friends Y; //说明类Y是类X的友元
int data1;
…
void fun_mem1(…){ …}
…
};
class Y{
float data2;
…
void fun_mem2(…){ …}
…
};
10.4 对象指针
当我们需要动态地在存储空间上使用对象时,就需要定义对象指针。正如在 C语言中使用
指针变量一样,什么时候需要,就使用 m a l l o c ( )为其分配内存,使用完毕,就利用 f r e e ( )释放
其占用的内存。
1. 运算符new与delete
在C + +中,运算符 n e w和d e l e t e可以让用户创建任意持续时间的动态变量,也可以让用户
为任意种类的 C语言的对象分配内存空间。通常,我们用 n e w来为C或C+ +的对象动态地分配
存储空间,用 delete来将占用内存的对象移走或称为释放,其意义与 C语言提供的 malloc()功能
与free()功能类似。
运算符new和delete的用法很简单,它们均为单目运算符:
例:我们定义对象指针如下:
int *num1;
float *num2;
double *num3
char *str1;
class X{…}*objx;
为对象指针分配内存空间:
num1=new int;
num2=new float;
num3=new double;
str1=new char[80];
objx=new X;
利用new运算符分配空间的对象,使用完毕后应通过 delete对所占空间进行释放。
220 C语言程序设计
下载
delete num1;
delete num2;
delete num3;
delete []str1;
delete objx;
2. 动态地创建类对象
生活中常会遇到的实际问题是处理的数据量不固定,数据的个数在动态地改变。链表技
术就是为解决这一问题应运而生的,这种特定的数据结构中每一个节点用于存放一组数据,
随着数据的不断增加,链表也在不断增长,占用的内存不断增加。当问题得到解决后,数据
不再需要保存,占用的内存就释放掉,这个过程就是数据的动态存储。
[例10-10] 动态存放一组字符串在一双向链表,链表结构如图 10-2所示。
head
NULL prev prev
图10-2 链表结构示意
双向链表的操作有:
1) 移动指针到链表头。
2) 移动指针到链表尾。
3) 在链表尾部追加一个字符串。
4) 从链表开始,按字母的排列顺序插入一个字符串。
5) 释放链表各节点所占内存。
程序如下:
#include "stdlib.h"
#include <stdio.h>
#include <string.h>
#include <conio.h>
class tnode{ // 链表节点的数据结构
public:
tnode *prev; // 指向前一个节点
下载
第10章 C++入门 221
tnode *next; //指向下一个节点
char *nodebody; // 节点所存字符串
int nodenum; // 节点字符串的长度
};
class dbllist{ // 链表结构
tnode *head; // 链表的头指针
tnode *base;
tnode *hold; //base 与hold 用于跟踪链表增长
tnode *create(char *); //为一个节点分配存储空间
public:
dbllist(); // 链表的构造函数
~dbllist(); // 链表的析构函数
void clear(); // 释放链表所占空间
tnode *gohead(); //将指针移到链表头
tnode *gotail(); // 将指针移到链表尾
tnode *gonext(); // 将指针移到链表的下一个节点
tnode *goprev(); //将指针移到链表的前一个节点
tnode *append(char *); // 追加一个字符串
tnode *insert(char *); // 插入一个字符串
char* accept(tnode *); //接收指定节点的字符串
};
dbllist::dbllist()
{
head=base=hold=NULL; // 链表指针初始化
}
dbllist::~dbllist() //析构函数
{
clear();
}
void dbllist::clear() // 释放链表各节点
{
base=head; // 头指针
while(base)
{ // 链表非空
hold=base->next;
// 删除节点如
// 图10-3 所示
delete base;
base=hold; //在跟踪链表的过程中释放各节点
}
head=base=hold=NULL;
}
tnode * dbllist::gohead() //将指针移到链表头
{
base=head;
if (base) return base; // 返回头指针
else return NULL;
}
tnode * dbllist::gotail() //将指针移到链表尾
{
if (base)
222 C语言程序设计
下载
{
while(base->next) base=base->next; // 跟踪链表到尾,返回尾指针
return base;
}
else return NULL;
}
tnode* dbllist::gonext() // 将指针移到链表的下一个节点
{
if (base)
{
if (base->next)
{
base=base->next; // 节点指针后移
return base;
}
else return NULL;
}
else return NULL;
}
tnode * dbllist::goprev() // 将指针移到链表的前一个节点
{
if (base)
{
if (base->prev)
{
base=base->prev; // 节点指针前移
return base;
}
else return NULL;
}
else return NULL;
}
tnode* dbllist::append(char * str) // 在尾部追加一个字符串
{
tnode *temp;
if((temp=create(str))==NULL) // 申请创建一个新节点(分配存储空间)
return NULL;
gotail(); // 找到尾节点
if (!base)
{
head=base=temp; // 链表无节点,连接到头
}
else
{ // 追加到尾部
base->next=temp;
temp->prev=base;
base=temp;
}
return base;
下载
第10章 C++入门 223
}
tnode * dbllist::insert(char* str) //插入一个字符串
{
tnode *temp;
gohead(); //指向链表头
if (!base) return(append(str)); // 若是空链表,直接追加后返回
if ((temp=create(str))==NULL) // 申请一个新节点 temp
return NULL;
while (base->next&&memcmp(str ,base->nodebody ,strlen(str)+1)>0)
base=base->next;
// 若当前节点不是尾,同时被插字符串按字母表顺序排在该节点的前面,则指针后移
if (!base->next&&memcmp(str ,base->nodebody ,strlen(str)+1)>0)
{ // 插入位置是链表尾。
base->next=temp;
//插入链表尾的操作如图 10-4
//
//
temp->prev=base;
base=temp;
}
else
{ // 非尾节点,将新插节点连结到链表内
hold=base->prev;
temp->prev=hold;
temp->next=base;
base->prev=temp;
if (!hold) head=temp; // 插入位置在表头
else hold->next=temp;
base=temp;
}
return base;
}
tnode * dbllist::create(char* str) //为新节点分配空间
{
hold=new tnode; // 申请新节点
hold->nodebody=new char[strlen(str)+1];
// 申请插入字符串所占空间
memmove(hold->nodebody ,str ,strlen(str)+1);// 复制字符串到该节点
hold->prev=hold->next=NULL; // 该节点的指向前后的指针为空
hold->nodenum=strlen(str)+1; // 节点字符串的长度
return hold;
}
char* dbllist::accept(tnode *ptr) //返回节点字符串的值
{
return ptr->nodebody;
}
void main() // 主程序
224 C语言程序设计
下载
{
tnode *pointer=NULL; // 节点类对象指针
dbllist lex; // 链表类对象
clrscr(); // 清屏幕
lex.append("aaaaa"); // 追加字符串
lex.append("bbbbbbbb");
lex.append("cccccc");
lex.append("aaaaaaaaaaaa");
pointer=lex.gohead(); // 得到链表头指针
while(pointer)
{ // 非空链表,顺序输出字符串
printf("%s\n" ,lex.accept(pointer));
pointer=lex.gonext();
}
pointer=lex.gotail(); // 得到链表尾指针
while(pointer)
{ // 非空链表,从后向前输出字符串
printf("%s\n" ,lex.accept(pointer));
pointer=lex.goprev();
}
lex.clear(); // 释放链表
lex.insert("xxxxxx"); // 按字母表顺序插入字符串
lex.insert("yyyyyy");
lex.insert("zzzzzz");
lex.insert("aaaaaaa");
pointer=lex.gohead();
while(pointer)
{
printf("%s\n" ,lex.accept(pointer));
pointer=lex.gonext();
}
pointer=lex.gotail();
while(pointer)
{
printf("%s\n" ,lex.accept(pointer));
pointer=lex.goprev();
}
}
base
next Prev next
base next next
hold
temp
程序运行后,输出为:
aaaaa
bbbbbbbb
cccccc
aaaaaaaaaaaa
aaaaaaaaaaaa
下载
第10章 C++入门 225
cccccc
bbbbbbbb
aaaaa
aaaaaaa
xxxxxx
yyyyyy
zzzzzz
zzzzzz
yyyyyy
xxxxxx
aaaaaaa
程序按不同接入链表的方法,将会按正序和逆序输出各节点所存字符串。
最后,我们再重申, new和delete的用法为:
obj_ptr=new obj_type(new_initializer);
delete obj_ptr;
delete [ ]obj_ptr;
10.5 派生类与继承类
在C++中派生类是指从某一类派生出新类的过程,它能继承基类或父类的属性和功能,所
以,我们也称派生类为继承类。派生或继承的过程类似与我们在 C语言中的一些代码的可重用。
各种C的编译版本事先为使用者开发出尽可能多的标准函数,以方便用户使用,使用者无需了
解函数实现的具体细节,就能方便灵活地使用。
在软件的开发过程中,要充分利用系统提供的各种资源,以减少开发人员的劳动。 C++对
系统或用户开发的代码即类的实现补充了更为广大的发展空间,既可以做类代码的再利用,
也可以做包含继承性的类的派生;既可以做某一个类的继承或派生,也可以做多个类的继承
或派生,这就是我们要谈到的单继承的派生和多继承的派生。
10.5.1 单继承的派生类
通过基类或父类继承产生新类的过程称派生,新类则称为派生类,旧的代码或旧类称为
基类。
从一个类派生出另一个类的语法非常简单:
class base{……};
……
……};
class derived:base{
……
对于类b a s e来说,它作为基类,应有完整的定义和说明,只有名字,不能作为一个基类。
所有基类成员相对派生类来说都是局部成员,换句话说,对派生类是隐蔽的、不可访问的,
如果需要对基类成员进行访问,则需在基类的类名前加上访问限制符如下:
class base{……};
226 C语言程序设计
下载
……
……};
class derived:public base{
……
派生类stack的定义,文件stack.h清单:
#include "list.h"
class Stack : public List //关于基类的共有派生
{
int top; // 栈顶指针
public:
Stack() {top = 0;}; //构造函数
Stack(int n) : List(n) {top = 0;};
// 重载的构造函数,包括对基类的初始化
int push(int elem); // 压栈
int pop(int& elem); //出栈
void print(); // 输出
};
派生类stack的成员函数定义,文件 stack.cpp清单:
#include <iostream.h>
#include "stack.h"
int Stack::push(int elem) //压栈
228 C语言程序设计
下载
{
int m = getmax();
if (top < m)
{
put_elem(elem,top++);
return 0;
}
else
return -1;
}
int Stack::pop(int& elem) // 出栈
{
if (top > 0)
{
get_elem(elem,--top);
return 0;
}
else
return -1;
}
void Stack::print() // 输出
{
int elem;
for (int i = top-1; i >= 0; --i)
{ // 按先进后出的顺序
get_elem(elem,i);
cout << elem << " ";
}
cout<<"\n"
}
最后给出main函数exam.cpp实现堆栈的操作:
#include "stack.h"
main()
{
Stack s(5); // 定义堆栈Stack 类的对象s ,堆栈大小为5
int i = 0;
// 插入1~5
while (s.push(i+1) == 0)
++i;
s.print(); // 输出
return 0;
}
程序运行的结果为 :
5 4 3 2 1
重新运行程序,得输出为:
5 4 3 2 1
1 2 3 4 5
1 2 3 4 5
由于派生类与基类均有 print(),所以为避免冲突通过派生类访问基类的同名函数,则需要
在基类成员的前面加上类的限制符,即“派生类对象 . 基 类 名 :: 成 员 ”,在程序中为
s.List.print().
还有一种情况也是我们应当引起重视的:
class A{......};
class B:public A{......};
class C:public B{......};
这种结构是按层次进行派生,每层派生类都只有一个直接基类,类 C继承类 A和类B的成
员特性,当然受到派生访问控制符的限制。
下面的例子是一个单基多级派生的问题。定义一个基类 Location用于定义点坐标,在此基
础上公有派生出类 Point, 以完成一个定位象素点的输出。由于 Point类继承了基类 Location的坐
标点,我们在此基础之上公有派生类 C i r c l e,利用此坐标点做圆心在屏幕上绘 制一个圆,并
230 C语言程序设计
下载
画出大小不一的、圆心、半径均不同的各种圆。程序设计的思路是:首先定义一个基本类
L o c a t i o n,它是针对一个象素点的坐标及初始化:在此基础上派生一个类 P o i n t,该类具有关
于点的属性及绘制点的基本操作。最后,定义一个公有派生类 Circle画圆。
[例10-12] 做工程项目:
point.h
demo.prj point2.cpp
circle.cpp
文件point.h清单
enum Boolean {false, true}; // 定义枚举类型
class Location { // 基类Location ,用于设置点坐标。
protected: // 允许派生类访问的保护段成员
int X; // 坐标点
int Y;
public: // 允许派生类访问
Location(int InitX, int InitY); // 构造函数
int GetX();
int GetY();
};
class Point : public Location
// 从类Location 一级派生,用于绘制点。
protected:
Boolean Visible; //下级派生类可以访问的 protected 段
public:
Point(int InitX, int InitY); 构造函数
//
void Show();
void Hide();
Boolean IsVisible();
void MoveTo(int NewX, int NewY);
};
文件circle.cpp清单
#include <graphics.h> // graphics library declarations
#include "point.h" // Location and Point class declarations
#include <conio.h> // for getch() function
// link with point2.obj and graphics.lib
class Circle : Point { 从类Point 和类Location 的二级派生
//
int Radius; 私有成员
//
public:
Circle(int InitX, int InitY, int InitRadius);
void Show(void);
void Hide(void);
void Expand(int ExpandBy);
void MoveTo(int NewX, int NewY);
void Contract(int ContractBy);
};
Circle::Circle(int InitX, int InitY, int InitRadius) : Point(InitX,InitY)
{
Radius = InitRadius;
};
void Circle::Show(void) // 画圆
{
Visible = true; // 显示标志
circle(X, Y, Radius); 利用标准函数画圆
//
}
232 C语言程序设计
下载
void Circle::Hide(void)
{
unsigned int TempColor; 用于存放当前屏幕色彩
//
TempColor = getcolor(); 读取当前屏幕色彩
//
setcolor(getbkcolor()); 设置当前屏幕色彩为背景色
//
Visible = false;
circle(X, Y, Radius); 删除圆
//
setcolor(TempColor); 恢复当前屏幕色彩
//
};
void Circle::Expand(int ExpandBy) // 放大圆
{
Hide(); 擦除圆
//
Radius += ExpandBy; 修改圆半径
//
if (Radius < 0)
Radius = 0;
Show(); 画圆
//
};
void Circle::Contract(int ContractBy) 缩小圆//
{
Expand(-ContractBy); 利用成员函数修改
//
}; // 圆半径,画圆
void Circle::MoveTo(int NewX, int NewY) 移动圆
//
{
Hide(); 擦除圆
//
X = NewX; 设置新圆心坐标
//
Y = NewY;
Show(); 重画圆
//
};
main() 测试函数
//
{
// 初始化图形系统
int graphdriver = DETECT, graphmode;
initgraph(&graphdriver, &graphmode, "..\\bgi");
Circle MyCircle(100, 200, 50); 定义类circle 的对象
//
MyCircle.Show(); 显示圆
//
getch(); 等待一个按键
//
MyCircle.MoveTo(200, 250); 移动圆心到(200,250 )、重画圆。
//
getch(); // 按一键
MyCircle.Expand(50); 增加圆半径50 ,放大圆
//
getch();
MyCircle.Contract(75); 缩小圆
//
getch();
closegraph(); // 关闭图形系统。
return 0;
}
运行上述程序:会看到一个圆,在键盘上按键,圆移动;再按任一键,圆放大;再按任
一键,圆缩小。
下载
第10章 C++入门 233
10.5.2 多继承的派生类
派生类只有一个基类时,称为单基继承或单基派生;若具有多个基类时,称为多基继承
或多基派生。那么多基继承或多基派生在语法上与单继承有所不同,其语法结构为:
class A{......};
......
class B{......};
......
class c:public A,public B{......};
Class Location
Class Location
int x;
int x;
int y; ....
int y; ....
图 10-5
定义项目CIRCLESTR.PRJ Point.h
Point2.cpp
Mcircle.cpp
234 C语言程序设计
下载
/* point.h--Example from Getting Started */
//***********************************************
// point.h 包含两个类:
// class Location
// class Point
enum Boolean {false, true};// 定义枚举类型
class Location { 类定义
//
protected: //可继承的受保护成员
int X;
int Y;
public: //公有成员
Location(int InitX, int InitY);
int GetX();
int GetY();
};
class Point : public Location { // class Location的派生
protected:
Boolean Visible; // 可继承的受保护成员
public:
Point(int InitX, int InitY); // constructor
void Show(); // 显示
void Hide(); // 隐藏
Boolean IsVisible();
void MoveTo(int NewX, int NewY); // 移动
};
/* POINT2.CPP--Example from Getting Started */
//**********************************************
// POINT2.CPP 包含Point 类和Location 类的说明
#include "point.h"
#include <graphics.h>
// Location类的成员函数
Location::Location(int InitX, int InitY) {
X = InitX;
Y = InitY;
};
int Location::GetX(void) {
return X;
};
int Location::GetY(void) {
return Y;
};
// Point类的成员函数
Point::Point(int InitX, int InitY) : Location(InitX,InitY) {
Visible = false; // make invisible by default
};
void Point::Show(void) {
Visible = true;
下载
第10章 C++入门 235
putpixel(X, Y, getcolor()); // uses default color
};
void Point::Hide(void) {
Visible = false;
putpixel(X, Y, getbkcolor()); // uses background color to erase
};
Boolean Point::IsVisible(void) {
return Visible;
};
void Point::MoveTo(int NewX, int NewY) {
Hide(); // make current point invisible
X = NewX; // change X and Y coordinates to new location
Y = NewY;
Show(); // show point at new location
};
// MCIRCLE.CPP
//******************************************************
#include <graphics.h> // Graphics library declarations
#include "point.h" // Location and Point class declarations
#include <string.h> // for string functions
#include <conio.h> // for console I/O
// link with point2.obj and graphics.lib
// The class hierarchy:
//
// (Circle and CMessage)->MCircle
class Circle : public Point { // Location->Point->Circle
// 多重派生
protected:
int Radius;
public:
Circle(int InitX, int InitY, int InitRadius);
void Show(void);
};
class GMessage : public Location
// 在图形屏幕显示字符串
char *msg; 被显示信息
//
int Font; 文字字体
//
int Field; 字型
//
public:
// 构造函数初始化
GMessage(int msgX, int msgY, int MsgFont, int FieldSize,
char *text);
void Show(void); 显示信息
//
};
class MCircle : Circle, GMessage { // 多类继承
public:
MCircle(int mcircX, int mcircY, int mcircRadius, int Font,
char *msg);
void Show(void); 画带字符串的圆
//
236 C语言程序设计
下载
};
// Circle类的成员函数
//Circle 类的构造函数
Circle::Circle(int InitX, int InitY, int InitRadius) :
Point (InitX, InitY) 构造函数的初始化
//
//包括对基类构造函数的初始化
{
Radius = InitRadius;
};
void Circle::Show(void)
{
Visible = true;
circle(X, Y, Radius); //画圆
}
// Gmessage类的成员函数
//Gmessage 类的构造函数的初始化
GMessage::GMessage(int msgX, int msgY, int MsgFont,
int FieldSize, char *text) :
Location(msgX, msgY)
//对基类构造函数的处理
{
Font = MsgFont; // standard fonts defined in graph.h
Field = FieldSize; // width of area in which to fit text
msg = text; // point at message
};
void GMessage::Show(void)
{
int size = Field / (8 * strlen(msg)); // 8 pixels per char.
settextjustify(CENTER_TEXT, CENTER_TEXT); // centers in circle
settextstyle(Font, HORIZ_DIR, size); // magnify if size > 1
outtextxy(X, Y, msg); // display the text
}
// Mcircle类的成员函数
//Mcircle 类的构造函数
MCircle::MCircle(int mcircX, int mcircY, int mcircRadius, int Font,
char *msg) : Circle (mcircX, mcircY, mcircRadius),
GMessage(mcircX,mcircY,Font,2*mcircRadius,msg)
//多继承应处理其多个基类的构造函数
{
}
void MCircle::Show(void)
{
Circle::Show(); //画圆
GMessage::Show(); // 写字符串
}
main() //画圆并写入字符串
{
int graphdriver = DETECT, graphmode;
下载
第10章 C++入门 237
initgraph(&graphdriver, &graphmode, "..\\bgi");
setbkcolor(15); //背景色
setcolor(4); //前景色
MCircle Small(250, 100, 25, SANS_SERIF_FONT, "You");
Small.Show();
MCircle Medium(250, 150, 100, TRIPLEX_FONT, "World");
Medium.Show();
MCircle Large(250, 250, 225, GOTHIC_FONT, "Universe");
Large.Show();
getch();
closegraph();
return 0;
}
运行程序显示为:
下载
附录A 常用字符与ASCII代码对照表
ASCII值 字 符 ASCII值 字 符 ASCII值 字 符
32 [space] 64 @ 96 `
33 ! 65 A 97 a
34 " 66 B 98 b
35 # 67 C 99 c
36 $ 68 D 100 d
37 % 69 E 101 e
38 & 70 F 102 f
39 ‘ 71 G 103 g
40 ( 72 H 104 h
41 ) 73 I 105 i
42 * 74 J 106 j
43 + 75 K 107 k
44 , 76 L 108 l
45 - 77 M 109 m
46 . 78 N 110 n
47 / 79 O 111 o
48 0 80 P 112 p
49 1 81 Q 113 q
50 2 82 R 114 r
51 3 83 S 115 s
52 4 84 T 116 t
53 5 85 U 117 u
54 6 86 V 118 v
55 7 87 W 119 w
56 8 88 X 120 x
57 9 89 Y 121 y
58 : 90 Z 122 z
59 ; 91 [ 123 {
60 < 92 \ 124 |
61 = 93 ] 125 }
62 > 94 ^ 126 ~
63 ? 95 _ 127 *
下载
附录B C库函数
数学函数
输入输出函数
使用以下函数,应在源文件中使用 stdio.h
函 数 名 函数类型和形参类型 功 能 返 回 值
函 数 名 函数类型和形参类型 功 能 返 回 值
字符屏幕和图形功能函数
函 数 名 函数类型和形参类型 功 能 说 明
函 数 名 函数类型和形参类型 功 能 说 明
函 数 名 函数类型和形参类型 功 能 说 明
动态存储分配
函 数 名 函数类型和形参类型 功 能 返 回 值
类型转换函数
函 数 名 函数类型和形参类型 功 能 返 回 值