C语言程序设计

374871097

贡献于2011-10-03

字数:0 关键词: C/C++开发

下载 第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”代表 数据传递等等,这样一来,人们很容易读懂并理解程序在干什么,纠错及维护都变得方便了, 这种程序设计语言就称为汇编语言,即第二代计算机语言。然而计算机是不认识这些符号的, 这就需要一个专门的程序,专门负责将这些符号翻译成二进制数的机器语言,这种翻译程序 被称为汇编程序。 汇编语言同样十分依赖于机器硬件,移植性不好,但效率仍十分高,针对计算机特定硬 件而编制的汇编语言程序,能准确发挥计算机硬件的功能和特长,程序精炼而质量高,所以至今仍是一种常用而强有力的软件开发工具。 3. 高级语言 从最初与计算机交流的痛苦经历中,人们意识到,应该设计一种这样的语言,这种语言 接近于数学语言或人的自然语言,同时又不依赖于计算机硬件,编出的程序能在所有机器上 通用。经过努力,1 9 5 4年,第一个完全脱离机器硬件的高级语言— F O RT R A N问世了,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、 A d a、C + +、V C、V B、D e l p h i、J AVA等。 高级语言的发展也经历了从早期语言到结构化程序设计语言,从面向过程到非过程化程 序语言的过程。相应地,软件的开发也由最初的个体手工作坊式的封闭式生产,发展为产业 化、流水线式的工业化生产。 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 + +、V B、D e l p h i就是典型代表。 高级语言的下一个发展目标是面向应用,也就是说:只需要告诉程序你要干什么,程序 就能自动生成算法,自动进行处理,这就是非过程化的程序语言。 1.2 C语言的特点 1.2.1 C语言是中级语言 C语言通常称为中级计算机语言。中级语言并没有贬义,不意味着它功能差、难以使用、 或者比B A S I C、P a s c a l那样的高级语言原始,也不意味着它与汇编语言相似,会给使用者带来 类似的麻烦。C语言之所以被称为中级语言,是因为它把高级语言的成分同汇编语言的功能结 合起来了。表1 - 1表明了C语言在计算机语言中所处的地位。 2 C语言程序设计 下载表1-1 C语言在计算机语言中的地位 高级 A d a、M o d u l a - 2、P a s c a l、C O B O L、F O RT R A N、B A S I C 中级 C、F O RT H、M a c r o - a s s e m b l e r 低级 A s s e m b l e r 作为中级语言,C允许对位、字节和地址这些计算机功能中的基本成分进行操作。 C语言 程序非常容易移植。可移植性表示为某种计算机写的软件可以用到另一种机器上去。举例来 说,如果为苹果机写的一个程序能够方便地改为可以在 IBM PC 上运行的程序,则称为是可移 植的。 所有的高级语言都支持数据类型的概念。一个数据类型定义了一个变量的取值范围和可 在其上操作的一组运算。常见的数据类型是整型、字符型和实数型。虽然 C语言有五种基本数 据类型,但与P a s c a l或A d a相比,它却不是强类型语言。 C程序允许几乎所有的类型转换。例 如,字符型和整型数据能够自由地混合在大多数表达式中进行运算。这在强类型高级语言中 是不允许的。 C语言的另一个重要特点是它仅有 3 2个关键字,这些关键字就是构成 C语言的命令。和 IBM PC的B A S I C相比,后者包含的关键字达 1 5 9个之多。 1.2.2 C语言是结构化语言 虽然从严格的学术观点上看, C语言是块结构(b l o c k - s t r u c t u r e d)语言,但是它还是常被 称为结构化语言。这是因为它在结构上类似于 A L G O L、P a s c a l和M o d u l a - 2(从技术上讲,块 结构语言允许在过程和函数中定义过程或函数。用这种方法,全局和局部的概念可以通过 “作用域”规则加以扩展,“作用域”管理变量和过程的“可见性”。因为C语言不允许在函数 中定义函数,所以不能称之为通常意义上的块结构语言)。 结构化语言的显著特征是代码和数据的分离。这种语言能够把执行某个特殊任务的指令 和数据从程序的其余部分分离出去、隐藏起来。获得隔离的一个方法是调用使用局部(临时) 变量的子程序。通过使用局部变量,我们能够写出对程序其它部分没有副作用的子程序。这 使得编写共享代码段的程序变得十分简单。如果开发了一些分离很好的函数,在引用时我们 仅需要知道函数做什么,不必知道它如何做。切记:过度使用全局变量(可以被全部程序访 问的变量)会由于意外的副作用而在程序中引入错误。 结构化语言比非结构化语言更易于程序设计,用结构化语言编写的程序的清晰性使得它 们更易于维护。这已是人们普遍接受的观点了。 C语言的主要结构成分是函数 C的独立子程序。 在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设计者的本意却是打算使非程序员能读程序(这是不大可能的事)。注意, 第1章 程序设计与算法 3下载这并不是攻击C O B O L的优点,而是想指出,它没有被设计成为程序员的理想语言。 BASIC 的 主要目的是允许非专业程序员在计算机上编程解决比较简单的问题。与其形成鲜明对照的是 C 语言,由于程序生成、修改和现场测试自始至终均由真正的程序员进行,因而它实现了程序 员的期望:很少限制、很少强求、块结构、独立的函数以及紧凑的关键字集合。用 C语言编程, 程序员可以获得高效机器代码,其效率几乎接近汇编语言代码。 C语言被程序员广泛使用的另一个原因是可以用它代替汇编语言。汇编语言使用的汇编指 令,是能够在计算机上直接执行的二进制机器码的符号表示。汇编语言的每个操作都对应为 计算机执行的单一指令。虽然汇编语言给予程序员达到最大灵活性和最高效率的潜力,但开 发和调试汇编语言程序的困难是难以忍受的。非结构性使得汇编语言程序难于阅读、改进和 维护。也许更重要的是,汇编语言程序不能在使用不同 C P U的机器间移植。 最初,C语言被用于系统程序设计。一个“系统程序”是一大类程序的一部分,这一大类 构成了计算机操作系统及实用程序。通常被称为系统程序的有: • 操作系统。 • 翻译程序。 • 编辑程序。 • 汇编程序。 • 编译程序。 • 数据库管理程序。 随着C语言的普及,加之其可移植性和高效率,许多程序员用它设计各类程序。几乎所有 的计算机上都有C语言编译程序,这使我们可以很少改动甚至不加改动地将为一种机器写的 C 语言源程序在另一种机器上编译执行。可移植性节省了时间和财力。 C语言不仅在速度和结构上有它的优势,而且每个 C语言系统都提供了专门的函数库,程 序员可以根据不同需要对其进行剪裁,以适应各种程序的设计。由于它允许(更准确地说是 鼓励)分别编译,所以C语言可使程序员方便地管理大型项目,最大限度地减少重复劳动。 1.3 C语言的程序结构 1.3.1 基本程序结构 任何一种程序设计语言都具有特定的语法规则和规定的表达方法。一个程序只有严格按 照语言规定的语法和表达方式编写,才能保证编写的程序在计算机中能正确地执行,同时也 便于阅读和理解。 为了了解C语言的基本程序结构,我们先介绍几个简单的 C程序。 [例1 - 1 ] # include m a i n ( ) / *主函数* / { printf("This is a sample of c program. \n"); /*调用标准函数, 显示引号中的内容* / } 4 C语言程序设计 下载这是一个最简单的C程序,其执行结果是在屏幕上显示一行信息: R U N ¿ This is a sample of c program. [例1 - 2 ] m a i n ( ) / *主函数* / { void proc(); /* 函数声明 * / int a=3; / *指定a为整数,初始值为3 * / p r o c ( ) ; / *调用函数p r o c , 无返回* / a = f u n c ( ) ; / *调用函数f u n c , 结果返回给a * / printf("This is a sample of c program. \n"); } void proc() / *定义函数p r o c , v o i d 指定该函数不返回结果* / { printf("Hello. \n"); } int func() / *定义函数f u n c , i n t 指定该函数返回一个整数* / { r e t u r n ( 2 ) ; / *返回整数2 * / } 本程序的执行过程是: • 程序从m a i n ( )处开始。 • 变量a代表一个整数,并且初始值为 3。 • 执行程序(函数)p r o c ( );屏幕上显示H e l l o,\ n为转义字符,代表换行的意思。 • 执行程序(函数)f u n c ( );并将结果赋予a,此时,a的值为2。 • 屏幕上显示“This is a sample of c program.”。 程序执行的结果是在屏幕显示两行信息: R U N ¿ H e l l o . This is a sample of c program. 程序中/ * . . . . . * /表示对程序的说明(称为注释),不参与程序的运行。注释文字可以是任意 字符,如汉字、拼音、英文等。 [例1 - 3 ] / *输入长方体的长、宽、高,计算长方体体积 * / m a i n ( ) { int x,y,z,v; / *定义整型变量* / s c a n f ( " % d , % d , % d " , & x , & y , & z ) ; / *调用标准函数,从键盘输入x , y , z 的值* / v = volume(x,y,z); / *调用v o l u m e 函数,计算体积* / prinf("v = %d\n",v); } 第1章 程序设计与算法 5下载int volume(a,b,c) / *定义v o l u m e 函数* / int a,b,c; / *对形参a , b , c 作类型定义* / { int p; / *定义函数内部使用的变量p * / p = a*b*c; / *计算体积p的值* / r e t u r n ( p ) ; / *将p值返回调用处* / } 本程序的功能是对从键盘输入的长方体的长、宽、高三个整型量求其体积的值。程序运 行的情况如下: R U N ¿ 5 , 8 , 6 ¿ v = 240 在本例中,m a i n函数在调用v o l u m e函数时,将实际参数x、y、z的值分别传送给v o l u m e函 数中的形式参数 a、b、c。经过执行v o l u m e函数得到一个结果(即 v o l u m e函数中变量p的值) 并把这个值赋给变量v。 从上面程序例子,可以看出 C程序的基本结构。 C程序为函数模块结构,所有的 C程序都是由一个或多个函数构成,其中必须只能有一个 主函数m a i n ( )。程序从主函数开始执行,当执行到调用函数的语句时,程序将控制转移到调用 函数中执行,执行结束后,再返回主函数中继续运行,直至程序执行结束。 C程序的函数是由 编译系统提供的标准函数(如 p r i n t f、s c a n f等)和由用户自己定义的函数(如 p r o c、f u n c、 v o l u m e等)。虽然从技术上讲,主函数不是 C语言的一个成分,但它仍被看做是其中的一部分, 因此,“m a i n”不能用作变量名。 函数的基本形式是: 函数类型 函数名(形式参数) 形式参数说明; { 数据说明部分; 语句部分; } 其中: 函数头 包括函数说明、函数名和圆括号中的形式参数(如 int volume(a,b,c)),如果函数 调用无参数传递,圆括号中形式参数为空(如 void proc()函数)。 形式参数说明 指定函数调用传递参数的数据类型(如例 1 . 3中语句int a,b,c;)。 函数体 包括函数体内使用的数据说明和执行函数功能的语句,花括号 {和}表示函数体的 开始和结束。 1.3.2 函数库和链接 从技术上讲,纯粹由程序员自己编写的语句构成 C语言程序是可能的,但这却是罕见的。 因为所有的C编译程序都提供能完成各种常用任务的函数— 函数库(如p r i n t f、s c a n f等)。 C编译程序的实现者已经编写了大部分常见的通用函数。当我们调用一个别人编写的函数 6 C语言程序设计 下载时编译程序“记忆”它的名字。随后,“链接程序”把我们编写的程序同标准函数库中找到的 目标码结合起来,这个过程称为“链接”。 保存在函数库中的函数是可重定位的。这意味着其中机器码指令的内存地址并未绝对地 确定, 只有偏移量是确定的。当把程序与标准函数库中的函数相链接时,内存偏移量被用来产 生实际地址。有关重定位的详细内容,请查阅其他技术书籍。 编写程序时用到的函数,许多都可以在标准函数库中找到。它们是可以简单地组合起来 的程序构件。编写了一个经常要用的函数之后,可将其放入库中备用。 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列举了3 2个关键字,它们与标准C句法结合,形成了程序设计语言 C。 表1-2 关 键 字 a u t o b r e a k c a s e c h a r c o n s t c o n t i n u e d e f a u l t d o d o u b l e e l s e e n u m e x t e r n f l o a t f o r g o t o i f i n t l o n g r e g i s t e r s h o r t s i g n e d s i z e o f s t a t i c r e t u r n s t r u c t s w i t c h t y p e d e f u n i o n u n s i g n e d v o i d v o l a t i l e w h i l e 第1章 程序设计与算法 7下载C语言的关键字都用小写字母。 C语言中区分大写与小写, e l s e是关键字,“E L S E”则不 是。在C程序中,关键字不能用于其它目的,即不允许将关键字作为变量名或函数名使用。 1.4 算法 什么是程序?程序 = 数据结构 + 算法。 对于面向对象程序设计,强调的是数据结构,而对于面向过程的程序设计语言如 C、 P a s c a l、F O RT R A N等语言,主要关注的是算法。掌握算法,也是为面向对象程序设计打下一 个扎实的基础。那么,什么是算法呢? 人们使用计算机,就是要利用计算机处理各种不同的问题,而要做到这一点,人们就必 须事先对各类问题进行分析,确定解决问题的具体方法和步骤,再编制好一组让计算机执行 的指令即程序,交给计算机,让计算机按人们指定的步骤有效地工作。这些具体的方法和步 骤,其实就是解决一个问题的算法。根据算法,依据某种规则编写计算机执行的命令序列, 就是编制程序,而书写时所应遵守的规则,即为某种语言的语法。 由此可见,程序设计的关键之一,是解题的方法与步骤,是算法。学习高级语言的重点, 就是掌握分析问题、解决问题的方法,就是锻炼分析、分解,最终归纳整理出算法的能力。 与之相对应,具体语言,如 C语言的语法是工具,是算法的一个具体实现。所以在高级语言的 学习中,一方面应熟练掌握该语言的语法,因为它是算法实现的基础,另一方面必须认识到 算法的重要性,加强思维训练,以写出高质量的程序。 下面通过例子来介绍如何设计一个算法: [例1-4] 输入三个数,然后输出其中最大的数。 首先,得先有个地方装这三个数,我们定义三个变量 A、B、C,将三个数依次输入到A、 B、C中,另外,再准备一个M A X装最大数。 由于计算机一次只能比较两个数,我们首先把 A与B比,大的数放入 M A X中,再把M A X 与C比,又把大的数放入M A X中。 最后,把M A X输出,此时M A X中装的就是A、B、 C三数中最大的一个数。算法可以表 示如下: 1) 输入A、B、C。 2) A与B中大的一个放入M A X中。 3) 把C与M A X中大的一个放入M A X中。 4) 输出M A X,M A X即为最大数。 其中的2 )、3 )两步仍不明确,无法直接转化为程序语句,可以继续细化: 2) 把A与B中大的一个放入M A X中,若A > B,则MAX ← A;否则MAX ← B。 3) 把C与M A X中大的一个放入M A X中,若C > M A X,则M A X←C。 于是算法最后可以写成: 1) 输入A,B,C。 2) 若A > B,则MAX ← A; 否则M A X←B。 3) 若C > M A X,则M A X← C。 4) 输出M A X,M A X即为最大数。 8 C语言程序设计 下载这样的算法已经可以很方便地转化为相应的程序语句了。 [例1-5] 猴子吃桃问题:有一堆桃子不知数目,猴子第一天吃掉一半,觉得不过瘾,又 多吃了一只,第二天照此办理,吃掉剩下桃子的一半另加一个,天天如此,到第十天早上, 猴子发现只剩一只桃子了,问这堆桃子原来有多少个? 此题粗看起来有些无从着手的感觉,那么怎样开始呢?假设第一天开始时有 a1只桃子,第 二天有a2只,. . .,第9天有a9只,第1 0天是a1 0只,在a1, a2, . . .,a1 0中,只有a1 0= 1是知道的,现要求 a1,而我们可以看出,a1, a2, . .,a1 0之间存在一个简单的关系: a9= 2 * ( a1 0+ 1 ) a8= 2 * ( a9+ 1 ) ┇ a1= 2 * ( a2+ 1 ) 也就是:ai= 2 * ( ai + 1+1) i=9,8,7,6,...,1 这就是此题的数学模型。 再考察上面从a9,a8直至a1的计算过程,这其实是一个递推过程,这种递推的方法在计算 机解题中经常用到。另一方面,这九步运算从形式上完全一样,不同的只是 ai的下标而已。由 此,我们引入循环的处理方法,并统一用 a0表示前一天的桃子数, a1表示后一天的桃子数,将 算法改写如下: 1) a 1=1; {第1 0天的桃子数,a1的初值} i = 9。 {计数器初值为9} 2) a 0= 2 * ( a1+ 1 )。 {计算当天的桃子数} 3) a 1= a0。 {将当天的桃子数作为下一次计算的初值} 4) i=i-1。 5) 若i > = 1,转2 )。 6) 输出a0的值。 其中2 )~5 )步为循环。 这就是一个从具体到抽象的过程,具体方法是: 1) 弄清如果由人来做,应该采取哪些步骤。 2) 对这些步骤进行归纳整理,抽象出数学模型。 3) 对其中的重复步骤,通过使用相同变量等方式求得形式的统一,然后简练地用循环解 决。 算法的描述方法有自然语言描述、伪代码、流程图、 N - S图、PA D图等。 1.4.1 流程图与算法的结构化描述 1. 流程图 流程图是一种传统的算法表示法,它利用几何图形的框来代表各种不同性质的操作,用 流程线来指示算法的执行方向。由于它简单直观,所以应用广泛,特别是在早期语言阶段, 只有通过流程图才能简明地表述算法,流程图成为程序员们交流的重要手段,直到结构化的 程序设计语言出现,对流程图的依赖才有所降低。 下面介绍常见的流程图符号及流程图的例子。 第1章 程序设计与算法 9下载本章例1 - 1的算法的流程图如图1 - 2所示。本章例1 - 2的算法的流程图如图1 - 3所示。 在流程图中,判断框左边的流程线表示判断条件为真时的流程,右边的流程线表示条件为假 时的流程,有时就在其左、右流程线的上方分别标注“真”、“假”或“T”、“F”或“Y”、“N”。 图1-1 常见的流程图符号 图1-2 例1-1的算法流程图 图1-3 例1-2的算法流程图 另外还规定,流程线是从下往上或从右向左时,必须带箭头,除此以外,都不画箭头, 流程线的走向总是从上向下或从左向右。 2. 算法的结构化描述 早期的非结构化语言中都有 g o t o语句,它允许程序从一个地方直接跳转到另一个地方去。 执行这样做的好处是程序设计十分方便灵活,减少了人工复杂度,但其缺点也是十分突出的, 1 0 C语言程序设计 下载 起止框 执行框 连接点 流程线 判断框 输入、输出框 注释框 开始 结束 输入A,B,C A>B C>MAX MAX<=A MAX<=C MAX<=B 输出MAX 开始 i=9 a1=1 i>1 i=i-1 a0=2*(a1+1) a1=a0 输出a 0 结束 T T F F一大堆跳转语句使得程序的流程十分复杂紊乱,难以看懂也难以验证程序的正确性,如果有 错,排起错来更是十分困难。这种转来转去的流程图所表达的混乱与复杂,正是软件危机中 程序人员处境的一个生动写照。而结构化程序设计,就是要把这团乱麻理清。 经过研究,人们发现,任何复杂的算法,都可以由顺序结构、选择(分支)结构和循环 结构这三种基本结构组成,因此,我们构造一个算法的时候,也仅以这三种基本结构作为 “建筑单元”,遵守三种基本结构的规范,基本结构之间可以并列、可以相互包含,但不允许 交叉,不允许从一个结构直接转到另一个结构的内部去。正因为整个算法都是由三种基本结 构组成的,就像用模块构建的一样,所以结构清晰,易于正确性验证,易于纠错,这种方法, 就是结构化方法。遵循这种方法的程序设计,就是结构化程序设计。 相应地,只要规定好三种基本结构的流程图的画法,就可以画出任何算法的流程图。 (1) 顺序结构 顺序结构是简单的线性结构,各框按顺序执行。其流程图的基本形态如图 1 - 4所示,语句 的执行顺序为:A→B→C。 (2) 选择(分支)结构 这种结构是对某个给定条件进行判断,条件为真或假时分别执行不同的框的内容。其基 本形状有两种,如图 1-5 a)、b)所示。图1-5 a)的执行序列为:当条件为真时执行 A,否则 执行B;图1 - 5 b)的执行序列为:当条件为真时执行 A,否则什么也不做。 图1-4 顺序结构的流程图 图1-5 选择(分支)结构的流程图 (3) 循环结构 循环结构有两种基本形态: w h i l e型循环和d o - w h i l e型循环。 a. while 型循环 如图1 - 6所示。 其执行序列为:当条件为真时,反复执行 A,一旦条件为假,跳出循环,执行循环紧后的 语句。 b. do-while型循环 如图1 - 7所示。 图1-6 while 型循环流程图 图1-7 do-while型循环流程图 图1-8 do-while 型循环转换为 while型循环 第1章 程序设计与算法 1 1下载 A B C 条件 A B a) 条件 A b) 条件 A T F 条件 A A 条件 A T T F T TF F执行序列为:首先执行 A,再判断条件,条件为真时,一直循环执行 A,一旦条件为假, 结束循环,执行循环紧后的下一条语句。 在图1 - 6、图1 - 7中,A被称为循环体,条件被称为循环控制条件。要注意的是: 1) 在循环体中,必然对条件要判断的值进行修改,使得经过有限次循环后,循环一定能 结束,如图1 - 3中的i = i - 1。 2) 当型循环中循环体可能一次都不执行,而直到型循环则至少执行一次循环体。 3) 直到型循环可以很方便地转化为当型循环,而当型循环不一定能转化为直到型循环。 例如,图1 - 7可以转化为图1 - 8。 1.4.2 用N-S图描述算法 N - S图是另一种算法表示法,是由美国人 I . N a s s i和B . S h n e i d e r m a n共同提出的,其根据是: 既然任何算法都是由前面介绍的三种结构组成,所以各基本结构之间的流程线就是多余的, 因此,N - S图也是算法的一种结构化描述方法。 N - S图中,一个算法就是一个大矩形框,框内又包含若干基本的框,三种基本结构的 N - S 图描述如下所示: 1. 顺序结构 如图1 - 9所示,执行顺序先A后B。 2. 选择结构 对应于图1 - 5的N - S图为图1 - 1 0。图1-10 a)条件为真时执行 A,条件为假时执行 B。图1 - 1 0 b )条件为真时执行A,为假时什么都不做。 图1-9 顺序结构的N-S图 图1-10 选择结构的N-S图 3. 循环结构 1) while型循环的N - S图如图1 - 11所示,条件为真时一直循 环执行循环体A,直到条件为假时才跳出循环。 2) do-while型循环的N - S图如图1 - 1 2,一直循环执行循环体 A,直到条件为假时才跳出循环。 本章例1 - 1的N - S图如图1 - 1 3,例1 - 2的N - S图如图1 - 1 4。应 该说,N - S图比流程图更直观易懂,而且相对简练一些。 图1-13 例1-1的N-S图 图1-14 例1-2的N-S图 1 2 C语言程序设计 下载 A B 当<条件>为真 A A 当<条件>为真 图1-11 while型循环的N-S图 图1-12 do-while型循环的N-S图 输入A,B,C max<=A max<=B max<=C 输出 MAX A>B C>max T T F F a1=1 i=9 当i>=1时,循环 输出a0 a0=2*(a1+1) a1=a0 i=i-1 F F条件T T A B A a) b) 条件1.4.3 用PAD图描述算法 PA D(Problem Analysis Diagram),是近年来在软件开发中被广泛使用的一种算法的图形 表示法,与前述的流程图、 N - S图相比,流程图、 N - S图都是自上而下的顺序描述,而 PA D图 除了自上而下以外,还有自左向右的展开,所以,如果说流程图、 N - S图是一维的算法描述的 话,则PA D图就是二维的,它能展现算法的层次结构,更直观易懂。 下面是PA D图的几种基本形态: 1. 顺序结构: 如图1 - 1 5所示。 2. 选择结构 (1) 单分支选择,条件为真执行 A,如图1-16 a)。 (2) 两分支选择,如图1-16 b),条件为真执行A,为假执行B。 (3) 多分支选择,如图1-16 c),当I = I1时执行A,I= I2时执行B,I = I3时执行C,I = I4时执行D。 图1-15 顺序结构的PAD 图1-16 选择结构的PAD 3. 循环结构 如图1 - 1 7所示。图1-17 a)为w h i l e型循环,图1-17 b)为d o - w h i l e型循环。 图1-17 循环结构的PAD 本章例1 . 1的PA D图如图1 - 1 8,例1 - 2的PA D图如图1 - 1 9。 图1-18 例1-1的PAD 图1-19 例1-2的PAD 第1章 程序设计与算法 1 3 A B C 条件 A 条件 A A B C D B a) b) I= i1 i2 i3 i4 WHILE<条件> A UNTIL<条件> A 输入A,B,C MAX<=A MAX<=B MAX<=C 输出MAX A>B C>MAX a1=1 i=9 while i>=1 输出 a0 a0=2*a1+1 a1=a0 i=i-1 a) b) 下载下载 第2章 数据类型、运算符和表达式 2.1 C语言的数据类型 C语言有五种基本数据类型:字符、整型、单精度实型、双精度实型和空类型。尽管这几 种类型数据的长度和范围随处理器的类型和 C语言编译程序的实现而异,但以 b i t为例,整数 与C P U字长相等,一个字符通常为一个字节,浮点值的确切格式则根据实现而定。对于多数 微机,表2 - 1给出了五种数据的长度和范围。 表2-1 基本类型的字长和范围 类 型 长 度(b i t) 范 围 c h a r (字符型) 8 0 ~ 2 5 5 i n t(整型) 1 6 - 3 2 7 6 8 ~ 3 2 7 6 7 f l o a t(单精度型) 3 2 约精确到6位数 d o u b l e(双精度型) 6 4 约精确到1 2位数 v o i d(空值型) 0 无值 表中的长度和范围的取值是假定 C P U的字长为1 6 b i t。 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还可用于 d o u b l e(注意,由于long float与d o u b l e意思相同,所以A N S I标准删除了多余的long float)。 表2 - 2给出所有根据A N S I标准而组合的类型、字宽和范围。切记,在计算机字长大于 1 6位 的系统中,short int与signed char可能不等。 表2-2 ANSI标准中的数据类型 类 型 长 度(b i t) 范 围 c h a r (字符型) 8 A S C I I字符 unsigned char(无符号字符型) 8 0 ~ 2 5 5 signed char(有符号字符型 ) 8 - 1 2 8 ~ 1 2 7 i n t(整型) 1 6 3 2 7 6 8 ~ 3 2 7 6 7 unsigned int(无符号整型) 1 6 0 ~ 6 5 5 3 5 signed int(有符号整型) 1 6 同i n t(续) 类 型 长 度(b i t) 范 围 short int(短整型) 8 1 2 8 ~ 1 2 7 unsigned short int(无符号短整型 ) 8 0 ~ 2 5 5 signed short int(有符号短整型 ) 8 同s h o r t i n t long int(长整型) 3 2 2 1 4 7 4 8 3 6 4 8 ~ 2 1 4 7 4 8 3 6 4 9 signed long int(有符号长整型 ) 3 2 2 1 4 7 4 8 3 6 4 8 ~ 2 1 4 7 4 8 3 6 4 9 unsigned long int(无符号长整型) 3 2 0 ~ 4 2 9 4 9 6 7 2 9 6 f l o a t(单精度型) 3 2 约精确到6位数 d o u b l e(双精度型) 6 4 约精确到1 2位数 *表中的长度和范围的取值是假定 C P U的字长为1 6 b i t。 因为整数的缺省定义是有符号数,所以 s i n g e d这一用法是多余的,但仍允许使用。 某些实现允许将u n s i g n e d用于浮点型,如unsigned double。但这一用法降低了程序的可移 植性,故建议一般不要采用。 为了使用方便,C编译程序允许使用整型的简写形式: • short int 简写为s h o r t。 • long int 简写为l o n g。 • unsigned short int 简写为unsigned short。 • unsigned int 简写为u n s i g n e d。 • unsigned long int 简写为unsigned long。 即,i n t可缺省。 2.2 常量与变量 2.2.1 标识符命名 在C语言中,标识符是对变量、函数标号和其它各种用户定义对象的命名。标识符的长度 可以是一个或多个字符。绝大多数情况下,标识符的第一个字符必须是字母或下划线,随后 的字符必须是字母、数字或下划线(某些 C语言编译器可能不允许下划线作为标识符的起始字 符)。下面是一些正确或错误标识符命名的实例。 正确形式 错误形式 c o u n t 2 c o u n t t e s t 2 3 hi! there h i g h _ b a l a n c e h i g h . . b a l a n c e A N S I标准规定,标识符可以为任意长度,但外部名必须至少能由前 8个字符唯一地区分。 这里外部名指的是在链接过程中所涉及的标识符,其中包括文件间共享的函数名和全局变量 名。这是因为对某些仅能识别前 8个字符的编译程序而言,下面的外部名将被当作同一个标识 符处理。 c o u n t e r s c o u n t e r s 1 c o u n t e r s 2 A N S I标准还规定内部名必须至少能由前 3 1个字符唯一地区分。内部名指的是仅出现于定 第2章 数据类型、运算符和表达式 1 5下载义该标识符的文件中的那些标识符。 C语言中的字母是有大小写区别的,因此 count Count COUNT是三个不同的标识符。 标识符不能和C语言的关键字相同,也不能和用户已编制的函数或 C语言库函数同名。 2.2.2 常量 C语言中的常量是不接受程序修改的固定值,常量可为任意数据类型,如下例所示 : 数据类型 常量举例 c h a r ' a '、' \ n '、' 9 ' i n t 2 1、 123 、2100 、-2 3 4 long int 3 5 0 0 0、 -3 4 short int 1 0、-1 2、9 0 unsigned int 1 0 0 0 0、 9 8 7、 4 0 0 0 0 f l o a t 1 2 3 . 2 3、 4 . 3 4 e-3 d o u b l e 1 2 3 . 2 3、 1 2 3 1 2 3 3 3、 -0 . 9 8 7 6 2 3 4 C语言还支持另一种预定义数据类型的常量,这就是串。所有串常量括在双撇号之间,例 如"This is a test"。切记,不要把字符和串相混淆,单个字符常量是由单撇号括起来的,如 ' a '。 2.2.3 变量 其值可以改变的量称为变量。一个变量应该有一个名字 (标识符),在内存中占据一定的存 储单元,在该存储单元中存放变量的值。请注意区分变量名和变量值这两个不同的概念。 所有的C变量必须在使用之前定义。定义变量的一般形式是: type variable_list; 这里的t y p e必须是有效的C数据类型,v a r i a b l e _ l i s t(变量表)可以由一个或多个由逗号分 隔的多个标识符名构成。下面给出一些定义的范例。 int i, j, l; short int si; unsigned int ui; double balance, profit,loss; 注意 C语言中变量名与其类型无关。 2.3 整型数据 2.3.1 整型常量 整型常量及整常数。它可以是十进制、八进制、十六进制数字表示的整数值。 十进制常数的形式是: d i g i t s 这里d i g i t s可以是从0到9的一个或多个十进制数位,第一位不能是 0。 八进制常数的形式是: 1 6 C语言程序设计 下载0 d i g i t s 在此,d i g i t s可以是一个或多个八进制数( 0~7之间),起始0是必须的引导符。 十六进制常数是下述形式: 0 x h d i g i t s 0 X h d i g i t s 这里h d i g i t s可以是一个或多个十六进制数(从 0~9的数字,并从“ a”~“f”的字母)。 引导符0是必须有的,X即字母可用大写或小写。 注意,空白字符不可出现在整数数字之间。表 2 - 3列出了整常数的形式。 表2-3 整常数的例子 十 进 制 八 进 制 十 六 进 制 1 0 0 1 2 0Xa或0XA 1 3 2 0 2 0 4 0X8 4 3 2 1 7 9 0 7 6 6 6 3 0X7 d b 3或0X7 D B 3 整常数在不加特别说明时总是正值。如果需要的是负值,则负号“ -”必须放置于常数表 达式的前面。 每个常数依其值要给出一种类型。当整常数应用于一表达式时,或出现有负号时,常数 类型自动执行相应的转换,十进制常数可等价于带符号的整型或长整型,这取决于所需的常 数的尺寸。 八进制和十六进制常数可对应整型、无符号整型、长整型或无符号长整型,具体类型也 取决于常数的大小。如果常数可用整型表示,则使用整型。如果常数值大于一个整型所能表 示的最大值,但又小于整型位数所能表示的最大数,则使用无符号整型。同理,如果一个常 数比无符号整型所表示的值还大,则它为长整型。如果需要,当然也可用无符号长整型。 在一个常数后面加一个字母 l或L,则认为是长整型。如1 0 L、7 9 L、0 1 2 L、0 11 5 L、0 X A L、 0 x 4 f L等。 2.3.2 整型变量 前面已提到, C规定在程序中所有用到的变量都必须在程序中指定其类型,即“定义”。 这是和B A S I C、F O RT R A N不同的,而与P a s c a l相似。 [例2 - 1 ] m a i n ( ) { 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); } 运行结果为: R U N ¿ a+u=22, b+u=-1 4 第2章 数据类型、运算符和表达式 1 7下载可以看到不同类型的整型数据可以进行算术运算。在本例中是 i n t型数据与unsingned int型 数据进行相加减运算。 2.4 实型数据 2.4.1 实型常量 实型常量又称浮点常量,是一个十进制表示的符号实数。符号实数的值包括整数部分、 尾数部分和指数部分。实型常量的形式如下: [ d i g i t s ] [ . d i g i t s ] [ E | e [ + | - ] d i g i t s ] 在此d i g i t s是一位或多位十进制数字(从 0~9)。 E(也可用e)是指数符号。小数点之前 是整数部分,小数点之后是尾数部分,它们是可省略的。小数点在没有尾数时可省略。 指数部分用 E或e开头,幂指数可以为负,当没有符号时视为正指数的基数为1 0,如 1 . 5 7 5 E 1 0表示为:1 . 5 7 5×1 01 0。在实型常量中不得出现任何空白符号。 在不加说明的情况下,实型常量为正值。如果表示负值,需要在常量前使用负号。 下面是一些实型常量的示例: 15.75, 1.575E10, 1575e-2, -0.0025, -2.5e-3, 25E-4 所有的实型常量均视为双精度类型。 实型常量的整数部分为0时可以省略,如下形式是允许的: .57, .0075e2, -.125, -.175E-2。 注意 字母E或e之前必须有数字,且E或e后面指数必须为整数,如e 3、2 . 1 e 3 . 5、. e 3、e 等都是不合法的指数形式。 2.4.2 实型变量 实型变量分为单精度( f l o a t型)和双精度(d o u b l e型)。对每一个实型变量都应再使用前 加以定义。如: float x,y; / *指定x , y为单精度实数* / double z; / *指定z为双精度实数* / 在一般系统中,一个 f l o a t型数据在内存中占 4个字节(3 2位)一个d o u b l e型数据占8个字 节(6 4位)。单精度实数提供7位有效数字,双精度提供 1 5 ~ 1 6位有效数字,数值的范围随机器 系统而异。 值得注意的是,实型常量是 d o u b l e型,当把一个实型常量赋给一个 f l o a t型变量时,系统会 截取相应的有效位数。例如 float a; a = 1 1 1 1 1 1 . 1 1 1 ; 由于f l o a t型变量只能接收7位有效数字,因此最后两位小数不起作用。如果将 a改为d o u b l e 型,则能全部接收上述9位数字并存储在变量a中。 1 8 C语言程序设计 下载2.5 字符型数据 2.5.1 字符常量 字符常量是指用一对单引号括起来的一个字符。如‘ a’,‘9’,‘!’。字符常量中的单引 号只起定界作用并不表示字符本身。单引号中的字符不能是单引号(’)和反斜杠(\),它们 特有的表示法在转义字符中介绍。 在C语言中,字符是按其所对应的 A S C I I码值来存储的,一个字符占一个字节。例如: 字符 A S C I I码值(十进制) ! 3 3 0 4 8 1 4 9 9 5 7 A 6 5 B 6 6 a 9 7 b 9 8 注意 字符' 9 '和数字9的区别,前者是字符常量,后者是整型常量,它们的含义和在计 算机中的存储方式都截然不同。 由于C语言中字符常量是按整数( s h o r t型)存储的,所以字符常量可以像整数一样在程序 中参与相关的运算。例如: ' a '-3 2 ; /* 执行结果9 7-32 = 65 * / 'A' + 32; /* 执行结果65+32 = 97 * / ' 9 '-9; /* 执行结果5 7-9 = 48 * / 2.5.2 字符串常量 字符串常量是指用一对双引号括起来的一串字符。双引号只起定界作用,双引号括起的 字符串中不能是双引号(")和反斜杠(\),它们特有的表示法在转义字符中介绍。例如: " C h i n a " ,"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个字节, 系统自动在后面加上N U L L字符,其存储形式为: 要特别注意字符串与字符串常量的区别,除了表示形式不同外,其存储性质也不相同, 字符' A '只占1个字节,而字符串常量" A "占2个字节。 第2章 数据类型、运算符和表达式 1 9下载 C h i n a N U L L2.5.3 转义字符 转义字符是C语言中表示字符的一种特殊形式。通常使用转义字符表示 A S C I I码字符集中 不可打印的控制字符和特定功能的字符,如用于表示字符常量的单撇号( '),用于表示字符串 常量的双撇号( ")和反斜杠(\)等。转义字符用反斜杠 \后面跟一个字符或一个八进制或十 六进制数表示。表2 - 4给出了C语言中常用的转义字符。 表2-4 转义字符 转 义 字 符 意 义 A S C I I码值(十进制) \ a 响铃( B E L ) 0 0 7 \ b 退格( B S ) 0 0 8 \ f 换页( F F ) 0 1 2 \ n 换行( L F ) 0 1 0 \ r 回车( C R ) 0 1 3 \ t 水平制表( H T ) 0 0 9 \ v 垂直制表( V T ) 0 11 \ \ 反斜杠 0 9 2 \ ? 问号字符 0 6 3 \ ' 单引号字符 0 3 9 \ " 双引号字符 0 3 4 \ 0 空字符( N U L L ) 0 0 0 \ d d d 任意字符 三位八进制 \ x h h 任意字符 二位十六进制 字符常量中使用单引号和反斜杠以及字符常量中使用双引号和反斜杠时,都必须使用转 义字符表示,即在这些字符前加上反斜杠。 在C程序中使用转义字符 \ d d d或者\ x h h可以方便灵活地表示任意字符。 \ d d d为斜杠后面跟 三位八进制数,该三位八进制数的值即为对应的八进制 A S C I I码值。\ x后面跟两位十六进制数, 该两位十六进制数为对应字符的十六进制 A S C I I码值。 使用转义字符时需要注意以下问题: 1) 转义字符中只能使用小写字母,每个转义字符只能看作一个字符。 2) \v 垂直制表和 \f 换页符对屏幕没有任何影响,但会影响打印机执行响应操作。 3) 在C程序中,使用不可打印字符时,通常用转义字符表示。 2.5.4 符号常量 C语言允许将程序中的常量定义为一个标识符,称为符号常量。符号常量一般使用大写英 文字母表示,以区别于一般用小写字母表示的变量。符号常量在使用前必须先定义,定义的 形式是: #define <符号常量名> <常量> 例如: #define PI 3 . 1 4 1 5 9 2 6 #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 0 C语言程序设计 下载# d e f i n e是C语言的预处理命令,它表示经定义的符号常量在程序运行前将由其对应的常量替换。 定义符号常量的目的是为了提高程序的可读性,便于程序的调试和修改。因此在定义符 号常量名时,应使其尽可能地表达它所代表的常量的含义,例如前面所定义的符号常量名 P I (p),表示圆周率3 . 1 4 1 5 9 2 6。此外,若要对一个程序中多次使用的符号常量的值进行修改, 只须对预处理命令中定义的常量值进行修改即可。 2.5.5 字符变量 字符变量用来存放字符常量,注意只能存放一个字符,不要以为在一个字符变量中可以 放字符串。 字符变量的定义形式如下: char c1, c2; 它表示c 1和c 2为字符变量,各放一个字符。因此可以用下面语句对 c 1、c 2赋值: c1 = 'a'; c2 = 'b'; [例2 - 2 ] m a i n ( ) { char c1,c2; c 1 = 9 7 ; c 2 = 9 8 ; printf("%c %c",c1,c2); } c 1、c 2被指定为字符变量。但在第 3行中,将整数9 7和9 8分别赋给c 1和c 2,它的作用相当 于以下两个赋值语句: c 1 = ' a ' ; c 2 = ' b ' ; 因为' a '和' b '的A S C I I码为9 7和9 8。第4行将输出两个字符。 " % c "是输出字符的格式。程序 输出: R U N ¿ a b [例2 - 3 ] m a i n ( ) { char c1,c2; c 1 = ' a ' ;c 2 = ' b ' ; c1 = c1 - 32; c2 =c2 - 32; p r i n t f ( " % c % c " , c 1 , c 2 ) ; } 运行结果为: R U N ¿ A B 它的作用是将两个小写字母转换为大写字母。因为 ' a '的A S C I I码为9 7,而' A '为6 5,' b '为9 8, ' B '为6 6。从A S C I I代码表中可以看到每一个小写字母比大写字母的 A S C I I码大3 2。即'a'='A' + 3 2。 第2章 数据类型、运算符和表达式 2 1下载2.6 运算符 C语言的内部运算符很丰富,运算符是告诉编译程序执行特定算术或逻辑操作的符号。 C 语言有三大运算符:算术、关系与逻辑、位操作。另外, C还有一些特殊的运算符,用于完成 一些特殊的任务。 2.6.1 算术运算符 表2 - 5列出了C语言中允许的算术运算符。在 C语言中,运算符“ +”、“-”、“*”和“/” 的用法与大多数计算机语言的相同,几乎可用于所有 C语言内定义的数据类型。当“ /”被用 于整数或字符时,结果取整。例如,在整数除法中, 1 0 / 3 = 3。 一元减法的实际效果等于用 - 1乘单个操作数,即任何数值前放置减号将改变其符号。模 运算符“%”在C语言中也同它在其它语言中的用法相同。切记,模运算取整数除法的余数, 所以“%”不能用于f l o a t和d o u b l e类型。 表2-5 算术运算符 运 算 符 作 用 运 算 符 作 用 - 减法,也是一元减法 % 模运算 + 加法 - - 自减(减1) * 乘法 + + 自增(增1) / 除法 下面是说明%用法的程序段。 int x,y; x = 1 0 ; y = 3 ; p r i n t f ( " % d " , x / y ) ; /* 显示 3 */ p r i n t f ( " % d " , x % y ) ; /* 显示 1 ,整数除法的余数 * / x = 1 ; y = 2 ; p r i n t f ( " % 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 ; 自增和自减运算符可用在操作数之前,也可放在其后,例如: x = x + 1;可写成+ + x;或 x + +;但在表达式中这两种用法是有区别的。自增或自减运算符在操作数之前, C语言在引用 2 2 C语言程序设计 下载操作数之前就先执行加1或减1操作;运算符在操作数之后, C语言就先引用操作数的值,而后 再进行加1或减1操作。请看下例: x = 1 0 ; y = + + x ; 此时,y = 11。如果程序改为: x = 1 0 ; y = x + + ; 则y = 1 0。在这两种情况下,x都被置为11,但区别在于设置的时刻,这种对自增和自减发 生时刻的控制是非常有用的。 在大多数C编译程序中,为自增和自减操作生成的程序代码比等价的赋值语句生成的代码 要快得多,所以尽可能采用加 1或减1运算符是一种好的选择。 下面是算术运算符的优先级: 最高 + +、- - -(一元减) *、/、% 最低 +、- 编译程序对同级运算符按从左到右的顺序进行计算。当然,括号可改变计算顺序。 C语言 处理括号的方法与几乎所有的计算机语言相同:强迫某个运算或某组运算的优先级升高。 2.6.3 关系和逻辑运算符 关系运算符中的“关系”二字指的是一个值与另一个值之间的关系,逻辑运算符中的 “逻辑”二字指的是连接关系的方式。因为关系和逻辑运算符常在一起使用,所以将它们放在 一起讨论。 关系和逻辑运算符概念中的关键是 Tr u e(真)和F l a s e(假)。C语言中,非0为Tr u e,0为 F l a s e。使用关系或逻辑运算符的表达式对 Fl a s e和Tu r e分别返回值0或1 (见表2 - 6 )。 表2-6 关系和逻辑运算符 关系运算符 含 义 关系运算符 含 义 > 大于 < = 小于或等于 > = 大于等于 = = 等于 < 小于 ! = 不等于 逻辑运算符 含 义 & & 与 | | 或 ! 非 表2 - 6给出于关系和逻辑运算符,下面用 1和0给出逻辑真值表。 关系和逻辑运算符的优先级比算术运算符低,即像表达式 1 0 > 1 + 1 2的计算可以假定是对表 达式1 0 > ( 1 + 1 2 )的计算,当然,该表达式的结果为 Fl a s e。 在一个表达式中允许运算的组合。例如: 1 0 > 5 & & ! ( 1 0 < 9 ) | | 3 < = 4 第2章 数据类型、运算符和表达式 2 3下载这一表达式的结果为Tr u e。 下表给出了关系和逻辑运算符的相对优先级: 最高 ! >= <= == != & & 最低 | | 同算术表达式一样,在关系或逻辑表达式中也使用括号来修改原计算顺序。 切记,所有关系和逻辑表达式产生的结果不是 0就是1,所以下面的程序段不仅正确而且 将在屏幕上打印数值1。 int x; x = 1 0 0 ; p r i n t f ( " % d " , x > 1 0 ) ; 2.6.4 位操作符 与其它语言不同,C语言支持全部的位操作符( Bitwise Operators)。因为C语言的设计目 的是取代汇编语言,所以它必须支持汇编语言所具有的运算能力。位操作是对字节或字中的 位(b i t)进行测试、置位或移位处理,这里字节或字是针对 C标准中的c h a r和i n t数据类型而言 的。位操作不能用于 f l o a t、d o u b l e、long double、v o i d或其它复杂类型。表 2 - 7给出了位操作 的操作符。位操作中的 A N D、O R和N O T(1的补码)的真值表与逻辑运算等价,唯一不同的 是位操作是逐位进行运算的。 表2-7 位操作符 操 作 符 含 义 操 作 符 含 义 & 与(A N D) ~ 1的补(N O T) | 或(O R) > > 右移 ^ 异或(X O R) < < 左移 下面是异或的真值表。 表2-8 异或的真值表 P q p ^ q 0 0 0 1 0 1 1 1 0 0 1 1 2 4 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如表2 - 8所示,当且仅当一个操作数为 Tr u e时,异或的输出为Tr u e,否则为Fl a s e。 位操作通常用于设备驱动程序,例如调制解调器程序、磁盘文件管理程序和打印机驱动 程序。这是因为位操作可屏蔽掉某些位,如奇偶校验位(奇偶校验位用于确保字节中的其它 位不会发生错误通常奇偶校验位是字节的最高位)。 通常我们可把位操作A N D作为关闭位的手段,这就是说两个操作数中任一为 0的位,其结 果中对应位置为0。例如,下面的函数通过调用函数 r e a d _ m o d e m ( ),从调制解调器端口读入一 个字符,并将奇偶校验位置成 0。 [例2 - 4 ] Char get_char_from_modem() { char ch; ch=read_modem(); /*从调制解调器端口中得到一个字符* / r e t u r n ( c h & 1 2 7 ) ; } 字节的位8是奇偶位,将该字节与一个位 1到位7为1、位8为0的字节进行与操作,可将该 字节的奇偶校验位置成 0。表达式c h & 1 2 7正是将c h中每一位同1 2 7数字的对应位进行与操作, 结果c h的位8被置成了0。在下面的例子中,假定c h接收到字符" A "并且奇偶位已经被置位。 奇偶位 ↓ 110000001 内容为‘A’的c h,其中奇偶校验位为1 0 11111111 二进制的1 2 7执行与操作 & 与操作 010000001 去掉奇偶校验的‘A’ 位操作O R与A N D操作相反,可用来置位。任一操作数中为 1的位将结果的对应位置 1。如 下所示,1 2 8 | 3的情况是: 1000000 128的二进制 0 0 0 0 0 11 3的二进制 | 或操作 1 0 0 0 0 11 结果 异或操作通常缩写为 X O R,当且仅当做比较的两位不同时,才将结果的对应位置位。如 下所示,异或操作1 2 7 ^ 1 2 0的情况是: 0 1111111 127的二进制 0 1111000 120的二进制 ^ 异或操作 0 0 0 0 0 111 结果 一般来说,位的A N D、O R和X O R操作通过对操作数运算,直接对结果变量的每一位分别 处理。正是因为这一原因(还有其它一些原因),位操作通常不像关系和逻辑运算符那样用在 条件语句中,我们可以用例子说明这一点:假定 X = 7,那么 x & & 8为Tu r e ( 1 ) ,而x & 8却为 Fl a s e ( 0 )。 记住,关系和逻辑操作符结果不是 0就是1。而相似的位操作通过相应处理,结果可为任 = = = 第2章 数据类型、运算符和表达式 2 5下载意值。换言之,位操作可以有 0或1以外的其它值,而逻辑运算符的计算结果总是 0或1。 移位操作符> >和< <将变量的各位按要求向或向左移动。右移语句通常形式是: variable >>右移位数 左移语句是: v a r i a b l e < <左移位数 当某位从一端移出时,另一端移入 0(某些计算机是送1,详细内容请查阅相应C编译程序 用户手册)。切记:移位不同于循环,从一端移出的位并不送回到另一端去,移去的位永远丢 失了,同时在另一端补0。 移位操作可对外部设备(如 D / A转换器)的输入和状态信息进行译码,移位操作还可用于 整数的快速乘除运算。如表 2 - 9所示(假定移位时补 0),左移一位等效于乘 2,而右移一位等 效于除以2。 表2-9 用移位操作进行乘和除 字 符 x 每个语句执行后的x x 的 值 x = 7 0 0 0 0 0 111 7 x < < 1 0 0 0 0 111 0 1 4 x < < 3 0 111 0 0 0 0 11 2 x < < 2 11 0 0 0 0 0 0 1 9 2 x > > 1 0 11 0 0 0 0 0 9 6 x > > 2 0 0 0 11 0 0 0 2 4 每左移一位乘2,注意x < < 2后,原x的信息已经丢失了,因为一位已经从一端出,每右移 一位相当于被2除,注意,乘后再除时,除操作并不带回乘法时已经丢掉的高位。 反码操作符为~。~的作用是将特定变量的各位状态取反,即将所有的 1位置成0,所有的0 位置成1。 位操作符经常用在加密程序中,例如,若想生成一个不可读磁盘文件时,可以在文件上 做一些位操作。最简单的方法是用下述方法,通过 1的反码运算,将每个字节的每一位取反。 原字节 0 0 1 0 11 0 0 第一次取反码 11 0 1 0 0 11 第二次取反码 0 0 1 0 11 0 0 注意,对同一行进行连续的两次求反,总是得到原来的数字,所以第一次求反表示了字 节的编码,第二次求反进行译码又得到了原来的值。 可以用下面的函数e n c o d e ( )对字符进行编码。 [例2 - 5 ] char encode(ch) char ch; { return (~ch); } 2.6.5 ?操作符 C语言提供了一个可以代替某些 i f - t h e n - e l s e语句的简便易用的操作符?。该操作符是三元 2 6 C语言程序设计 下载的,其一般形式为: E X P 1 ? E X E 2 : E X P 3 E X P 1,E X P 2和E X P 3是表达式,注意冒号的用法和位置。 操作符“ ? ”作用是这样的,在计算E X P 1之后,如果数值为Tr u e,则计算E X P 2,并将结 果作为整个表达式的数值;如果 E X P 1的值为Fl a s e,则计算E X P 3,并以它的结果作为整个表 达式的值,请看下例: x = 1 0 ; y = x > 9 ? 1 0 0 : 2 0 0 ; 例中,赋给y的数值是1 0 0,如果x被赋给比9小的值,y的值将为2 0 0,若用i f - e l s e语句改写,有 下面的等价程序: x = 1 0 ; if(x>9) y=100; else y=200; 有关C语言中的其它条件语句将在第 3章进行讨论。 2.6.6 逗号操作符 作为一个操作符,逗号把几个表达式串在一起。逗号操作符的左侧总是作为 v o i d (无值), 这意味着其右边表达式的值变为以逗号分开的整个表达式的值。例如: x = ( y = 3 , y + 1 ) ; 这行将3赋给y,然后将4赋给x,因为逗号操作符的优先级比赋值操作符优先级低,所以 必须使用括号。 实际上,逗号表示操作顺序。当它在赋值语句右边使用时,所赋的值是逗号分隔开的表 中最后那个表达式的值。例如, y = 1 0 ; x = ( y = y - 5 , 2 5 / y ) ; 执行后,x的值是5,因为y的起始值是1 0,减去5之后结果再除以2 5,得到最终结果。 在某种意义上可以认为,逗号操作符和标准英语的 a n d是同义词。 2.6.7 关于优先级的小结 表2 - 1 0列出了C语言所有操作符的优先级,其中包括将在本书后面讨论的某些操作符。注 意,所有操作符(除一元操作符和?之外)都是左结合的。一元操作符( *,&和-)及操作符 “?”则为右结合。 表2-10 C语言操作符的优先级 最 高 级 ()[] → !~ ++ -- -(type) * & sizeof * / % + - << >> <= >= == != 第2章 数据类型、运算符和表达式 2 7下载(续) & ^ | & & | | ? = += -= *= /= 最低级 , 2.7 表达式 表达式由运算符、常量及变量构成。 C语言的表达式基本遵循一般代数规则,有几点却是 与C语言紧密相关的,以下将分别加以讨论。 2.7.1 表达式中的类型转换 混合于同一表达式中的不同类型常量及变量,应均变换为同一类型的量。 C语言的编译程 序将所有操作数变换为与最大类型操作数同类型。变换以一次一操作的方式进行。具体规则 如下: 图2-1 类型转换实例 1 ) 所有c h a r及short int 型量转为i n t型,所有f l o a t转换为d o u b l e。 2) 如操作数对中一个为 long double,另一个转换为 long double。① 要不然,一个为 d o u b l e,另一个转为d o u b l e。② 要不然,一个为l o n g,另一个转为l o n g。③ 要不然,一个为 u n s i g n e d,另一个转为u n s i g n e d。 一旦运用以上规则。每一对操作数均变为同类型。注意,规则 2 )有几种必须依次应用的 条件。 图2 - 1示出了类型转换。首先, char ch转换成 i n t,且float f 转换成d o u b l e;然后c h / i的结 果转换成 d o u b l e,因为f * d是d o u b l e;最后由于这次两个操作数都是 d o u b l e,所以结果也是 2 8 C语言程序设计 下载 char ch; int i; float f; double d; result=(ch / i) + ( f * d ) - ( f + i ); int double double int double double d o u b l e d o u b l ed o u b l e . 2.7.2 构成符cast 可以通过称为c a s t的构成符强迫一表达式变为特定类型。其一般形式为: (t y p e )e x p r e s s i o n ( t y p e )是标准 C语言中的一个数据类型。例如,为确保表达式 x / 2的结果具有类型 f l o a t,可写 为: (f l o a t )x / 2 通常认为c a s t是操作符。作为操作符,c a s t是一元的,并且同其它一元操作符优先级相同。 虽然c a s t在程序中用得不多,但有时它的使用的确很有价值。例如,假设希望用一整数控 制循环,但在执行计算时又要有小数部分。 [例2 - 6 ] main() { int i ; for (i+1;i<=100;++i) printf("%d/2 is :%f",i,(float)i/2); } 若没有c a s t ( f l o a t ),就仅执行一次整数除;有了 c a s t就可保证在屏幕上显示答案的小数部 分。 2.7.3 空格与括号 为了增加可读性,可以随意在表达式中插入tab和空格符。例如,下面两个表达式是相同的。 x = 1 0 / y * ( 1 2 7 / x ) ; x = 1 0 / y * ( 1 2 7 / x ) ; 冗余的括号并不导致错误或减慢表达式的执行速度。我们鼓励使用括号,它可使执行顺 序更清楚一些。例如,下面两个表达式中哪个更易读一些呢? x = y / 2 - 3 4 * t e m p & 1 2 7 ; x = ( y / 2 ) - ( ( 3 4 * t e m p ) & 1 2 7 ) ; 2.7.4 C语言中的简写形式 C语言提供了某些赋值语句的简写形式。例如语句: x = x + 1 0 ; 在C语言中简写形式是: x + = 1 0 ; 这组操作符对 + =通知编译程序将 X + 1 0的值赋予X。这一简写形式适于 C语言的所有二元 操作符(需两个操作数的操作符)。在C语言中, variable=variable1 operator expression; 第2章 数据类型、运算符和表达式 2 9下载与variable1 operator=expression相同。 请看另一个例子: x = x - 1 0 0 ; 其等价语句是 x - = 1 0 0 ; 简写形式广泛应用于专业C语言程序中,希望读者能熟悉它。 3 0 C语言程序设计 下载下载 第3章 程序控制语句 3.1 程序的三种基本结构 通常的计算机程序总是由若干条语句组成,从执行方式上看,从第一条语句到最后一条 语句完全按顺序执行,是简单的顺序结构;若在程序执行过程当中,根据用户的输入或中间 结果去执行若干不同的任务则为选择结构;如果在程序的某处,需要根据某项条件重复地执 行某项任务若干次或直到满足或不满足某条件为止,这就构成循环结构。大多数情况下,程 序都不会是简单的顺序结构,而是顺序、选择、循环三种结构的复杂组合。 三种基本结构的流程图、N - S图以及PAD图可以参看本书第1章1 . 4节“算法”相关内容。 C语言中,有一组相关的控制语句,用以实现选择结构与循环结构: 选择控制语句: i f; s w i t c h、c a s e 循环控制语句: f o r、w h i l e、d o⋯ w h i l e 转移控制语句: b r e a k、c o n t i n u e、g o t o 我们将在后面几节中详细介绍。 3.2 数据的输入与输出 在程序的运行过程中,往往需要由用户输入一些数据,而程序运算所得到的计算结果等 又需要输出给用户,由此实现人与计算机之间的交互,所以在程序设计中,输入输出语句是 一类必不可少的重要语句,在 C语言中,没有专门的输入输出语句,所有的输入输出操作都是 通过对标准I / O库函数的调用实现。最常用的输入输出函数有 s c a n f ( )、p r i n t f ( )、g e t c h a r ( )和 p u t c h a r ( ),以下分别介绍。 3.2.1 scanf()函数 格式化输入函数 s c a n f ( )的功能是从键盘上输入数据,该输入数据按指定的输入格式被赋 给相应的输入项。函数一般格式为: s c a n f ( "控制字符串",输入项列表); 其中控制字符串规定数据的输入格式,必须用双引号括起,其内容是由格式说明和普通 字符两部分组成。输入项列表则由一个或多个变量地址组成,当变量地址有多个时,各变量 地址之间用逗号“,”分隔。 s c a n f ( )中各变量要加地址操作符,就是变量名前加“ &”,这是初学者容易忽略的一个问 题。应注意输入类型与变量类型一致。 下面探讨控制字符串的两个组成部分:格式说明和普通字符。 1. 格式说明 格式说明规定了输入项中的变量以何种类型的数据格式被输入,形式是:3 2 C语言程序设计 下载 % [ <修饰符> ] <格式字> 各个格式字符及其意义见表 3 - 1。 表3-1 输入格式字符 格 式 字 符 意 义 d 输入一个十进制整数 o 输入一个八进制整数 x 输入一个十六进制整数 f 输入一个小数形式的浮点数 e 输入一个指数形式的浮点数 c 输入一个字符 s 输入一个字符串 各修饰符是可选的,可以没有,这些修饰符是: ⑴ 字段宽度 例如:s c a n f ( " % 3 d ",& a ) 按宽度3输入一个整数赋给变量 a。 ⑵ l和h 可以和d、o、x一起使用,加 l表示输入数据为长整数,加 h表示输入数据为短整数,例 如: s c a n f ( " % 1 0 l d % h d " , & x , & i ) 则x按宽度为1 0的长整型读入,而i按短整数读入。 ⑶ 字符* *表示按规定格式输入但不赋予相应变量,作用是跳过相应的数据。 例如: s c a n f ( " % 4 d % * d % 4 d " , & x , & y , & z ) 执行该语句,若输入为“1 2 3¿ ” 结果为x = 1,z = 3,y未赋值,2被跳过。 2. 普通字符 普通字符包括空格、转义字符和可打印字符。 (1) 空格 在有多个输入项时,一般用空格或回车作为分隔符,若以空格作分隔符,则当输入项中 包含字符类型时,可能产生非预期的结果,例如: s c a n f ( " % d % c " , & a , & c h ) 输入3 2 q 期望a = 3 2,c h = q,但实际上,分隔符空格被读入并赋给 c h。 为避免这种情况,可使用如下语句: s c a n f ( " % d % c " , & a , & c h ) 此处% d后的空格,就可跳过字符“ q”前的所有空格,保证非空格数据的正确录入。 (2) 转义字符:\ n、\ t 先看下面的例子:s c a n f ( " % d % d " , & a , & b ) ; s c a n f ( " % d % d % d " , & x , & y , & z ) ; 输入为 1 2 3¿ 4 5 6¿ 结果为:a = 1, b = 2, x = 3, y = 4, z = 5 若将上述语句改为: s c a n f ( " % d % d \ n " , & a , & b ) ; s c a n f ( " % d % d % d " , & x , & y , & z ) ; 对同样的输入,其结果为 a = 1,b = 2,x = 4,y = 5,z = 6,由于在第一个s c a n f的最后有一个 \ n,所以第二个s c a n f语句将从第二个输入行获得数据。 (3) 可打印字符 例如:s c a n f ( " % d,% d,% c ",& a,& b,& c h ) ; 当输入为:1, 2, q¿ 即:a = 1,b = 2,c h = q 若输入为1 2 q¿ 除a = 1正确赋值外,对 b与c h的赋值都将以失败告终,也就是说,这些不打印字符应是输 入数据分隔符,s c a n f在读入时自动去除与可打印字符相同的字符。 [例3-1] 试编写求梯形面积的程序,数据由键盘输入。 分析:设梯形上底为A,下底为B,高为H面职为S,则 S=(A+B)×H÷2 程序如下: m a i n ( ) { float a,b,h,s; printf("please input a,b,h:"); s c a n f ( " % 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); p r i n t f ( " s = % 7 . 4 f " , s ) ; } 运行结果如下: R U N ¿ please input a,b,h:3.5 4.2 2.8¿ a=3.50 b=4.20 h=2.80 s = 1 0 . 7 8 0 0 3.2.2 printf( )函数 与格式化输入函数 s c a n f ( )相对应的是格式化输出函数 p r i n t f ( ),其功能为按控制字符串规 定的格式,向缺省输出设备(一般为显示器)输出在输出项列表中列出的各输出项,其基本 格式为: p r i n t f(“控制字符串”,输出项列表) 第3章 程序控制语句 3 3下载输出项可以是常量、变量、表达式,其类型与个数必须与控制字符串中格式字符的类型、 个数一致、当有多个输出项时,各项之间用逗号分隔。 控制字符串必须用双引号括起,由格式说明和普通字符两部分组成。 1. 格式说明 一般格式为: % [ <修饰符> ] <格式字符> 格式字符规定了对应输出项的输出格式,常用格式字符见表 3 - 2。 表3-2 输出格式字符 格 式 字 符 意 义 格 式 字 符 意 义 c 按字符型输出 o 按八进制整数输出 d 按十进制整数输出 x 按十六进制整数输出 u 按无符号整数输出 s 按字符串输出 f 按浮点型小数输出 g 按e和f格式中较短的一种输出 e 按科学计数法输出 修饰符是可选的,用于确定数据输出的宽度、精度、小数位数、对齐方式等,用于产生 更规范整齐的输出,当没有修饰符时,以上各项按系统缺省设定显示。 (1) 字段宽度修饰符 表3 - 3列出了字段宽度修饰符。 表3-3 字段宽度修饰符 修 饰 符 格 式 说 明 意 义 M % m d 以宽度m输出整型数,不足m时,左补空格 0 m % 0 m d 以宽度m输出整型数,不足m时,左补零 m,n %m .nf 以宽度m输出实型小数,小数位为n位 例如:设i = 1 2 3,a = 1 2 . 3 4 5 6 7, 则: p r i n t f ( " % 4 d + + + % 5 . 2 f ",i,a ) ; 输出: 1 2 3 + + + 1 2 . 3 5 p r i n t f ( " % 2 d + + + % 2 . 1 f ",i,a ) ; 输出: 1 2 3 + + + 1 2 . 3 可以看出,当指定场宽小于数据的实际宽度时,对整数,按该数的实际场宽输出,对浮 点数,相应小数位的数四舍五入。例如: 1 2 . 3 4 5 6 7按%5.2f 输出,输出1 2 . 3 5。若场宽小于等 于浮点数整数部分的宽度,则该浮点数按实际位数输出,但小数位数仍遵守宽度修饰符给出 的值。如上面的1 2 . 3 4 5 6 7按%2.1f 输出,结果为:1 2 . 3。 在实际应用中,还有一种更灵活的场宽控制方法,用常量或变量的值作为输出场宽,方 法是以一个" * "作为修饰符,插入到%之后。 例如:i = 1 2 3 ; p r i n t f ( " % * d " , 5 , i ) ; 3 4 C语言程序设计 下载此处,5为场宽,输出为 1 2 3 在程序中,可以用一个整形变量K来指示场宽: p r i n t f ( " % * d " , k , i ) ; 可以根据k的值动态地决定i的显示场宽,这在解某些问题时是相当有用的。 (2) 对齐方式修饰符 负号“-”为“左对齐”控制符,一般所有输出数据为右对齐格式,加一个“ -”号,则 变为“左对齐”方式。 例如: i=1 2 3,a = 1 2 . 3 4 5 6 7 p r i n t f (“% 4 d % 1 0 . 4 f”,i,a ) ; 输出为: 1 2 3 1 2 . 3 4 5 7 p r i n t f (“% - 4 d % 1 0 . 4 f”,i,a ) ; 输出为: 1 2 3 1 2 . 3 4 5 7 p r i n t f (“% 4 d % - 1 0 . 4 f”,i,a ) ; 输出为: 1 2 3 1 2 . 3 4 5 7 (3) l和h 可以与输出格式字符d、f、u等连用,以说明是用l o n g型或s h o r t型格式输出数据,如: %hd 短整型 %lf 精度型 %ld 长整型 %hu 无符号短整型 2. 普通字符 普通字符包括可打印字符和转义字符,可打印字符主要是一些说明字符,这些字符按原 样显示在屏幕上,如果有汉字系统支持,也可以输出汉字。 转义字符是不可打印的字符,它们其实是一些控制字符,控制产生特殊的输出效果。 例如:i = 1 2 3,n = 4 5 6,a = 1 2 . 3 4 5 6 7,且i为整型,n为长整型。 p r i n t f ( " % 4 d \ t % 7 . 4 f \ n \ t % l u \ n " , i , a , n ) ; 输出为: 1 2 3 1 2 . 3 4 5 7 4 5 6 其中\ t为水平制表符,作用是跳到下一个水平制表位,在各个机器中,水平制表位的宽度 是不一样的,这里设为8个字符宽度,那么“ \ t”跳到下一个8的倍数的列上。 “\ n”为回车换行符,遇到“\ n”,显示自动换到新的一行。 在c语言中,如果要输出%,则在控制字符中用两个%表示,即%%。 [例3-2] 输出格式控制符的使用。 # include m a i n ( ) { int a; 第3章 程序控制语句 3 5下载3 6 C语言程序设计 下载 long int b; short int c; unsigned int d; char e; float f; double g; a = 1 0 2 3 ; b = 2 2 2 2 ; c = 1 2 3 ; d = 1 2 3 4 ; e = ' x ' ; f = 3 . 1 4 1 5 9 2 6 5 3 5 8 9 8 ; g = 3 . 1 4 1 5 9 2 6 5 3 5 8 9 8 ; p r i n t f ( " a = % d \ n " , a ) ; p r i n t f ( " a = % 0 \ n " , a ) ; p r i n t f ( " a = % x \ n " , a ) ; p r i n t f ( " b = % l d \ n " , b ) ; p r i n t f ( " c = % d \ n " , c ) ; p r i n t f ( " d = % u \ n " , d ) ; p r i n t f ( " e = % c \ n " , e ) ; p r i n t f ( " f = % f \ n " , f ) ; p r i n t f ( " g = % f \ n " , g ) ; p r i n t f ( " \ n " ) ; } 执行程序,输出为: R U N ¿ a = 1 0 2 3 a = 1 7 7 7 a = 3 f f b = 2 2 2 2 c = 1 2 3 d = 1 2 3 4 e = x f = 3 . 1 4 1 5 9 3 g = 3 . 1 4 1 5 9 3 3.2.3 getchar()函数与putchar()函数 putchar() 与g e t c h a r ( )是对单个字符进行输入输出的函数。 g e t c h a r ( )的功能是返回键盘输入的一个字符,它不带任何参数,其通常格式如下: c h = g e t c h a r ( ) c h为字符型变量,上述语句接收从键盘输入的一个字符并将它赋给 c h。 p u t c h a r ( )的作用是向屏幕上输出一个字符,它的功能与 p r i n t f函数中的% c相当。p u t c h a r ( )必 须带输出项,输出项可以是字符型常量、变量、表达式,但只能是单个字符而不能是字符串。 [例3-3] 输入一个字符,回显该字符并输出其 A S C I I码值。 # i n c l u d e < s t d i o . h > m a i n ( ){ char ch; c h = g e t c h a r ( ) ; p u t c h a r ( c h ) ; p r i n t f ( " % d \ n " , c h ) ; } 运行程序: R U N ¿ g¿ g 103 需要注意的是,字符输入输出函数定义在头文件 s t d i o . h中,故当程序中使用 p u t c h a r ( )或 g e t c h a r ( )函数时,必须在m a i n ( )之前用语句: #i n c l u d e " s t d i o . h " 将s t d i o . h包含进来。 3.2.4 程序应用举例 [例3-4] 下面的程序是一个复数加法的例子。 #include m a i n ( ) { float a1,b1,a2,b2; char ch; printf("\t\t\tcomplexs Addition\n"); printf("please input the first complex:\n"); printf("\t realpart:"); s c a n f ( " % f " , & a 1 ) ; printf("\t virtualpart:"); s c a n f ( " % f " , & b 1 ) ; 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 :"); s c a n f ( " % f " , & b 2 ) ; 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..."); c h = g e t c h a r ( ) ; ch=getchar(); } 运行结果如下: R U N ¿ complexs addition please input the first complex : r e a l p a r t :1 . 2 ¿ v i r t u a l p a r t :3 . 4 ¿ 第3章 程序控制语句 3 7下载3 8 C语言程序设计 下载 1.20 +i 3.40 please input the second complex : r e a l p a r t :5 . 6 ¿ v i r t u a l p a r t :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与M I N皆定义为i n t型变量。 每次比较两个数,首先比较 A和B,将小的一个赋给 M I N,再把第三个数 C与M I N比较, 再将小的一个赋给M I N,则最后M I N即为A、B、C中最小数。 算法如下: 1) 输入A、B、C。 2) 将A与B中小的一个赋给M I N。 3 ) 将M I N与C中小的一个赋给M I N。 4) 输出M I N。 将第2)步细化为:若A < B,则MIN <==A,否则:MIN <==B;其流程图见图3- 1。 第3)步细化为:若C < M I N,则MIN <==C;其流程图见图3 - 2。 图3-1 例3-5中第2)步的流程图 图3-2 例3-5中第3)步的流程图 对应图3 - 1和图3 - 2,正是i f语句的两种基本形式,与图 3 - 2对应的i f语句的格式为: if <表达式> 语句 当表达式为真时,执行语句,表达式为假时跳过语句。 A = y,只需顺序打出,否则,应将 x,y中的数进行交换,然 后输出。两数交换必须使用一个中间变量 t, 定义三个浮点数x、y、t。 算法: 1) 读入x、y; 2 ) 大数存入x,小数存入y; 3 ) 输出x、y。 第2)步求精: 若x < y,则交换x与y; 再求精,x与y交换; ① t <== x ② x <== y ③ y <== t 算法的流程图见图3 - 3,程序如下: # include m a i n ( ) { float x,y,t; printf("input x,y:"); s c a n f ( " % f % f " , & x , & y ) ; if (x 语句1 else if<表达式2 > 语句2 else if <表达式3 > 语句3 else 语句4 对应的流程图见图3 - 4。 4 0 C语言程序设计 下载 开始 结束 x = 1 0 0 0 0 则 t a x = 0 . 0 5 *(p r i c e - 1 0 0 0 0); price=10000; 否则,若p r i c e > = 5 0 0 0 则 t a x = 0 . 0 3 * ( p r i c e-5000)+tax; price=5000; 否则,若p r i c e > = 1 0 0 0 则 t a x = 0 . 0 2 * ( p r i c e-1000)+tax; price=1000; 程序如下: # include m a i n ( ) { float price,tax=0; printf("input price:"); s c a n f ( " % f " , & p r i c e ) ; i f ( p r i c e > = 1 0 0 0 0 . 0 ) { tax=0.05*(price-10000)+tax; price=10000; } if (price>=5000.0) { t a x = 0 . 0 3 * ( p r i c e - 5 0 0 0 ) + t a x ; p r i c e = 5 0 0 0 ; } i f ( p r i c e > = 1 0 0 0 . 0 0 ) { t a x = 0 . 0 2 * ( p r i c e - 1 0 0 0 ) + t a x ; } printf("the tax=%10.3 f",tax); } 第3章 程序控制语句 4 1下载 语句1 语句2 语句3 语句4 表达式2 表达式3 表达式1 真 真 假 假 假4 2 C语言程序设计 下载 运行程序: R U N ¿ 1 5 0 0 0 ¿ the tax=480.000 4. if语句嵌套 在一个if 语句中可以又出现另一个i f语句,这称为i f语句的嵌套或多重i f语句: if <表达式1 > i f < 表达式11> ⋯⋯ e l s e 语句2; [例3-8] 计算函数 1 x>0 y= 0 x=0 -1 x < 0 流程图见图3 - 5。 图3-5 例3-8的流程图 源程序如下: m a i n ( ) { float x,y; printf("input x,y:"); s c a n f ( " % f " , & x ) ; if (x>=0) if (x>0) y = 1 ; e l s e BEGIN END 输入x x>=0 x>0 y=0 y=-1 y=1 输出 y 真 真 假 假第3章 程序控制语句 4 3下载 y = 0 ; e l s e y = - 1 ; p r i n t f ( " y = % 4 . 0 f \ n " , y ) ; } 对多重i f,最容易犯的错误是 i f与e l s e配对错误, 例如,写成如下形式: y = 0 ; if (x>=0) if (x>0) y = 1 ; e l s e y = - 1 ; 从缩排上可以看出,作者希望 e l s e是与if x>=0配对,但是C语言规定e l s e总是与离它最近 的上一个if 配对,结果,上述算法的流程图变成图 3 - 6,完全违背了设计者的初衷。 改进的办法是使用复合语句,将上述程序段改写如下: y = 0 ; if (x > = 0 ) { if (x > 0 ) y = 1 ; } e l s e y = - 1 ; 3.3.2 switch 语句 i f 语句只能处理从两者间选择之一,当要实现几种可能之一时,就要用 if...else if甚至多 重的嵌套i f来实现,当分支较多时,程序变得复杂冗长,可读性降低。 C语言提供了s w i t c h开 关语句专门处理多路分支的情形,使程序变得简洁。 s w i t c h语句的一般格式为: switch <表达式> case 常量表达式1:语句序列1; b r e a k ; case 常量表达式2:语句序列2; b r e a k ; ⋯⋯ case 常量表达式n :语句n ; b r e a k ; default: 语句n + 1 ; 其中常量表达式的值必须是整型,字符型或者枚举类型,各语句序列允许有多条语句, 不需要按复合语句处理,若语句序列 i为空,则对应的b r e a k语句可去掉。图3 - 7是s w i t c h语句的 流程图。 特殊情况下,如果s w i t c h表达式的多个值都需要执行相同的语句,可以采用下面的格式: y=0 y=1 x>=0 x>0 y=-1 真 真 图3-6 错误的算法流程图 假4 4 C语言程序设计 下载 switch (i) { case 1: case 2: case 3: 语句1; b r e a k ; case 4: case 5: 语句2; b r e a k ; default: 语句3; } 当整型变量i的值为1、2或3时,执行语句1,当i的值为4或5时,执行语句2,否则,执行 语句3。 [例3-9] 输入月份,打印1 9 9 9年该月有几天。 程序如下: #include m a i n ( ) { int month; int day; 表达式= 常量表达式1 表达式= 常量表达式2 表达式= 常量表达式i 表达式=常 量表达式i+1 表达式= 常量表达式n 语句序列1 语句序列2 语句序列i+1 语句序列n 语句序列n+1 BREAK BREAK BREAK BREAK 图3-7 switch 语句的流程图第3章 程序控制语句 4 5下载 printf("please input the month number :"); s c a n f ( " % d " , & m o n t h ) ; switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: day=31; b r e a k ; case 4: case 6: case 9: case 11:day=30; b r e a k ; case 2: day=28; b r e a k ; default : day=-1; } if day=-1 printf("Invalid month input !\n"); e l s e printf("1999.%d has %d days \n",month,day); } 3.3.3 程序应用举例 [例3-10] 解一元二次方程a x2+ b x + c = 0,a、b、c由键盘输入。 分析:对系数a、b、c考虑以下情形 1) 若a = 0: ① b < > 0,则x=-c/b ; ② b = 0, 则:① c = 0, 则x无定根; ② c < > 0,则x无解。 2) 若a < > 0 ; ① b2-4 a c > 0,有两个不等的实根; ② b2-4 a c = 0,有两个相等的实根; ③ b2-4 a c < 0,有两个共轭复根。 用嵌套的i f语句完成。程序如下: #include # include m a i n ( ) { float a,b,c,s,x1,x2; double t;4 6 C语言程序设计 下载 printf(" please input a,b,c:"); s c a n f ( " % f % f % f " , & a , & b , & c ) ; if (a==0.0) i f ( b ! = 0 . 0 ) printf("the root is :%f\n",-c/b); else if (c==0.0) printf("x is inexactive\n "); e l s e printf("no root!\n"); e l s e { s = b * b - 4 * a * c ; i f ( s > = 0 . 0 ) i f ( s > 0 . 0 ) { t = s q r t ( s ) ; x 1 = - 0 . 5 * ( b + t ) / a ; x 2 = - 0 . 5 * ( b - t ) / a ; printf("There are two different roots:%f and%f\n",x 1 , x 2 ) ; } e l s e printf("There are two equal roots:%f\n",-0.5*b/a); e l s e { t = s q r t ( - s ) ; x1=-0.5*b/a; / *实部* / x2=abs(0.5*t/a); /*虚部的绝对值* / printf("There are two virtual roots:"); printf("%f+i%f\t\t%f-i%f\n",x1,x2,x1,x2 ); } } } 运行结果如下: R U N ¿ please input a,b,c : 1 2 3¿ There are two virtual roots: -1.000000 + i 1.000000 -1.000000 - i 1.000000 R N U ¿ please input a,b,c : 2 5 3¿ There are two different roots : -1.500000 and -1.000000 R N U ¿ please input a,b,c : 0 0 3¿ No root! 3.4 循环控制语句 循环控制结构(又称重复结构)是程序中的另一个基本结构。在实际问题中,常常需要 进行大量的重复处理,循环结构可以使我们只写很少的语句,而让计算机反复执行,从而完成大量类同的计算。 C语言提供了w h i l e语句、d o . . . w h i l e语句和f o r语句实现循环结构。 3.4.1 while语句 w h i l e语句是当型循环控制语句,一般形式为 : while <表达式> 语句; 语句部分称为循环体,当需要执行多条语句时,应使用复合语 句。 w h i l e语句的流程图见图 3 - 8,其特点是先判断,后执行,若条 件不成立,有可能一次也不执行。 [例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,程序如下: m a i n ( ) { int n,i; long int s; printf(" please input n (n>=0) :"); s c a n f ( " % 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); } e l s e printf("Invalid input! \n"); } 运行结果如下: R U N ¿ please input n(n>=0): 0¿ 0!= 1 第3章 程序控制语句 4 7下载 语句 真 假 表达式 图3-8 while语句的流程图4 8 C语言程序设计 下载 R U N ¿ please input n(n>=0): 6¿ 6!= 720 R U N ¿ please input n(n>=0): - 2¿ Invalid input! 考察图3- 9中循环部分的流程图可以看出,在循环前各变量应有合适的值 ( s = 1 ),另外,控 制循环结束的变量 (此处为i )必须在循环体中被改变,否则,循环将无限进行下去,成为死循 环。 图3-9 例3-11的算法流程图 [例3-12] 利用格里高利公式求p : p/4 = 1 - 1/3 + 1/5 - 1/7 + ... 直到最后一项的绝对值小于等于 1 0- 6为止。 程序如下: # include # include { m a i n ( ) { double e,p i ; long int n,s ; t = 1 . 0 ; n = 1 ; s = 1 ; p i = 0 . 0 ; BEGIN 读入 n N>=0 S=1 假真 真 真 假 i=1 i=i+1 s=s*i 输出s 报错 END i<=n N>=0第3章 程序控制语句 4 9下载 while (fabs(t)>=1e-6) { p i = p i + t ; n = n + 2 ; s = - s ; t = ( f l o a t ) ( s ) / ( f l o a t ) ( n ) ; } p i = p i * 4 ; printf(" pi = %lf\n",p i ) ; } 运行结果为: R U N ¿ pi = 3.141591 本题中,将多项式的每一项用 t表示,s代表符号,在每一次循环中,只要改变 s、n的值, 就可求出每一项t。 一般情况下,w h i l e型循环最适合于这种情况:知道控制循环的条件为某个逻辑表达式的 值,而且该表达式的值会在循环中被改变,如同例 3 - 1 2的情况一样。 3.4.2 do... while 语句 在C语句中,直到型循环的语句是 do . . .while ,它的一般形式为: do 语句 while <表达式> 其中语句通常为复合语句,称为循环体。 d o. . .while 语句的流程图见图 3 - 1 0,其基本特点是:先执行后判断, 因此,循环体至少被执行一次。 但需要注意的是,d o. . .w h i l e与标准的直到型循环有一个极为重要的区 别,直到型循环是当条件为真时结束循环,而 d o. . .w h i l e语句恰恰相反, 当条件为真时循环,一旦条件为假,立即结束循环,请注意 d o. . .w h i l e语句的这一特点。 例[3-13] 计算sin(x) = x- x3/3! + x5/5! - x7/7! + ... 直到最后一项的绝对值小于 1 e - 7时为止。 分析:这道题使用递推方法来做。 让多项式的每一项与一个变量 n对应,n的值依次为1,3,5,7,. . .,从多项式的前一项 算后一项,只需将前一项乘一个因子: ( - x 2) / ( ( n - 1 ) * n ) 用s表示多项式的值,用t表示每一项的值,程序如下 : #include # include m a i n ( ) { double s,t,x ; int n ; printf("please input x :"); 语句 表达式 真 图3-10 do...while语 句的流程图5 0 C语言程序设计 下载 s c a n f ( " % l f " ,& x ) ; t = x ; n=1; s = x ; d o { n = n + 2 ; t = t * ( - x * x ) / ( ( f l o a t ) ( n ) - 1 ) / ( f l o a t ) ( n ) ; s = s + t ; } while (fabs(t)>=1e-7); printf("sin(%f )=%lf",x,s ) ; } 运行结果如下: R U N ¿ please input x:1 . 5 7 5 3 ¿ s i n ( 1 . 5 7 5 3 0 0 ) = 0 . 9 9 9 9 9 0 R U N ¿ please input x:- 0 . 6 5 ¿ s i n ( - 0 . 6 5 0 0 0 0 ) = - 0 . 6 0 5 1 8 6 3.4.3 for 语句 f o r语句是循环控制结构中使用最广泛的一种循环控制语句,特别适合已知循环次数的情 况。它的一般形式为: for (<表达式1 >;<表达式2 >;<表达式3>) 语句 f o r语句很好地体现了正确表达循环结构应注意的三个问题: 1) 控制变量的初始化。 2) 循环的条件。 3) 循环控制变量的更新。 表达式1:一般为赋值表达式,给控制变量赋初值; 表达式2:关系表达式或逻辑表达式,循环控制条件; 表达式3:一般为赋值表达式,给控制变量增量或减量。 语句:循环体,当有多条语句时,必须使用复合语句。 f o r循环的流程图如图3 - 11,其执行过程如下: 首先计算表达式1,然后计算表达式2,若表达式2为真,则执行循环体;否则,退出 f o r循 环,执行f o r循环后的语句。如果执行了循环体,则循环体每执行一次,都计算表达式 3,然后 重新计算表达式2,依此循环,直至表达式2的值为假,退出循环。 [例3-14] 计算自然数1到n的平方和。 # include # include m a i n ( ) { int i; float s; 表达式1 表达式 3 循环体 表达式 2 假 图3-11 for 循环的流程图 真第3章 程序控制语句 5 1下载 printf("please input n :"); s c a n f ( " % d " ,& n ) ; s = 0 . 0 ; f o r ( i = 1 ; i < = n ; i + + ) s = s + ( f l o a t ) ( i ) * ( f l o a t ) ( i ) ; printf("1*1 + 2*2 +...+%d*%d = %f\n",n,n,s ) ; } 运行结果如下: R U N ¿ please input n : 5¿ 1*1 + 2*2 + ... + 5* 5 = 55.000000 f o r语句的几种格式 f o r语句的三个表达式都是可以省略的,但分号“;”绝对不能省略。 a. for(; ;)语句; 这是一个死循环,一般用条件表达式加 b r e a k语句在循环体内适当位置,一旦条件满足时, 用b r e a k语句跳出f o r循环。 例如,在编制菜单控制程序时,可以如下: for(; ;) { printf("please input choice( Q=Exit):"); /* 显示菜单语句块:* / s c a n f ( " % c " ,& c h ) ; if (ch=='Q') or (ch=='q') break; /* 语句段 * / } b. for(;表达式2;表达式3 ) 使用条件是:循环控制变量的初值不是已知常量,而是在前面通过计算得到,例如: i = m - n ; ⋯⋯ f o r ( ;i < k ;i++) 语句; c. for(表达式1;表达式2;)语句 一般当循环控制变量非规则变化,而且循环体中有更新控制变量的语句时使用。 例如: f o r ( i = 1 ;i < = 1 0 0 ;) { ⋯⋯ 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语句,下面再讨论两个问题: 三种语句的选用 同一个问题,往往既可以用 w h i l e语句解决,也可以用d o. . .w h i l e或者f o r语句来解决,但在 实际应用中,应根据具体情况来选用不同的循环语句,选用的一般原则是: 1) 如果循环次数在执行循环体之前就已确定,一般用 f o r语句;如果循环次数是由循环体 的执行情况确定的,一般用 w h i l e语句或者d o. . .w h i l e语句。 2) 当循环体至少执行一次时,用 d o. . .w h i l e语句,反之,如果循环体可能一次也不执行, 选用w h i l e语句。 循环的嵌套 一个循环的循环体中有另一个循环叫循环嵌套。这种嵌套过程可以有很多重。一个循环 外面仅包围一层循环叫二重循环;一个循环外面包围两层循环叫三重循环;一个循环外面包 围多层循环叫多重循环。 三种循环语句f o r、w h i l e、d o. . .w h i l e可以互相嵌套自由组合。但要注意的是,各循环必须 完整,相互之间绝不允许交叉。如下面这种形式是不允许的: do { ⋯⋯ for (;;) { ⋯⋯ }while( ); } [例3-15] 打印8行7列的星形矩阵。 流程图见图3 - 1 2,程序如下: 图3-12 例3-15的算法流程图 # include 5 2 C语言程序设计 下载 BEGIN END i=0 k=0 i<8 k<7 k=k+1 换行 i=i+1 内循环 外循环 '*'第3章 程序控制语句 5 3下载 main( ) { int i,j ; f o r ( i = 0 ; i < 8 ,i++) /*控制行* / { for(j=0;j<7>;j++) /*控制列* / p r i n t f ( " * " ) ; p r i n t f ( " \ n " ) ; / *换行*/ } } 打印结果如下: R U N ¿ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 将程序中for(j=0; j<7; j++)改为for(j=0; j main( ) { int r; float area; f o r ( r = 1 ; r < = 1 0 ; r + + ) { a r e a = 3 . 1 4 1 5 9 3 * r * r ; i f ( a r e a > 1 0 0 . 0 ) b r e a k ; p r i n t f ( " s q u a r e = % f \ n " ,a r e a ) ; } printf("now r=%d\n",r ) ; }5 4 C语言程序设计 下载 运行程序: R U N¿ s q u a r e = 3 . 1 4 1 5 9 3 s q u a r e = 1 2 . 5 6 6 3 7 3 s q u a r e = 2 8 . 2 7 4 3 3 8 s q u a r e = 5 0 . 2 6 5 4 8 8 s q u a r e = 7 8 . 5 3 9 8 2 5 now r=6 当b r e a k处于嵌套结构中时,它将只跳出最内层结构,而对外层结构无影响。 2. continue语句 c o n t i n u e语句只能用于循环结构中,一旦执行了 c o n t i n u e语句,程序就跳过循环体中位于 该语句后的所有语句,提前结束本次循环周期并开始新一轮循环。 [例3-17] 计算半径为1到1 5的圆的面积,仅打印出超过 5 0的圆面积。 # include m a i n ( ) { int r; float area; for (r=1;r<=5;r++) { a r e a = 3 . 1 4 1 5 9 3 * r * r ; i f ( a r e a < 5 0 . 0 ) c o n t i n u e ; printf("square =%f",a r e a ) ; } } 结果为: R U N ¿ s q u a r e = 5 0 . 2 6 5 4 8 8 s q u a r e = 7 8 . 5 3 9 8 2 5 同b r e a k一样,c o n t i n u e语句也仅仅影响该语句本身所处的循环层,而对外层循环没有影 响。 3.4.5 程序应用举例 [例3-18] 验证哥德巴赫猜想:任一充分大的偶数,可以用两个素数之和表示,例如: 4 = 2 + 2 6 = 3 + 3 ⋯⋯ 9 8 = 1 9 + 7 9 哥德巴赫猜想是世界著名的数学难题,至今未能在理论上得到证明,自从计算机出现后, 人们就开始用计算机去尝试解各种各样的数学难题,包括费马大定理、四色问题、哥德巴赫 猜想等,虽然计算机无法从理论上严密地证明它们,而只能在很有限的范围内对其进行检验, 但也不失其意义。费马大定理已于 1 9 9 4年得到证明,而哥德巴赫猜想这枚数学王冠上的宝石,至今无人能及。 分析:我们先不考虑怎样判断一个数是否为素数,而从整体上对这个问题进行考虑,可 以这样做:读入一个偶数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是素数, 令f l a g p = 1,若q是素数,令f l a g q = 1,于是第7步变成: 7) } while (flagp*flagq==0); 再来分析第5、第6步,怎样判断一个数是不是素数呢? 素数就是除了1和它自身外,不能被任何数整除的整数,由定义可知: 2、3、5、7、11、1 3、1 7、1 9等是素数; 1、4、6、8、9、1 0、1 2、1 4等不是素数; 要判断i是否是素数,最简单的办法是用 2、3、4、⋯⋯ i-1这些数依次去除i,看能否除尽, 若被其中之一除尽,则i不是素数,反之,i是素数。 但其实,没必要用那么多的数去除,实际上,用反证法很容易证明,如果小于等于 i的平 方根的数都除不尽,则i必是素数。于是,上述算法中的第 5步、第6步可以细化为: 第5)步 p是素数吗? f l a g p = 1 ; for (j=2;j<=[sqrt(p)];j++) if p除以j的余数 = 0 { flagp=0; break; } 第6 )步 q是素数吗? f l a g q = 1 ; for (j=2;j<=[sqrt(q)];j++) if q除以j的余数 = 0 { flagq=0; break; } 程序如下: #include # include m a i n ( ) { 第3章 程序控制语句 5 5下载int j,n,p,q,f l a g p ,f l a g q ; printf("please input n :"); scanf(" %d",& n ) ; if (((n%2)!=0)||(n<=4)) printf("input data error!\n"); e l s e { p = 1 ; do { p = p + 1 ; q = n - p ; f l a g p = 1 ; f o r ( j = 2 ; j < = ( i n t ) ( f l o o r ( s q r t ( ( d o u b l e ) ( p ) ) ) ) ; j + + ) { if ((p%j)==0) { f l a g p = 0 ; b r e a k ; } } f l a g q = 1 ; for (j=2;j<=(int)(floor(sqrt((double)(q))));j++) { if ((q%j)==0) { f l a g q = 0 ; b r e a k ; } } } while (flagp*flagq==0); printf("%d = %d + %d \n",n,p,q ) ; } } 程序运行结果如下: R U N ¿ please input n : 8¿ 8 =3+5 R U N ¿ please input n : 98¿ 98 =19+79 R U N ¿ please input n : 9¿ input data error! 5 6 C语言程序设计 下载下载 第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 函数说明与返回值 当一个函数没有明确说明类型时, C语言的编译程序自动将整型( i n t)作为这个函数的缺 省类型,缺省类型适用于很大一部分函数。当有必要返回其它类型数据时,需要分两步处理 : 首先,必须给函数以明确的类型说明符;其次,函数类型的说明必须处于对它的首次调用之 前。只有这样,C编译程序才能为返回非整型的值的函数生成正确代码。 4.1.1 函数的类型说明 可将函数说明为返回任何一种合法的 C语言数据类型。 类型说明符告诉编译程序它返回什么类型的数据。这个信息对于程序能否正确运行关系 极大,因为不同的数据有不同的长度和内部表示。 返回非整型数据的函数被使用之前,必须把它的类型向程序的其余部分说明。若不这样 做,C语言的编译程序就认为函数是返回整型数据的函数,调用点又在函数类型说明之前,编 译程序就会对调用生成错误代码。为了防止上述问题的出现,必须使用一个特别的说明语句,通知编译程序这个函数返回什么值。下例示出了这种方法。 [例4 - 1 ] float sum( ); / *函数说明 * / main ( ) { float first,s e c o n d ; first =123.23; s e c o n d = 9 9 . 0 9 ; printf ("%f",sum (first,s e c o n d ) ) ; } float sum (a,b ) / *函数定义* / float a,b; { return a+b; } 第一个函数的类型说明sum( )函数返回浮点类型的数据。这个说明使编译程序能够对sum( ) 的调用产生正确代码。 函数类型说明语句的一般形式是 : type_specifier function_name ( ); 即使函数使用形参,也不要将其写入说明句。若未使用类型说明语句,函数返回的数据类 型可能与调用者所要求的不一致,其结果是难以预料的。如果两者同处于一个文件中,编译程 序可以发现该错误并停止编译。如果不在同一个文件中,编译程序无法发现这种错误。类型检 查仅在编译中进行,链接和运行时均不检查。因此,必须十分细心以确保绝不发生上述错误。 当被说明为整型的函数返回字符时,这个字符值被转换为整数。因为 C语言以不加说明的 方式进行字符型与整型之间的数据转换,因而多数情况下,返回字符值的函数并不是说明为 返回字符值,而是由函数的这种字符型向整型的缺省类型转换隐含实现的。 4.1.2 返回语句 返回语句r e t u r n有两个重要用途。第一,它使得内含它的那个函数立即退出,也就是使程 序返回到调用语句处继续进行。第二,它可以用来回送一个数值。本章将说明这两个用途。 1. 从函数返回 函数可以用两种方法停止运行并返回到调用程序。第一种是在执行完函数的最后一个语 句之后,从概念上讲,是遇到了函数的结束符“ }”(当然这个花括号实际上并不会出现在目 标码中,但我们可以这样理解)。例如,下面的函数在屏幕上显示一个字符串。 [例4 - 2 ] pr_reverse () { char s[80]; / *定义一个字符数组* / s c a n f ( " % s " ,s ); / *输入一个字符串,其长度不超过7 9个字符* / p r i n t f ( " % s \ n " ,s ); } 5 8 C语言程序设计 下载一旦字串显示完毕,函数就没事可做了,这时它返回到被调用处。 在实际情况中,没有多少函数是以这种缺省方式终止运行的。因为有时必须送回一个值, 大多数函数用r e t u r n语句终止运行,有时在函数中设立了多个终止点以简化函数、提高效率。切 记,一个函数可以有多个返回语句。如下所示,函数在s 1、s 2相等时返回1,不相等时返回- 1。 [例4 - 3 ] f i n d _ c h a r ( s 1 ,s 2 ) char s1,s 2; { i f ( s 1 = = s 2 ) return 1; e l s e return -1; } 2. 返回值 所有的函数,除了空值类型外,都返回一个数值(切记,空值是 A N S I建议标准所做的扩 展,也许并不适合读者手头的 C编译程序)。该数值由返回语句确定。无返回语句时,返回值 是0。这就意味着,只要函数没有被说明为空值,它就可以用在任何有效的 C语言表达式中作 为操作数。这样下面的表达式都是合法的 C语言表达式。 x = power (y); if (max (x,y) >100) printf("greater"); for (ch=getchar( ); isdigit (ch);) . . . ; 可是,函数不能作为赋值对象,下列语句是错误的: s w a p ( 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 = 1 0 ; y = 2 0 ; 第4章 函 数 5 9下载z = m u l ( x ,y ); /* 1 */ p r i n t f ( " % d " ,m u l ( x ,y ) ) ; /* 2 */ m u l ( x ,y ); /* 3 */ } 在第一行,mul( )的返回值被赋予z,在第二行中,返回值实际上没有赋给任何变量,但 被printf( )函数所使用。最后,在第三行,返回值被丢弃不用,因为既没有把它赋给第一个变 量,也没有把它用作表达式中的一部分。 4.2 函数的作用域规则 “语言的作用域规则”是一组确定一部分代码是否“可见”或可访问另一部分代码和数据 的规则。 C语言中的每一个函数都是一个独立的代码块。一个函数的代码块是隐藏于函数内部的, 不能被任何其它函数中的任何语句(除调用它的语句之外)所访问(例如,用 g o t o语句跳转 到另一个函数内部是不可能的)。构成一个函数体的代码对程序的其它部分来说是隐蔽的,它 既不能影响程序其它部分,也不受其它部分的影响。换言之,由于两个函数有不同的作用域, 定义在一个函数内部的代码数据无法与定义在另一个函数内部的代码和数据相互作用。 C语言中所有的函数都处于同一作用域级别上。这就是说,把一个函数定义于另一个函数 内部是不可能的。 4.2.1 局部变量 在函数内部定义的变量成为局部变量。在某些 C语言教材中,局部变量称为自动变量,这 就与使用可选关键字 a u t o定义局部变量这一作法保持一致。局部变量仅由其被定义的模块内 部的语句所访问。换言之,局部变量在自己的代码模块之外是不可知的。切记:模块以左花 括号开始,以右花括号结束。 对于局部变量,要了解的最重要的东西是:它们仅存在于被定义的当前执行代码块中, 即局部变量在进入模块时生成,在退出模块时消亡。 定义局部变量的最常见的代码块是函数。例如,考虑下面两个函数。 [例4 - 5 ] f u n c 1 ( ) { int x; /* 可定义为 auto int x; */ x = 1 0 ; } f u n c 2 ( ) { int x; /* 可定义为 auto int x; */ x = - 1 9 9 9 ; } 整数变量x被定义了两次,一次在 f u n c 1 ( )中,一次在f u n c 2 ( )中。f u n c 1 ( )和f u n c 2 ( )中的x互 不相关。其原因是每个x作为局部变量仅在被定义的块内可知。 6 0 C语言程序设计 下载语言中包括了关键字a u t o,它可用于定义局部变量。但自从所有的非全局变量的缺省值假 定为a u t o以来,a u t o就几乎很少使用了,因此在本书所有的例子中,均见不到这一关键字。 在每一函数模块内的开始处定义所有需要的变量,是最常见的作法。这样做使得任何人 读此函数时都很容易,了解用到的变量。但并非必须这样做不可,因为局部变量可以在任何 模块中定义。为了解其工作原理,请看下面函数。 [例4 - 6 ] f ( ) { int t; s c a n f ( " % d " , & t ) ; i f ( t = = 1 ) { char s[80]; /*此变量仅在这个块中起作用* / printf("enter name:"); g e t s ( s ) ; /* 输入字符串* / p r o c e s s ( s ) ; /* 函数调用* / } } 这里的局部变量 s就是在 i f块入口处建立,并在其出口处消亡的。因此 s仅在i f块中可知, 而在其它地方均不可访问,甚至在包含它的函数内部的其它部分也不行。 在一个条件块内定义局部变量的主要优点是仅在需要时才为之分配内存。这是因为局部 变量仅在控制转到它们被定义的块内时才进入生存期。虽然大多数情况下这并不十分重要, 但当代码用于专用控制器(如识别数字安全码的车库门控制器)时,这就变得十分重要了, 因为这时随机存储器(R A M)极其短缺。 由于局部变量随着它们被定义的模块的进出口而建立或释放,它们存储的信息在块工作 结束后也就丢失了。切记,这点对有关函数的访问特别重要。当访问一函数时,它的局部变 量被建立,当函数返回时,局部变量被销毁。这就是说,局部变量的值不能在两次调用之间 保持。 4.2.2 全局变量 与局部变量不同,全局变量贯穿整个程序,并且可被任何一个模块使用。它们在整个程 序执行期间保持有效。全局变量定义在所有函数之外,可由函数内的任何表达式访问。在下 面的程序中可以看到,变量 c o u n t定义在所有函数之外,函数 m a i n ( )之前。但其实它可以放置 在任何第一次被使用之前的地方,只要不在函数内就可以。实践表明,定义全局变量的最佳 位置是在程序的顶部。 [例4 - 7 ] int count; /*count 是全局变量 * / m a i n ( ) { count = 100; f u n c 1 ( ) ; } f u n c 1 ( ) 第4章 函 数 6 1下载{ int temp; temp = count; f u n c 2 ( ) ; printf("count is %d",count); /* 打印 100 */ } f u n c 2 ( ) { int count; for(count = 1; count < 10; count++) p u t c h a r ( ' . ' ) ; /* 打印出"。" */ } 仔细研究此程序后,可见变量 c o u n t既不是m a i n ( )也不是f u n c 1 ( )定义的,但两者都可以使 用它。函数f u n c 2 ( )也定义了一个局部变量 c o u n t。当f u n c 2访问c o u n t时,它仅访问自己定义的 局部变量c o u n t,而不是那个全局变量 c o u n t。切记,全局变量和某一函数的局部变量同名时, 该函数对该名的所有访问仅针对局部变量,对全局变量无影响,这是很方便的。然而,如果 忘记了这点,即使程序看起来是正确的,也可能导致运行时的奇异行为。 全局变量由C编译程序在动态区之外的固定存储区域中存储。当程序中多个函数都使用同 一数据时,全局变量将是很有效的。然而,由于三种原因,应避免使用不必要的全局变量: ①不论是否需要,它们在整个程序执行期间均占有存储空间。②由于全局变量必须依靠外部 定义,所以在使用局部变量就可以达到其功能时使用了全局变量,将降低函数的通用性,这 是因为它要依赖其本身之外的东西。③大量使用全局变量时,不可知的和不需要的副作用将 可能导致程序错误。如在编制大型程序时有一个重要的问题:变量值都有可能在程序其它地 点偶然改变。 结构化语言的原则之一是代码和数据的分离。 C语言是通过局部变量和函数的使用来实现 这一分离的。下面用两种方法编制计算两个整数乘积的简单函数 m u l ( )。 通用的 专用的 m u l ( x , y ) int x,y; int x,y; m u l ( ) { { r e t u r n ( x * y ) ; r e t u r n ( x * y ) ; } } 两个函数都是返回变量x和y的积,可通用的或称为参数化版本可用于任意两整数之积, 而专用的版本仅能计算全局变量 x和y的乘积。 4.2.3 动态存储变量 从变量的作用域原则出发,我们可以将变量分为全局变量和局部变量;换一个方式,从 变量的生存期来分,可将变量分为动态存储变量及静态存储变量。 动态存储变量可以是函数的形式参数、局部变量、函数调用时的现场保护和返回地址。 这些动态存储变量在函数调用时分配存储空间,函数结束时释放存储空间。动态存储变量的 定义形式为在变量定义的前面加上关键字“ a u t o”,例如: 6 2 C语言程序设计 下载auto int a, b, c; “a u t o”也可以省略不写。事实上,我们已经使用的变量均为省略了关键字“ a u t o”的动 态存储变量。有时我们甚至为了提高速度,将局部的动态存储变量定义为寄存器型的变量, 定义的形式为在变量的前面加关键字“ r e g i s t e r”,例如: register int x, y, z; 这样一来的好处是:将变量的值无需存入内存,而只需保存在 C P U内的寄存器中,以使 速度大大提高。由于 C P U内的寄存器数量是有限的,不可能为某个变量长期占用。因此,一 些操作系统对寄存器的使用做了数量的限制。或多或少,或根本不提供,用自动变量来替代。 4.2.4 静态存储变量 在编译时分配存储空间的变量称为静态存储变量,其定义形式为在变量定义的前面加上 关键字“s t a t i c”,例如: 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; } 运行程序: R U N ¿ 2 3 4 从上述程序看,函数 f( )被三次调用,由于局部变量 x是静态存储变量,它是在编译时分 配存储空间,故每次调用函数 f( )时,变量x不再重新初始化,保留加 1后的值,得到上面的输 出。 4.3 函数的调用与参数 如果一个函数要使用参数,它就必须定义接受参数值的变量。 第4章 函 数 6 3下载4.3.1 形式参数与实际参数 函数定义时填入的参数我们称之为形式参数,简称形参,它们同函数内部的局部变量作 用相同。形参的定义是在函数名之后和函数开始的花括号之前。 调用时填入的参数,我们称之为实际参数,简称实参。 必须确认所定义的形参与调用函数的实际参数类型一致,同时还要保证在调用时形参与 实参的个数出现的次序也要一一对应。如果不一致,将产生意料不到的结果。与许多其它高 级语言不同,(是健壮的,它总要做一些甚至你不希望的事情,几乎没有运行时错误检查,完 全没有范围检测。作为程序员,必须小心行事以保证不发生错误,安全运行。 4.3.2 赋值调用与引用调用 一般说来,有两种方法可以把参数传递给函数。第一种叫做“赋值调用”(call by value), 这种方法是把参数的值复制到函数的形式参数中。这样,函数中的形式参数的任何变化不会 影响到调用时所使用的变量。 把参数传递给函数的第二种方法是“引用调用”(call by reference)。这种方法是把参数 的地址复制给形式参数,在函数中,这个地址用来访问调用中所使用的实际参数。这意味着, 形式参数的变化会影响调用时所使用的那个变量 (详细内容请参见后续章节)。 除少数情况外,C语言使用赋值调用来传递参数。这意味着,一般不能改变调用时所用变 量的值。请看例4 - 9。 [例4 - 9] main ( ) { int t =10; printf("%d %d ",s q r ( t ) ,t ); /* sqr(t)是函数调用,t是实参* / } int sqr(x) /* 函数定义,x是形式参数* / int x; { x = x * x ; return (x); } 在这个例子里,传递给函数 sqr( )的参数值是复制给形式参数 x的,当赋值语句x = x * x执行 时,仅修改局部变量x。用于调用s q r ( )的变量 t,仍然保持着值1 0。 执行程序: R U N ¿ 100 10 切记,传给函数的只是参数值的复制品。所有发生在函数内部的变化均无法影响调用时 使用的变量。 4.4 递归 C语言函数可以自我调用。如果函数内部一个语句调用了函数自己,则称这个函数是“递 6 4 C语言程序设计 下载归”。递归是以自身定义的过程。也可称为“循环定义”。 递归的例子很多。例如定义整数的递归方法是用数字 1,2,3,4,5,6,7,8,9加上或 减去一个整数。例如,数字 1 5是7 + 8;数字2 1是9 + 1 2; 数字1 2是9 + 3。 一种可递归的计算机语言,它的函数能够自己调用自己。一个简单的例子就是计算整数 阶乘的函数factor( )数N的阶乘是1到N之间所有数字的乘积。例如3的阶乘是1×2×3,即是6。 factor( )和其等效函数fact( )如例4 - 1 0所示。 [例4 - 1 0 ] f a c t o r ( n ) /* 递归调用方法 * / int n; { int answer; if (n==1) return (1); answer=factor(n-1) * n; /* 函数自身调用 * / r e t u r n ( a n s w e r ) ; } [例4 - 11 ] f a c t ( n ) /* 非递归方法 * / int n; { int t,a n s w e r ; a n s w e r = 1 ; for (t=1; t < = n ; t + + ) answer = answer * t; r e t u r n ( a n s w e r ) ; } 非递归函数fact( )的执行应该是易于理解的。它应用一个从 1开始到指定数值结束的循环。 在循环中,用“变化”的乘积依次去乘每个数。 factor( )的递归执行比fact( )稍复杂。当用参数1调用factor( )时,函数返回1;除此之外的 其它值调用将返回 factor(n-1) * n这个乘积。为了求出这个表达式的值,用( n - 1)调用f a c t o r ( )一直到n等于1,调用开始返回。 计算2的阶乘时对factor( )的首次调用引起了以参数1对factor( )的第二次调用。这次调用返 回1,然后被2乘(n的初始值),答案是2(把printf( )语句插入到factor ( )中,察看各级调用及 其中间答案,是很有趣的)。 当函数调用自己时,在栈中为新的局部变量和参数分配内存,函数的代码用这些变量和 参数重新运行。递归调用并不是把函数代码重新复制一遍,仅仅参数是新的。当每次递归调 用返回时,老的局部变量和参数就从栈中消除,从函数内此次函数调用点重新启动运行。可 递归的函数被说成是对自身的“推入和拉出”。 大部分递归例程没有明显地减少代码规模和节省内存空间。另外,大部分例程的递归形 式比非递归形式运行速度要慢一些。这是因为附加的函数调用增加了时间开销(在许多情况 下,速度的差别不太明显)。对函数的多次递归调用可能造成堆栈的溢出。不过溢出的可能性 第4章 函 数 6 5下载不大,因为函数的参数和局部变量是存放在堆栈中的。每次新的调用就会产生一些变量的复 制品。这个堆栈冲掉其它数据和程序的存储区域的可能性是存在的。但是除非递归程序运行 失控,否则不必为上述情况担心。 递归函数的主要优点是可以把算法写的比使用非递归函数时更清晰更简洁,而且某些问 题,特别是与人工智能有关的问题,更适宜用递归方法。递归的另一个优点是,递归函数不 会受到怀疑,较非递归函数而言,某些人更相信递归函数。编写递归函数时,必须在函数的 某些地方使用i f语句,强迫函数在未执行递归调用前返回。如果不这样做,在调用函数后,它 永远不会返回。在递归函数中不使用 i f语句,是一个很常见的错误。在开发过程中广泛使用 printf( )和getchar( )可以看到执行过程,并且可以在发现错误后停止运行。 4.5 实现问题 在编写 C语言的函数时,有几个要点需要我们牢记,因为它们影响到函数的效率和可用 性。 4.5.1 参数和通用函数 通用函数是指能够被用在各种情况下,或者是可被许多不同程序员使用的函数。我们不 应该把通用函数建立在全局变量上(不应该在通用函数中使用全局变量)。函数所需要的所有 数据都应该用参数传递(在个别难以这样做的情况下,可以使用静态变量)。使用参数传递, 除了有助于函数能用在多种情况下之外,还能提高函数代码的可读性。不用全局变量,可以 使得函数减少因副作用而导致错误的可能性。 4.5.2 效率 函数是C语言的基本构件。对于编写简单程序之外的所有程序来说,函数是必不可少的。 但在一些特定的应用中,应当消除函数,而采用内嵌代码。内嵌代码是指一个函数的语句中 不含函数调用语句。仅当执行速度是很关键的场合下,才用内嵌代码而不用函数。 有两个原因使得内嵌代码的执行速度比函数快。首先,调用需要花费时间;其次,如果 有参数需要传递,就要把它们放在堆栈中,这也要用时间。在几乎所有的应用中,执行时间 上的这些微小开销是微不足道的。不过当时间开销至关重要时,使用内嵌代码消除函数调用, 可以把每次函数调用的开销节省下来。下面的两个程序都是打印从 1到1 0的数的平方。由于函 数调用需要花费时间,所以内嵌代码版本运行的比另一个要快。 内嵌 函数调用 main ( ) main ( ) { { int x; int x; for (x=1,x < 1 1 ;+ + x ) for (xx=1;x < 1 1 ;+ + x ) printf ("%d",x * x ) ; printf ("%d",s q r ( x ) ) ; } } s q r ( a ) ; int a; { return a*a; } 6 6 C语言程序设计 下载4.6 函数库和文件 一个函数设计完后,我们可以用三种方法处理它: 1) 把它放在main( )函数的同一个文件 中;2) 把它和写好的其它函数一起放在另一个文件中; 3) 把它放在函数库中。下面分别讨论 这三种方法。 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程序设计的环境。本节将介绍如何应用预处理程序和注释简化 第4章 函 数 6 7下载程序开发过程,并提高程序的可读性。 4.7.1 C语言的预处理程序 ANSI 标准定义的C语言预处理程序包括下列命令: # d e f i n e # e r r o r # i n c l u d e # i f # e l s e # e l i f # e n d i f # i f d e f # i f n d e f # u n d e f # l i n e # p r a g m a 非常明显,所有预处理命令均以符号#开头,下面分别加以介绍。 4.7.2 #define 命令#d e f i n e定义了一个标识符及一个串。在源程序中每次遇到该标识符时,均以定义的 串代换它。A N S I标准将标识符定义为宏名,将替换过程称为宏替换。命令的一般形式为: #define identifier string 注意,该语句没有分号。在标识符和串之间可以有任意个空格,串一旦开始,仅由一新 行结束。 例如,如希望T U R E取值1,FALSE 取值0,可说明两个宏# d e f i n e #define TURE 1 #define FALSE 0 这使得在源程序中每次遇到 T U R E或FA L S E就用0或1代替。 例如,在屏幕上打印“0 1 2”: printf("%d %d %d",F A L S E ,T R U E ,T R U E + 1 ) ; 宏名定义后,即可成为其它宏名定义中的一部分。例如,下面代码定义了 O N E、T W O及 T H R E E的值。 # d e f i n e O N E 1 # d e f i n e T W O O N E + O N E # d e f i n e T H R E E O N E + T W O 懂得宏替换仅仅是以串代替标识符这点很重要。因此,如果希望定义一个标准错误信息, 可编写如下代码: #define E_MS "standard error on input\n" p r i n t f ( E _ M S ) ; 编译程序遇到标识符 E _ M S时,就用“standard error on input\n”替换。对于编译程序, p r i n t f ( )语句实际是如下形式: 6 8 C语言程序设计 下载printf("standard error on input\n"); 如果在串中含有标识符,则不进行替换。例如: #define XYZ this is a test . . . p r i n t f ( " X Y Z " ) ; 该段不打印"this is a test"而打印" X Y Z "。 如果串长于一行,可以在该行末尾用一反斜杠续行,例如: #define LONG_STRING "this is a very long \ string that is used as an example" 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 - 1 2 ] #define MIN(a,b) (a 这两行代码均使用C编译程序读入并编译用于处理磁盘文件库的子程序。 将文件嵌入# i n c l u d e命令中的文件内是可行的,这种方式称为嵌套的嵌入文件,嵌套层次 依赖于具体实现。 如果显式路径名为文件标识符的一部分,则仅在哪些子目录中搜索被嵌入文件。否则, 如果文件名用双引号括起来,则首先检索当前工作目录。如果未发现文件,则在命令行中说 明的所有目录中搜索。如果仍未发现文件,则搜索实现时定义的标准目录。 如果没有显式路径名且文件名被尖括号括起来,则首先在编译命令行中的目录内检索。 如果文件没找到,则检索标准目录,不检索当前工作目录。 4.7.5 条件编译命令 有几个命令可对程序源代码的各部分有选择地进行编译,该过程称为条件编译。商业软 件公司广泛应用条件编译来提供和维护某一程序的许多顾客版本。 1. #if、# e l s e,# e l i f及# e n d i f #if 的一般含义是如果 #if 后面的常量表达式为 t r u e,则编译它与# e n d i f之间的代码,否则 跳过这些代码。命令#endif 标识一个#if 块的结束,参见例4 - 1 3。 #if constant-expression statement sequence # e n d i f [例4 - 1 3 ] # define MAX 100 main( ) { # if MAX>99 printf("compiled for array greater than 99\n"); # endif } 由于M A X大于9 9,以上程序在屏幕上显示一串消息。该例说明了一个重点:跟在 # if 后 面的表达式在编译时求值,因此它必须仅含常量及已定义过的标识符,不可使用变量。表达 式不许含有操作符s i z e o f。 # else 命令的功能有点象C语言中的e l s e; # e l s e建立另一选择(在# if失败的情况下)。因 而上面的例子可扩充,参见例 4 - 1 4。 [例4 - 1 4 ] # define MAX 10 main ( ) { # if MAX>99 printf("compiled for array greater than 99\n"); # e l s e 7 0 C语言程序设计 下载printf("compiled for small array \ n"); # e n d i f } 在此例中,因为 M A X小于9 9,所以,不编译 # i f块,而是编译 # else块,因此,屏幕上显 示"compiled for small array"这一消息。 注意,# else 既是# if 块又是#else 块头。这是因为任何#if 仅有一个# e n d i f。 # e l i f命令意义与ELSE IF 相同,它形成一个 if else-if阶梯状语句,可进行多种编译选择。 #elif 后跟一个常量表达式。如果表达式为 t r u e,则编译其后的代码块,不对其它 # e l i f表达式进 行测试。否则,顺序测试下一块。 #if expression statement sequence #elif expression1 statement sequence #elif expression2 statement sequence #elif expression3 statement sequence #elif expression4 #elif expression3N statement sequence # e n d i f 例如:下面程序利用A C T I V E _ C O U N T RY定义货币符号。 #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 "; # e l s e char currency[ ]="franc"; # e n d i f # i f与# e l i f命令可能一直嵌套到实现规定的权限,其中 # e n d i f、# e l s e或# e l i f与最近# i f或# e l i f 关联。例如,下面程序是完全有效的。 #if MAX>100 #if SERIAL_VERSION int port=198; # e l i f int port=200; # e l i f # e l s e char out_buffer[100]; # e n d i f 第4章 函 数 7 1下载2. # ifdef 和# ifndef 条件编译的另一种方法是用 # i f d e f与# i f n d e f命令,它们分别表示“如果有定义”及“如果 无定义”。 # ifdef的一般形式是: # ifdef macroname statement sequence # e n d i f 如果宏名在前面# d e f i n e语句中已定义过,则该语句后的代码块被编译。 # i f n d e f的一般形式是: #ifndef macroname statement sequence # e n d i f 如果宏名在#define 语句中无定义,则编译该代码块。 #ifdel 与# i f n d e f可以用于#else 语句中,但#elif 不行。参见4 - 1 5。 [例4 - 1 5 ] #define TED 10 main () { #ifdef TED printf("Hi Ted\n"); # e l s e printf("Hi anyone\n"); # e n d i f #ifndef RALPH printf ("RALPH not defined\n"); # e n d i f } 上述代码打印“ Hi Ted ”及“RALPH not defined”。如果T E D没有定义,则显示“ H i a n y o n e”,后面是“RALPH not defined”。 可以像嵌套#if 那样将#ifdef 与#ifndef 嵌套至任意深度。 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 语句之前,L E N与W I D T H均有定义。 # undef 的主要目的是将宏名局限在仅需要它们的代码段中。 7 2 C语言程序设计 下载4.7.7 #line 命令# l i n e改变_LINE_ 与_ F I L E _的内容,它们是在编译程序中预先定义的标识符。 命令的基本形式如下: # line number["filename"] 其中的数字为任何正整数,可选的文件名为任意有效文件标识符。行号为源程序中当前 行号,文件名为源文件的名字。命令 # l i n e主要用于调试及其它特殊应用。 例如,下面说明行计数从1 0 0开始;printf( ) 语句显示数1 0 2,因为它是语句#line 100后的 第3行。 #line 100 /* 初始化行计数器 * / main ( ) /* 行号 100 */ { /* 行号101 */ p r i n t f ( " % d \ n " ,_ L I N E _ ) ; /* 行号 102 */ } 4.7.8 #pragma 命令#pragma 为实现时定义的命令,它允许向编译程序传送各种指令。例如,编译程序 可能有一种选择,它支持对程序执行的跟踪。可用 # p r a g m a语句指定一个跟踪选择。 4.7.9 预定义的宏名 A N S I标准说明了五个预定义的宏名。它们是: _ L I N E _ _ F I L E _ _ D A T E _ _ T I M E _ _ S T D C _ 如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序 也许还提供其它预定义的宏名。 _ L I N E _及_ F I L E _宏指令在有关# l i n e的部分中已讨论,这里讨论其余的宏名。 _ D AT E _宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。 源代码翻译到目标代码的时间作为串包含在 _ T I M E _中。串形式为时:分:秒。 如果实现是标准的,则宏 _ S T D C _含有十进制常量 1。如果它含有任何其它数,则实现是 非标准的。 注意:宏名的书写由标识符与两边各二条下划线构成。 4.7.10 注释 在C语言中,所有的注释由字符 / *开始,以* /结束。在星号及斜杠之间不允许有空格。编 译程序忽略注释开始符到注释结束符间的任何文本。例如,下面程序在屏幕上只打印 “h e l l o”。 main () { p r i n t f ( " h e l l o " ) ; 第4章 函 数 7 3下载/*printf ("This is a sample to print hello");* / } 注释可出现在程序的任何位置,但它不能出现在关键字或标识符中间。 即,注释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 #include /*包含字符串库函数说明的头文件 * / # include void forward_and_backwards(char line_of_char[],int index); /* 函数声明* / void main() { char line_of_char[80]; / *定义字符数组* / int index = 0; s t r c p y ( l i n e _ o f _ c h a r ,"This is a string."); / *字符串拷贝* / f o r w a r d _ a n d _ b a c k w a r d s ( l i n e _ o f _ c h a r ,i n d e x ) ; / *函数调用* / } void forward_and_backwards(char line_of_char[],int index) /*函数定义* / { if (line_of_char[index]) { p r i n t f ( " % c " ,l i n e _ o f _ c h a r [ i n d e x ] ) ; / *输出字符* / f o r w a r d _ a n d _ b a c k w a r d s ( l i n e _ o f _ c h a r ,i n d e x + 1 ) ; / *递归调用* / p r i n t f ( " % c " ,l i n e _ o f _ c h a r [ i n d e x ] ) ; / *输出字符* / } } 这是一个递归函数调用的例子。程序中函数 f o r w a r d _ a n d _ b a c k w a r d s ( )的功能是显示一个 字符串后反向显示该字符串。 [例4-17] 计算1~7的平方及平方和。 #include 7 4 C语言程序设计 下载# include void header(); / *函数声明* / void square(int number); void ending(); int sum; /* 全局变量 * / m a i n ( ) { int index; h e a d e r ( ) ; / *函数调用* / for (index = 1;index <= 7;i n d e x + + ) s q u a r e ( i n d e x ) ; e n d i n g ( ) ; / *结束* / } void header() { sum = 0; /* 初始化变量"sum" */ printf("This is the header for the square program\n\n"); } void square(int number) { int numsq; numsq = number * number; sum += numsq; printf("The square of %d is %d\n",n u m b e r ,n u m s q ) ; } void ending() { printf("\nThe sum of the squares is %d\n",s u m ) ; } 运行程序: R U N ¿ 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 这个程序打印出1到7的平方值,最后打印出1到7的平方值的和,其中全局变量 s u m在多个 函数中出现过。 全局变量在h e a d e r中被初始化为零;在函数 s q u a r e中,s u m对n u m b e r的平方值进行累加, 第4章 函 数 7 5下载也就是说,每调用一次函数 s q u a r e和s u m就对n u m b e r的平方值累加一次;全局变量 s u m在函数 e n d i n g中被打印。 [例4-18] 全局变量与局部变量的作用。 #include void head1(void); void head2(void); void head3(void); int count; /* 全局变量 * / m a i n ( ) { register int index; / *定义为主函数寄存器变量 * / h e a d 1 ( ) ; h e a d 2 ( ) ; h e a d 3 ( ) ; for (index = 8;index > 0;index--) /* 主函数 "for" 循环 * / { int stuff; /* 局部变量 * / /* 这种变量的定义方法在 Turbo C 中是不允许的 * / /* stuff 的可见范围只在当前循环体内 * / for(stuff = 0;stuff <= 6;s t u f f + + ) printf("%d ",s t u f f ) ; printf(" index is now %d\n",i n d e x ) ; } } int counter; /* 全局变量 * / /* 可见范围为从定义之处到源程序结尾* / void head1(void) { int index; / *此变量只用于 head1 */ index = 23; printf("The header1 value is %d\n",i n d e x ) ; } void head2(void) { int count; /* 此变量是函数h e a d 2 ( ) 的局部变量 * / /* 此变量名与全局变量c o u n t 重名 * / /* 故全局变量c o u n t 不能在函数h e a d 2 ( ) 中使用* / count = 53; printf("The header2 value is %d\n",c o u n t ) ; counter = 77; } 7 6 C语言程序设计 下载void head3(void) { printf("The header3 value is %d\n",c o u n t e r ) ; } 运行程序: R U N ¿ 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 该程序的演示帮助读者来了解全局变量、局部变量的作用域,请仔细理解体会。 第4章 函 数 7 7下载下载 第5章 数 组 数组是一个由若干同类型变量组成的集合,引用这些变量时可用同一名字。数组均由连 续的存储单元组成,最低地址对应于数组的第一个元素,最高地址对应于最后一个元素,数 组可以是一维的,也可以是多维的。 5.1 一维数组 一维数组的一般说明形式如下 : type-specifier var_name [size]; 在C语言中,数组必须显示地说明,以便编译程序为它们分配内存空间。在上式中,类型 说明符指明数组的类型,也就是数组中每一个元素个数,一维数组的总字节数可按下式计算 : s i z e o f ( 类型) *数组长度 = 总字节数 [例5-1] 将数字0到9装入一个整型数组。 main( ) { int x[10]; /* 定义包含1 0个整型数的数组,引用为x [ 0 ] ,x [ 1 ] . . . x [ 9 ] * / int t ; for (t=0; t<10;++t) x[t]=t; } C语言并不检验数组边界,因此,数组的两端都有可能越界而使其它变量的数组甚至程序 代码被破坏。在需要的时候,数组的边界检验便是程序员的职责。例如,当使用 gets( )接收字 符输入时,必须确认字符数组的长度足以存放最长的字符串。 一维数组在本质上是由同类数据构成的表,例如,对下列数组 a : char a[7] 图5 - 1说明了数组a在内存中的情形,假定起始地址为 1 0 0 0。 图5-1 起始地址为1000的7元素字符数组 5.1.1 向函数传递一维数组 将一维数组传递给函数时,把数组名作为参数直接调用函数即可,无需任何下标。这样, 数组的第一个元素的地址将传递给该函数。 C语言并不是将整个数组作为实参来传递,而是用 指针来代替它。例如,下面的程序将数组 i的第一个元素的地址传递给函数 func1( )。 main( ) 元素 0 1 2 3 4 5 6 地址 1 0 0 0 1 0 0 1 1 0 0 2 1 0 0 3 1 0 0 4 1 0 0 5 1 0 0 6{ int i[10]; func1(i); /*函数调用,实参是数组名* / . . . } 函数若要接收一维数组的传递,则可以用下面的二种方法之一来说明形式参数; 1) 有界 数组;2) 无界数组。例如,函数func1 ( )要接收数组i可如下说明: f u n c 1 ( s t r ) char str[10]; /* 有界数组,数组的下标只能小于或等于传递数组的大小。 * / { . . . } 也可说明为: f u n c 1 ( s t r ) char str[ ]; / * 无界数组 * / { . . . } 这二种说明方法的效果是等价的,它们都通知编译程序建立一个字符指针。第一种说明 使用的是标准的数组说明;后一种说明使用了改进型的数组说明,它只是说明函数将要接收 一个具有一定长度的整型数组。细想就会发现,就函数而言,数组究竟有多长并无关紧要, 因为C语言并不进行数组的边界检验。事实上,就编译程序而言,下面的说明也是可行的。 func1 (str); int str[32]; { . . . } 因为编译程序只是产生代码使函数 func1( )接收一个指针,并非真正产生一个包含 3 2个元 素的数组。 5.1.2 字符串使用的一维数组 显然,一维数组的最普通的用法是作为字符串。在 C语言中,字符串被定义为一个以空字 符终结的字符数组。空字符以‘ \ 0’来标识,它通常是不显示的。因此,在说明字符数组时, 必须比它要存放的最长字符串多一个字符。例如,假如要定义一个存放长度为 1 0的字符串的 数组s,可以写成: char s[11]; 第5章 数 组 7 9下载这样就给字符串末尾的空字符保留了空间。 尽管C语言并不把字符串定义为一种数据类型,但却允许使用字符串常量。字符串常量是 由双引号括起来的字符表。例如,下面两个短语均为字符串常量 : "hello there" "this is a test" 不必向字符串的末尾加空字符, C编译程序会自动完成这一工作。 C语言支持多串操作函数,最常用的有 : 名字 功能 strcpy(s1 s2) 将s 2拷贝到s 1 strcat(s1 s2) 将s 2连接到s 1的末尾 s t r l e n ( s 1 ) 返回s 1的长度 s t r c m p ( s 1,s 2 ) 若s 1与s 2相等,返回值为0 若s 1 < s 2,返回值小于0 若s 1 > s 2,返回值大于0 例5 - 2说明了这些函数的用法。 [例5 - 2 ] # include main ( ) { char s1[80],s2[80]; /*定义字符数组* / gets (s1); /*输入字符串* / gets (s2); printf ("lengthsf: %d %d \n",s t r l e n ( s 1 ) ,s t r l e n ( s 2 ) ) ; if (!strcmp(s1,s2)) printf ("the strings are equal \n"); s t r c a t ( s 1 ,s 2 ) ; printf ("%s\n",s 1 ) ; } 切记,当两个串相等时,函数 strcmp( )将返回Fa l s e,因而当测试串的等价性时,要像前 例中的那样,必须用逻辑运算符!将测试条件取反。 当程序运行并以“h e l l o”和“h e l l o”这两个串作为输入时,其输出为 : R U N ¿ h e l l o ¿ h e l l o ¿ lengths:5 5 The strings are equal h e l l o h e l l o 5.2 二维数组 5.2.1 二维数组的一般形式 C语言允许使用多维数组,最简单的多维数组是二维数组。实际上,二维数组是以一维数 8 0 C语言程序设计 下载组为元素构成的数组,要将 d说明成大小为(1 0,2 0)的二维整型数组,可以写成 : int d[10][20] 请留心上面的说明语句, C不像其它大多数计算机语言那样使用逗号区分下标,而是用方 括号将各维下标括起,并且,数组的二维下标均从 0计算。 与此相似,要存取数组d中下标为(3,5)的元素可以写成: d [ 3 ] [ 5 ] 在例5 - 3中,整数1到1 2被装入一个二维数组。 [例5 - 3 ] main ( ) { int t,i,n u m [ 3 ] [ 4 ] for (t=0; t<3; ++t) for (i=0;i<4;++i) n u m [ t ] [ i ] = ( t * 4 ) + i + 1 ; } 在此例中,n u m [ 0 ] [ 0 ]的值为1,n u m [ 0 ] [ 2 ]的值为3,. . . . . .,n u m [ 2 ] [ 3 ]的值为1 2。可以将 该数组想象为如下表格: 0 1 2 3 0 1 2 3 4 1 5 6 7 8 2 9 1 0 11 1 2 二维数组以行—列矩阵的形式存储。第一个下标代表行,第二个下标代表列,这意味着 按照在内存中的实际存储顺序访问数组元素时,右边的下标比左边的下标的变化快一些。图 5 - 2是一个二维数组在内存中的情形,实际上,第一下标可以认为是行的指针。 记住,一旦数组被证明,所有的数组元素都将分配相应的存储空间。对于二维数组可用 下列公式计算所需的内存字节数: 行数×列数×类型字节数=总字节数 因而,假定为双字节整型,大小为( 1 0,5)的整型数组将需要:1 0×5×2=100 字节 当二维数组用作函数的参数时,实际上传递的是第一个元素(如 [ 0 ] [ 0 ])的指针。不过该 函数至少得定义第二维的长度,这是因为 C编译程序若要使得对数组的检索正确无误,就需要 知道每一行的长度。例如,将要接收大小为( 1 0,1 0)的二维数组的函数,可以说明如下: func1 (x) int x[ ][10] { . . . } 第5章 数 组 8 1下载第2下标 0,0 0,1 0,2 0,3 0,4 0,5 0,6 0,7 1,0 1,1 1,2 1,3 1,4 1,5 1,6 1,7 2,0 2,1 2,2 2,3 2,4 2,5 2,6 2,7 3,0 3,1 3,2 3,3 3,4 3,5 3,6 3,7 4,0 4,1 4,2 4,3 4,4 4,5 4,6 4,7 5,0 5,1 5,2 5,3 5,4 5,5 5,6 5,7 6,0 6,1 6,2 6,3 6,4 6,5 6,6 6,7 图5-2 内存中的二维数组 第一维的长度也可指明,但没有必要。 C编译程序对函数中的如下语句: X [ 2 ] [ 4 ] 处理时,需要知道二维的长度。若行长度没定义,那么它就不可能知道第三行从哪儿开 始。 [例5-4] 用一个二维数组存放某一教师任教的各班学生的分数。假定教师有三个班,每班 最多有三十名学生。注意各函数存取数组的方法。 #define classes 3 #define grades 30 #include 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"); p r i n t f ( " ( Q ) u i t \ n " ) ; ch=toupper(getchar()); /*将键盘输入字符转换为大写* / } while(ch!='E' && ch!='R' && ch!='Q'); switch(ch) { case 'E': enter_grades( ); b r e a k ; case 'R': 8 2 C语言程序设计 下载d i s p _ g r a d e s ( g r a d e ) ; b r e a k ; case 'Q': e x i t ( 0 ) ; } } } void enter_grades(a) int a[][grades]; { int t, i; for (t=0;t m a i n ( ) { int score[10]; / * 1 0 个评委的成绩* / float mark; /*最后得分* / int i; int max = -1; / *最高分* / int min = 101; /*最低分* / int sum = 0; /*10个评委的总和* / f o r ( i = 0 ; i < 1 0 ; i + + ) { printf("Please Enter the Score of No. %d",i + 1 ) ; s c a n f ( " % d \ n " ,& s c o r e [ i ] ) ; 第5章 数 组 8 7下载s u m = s u m + s c o r e [ i ] ; } f o r ( i = 0 ; i < 1 0 ; i + + ) { i f ( s c o r e [ i ] > m a x ) m a x = s c o r e [ i ] ; } f o r ( i = 0 ; i < 1 0 ; i + + ) { i f ( s c o r e [ i ] < m i n ) m i n = s c o r e [ i ] ; } m a r k = ( s u m - m i n - m a x ) / 8 . 0 ; printf("The mark of the player is %.1f\n",m a r k ) ; } [例5-7] 数列排序,采用选择法实现对有 5个数的数列进行排序。 选择法的算法思想是:(降序) 1. 将待排序的n个数放入数组n u m中,即n u m [ 0 ]、n u m [ 1 ]、. . . n u m [ n - 1 ]。 2. 让n u m [ 0 ]与后续n u m [ 1 ] . . . n u m [ n - 1 ]依次比较,保证大数在前、小数在后。此次比较, n u m [ 0 ]是数组中最大。 3. 余下n - 1个元素 4. num[1]与n u m [ 2 ] . . . n u m [ n - 1 ]依次比较,大数在前、小数在后,此次 n u m [ 1 ]是全部元素的 最大。 n u m [ n - 2 ]与n u m [ n - 1 ]比较,n u m [ n - 2 ]存大数。 n u m [ n - 1 ]存小数,比较结束,整理有序。 例:待排序5个数为: 44 76 82 63 71 一趟排序: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 m a i n ( ) { int num[5]; int i,j ; int temp; num[0]=94; num[1]=76; num[2]=82; num[3]=63; num[4]=71; for(i=0; i<4; i++) for(j=i+1; j<5; j++) { 8 8 C语言程序设计 下载i f ( n u m [ i ] > n u m [ j ] ) { t e m p = n u m [ i ] ; n u m [ i ] = n u m [ j ] ; n u m [ j ] = t e m p ; } } for(i=0; i<5; i++) p r i n t f ( " % 4 d " ,n u m [ i ] ) ; p r i n t f ( " o k \ n " ) ; } 这是一个非常简单的排序程序,我们只需稍加扩展就可以编制出很多功能强大的管理程 序,如学生统计总分、平均排列年级名次等。 [例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 m i a n ( ) { int select; int i,j ; int score[5][7]; int average=0; int sum=0; d o { p r i n t f ( " 本程序有4项功能\ n " ) ; p r i n t f ( " 1 、根据学号查询学生成绩\ n " ) ; p r i n t f ( " 2 、根据考试号统计成绩\ n " ) ; p r i n t f ( " 3 、根据考试号和学号查询成绩\ n " ) ; p r i n t f ( " 4 、成绩录入\ n " ) ; p r i n t f ( " 0 、退出\ n " ) ; p r i n t f ( " 请输入选择(0 - 4 ): " ) ; s c a n f ( " % d \ n " ,& s e l e c t ) ; 第5章 数 组 8 9下载 考试 成绩学号s w i t c h ( s e l e c t ) { case 0: p r i n t f ( " O K \ n " ) ; e x i t ( 0 ) b r e a k ; case 1: p r i n t f ( " 输入学号:" ) ; s c a n f ( " % d \ n " ,& i ) ; for(j=1; j<7; j++) { p r i n t f ( " 第% d科成绩是% d \ n " ,j,s c o r e [ i ] [ j ] ) ; sum += score[i][j]; } a v e r a g e = s u m / 6 ; p r i n t f ( " 学生的平均成绩是% d \ n " ,a v e r a g e ) ; b r e a k ; case 2: p r i n t f ( " 输入考试号:" ) ; s c a n f ( " % d \ n " ,& j ) ; for(i=1; i<5; i++) { p r i n t f ( " 第% d号学生本科成绩是% d \ n " ,i,s c o r e [ i ] [ j ] ) ; sum += score[i][j]; } a v e r a g e = s u m / 4 ; p r i n t f ( " 本科平均成绩是% d \ n " ,a v e r a g e ) ; b r e a k ; case 3: p r i n t f ( " 输入学号和考试号 :" ) ; scanf("%d %d\n",& i,& j ) ; p r i n t f ( " 第 % d 号学生的第 % d 科考试成绩是 % d \ n " ,i, j, s c o r e [ i ] [ j ] ) ; b r e a k ; case 4: p r i n t f ( " 请输入成绩\ n " ) ; for(i=1; i<5; i++) for(j=1; j<7; j++) s c a n f ( " % d \ n " ,& s c o r e [ i ] [ j ] ) ; b r e a k ; d e f a u l t : b r e a k ; } w h i l e ( 1 ) ; } 从本例中可以看出,当涉及到二维数组时,通常用两重 f o r循环来存取元素。 9 0 C语言程序设计 下载下载 第6章 指 针 指针是C语言的精华部分,通过利用指针,我们能很好地利用内存资源,使其发挥最大的 效率。有了指针技术,我们可以描述复杂的数据结构,对字符串的处理可以更灵活,对数组 的处理更方便,使程序的书写简洁,高效,清爽。但由于指针对初学者来说,难于理解和掌 握,需要一定的计算机硬件的知识做基础,这就需要多做多练,多上机动手,才能在实践中 尽快掌握,成为C的高手。 6.1 指针与指针变量 过去,我们在编程中定义或说明变量,编译系 统就为已定义的变量分配相应的内存单元,也就是 说,每个变量在内存会有固定的位置,有具体的地 址。由于变量的数据类型不同,它所占的内存单元 数也不相同。若我们在程序中做定义为: int a=1, b=2; float x=3.4, y = 4 . 5 ; double m=3.124; char ch1='a', ch2='b'; 让我们先看一下编译系统是怎样为变量分配内 存的。变量a , b是整型变量,在内存各占 2个字节; x , y是实型,各占4个字节;m是双精度实型,占8个 字节;c h 1 , c h 2是字符型,各占 1个字节。由于计算 机内存是按字节编址的,设变量的存放从内存 2 0 0 0 单元开始存放,则编译系统对变量在内存的安放情 况为图6 - 1所示。 变量在内存中按照数据类型的不同,占内存的 大小也不同,都有具体的内存单元地址,如变量 a 在内存的地址是 2 0 0 0,占据两个字节后, 变量 b的内存地址就为 2 0 0 2,变量 m的内存地址为 2 0 1 2等。对内存中变量的访问,过去用 scanf("%d%d%f",&a,&b,&x) 表示将数据输入变量的地址所指示的内存单元。那么,访问变量, 首先应找到其在内存的地址,或者说,一个地址唯一指向一个内存变量,我们称这个地址为 变量的指针。如果将变量的地址保存在内存的特定区域,用变量来存放这些地址,这样的变 量就是指针变量,通过指针对所指向变量的访问,也就是一种对变量的“间接访问”。 设一组指针变量 p a、p b、p x、p y、p m、p c h 1、p c h 2,分别指向上述的变量 a、b、x、y、 m、c h 1、c h 2,指针变量也同样被存放在内存,二者的关系如图 6 - 2所示: 在图6 - 2中,左部所示的内存存放了指针变量的值,该值给出的是所指变量的地址,通过 该地址,就可以对右部描述的变量进行访问。如指针变量 p a的值为2 0 0 0,是变量a在内存的地 变量a 变量b 变量x 变量y 变量m 12000 2002 2004 2008 2012 2020 2021 2 3.4 4.5 3.124 a b 变量ch1 变量ch2 图6-1 不同数据类型的变量在内存中 占用的空间址。因此,p a就指向变量a。变量的地址就是指针,存放指针的变量就是指针变量。 图6-2 指针变量与变量在内存中的关系 6.2 指针变量的定义与引用 6.2.1 指针变量的定义 在C程序中,存放地址的指针变量需专门定义; int *ptr1; float *ptr2; char *ptr3; 表示定义了三个指针变量 p t r 1、p t r 2、p t r 3。p t r 1可以指向一个整型变量, p t r 2可以指向一 个实型变量,p t r 3可以指向一个字符型变量,换句话说, p t r 1、p t r 2、p t r 3可以分别存放整型变 量的地址、实型变量的地址、字符型变量的地址。 定义了指针变量,我们才可以写入指向某种数据类型的变量的地址,或者说是为指针变 量赋初值: int *ptr1,m= 3; float *ptr2, f=4.5; char *ptr3, ch='a'; p t r 1 = & m ; p t r 2 = & f ; p t r 3 = & c h ; 上述赋值语句 p t r 1 = & m表示将变量m的地址赋给指针变量p t r 1,此时 p t r 1就指向m。三条 赋值语句产生的效果是p t r 1指向m;p t r 2指向f;p t r 3指向ch 。用示意图6 - 3描述如下: 9 2 C语言程序设计 下载 pa pb px py pm pch1 pch2 2000 2002 2004 2008 2012 2020 2021 2000 2002 2004 2008 2012 2020 2021 1 2 3.4 4.5 3.124 a b 1000 1002 1004 1006 1008 1010 1012 1014 1016 变量a 变量b 变量x 变量y 变量m 变量ch1 变量ch2第6章 指 针 9 3下载 图6-3 赋值语句的效果 需要说明的是,指针变量可以指向任何类型的变量,当定义指针变量时,指针变量的值 是随机的,不能确定它具体的指向,必须为其赋值,才有意义。 6.2.2 指针变量的引用 利用指针变量,是提供对变量的一种间接访问形式。对指针变量的引用形式为: *指针变量 其含义是指针变量所指向的值。 [例6-1] 用指针变量进行输入、输出。 m a i n ( ) { int *p,m; s c a n f ( " % d " , & m ) ; p = & m ; / *指针p指向变量m * / printf("%d",*p); /* p是对指针所指的变量的引用形式,与此m意义相同* / } 运行程序: R U N ¿ 3¿ 3 上述程序可修改为: m a i n ( ) { int *p,m; p = & m ; s c a n f ( " % d " , p ) ; /* p是变量m的地址,可以替换& m * / printf("%d", m); } 运行效果完全相同。请思考一下若将程序修改为如下形式: m a i n ( ) { int *p,m; s c a n f ( " % d " , p ) ; p = & m ; printf("%d", m); } 会产生什么样的结果呢?事实上,若定义了变量以及指向该变量的指针为: int a,*p; &m &f 4.53 ptr1 ptr2 f ptr3 chm &ch a9 4 C语言程序设计 下载 若p=&a; 则称p指向变量a,或者说p具有了变量a的地址。在以后的程序处理中,凡是可 以写& a的地方,就可以替换成指针的表示 p,a就可以替换成为* p。 6.3 指针运算符与指针表达式 6.3.1 指针运算符与指针表达式 在C中有两个关于指针的运算符: • &运算符: 取地址运算符,& m即是变量m的地址。 • *运算符:指针运算符,* p t r表示其所指向的变量。 [例6-2] 从键盘输入两个整数,按由大到小的顺序输出。 m a i n ( ) { int *p1,*p2,a,b,t; / *定义指针变量与整型变量* / s c a n f ( " % d , % d " , & a , & b ) ; p 1 = & a ; / *使指针变量指向整型变量* / p 2 = & b ; i f ( * p 1 < * p 2 ) { / *交换指针变量指向的整型变量* / t = * p 1 ; * p 1 = * p 2 ; * p 2 = t ; } p r i n t f ( " % d , % d \ n " , a , b ) ; } 在程序中,当执行赋值操作 p 1 = & a和 p 2 = & b后,指针实实在在地指向了变量 a与b,这时 引用指针* p 1与* p 2,就代表了变量a与b。 运行程序: R U N ¿ 3 , 4 ¿ 4 , 3 在程序运行过程中,指针与所指的变量之间的关系如图 6 - 4所示: 图6-4 程序运行中指针与变量之间的关系 当指针被赋值后,其在内存的安放如 a ),当数据比较后进行交换,这时,指针变量与所指 向的变量的关系如 b )所示,在程序的运行过程中,指针变量与所指向的变量其指向始终没变。 下面对程序做修改。 &b 3 p2 b *p2 &a 4 p1 a *p1 &a 3 p1 a *p1 &b 4 p2 b *p2 a) b)第6章 指 针 9 5下载 [例6 - 3 ] m a i n ( ) { int *p1,*p2,a,b,*t; s c a n f ( " % d , % d " , & a , & b ) ; p 1 = & a ; p 2 = & b ; i f ( * p 1 < * p 2 ) { / *指针交换指向* / t = p 1 ; p 1 = p 2 ; p 2 = t ; } p r i n t f ( " % d , % d \ n " , * p 1 , * p 2 ) ; } 程序的运行结果完全相同,但程序在运行过程中,实际存放在内存中的数据没有移动, 而是将指向该变量的指针交换了指向。其示意如图 6 - 5: 图6-5 修改后的程序在运行中指针与变量之间的关系 当指针交换指向后,p 1和p 2由原来指向的变量a和b改变为指向变量b和a,这样一来,* p 1 就表示变量b,而* p 2就表示变量a。在上述程序中,无论在何时,只要指针与所指向的变量满 足p = & a;我们就可以对变量 a 以指针的形式来表示。此时 p等效于& a,* p等效于变量a 。 6.3.2 指针变量作函数的参数 函数的参数可以是我们在前面学过的简单数据类型,也可以是指针类型。使用指针类型 做函数的参数,实际向函数传递的是变量的地址。由于子程序中获得了所传递变量的地址, 在该地址空间的数据当子程序调用结束后被物理地保留下来。 [例6-4] 利用指针变量作为函数的参数,用子程序的方法再次实现上述功能。 m a i n ( ) { void chang(); / *函数声明* / int *p1,*p2,a,b,*t; s c a n f ( " % d , % d " , & a , & b ) ; p 1 = & a ; p 2 = & b ; c h a n g ( p 1 , p 2 ) ; / *子程序调用* / p r i n t f ( " % d , % d \ n " , * p 1 , * p 2 ) ; &a 4 p2 b p1 &b 3 p1 a *p2 &b 4 p2 b *p2 &a 3 p1 a *p1 a) b)return 0; } void chang(int *pt1,int *pt2) { / *子程序实现将两数值调整为由大到小* / int t; if (*pt1<*pt2) / *交换内存变量的值* / { t=*pt1; *pt1=*pt2; * p t 2 = t ; } r e t u r n ; } 由于在调用子程序时,实际参数是指针变量,形式参数也是指针变量,实参与形参相结 合,传值调用将指针变量传递给形式参数 p t 1和p t 2。但此时传值传递的是变量地址,使得在子 程序中p t 1和p t 2具有了p 1和p 2的值,指向了与调用程序相同的内存变量,并对其在内存存放的 数据进行了交换,其效果与 [例6 - 2 ]相同。 思考下面的程序,是否也能达到相同的效果呢? m a i n ( ) { void chang(); int *p1,*p2,a,b,*t; s c a n f ( " % d , % d " , & a , & b ) ; p 1 = & a ; p 2 = & b ; c h a n g ( p 1 , p 2 ) ; p r i n t f ( " % d , % d \ n " , * p 1 , * p 2 ) ; } void chang(int *pt1,int *pt2) { int *t; if (*pt1<*pt2) { t=pt1; pt1=pt2; p t 2 = t ; } r e t u r n ; } 程序运行结束,并未达到预期的结果,输出与输入完全相同。其原因是对子程序来说, 函数内部进行指针相互交换指向,而在内存存放的数据并未移动,子程序调用结束后, m a i n ( )函数中p 1和p 2保持原指向,结果与输入相同。 6.4 指针与数组 变量在内存存放是有地址的,数组在内存存放也同样具有地址。对数组来说,数组名就 是数组在内存安放的首地址。指针变量是用于存放变量的地址,可以指向变量,当然也可存 放数组的首址或数组元素的地址,这就是说,指针变量可以指向数组或数组元素,对数组而 言,数组和数组元素的引用,也同样可以使用指针变量。下面就分别介绍指针与不同类型的 数组。 9 6 C语言程序设计 下载6.4.1 指针与一维数组 假设我们定义一个一维数组,该数组在内存会有系统分配的一个存储空间,其数组的名 字就是数组在内存的首地址。若再定义一个指针变量,并将数组的首址传给指针变量,则该 指针就指向了这个一维数组。我们说数组名是数组的首地址,也就是数组的指针。而定义的 指针变量就是指向该数组的指针变量。对一维数组的引用,既可以用传统的数组元素的下标 法,也可使用指针的表示方法。 int a[10] , *ptr; /* 定义数组与指针变量* / 做赋值操作:ptr=a; 或 p t r = & a [ 0 ] ; 则p t r就得到了数组的首址。其中, a是数组的首地址,& a [ 0 ]是数组元素a [ 0 ]的地址,由于 a [ 0 ]的地址就是数组的首地址,所以,两条赋值操作效果完全相同。指针变量 p t r就是指向数 组a的指针变量。 若p t r指向了一维数组,现在看一下 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) 指向数组的指针变量也可用数组的下标形式表示为 p t r [ n ],其效果相当于* ( p t r + n )。 [例6-5] /*以下标法输入输出数组各元素。 下面从键盘输入1 0个数,以数组的不同引用形式输出数组各元素的值。 # include m a i n ( ) { int n,a[10],*ptr=a; f o r ( n = 0 ; n < = 9 ; n + + ) s c a n f ( " % d " , & a [ n ] ) ; printf("1------output! \n"); f o r ( n = 0 ; n < = 9 ; n + + ) p r i n t f ( " % 4 d " , a [ n ] ) ; p r i n t f ( " \ n " ) ; } 运行程序: R U N ¿ 1 2 3 4 5 6 7 8 9 0¿ 1 - - - - - - o u t p u t ! 1 2 3 4 5 6 7 8 9 0 [例6-6] 采用指针变量表示的地址法输入输出数组各元素。 # include m a i n ( ) { int n,a[10],*ptr=a; / *定义时对指针变量初始化* / 第6章 指 针 9 7下载9 8 C语言程序设计 下载 f o r ( n = 0 ; n < = 9 ; n + + ) s c a n f ( " % d " , p t r + n ) ; printf("2------output! \n"); f o r ( n = 0 ; n < = 9 ; n + + ) p r i n t f ( " % 4 d " , * ( p t r + n ) ) ; p r i n t f ( " \ n " ) ; } 运行程序: R U N ¿ 1 2 3 4 5 6 7 8 9 0¿ 2 - - - - - - o u t p u t ! 1 2 3 4 5 6 7 8 9 0 [例6-7] 采用数组名表示的地址法输入输出数组各元素。 m a i n ( ) { int n,a[10],*ptr=a; f o r ( n = 0 ; n < = 9 ; n + + ) s c a n f ( " % d " , a + n ) ; printf("3------output! \n"); f o r ( n = 0 ; n < = 9 ; n + + ) p r i n t f ( " % 4 d " , * ( a + n ) ) ; p r i n t f ( " \ n " ) ; } 运行程序: R U N ¿ 1 2 3 4 5 6 7 8 9 0¿ 3 - - - - - - o u t p u t ! 1 2 3 4 5 6 7 8 9 0 [例6-8] 用指针表示的下标法输入输出数组各元素。 m a i n ( ) { int n,a[10],*ptr=a; f o r ( n = 0 ; n < = 9 ; n + + ) s c a n f ( " % d " , & p t r [ n ] ) ; printf("4------output! \n"); f o r ( n = 0 ; n < = 9 ; n + + ) p r i n t f ( " % 4 d " , p t r [ n ] ) ; p r i n t f ( " \ n " ) ; } 运行程序: R U N ¿ 1 2 3 4 5 6 7 8 9 0¿ 4 - - - - o u t p u t !1 2 3 4 5 6 7 8 9 0 [例6-9] 利用指针法输入输出数组各元素。 m a i n ( ) { int n,a[10],*ptr=a; f o r ( n = 0 ; n < = 9 ; n + + ) s c a n f ( " % d " , p t r + + ) ; printf("5------output! \n"); p t r = a ; / *指针变量重新指向数组首址* / f o r ( n = 0 ; n < = 9 ; n + + ) p r i n t f ( " % 4 d " , * p t r + + ) ; p r i n t f ( " \ n " ) ; } 运行程序: R U N ¿ 1 2 3 4 5 6 7 8 9 0¿ 5 - - - - - o u t p u t ! 1 2 3 4 5 6 7 8 9 0 在程序中要注意 * p t r + +所表示的含义。 * p t r表示指针所指向的变量; p t r + +表示指针所指 向的变量地址加 1个变量所占字节数,具体地说,若指向整型变量,则指针值加 2,若指向实 型,则加4,依此类推。而p r i n t f (“% 4 d”, * p t r + + )中,* p t r + +所起作用为先输出指针指向的变 量的值,然后指针变量加1。循环结束后,指针变量指向如图 6 - 6所示: 图6-6 例6-9中循环结束后的指 针变量 指针变量的值在循环结束后,指向数组的尾部的后面。假设元素 a [ 9 ]的地址为1 0 0 0 ,整型 占2字节,则p t r的值就为1 0 0 2。请思考下面的程序段: m a i n ( ) { int n,a[10],*ptr=a; f o r ( n = 0 ; n < = 9 ; n + + ) s c a n f ( " % d " , p t r + + ) ; printf("4------output! \n"); f o r ( n = 0 ; n < = 9 ; n + + ) p r i n t f ( " % 4 d " , * p t r + + ) ; p r i n t f ( " \ n " ) ; } 程序与例6 - 9相比,只少了赋值语句p t r = a;程序的运行结果还相同吗? 6.4.2 指针与二维数组 定义一个二维数组: 第6章 指 针 9 9下载 a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9] 1 2 3 4 5 6 7 8 9 0 ptr1 0 0 C语言程序设计 下载 int a[3][4]; 表示二维数组有三行四列共 1 2个元素,在内存中按行存放,存放形式为图 6 - 7: 其中a是二维数组的首地址, & a [ 0 ] [ 0 ]既可以看作数组 0行0列的首地址,同样还可以看作 是二维数组的首地址, a [ 0 ]是第0行的首地址,当然也是数组的首地址。同理 a [ n ]就是第n行的 首址;& a [ n ] [ m ]就是数组元素a [ n ] [ m ]的地址。 既然二维数组每行的首地址都可以用 a [ n ]来表示,我们就可以把二维数组看成是由 n行一 维数组构成,将每行的首地址传递给指针变量,行中的其余元素均可以由指针来表示。下面 的图6 - 8给出了指针与二维数组的关系: 图6-7 二维数组在内存中的存放 图6-8 指针与二维数组的关系 我们定义的二维数组其元素类型为整型,每个元素在内存占两个字节,若假定二维数组 从1 0 0 0单元开始存放,则以按行存放的原则,数组元素在内存的存放地址为 1 0 0 0~1 0 2 2。 用地址法来表示数组各元素的地址。对元素 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];若赋值: p t r = a;则用ptr++ 就能访问数组的各元素。 [例6-10] 用地址法输入输出二维数组各元素。 # include m a i n ( ) { int a[3][4]; int i,j; f o r ( i = 0 ; i < 3 ; i + + ) f o r ( j = 0 ; j < 4 ; j + + ) s c a n f ( " % d " , a [ i ] + j ) ; / *地址法* / f o r ( i = 0 ; i < 3 ; i + + ) { f o r ( j = 0 ; j < 4 ; j + + ) printf("%4d",*(a[i]+j)); /* *(a[i]+j)是地址法所表示的数组元素* / p r i n t f ( " \ n " ) ; } } 运行程序: R U N ¿ 1 2 3 4 5 6 7 8 9 10 11 12¿ 1 2 3 4 5 6 7 8 9 1 0 1 1 1 2 [例6 - 11] 用指针法输入输出二维数组各元素。 # include m a i n ( ) { int a[3][4],*ptr; int i,j; p t r = a [ 0 ] ; f o r ( i = 0 ; i < 3 ; i + + ) f o r ( j = 0 ; j < 4 ; j + + ) s c a n f ( " % d " , p t r + + ) ; / *指针的表示方法* / p t r = a [ 0 ] ; f o r ( i = 0 ; i < 3 ; i + + ) { f o r ( j = 0 ; j < 4 ; j + + ) p r i n t f ( " % 4 d " , * p t r + + ) ; p r i n t f ( " \ n " ) ; } } 运行程序: R U N ¿ 1 2 3 4 5 6 7 8 9 10 11 12¿ 1 2 3 4 第6章 指 针 1 0 1下载1 0 2 C语言程序设计 下载 5 6 7 8 9 1 0 1 1 1 2 对指针法而言,程序可以把二维数组看作展开的一维数组: m a i n ( ) { int a[3][4],*ptr; int i,j; p t r = a [ 0 ] ; f o r ( i = 0 ; i < 3 ; i + + ) f o r ( j = 0 ; j < 4 ; j + + ) s c a n f ( " % d " , p t r + + ) ; / *指针的表示方法* / p t r = a [ 0 ] ; f o r ( i = 0 ; i < 1 2 ;i + + ) p r i n t f ( " % 4 d " , * p t r + + ) ; p r i n t f ( " \ n " ) ; } 运行程序: R U N ¿ 1 2 3 4 5 6 7 8 9 10 11 12¿ 1 2 3 4 5 6 7 8 9 1 0 1 1 1 2 6.4.3 数组指针作函数的参数 学习了指向一维和二维数组指针变量的定义和正确引用后,我们现在学习用指针变量作 函数的参数。 [例6-12] 调用子程序,实现求解一维数组中的最大元素。 我们首先假设一维数组中下标为 0的元素是最大和用指针变量指向该元素。后续元素与该 元素一一比较,若找到更大的元素,就替换。子程序的形式参数为一维数组,实际参数是指 向一维数组的指针。 # include m a i n ( ) { int sub_max(); / *函数声明* / int n,a[10],*ptr=a; / *定义变量,并使指针指向数组* / int max; f o r ( n = 0 ; n < = i - 1 ; n + + ) / *输入数据* / s c a n f ( " % d " , & a [ n ] ) ; m a x = s u b _ m a x ( p t r , 1 0 ) ; / *函数调用,其实参是指针* / p r i n t f ( " m a x = % d \ n " , m a x ) ; } int sub_max(b,i) / *函数定义,其形参为数组* / int b[],i; { int temp,j; t e m p = b [ 0 ] ;f o r ( j = 1 ; j < = 9 ; j + + ) if(temp m a i n ( ) { int sub_max(); int n,a[10],*ptr=a; int max; f o r ( n = 0 ; n < = 9 ; n + + ) s c a n f ( " % d " , & a [ n ] ) ; m a x = s u b _ m a x ( p t r , 1 0 ) ; p r i n t f ( " m a x = % d \ n " , m a x ) ; } int sub_max(b,i) / *形式参数为指针变量* / int *b,i; { int temp,j; t e m p = b [ 0 ] ; / *数组元素指针的下标法表示* / f o r ( j = 1 ; j < = i - 1 ; j + + ) if(temp m a i n ( ) 第6章 指 针 1 0 3下载{ int sub_max(); int n,a[10],*ptr=a; int max; f o r ( n = 0 ; n < = 9 ; n + + ) s c a n f ( " % d " , & a [ n ] ) ; m a x = s u b _ m a x ( p t r , 1 0 ) ; p r i n t f ( " m a x = % d \ n " , m a x ) ; } int sub_max(b,i)/ *子程序定义* / int *b,i; { int temp,j; t e m p = * b + + ; f o r ( j = 1 ; j < = i - 1 ; j + + ) if(temp<*b) temp=*b++; return temp; } 图6-9 例6-12程序在内存中虚实结合示意图 图6-10 例6-13程序在内存中虚实结合示意图 在程序中,赋值语句t e m p = * b + +;可以分解为:t e m p = * b;b + +;两句,先作t e m p = * b;后 作b + +;程序的运行结果与上述完全相同。 对上面的程序作修改,在子程序中不仅找最大元素,同时还要将元素的下标记录下来。 # include m a i n ( ) { int *max();/* 函数声明* / int n,a[10],*s,i; f o r ( i = 0 ; i < 1 0 ; i + + ) / * 输入数据* / 1 0 4 C语言程序设计 下载 b[0] b[1] b[2] b[3] b[4] b[5] b[6] b[7] b[8] b[9] a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9] 主程序 a 子程序 ptr a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9] 主程序 a 子程序 ptr b第6章 指 针 1 0 5下载 scanf("%d",a+i); s = m a x ( a , 1 0 ) ; / *函数调用* / p r i n t f ( " m a x = % d , i n d e x = % d \ n " , * s , s - a ) ; } int *max(a,n) / *定义返回指针的函数* / int *a,n; { int *p,*t; / * p 用于跟踪数组,t用于记录最大值元素的地址* / f o r ( p = a , t = a ; p - a < n ; p + + ) if(*p>*t) t=p; return t; } 在m a x()函数中,用 p - a < n来控制循环结束, a是数组首地址, p用于跟踪数组元素的地 址,p - a正好是所跟踪元素相对数组头的距离,或者说是所跟踪元素相对数组头的元素个数, 所以在m a i n ( )中,最大元素的下标就是该元素的地址与数组头的差,即 s - a。运行程序: R U N ¿ 1 3 5 7 9 2 4 6 8 0¿ m a x = 9 , i n d e x = 4 [例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 #define N 10 m a i n ( ) { void input(); / *函数声明* /1 0 6 C语言程序设计 下载 void sort(); void output(); int a[N],*p; / *定义一维数组和指针变量* / i n p u t ( a , N ) ; / *数据输入函数调用,实参a是数组名* / p = a ; / *指针变量指向数组的首地址* / s o r t ( p , N ) ; / *排序,实参p是指针变量* / o u t p u t ( p , N ) ; / *输出,实参p是指针变量* / } void input(arr,n) / *无需返回值的输入数据函数定义,形参a r r 是数组* / int arr[],n; { int i; printf("input data:\n"); f o r ( i = 0 ; i < n ; i + + ) / *采用传统的下标法*/ s c a n f ( " % d " , & a r r [ i ] ) ; } void sort(ptr,n) / *冒泡排序,形参p t r 是指针变量* / int *ptr,n; { int i,j,t; f o r ( i = 0 ; i < n - 1 ; i + + ) f o r ( j = 0 ; j < n - 1 - i ; j + + ) if (*(ptr+j)>*(ptr+j+1))/*相临两个元素进行比较* / { t = * ( p t r + j ) ; / *两个元素进行交换* / * ( p t r + j ) = * ( p t r + j + 1 ) ; * ( p t r + j + 1 ) = t ; } } void output(arr,n) / *数据输出* / int arr[],n; { int i,*ptr=arr; / *利用指针指向数组的首地址* / printf("output data:\n"); f o r ( ; p t r - a r r < n ; p t r + + ) / *输出数组的n个元素* / p r i n t f ( " % 4 d " , * p t r ) ; p r i n t f ( " \ n " ) ; } 运行程序: R U N ¿ 3 5 7 9 3 23 43 2 1 10¿ 1 2 3 3 5 7 9 10 23 4 3 由于C程序的函数调用是采用传值调用,即实际参数与形式参数相结合时,实参将值传给 形式参数,所以当我们利用函数来处理数组时,如果需要对数组在子程序中修改,只能传递 数组的地址,进行传地址的调用,在内存相同的地址区间进行数据的修改。在实际的应用中, 如果需要利用子程序对数组进行处理,函数的调用利用指向数组(一维或多维)的指针作参 数,无论是实参还是形参共有下面四种情况:实 参 形 参 1 数组名 数组名 2 数组名 指针变量 3 指针变量 数组名 4 指针变量 指针变量 在函数的调用时,实参与形参的结合要注意所传递的地址具体指向什么对象,是数组的 首址,还是数组元素的地址,这一点很重要。 [例6-16] 用指向二维数组的指针作函数的参数,实现对二维数组的按行相加。 # include #define M 3 #define N 4 m a i n ( ) { float a[M][N]; float score1,score2,score3, *pa=a[0];/*指针变量p a指向二维数组* / /* score1,score2,score3分别记录三行的数据相加* / int i,j; void fun(); f o r ( i = 0 ; i < M ; i + + ) for(j=0;j m a i n ( ) { int a[3][4],*ptr,i,j,max,maxi,maxj; / * m a x 是数组的最大, m a x i 是最大元素所在行, m a x j 是最大元素所在列* / f o r ( i = 0 ; i < 3 ; i + + ) f o r ( j = 0 ; j < 4 ; j + + ) s c a n f ( " % d " , & a [ i ] [ j ] ) ; p t r = a [ 0 ] ; / *将二维数组的首地址传递给指针变量* / m a x _ a r r ( p t r , & m a x , & m a x i , 1 2 ) ; m a x j = m a x i % 4 ; / *每行有四个元素,求该元素所在列* / m a x i = m a x i / 4 ; / *求该元素所在行* / printf("max=%d,maxi=%d,maxj=%d",max,maxi,maxj); } int max_arr(b,p1,p2,n) int *b,*p1,*p2,n; / * b 指向二维数组的指针,p 1指向最大值,p 2指向最大值在一维数组中的位置,* / / * n 是数组的大小* / { int i; *p1=b[0]; *p1=0; f o r ( i = 1 ; i < n ; i + + ) / * 找最大* / if (b[i]>*p1) {*p1=b[i]; *p2=i;} } 运行程序: R U N ¿ 4 7 8 9¿ 3 7 9 3¿ 1 5 2 6¿ max=9,maxi=0,maxj=3 6.4.4 指针与字符数组 在前面的课程中,我们用过了字符数组,即通过数组名来表示字符串,数组名就是数组 的首地址,是字符串的起始地址。下面的例子用于简单字符串的输入和输出。第6章 指 针 1 0 9下载 #include m a i n ( ) { char str[20]; g e t s ( s t r ) ; p r i n t f ( " % s \ n " , s t r ) ; } R U N ¿ good morning!¿ good morning!¿ 现在,我们将字符数组的名赋予一个指向字符类型的指针变量,让字符类型指针指向字 符串在内存的首地址,对字符串的表示就可以用指针实现。其定义的方法为: char str[20], * P = s t r;这样一来,字符串s t r就可以用指针变量P来表示了。 #include m a i n ( ) { char str[20],*p=str ; /* p=str则表示将字符数组的首地址传递给指针变量p */ g e t s ( s t r ) ; p r i n t f ( " % s \ n " , p ) ; } R U N ¿ good morning!¿ good morning!¿ 需要说明的是,字符数组与字符串是有区别的,字符串是字符数组的一种特殊形式,存 储时以“\ 0”结束,所以,存放字符串的字符数组其长度应比字符串大 1。对于存放字符的字 符数组,若未加“\ 0”结束标志,只能按逐个字符输入输出。 [例6-18] 字符数组的正确使用方法。 # include m a i n ( ) { char str[10],*p=str; int i; s c a n f ( " % s " , s t r ) ; / *输入的字符串长度超过1 0 * / for( i=0;i<10;i++) p r i n t f ( " % c " , * p + + ) ; / *正确输出* / p r i n t f ( " \ n " ) ; p = s t r ; p r i n t f ( " % s " , p ) ; / *字符数组无' \ 0 ' 标志,输出出错* / puts(str); / *字符数组无' \ 0 ' 标志,输出出错* / } 对上述程序中字符数组以字符串形式输出,若无“ \ 0”标志,则找不到结束标志,输出出 错。 [例6-19] 用指向字符串的指针变量处理两个字符串的复制。字符串的复制要注意的是:若将串 1复制到串2,一定要保证串2的长度大于或等于串1。 # i n c l u d e < s t d i o . h > m a i n ( ) { char str1[30],str2[20],*ptr1=str1,*ptr2=str2; printf("input str1:"); g e t s ( s t r 1 ) ; / *输入s t r 1 * / printf("input str2:"); g e t s ( s t r 2 ) ; / *输入s t r 2 * / p r i n t f ( " s t r 1 - - - - - - - - - - - - s t r 2 \ n " ) ; p r i n t f ( " % s . . . . . . . % s \ n " , p t r 1 , p t r 2 ) ; while(*ptr2) *ptr1++=*ptr2++;/ *字符串复制* / * p t r 1 = ' \ 0 ' ; /* 写入串的结束标志* / p r i n t f ( " s t r 1 - - - - - - - - - - - - s t r 2 \ n " ) ; p r i n t f ( " % s . . . . . . . % s \ n " , s t r 1 , s t r 2 ) ; } 在程序的说明部分,定义的字符指针指向字符串。语句 while(*ptr2) *ptr1++=*ptr2++;先 测试表达式的值,若指针指向的字符是“ \ 0”,该字符的A S C I I码值为0,表达式的值为假,循 环结束,表达式的值非零,则执行循环 * p t r 1 + + = * p t r 2 + +。语句* p t r 1 + +按照运算优先级别,先 算* p t r 1,再算p t r 1 + +。 运行程序:R U N¿ input str1: I love China! ¿ input str2: I love Chengdu! ¿ s t r 1 - - - - - - - - - - - - - - - - - - - - s t r 2 I love China! ....... I love Chengdu! s t r 1 - - - - - - - - - - - - - - - - - - - - s t r 2 I love Chengdu! ....... I love Chengdu! 现在,我们修改程序中语句p r i n t f ( " % s . . . . . . . % s \ n " , s t r 1 , s t r 2 )为printf("%s.......%s\n",ptr1, ptr2); 会出现什么结果呢?请思考。 [例6-20] 用指向字符串的指针变量处理两个字符串的合并。 # include m a i n ( ) { char str1[50],str2[20],*ptr1=str1,*ptr2=str2; p r i n t f ( "input str1:"); g e t s ( s t r 1 ) ; p r i n t f ( "input str2:"); g e t s ( s t r 2 ) ; p r i n t f ( "s t r 1 - - - - - - - - - - - - s t r 2 \ n " ) ; p r i n t f ( "% s . . . . . . . % s \ n " , p t r 1 , p t r 2 ) ; while(*ptr1) ptr1++; / *移动指针到串尾* / while(*ptr2) *ptr1++=*ptr2++; /*串连接* / * p t r 1 = ' \ 0 ' ; / *写入串的结束标志* / ptr1=str1; ptr2=str2; p r i n t f ( "s t r 1 - - - - - - - - - - - - - - - - - - s t r 2 \ n " ) ; 1 1 0 C语言程序设计 下载p r i n t f ( "% s . . . . . . . % s \ n " , p t r 1 , p t r 2 ) ; } R U N ¿ input str1: I love China! ¿ input str2: I love Chengdu! ¿ s t r 1 - - - - - - - - - - - - - - - - - - - - s t r 2 I love China! ....... I love Chengdu! s t r 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - s t r 2 I love China! I love Chengdu! ...... I love Chengdu!. 需要注意的是,串复制时,串 1的长度应大于等于串 2;串连接时,串1的长度应大于等于 串1与串2的长度之和。 6.5 指针的地址分配 我们可以定义指针变量指向任何类型的变量。在上述的处理过程中,指针变量指向的变 量通过传递变量的地址来实现。指针变量的取值是内存的地址,这个地址应当是安全的,不 可以是随意的,否则,写入内存单元的值将会使得已存放的数据或程序丢失。应使用编译系 统提供的标准函数来实现地址分配。 A N S I标准建议设置了两个最常用的动态分配内存的函数 malloc() 和f r e e ( ),并包含在 s t d l i b . h中,但有些C编译却使用m a l l o c . h包含。使用时请参照具体的 C编译版本。 我们这里所指的动态内存分配其含义是指:当定义指针变量时,其变量的取值是随机的, 可能指向内存的任一单元。若指针的指向是不安全的内存地址,在该地址空间上的数据交换 就会产生意料不到的效果。为此,在程序的执行过程中,要保证指针操作的安全性,就要为 指针变量分配安全地址。在程序执行时为指针变量所做的地址分配就称之为动态内存分配。 当无需指针变量操作时,可以将其所分配的内存归还系统,此过程我们称之为内存单元的释 放。 malloc( )用以向编译系统申请分配内存; free( )用以在使用完毕释放掉所占内存。 [例6-21] 两个字符串的交换。 #include #include #include m a i n ( ) { char *ptr1,*ptr2,*temp; ptr1=malloc(30); /*动态为指针变量分配长度为3 0字节的存储空间* / p t r 2 = m a l l o c ( 2 0 ) ; t e m p = m a l l o c ( 3 0 ) ; printf("input str1:"); g e t s ( p t r 1 ) ; / *输入字符串* / printf("input str2:"); g e t s ( p t r 2 ) ; p r i n t f ( " s t r 1 - - - - - - - - - - - - s t r 2 \ n " ) ; p r i n t f ( " % s . . . . . . . % s \ n " , p t r 1 , p t r 2 ) ; s t r c p y ( t e m p , p t r 1 ) ; / * 串复制* / 第6章 指 针 1 1 1下载s t r c p y ( p t r 1 , p t r 2 ) ; s t r c p y ( p t r 2 , t e m p ) ; p r i n t f ( " s t r 1 - - - - - - - - - - - - s t r 2 \ n " ) ; p r i n t f ( " % s . . . . . . . % s \ n " , p t r 1 , p t r 2 ) ; f r e e ( p t r 1 ) ; f r e e ( p t r 2 ) ; } 为指针变量分配的存储空间长度取决于存放字符的多少。在上述的程序中,两个串的交 换可以通过标准函数 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;三条赋值语句实现。但是,利用指针交换指向,其物理意义与串通过 函数进行的复制完全不同。前者是存放串地址的指针变量数据交换,后者是串在内存物理空 间的数据交换。指针变量用完后,将指针变量所占的存储空间释放。 运行程序:r u n¿ input str1: China¿ input str2: C h e n g d u ¿ s t r 1 - - - - - - - - - - - - s t r 2 C h i n a - - - - - - - - - - C h e n g d u s t r 1 - - - - - - - - - - - - s t r 2 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 * p t r [ 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 #include m a i n ( ) { char *ptr1[4]={"china","chengdu","sichuang","chongqin"}; /* 指针数组p t r 1 的4个指针分别依此指向4个字符串* / int i,*ptr2[3],a[3]={1,2,3},b[3][2]={1,2,3,4,5,6}; 1 1 2 C语言程序设计 下载第6章 指 针 1 1 3下载 for (i=0;i<4;i++) printf("\n%s",ptr1[i]); /*依此输出 p t r 1 数组4个指针指向的4个字符串* / p r i n t f ( " \ n " ) ; f o r ( i = 0 ; i < 3 ; i + + ) ptr2[i]=&a[i]; /*将整型一维数组a的3个元素的地址传递给指针数组p t r 2 * / f o r ( i = 0 ; i < 3 ; i + + ) / * 依此输出p t r 2 所指向的3个整型变量的值* / p r i n t f ( " % 4 d " , * p t r 2 [ i ] ) ; p r i n t f ( " \ n " ) ; f o r ( i = 0 ; i < 3 ; i + + ) ptr2[i]=b[i]; /*传递二维数组b的每行首地址给指针数组的4 个指针* / f o r ( i = 0 ; i < 3 ; i + + ) / * 按行输出* / p r i n t f ( " % 4 d % 4 d \ n " , * p t r 2 [ i ] , * p t r 2 [ i ] + 1 ) ; } 程序中指针数组与所指对象的关系如图 6 - 1 2所示。 图6-12 例6-22程序中指针数组与所指对象的关系 p t r 1指针数组中的 4个指针分别指向 4个字符串,如图 6 - 11的a)所示,程序中依此输出; p t r 2指针数组共有3个指针,若将整型一维数组 a中各元素地址分别传递给指针数组的各指针, 则p t r 2 [ 0 ]就指向a [ 0 ];p t r 2 [ 1 ]就指向a [ 1 ];p t r 2 [ 2 ]就指向a [ 2 ]。若将二维数组各行的首地址分别 传递给指针数组的各指针,如图 6 - 11 b)所示,这样一来, p t r 2 [ 0 ]就指向了b数组的第0行,该 行有两个元素,其地址为 p t r 2 [ 0 ]与p t r 2 [ 0 ] + 1;相应指针数组第i个元素p t r 2 [ i ]指向的b数组的第i 行两个元素地址分别为p t r 2 [ i ]与 p t r [ i ] + 1。 运行程序: R U N ¿ c h i n a c h e n g d u s i c h u a n g c h o n g q i n 1 2 3 1 2 2 4 5 6 在处理二维字符数组时,我们可以把二维字符数组看成是由多个一维字符数组构成,也 就是说看成是多个字符串构成的二维字符数组,或称为字符串数组。 指针数组对于解决这类问题(当然也可以解决其它问题)提供了更加灵活方便的操作。 有一点需要说明,若定义一个指针数组后,指针数组各元素的取值(即地址)要注意安全性。如定义指针数组: char *ptr[3]; 我们说该数组包含三个指针,但指针的指向是不确定的,指针现在可能指向内存的任一 地址。假定现在作语句: scanf("%s", ptr[ i ] ), 则输入的字符串在内存的存放其地址由 ptr[ i ] 决定。除非给指针数组元素赋值安全的地址。 [例6-23] 定义字符指针数组,包含 5个数组元素。同时再定义一个二维字符数组其数组 大小为5 * 1 0,即5行1 0列,可存放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 #include # i n c l u d e < s t d i o . h > m a i n ( ) { char *ptr1[4],str[4][20],temp[20]; / *定义指针数组、二维字符数组、用于交换的一维字符数组 * / int i,j; for (i=0;i<4;i++) g e t s ( s t r [ i ] ) ; / *输入4个字符串* / p r i n t f ( " \ n " ) ; f o r ( i = 0 ; i < 4 ; i + + ) p t r 1 [ i ] = s t r [ i ] ; / *将二维字符数组各行的首地址传递给指针数组的各指针 * / printf("original string:\n"); f o r ( i = 0 ; i < 4 ; i + + ) / *按行输出原始各字符串* / p r i n t f ( " % s \ n " , p t r 1 [ i ] ) ; printf("ordinal string:\n"); f o r ( i = 0 ; i < 3 ; i + + ) / *冒泡排序* / f o r ( j = 0 ; j < 4 - i - 1 ; j + + ) i f ( s t r c m p ( p t r 1 [ j ] , p t r 1 [ j + 1 ] ) > 0 ) { strcpy(temp,ptr1[j]); s t r c p y ( p t r 1 [ j ] , p t r 1 [ j + 1 ] ) ; s t r c p y ( p t r 1 [ j + 1 ] , t e m p ) ; } for( i=0;i<4;i++) / *输出排序后的字符串* / printf("%s\n" , ptr1[i]); } 运行程序: R U N ¿ j k j k d k d d f s ¿ f h f g k j k f g k f ¿ h k f g k g f k k l g ¿ j j k d j d k ¿ original string: 1 1 4 C语言程序设计 下载j k j k d k d d f s f h f g k j k f g k f h k f g k g f k k l g j j k d j d k ordinal string: f h f g k j k f g k f h k f g k g f k k l g j j k d j d k j k j k d k d d f s 程序中一定要注意指针的正确使用。一旦将二维字符数组的各行首地址传递给指针数组 的各指针,则相当于给指针分配了安全可操作的地址,地址空间大小由二维字符数组来决定。 当然也可由编译系统为指针分配地址用于字符串的存放。 [例6-24] 利用 m a l l o c ( )函数为指针分配存储空间,实现字符串的排序。 #include #include #include m a i n ( ) { char *ptr1[4],*temp; int i,j; for (i=0;i<4;i++) { p t r 1 [ i ] = m a l l o c ( 2 0 ) ; / *为指针数组各指针分配2 0字节的存储空间* / g e t s ( p t r 1 [ i ] ) ; } p r i n t f ( " \ n " ) ; printf("original string:\n"); f o r ( i = 0 ; i < 4 ; i + + ) p r i n t f ( " % s \ n " , p t r 1 [ i ] ) ; printf("ordinal string:\n"); f o r ( i = 0 ; i < 3 ; i + + ) f o r ( j = 0 ; j < 4 - i - 1 ; j + + ) i f ( s t r c m p ( p t r 1 [ j ] , p t r 1 [ j + 1 ] ) > 0 ) { t e m p = p t r 1 [ j ] ; / *利用指向字符串的指针,进行指针地址的交换 * / p t r 1 [ j ] = p t r 1 [ j + 1 ] ; p t r 1 [ j + 1 ] = t e m p ; } for( i=0;i<4;i++) / *字符串输出* / printf("%s\n" , ptr1[i]); } 运行程序,其结果与上述例 6 - 2 3完全相同。 [例6-25] 对已排好序的字符指针数组进行指定字符串的查找。字符串按字典顺序排列, 查找算法采用二分法,或称为折半查找。 折半查找算法描述: 第6章 指 针 1 1 5下载1. 设按开序(或降序)输入n 个字符串到一个指针数组。 2. 设low 指向指针数组的低端,high 指向指针数组的高端,m i d = ( l o w + h i g h ) / 2 3. 测试m i d所指的字符串,是否为要找的字符串。 4. 若按字典顺序,m i d所指的字符串大于要查找的串,表示被查字符串在 l o w和m i d之间, 否则,表示被查字符串在m i d和h i g h之间。 5. 修改l o w式h i g h的值,重新计算m i d,继续寻找。 #include #include #include #include m a i n ( ) { char *binary(); /* 函数声明* / char *ptr1[5],*temp; int i,j; for (i=0;i<5;i++) { p t r 1 [ i ] = m a l l o c ( 2 0 ) ; / *按字典顺序输入字符串* / g e t s ( p t r 1 [ i ] ) ; } p r i n t f ( " \ n " ) ; printf("original string:\n"); f o r ( i = 0 ; i < 5 ; i + + ) p r i n t f ( " % s \ n " , p t r 1 [ i ] ) ; printf("input search string:\n"); t e m p = m a l l o c ( 2 0 ) ; gets(temp); /*输入被查找字符串* / i = 5 ; temp=binary(ptr1,temp,i ); / *调用查找函数* / if (temp) printf("succesful-----%s\n" ,temp); else printf("no succesful!\n"); r e t u r n ; } char *binary(char *ptr[],char *str,int n) 定义返回字符指针的函数* / { /*折半查找* / int hig,low,mid; l o w = 0 ; h i g = n - 1 ; w h i l e ( l o w < = h i g ) { m i d = ( l o w + h i g ) / 2 ; if (strcmp(str,ptr[mid])<0) h i g = m i d - 1 ; else if(strcmp(str,ptr[mid])>0) l o w = m i d + 1 ; else return(str); / *查帐成功,返回被查字符串* / 1 1 6 C语言程序设计 下载} return NULL; / *查找失败,返回空指针* / } 运行程序: R U N ¿ c h e n g d u ¿ c h o n g q i n ¿ b e i j i n g ¿ t i a n j i n ¿ s h a n g h a i ¿ original string: chengdu chongqin beijing tianjin shanghai input search string: b e i j i n g ¿ succesful----- beijing [例6-26] 在一个已排好序的字符串数组中,插入一个键盘输入的字符串,使其继续保持 有序。 在上述程序查找成功的基础上,我们将该字符串插入到字符数组中。插入的位置可以是 数组头、中间或数组尾。查找的算法采用折半算法,找到插入位置后,将字符串插入。 #include #include #include #include m a i n ( ) { int binary(); / *查找函数声明* / void insert(); / *插入函数声明* / char *temp,*ptr1[6]; int i,j; for (i=0;i<5;i++) { p t r 1 [ i ] = m a l l o c ( 2 0 ) ; / *为指针分配地址后* / g e t s ( p t r 1 [ i ] ) ; / *输入字符串* / } ptr1[5]=malloc(20); p r i n t f ( " \ n " ) ; printf("original string:\n"); f o r ( i = 0 ; i < 5 ; i + + ) / *输出指针数组各字符串* / p r i n t f ( " % s \ n " , p t r 1 [ i ] ) ; printf("input search string:\n"); t e m p = m a l l o c ( 2 0 ) ; g e t s ( t e m p ) ; / *输入被插字符串* / 第6章 指 针 1 1 7下载1 1 8 C语言程序设计 下载 i=binary(ptr1,temp,5 );/ *寻找插入位置i * / p r i n t f ( " i = % d \ n " , i ) ; i n s e r t ( p t r 1 , t e m p , 5 , i ) ; / *在插入位置 i处插入字符串* / printf("output strings:\n"); f o r ( i = 0 ; i < 6 ; i + + ) / *输出指针数组的全部字符串* / p r i n t f ( " % s \ n " , p t r 1 [ i ] ) ; r e t u r n ; } int binary(char *ptr[],char *str,int n) { /*折半查找插入位置* / int hig,low,mid; l o w = 0 ; h i g = n - 1 ; if (strcmp(str,ptr[0])<0) return 0; / *若插入字符串比字符串数组的第0个小,则插入位置为0 */ if (strcmp(str,ptr[hig])>0) return n; / *若插入字符串比字符串数组的最后一个大,则应插入字符串数组的尾部 * / w h i l e ( l o w < = h i g ) { m i d = ( l o w + h i g ) / 2 ; if (strcmp(str,ptr[mid])<0) h i g = m i d - 1 ; else if(strcmp(str,ptr[mid])>0) l o w = m i d + 1 ; else return(mid); / *插入字符串与字符串数组的某个字符串相同 * / } return low; / *插入的位置在字符串数组中间* / } void insert(char *ptr[],char *str,int n,int i) { int j; for (j=n;j>i;j--) / *将插入位置之后的字符串后移* / s t r c p y ( p t r [ j ] , p t r [ j - 1 ] ) ; strcpy(ptr[i],str); 将被插字符串按字典顺序插入字符串数组 * / } 在程序中,字符串数组的 6个指针均分配 存放2 0字节的有效地址。语句 p t r 1 [ 5 ] = m a l l o c ( 2 0 )保证插入字符串后,也具有安全的存储空 间,字符串的长度以串中最长的为基准向系 统申请存储空间,以保证在串的移动中有足 够的存储空间。 6.7 指向指针的指针 一个指针变量可以指向整型变量、实型 &i 5 5.3 ‘a’ &x 4 &j &ch &p2 p1(双重指针) p2(指针变量) x(整型变量) p ch(字符变量) p j(实型变量) p i(整型变量) 图6-13 双重指针第6章 指 针 1 1 9下载 变量、字符类型变量,当然也可以指向指针类型变量。当这种指针变量用于指向指针类型变 量时,我们称之为指向指针的指针变量,这话可能会感到有些绕口,但你想到一个指针变量 的地址就是指向该变量的指针时;这种双重指针的含义就容易理解了。下面用一些图来描述 这种双重指针,见图6 - 1 3。 在图中,整型变量 i的地址是 & i,将其传递给指针变量 p,则p指向i;实型变量 j的地址 是& j,将其传递给指针变量p,则p指向j; 字符型变量c h的地址是& c h,将其传递给指针变量p, 则p指向ch; 整型变量x的地址是& x,将其传递给指针变量 p 2,则p 2指向x,p 2是指针变量,同 时,将p 2的地址 & p 2传递给p 1,则p 1指向p 2。这里的p 1就是我们谈到的指向指针变量的指针 变量,即指针的指针。 指向指针的指针变量定义如下: 类型标识符 * *指针变量名 例如: float **ptr; 其含义为定义一个指针变量 p t r,它指向另一个指针变量(该指针变量又指向一个实型变 量)。由于指针运算符“*”是自右至左结合,所以上述定义相当于: float *(*ptr); 下面看一下指向指针变量的指针变量怎样正确引用。 [例6-27] 用指向指针的指针变量访问一维和二维数组。 #include #include m a i n ( ) { int a[10],b[3][4],*p1,*p2,**p3,i,j; /*p3是指向指针的指针变量* / f o r ( i = 0 ; i < 1 0 ; i + + ) s c a n f ( " % d " , & a [ i ] ) ; / *一维数组的输入* / for (i=0;i<3;i++) f o r ( j = 0 ; j < 4 ; j + + ) s c a n f ( " % d " , & b [ i ] [ j ] ) ; / *二维数组输入* / for (p1=a,p3=&p1,i=0;i<10;i++) p r i n t f ( " % 4 d " , * ( * p 3 + i ) ) ; / *用指向指针的指针变量输出一维数组* / p r i n t f ( " \ n " ) ; for (p1=a;p1-a<10;p1++) /* 用指向指针的指针变量输出一维数组* / { p 3 = & p 1 ; printf("%4d",**p3); } p r i n t f ( " \ n " ) ; f o r ( i = 0 ; i < 3 ; i + + ) /* 用指向指针的指针变量输出二维数组* / { p 2 = b [ i ] ; p 3 = & p 2 ; for (j=0;j<4;j++) p r i n t f ( " % 4 d " , * ( * p 3 + j ) ) ; p r i n t f ( " \ n " ) ; } f o r ( i = 0 ; i < 3 ; i + + ) /* 用指向指针的指针变量输出二维数组* /1 2 0 C语言程序设计 下载 { p 2 = b [ i ] ; f o r ( p 2 = b [ i ] ; p 2 - b [ i ] < 4 ; p 2 + + ) { p 3 = & p 2 ; p r i n t f ( " % 4 d " , * * p 3 ) ; } p r i n t f ( " \ n " ) ; } } 程序的存储示意如图 6 - 1 4所示,对一维数组 a来说,若把数组的首地址即数组名赋给指针 变量p 1,p 1就指向数组 a,数组的各元素用 p 1表示为,*(p 1 + i),也可以简化为 * p 1 + i表示。 如果继续作将p 3 = & p 1,则将p 1的地址传递给指针变量 p 3,* p 3就是p 1。用p 3来表示一维数组 的各元素,只需要将用p 1表示的数组元素*(p 1 + i)中的p 1换成* p 3即可,表示为* ( * p 3 + i )。 图6-14 例6-27程序的存储示意图 同样,对二维数组b来说,b [ i ]表示第i行首地址,将其传递给指针变量 p 2,使其指向该行。 该行的元素用 p 2表示为* ( p 2 + i )。若作p 3 = & p 2,则表示p 3指向p 2,用p 3表示的二维数组第 i行 元素为:* ( * p 3 + i )。这与程序中的表示完全相同。 运行程序: R U N ¿ 1 2 3 4 5 6 7 8 9 0¿ 1 3 5 7¿ 2 4 6 8¿ 5 7 9 2¿ 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 3 5 7 2 4 6 8 5 7 9 2 1 3 5 7 2 4 6 8 5 7 9 2[例6-28] 利用指向指针的指针变量对二维字符数组的访问。 #include #include m a i n ( ) { int i; static char c[][16]={"c language","fox","computer","home page"}; / *二维字符数组* / static char *cp[]={c[0],c[1],c[2],c[3]};/* 指针数组* / static char **cpp; /* 指向字符指针的指针变量* / c p p = c p ; / *将指针数组的首地址传递给指向字符指针的指针变量 * / for (i=0;i<4;i++) / *按行输出字符串* / p r i n t f ( " % s \ n " , * c p p + + ) ; p r i n t f ( " - - - - - - - - - - - \ n " ) ; for (i=0;i<4;i++) / *按行输出字符串* / { c p p = & c p [ i ] ; p r i n t f ( " % s \ n " , * c p p ) ; } } 运行程序: R U N ¿ c language f o x c o m p u t e r home page - - - - - - - - - - c language f o x c o m p u t e r home page 程序中需要注意的是,执行 c p p = c p是将指针数组的首地址传递给双重指针,所以 * (c p p + i)表示第i行的首地址,而不是c p p + i。在程序设计时一定分清。 6.8 main函数的参数 C程序最大的特点就是所有的程序都是用函数来装配的。 m a i n ( )称之为主函数,是所有程 序运行的入口。其余函数分为有参或无参两种,均由 m a i n ( )函数或其它一般函数调用,若调用 的是有参函数,则参数在调用时传递。 m a i n ( ) { . . . y 1 = f 1 ( x 1 , x 2 ) ; . . . } f1(int a,int b) 第6章 指 针 1 2 1下载{ . . . . Y 2 = f 2 ( x 3 , x 4 ) ; . . . . } f2( int m,int n) { . . . . . . . . . } 在前面课程的学习中,对 m a i n ( )函数始终作为主调函数处理,也就是说,允许 m a i n ( )调用 其它函数并传递参数。事实上, m a i n ( )函数既可以是无参函数,也可以是有参的函数。对于有 参的形式来说,就需要向其传递参数。但是其它任何函数均不能调用 m a i n ( )函数。当然也同样 无法向m a i n ( )函数传递,只能由程序之外传递而来。这个具体的问题怎样解决呢? 我们先看一下main( )函数的带参的形式: m a i n ( a r g c , a r g v ) int argc,char * argv[]; { . . . . . } 从函数参数的形式上看,包含一个整型和一个指针数组。当一个 C的源程序经过编译、链 接后,会生成扩展名为 . E X E的可执行文件,这是可以在操作系统下直接运行的文件,换句话 说,就是由系统来启动运行的。对 m a i n ( )函数既然不能由其它函数调用和传递参数,就只能由 系统在启动运行时传递参数了。 在操作系统环境下,一条完整的运行命令应包括两部分:命令与相应的参数。其格式为: 命令 参数1 参数2 . . . .参数n¿ 此格式也称为命令行。命令行中的命令就是可执行文件的文件名,其后所跟参数需用空 格分隔,并为对命令的进一步补充,也即是传递给 m a i n ( )函数的参数。 命令行与m a i n ( )函数的参数存在如下的关系: 设命令行为: 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 - 1 5所示。 数组的各指针分别指向一个字符串。应当引起 注意的是接收到的指针数组的各指针是从命令行的开始接收的,首先接收到的是命令,其后 才是参数。 下面用实例来说明带参数的 m a i n ( )函数的正确使用。 1 2 2 C语言程序设计 下载 图6-15 指针数组的取值情况[例6-29] 利用图形库函数绘制一个变化的环。它是把一个半径为 R 1的圆周分成n份,然 后以每个等分点为圆心,以 R s为半径画n个圆(关于作图的详细理论本教材第 9章第1节作了专 门介绍,这里只作简单分析)。利用m a i n ( )函数的带参数形式,我们可以从键盘以命令行的方 式输入R 1和R s及屏幕的背景色。 #include /*包含图形库函数的头文件* / #include #define pi 4.1415926 m a i n ( a r g c , a r g v ) int argc;char *argv[]; /* 定义带参数的m a i n ( ) * / { int x,y,r1,rs,color; double a; int gdriver=DETECT,gmode; initgraph(&gdriver,&gmode,"..\\bgi ");/*启动图形工作方式* / r 1 = a t o i ( a r g v [ 1 ] ) ; / *计算基础圆半径* / r s = a t o i ( a r g v [ 2 ] ) ; / *计算同心圆半径* / c o l o r = a t o i ( a r g v [ 3 ] ) ; / *背景色* / c l e a r d e v i c e ( ) ; / *清除图形屏幕* / s e t b k c o l o r ( c o l o r ) ; / *设置背景色* / s e t c o l o r ( 4 ) ; / *设置图形显示颜色* / for(a=0; a<=2*pi;a+=pi/18) / *绘制同心圆* / { x = r 1 * c o s ( a ) + 3 2 0 ; y = r 1 * s i n ( a ) + 2 4 0 ; c i r c l e ( x , y , r s ) ; / *以圆心坐标为x、y,半径为r s画圆* / } g e t c h ( ) ; / *等待按键继续* / c l o s e g r a p h ( ) ; / *关闭图形工作方式* / } 若程序名为L 6 - 2 9 . c,经编译、连结生成可执行文件 L 6 - 2 9 . e x e。在操作系统的环境下运行 程序,命令行方式为: l6-29 40 20 3¿ 则命令行与 m a i n ( )函数的参数有如图 6 - 1 6所示的关 系。 图6 - 1 6中, a rg v [ 0 ]是程序名, a rg v [ 1 ]是r 1的值, a rg v [ 2 ]是r s的值,a rg v [ 3 ]是屏幕的背景色。 由于指针数组均存放字符串,所需的圆半径及背景 色彩通过a t o i ( )函数转换为整型。 通过带参数的m a i n ( )函数,我们可以为自己的程序设置口令,在运行程序的命令行中给出 所需的口令,正确则继续,否则退出。程序图形输出如图 6 - 1 7所示。 [例6-30] 将上述程序作修改,在程序的入口处添置密码,若给定密码正确,则显示图 形。 #include 第6章 指 针 1 2 3下载 图6-16 例6-29的命令行与main( )函数的 参数间的关系#include #define pi 4.1415926 m a i n ( a r g c , a r g v ) 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"); e x i t ( 0 ) ; } initgraph(&gdriver,&gmode,"..\\bgi "); r 1 = a t o i ( a r g v [ 2 ] ) ; r s = a t o i ( a r g v [ 3 ] ) ; c o l o r = a t o i ( a r g v [ 4 ] ) ; c l e a r d e v i c e ( ) ; s e t b k c o l o r ( c o l o r ) ; s e t c o l o r ( 4 ) ; for(a=0; a<=2*pi;a+=pi/18) { x = r 1 * c o s ( a ) + 3 2 0 ; y = r 1 * s i n ( a ) + 2 4 0 ; c i r c l e ( x , y , r s ) ; } g e t c h ( ) ; c l o s e g r a p h ( ) ; } 图6-17 例6-29程序输出的图形 在操作系统的环境下运行程序, 命令行 中增加口令“p a s s”,命令行方式为: l6-30 pass 20 40 3¿ 指针数组的存储字符串如图 6 - 1 8所示。 若给定字符串a rg v [ 1 ]的值是p a s s,则程序 正确运行,否则程序退出。口令正确的情况 下,显示的图形为图6 - 1 7中的一个。 1 2 4 C语言程序设计 下载 图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; / *身份证号码* / 姓 名 性 别 职 业 年 龄 身份证号码 (字符串) (字符) (字符串) (整型) (长整型或字符串) 班 级 学 号 姓 名 操 作 系 统 数 据 结 构 计算机网络 (字符串) (长整型) (字符串) (实型) (实型) (实型) 姓 名 工 作 单 位 家 庭 住 址 邮 编 电 话 号 码 E - m a i l (字符串) (字符串) (字符串) (长整型) (字符串或长整型) (字符串)} ; 成绩表: 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]; / * E m a i l * / }; 这一系列对不同登记表的数据结构的描述类型称为结构体类型。由于不同的问题有不同 的数据成员,也就是说有不同描述的结构体类型。我们也可以理解为结构体类型根据所针对 的问题其成员是不同的,可以有任意多的结构体类型描述。 下面给出C对结构体类型的定义形式: struct 结构体名 { 成员项表列 }; 有了结构体类型,我们就可以定义结构体类型变量,以对不同变量的各成员进行引用。 7.1.1 结构体类型变量的定义 结构体类型变量的定义与其它类型的变量的定义是一样的,但由于结构体类型需要针对 问题事先自行定义,所以结构体类型变量的定义形式就增加了灵活性,共计有三种形式,分 别介绍如下: 1) 先定义结构体类型,再定义结构体类型变量: struct stu / *定义学生结构体类型* / { char name[20]; / *学生姓名* / char sex; / *性别* / long num; / *学号* / float score[3]; / *三科考试成绩* / }; struct stu student1,student2;/ *定义结构体类型变量* / 1 2 6 C语言程序设计 下载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 { int day; int month; int year; } time1,time2; 则变量t i m e 1和t i m e 2各成员的引用形式为:t i m e 1 . d a y、 t i m e 1 . m o n t h、t i m e 1 . y e a r及t i m e 2 . d a y、t i m e 2 . m o n t h、 t i m e 2 . y e a r,如图7 - 1所示。 其结构体类型变量的各成员与相应的简单类型变量使 用方法完全相同。 7.1.3 结构体类型变量的初始化 由于结构体类型变量汇集了各类不同数据类型的成员,所以结构体类型变量的初始化就 第7章 结构体与共用体 1 2 7下载 day month year day month year time2 timel.day time2.day time 1 timel.month time2.month timel.year time2.year 图7-1 结构体类型示例中变量各成员 的引用形式略显复杂。 结构体类型变量的定义和初始化为: 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);转换s t r所指向的字符串为整型,其函数的返回值为整型。 double atof(char *str);转换s t r所指向的字符串为实型,其函数的返回值为双精度的实型。 long atol(char *str);转换s t r所指向的字符串为长整型,其函数的返回值为长整型。 使用上述函数,要包含头文件 " s t d l i b . h "。 对上述的结构体类型变量成员输入采用的一般形式: char temp[20]; g e t s ( s t u d e n t . n a m e ) ; / *输入姓名* / s t u d e n t . s e x = g e t c h a r ( ) ; / *输入性别* / g e t s ( t e m p ) ; / *输入学号* / s t u d e n t . n u m = a t o l ( t e m p ) ; / *转换为长整型* / f o r ( i = 0 ; i < 3 ; i + + ) / *输入三科成绩* / { g e t s ( t e m p ) ; s t u d e n t . s c o r e [ i ] = a t o i ( t e m p ) ; } 对该结构体类型变量成员的输出也必须采用各成员独立输出,而不能将结构体类型变量 以整体的形式输入输出。 C允许针对具体问题定义各种各样的结构体类型,甚至是嵌套的结构体类型。 struct data { int day; 1 2 8 C语言程序设计 Liping f 970541 98.5 97.4 95 下载int mouth; int year; } ; struct stu { char name[20]; struct data birthday; /*出生年月,嵌套的结构体类型* / long num; } person; 该结构体类型变量成员的引用形式: person.name 、p e r s o n . b i r t h d a y. d a y、person. birthday. m o n t h、person. birthday. y e a r、person.num 。 7.2 结构体数组的定义和引用 单个的结构体类型变量在解决实际问题时作用不大,一般是以结构体类型数组的形式出 现。结构体类型数组的定义形式为: struct stu / *定义学生结构体类型* / { char name[20]; / *学生姓名* / char sex; / *性别* / long num; / *学号* / float score[3]; / *三科考试成绩* / }; struct stu stud[20]; /*定义结构体类型数组stud ,* / / *该数组有2 0个结构体类型元素* / 其数组元素各成员的引用形式为: s t u d [ 0 ] . n a m e 、s t u d [ 0 ] . s e x 、s t u d [ 0 ] . s c o r e [ i ] ; s t u d [ 1 ] . n a m e 、s t u d [ 1 ] . s e x 、s t u d [ 1 ] . s c o r e [ i ] ; . . . . . . s t u d [ 1 9 ] . n a m e 、s t u d [ 1 9 ] . s e x 、s t u d [ 1 9 ] . s c o r e [ i ] ; [例7-1] 设某组有4 个人,填写如下的登记表,除姓名、学号外,还有三科成绩,编程实 现对表格的计算,求解出每个人的三科平均成绩,求出四个学生的单科平均,并按平均成绩 由高分到低分输出。 题目要求的问题多,采用模块化编程方式,将问题进行分解如下: 1) 结构体类型数组的输入。 2) 求解各学生的三科平均成绩。 3) 按学生的平均成绩排序。 第7章 结构体与共用体 1 2 9下载 N u m b e r N a m e E n g l i s h M a t h e m a P h y s i c s Av e r a g e 1 L i p i n g 7 8 9 8 7 6 2 Wa n g l i n g 6 6 9 0 8 6 3 J i a n g b o 8 9 7 0 7 6 4 Ya n g m i n g 9 0 1 0 0 6 74) 按表格要求输出。 5) 求解组内学生单科平均成绩并输出。 6) 定义m a i n ( )函数,调用各子程序。 第一步,根据具体情况定义结构体类型。 struct stu { char name[20]; /*姓名* / long number; /*学号* / float score[4]; /* 数组依此存放E n g l i s h 、M a t h e m a 、P h y s i c s ,及A v e r a g e * / } ; 由于该结构体类型会提供给每个子程序使用,是共用的,所以将其定义为外部的结构体 类型,放在程序的最前面。 第二步,定义结构体类型数组的输入模块。 void input(arr,n) /*输入结构体类型数组a r r 的n个元素* / struct stu arr[]; int n; { int i,j; char temp[30]; for (i=0;iarr[j+1].score[3]) { temp=arr[j]; /*结构体类型变量不允许以整体输入或输出,但允许相互赋值 * / arr[j]=arr[j+1]; /*进行交换* / a r r [ j + 1 ] = t e m p ; } } 第五步,按表格要求输出。 void output(arr,n) /*以表格形式输出有n个元素的结构体类型数组各成员* / int n; struct stu arr[]; {int i,j; printf("********************TABLE********************\n"); /*打印表头* / printf("----------------------------------------------------\n"); / *输出一条水平线* / p r i n t f ( " | % 1 0 s | % 8 s | % 7 s | % 7 s | % 7 s | % 7 s | \ n " , " N a m e " , " N u m b e r " , " E n g l i s h " , " M a t h e m a " , " p h y s i c s " , " a v e r a g e " ) ; / *输出效果为:| Name| Number|English|Mathema|Physics|Average|*/ p r i n t f ( " - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ n " ) ; for (i=0;i #include struct stu { char name[20]; long number; float score[4]; } ; m a i n ( ) { void input(); / *函数声明* / void aver(); void order(); void output(); void out_row(); struct stu stud[4]; / *定义结构体数组* / float row[3]; i n p u t ( s t u d , 4 ) ; / *依此调用自定义函数* / a v e r ( s t u d , 4 ) ; o r d e r ( s t u d , 4 ) ; o u t p u t ( s t u d , 4 ) ; o u t _ r o w ( s t u d , 4 ) ; } / * * * * * * * * * * * * * * * * * * * * * * * * * * * * / void input(arr,n) struct stu arr[]; int n; { int i,j; char temp[30]; for (i=0;iarr[j+1].score[3]) { temp=arr[j]; a r r [ j ] = a r r [ j + 1 ] ; a r r [ j + 1 ] = t e m p ; } } / * * * * * * * * * * * * * * * * * * / void output(arr,n) int n; struct stu arr[]; {int i,j; p r i n t f ( " * * * * * * * * * * * * * * * * * * * * T A B L E * * * * * * * * * * * * * * * * * * * * \ n " ) ; p r i n t f ( " - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ n " ) ; p r i n t f ( " | % 1 0 s | % 8 s | % 7 s | % 7 s | % 7 s | % 7 s | \ n " , " N a m e " , " N u m b e r " , " E n g l i s h " , " m a t h e m a " , " p h y s i c s " , " a v e r a g e " ) ; p r i n t f ( " - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ n " ) ; for (i=0;i 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 ; 定义指针变量p 1、p 2,分别指向结构体类型变量。引用形式为:指针变量→成员; [例7-2] 对指向结构体类型变量的正确使用。输入一个结构体类型变量的成员,并输出。 #include /*使用m a l l o c ( ) 需要* / struct data / *定义结构体* / { 第7章 结构体与共用体 1 3 5下载 ←→int day,month,year; } ; struct stu /*定义结构体* / { char name[20]; long num; struct data birthday; /*嵌套的结构体类型成员* / } ; main() /*定义m a i n ( ) 函数* / { struct stu *student; /*定义结构体类型指针* / student=malloc(sizeof(struct stu)); /*为指针变量分配安全的地址* / printf("Input name,number,year,month,day:\n"); scanf("%s",student->name); /*输入学生姓名、学号、出生年月日* / s c a n f ( " % l d " , & s t u d e n t - > n u m ) ; s c a n f ( " % d % d % d " , & s t u d e n t - > b i r t h d a y . y e a r , & s t u d e n t - > b i r t h d a y . m o n t h , & s t u d e n t - > b i r t h d a y . d a y ) ; printf("\nOutput name,number,year,month,day\n" ); / *打印输出各成员项的值* / p r i n t f ( " % 2 0 s % 1 0 l d % 1 0 d / / % d / / % d \ n " , s t u d e n t - > n a m e , s t u d e n t - > n u m , s t u d e n t - > b i r t h d a y . y e a r , s t u d e n t - > b i r t h d a y . m o n t h , s t u d e n t - > b i r t h d a y . d a y ) ; } 程序中使用结构体类型指针引用结构体变量的成员,需要通过 C提供的函数m a l l o c ( )来为 指针分配安全的地址。函数 s i z e o f ( )返回值是计算给定数据类型所占内存的字节数。指针所指 各成员形式为: s t u d e n t - > n a m e s t u d e n t - > n u m s t u d e n t - > b i r t h d a y . y e a r s t u d e n t - > b i r t h d a y . m o n t h s t u d e n t - > b i r t h d a y . d a y 运行程序: R U N ¿ 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 1 3 6 C语言程序设计 下载{ int day,month,year; } ; struct stu /*定义结构体* / { char name[20]; long num; struct data birthday; /*嵌套的结构体类型成员* / } ; struct stu student[4],*p; /*定义结构体数组及指向结构体类型的指针* / 作 p = s t u d e n t,此时指针p就指向了结构体数组s t u d e n t。 p是指向一维结构体数组的指针,对数组元素的引用可采用三种方法。 1) 地址法 s t u d e n t + i和p + i均表示数组第i个元素的地址,数组元素各成员的引用形式为: (s t u d e n t + i)- > n a m e、( s t u d e n t + i ) - > n u m和( p + i ) - > n a m e、(p + i)- > n u m等。s t u d e n t + i和p + i 与& s t u d e n t [ i ]意义相同。 2) 指针法 若p指向数组的某一个元素,则 p + +就指向其后续元素。 3) 指针的数组表示法 若p = s t u d e n t,我们说指针 p指向数组 s t u d e n t,p [ i ]表示数组的第 i个元素,其效果与 s t u d e n t [ i ]等同。对数组成员的引用描述为 : p [ i ] . n a m e、p [ i ] . n u m等。 [例7-4] 指向结构体数组的指针变量的使用。 struct data /*定义结构体类型* / { int day,month,year; } ; struct stu /*定义结构体类型* / { char name[20]; long num; struct data birthday; } ; m a i n ( ) { int i; struct stu *p,student[4]={{"liying",1,1978,5,23},{"wangping",2,1979,3,14}, { " l i b o " , 3 , 1 9 8 0 , 5 , 6 } , { " x u y a n " , 4 , 1 9 8 0 , 4 , 2 1 } } ; / *定义结构体数组并初始化* / p=student; /*将数组的首地址赋值给指针p , p 指向了一维数组s t u d e n t * / printf("\n1----Output name,number,year,month,day\n" ); for(i=0;i<4;i++) /*采用指针法输出数组元素的各成员* / p r i n t f ( " % 2 0 s % 1 0 l d % 1 0 d / / % d / / % d \ n " , ( p + i ) - > n a m e , ( p + i ) - > n u m , ( p + i ) - > b i r t h d a y . y e a r , ( p + i ) - > b i r t h d a y . m o n t h , ( p + i ) - > b i r t h d a y . d a y ) ; 第7章 结构体与共用体 1 3 7下载printf("\n2----Output name,number,year,month,day\n" ); f o r ( i = 0 ; i < 4 ; i + + , p + + ) / *采用指针法输出数组元素的各成员* / p r i n t f ( " % 2 0 s % 1 0 l d % 1 0 d / / % d / / % d \ n " , p - > n a m e , p - > n u m , p - > b i r t h d a y . y e a r , p - > b i r t h d a y . m o n t h , p - > b i r t h d a y . d a y ) ; printf("\n3-----Output name,number,year,month,day\n" ); f o r ( i = 0 ; i < 4 ; i + + ) / *采用地址法输出数组元素的各成员* / p r i n t f ( " % 2 0 s % 1 0 l d % 1 0 d / / % d / / % d \ n " , ( s t u d e n t + i ) - > n a m e , ( s t u d e n t + i ) - > n u m , ( s t u d e n t + i ) - > b i r t h d a y . y e a r , ( s t u d e n t + i ) - > b i r t h d a y . m o n t h , ( s t u d e n t + i ) - > b i r t h d a y . d a y ) ; p = s t u d e n t ; printf("\n4-----Output name,number,year,month,day\n" ); f o r ( i = 0 ; i < 4 ; i + + ) / *采用指针的数组描述法输出数组元素的各成员* / p r i n t f ( " % 2 0 s % 1 0 l d % 1 0 d / / % d / / % d \ n " , p [ i ] . n a m e , p [ i ] . n u m , p [ i ] . b i r t h d a y . y e a r , p [ i ] . b i r t h d a y . m o n t h , p [ i ] . b i r t h d a y . d a y ) ; } 运行程序: R U N ¿ 1----Output name,number,year,month,day liying 1 1 9 7 8 / / 5 / / 2 3 w a n g p i n g 2 1 9 7 9 / / 3 / / 1 4 l i b o 3 1 9 8 0 / / 5 / / 6 x u y a n 4 1 9 8 0 / / 4 / / 2 1 2----Output name,number,year,month,day liying 1 1 9 7 8 / / 5 / / 2 3 w a n g p i n g 2 1 9 7 9 / / 3 / / 1 4 l i b o 3 1 9 8 0 / / 5 / / 6 x u y a n 4 1 9 8 0 / / 4 / / 2 1 3----Output name,number,year,month,day liying 1 1 9 7 8 / / 5 / / 2 3 w a n g p i n g 2 1 9 7 9 / / 3 / / 1 4 l i b o 3 1 9 8 0 / / 5 / / 6 x u y a n 4 1 9 8 0 / / 4 / / 2 1 4----Output name,number,year,month,day liying 1 1 9 7 8 / / 5 / / 2 3 w a n g p i n g 2 1 9 7 9 / / 3 / / 1 4 l i b o 3 1 9 8 0 / / 5 / / 6 x u y a n 4 1 9 8 0 / / 4 / / 2 1 对二维或多维数组的指针,有兴趣的同学可课后讨论,总结出来。 7.4 链表的建立、插入和删除 数组作为存放同类数据的集合,给我们在程序设计时带来很多的方便,增加了灵活性。 1 3 8 C语言程序设计 下载但数组也同样存在一些弊病。如数组的大小在定义时要事先规定,不能在程序中进行调整, 这样一来,在程序设计中针对不同问题有时需要 3 0个大小的数组,有时需要 5 0个数组的大小, 难于统一。我们只能够根据可能的最大需求来定义数组,常常会造成一定存储空间的浪费。 我们希望构造动态的数组,随时可以调整数组的大小,以满足不同问题的需要。链表就 是我们需要的动态数组。它是在程序的执行过程中根据需要有数据存储就向系统要求申请存 储空间,决不构成对存储区的浪费。 链表是一种复杂的数据结构,其数据之间的相互关系使链表分成三种:单链表、循环链 表、双向链表,下面将逐一介绍。 7.4.1 单链表 图7 - 3是单链表的结构。 图7-3 单链表 单链表有一个头节点 h e a d,指向链表在内存的首地址。链表中的每一个节点的数据类型 为结构体类型,节点有两个成员:整型成员(实际需要保存的数据)和指向下一个结构体类 型节点的指针即下一个节点的地址(事实上,此单链表是用于存放整型数据的动态数组)。链 表按此结构对各节点的访问需从链表的头找起,后续节点的地址由当前节点给出。无论在表 中访问那一个节点,都需要从链表的头开始,顺序向后查找。链表的尾节点由于无后续节点, 其指针域为空,写作为N U L L。 图7 - 3还给出这样一层含义,链表中的各节点在内存的存储地址不是连续的,其各节点的 地址是在需要时向系统申请分配的,系统根据内存的当前情况,既可以连续分配地址,也可 以跳跃式分配地址。 看一下链表节点的数据结构定义: struct node { int num; struct node *p; } ; 在链表节点的定义中,除一个整型的成员外,成员 p是指向与节点类型完全相同的指针。 在链表节点的数据结构中,非常特殊的一点就是结构体内的指针域的数据类型使用了未定义 成功的数据类型。这是在C中唯一规定可以先使用后定义的数据结构。 • 单链表的创建过程有以下几步: 1 )定义链表的数据结构。 2 )创建一个空表。 3 )利用m a l l o c ( )函数向系统申请分配一个节点。 第7章 结构体与共用体 1 3 9 1200 1200 2000 1800 2400 12 2000 34 1800 56 2400 78 NULL head 下载4 ) 将新节点的指针成员赋值为空。若是空表,将新节点连接到表头;若是非空表,将新 节点接到表尾。 5 )判断一下是否有后续节点要接入链表,若有转到3 ),否则结束。 • 单链表的输出过程有以下几步 1) 找到表头。 2) 若是非空表,输出节点的值成员,是空表则退出。 3 )跟踪链表的增长,即找到下一个节点的地址。 4) 转到2 )。 [例7-5] 创建一个存放正整数(输入 - 9 9 9做结束标志)的单链表,并打印输出。 #include /*包含m a l l o c ( ) 的头文件* / #include struct node /*链表节点的结构* / { int num; struct node *next; } ; m a i n ( ) { struct node *creat(); / *函数声明* / void print(); struct node *head; / *定义头指针* / h e a d = N U L L ; / *建一个空表* / h e a d = c r e a t ( h e a d ) ; / *创建单链表* / p r i n t ( h e a d ) ; / *打印单链表* / } / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / struct node *creat(struct node *head) /*函数返回的是与节点相同类型的指针* / { struct node *p1,*p2; p1=p2=(struct node*) malloc(sizeof(struct node)); /*申请新节点* / s c a n f ( " % d " , & p 1 - > n u m ) ; / *输入节点的值* / p 1 - > n e x t = N U L L ; / *将新节点的指针置为空* / w h i l e ( p 1 - > n u m > 0 ) / *输入节点的数值大于0 * / { if (head==NULL) head=p1; / *空表,接入表头* / else p2->next=p1; / *非空表,接到表尾* / p 2 = p 1 ; p1=(struct node *)malloc(sizeof(struct node)); /*申请下一个新节点* / s c a n f ( " % d " , & p 1 - > n u m ) ; / *输入节点的值* / } return head; /*返回链表的头指针* / } / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / void print(struct node *head) /*输出以h e a d 为头的链表各节点的值* / { 1 4 0 C语言程序设计 下载struct node *temp; t e m p = h e a d ; / *取得链表的头指针* / while (temp!=NULL) / *只要是非空表* / { p r i n t f ( " % 6 d " , t e m p - > n u m ) ; / *输出链表节点的值* / t e m p = t e m p - > n e x t ; / *跟踪链表增长* / } } 在链表的创建过程中,链表的头指针是非常重要的参数。因为对链表的输出和查找都要 从链表的头开始,所以链表创建成功后,要返回一个链表头节点的地址,即头指针。 运行程序:R U N ¿ 1 2 3 4 5 6 7 -999¿ 1 2 3 4 5 6 7 链表的创建过程用图示如下: 第一步,创建空表: h e a d N U L L 第二步,申请新节点: p 1 p2 第三步,若是空表,将新节点接到表头: h e a d P 1 P 2 若是非空表,head ... p2 p1 P 2 - > n e x t = p 1。 第四步,p2=p1: head p 2第五步,申请新节点:p 1 若数值为负,则结束;否则转到第三步。 7.4.2 单链表的插入与删除 在链表这种特殊的数据结构中,链表的长短需要根据具体情况来设定,当需要保存数据 时向系统申请存储空间,并将数据接入链表中。对链表而言,表中的数据可以依此接到表尾 或连结到表头,也可以视情况插入表中;对不再需要的数据,将其从表中删除并释放其所占 空间,但不能破坏链表的结构。这就是下面将介绍的链表的插入与删除。 1. 链表的删除 在链表中删除一个节点,用图 7 - 4描述如下: [例7-6] 创建一个学生学号及姓名的单链表,即节点包括学生学号、姓名及指向下一个 节点的指针,链表按学生的学号排列。再从键盘输入某一学生姓名,将其从链表中删除。 首先定义链表的结构: struct 第7章 结构体与共用体 1 4 1下载 1 NULL 1 NULL NULL NULL NULL{ int num;/ *学生学号* / char str[20]; / *姓名* / struct node *next; } ; 图7-4 链表中节点的删除 从图7 - 4中看到,从链表中删除一个节点有三种情况,即删除链表头节点、删除链表的中 间节点、删除链表的尾节点。题目给出的是学生姓名,则应在链表中从头到尾依此查找各节 点,并与各节点的学生姓名比较,若相同,则查找成功,否则,找不到节点。由于删除的节 点可能在链表的头,会对链表的头指针造成丢失,所以定义删除节点的函数的返回值定义为 返回结构体类型的指针。 struct node *delet(head,pstr)/*以h e a d 为头指针,删除p s t r 所在节点* / struct node *head; char *pstr; { struct node *temp,*p; t e m p = h e a d ; / *链表的头指针* / if (head==NULL) / *链表为空* / printf("\nList is null!\n"); else /*非空表* / { t e m p = h e a d ; while (strcmp(temp->str,pstr)!=0&&temp->next!=NULL) / *若节点的字符串与输入字符串不同,并且未到链表尾 * / { p = t e m p ; t e m p = t e m p - > n e x t ; / *跟踪链表的增长,即指针后移* / } if(strcmp(temp->str,pstr)==0 ) / *找到字符串* / { if(temp==head) { / *表头节点* / printf("delete string :%s\n",temp->str); h e a d = h e a d - > n e x t ; f r e e ( t e m p ) ; / *释放被删节点* / 1 4 2 C语言程序设计 下载 NULL NULL NULL NULL head head head 删除表中节点s'p->next=s->next 删除表头节点head=head->next 删除表尾节点s,p->next=NULL p p s s} e l s e { p->next=temp->next; /*表中节点* / printf("delete string :%s\n",temp->str); f r e e ( t e m p ) ; } } else printf("\nno find string!\n");/*没找到要删除的字符串* / } r e t u r n ( h e a d ) ; / *返回表头指针* / } 2. 链表的插入 首先定义链表的结构: struct { int num; /*学生学号* / char str[20]; /*姓名* / struct node *next; } ; 在建立的单链表中,插入节点有三种情况,如图 7 - 5所示。 图7-5 单链表中插入节点 插入的节点可以在表头、表中或表尾。假定我们按照以学号为顺序建立链表,则插入的 节点依次与表中节点相比较,找到插入位置。由于插入的节点可能在链表的头,会对链表的 头指针造成修改,所以定义插入节点的函数的返回值定义为返回结构体类型的指针。节点的 插入函数如下: struct node *insert(head,pstr,n) / *插入学号为n、姓名为p s t r 的节点* / struct node *head; / *链表的头指针* / char *pstr; 第7章 结构体与共用体 1 4 3下载 NULL NULL NULLhead head head 在表头插入节点p1,head=p1 在表中插入节点p1,p3->next=p1;p1->next=p2; 在表尾插入节点p1,p2->next=p1;p1->next=NULL p1 p1 p3 p2 p2 p1int n; { struct node *p1,*p2,*p3; p1=(struct node*)malloc(sizeof(struct node));/*分配一个新节点* / s t r c p y ( p 1 - > s t r , p s t r ) ; / *写入节点的姓名字串* / p 1 - > n u m = n ; / *学号* / p 2 = h e a d ; if (head==NULL) / *空表* / { head=p1; p1->next=NULL;/ *新节点插入表头* / } e l s e { /*非空表* / while(n>p2->num&&p2->next!=NULL) / *输入的学号小于节点的学号,并且未到表尾* / { p 3 = p 2 ; p 2 = p 2 - > n e x t ; / *跟踪链表增长* / } if (n<=p2->num) / *找到插入位置* / if (head==p2) / *插入位置在表头* / { h e a d = p 1 ; p 1 - > n e x t = p 2 ; } e l s e { /*插入位置在表中* / p 3 - > n e x t = p 1 ; p 1 - > n e x t = p 2 ; } e l s e { /*插入位置在表尾* / p 2 - > n e x t = p 1 ; p 1 - > n e x t = N U L L ; } } r e t u r n ( h e a d ) ; / *返回链表的头指针* / } 3. 实例[例7 - 7 ] 创建包含学号、姓名节点的单链表。其节点数任意个,表以学号为序,低学号的在前, 高学号的在后,以输入姓名为空作结束。在此链表中,要求删除一个给定姓名的节点,并插 入一个给定学号和姓名的节点。 # include "stdlib.h" # include "malloc. h" 1 4 4 C语言程序设计 下载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 为头的链表* / p r i n t ( h e a d ) ;/ *调用函数输出节点* / 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); /*调用函数输出节点* / r e t u r n ; } / * * * * * * * * * * * * * * * * * * * * * * / / * * * 创建链表* * * * * * * * * * * * / 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"); g e t s ( t e m p ) ; gets (p1->str); pl->num=atoi (temp); p l - > n e x t = N U L L ; while (strlen (pl->str)>0 { if (head==NULL) head=pl; else p2->next=p1; 第7章 结构体与共用体 1 4 5下载P 2 = p l ; pl=(struct node *)malloc(sizeof(struct node)); printf ("input num, name: \n"); printf("exit:double times Enter!\n"); g e t s ( t e m p ) ; gets(pl ->str); p1->num=atoi (temp); P 1 - > n e x t = N U L L ; } 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); p 1 - > n u m = n ; p 2 = h e a d ; i f ( h e a d = = N U L L ) { h e a d = p l ; p l - > n e x t = N U L L ; } e l s e { while (n>p2->num&&p2->next!=NULL) { p 3 = P 2 p 2 = p 2 - > n e x t ; } if (n<=p2->num) if (head==p2) { h e a d = p l ; p l - > n e x t = p 2 ; } else { p 3 - > n e x t = p l ; p l - > n e x t = p 2 ; } else { p 2 - > n e x t = p l ; p l - > n e x t = N U L L ; 1 4 6 C语言程序设计 下载} } r e t u r n ( h e a d ) ; } / * * * * * * * * * * * * * * * * * * * * * * * * * / / * * * * * 删除节点* * * * * * * * * * * * * / struct node *delet (head, pstr) struct node *head; char *pstr; { struct node *temp,*p; t e m p = h e a d ; if (head==NULL) printf("\nList is null!\n"); else { t e m p = h e a d ; while (strcmp(temp->str,pstr)!=O&&temp->next!=NULL) { p = t e m p ; t e m p = t e m p - > n e x t , } i f ( s t r c m p ( t e m p - > s t r , p s t r ) = = 0 ) { if (temp== head) { h e a d = h e a d - > n e x t ; f r e e ( t e m p ) ; } else { p->next =temp->next; printf("delete string :%s\n",temp->str); f r e e ( t e m p ) ; } } else printf("\nno find string!\n"); } return(head); } / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / / * * * * * * * * * * 链表各节点的输出* * * * * * * * * * / void print (struct node *head) { struct node *temp; t e m p = h e a d ; printf("\n output strings:\n"); while (temp!=NULL) { p r i n t f ( " \ n % d - - - - % s \ n " , t e m p - > n u m ,t e m p - > s t r ) ; 第7章 结构体与共用体 1 4 7下载t e m p = t e m p - > n e x t ; } r e t u r n ; } 运行程序: R U N ¿ input num,name: exit:double times Enter! 1¿ H u a n g p i n g ¿ input num,name: exit:double times Enter! 3¿ L i x i a o b o ¿ input num,name: exit:double times Enter! 4¿ Y a n g j i n h u a ¿ input num,name: exit:double times Enter! 7¿ x u e h o n g ¿ input num,name: exit:double times Enter! ¿ ¿ output strings: 1------- Huangping 3 - - - - - - - - L i x i a o b o 4 - - - - - - - - Y a n g j i n h u a 7 - - - - - - - - x u e h o n g input inserted num,name: 5¿ L i l i n g ¿ output strings: 1------- Huangping 3 - - - - - - - - L i x i a o b o 4 - - - - - - - - Y a n g j i n h u a 5 - - - - - - - - L i l i n g 7 - - - - - - - - x u e h o n g input deleted name: L i x i a o b o ¿ delete string : Lixiaobo 1------- Huangping 4 - - - - - - - - Y a n g j i n h u a 5 - - - - - - - - L i l i n g 7 - - - - - - - - x u e h o n g 1 4 8 C语言程序设计 下载7.5 共用体 所谓共用体类型是指将不同的数据项组织成一个整体,它们在内存中占用同一段存储单 元。其定义形式为: union 共用体名 {成员表列}; 7.5.1 共用体的定义 union data { int a ; float b ; d o u b l e c ; c h a r d ; } obj; 该形式定义了一个共用体数据类型 union data ,定义了共用体数据类型变量 o b j。共用体 数据类型与结构体在形式上非常相似,但其表示的含义及存储是完全不同的。先让我们看一 个小例子。 [例7 - 8 ] union data /*共用体* / { int a; float b; double c; char d; } m m ; struct stud /*结构体* / { int a; float b; double c; char d; } ; m a i n ( ) { struct stud student printf("%d,%d",sizeof(struct stud),sizeof(union data)); } 运行程序输出: R U M ¿ 1 5,8 程序的输出说明结构体类型所占的内存空间为其各成员所占存储空间之和。而形同结构 体的共用体类型实际占用存储空间为其最长的成员所占的存储空间。详细说明如图 7 - 6所示。 第7章 结构体与共用体 1 4 9下载图7-6 共用体类型与结构体类型占用存储空间的比较 对共用体的成员的引用与结构体成员的引用相同。但由于共用体各成员共用同一段内存 空间,使用时,根据需要使用其中的某一个成员。从图中特别说明了共用体的特点,方便程 序设计人员在同一内存区对不同数据类型的交替使用,增加灵活性,节省内存。 7.5.2 共用体变量的引用 可以引用共用体变量的成员,其用法与结构体完全相同。若定义共用体类型为: union data /*共用体* / { int a; float b; double c; char d; } m m ; 其成员引用为:m m . a , m m . b , m m . c , m m . d 但是要注意的是,不能同时引用四个成员,在某一时刻,只能使用其中之一的成员。 [例7-9] 对共用体变量的使用。 m a i n ( ) { union data { int a; float b; double c; char d; } m m ; m m . a = 6 ; printf("%d\n",mm.a); m m . c = 6 7 . 2 ; p r i n t f ( " % 5 . 1 l f \ n " , m m . c ) ; m m . d = ' W ' ; m m . b = 3 4 . 2 ; p r i n t f ( " % 5 . 1 f , % c \ n " , m m . b , m m . d ) ; } 运行程序输出为: 6 1 5 0 C语言程序设计 下载 a b c d d 2 4 8 15 a b 8 c 1 共用体 结构体6 7.2 3 4.2,= 程序最后一行的输出是我们无法预料的。其原因是连续做 m m . d = ' W ';m m . b = 3 4 . 2;两个 连续的赋值语句最终使共用体变量的成员 m m . b所占四字节被写入34 .2,而写入的字符被覆盖 了,输出的字符变成了符号“ =”。事实上,字符的输出是无法得知的,由写入内存的数据决 定。 例子虽然很简单,但却说明了共用体变量的正确用法。 [例7-10] 通过共用体成员显示其在内存的存储情况。 定义一个名为t i m e的结构体,再定义共用体d i g: struct time { int year; / *年* / int month;/ *月* / int day; / *日* / } ; union dig { struct time data; /*嵌套的结构体类型* / char byte[6]; } ; 假定共用体的成员在内存的存储是从地址 1 0 0 0单元开始存放,整个共用体类型需占存储 空间6个字节,即共用体 d i g的成员d a t a与b y t e共用这6个字节的存储空间,存储空间分配示意 如图7 - 7所示。 图7-7 共用体dig成员在存储空间中的分配示意图 由于共用体成员 d a t a包含三个整型的结构体成员,各占 2个字节。由图7 - 7所示可见, d a t a . y e a r是由2个字节组成,用 b y t e字符数组表示为 b y t e [ 0 ]和byte[1] 。b y t e [ 1 ]是高字节, b y t e [ 0 ]是低字节。下面用程序实现共用体在内存中的存储。 struct time { int year; /*年* / int month; / *月* / int day; / *日* / } ; 第7章 结构体与共用体 1 5 1下载 byte[0] data.year byte[1] byte[2] data.month byte[3] byte[4] data.day byte[5] 共 用 体 类 型 1000 1001 1002 1003 1004 1005 存储器union dig { struct time data; /*嵌套的结构体类型* / char byte[6]; } ; m a i n ( ) { union dig unit; int i; printf("enter year:\n"); s c a n f ( " % d " , & u n i t . d a t a . y e a r ) ; / *输入年* / printf("enter month:\n"); s c a n f ( " % d " , & u n i t . d a t a . m o n t h ) ; / *输入月* / printf("enter day:\n"); s c a n f ( " % d " , & u n i t . d a t a . d a y ) ; / *输入日* / p r i n t f ( " y e a r = % d month=%d day=%d\n", unit.data.year,unit. data. month, unit. d a t a . d a y ) ; / *打印输出* / f o r ( i = 0 ; i < 6 ; i + + ) p r i n t f ( " % d , " , u n i t . b y t e [ i ] ) ; / *按字节以十进制输出* / p r i n t f ( " \ n " ) ; } 运行程序: R U N ¿ enter year: 1 9 7 6 ¿ enter month: 4¿ enter day: 2 3¿ year=1976 month=4 day=23 1 8 4 , 7 , 4 , 0 , 2 3 , 0 从程序的输出结果来看, 1 9 7 6占两个字节,由第 0、1字节构成,即7×2 5 6 + 1 8 4 = 1 9 7 6。4 同样占两个字节,由第2、3字节构成,0×2 5 6 + 4 = 4,2 3由第4、5字节构成,2 3 = 0×2 5 6 + 2 3。 1 5 2 C语言程序设计 下载下载 第8章 输入、输出和文件系统 在前面的程序设计中,我们介绍了输入和输出,即从标准输入设备— 键盘输入,由标准 输出设备— 显示器或打印机输出。不仅如此,我们也常把磁盘作为信息载体,用于保存中 间结果或最终数据。在使用一些字处理工具时,会利用打开一个文件来将磁盘的信息输入到 内存,通过关闭一个文件来实现将内存数据输出到磁盘。这时的输入和输出是针对文件系统, 故文件系统也是输入和输出的对象,谈到输入和输出,自然也离不开文件系统。 文件可以从不同的角度来分类: 1) 按文件所依附的介质来分:有卡片文件、纸带文件、磁带文件、磁盘文件等。 2) 按文件内容来分:有源文件、目标文件、数据文件等。 3) 按文件中数据组织形式分:有字符文件和二进制文件。 字符文件通常又称为A S C I I码文件或正文文件,按字符存储,具有可读性;而二进制文件 是以二进制存储,不具备可读性,但从存储空间的利用来看,实型数无论位数大小均占 4位, 字符确需按位数来存放,这样的话,二进制文件相对就节省了空间。 目前C语言使用的文件系统分为缓冲文件系统(标准 I / O)和非缓冲文件系统(系统 I / O)。 8.1 缓冲文件系统 缓冲文件系统的特点是:在内存开辟一个“缓冲区”,为程序中的每一个文件使用,当执 行读文件的操作时,从磁盘文件将数据先读入内存“缓冲区”,装满后再从内存“缓冲区”依 此读入接收的变量。执行写文件的操作时,先将数据写入内存“缓冲区”,待内存“缓冲区” 装满后再写入文件。由此可以看出,内存“缓冲区”的大小,影响着实际操作外存的次数, 内存“缓冲区”越大,则操作外存的次数就少,执行速度就快、效率高。一般来说,文件 “缓冲区”的大小随机器而定。 8.1.1 文件的打开与关闭 任何关于文件的操作都要先打开文件,再对文件进行读写,操作完毕后,要关闭文件。 1. 文件类型指针 人们在操作文件时,通常都关心文件的属性,如文件的名字、文件的性质、文件的当前 状态等。对缓冲文件系统来说,上述特性都是要仔细考虑的。 ANSI C为每个被使用的文件在 内存开辟一块用于存放上述信息的小区,利用一个结构体类型的变量存放。该变量的结构体 类型由系统取名为F I L E,在头文件s t d i o . h中定义如下: typedef struct{ i n t _ f d ; / *文件号* / i n t _ c l e f t ; / *缓冲区中的剩余字符* / i n t _ m o d e ; / *文件的操作模式* / c h a r * _ n e x t ; / *下一个字符的位置* / char *_buff; / *文件缓冲区的位置* /} FILE; 在操作文件以前,应先定义文件变量指针: FILE *fp1,fp2; 按照上面的定义,f p 1和f p 2均为指向结构体类型的指针变量,分别指向一个可操作的文件, 换句话说,一个文件有一个文件变量指针,今后对文件的访问,会转化为针对文件变量指针 的操作。 2. 文件的打开 ANSI C 提供了打开文件的函数: FILE *fopen(char *fname,char *mode) 函数原型在s t d i o . h文件中,f o p e n ( )打开一个f n a m e指向的外部文件,返回与它相连接的流。 f n a m e是字符串,应是一个合法的文件名,还可以指明文件路经。对文件的操作模式由 m o d e 决定,m o d e也是字符串,由表8 - 1给出m o d e的取值表。 表8-1 mode的取值表 Mode 含 义 r 打开一个文本文件只读 w 打开一个文本文件只写 a 打开一个文本文件在尾部追加 r b 打开一个只读的二进制文件 w b 打开一个只写的二进制文件 a b 对二进制文件追加 r + 打开一个可读 /写的文本文件 w + 创建一个新的可读/写的文本文件 a + 打开一个可读 /写的文本文件 r b + 打开一个可读 /写的二进制文件 w b + 创建一个新的可读/写的二进制文件 a b 打开一个可读 /写的二进制文件 如表8 - 1所示,文件的操作方式有文本文件和二进制文件两种,打开文件的正确方法如下 例所示: #include FILE *fp; If ((fp=fopen("test.txt","w"))==NULL) { /*创建一个只写的新文本文件* / printf("cannot open file \n"); e x i t ( 0 ) ; } 这种方法能发现打开文件时的错误。在开始写文件之前检查诸如文件是否有写保护,磁 盘是否已写满等,因为函数会返回一个空指针 N U L L,N U L L值在s t d i o . h中定义为0。事实上打 开文件是要向编译系统说明三个信息:①需要访问的外部文件是哪一个。②打开文件后要执 行读或写即选择操作方式。③确定哪一个文件指针指向该文件。对打开文件所选择的操作方 式来说,一经说明不能改变,除非关闭文件后重新打开。是只读就不能对其写操作,对已存 1 5 4 C语言程序设计 下载文件如以新文件方式打开,则信息必丢失。 3. 文件的关闭 ANSI C 提供了关闭文件的函数: int fclose(FILE *stream) f c l o s e ( )函数关闭与s t r e a m相连接的文件,并把它的缓冲区内容全部写出。在 f c l o s e ( )函数 调用以后,流s t r e a m与此文件无关,同时原自动分配的缓冲区也失去定位。 f c l o s e ( )函数关闭文件操作成功后,函数返回 0;失败则返回非零值。 [例8-1] 打开和关闭一个可读可写的二进制文件: #include m a i n ( ) { FILE *fp; If ((fp=fopen("test.dat","rb"))==NULL) { printf("cannot open file\n"); e x i t ( 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 ( )函数从输入流的当前位置返回一个字符,并将文件指针指示器移到下一个字符处, 如果已到文件尾,函数返回 E O F,此时表示本次操作结束,若读写文件完成,则应关闭文件。 int fputc(int ch,FILE *stream) f p u t c()函数完成将字符 c h的值写入所指定的流文件的当前位置处,并将文件指针后移 一位。fputc ()函数的返回值是所写入字符的值,出错时返回 E O F。 [例8-2] 将存放于磁盘的指定文本文件按读写字符方式逐个地从文件读出,然后再将其 显示到屏幕上。采用带参数的 m a i n ( ),指定的磁盘文件名由命令行方式通过键盘给定。 #include main( argc,argv) int argc; 第8章 输入、输出和文件系统 1 5 5下载char *argv[]; { char ch; FILE *fp; int i; i f ( ( f p = f o p e n ( a r g v [ 1 ] , " r " ) ) = = N U L L ) / *打开一个由a r g v [ 1 ] 所指的文件* / { printf("not open"); e x i t ( 0 ) ; } while ((ch=fgetc(fp))!=EOF) / *从文件读一字符,显示到屏幕* / p u t c h a r ( c h ) ; f c l o s e ( f p ) ; } 程序是一带参数的m a i n ( )函数,要求以命令行方式运行,其参数 a rg c是用于记录输入参数 的个数,a rg v是指针数组,用于存放输入参数的字符串,串的个数由 a rg c描述。假设我们指定 读取的文件名为 L 8 - 2 . c,并且列表文件内容就是源程序。经过编译和连接生成可执行的文件 L 8 - 2 . e x e。运行程序l 8 - 2 . e x e,输入的命令行方式为: c : \ t c > l8-2 L8-2.c ¿ 上述程序以命令行方式运行,其输入参数字符串有两个,即 a rg v [ 0 ] = " c : \ t c > l 8 - 2 "、 a rgv[1]=" L8-2.c ",a rg c = 2。故打开的文件是L8-2.c 。程序中对f g e t c ( )函数的返回值不断进行 测试,若读到文件尾部或读文件出错,都将返回 C的整型常量E O F,其值为非零有效整数。程 序的运行输出为源程序本身: c : \ t c > l8-2 L8-2.c ¿ #include main( argc,argv) int argc; char *argv[]; { char ch; FILE *fp; int i; i f ( ( f p = f o p e n ( a r g v [ 1 ] , " r " ) ) = = N U L L ) / *打开一个由a r g v [ 1 ] 所指的文件* / { printf("not open"); e x i t ( 0 ) ; } while ((ch=fgetc(fp))!=EOF) / *从文件读一字符,显示到屏幕* / p u t c h a r ( c h ) ; f c l o s e ( f p ) ; } [例8-3] 从键盘输入字符,存到磁盘文件 t e s t . t x t中: #include m a i n ( ) 1 5 6 C语言程序设计 下载{ FILE fp; / *定义文件变量指针* / char ch; i f ( ( f p = f o p e n ( " t e s t . t x t " , " w " ) ) = = N U L L ) / *以只写方式打开文件*/ { printf("cannot open file!\n"); e x i t ( 0 ) ; } while ((ch=fgetchar())!='\n') /*只要输入字符非回车符* / f p u t c ( c h , f p ) / *写入文件一个字符* / f c l o s e ( f p ) ; } 程序通过从键盘输入一以回车结束的字符串,写入指定的流文件 t e s t . t x t,文件以文本只写 方式打开,所以流文件具有可读性,能支持各种字符处理工具访问。简单地说,我们可以通 过D O S提供的t y p e命令来列表显示文件内容。 运行程序: R U N ¿ I love china!¿ 在D O S操作系统环境下,利用type 命令显示t e s t . t x t文件如下: c : \ t c > type test.txt¿ I love china! 2. 读写字符串 C提供读写字符串的函数原型在 s t d i o . h头文件中,其函数形式为: Char *fgets(char *str,int num,FILE *stream) fgets() 函数从流文件s t r e a m中读取至多n u m - 1个字符,并把它们放入s t r指向的字符数组中。 读取字符直到遇见回车符或 E O F(文件结束符)为止,或读入了所限定的字符数。 int fputs(char *str,FILE *stream) f p u t s ( )函数将s t r指向的字符串写入流文件。操作成功时,函数返回 0值,失败返回非零值。 [例8-4] 向磁盘写入字符串,并写入文本文件 t e s t . t x t: #include #include m a i n ( ) { FILE *fp; char str[128]; if ((fp=fopen("test.txt","w"))==NULL)/ *打开只写的文本文件* / { printf("cannot open file!"); e x i t ( 0 ) ; } w h i l e ( ( s t r l e n ( g e t s ( s t r ) ) ) ! = 0 ) { /*若串长度为零,则结束* / f p u t s ( s t r , f p ) ; / *写入串* / fputs("\n",fp); /*写入回车符* / 第8章 输入、输出和文件系统 1 5 7下载} fclose(fp); /*关文件* / } 运行该程序,从键盘输入长度不超过 1 2 7个字符的字符串,写入文件。如串长为 0,即空 串,程序结束。 输入:H e l l o !¿ How do you do¿ G o o d - b y e ! ¿ ¿ 运行结束后,我们利用d o s的t y p e命令列表文件: c : \ t c > type test.txt¿ H e l l o ! How do you do Good-bye! 这里所输入的空串,实际为一单独的回车符,其原因是 g e t s函数判断串的结束是以回车作 标志的。 [例8-5] 从一个文本文件t e s t 1 . t x t中读出字符串,再写入令一个文件 t e s t 2 . t x t。 #include #include m a i n ( ) { FILE *fp1,*fp2; char str[128]; if ((fp1=fopen("test1.txt","r"))==NULL) { / *以只读方式打开文件1 * / printf("cannot open file\n"); e x i t ( 0 ) ; } if ((fp2=fopen("test2.txt","w"))==NULL) { /*以只写方式打开文件2 * / printf("cannot open file\n"); e x i t ( 0 ) ; } while ((strlen(fgets(str,128,fp1)))>0) / *从文件中读回的字符串长度大于0 */ { fputs(str,fp2 ); / *从文件1读字符串并写入文件2 * / p r i n t f ( " % s " , s t r ) ; / *在屏幕显示* / } f c l o s e ( f p 1 ) ; f c l o s e ( f p 2 ) ; } 程序共操作两个文件,需定义两个文件变量指针,因此在操作文件以前,应将两个文件 以需要的工作方式同时打开(不分先后),读写完成后,再关闭文件。设计过程是按写入文件 1 5 8 C语言程序设计 下载的同时显示在屏幕上,故程序运行结束后,应看到增加了与原文件相同的文本文件并显示文 件内容在屏幕上。 3. 格式化的读写 前面的程序设计中,我们介绍过利用 s c a n f ( )和p r i n t f ( )函数从键盘格式化输入及在显示器 上进行格式化输出。对文件的格式化读写就是在上述函数的前面加一个字母 f成为f s c a n f ( )和 f p r i n t f ( )。其函数调用方式: int fscanf(FILE *stream,char *format,arg_list) int fprintf(FILE *stream,char *format,arg_list) 其中,s t r e a m为流文件指针,其余两个参数与 s c a n f ( )和p r i n t f ( )用法完全相同。 [例8-6] 将一些格式化的数据写入文本文件,再从该文件中以格式化方法读出显示到屏 幕上,其格式化数据是两个学生记录,包括姓名、学号、两科成绩。 #include m a i n ( ) { FILE *fp; int i; struct stu{ / *定义结构体类型* / char name[15]; char num[6]; float score[2]; } s t u d e n t ; / *说明结构体变量* / if ((fp=fopen("test1.txt","w"))==NULL) { / *以文本只写方式打开文件* / printf("cannot open file"); e x i t ( 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, s t u d e n t . s c o r e [ 0 ] , s t u d e n t . s c o r e [ 1 ] ) ; / * 写入文件* / } f c l o s e ( f p ) ; / *关闭文件* / if ((fp=fopen("test.txt","r"))==NULL) { /*以文本只读方式重新打开文件* / printf("cannot open file"); e x i t ( 0 ) ; } printf("output from file:\n"); while (fscanf(fp,"%s %s %f %f\n",student.name,student.num, & s t u d e n t . s c o r e [ 0 ] , s t u d e n t . s c o r e [ 1 ] ) ! = E O F ) / *从文件读入* / printf("%s %s %7.2f %7.2f\n",student.name,student.num, 第8章 输入、输出和文件系统 1 5 9下载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"); e x i t ( 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 (字 段长度)个字符长,并把它们放到b u f(缓冲区)指向的字符数组中。 fread ()函数返回实际已读取的字段数。若函数调用时要求读取的字段数超过文件存放 的字段数,则出错或已到文件尾,实际在操作时应注意检测。 f w r i t e ( )函数从b u f (缓冲区)指向的字符数组中,把 c o u n t (字段数)个字段写到s t r e a m所指向 的流中,每个字段为s i z e个字符长,函数操作成功时返回所写字段数。 关于成块的文件读写,在创建文件时只能以二进制文件格式创建。 [例8-7] 向磁盘写入格式化数据,再从该文件读出显示到屏幕。 #include "stdio.h" #include "stdlib.h" m a i n ( ) { FILE *fp1; 1 6 0 C语言程序设计 下载int i; struct stu{ / *定义结构体* / char name[15]; char num[6]; float score[2]; } s t u d e n t ; if ((fp1=fopen("test.txt","wb"))==NULL) { /*以二进制只写方式打开文件* / printf("cannot open file"); e x i t ( 0 ) ; } printf("input data:\n"); for( i=0;i<2;i++) { scanf("%s %s %f %f",student.name,student.num, & s t u d e n t . s c o r e [ 0 ] , & s t u d e n t . s c o r e [ 1 ] ) ; / * 输入一记录* / fwrite(&student,sizeof(student),1,fp1); /*成块写入文件* / } f c l o s e ( f p 1 ) ; if ((fp1=fopen("test.txt","rb"))==NULL) { /*重新以二进制只写打开文件* / printf("cannot open file"); e x i t ( 0 ) ; } printf("output from file:\n"); for (i=0;i<2;i++) { f r e a d ( & s t u d e n t , s i z e o f ( s t u d e n t ) , 1 , f p 1 ) ; / * 从文件成块读* / printf("%s %s %7.2f %7.2f\n",student.name,student.num, s t u d e n t . s c o r e [ 0 ] , s t u d e n t . s c o r e [ 1 ] ) ; / * 显示到屏幕* / } f c l o s e ( f p 1 ) ; } 运行程序: 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或 第8章 输入、输出和文件系统 1 6 1下载s t d l i b . h写在程序的前面。 [例8-8] 将输入的不同格式数据以字符串输入,然后将其转换进行文件的成块读写。 #include #include m a i n ( ) { FILE *fp1; char *temp; int i; struct stu{ / *定义结构体类型* / char name[15]; / *姓名* / char num[6]; / *学号* / float score[2]; / *二科成绩* / } s t u d e n t ; if ((fp1=fopen("test.txt","wb"))==NULL) / *打开文件* / { printf("cannot open file"); e x i t ( 0 ) ; } for( i=0;i<2;i++) { printf("input name:"); g e t s ( s t u d e n t . n a m e ) ; / *输入姓名* / printf("input num:"); g e t s ( s t u d e n t . n u m ) ; / *输入学号* / printf("input score1:"); g e t s ( t e m p ) ; / *输入成绩* / s t u d e n t . s c o r e [ 0 ] = a t o f ( t e m p ) ; printf("input score2:"); g e t s ( t e m p ) ; s t u d e n t . s c o r e [ 1 ] = a t o f ( t e m p ) ; f w r i t e ( & s t u d e n t , s i z e o f ( s t u d e n t ) , 1 , f p 1 ) ; / *成块写入到文件* / } f c l o s e ( f p 1 ) ; if ((fp1=fopen("test.txt","rb"))==NULL) { printf("cannot open file"); e x i t ( 0 ) ; } p r i n t f ( " - - - - - - - - - - - - - - - - - - - - - \ n " ) ; p r i n t f ( " % - 1 5 s % - 7 s % - 7 s % - 7 s \ n " , " n a m e " , " n u m " , " s c o r e 1 " , " s c o r e 2 " ) ; p r i n t f ( " - - - - - - - - - - - - - - - - - - - - - \ n " ) ; for (i=0;i<2;i++) { f r e a d ( & s t u d e n t , s i z e o f ( s t u d e n t ) , 1 , f p 1 ) ; p r i n t f ( " % - 1 5 s % - 7 s % 7 . 2 f % 7 . 2 f \ n " , s t u d e n t . n a m e , s t u d e n t . n u m , s t u d e n t . s c o r e [ 0 ] , s t u d e n t . s c o r e [ 1 ] ) ; 1 6 2 C语言程序设计 下载} f c l o s e ( f p 1 ) ; } 运行程序如下:R U N¿ input name:l i - y i n g input num: j 0 1 2 3 input score1:9 8 . 6 5 input score2:8 9 . 6 input name:l i - l i input num: j 0 1 2 4 input score1:6 8 . 6 5 input score2:8 6 . 6 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - n a m e n u m s c o r e 1 s c o r e 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - l i - y i n g j 0 1 2 3 9 8 . 6 5 8 9 . 6 0 l i - l i j 1 2 4 6 8 . 6 4 8 6 . 6 0 8.1.3 随机读写文件 随机对文件的读写是指在文件内部任意对文件内容进行访问,这也就需要对文件进行详 细的定位,只有定位准确,才有可能对文件随机访问。 C语言提供了用于文件定位的函数,它的作用是使文件指针移动到所需要的位置。 int fseek(FILE *fp,long d,int pos) f p是文件指针,d是位移量,p o s是起始点。 P o s的取值为: 0 :文件开始处 1 :文件的当前位置 2 :文件的尾部 位移量d是l o n g型的数据,可以为正或负值。表示从起始点向下或向上的指针移动。函数 的返回值若操作成功为0,操作失败为非零。 例如:f s e e k ( f p , 5 L , 0 );将文件指针从文件头向下移动 5个字节。 f s e e k ( f p , - 1 0 L , 2 );将文件指针从当前位置向上移动 1 0个字节。 rewind() 将文件指针移动到文件头。 ftell(FILE *fp) 返回文件指针的当前位置。 [例8-9] 写入5个学生记录,记录内容为学生姓名、学号、两科成绩。写入成功后,随机 读取第三条记录,并用第二条记录替换。 #include #include #define n 5 m a i n ( ) { FILE *fp1; / *定义文件指针* / 第8章 输入、输出和文件系统 1 6 3下载char *temp; int i,j; struct stu{ / *定义学生记录结构* / char name[15]; char num[6]; float score[2]; } s t u d e n t [ n ] ; if ((fp1=fopen("test.txt","wb"))==NULL) / *以二进制只写方式打开文件* / { printf("cannot open file"); e x i t ( 0 ) ; } for( i=0;i #include #include #include m a i n ( ) { FILE *fptr1,*fptr2,*fptr3; / *定义文件指针* / char temp[15],temp1[15],temp2[15]; if ((fptr1=fopen("addr.txt","r"))==NULL)/ *打开文件* / { printf("cannot open file"); e x i t ( 0 ) ; } if ((fptr2=fopen("tel.txt","r"))==NULL) { printf("cannot open file"); e x i t ( 0 ) ; } if ((fptr3=fopen("addrtel.txt","w"))==NULL) { printf("cannot open file"); e x i t ( 0 ) ; } c l r s c r ( ) ; / *清屏幕* / while(strlen(fgets(temp1,15,fptr1))>1) /*读回的姓名字段长度大于1 * / { f g e t s ( t e m p 2 , 1 5 , f p t r 1 ) ; / *读地址* / f p u t s ( t e m p 1 , f p t r 3 ) ; / *写入姓名到合并文件* / f p u t s ( t e m p 2 , f p t r 3 ) ; / *写入地址到合并文件* / s t r c p y ( t e m p , t e m p 1 ) ; / *保存姓名字段* / do /*查找姓名相同的记录* / { f g e t s ( t e m p 1 , 1 5 , f p t r 2 ) ; f g e t s ( t e m p 2 , 1 5 , f p t r 2 ) ; } while (strcmp(temp,temp1)!=0); r e w i n d ( f p t r 2 ) ; / *将文件指针移到文件头,以备下次查找* / f p u t s ( t e m p 2 , f p t r 3 ) ; / *将电话号码写入合并文件* / } f c l o s e ( f p t r 1 ) ; / *关闭文件* / f c l o s e ( f p t r 2 ) ; f c l o s e ( f p t r 3 ) ; } 1 6 8 C语言程序设计 下载程序运行后,我们来看一下合并后的文件 a d d r t e l . t x t的内容: type addrtel.txt¿ hejie tianjing 8 7 6 4 liying shanghai 1 2 3 4 5 liming chengdu 7 6 5 4 3 2 2 wangpin chongqing 8 7 6 4 3 第8章 输入、输出和文件系统 1 6 9下载下载 第9章 实用编程技巧 9.1 图形应用技巧 9.1.1 显示适配器类型的自动测试 目前P C机及兼容机的显示器及其适配器的类型非常多,有单色的,也有彩色的。这些显 示器及适配器的模式对应用程序来说是非常重要的。如何在程序中自动识别显示器的模式, 以便更好地使用当前的显示模式是每个微机应用程序开发者的一个重要课题。下面程序可以 方便测出当前显示器适配器的模式(有关具体知识,请参见其它相关的技术书籍)。 [例9-1] 测试显示适配器类型。 #include #include #define P(note) printf(note) #define PV(format,value) printf(format,v a l u e ) #define PM printf("mode is ") #define PD printf("\n\tdetected graphics drive is") void main( ) { int gdrive ,g e r r o r ,g m o d e ; d e t e c t g r a p h ( & g d r i v e ,& g m o d e ) ; /* 标准测试函数 * / i f ( g d r i v e < 0 ) {P("No graphics hardware detected !\n"); r e t u r n ; } switch (gdrive) {case 1: PD; P ( " C G A " ) ; s w i t c h ( g m o d e ) { case 0 : P M; P("CGAC0 320×2 0 0 " ) ; b r e a k ; case 1 : P M; P("CGAC1 320×2 0 0 " ) ; b r e a k ; case 2 : P M;第9章 实用编程技巧 1 7 1下载 P("CGAC2 320×2 0 0 " ) ; b r e a k ; case 3 : PM; P("CGAC3 640×2 0 0 " ) ; b r e a k ; case 4 : PM; P("CGAh4 320×2 0 0 " ) ; b r e a k ; } b r e a k ; case 2: PD; P ( " M C G A " ) ; s w i t c h ( g m o d e ) { case 0 : PM ; P("MCGAC0 320×2 0 0 " ) ; b r e a k ; case 1 : PM ; P("MCGAC1 320×2 0 0 " ) ; b r e a k ; case 2 : PM ; P("MCGAC2 320×2 0 0 " ) ; b r e a k ; case 3 : PM ; P("MCGAC3 320×2 0 0 " ) ; b r e a k ; case 4 : PM ; P("MCGAC4 620×2 0 0 " ) ; b r e a k ; case 5 : PM ; P("MCGAC5 620×4 8 0 " ) ; b r e a k ; } b r e a k ; case 3 : PD; P ( " E G A " ) ; s w i t c h ( g m o d e ) { case 0 :PM ; P("EGALO 640×2 0 0 " ) ; b r e a k ; case 1 :PM ; P("EGALO 640×3 5 0 " ) ; b r e a k ; } b r e a k ; case 4 :PD ; P ( " E G A 6 4 " ) ; s w i t c h ( g m o d e ) {case 0 :PM ; P("EGA64LO 640×2 0 0 " ) ; b r e a k ; case 1 : PM ; P("EGA64HI 640×3 5 0 " ) ; b r e a k ; } b r e a k ; case 5 :PD ; P ( " E G A M O N O " ) ; P M; P("EGAMONO 640×3 5 0 " ) ; b r e a k ; case 6 :PD ; P ( " I M B 8 6 1 4 " ) ; s w i t c h ( g m o d e ) {case 0 : PM ; P("IMB8514LO 640×4 8 0 " ) ;1 7 2 C语言程序设计 下载 b r e a k ; case 1 : PM ; P("IMB8514HI 1024×7 6 8 " ) ; b r e a k ; } b r e a k ; case 7 :PD ; P ( " H E R C M O N O " ) ; PM ; P("HERCMONO 720×3 4 8 " ) ; b r e a k ; case 8 :PD ; P ( " A T T 4 0 0 " ) ; s w i t c h ( g m o d e ) {case 0 : PM ; P("ATT400C0 320×2 0 0 " ) ; b r e a k ; case 1 : PM ; P("ATT400C1 320×2 0 0 " ) ; b r e a k ; case 2 : PM ; P("ATT400C2 320×2 0 0 " ) ; b r e a k ; case 3 : PM ; P("ATT400C3 320×2 0 0 " ) ; b r e a k ; case 4 : PM ; P("ATT400CMCD 640×4 0 0 " ) ; b r e a k ; } b r e a k ; case 9 :PD ; P ( " V G A " ) ; s w i t c h ( g m o d e ) {case 0 : PM ; P("VGALO 640×4 0 0 " ) ; b r e a k ; case 1 : PM ; P("VGALO 640×3 5 0 " ) ; b r e a k ; case 2 : PM ; P("VGALO 640×4 8 0 " ) ; b r e a k ; } b r e a k ; case 10 :PD ; P ( " P C 3 2 7 0 " ) ; PM ; P("PC3270HI 720×3 5 0 " ) ;第9章 实用编程技巧 1 7 3下载 b r e a k ; } P("\n\n\t\t\t THANK YOU !"); } 开发图形软件的基本方法 大家都知道,Turbo C 具有汇编语言那样直接控制系统硬件以及调用操作系统资源的功能, 同时又具有一般高级语言完成复杂运算的能力。因此, C语言已成为开发图形软件最理想的程 序语言。下面主要介绍几个生成基本图形的函数,它们是开发复杂图形软件的基础。 显示方式与色调函数 若要在屏幕上显示图形,首先要把屏幕设置为彩色图形显示方式,常用的方式是: mode 4 320×200 4 色 在这种显示方式下,可以使用两种不同的配色器来配置色调,见表 9 - 1。 表9-1 屏幕色调 配 色 器 号 颜 色 0 颜 色 1 颜 色 2 颜色 3 0 同底色 绿 红 黄 1 同底色 青 淡红 白 利用C语言标准库函数int86( ),可以方便地完成显示与色调的设置。这两个函数 s e t m o d e ( ) 和palet( ) 见程序T X . c。例如,要求设置4号显示模式和使用0号配色器时,调用形式如下: s e t m o d e ( 4 ); p a l e t ( 0 ); 画点函数和画线函数 在设置屏幕显示和色调后,就可使用各种绘图函数绘制不同颜色不同形状的图形。任何 图形都有是由点组成的,所以画点函数是其它函数的基础。 在屏幕上画一个点有两种方法:一是调用 D O S功能,二是直接存取显示缓冲区(视频 R A M)。由于后者有更高的执行速度,所以一般人都使用第二种方法。画点函数 p o i n t ( )中有三 个参数:x、y、c o l o r,其中x、y分别是显示点的行和列坐标, c o l o r是点的颜色,取值 0 ~ 3, 对应的颜色如表9 - 1所示。该函数根据彩色图形显示的原理,直接控制视频 R A M区。它通过指 针p t r访问该内存区域。指针初始化时, char far ptr=(char far *) 0×b 8 0 0 0 0 0 0 视频R A M的首地址赋予了指针p t r,从而通过该指针就可以直接存取视频 R A M的任何单元。 (针对不同的显示模式,此地址可能不同 )。 使用p o i n t ( )函数可以在屏幕的任意位置上显示指定颜色的一个点。例如在 2 0行1 5列上显 示一个红色点时,使用: p o i n t ( 2 0 ,1 5,2 ); 使用画点函数可以编写画直线的函数 l i n e ( ),其原理是已知直线的两个端点坐标时,用迭 代过程确定组成直线各点的位置。函数 l i n e ( )的参数为x 1,y 1,x 2,y 2,c o l o r。其中x 1、y 1和 x 2、y 2分别是直线的起点和终点坐标。 C o l o r是直线的颜色,取值 0 ~ 3。例如在屏幕的右斜对 角上画一条绿色直线,使用的格式为:l i n e ( 0 ,0,1 1 9 ,3 1 9 ,1 ); 矩形与填充函数 矩形是由四条直线组成的。使用直线函数可以矩形。绘制时,只需知道它左上角和右下 角的坐标,就可以用直线函数绘出它的四条边,矩形函数 b o x ( )的参数为 x 1、y 1、x 2、y 2、 c o l o r。其中x 1、y 1和x 2、y 2分别是矩形左上角和右下角的坐标, c o l o r是四条边的颜色。 矩形填充块,实际是在指定位置上画出具有相同长度和颜色的直线,其填充函数 f i l l b o x ( ) 中的参数与b o x ( )中的参数相同。 绘制图形 使用上述几个基本图形的函数,可以编写出在屏幕上绘制任意图形的程序。它使用键盘 上的箭头等功能键,实现显示位置和颜色的控制。为了获取键盘扫描代码和显示当前绘图位 置,要使用十字函数x h a i r ( )和获取键盘扫描代码函数g e t k e y ( )。 下面给出一个简单的绘图程序 t x . c。它相当于用画笔在屏幕上绘制图形,画笔的位置用十 字光标显示。画笔的移动由↓、→、↑、←四个键控制。用 H o m e、P g U p、P g D n和E n d键分 别控制十字光标向 4 5°方向移动。画笔的抬起落下由字母键 O控制,颜色由数字键 0~3控制。 功能键F 1用于设定单步前进,F 2用于设定5步前进。 该程序还可以画出矩形、填充矩形和直线。这时需要设置它们的坐标位置,直线需要两 个端点坐标,矩形需要两个对角的坐标。例如,在抬笔状态下,把十字光标移至第一个位置 后按回车键,然后再移至第二个位置按回车键。之后按下 L键时,则在设定的两个端点的位置 上画出一条直线;如果按B键,则以两个位置为对角画出矩形;如果按 F键,则画出填充矩形。 另外,用P键可改变色调,按Q键结束运行,返回到D O S状态。 [例9-2] 绘图程序t x . c # i n c l u d e < s t d i o . h > # i n c l u d e < d o s . h > # i n c l u d e < c t y p e . h > # i n c l u d e < c o n i o . h > # i n c l u d e < m a t h . h > void setmode(int); void palet(int); void point(int,i n t ,i n t ) ; void line(int,i n t ,i n t ,i n t ,i n t ) ; void box(int,i n t ,i n t ,i n t ,i n t ) ; void fillboX(int,i n t ,i n t ,i n t ,i n t ) ; void Xhair(int,i n t ) ; int getkey(void); void main() { u n i o n { char c[2]; int i; } k e y ; int X=10,y = 1 0 ,c c = 2 ,o n f l a g = 1 ,p a l n u m = 1 ; 1 7 4 C语言程序设计 下载第9章 实用编程技巧 1 7 5下载 i n t X 1 = 0 ,y 1 = 0 ,X 2 = 0 ,y 2 = 0 ,f i r s t p o i n t = 1 ; int d=1; s e t m o d e ( 4 ) ; p a l e t ( 0 ) ; X h a i r ( X ,y ); do { k e y . i = g e t k e y ( ) ; X h a i r ( X ,y ); i f ( ! k e y . c [ 0 ] ) s w i t c h ( k e y . c [ 1 ] ) { case 75: if(onflag) /*left*/ l i n e ( X ,y,X,y - d ,c c ) ; y - = d ; b r e a k ; case 77: if(onflag) /* right */ l i n e ( X ,y,X,y + d ,c c ) ; y + = d ; b r e a k ; case 72:if(onflag) /* up */ l i n e ( X ,y,X - d ,y,c c ) ; X - = d ; b r e a k ; case 80: if(onflag) /* down */ l i n e ( X ,y,X + d ,y,c c ) ; X + = d ; b r e a k ; case 71:if(onflag) /* Home-up left */ l i n e ( X ,y,X - d ,y + d ,c c ) ; X - = d ; X - = d ; b r e a k ; case 73:if(onflag) /*PgUp-up right */ l i n e ( X ,y,X - d ,y + d ,c c ) ; X - = d ; y + = d ; b r e a k ; case 79:if(onflag) /*End-down left */ l i n e ( X ,y,X + d ,y - d ,c c ) ; X + = d ; y - = d ; b r e a k ; case 81:if(onflag) /* PgUp-down right */ l i n e ( X ,y,X + d ,y + d ,c c ) ; X + = d ; y + = d ; b r e a k ; case 59: /*F1*/ d = 1 ; b r e a k ;case 60: /*F2*/ d = 5 ; b r e a k ; } e l s e s w i t c h ( t o l o w e r ( k e y . c [ 0 ] ) ) { case 'o': /* brush on-off */ o n f l a g = ! o n f l a g ; b r e a k ; case '1': /* color 1*/ c c = 1 ; b r e a k ; case '2': /* color2 */ c c = 2 ; b r e a k ; case '3': /* color 3*/ c c = 3 ; b r e a k ; case '0': /* color 0*/ c c = 0 ; b r e a k ; case 'b': /* set boX */ b o X ( X 1 ,y 1,X 2,y 2,c c ) ; b r e a k ; case 'f': /*set fill boX */ f i l l b o X ( X 1 ,y 1,X 2,y 2,c c ) ; b r e a k ; case 'l': /* set line */ l i n e ( X 1 ,y 1,X 2,y 2,c c ) ; b r e a k ; case 'r': /*set endpoint */ i f ( f i r s t p o i n t ) { X 1 = X ; y 1 = y ; } else { X 2 = X ; y 2 = y ; } firstpoint = !firstpoint; b r e a k ; case 'p': /* set color */ palnum = palnum==1 ? 2 : 1; p a l e t ( p a l n u m ) ; b r e a k ; } X h a i r ( X ,y ); } w h i l e ( k e y . c [ 0 ] ! = ' q ' ) ; g e t c h ( ) ; 1 7 6 C语言程序设计 下载s e t m o d e ( 2 ) ; } / *设置显示方式 * / void setmode(mode) int mode; { union REGS regs; r e g s . h . a l = m o d e ; r e g s . h . a h = 0 ; i n t 8 6 ( 0 X 1 0 ,& r e g s ,& r e g s ) ; } / *设置色调* / void palet(pn) int pn; { union REGS regs; r e g s . h . b h = 1 ; r e g s . h . b l = p n ; r e g s . h . a h = 1 1 ; i n t 8 6 ( 0 X 1 0 ,& r e g s ,& r e g s ) ; } / *画点函数* / void point(X,y,c o l o r ) int X,y,c o l o r ; { union { char cc[2]; int i; } m a s k ; int i,i n d e X ,p o s i t ; unsigned char t; char Xor; char far *ptr=(char far *)0Xb8000000; m a s k . i = 0 X f f 3 f ; i f ( X < 0 | | X > 1 9 9 | | y < 0 | | y > 3 1 9 ) r e t u r n ; X o r = c o l o r & 1 2 8 ; c o l o r = c o l o r & 1 2 7 ; p o s i t = y % 4 ; c o l o r < < = 2 * ( 3 - p o s i t ) ; m a s k . i > > = 2 * p o s i t ; i n d e X = X * 4 0 + ( y / 4 ) ; i f ( X % 2 ) i n d e X + = 8 1 5 2 ; if(! Xor){ t = * ( p t r + i n d e X ) & m a s k . c c [ 0 ] ; * ( p t r + i n d e X ) = t | c o l o r ; } e l s e { t = * ( p t r + i n d e X ) | ( c h a r ) 0 ; 第9章 实用编程技巧 1 7 7下载* ( p t r + i n d e X ) = t ^ c o l o r ; } } / *直线函数* / void line (X1,y 1,X 2,y 2,c o l o r ) int X1,y 1,X 2,y 2,c o l o r ; { register int t,d i s ; int Xerr=0,y e r r = 0 ,d X,d y; int incX,i n c y ; d X = X 2 - X 1 ; d y = y 2 - y 1 ; if (dX>0)incX=1; else if(dX==0)incX=0; else incX=-1; i f ( d y > 0 ) i n c y = 1 ; else if(dy==0)incy=0; else incy=-1; d X = a b s ( d y ) ; d y = a b s ( d y ) ; for (t=0;t < = d i s + 1 ;t + + ) { p o i n t ( X 1 ,y 1,c o l o r ) ; X e r r + = d X ; y e r r + = d y ; i f ( X e r r > d i s ) { X e r r - = d i s ; X 1 + = i n c X ; } i f ( y e r r > d i s ) { y e r r - = d i s ; y 1 + = i n c y ; } } } / *矩形函数* / void boX(X1,y 1,X 2,y 2,c o l o r ) int X1,y 1,X 2,y 2,c o l o r ; { line (X1,y 1,X 2,y 1,c o l o r ) ; l i n e ( X 1 ,y 1,X 2,y 2,c o l o r ) ; l i n e ( X 1 ,y 2,X 2,y 2,c o l o r ) ; l i n e ( X 2 ,y 1,X 2,y 2,c o l o r ) ; } / *矩形填充函数* / void fillboX(X1,y 1,X 2,y 2,c o l o r ) int X1,y 1,X 2,y 2,c o l o r ; { register int i,b e g i n ,e n d ; begin=X1X2? X1:X2; for (i=begin;i < = e n d ;i + + ) l i n e ( i ,y 1,i,y 2,c o l o r ) ; } / *十字光标定位函数* / void Xhair(X,y ) int X,y; { l i n e ( X - 4 ,y,X + 3 ,y,1 | 1 2 8 ) ; l i n e ( X ,y + 4 ,X,y - 3 ,1 | 1 2 8 ) ; } / *获取键盘扫描码函数* / int getkey() { union REGS regs; r e g s . h . a h = 0 ; return int86(0X16,& r e g s ,& r e g s ) ; } 9.1.2 屏幕图像的存取技巧 Turbo C提供了丰富的图形操作函数,利用这些函数可以很容易编写图形和图像处理程序, 但是Turbo C没有提供屏幕图像存储和恢复的函数,而在许多情况下需要将屏幕上的图像全部 或部分的以文件的形式保存在磁盘上,在需要时快速地从磁盘调入内存并重现在屏幕上。下 面介绍的程序就是用来对屏幕上任一块矩形区域进行存取的程序 t x 1 . c。saveimage( )函数首先 将屏幕上的一块矩形区域图像的数据写入内存某地址,然后创建一个二进制文件,并把该地 址的数据写入此文件中。这样屏幕上的图像就以文件的形式存储在磁盘上了。 loadimage( )函 数首先将磁盘上的图像数据文件打开并读入到内存某地址,然后从该地址将这些数据读到视 屏缓冲区。这样,原先保存的图像就重现在屏幕上了。 t x 1 . c程序首先测试硬件的图形适配卡类型,并根据其值进入相应的图形方式,然后在屏 幕上画一个彩色饼形统计图,并将该图像存入文件 g r a p h . d a t中,最后打开g r a p h . d a t文件并读入 内存,将这幅图像重现在屏幕上。 [例9-3] 存取任意屏幕图像程序t x 1 . c /* 存取任意屏幕图像头文件tX1.h */ # i n c l u d e < s t d i o . h > # i n c l u d e < g r a p h i c s . h > # i n c l u d e < c o n i o . h > # i n c l u d e < f c n t l . h > # i n c l u d e < a l l o c . h > # i n c l u d e < s t d l i b . h > # i n c l u d e < i o . h > void saveimage(X1,y 1,X 2,y 2,f ) int X1,y 1,X 2,y 2; char *f; 第9章 实用编程技巧 1 7 9下载{ char *ptr; unsigned size; FILE *fn; s i z e = i m a g e s i z e ( X 1 ,y 1,X 2,y 2 ) ; p t r = m a l l o c ( s i z e ) ; g e t i m a g e ( X 1 ,y 1,X 2,y 2,p t r ) ; if ((fn=fopen(f,"wb"))==NULL) { r e s t o r e c r t m o d e ( ) ; printf("Cannot create file %s\n",f ); e X i t ( 1 ) ; } f w r i t e ( p t r ,s i z e ,1,f n ) ; f c l o s e ( f n ) ; f r e e ( p t r ) ; } void loadimage(X1,y 1,f ) int X1,y 1; char *f; { char *ptr; unsigned size; FILE *fn; i f ( ( f n = f o p e n ( f ," r b " ) ) = = N U L L ) { r e s t o r e c r t m o d e ( ) ; printf("Cannot open file %s\n",f ); e X i t ( 1 ) ; } size = 0; while(fgetc(fn) != EOF) size++; p t r = m a l l o c ( s i z e ) ; r e w i n d ( f n ) ; f r e a d ( p t r ,s i z e ,1,f n ) ; f c l o s e ( f n ) ; p u t i m a g e ( X 1 ,y 1,p t r ,C O P Y _ P U T ) ; f r e e ( p t r ) ; } /* 图像存取示例* / void initialize(void); void quit(void); int X,y; char *f="graph.dat"; void main(void) { i n i t i a l i z e ( ) ; o u t t e X t X y ( X / 2 ,- 1 0 ,"Saving Image"); 1 8 0 C语言程序设计 下载s a v e i m a g e ( 0 ,0,X,y,f ); o u t t e X t X y ( X + 5 0 ,y + y / 2 + 1 0 ,"Loading Image"); l o a d i m a g e ( X ,y / 2 ,f ); quit( ); } void initialize( ) { int gdrive=DETECT,g m o d e ,e r r o r c o d e ; int angle=360/MAXCOLORS,c o l o r ; i n i t g r a p h ( & g d r i v e ,& g m o d e , " " ) ; errorcode=graphresult( ); if (errorcode!=grOk) { printf("graphics error:%s\n",g r a p h e r r o r m s g ( e r r o r c o d e ) ) ; printf("press any key to halt:"); getch( ); e X i t ( 1 ) ; } X=getmaXX() *2/5; y=getmaXy( ) *2/5; s e t v i e w p o r t ( X / 2 ,y / 2 ,getmaXX( )-X/2,getmaXy( )-y/2,0 ); setteXtjustify( CENTER_TEXT,C E N T E R _ T E X T ) ; r e c t a n g l e ( 0 ,0,X,y ); for (color=0;c o l o r < M A X C O L O R S ;c o l o r + + ) { s e t f i l l s t y l e ( S O L I D _ F I L L ,c o l o r ) ; p i e s l i c e ( X / 2 ,y / 2 ,c o l o r * a n g l e ,( c o l o r + 1 ) * a n g l e ,y / 3 ) ; } } void quit( ) { getch( ); closegraph( ); } 9.1.3 屏幕显示格式的控制方法 一个良好的屏幕格式能给操作者提供很大的方便,也给人们一种赏心悦目的感觉。 为了控制屏幕显示格式,需要编写两个屏幕控制。我们可以借助这两个函数,设计出用 户所需要的屏幕显示格式。 下面是一个简单的演示程序。程序中借助这两个函数,在屏幕中间显示变动的数字。 [例9-4] 控制显示格式 tx3.c # include # include # i n c l u d e < d o s . h > 第9章 实用编程技巧 1 8 1下载1 8 2 C语言程序设计 下载 void cls(int); void gotoXy(int,i n t ) ; void cls(line) int line; { union REGS in,o u t ; i n . X . a X = 0 6 0 0 ; i n . X . c X = 0 0 0 0 ; i n . h . d h = l i n e - 1 ; i n . h . d l = 7 9 ; i n . h . b h = 0 7 ; i n t 8 6 ( 0 X 1 0 ,& i n ,& o u t ) ; } void gotoXy (X,y ) int X,y; { union REGS in,o u t ; i n . h . d h = X ; i n . h . d l = y ; i n . h . a h = 0 2 ; i n . h . b h = 0 ; int86 (0X10,& i n ,& o u t ) ; } void main( ) { int i; g o t o X y ( 0 ,0 ); c l s ( 1 1 ) ; g o t o X y ( 4 ,2 0 ) ; p r i n t f ( " - - - - - - - - - - - - - - - - - - - - - - - - " ) ; g o t o X y ( 5 ,2 0 ) ; printf("|proceeding record No: |"); g o t o X y ( 6 ,2 0 ) ; p r i n t f ( " - - - - - - - - - - - - - - - - - - - - - - - - " ) ; for (i=1;i < = 1 0 0 0 ;i + + ) { g o t o X y ( 5 ,4 5 ) ; p r i n t f ( " % 4 d " ,i ); } } 9.1.4 使图形软件脱离BGI的方法 大家知道,用 Tu r b o编译的图形软件,在运行时,当前目录下必须要有相应的 B G I文件。 例如C G A . B G I、E G AV G A . B G I等。这对于应用程序是不太方便的。为了解决这个问题,可使 用以下方法。 1 ) 用Turbo C提供的B T I O B J . E X E把* . B G I编译成目标文件* . O B J。例如:第9章 实用编程技巧 1 8 3下载 C> BGIOBJ CGA 这样就产生了一个 C G A . O B J。同样,将E G AV G A . B G I进行编译,再把这些 * . O B J拷入T C 目录下的LIB 子目录中。 使用时,先编译一个 p r o j e c t 文件,把需要的 O B J文件列入 p r o j e c t文件中。例如对 BGIDEMO .C,相应B G I D E M O . P R J可写为: B G I D E M O . C E G A V G A . O B J 这样在集成环境下调试B G I D E M O . C时,当前目录下不需要有E G AV G A . B G I,最后生成的 E X E文件,运行时也不需要E G AV G A . B G I。 2) 对命令行编译T C C . E X E只要在编译时列入相应的E G AV G A . B G I,最后生成的E X E文件。 运行时也不需要E G AV G A . B G I的E X E文件。 3) 对于经常使用T C C . E X E的用户,可用Turbo C提供的T L I B . E X E将上述* . o b j扩充进图形 库G R A P H I C S . L I B。方法为: C> TLIB GRAPHICS.LIB +EGAVGA.OBJ 这样,以后在编译时,只要联贯新的 G R A P H I C S . L I B就可编译出不需要B G I的图形软件。 9.1.5 拷贝屏幕图形的方法 在图形方式下,有时需将屏幕信息在打印机上输出。为了输出屏幕图形,要有一个内存 驻留程序。这时要考虑内存驻留程序的激活问题,激活时机的控制以及 T S R的初始化问题。 所有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时,T S R程序才允许激活。 实现时,使用了i n t e r r u p t类型说明、寄存器伪变量和程序终止并驻留的技术。 首先由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] 屏幕图形硬拷贝程序t x 4 . c #include # i n c l u d e < s t d i o . h > # i n c l u d e < g r a p h i c s . h > # i n c l u d e < b i o s . h > #define STK_SIZE 0×1 0 0 01 8 4 C语言程序设计 下载 #define print(ch) biosprint(0,c h,0 ) void set_graphics(int cols); void print_scr(int X1,int y1,int X2,int y2); char video_mode(void); void interrupt new_int5(void); void interrupt (*old_int5)(); unsigned char stack [STK_SIZE]; unsigned sp,s s; m a i n ( ) { union REGS r; struct SREGS s; o l d _ i n t 5 = g e t v e c t ( 5 ) ; k e e p ( 0 ,2 0 0 0 ) ; return 0; } void interrupt new_int(void) { char vmode; v m o d e = v i d e o _ m o d e ( ) ; i f ( ( v m o d e = = 2 ) | ( v m o d e = = 3 ) | ( v m o d e = = 7 ) ) s e t v e c t ( 5 ,o l d _ i n t 5 ) ; e l s e { d i s a b l e ( ) ; s s = _ S S ; s p = _ S P ; _ S S = _ D S ; _ S P = ( u n s i g n e d ) & s t a c k [ S T K _ S I Z E - 2 ] ; enable( ); p r i n t _ s c r ( 0 ,0,6 3 9 ,3 4 9 ) ; d i s a b l e ( ) ; _ S P = s p ; _ S S = s s ; enable( );} } void print_scr(int X1,int y1,int X2,int y2) { register int i,X,y,p X; int cols,c o l o r ,s u m ; X 2 + + ; y 2 + + ; c o l s = X 2 - X 1 ; f o r ( y = y 1 ;y < y 2 ;y + = 8 ) { s e t _ g r a p h i c s ( c o l s ) ; f o r ( X = X 1 ;X < = X 2 ;X + + ) { s u m = 0 ; f o r ( i = 0 ;i < 8 ;i + + ) {第9章 实用编程技巧 1 8 5下载 i f ( y + i < y 2 ) { c o l o r = g e t p i X e l ( X ,y + i ) ; i f ( c o l o r ) s u m + = 1 < < ( 7 - i ) ; } } p r i n t ( s u m ) ; } p r i n t f ( " \ n " ) ; } } void set_graphics(int cols) { char den_code; union aa{ unsigned char c[2]; unsigned int i; } u; u . i = c o l s ; p r i n t ( 2 7 ) ; p r i n t ( 6 5 ) ; p r i n t ( 8 ) ; p r i n t ( 2 7 ) ; p r i n t ( 7 6 ) ; p r i n t ( u . c [ 0 ] ) ; p r i n t ( u . c [ 1 ] ) ; } char video_mode(void) { union REGS r; r . h . a h = 1 5 ; return int86(0×1 0,& r,& r ) & 2 5 5 ; } 9.1.6 随意改变VGA显示器显示颜色的技巧 V G A显示适配器是一种使用很普遍的高性能图形适配器,最多有 2 6 1 2 4 4(6 4×6 4×6 4) 种颜色,可同时使用其中的任意种。但是目前来说,普遍使用的仍是非曲直 6种颜色的显示模 式。 若仅仅用现有的 1 6种颜色编制图形软件或窗口软件,画面则显得单调,能否根据需要自 由设置这 1 6种颜色呢?答案是肯定的。在显示器上某一色号所显示的颜色仅由显示卡上的 D A C颜色寄存器中的值决定。 D A C颜色寄存器是一个 1 8位的寄存器,红、绿、蓝各占六位, 卡上共有2 5 6个这样的寄存器,分别对应于 2 5 6个色号。开机时的 1 6种颜色(即0 ~ 1 5号)被设 置成如下寄存器及比色: 色号 对应寄存器号码 红 绿 蓝 0 0 0 0 01 8 6 C语言程序设计 下载 1 1 0 0 4 2 2 2 0 4 2 0 3 3 0 4 2 4 2 4 4 4 2 0 0 5 5 4 2 0 4 2 6 2 0 4 2 2 1 0 7 7 4 2 4 2 4 2 8 5 6 2 1 2 1 2 1 9 5 7 2 1 2 1 6 3 1 0 5 8 2 1 6 3 2 1 11 5 9 2 1 6 3 6 3 1 2 6 0 6 3 2 1 2 1 1 3 6 1 6 3 2 1 6 3 1 4 6 2 6 3 6 3 2 1 1 5 6 3 6 3 6 3 6 3 如果改变这1 6个寄存器中的值,即可改变在屏幕上显示的 1 6种颜色,而对程序运行没有 任何其它影响。 具体实现可以通过调用 VGA BIOS中断进行,也可以通过 V G A寄存器编程实现。在西文 方式下以上两种方法都可以使用,但在中文系统下,由于中文系统修改了视频中断 1 0 H,因此 只能通过V G A寄存器编程实现。 下面两个程序,一个用于设置颜色,另一个用于检查设置。设置的颜色可从 2 6 1 2 4 4种颜 色中任意设定0 ~ 1 5号颜色。在程序中: r e d,g r e e n,blue 取值为0 ~ 6 3;c o l o r n u m为要改变颜 色所对应的寄存器号。使用的格式为: setcolor <寄存器中> <红> <绿> <蓝> getcolor <寄存器号> 例如,对亮绿色(1 0号)进行改色,可在D O S提示符下,键入: setcolor 58 35 25 15 回车后,1 0号码色将成为由红色 3 5、绿色2 5、蓝色1 5、调成的新颜色。若要检查 1 0号的 设置,可在D O S提示符下,键入: getcolor 58 回车后,屏幕上将出现寄存器号、红、绿、蓝的颜色值。 [例9-6] 设置新色彩s e t c o l o r. c。 /* 格式:s e t c o l o r < 寄存器中>< 红 > < 绿 > < 蓝 > * / # i n c l u d e < d o s . h > # i n c l u d e < s t d l i b . h > # i n c l u d e < s t d i o . h > void main(int argc,char *argv[]) { int colornum,r e a d 0 ,g r e e n 0 ,b l u e 0 ; union REGS r;i f ( a r g c < 5 ) { printf("input error!\n"); e X i t ( 1 ) ; } c o l o r n u m = a t o i ( a r g v [ 1 ] ) ; r e a d 0 = a t o i ( a r g v [ 2 ] ) ; g r e e n 0 = a t o i ( a r g v [ 3 ] ) ; b l u e 0 = a t o i ( a r g v [ 4 ] ) ; o u t p o r t b ( 0 ×3 c 8 ,c o l o r n u m ) ; o u t p o r t b ( 0 ×3 c 9 ,r e a d 0 ) ; o u t p o r t b ( 0 ×3 c 9 ,g r e e n 0 ) ; o u t p o r t b ( 0 ×3 c 9 ,b l u e 0 ) ; } /* 检查颜色设置 g e t c o l o r . c * / /* 格式:g e t c o l o r < 寄存器号> * / # i n c l u d e < d o s . h > # i n c l u d e < s t d i o . h > # i n c l u d e < s t d l i b . h > main(int argc,char *argv[ ] ) { int colornum,r e a d 0 ,g r e e n 0 ,b l u e 0 ; union REGS r; i f ( a r g c < 2 ) { printf("input error!\n"); e X i t ( 1 ) ; } c o l o r n u m = a t o i ( a r g v [ 1 ] ) ; outportb (0×3 c 7 ,c o l o r n u m ) ; r e a d 0 = i n p o r t b ( 0 ×3 c 9 ) ; g r e e n 0 = i n p o r t b ( 0 ×3 c 9 ) ; b l u e 0 = i n p o r t b ( 0 ×3 c 9 ) ; p r i n t f ( " N o = % d ,r e a d = % d ,g r e e n = % d ,b l u e = % d \ n " ,c o l o r n u m ,r e a d 0 ,g r e e n 0 , b l u e 0 ) ; } 9.1.7 用随机函数实现动画的技巧 在一些特殊的C语言动画技术中,可以利用随机函数 int random(int num ) 取一个0 ~ n u m范 围内的随机数,经过某种运算后,再利用 C 语言的作图语句产生各种大小不同的图形,也能 产生很强的移动感。 程序d h 1 . c就是利用随机函数来产生动画应用。该程序运行后,屏幕中间绘出一台微型计 算机,微机下方同时显示“ c o m p u t e r”的放大字形,在画出微机的小屏幕内,产生各种大小 不同、颜色各异的矩形,这些矩形互相覆盖,给人以极强的动画感。 第9章 实用编程技巧 1 8 7下载程序中改变x 1、x 2、y 1、y 2的值,能将图形移动屏幕的任何位置,改变 x、y的值,能将 图形放大或缩小。 [例9-7] 动画显示程序DH1.C # i n c l u d e < c o n i o . h > # i n c l u ] d e < s t d i o . h > # i n c l u d e < s t d l i b . h > # i n c l u d e < g r a p h i c s . h > # i n c l u d e < t i m e . h > #define X1 260 #define X2 320 #define y1 140 #define y2 180 #define Xy 16 int gdrive,g m o d e ,m c o l o r ,e c o d e ; struct palettetype palette; void initialize(void); void rbars(void); int main( ) { initialize( ); / *初始化图形系统* / /* 显示放大字体* / s e t c o l o r ( Y E L L O W ) ; s e t t e x t s t y l e ( T R I P L E X _ F O N T ,H O R I Z _ D I R ,4 ); s e t t e x t j u s t i f y ( C E N T E R _ T E X T ,C E N T E R _ T E X T ) ; outtextxy((getmaXX( )/2-17),3 6 0 ," C O M P U T E R " ) ; rbars( ); / *主程序* / closegraph( );/ *关闭图形系统* / return 1; } void initialize(void) { g d r i v e = D E T E C T ; initgraph (&gdrive,& g m o d e ," " ) ; ecode=graphresult( ); if (ecode!=0) { printf("Graphice Error : %d\n ",g r a p h e r r o r m s g ( e c o d e ) ) ; e X i t ( 1 ) ; } g e t p a l e t t e ( & p a l e t t e ) ; mcolor=getmaXcolor( )+1; } void rbars(void) { int color ; /* 画计算机图形* / s e t c o l o r ( D A R K G R A Y ) ; 1 8 8 C语言程序设计 下载第9章 实用编程技巧 1 8 9下载 b a r 3 d ( X 1 - 2 0 ,y 1 - 2 0 ,X 2 + 5 6 ,y 2 + 7 0 ,0,3 ); s e t f i l l s t y l e ( C L O S E _ D O T _ F I L L ,B L U E ) ; s e t f i l l s t y l e ( S O L I D _ F I L L ,R E D ) ; c i r c l e ( X 2 + 2 8 ,y 2 + 6 0 ,4 ); b a r ( X 1 + 4 ,y 1 + 7 8 ,X 1 + 2 0 ,y 1 + 8 3 ) ; s e t c o l o r ( M A G E N T A ) ; c i r c l e ( X 2 + 2 8 ,y 2 + 6 0 ,4 ); c i r c l e ( X 2 + 1 6 ,y 2 + 6 0 ,4 ); c i r c l e ( X 2 + 4 ,y 2 + 6 0 ,4 ); s e t c o l o r ( W H I T E ) ; s e t f i l l s t y l e ( S O L I D _ F I L L ,D A R K G R A Y ) ; b a r 3 d ( X 1 - 6 0 ,y 1 + 1 2 0 ,X 1 + 1 5 4 ,y 1 + 1 7 0 ,0,2 ); b a r 3 d ( X 1 + 1 2 0 ,y 1 + 1 2 6 ,X 1 + 1 0 0 ,y 1 + 1 6 4 ,0,2 ); line (X1+20,y 1 + 1 4 5 ,X 1 + 1 0 0 ,y 1 + 1 4 5 ) ; s e t f i l l s t y l e ( S O L I D _ F I L L ,G R E E N ) ; b a r ( X 1 + 2 6 ,y 1 + 1 3 0 ,X 1 + 3 4 ,y 1 + 1 3 2 ) ; b a r ( X 1 + 2 6 ,y 1 + 1 5 0 ,X 1 + 3 4 ,y 1 + 1 5 2 ) ; s e t f i l l s t y l e ( W I D E _ D O T _ F I L L ,R E D ) ; b a r ( X 1 - 2 4 ,y 1 + 1 2 8 ,X 1 - 4 4 ,y 1 + 1 4 2 ) ; / *利用随机函数实现矩形画面互相覆盖,产生动感 * / while(!kbhit( )) { c o l o r = r a n d o m ( m c o l o r - 1 ) + 1 ; s e t c o l o r ( c o l o r ) ; s e t f i l l s t y l e ( r a n d o m ( 1 1 ) + 1 ,c o l o r ) ; bar3d(X1+random(getmaXX( )/Xy),y1+random(getmaXy( )/Xy), X2+getmaXX( )/Xy,y2+ getmaXy( )/Xy,0,5 ); } } 9.1.8 用putimage 函数实现动画的技巧 计算机图形动画显示的是由一系列静止图像在不同位置上的重现。计算机图形动画技术 一般分为画擦法和覆盖刷新法两大类。画擦法是先画 T时刻的图形,然后在 T +△T时刻把它擦 掉,改画新时刻的图形是由点、线、圆等基本图元组成。这种一画一擦的方法对于实现简单 图形的动态显示是比较有效的。而当需要显示比较复杂的图形时,由于画擦图形时间相对较 长,致使画面在移动时出现局部闪烁现象,使得动画视觉效果变差。所以,为提高图形的动 态显示效果,在显示比较复杂的图形时多采用覆盖刷新的方法。 在Turbo C 的图形函数中,有几个函数可完成动画的显示: getimage(int left,int top,int right,int bottom,void far*buf) 函数把屏幕图形部分拷贝 到由b u f所指向的内存区域。 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项,在下一个要显示的位置上于屏幕中重写图像,如此重复、交替地显示下去,1 9 0 C语言程序设计 下载 即可达到覆盖刷新的目的,从而实现动画显示。由于图形是一次性覆盖到显示区的,并在瞬 间完成,其动态特性十分平滑,动画效果较好。 程序d h 2 . c就是根据上述思路而实现的。程序运行时,将在屏幕上出现一个跳动的红色小 球。 [例9-8] 动画显示程序d h 2 . c #include # i n c l u d e < g r a p h i c s . h > # i n c l u d e < a l l o c . h > # i n c l u d e < c o n i o . h > void main(void) { int driver=DETECT,m o d e ; int k=0,i,m,m 1; int maXX,m a y y ,s i z e ; char *buf; i n i t g r a p h ( & d r i v e r ,& m o d e ," "); m a X X = g e t m a X X ( ) ; m a y y = g e t m a X y ( ) ; s e t f i l l s t y l e ( S O L I D _ F I L L ,L I G H T G R A Y ) ; b a r ( 1 ,1,m a X X ,m a y y ) ; s e t c o l o r ( R E D ) ; f o r ( i = 0 ;i < = 1 0 ;i + + ) c i r c l e ( 1 5 0 ,1 5 0 ,i ); s i z e = i m a g e s i z e ( 1 0 0 ,1 0 0 ,2 5 0 ,2 0 0 ) ; if(size != -1) b u f = m a l l o c ( s i z e ) ; i f ( b u f ) { g e t i m a g e ( 1 0 0 ,1 0 0 ,2 5 0 ,2 0 0 ,b u f ) ; m = 1 2 0 ;m 1 = m ; d o { k = k + 1 ; if ((m1+100)>mayy) { f o r ( m = m + 3 0 ;m < m a X X ;m = m + 3 0 ) { m 1 = m 1 - 2 0 ; p u t i m a g e ( m ,m 1,b u f ,C O P Y _ P U T ) ; } } i f ( ( m + 1 0 0 ) > m a X X ) { m = m - 1 0 0 ; f o r ( m 1 = m 1 + 1 0 0 ;m 1 > = 1 ;m 1 = m 1 - 1 0 ) {m 1 = m 1 - 1 9 ; p u t i m a g e ( m ,m 1,b u f ,C O P Y _ P U T ) ; } f o r ( m = m ;m > 1 ;m = m - 3 0 ) { m 1 = m 1 - 1 7 ; p u t i m a g e ( m ,m 1,b u f ,C O P Y _ P U T ) ; } } m 1 = m 1 + 2 0 ; m = m + 2 0 ; p u t i m a g e ( m ,m 1,b u f ,C O P Y _ P U T ) ; } w h i l e ( k ! = 1 0 0 0 ) ; g e t c h ( ) ; } r e s t o r e c r t m o d e ( ) ; } 9.2 菜单设计技术 菜单在用户编写的程序中占据相当一部分内容。设计一个高质量的菜单,不仅能使系统 美观,更主要的是能够使操作者使用方便,避免一些误操作带来的严重后果。 9.2.1 下拉式菜单的设计 下拉式菜单是一个窗口菜单,它具有一个主菜单,其中包括几个选择项,主菜单的每一 项又可以分为下一级菜单,这样逐级下分,用一个个窗口的形式弹出在屏幕上,一旦操作完 毕又可以从屏幕上消失,并恢复原来的屏幕状态。 设计下拉式菜单的关键就是在下级菜单窗口弹出之前,要将被该窗口占用的屏幕区域保 存起来,然后产生这一级菜单窗口,并可用光标键选择菜单中各项,用回车键来确认。如果 某选择项还有下级菜单,则按同样的方法再产生下一级菜单窗口。 用Turbo C 在文本方式时提供的函数 gettext( )来放屏幕规定区域的内容,当需要时用 puttext( )函数释放出来,再加上键盘管理函数 bioskey( ),就可以完成下拉式菜单的设计。 程序m e n u 1 . c是一个简单拉式菜单。运行时在屏幕上一行显示主菜单的内容,当按 A LT + F 则进入 F i l e子菜单,然后可用光标键移动色棒选择操作,用回车确认。用 E s c键退出主菜单, 并可用A LT + X退出菜单系统。 [例9-9] 下拉式菜单m e n u 1 . c / *下拉式菜单m e n u 1 . c * / # i n c l u d e < c o n i o . h > # i n c l u d e < s t d i o . h > # i n c l u d e < s t d l i b . h > # i n c l u d e < b i o s . h > void main(void) { 第9章 实用编程技巧 1 9 1下载1 9 2 C语言程序设计 下载 int i,k e y ,k e y 0 ,k e y 1 ,y,t e s t ; 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],b u f 1 [ 1 6 * 2 ] ; / *定义保存屏幕区域的数组变量* / t e x t b a c k g r o u n d ( B L U E ) ; / *设置文本屏幕背景色* / clrscr( ); / *屏幕背径着色* / w i n d o w ( 1 ,1,8 0,1 ); / *定义一个文本窗口* / t e x t b a c k g r o u n d ( W H I T E ) ; / *设置窗口背景色* / t e x t c o l o r ( B L A C K ) ; clrscr( ); w i n d o w ( 1 ,1,8 0,2 ); for (i=0;i < 8 ;i + + ) c p r i n t f ( " % s " ,m [ i ] ) ; / *显示主菜单的内容* / w h i l e ( 1 ) { k e y = 0 ; while(bioskey(1) == 0); / *等待键盘输入* / key = bioskey(0); / *取键盘输入码* / key = key&0Xff? 0:key>>8; / *只取扩充键码* / if(key == 45) eXit (0); / *如果按A L T + X 键则退出* / if(key == 33) / *如果按A L T + F 则显示子菜单* / { t e x t b a c k g r o u n d ( B L A C K ) ; textcolor (WHITE); g o t o x y ( 4 ,1 ); c p r i n t f ( " % s " ,m [ 0 ] ) ; g e t t e x t ( 4 ,2,1 9,1 1,b u f ) ;/ *保存窗口区域的在原有内容* / w i n d o w ( 4 ,2,1 9,1 1 ) ; t e x t b a c k g r o u n d ( W H I T E ) ; t e x t c o l o r ( B L A C K ) ; clrscr( ); w i n d o w ( 4 ,2,1 9,1 2 ) ; g o t o x y ( 1 ,1 ); / *作一个单线形边框* / p u t c h ( 0 x f f ) ; for (i=2;i < 1 0 ;i + + ) { g o t o x y ( 1 ,i ); p u t c h ( 0 ×b 3 ) ; g o t o x y ( 1 6 ,i ); p u t c h ( 0 ×b 3 ) ; } g o t o x y ( 1 ,1 0 ) ;第9章 实用编程技巧 1 9 3下载 p u t c h ( 0 X c 0 ) ; for (i=2;i < 1 6 ;i + + ) p u t c h ( 0 X c 4 ) ; p u t c h ( 0 X d 9 ) ; for (i=2;i < 1 0 ;i + + ) { g o t o x y ( 2 ,i ); c p r i n t f ( " % s " ,f [ i - 1 ] ) ; } g e t t e x t ( 2 ,2,1 8,3,b u f 1 ) ; t e x t b a c k g r o u n d ( B L A C K ) ; t e x t c o l o r ( W H I T E ) ; g o t o x y ( 2 ,2 ); c p r i n t f ( " % s " ,f [ 0 ] ) ; y = 2 ; k e y 1 = 0 ; w h i l e ( ( k e y 0 ! = 2 7 ) & & ( k e y 1 ! = 4 5 ) & & ( k e y 0 ! = 1 3 ) ) { / * 输入为A L T + X ,回车或E S C 键退出循环* / w h i l e ( b i o s k e y ( 1 ) = = 0 ) ; / *等待键盘输入* / k e y 0 = k e y 1 = b i o s k e y ( 0 ) ; / *取键盘输入码* / k e y 0 = k e y 0 & 0 X f f ; / *只取扩充码* / key1=key1&0Xff? 0:key1>>8; if (key1==72||key1==80) / *如果为上下箭头键* / { p u t t e x t ( 2 ,y,1 8,y + 1 ,b u f 1 ) ; / *恢复原来的信息* / if (key1==72) y= y==2? 9:y-1; / *上箭头处理* / if (key1==80) y= y==9? 2:y+1; / *下箭头处理* / g e t t e X t ( 2 ,y,1 8,y + 1 ,b u f 1 ) ; / *保存新色棒前产生这一位置屏幕内容* / t e x t b a c k g r o u n d ( B L A C K ) ; / *产生新色棒* / t e x t c o l o r ( W H I T E ) ; g o t o x y ( 2 ,y ); c p r i n t f ( " % s " ,f [ y - 1 ] ) ; } } if(key1 == 45) eXit(0); / *按A L T + X 退出* / if(key0 == 13) / *回车按所选菜单项进行处理* / { s w i t c h ( y ) { case 1: b r e a k ; case 2: b r e a k ; case 9: e X i t ( 0 ) ; d e f a u l t : b r e a k ; } }1 9 4 C语言程序设计 下载 else /*按E S C 键返回主菜单* / { w i n d o w ( 1 ,1,8 0,2 ); p u t t e x t ( 4 ,2,1 9,1 1,b u f ) ; / *释放子菜单窗口占据的屏幕原来内容* / t e x t b a c k g r o u n d ( W H I T E ) ; t e x t c o l o r ( B L A C K ) ; g o t o x y ( 4 ,1 ); c p r i n t f ( " % s " ,m [ 0 ] ) ; } } } } 9.2.2 选择式菜单的设计 所谓选择式菜单,就是在屏幕上出现一个菜单,操作者可根据菜单上所提供的数字或字 母按相应的键去执行特定的程序,当程序执行完后又回到主菜单上。 这种菜单编制简单,操作方便,使用灵活,尤其适用于大型管理程序。如果在自动批处 理文件上加入这种菜单后,操作者可根据菜单上的提示,进行相应的操作,这样可以简化许 多步骤,对一般微机用户来说是比较适合的。 [例9-10] 选择式菜单程序menu2.c #include # i n c l u d e < s t d l i b . h > # i n c l u d e < c o n i o . h > main( ) { char ch; int i; do { s y s t e m ( " c l s " ) ; 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) { case '1' : system("tc");b r e a k ; case '2' : system("win");b r e a k ; case '3' : system("wps");b r e a k ; case '4' : system("dbase");b r e a k ; case '0' : system("cls");e X i t ( 1 ) ; d e f a u l t : printf("\n\t*** wrong !!! ***\n");第9章 实用编程技巧 1 9 5下载 for (i=0;i < 6 0 0 ;i + + ) ; { ; } } } while (1); } 9.2.3 实现阴影窗口的技巧 目前,许多应用软件都采用了输出窗口技术,有的甚至使用了带有阴影的输出窗口。这 种技术给人们以新鲜醒目的感觉,可达到事半功倍的作用。 程序 m e n u 3 . c是一个阴影窗口的例子。其中用到两个自编函数,一个是建立窗口函数 set_win( ),另一个是建立带有阴影部分的窗口函数 set_bkwin( )。这两个函数需要传递以下几 个参数: int X1,y 1,X 2,y 2,b,b c,t c char *head 其中:x 1、y 1、x 2、y 2决定了窗口边框大小,b用来选择窗口的类型;当 b = 0时,无边框, 当b =1时,单线边框,当 b = 2时,上下边框为双线,左右边框为单线,当 b = 3时,双线边框, 当b = 4时,上下边框为单线,左右边框为双线;参数 b c用来决定窗口的背景颜色,其范围为 0 ~ 7;参数t c决定窗口内字符的颜色,其范围为 0 ~ 1 5; 参数h e a d为窗口名称。 在文本状态下,一个字符在屏幕缓冲区内要占用2个字节来存储。且字符内容在前属性 在后,顺序存储。所谓属性,就是字符的背景颜色和字符颜色,我们可以通过改变其属性字 节,来实现窗口的阴影。 [例9 - 11] 阴影窗口程序m e n u 3 . c # i n c l u d e < s t d i o . h > # i n c l u d e < s t r i n g . h > # i n c l u d e < c o n i o . h > #define screen (*screen_ptr) typedef struct texel_struct { unsigned char attr; } t e X e l ; typedef texel screen_array[25][80]; screen_array far *screen_ptr = (screen_array far *) 0Xb800; void set_win( int X1,int y1, int X2,int y2,int b ,int bc, int tc, char *head); void set_bkwin(int X1,int y1,int X2,int y2,int b,int bc,int tc,c h a r * h e a d ) ; void main(void ) { s e t _ b k w i n ( 1 ,2,2 5,1 8,2,2,1," w i n d o w " ) ; g e t c h ( ) ;1 9 6 C语言程序设计 下载 } void set_bkwin(X1,y 1,X 2,y 2,b,b c,t c,h e a d ) int X1, y 1, X 2, y 2, b, b c, t c; char *head; { int i, j; for (i=X1+1;i < X 2 + 2 ;i + + ) { for (j=y2;j < y 2 + 1 ;j + + ) s c r e e n [ j ] [ i ] . a t t r = 8 ; } for (i=X2+1;i < X 2 + 2 ;i + + ) { for (j=y1;j < y 2 + 1 ;j + + ) s c r e e n [ j ] [ i ] . a t t r = 8 ; } s e t _ w i n ( X 1 ,y 1,X 2,y 2,b,b c,t c,h e a d ) ; } void set_win(X1,y 1,X 2,y 2,b,b c,t c,h e a d ) int X1,y 1,X 2,y 2,b,b c,t c; char *head; { int i,j ; int c[4][6] ={ { 0 X d a , 0 X c 4 , 0 X b f ,0 X b 3 , 0 X c 0 , 0Xd9 }, { 0 X d 5 , 0 X c d , 0 X b 8 ,0 X b 3 , 0 X d 4 , 0Xbe }, { 0 X c 9 , 0 X c d , 0 X b b ,0 X b a , 0 X c 8 , 0Xbc }, { 0 X d 6 , 0 X c 4 , 0 X b 7 ,0 X b a , 0 X b 3 , 0Xbd }, }; j = ( X 2 - X 1 ) / 2 - s t r l e n ( h e a d ) / 2 + X 1 + 1 ; t e x t b a c k g r o u n d ( b c ) ; t e x t c o l o r ( t c ) ; i f ( b ! = 0 ) { w i n d o w ( 1 ,1,8 0,2 5 ) ; g o t o x y ( X 1 ,y 1 ) ; p u t c h ( c [ b - 1 ] [ 0 ] ) ; for (i=X1+1;i < X 2 ;i + + ) p u t c h ( c [ b - 1 ] [ 1 ] ) ; p u t c h ( c [ b - 1 ] [ 2 ] ) ; for (i=y1+1;i < y 2 ;i + + ) { g o t o x y ( X 1 ,i ); p u t c h ( c [ b - 1 ] [ 3 ] ) ; g o t o x y ( X 2 ,i ); p u t c h ( c [ b - 1 ] [ 3 ] ) ; }第9章 实用编程技巧 1 9 7下载 g o t o x y ( X 1 ,y 2 ) ; p u t c h ( c [ b - 1 ] [ 4 ] ) ; for (i=X1+1;i < X 2 ;i + + ) p u t c h ( c [ b - 1 ] [ 1 ] ) ; } if (head[0]!=NULL) { g o t o x y ( j ,y 1 ) ; t e x t c o l o r ( W H I T E ) ; t e x t b a c k g r o u n d ( B L U E ) ; c p r i n t f ( " % s " ,h e a d ) ; } t e x t c o l o r ( t c ) ; t e x t b a c k g r o u n d ( t c ) ; w i n d o w ( X 1 + 1 ,y 1 + 1 ,X 2 - 1 ,y 2 - 1 ) ; clrscr( ); } 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 ( )函数正好用来控制发声时间,而且它 不受P C机主频高低的影响。下面这段程序可使微机发出 c调1的声音。 表9-2 音符与频率关系表 音符 c d e f g a b 1 2 3 4 5 6 7 频率 2 6 2 2 9 4 3 3 0 3 4 9 3 9 2 4 4 0 4 9 4 单符 c d e f g a b 1 2 3 4 5 6 7 频率 5 2 3 5 8 7 6 5 9 6 9 8 7 8 4 8 8 0 9 8 8 音符 c d e f g a b 1 2 3 4 5 6 7 频率 1 0 4 7 11 7 5 1 3 1 9 1 3 9 7 2 5 6 8 1 7 6 0 1 9 7 6 [例9-12] 音乐程序m u s i c 1 . c # i n c l u d e < s t d i o . h > # i n c l u d e < d o s . h > void pause(int); void sound1(int,i n t ) ; void main(void)1 9 8 C语言程序设计 下载 { int i,f r e q ,s p e e d = 5 ; int time=4*speed; char *qm="iddgwwwqqgfff dddfghhhggg ddgwwwqqgfff\ ddffhjqqqqq wpggjhgddgqq hhqwwqjjjggg\ ddgwwwqqqgfff ddffhjqqqqqq";/ *定义歌曲* / while (*qm++ !='\0'){ i = 1 ; s w i t c h ( * q m ) { case 'k': t i m e = 1 * s p e e d ; i = 0 ; b r e a k ; case 'i': t i m e = 6 * s p e e d ; i = 0 ; b r e a k ; case 'o': t i m e = 1 0 * s p e e d ; i = 0 ; b r e a k ; case 'p': p a u s e ( t i m e ) ; i = 0 ; b r e a k ; case 'a': f r e q = 5 2 3 ; b r e a k ; case 's': f r e q = 5 8 7 ; b r e a k ; case 'd': f r e q = 6 5 9 ; b r e a k ; case 'f': f r e q = 6 9 8 ; b r e a k ; case 'g': f r e q = 7 8 4 ; b r e a k ; case 'h': f r e q = 8 8 0 ; b r e a k ; case 'j': f r e q = 9 8 8 ; b r e a k ; case 'z': f r e q = 2 6 2 ; b r e a k ; case 'X': f r e q = 2 9 4 ; b r e a k ; case 'c':第9章 实用编程技巧 1 9 9下载 f r e q = 3 3 0 ; b r e a k ; case 'v': f r e q = 3 4 9 ; b r e a k ; case 'b': f r e q = 3 9 2 ; b r e a k ; case 'n': f r e q = 4 4 0 ; b r e a k ; case 'm': f r e q = 4 9 4 ; b r e a k ; case 'q': f r e q = 1 0 4 7 ; b r e a k ; case 'w': f r e q = 1 1 7 5 ; b r e a k ; case 'e': f r e q = 1 3 1 9 ; b r e a k ; case 'r': f r e q = 1 3 9 7 ; b r e a k ; case 't': f r e q = 2 5 6 8 ; b r e a k ; case 'y': f r e q = 1 7 6 0 ; b r e a k ; case 'u': f r e q = 1 9 7 6 ; b r e a k ; d e f a u l t : i =0; b r e a k ; } i f ( i ) s o u n d 1 ( f r e q ,t i m e ) ; } } void sound1(int freq,int time) /*freq为频率,t i m e为持续时间 * / { union { long divisor; unsigned char c[2]; } count;unsigned char ch; c o u n t . d i v i s o r = 1 1 9 3 2 8 0 / f r e q ; /* 1193280 是系统时钟速率 * / o u t p ( 6 7 ,1 8 2 ) ; o u t p ( 6 6 ,c o u n t . c [ 0 ] ) ; o u t p ( 6 6 ,c o u n t . c [ 1 ] ) ; c h = i n p ( 9 7 ) ; o u t p ( 9 7 ,c h | 3 ) ; p a u s e ( t i m e ) ; o u t p ( 9 7 ,c h ) ; } void pause(int time) { int t1,t 2; union REGS in,o u t ; i n . h . a h = 0 X 2 c ; i n t 8 6 ( 0 X 2 1 ,& i n ,& o u t ) ; / *取当前时间 * / t 1 = t 2 = 1 0 0 * o u t . h . d h + o u t . h . d l ; / * o u t . h . d h 为秒值,o u t . h . d l 为1 / 1 0 0 秒值 * / w h i l e ( t 2 - t 1 < t i m e ) { i n t 8 6 ( 0 X 2 1 ,& i n ,& o u t ) ; t 2 = 1 0 0 * o u t . h . d h + o u t . h . d l ; if (t2 #include #include #include #include #include int sa[1000],sb[1000]; int j,step,rate,len,lenl,half; chat, strl27[127]; int getmusic(); void chang(void); void music(void); / * * * * * * * * * * * * * * * * * * * * * * * * / int main() { len=0; len1=0; i f ( g e t m u s i c ( ) ! = 0 ) exit (1); d e l a y ( 5 0 0 ) ; music ( ); return 0; } / * * * * * * * * * * * * * * * * * * / int getmusic() { FILE * fp; if ((fp=fopen("ma.txt","r"))==NULL) /*打开曲谱文件* { printf("file not open\n") ; return 1; } fscanf(fp, "%d %d\n", &step,&rate); while(! fgets(str127, 127, fp)==NULL) { chang ( ); } fclose (fp); return 0; } / * * * * * * * * * * * * * * * * * * * / void chang(void) {2 0 2 C语言程序设计 下载 int k; for (k=0;k=‘0’)& & ( s t r 1 2 7 [ k ] < ’ 7 ’ ) ) { s a [ l e n ] = s t r 1 2 7 [ k ] - 4 8 ; S w i t c h ( s a [ l e n ] ) { case 1:sa[len]=262;break; case 2:sa[len]=294;break; case 3:sa[len]=330;break; case 4:sa[len]=349;break; case 5:sa[len]=392;break; case 6:sa[len]=440;break; case 7:sa[len]=494;break; case 0:sa[len]=0; } l e n + + ; i f ( l e n > 9 9 9 ) e x i t ( 0 ) ; h a l f = 0 ; } for (k=0;k 0 ) s a [ l e n 1 ] = s a [ l e n 1 ] / 2 ; b r e a k ; c a s e ' * ' : i f ( s a [ l e n 1 ] > 0 ) s a [ l e n 1 ] = s a [ l e n 1 ] * 2 ; b r e a k ; } } } / * * * * * * * * * * * * * * * * * * / void music(void) {j = 0 ; while ((j<=len)&&(bioskey(1)==0)) { sound(2*sa[j]); /*发声* / delay(2*rate*sb[j]); /*延迟* / nosound( ); /*关闭发声* / j + + ; } } 9.3.3 实现后台演奏音乐的技巧 B A S I C语言有一个前后台演奏音乐的语句 p l a y,该语句有很强的音乐功能。而 C语言虽有 s o u n d ( )函数,但不能进行后台演奏,并且必须指明音乐频率,才能使它发声。为此可编制一 个与p l a y语句相同的后台演奏音乐函数。 若要奏乐,每一个音符必须有一个频率用 sound 去发声,且必须有适当的时间延时,形成 拍子,这样才能演奏音乐。我们可用指定 1拍的时间来推出其它节拍。例如: #define L1 1000 #define L2 L1/2 #define l4 L1/4 即L 1为1拍,L 2为1 / 2拍,L 4为1 / 4拍。 后台演奏,可通过修改 1 C向量来实现。计算机每秒发出 1 8 . 2次中断调用1 C,因此,就可 以通过它来计算,实现后台演奏。 程序P L AY. C只是一个简单的后台演奏音乐的例子,将其编译后,就可在 D O S提示符后直 接执行,演奏过程中,按任一键都将停止演奏。 [例9-13] 后台演奏程序P L AY.C # i n c l u d e < s t d i o . h > # i n c l u d e < d o s . h > # i n c l u d e < b i o s . h > # i n c l u d e < c o n i o . h > #define L1 1000 #define L2 L2/2 #define L4 L1/4 void play(int *); void interrupt new_int9(void); void interrupt (*old_int9)(void); int HZ[4][7]={ { 1 3 1 ,1 4 7 ,1 6 5 ,1 7 5 ,1 9 6 ,2 2 0 ,2 4 7 } , { 2 6 2 ,2 9 4 ,3 3 0 ,3 4 9 ,3 9 2 ,4 4 0 ,4 9 4 } , { 5 2 3 ,5 8 7 ,6 5 9 ,6 9 8 ,7 8 4 ,8 8 0 ,9 8 0 } }; int *s; int buf[100]={11,1 2,1 2,1 2,1 3,1 2,1 4,1 2,1 6,1 2,1 7,1 2,2 1,1 1,2 2,1 2 , 第9章 实用编程技巧 2 0 3下载2 3,1 2,2 4,1 2,2 5,1 2,2 6,1 2,2 7,1 2,3 1,1 2,0,0,0 }; void main(void) { p l a y ( b u f ) ; while (*s && ! bioskey(0)); /* 判断结束条件 * / n o s o u n d ( ) ; s e t v e c t ( 0 X 1 c ,o l d _ i n t 9 ) ; } void play(int *ms) { s = m s ; o l d _ i n t 9 = g e t v e c t ( 0 X 1 c ) ; s e t v e c t ( 0 X 1 c ,n e w _ i n t 9 ) ; } void interrupt new_int9(void) { static int count=0,t t = 0 ; c o u n t + + ; if (*s!=0) {if (count>=tt) { s o u n d ( H Z [ * s / 1 0 ] [ * s % 1 0 ] ) ;s + + ; t t = * s * 1 8 . 2 / 1 0 0 0 ; s + + ; c o u n t = 0 ; } else nosound(); o l d _ i n t 9 ( ) ; } } 2 0 4 C语言程序设计 下载下载 第10章 C++入门 9 0年代以来,面向对象的程序设计(Object-Oriented Programming,简称O O P)异军突起, 迅速在全世界流行,一跃成为主流的程序设计技术。在软件市场中,覆盖面大、垄断市场的 新一代程序设计语言、软件开发工具和环境以及操作系统大多是面向对象的。 10.1 面向对象的概念 10.1.1 面向对象的程序结构 面向对象的程序设计是一种基于结构分析的、以数据为中心的程序设计方法。在面向对 象的程序中,活动的基本单位是对象,向对象发送消息可以激活对象的行为。为此,许多人 把面向对象的程序描述为: 程序=对象+消息传递 1. 对象 对象类似于C语言中的变量,可以泛指自然界中的任何事务,包括具体实物和抽象概念。 对象具有一些属性、状态和行为。例如,每个人都有姓名、性别、年龄、身高、体重等属性, 有工作、学习、吃饭、睡觉等行为。所以,对象一般可以表示为:属性 +行为。在面向对象的 程序设计中,对象被表示为:数据 +操作,操作也称为方法。这就是说,面向对象程序设计中 的对象是指由一组数据和作用于其上的一组方法组成的实体。 2. 类 在面向对象的程序设计中,会涉及到许多对象,我们无法将所有的对象都描述清楚,如 果那样做的话,程序将无限长或无法描述。因此在面向对象的程序设计中引入了类的概念, 将同类的对象归于一类,同类对象具有相同的属性和行为,如各种花同属一类,各种草、植 物等也分别属于不同的类。 3. 消息 消息就是对对象进行某种操作的信息。当要求对象执行某种特定操作时,就向该对象发 送操作消息,对象接收到指定操作的消息后,就调用相应的操作方法,完成有关操作。 消息及其传递机制是面向对象程序设计的一个重要角色。对象的一切活动,都要通过消 息来驱动,消息传递是对象间通信的唯一途径。 4. 方法 方法就是对对象进行的某种操作。当对象接收到相应的消息时,就调用对应的方法完成 指定的操作,有了消息,就驱动对象工作;有了方法,就能实现消息所要求的操作。 5. 继承 继承是面向对象语言的另一个重要概念。在客观世界中,存在着整体与个体的关系、一 般与特殊的关系,继承将后者模型化。例如,对人的分类我们用图 1 0 - 1描述如下。 在类的层次结构图中,下层节点都具有上层节点的特性,都具备人的共同特点。但下层 节点较之上层节点而言,又具有新的特性,是上层节点 所不具有的。这种下层节点对上层节点的特性的保持, 就是我们所说的继承。 在面向对象语言中,类功能支持这种层次结构。除 了根结点外,每个类都有它的超类,又称为父类或基类。 除了叶结点外,每个类都有它的子类,又称为派生类。 一个子类可以从它的基类继承所有的数据和操作,并扩 充自己的特殊数据和操作。基类抽象出共同的属性和操 作,子类体现其差别。有了类的层次结构和继承性,不 同对象的共同特性只需定义一次,用户就可以充分利用已有的类,进行完善和扩充,达到软 件可重用的目的。 10.1.2 C++的类 C + +语言是一种面向对象的程序设计语言,是对传统 C语言的完善和扩充,并支持面向对 象的概念:对象、类、方法、消息和继承,下面给出一个 C + +关于类的结构: [例10-1] 栈操作。栈是一种后进先出的数据结构,我们利用数组这个静态的存储来实现 栈、充当栈,完成数据的压栈和出栈。 #include "iostream.h" #define SIZE 100 / /定义栈类型 class stack / *定义类* / { int stck[SIZE]; / *数据成员,整型数组做堆栈* / int top; / *栈顶指针* / p u b l i c : void init(void); / *初始化成员函数* / void push(int i); / *压栈* / int pop(void); / *出栈* / } ; / /栈的初始化 void stack:::i n i t ( v o i d ) / *类成员函数的定义* / { t o p = 0 ; / *定义栈顶指针指向数组头* / } / /进栈操作 void stack::push(int i) { if (top= =SIZE) { cout<<"The stack is full!"; / *栈满* / r e t u r n ; 2 0 6 C语言程序设计 下载 解放军农民工人 人 海军 空军 陆军 图10-1 对人的分类} s t c k [ t o p ] = i ; /* 压入数据到栈顶* / t o p + + ; / *指针加1 * / } / /出栈操作 int stack::p o p ( v o i d ) { if (top= =0) { cout<<"The stack is underflow!";/ *栈空,无数据* / return 0; } top- -; / *指针减1 * / return stck[top]; / *返回栈顶元素* / } void main(void) { stack stack1, s t a c k 2 ; / /创建对象, s t a c k 1 . i n i t ( ) ; / *调用成员函数,对栈初始化* / s t a c k 2 . i n i t ( ) ; s t a c k 1 . p u s h ( 1 ) ; / /在s t a c k 1 栈中,压栈1。 stack2.push(2); //在s t a c k 2 栈中,压栈2。 s t a c k 1 . p u s h ( 3 ) ; s t a c k 2 . p u s h ( 4 ) ; cout< m a i n ( ) 第1 0章 C + +入门 2 0 7下载{ c o u t < < " H e l l o ,World !"; } C + +标准流的输入输出可以在i o s t r e a m . h文件中找到。c o u t是C + +中与标准输出设备相关的 输出流,“< <” 是运算符,该运算符完成将引号内的字符串写到标准输出流 c o u t,简单地说, 就是将字符串“H e l l o,Wo r l d !”写到标准输出设备— 显示器上,为此,运行程序,我们将 在屏幕上看到: H e l l o ,W o r l d ! [例10-3] 利用海伦公式,输入三角形的三条边,若满足任意两边之和大于第三边,则计 算出给定三角形的面积。 #include #include m a i n ( ) { float s,s 1,a 1,a 2,a 3 ; // a1,a 2,a 3是三角形的三条边;s 是三角形的面积; // s1是二分之一的周长 c o u t < < " a 1 = " ; c i n > > a 1 ; / *键盘输入* / c o u t < < " a 2 = " ; c i n > > a 2 ; c o u t < < " a 3 = " ; c i n > > a 3 ; if (((a1+a2)>a3)&&((a1+a3)>a2)&&((a2+a3)>a1)) / /任意两边之和应大于第三边 { s 1 = ( a 1 + a 2 + a 3 ) / 2 ; s = s q r t ( s 1 * ( s 1 - a 1 ) * ( s 1 - a 2 ) * ( s 1 - a 3 ) ) ; / /计算面积 c o u t < < " a r e a = " < < s ; c o u t < < " \ n " ; } e l s e c o u t < < " e r r o r ! " ; return 0; } 程序中,c i n是C + +的标准输入流;“ > >”是运算符,该运算符完成从标准输入流(通常 c i n与键盘相连)中读取数据。运行该程序后,可以看到如下的显示信息: a 1 =3¿ a 2 =4¿ a 3 =5¿ a r e a = 6 10.3 类与对象 客观世界中的事物都包含属性和行为两个方面。在C + +程序设计中,对事物的描述分别用数 据成员和成员函数来表现,并把它们封装起来,形成一个抽象的数据类型— 类。这就是说,类 2 0 8 C语言程序设计 下载具有两种成员:数据成员和成员函数,按照面向对象的概念,成员函数又称为方法函数或方法。 10.3.1 类的定义与对象的引用 1. 类的定义 类定义的基本格式如下所示: class 类型名 { p r i v a t e : 私有成员声明; p r o t e c t e d : 保护成员声明; p u b l i c : 公有成员声明; } 类成员分为私有成员和公有成员两部分。外界不能访问一个对象的私有成员,只能与对 象的公有成员之间进行信息交换。定义类即是确定选择成员并区分它们的访问权限。 [例10-4] C++程序结构示例。 #include class exam1 / /定义类 { p r i v a t e : / /类的私有成员 int x,y ; / /数据成员 p u b l i c : / /类的公有成员 类的声明部分 void init(); / /成员函数 float average(); void print(); } ; void exam1::init() / /类成员函数的定义 { x = 3 ; y = 4 ; } float exam1::average() / /类成员函数的定义 类的实现部分 {return (x+y)/2.0; } void exam1::print() / /类成员函数的定义 { p r i n t f ( " \ n x = % d ,y = % d ,a v e r = % 7 . 2 f \ n " ,x,y,a v e r a g e ( ) ) ; } m a i n ( ) / /主函数 { exam1 obj; / /声明并创建一个对象 o b j . i n i t ( ) ; / /调用成员函数初始化 类的使用 o b j . p r i n t ( ) ; / /输出运算结果 return 0; } 第1 0章 C + +入门 2 0 9下载1) 类在C + +中用关键字c l a s s来说明,紧跟其后的是类名 e x a m 1,类中包含两个私有数据成 员x、y和三个公有的成员函数i n i t ( )、a v e r a g e ( )、p r i n t ( )。 C + +允许隐藏内部状态,由p r i v a t e开始的私有段成员就满足这一特性,它们只能允许该类 对象的成员函数来访问。类中的公有段既可以是数据成员,也可以是成员函数,这是类提供 给外部的接口。当然,C + +还提供另一种保护段符号: protected ,下面会介绍到。 运行该程序,得到的输出显示为: x = 3 , y=4 , aver= 3.50 2) 类的成员在类的定义中出现的顺序可以任意,并且类的实现既可以放在类的外面,又 可以内嵌在类内,下面调整类成员的顺序为: class exam1 / /定义类 { p u b l i c : float average(); void print(); p r i v a t e : int x,y ; p u b l i c : void init(); } ; 3) 若类的实现定义在类的外面,在成员函数的函数头中函数名前,应使用作用域限定符:: 指明该函数是哪一个类中的成员函数,即有如下格式。 类型 类名::成员函数名(参数表) { 函数体 } 4) 除特殊指明外,成员函数操作的是同一个对象的数据成员。下面的示例将类成员函数 的实现内嵌在类中: class exam1 / /定义类 { p u b l i c : float average() { return (x+y)/2.0; } void print() { p r i n t f ( " \ n x = % d ,y = % d ,a v e r = % 7 . 2 f \ n " ,x,y,a v e r a g e ( ) ) ; } p r i v a t e : int x,y ; p u b l i c : void init() { x = 3 ; y = 4 ; } } ; 2 1 0 C语言程序设计 下载5) 类定义的最后一个花括号的外面一定要有分号结束。程序中出现的“ / /”符号是作为注 释开始的标志,与C语言中“/* ...... */”用法完全相同。 6 )使用p u b l i c、p r i v a t e和p r o t e c t e d关键字 p u b l i c、p r i v a t e和p r o t e c t e d关键字称为访问说明符。 说明为p u b l i c的类成员可以被任何函数所使用(当它们是数据成员时)或调用(当它们是 成员函数时)。调用者不必属于这个类或任何类。 说明为p r i v a t e的类成员只能被同类中的成员函数所使用或调用,也可以被同类的友元使 用或调用。在类的成员中,若没有额外声明访问权限,则表明为 p r i v a t e的类成员。 说明为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和其它. c p p文件。规模较大的程序,应采用模块化的程序设计技术。 C + +程序的编辑、编译、连接及运行的方法和过程,在 D O S下,与 C语言基本一样。 Borland C++或是Turbo C++,均有一个集成开发环境,易学易用(与 Turbo C大同小异),操 作非常方便。 10.3.2 构造函数与析构函数 C + +中,类是一种数据类型,这样的类型总与存储空间相关,即要占用一定的内存资源。 当我们定义了对象时,编译系统就会为它分配存储,进行一定的初始化,由于类的结构各不 相同,所需的工作量也各不相同,为此, C + +提供构造函数来完成上述工作。构造函数是属 于某一特定类,可由用户设置,也可用系统缺省设置。与之相对应的是类的析构函数,当类 的对象退出作用域时,析构函数负责回收存储空间,并做一些必要的善后处理。析构函数也 是属于某一特定类,可由用户设置,也可用系统缺省。 1. 构造函数 当定义一个对象时,我们需要给对象开辟一个存储空间,将对象的数据成员初始化。在 使用构造函数以前,首先对构造函数作如下说明: 1) 构造函数具有与类名相同的函数名。 2) 构造函数没有返回类型,即使v o i d也不可以。它的返回值是隐含的,是指向类本身的指针。 第1 0章 C + +入门 2 1 1下载3) 构造函数在对象被定义时自动调用,作相应的初始化。 4) 构造函数可以有参数,也可无参数。 5) 构造函数名可以重载。 6) 当类中无与类名相同的构造函数时, C + +编译系统为其设置缺省的构造函数。 [例10-5] 构造函数应用举例 #include class A { int a,b,c ; / /缺省访问权限,为私有数据成员 p u b l i c : / /公有段 A ( i n t = 1 ,i n t = 2 ,i n t = 3 ) ; / /构造函数1 A ( d o u b l e ,d o u b l e ,d o u b l e ) ; / /构造函数2 A ( l o n g ) ; / /构造函数3 A ( A & ) ; / /构造函数4(拷贝构造函数) void show() / /公有成员函数 { p r i n t f ( " % d , % d, % d \ n " ,a,b,c ) ; } } ; A::A(int I1,int I2,int I3) / /构造函数1的实现 { a = I 1 ; b = I 2 ; c = I 3 ; } A::A(double f1,double f2,double f3) / /构造函数2的实现 { a = ( i n t ) f 1 ; b = ( i n t ) f 2 ; c = ( i n t ) f 3 ; } A::A(long n) / /构造函数3的实现 { a = b = c = ( i n t ) n ; } A::A(A& other) / /构造函数4的实现 { a = o t h e r . a ; b = o t h e r . b ; c = o t h e r . c ; } m a i n ( ) { A x1; / /定义对象x 1,调用缺省参数的构造函数1 x 1 . s h o w ( ) ; / /调用公有段成员函数s h o w ( ) A x2(3); / /定义对象x 2,调用构造函数1 x 2 . s h o w ( ) ; / /调用公有段成员函数s h o w ( ) A x3(3,1 ) ; / /定义对象x 3,调用构造函数1 x 3 . s h o w ( ) ; / /调用公有段成员函数s h o w ( ) A x4(3.14,2 . 4 1 4 ,6 . 2 8 ) ; / /定义对象x 4,调用构造函数2 x 4 . s h o w ( ) ; / /调用公有段成员函数s h o w ( ) A x5(53L); / /定义对象x 5,调用构造函数3 x 5 . s h o w ( ) ; / /调用公有段成员函数s h o w ( ) 2 1 2 C语言程序设计 下载A x6=x5; / /定义对象x 6,调用拷贝构造函数4 x 6 . s h o w ( ) ; / /调用公有段成员函数s h o w ( ) return 0; } 运行上述程序,得如下输出: 1,2,3 3,2,3 3,1,3 3,2,6 5 3,5 3,5 3 5 3,5 3,5 3 可以提供不带参数的构造函数,即是缺省的构造函数。例如: class A { . . . A ( ) ; . . . } ; A : : A ( ) { a = 0 ; b = 0 ; c = 0 ; } 但要注意的是,不能将可缺省参数的构造函数与缺省的构造函数一起使用,以免编译系 统混淆。例如: c l a s s A { . . . A( ); A ( i n t = 1 , i n t = 2 , i n t = 3 ) ; . . . } ; void main( ) { A obj; / /编译系统无法区分应调用哪一个构造函数 . . . } 2. 析构函数 与构造函数对应的是析构函数。 C + +用析构函数来处理对象的善后工作,在对象撤销时自 动调用,并可能要释放一些动态的存储空间等。析构函数具有如下的一些特点: 1) 与类同名,之前冠以波浪线,以区别构造函数。 2) 不指定返回类型。 3) 不能指定参数。 4) 一个类只能有一个析构函数。 析构函数可以这样写: class A { . . . 第1 0章 C + +入门 2 1 3下载p u b l i c : . . . ~A ( ) ; } ; A : : A ( ) { . . . } / /析构函数定义 [例10-6] 我们将构造函数进行编号,在程序的运行中去发现对象被定义后,调用的构造 函数及对象被撤销时,调用的析构函数的处理过程。 #include class A { int a,b,c,n u m b e r ; / /类的私有成员,n u m b e r 用于标志构造函数 p u b l i c : A ( i n t = 1 ,i n t = 2 ,i n t = 3 ) ; / /类的构造函数 A ( d o u b l e ,d o u b l e ,d o u b l e ) ; A ( l o n g ) ; A ( A & ) ; ~ A ( ) { / /类的析构函数 printf("object x destroyed by constr %d created\r\n",n u m b e r ) ; } void show() { p r i n t f ( " % d , % d, % d \ n " ,a,b,c ) ; } } ; A::A(int i1,int i2,int i3) / /构造函数1 { a = i 1 ; b = i 2 ; c=i3; number=1; } A::A(double f1,double f2,double f3) / /构造函数2 { a = ( i n t ) f 1 ; b = ( i n t ) f 2 ; c=(int)f3; number=2; } A::A(long n) / /构造函数3 { a=b=c=(int)n; number= 3; } A::A(A& other) / /拷贝的构造函数4 { a = o t h e r . a ; b = o t h e r . b ; c = o t h e r . c ; n u m b e r = 4 ; } m a i n ( ) { A x1; / /定义对象X 1调用构造1 x 1 . s h o w ( ) ; A x2(3); / /定义对象X 2调用构造1 x 2 . s h o w ( ) ; A x3(3,1 ) ; / /定义对象X 3调用构造1 x 3 . s h o w ( ) ; 2 1 4 C语言程序设计 下载A x4(3.14,2 . 4 1 4 ,6 . 2 8 ) ; / /定义对象X 4调用构造2 x 4 . s h o w ( ) ; A x5(53L); / /定义对象X 5调用构造3 x 5 . s h o w ( ) ; A x6=x5; / /定义对象X 6调用构造4 x 6 . s h o w ( ) ; return 0; / /各对象调用相应的析构函数。 } 程序的输出: 1,2,3 3,2,3 3,1,3 3,2,6 5 3,5 3,5 3 5 3,5 3,5 3 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 #include #include class string{ int length; char str[256]; p u b l i c : 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) / /若串t e x t 的长度小于2 5 6 s t r c p y ( s t r ,t e x t ) ; / /将串t e x t 复制给串s t r 第1 0章 C + +入门 2 1 5下载e l s e s t r n c p y ( s t r ,t e x t ,2 5 5 ) ; / /若串t e x t 的长度超过2 5 5 / /则将串t e x t 的2 5 5 个字符复制给串str l e n g t h = s t r l e n ( s t r ) ; } char *string::search(char arg)/ /成员函数1的定义 { return strchr(str,a r g ) ; / /返回串s t r 中第一次出现字符a r g 的位置 } char *string::search(char *arg)/ /重载成员函数2的定义 { return strstr(str,a r g ) ; / /返回串s t r 在串a r g 中第一次出现的位置 } void main() { string hellomsg='Hello,t h e r e ,I'm a string!'; / /定义串s t r i n g 的对象,自动调用构造函数1 char *found; / /定义字符串f o u n d c o u t < < h e l l o m s g . s e a r c h ( ' t ' ) < < " \ r \ n " ; / /输出对象的成员函数1的返回值 c o u t < < h e l l o m s g . s e a r c h ( " s t r i n g " ) < < " \ r \ n " ; / /输出对象的成员函数2的返回值 } 运行程序,输出为: Hello, there,I'm a string! s t r i n g ! 10.3.4 友元 在类成员的介绍中,我们对类的三种成员做过详细的说明,特别是强调了类的私有成员 和类保护段成员的隐蔽性,它们只能被类对象的成员函数所访问,这也同样表明,类的成员 函数可以访问类中的所有成员。友元是 C + +提供给外部的类或函数访问类的私有成员和保护 成员的一种途径。 在一个类中,将f r i e n d加在某个函数或某个类的前面,则该函数或类将成为所在类的友元。 友元不受它们在类中出现次序的影响,而仅表明其是类的友元。 [例10-8] 说明类的友元函数的作用及与成员函数的区别。 #include #include class stud{ char *name,* n u m ,* t e l ; / /类的私有成员:姓名,学号,电话号码 p u b l i c : stud(char *na,char *nu, char *pho ) / /类的构造函数 { name=new char[strlen(na)+1]; / /使用运算符n e w 向系统申请n a m e 所需的存 储空间 s t r c p y ( n a m e ,n a ) ; / /将参数n a的值复制给姓名n a m e 2 1 6 C语言程序设计 下载num=new char[strlen(nu)+1]; / /使用运算符n e w 向系统申请n u m 所需的存储空间 s t r c p y ( n u m ,n u ) ; / /将参数n u的值复制给学号num tel=new char[strlen(pho)+1]; / /使用运算符n e w 向系统申请t e l 所需的存储空间 s t r c p y ( t e l ,p h o ) ; / /将参数p h o 的值复制给电话号码t e l } void show(stud&); / /类的公有成员函数 friend void show(stud&);/ /类的友元函数 ~ s t u d ( ) / /类的析构函数 { delete name; delete num; delete tel;} / /使用运算符d e l e t e 释放类的各数据成员所占存储空间 } ; void show(stud &student)/ /友元函数的定义 { c o u t < < " 类的友元函数的调用:\ n " ; cout<<"student\"s name is:"< class hundreds; / /类的声明。在类未定义前需使用时,必须先声明。 class cubic / /类c u b i c 的定义 { int sum; p u b l i c : void set_sum(int a,int b,int c){ sum=a*a*a+b*b*b+c*c*c;} / /类的成员函数用于求其各位数的立方和。 friend int equal(cubic c,hundreds h); / /类的友元函数 } cub; / /在类定义的同时定义对象c u b class hundreds{/ /类h u n d r e d s 的定义 int num; p u b l i c : void set_num(int a,int b,int c){ n u m = 1 0 0 * a + 1 0 * b + c ; } / /类的成员函数用于求其三个数构成的三位数 friend int equal(cubic c,hundreds h);//类的友元函数 void display(void){cout< #include #include class tnode{ / /链表节点的数据结构 p u b l i c : tnode *prev; / /指向前一个节点 2 2 0 C语言程序设计 下载 NULL nodebody nodenum next prev nodebody nodenum next prev nodebody nodenum NULL headtnode *next; / /指向下一个节点 char *nodebody; / /节点所存字符串 int nodenum; / /节点字符串的长度 } ; class dbllist{ / /链表结构 tnode *head; / /链表的头指针 tnode *base; tnode *hold; / / b a s e 与h o l d 用于跟踪链表增长 tnode *create(char *); / /为一个节点分配存储空间 p u b l i c : d b l l i s t ( ) ; / /链表的构造函数 ~ d b l l i s t ( ) ; / /链表的析构函数 void clear(); / /释放链表所占空间 tnode *gohead(); / /将指针移到链表头 tnode *gotail(); / /将指针移到链表尾 tnode *gonext(); / /将指针移到链表的下一个节点 tnode *goprev(); / /将指针移到链表的前一个节点 tnode *append(char *); / /追加一个字符串 tnode *insert(char *); / /插入一个字符串 char* accept(tnode *); / /接收指定节点的字符串 } ; d b l l i s t : : d b l l i s t ( ) { h e a d = b a s e = h o l d = N U L L ; / /链表指针初始化 } d b l l i s t : : ~ d b l l i s t ( ) / /析构函数 { clear(); } void dbllist::clear()/ /释放链表各节点 { b a s e = h e a d ; / /头指针 w h i l e ( b a s e ) { / /链表非空 h o l d = b a s e - > n e x t ; / /删除节点如 / /图1 0 - 3 所示 delete base; b a s e = h o l d ; / /在跟踪链表的过程中释放各节点 } h e a d = b a s e = h o l d = N U L L ; } tnode * dbllist::gohead() / /将指针移到链表头 { b a s e = h e a d ; if (base) return base; / /返回头指针 else return NULL; } tnode * dbllist::gotail() / /将指针移到链表尾 { if (base) 第1 0章 C + +入门 2 2 1下载{ while(base->next) base=base->next; / /跟踪链表到尾,返回尾指针 return base; } else return NULL; } tnode* dbllist::gonext() / /将指针移到链表的下一个节点 { if (base) { if (base->next) { b a s e = b a s e - > n e x t ; / /节点指针后移 return base; } else return NULL; } else return NULL; } tnode * dbllist::goprev() / /将指针移到链表的前一个节点 { if (base) { if (base->prev) { b a s e = b a s e - > p r e v ; / /节点指针前移 return base; } else return NULL; } else return NULL; } tnode* dbllist::append(char * str) / /在尾部追加一个字符串 { tnode *temp; i f ( ( t e m p = c r e a t e ( s t r ) ) = = N U L L ) / /申请创建一个新节点(分配存储空间) return NULL; g o t a i l ( ) ; / /找到尾节点 if (!base) { h e a d = b a s e = t e m p ; / /链表无节点,连接到头 } e l s e { / /追加到尾部 b a s e - > n e x t = t e m p ; t e m p - > p r e v = b a s e ; b a s e = t e m p ; } return base; 2 2 2 C语言程序设计 下载} tnode * dbllist::insert(char* str) / /插入一个字符串 { tnode *temp; g o h e a d ( ) ; / /指向链表头 if (!base) return(append(str)); / /若是空链表,直接追加后返回 if ((temp=create(str))==NULL) / /申请一个新节点t e m p return NULL; while (base->next&&memcmp(str,b a s e - > n o d e b o d y ,s t r l e n ( s t r ) + 1 ) > 0 ) b a s e = b a s e - > n e x t ; / /若当前节点不是尾,同时被插字符串按字母表顺序排在该节点的前面,则指针后移 if (!base->next&&memcmp(str,b a s e - > n o d e b o d y ,s t r l e n ( s t r ) + 1 ) > 0 ) { // 插入位置是链表尾。 base->next=temp; / /插入链表尾的操作如图1 0 - 4 / / / / t e m p - > p r e v = b a s e ; b a s e = t e m p ; } e l s e { / /非尾节点,将新插节点连结到链表内 h o l d = b a s e - > p r e v ; t e m p - > p r e v = h o l d ; t e m p - > n e x t = b a s e ; b a s e - > p r e v = t e m p ; if (!hold) head=temp; / /插入位置在表头 else hold->next=temp; b a s e = t e m p ; } return base; } tnode * dbllist::create(char* str) / /为新节点分配空间 { hold=new tnode; / /申请新节点 hold->nodebody=new char[strlen(str)+1]; / /申请插入字符串所占空间 m e m m o v e ( h o l d - > n o d e b o d y ,s t r ,s t r l e n ( s t r ) + 1 ) ; / / 复制字符串到该节点 h o l d - > p r e v = h o l d - > n e x t = N U L L ; / /该节点的指向前后的指针为空 h o l d - > n o d e n u m = s t r l e n ( s t r ) + 1 ; / /节点字符串的长度 return hold; } char* dbllist::accept(tnode *ptr) / /返回节点字符串的值 { return ptr->nodebody; } void main() / /主程序 第1 0章 C + +入门 2 2 3下载{ tnode *pointer=NULL; / /节点类对象指针 dbllist lex; / /链表类对象 c l r s c r ( ) ; / /清屏幕 l e x . a p p e n d ( " a a a a a " ) ; / /追加字符串 l e x . a p p e n d ( " b b b b b b b b " ) ; l e x . a p p e n d ( " c c c c c c " ) ; l e x . a p p e n d ( " a a a a a a a a a a a a " ) ; p o i n t e r = l e x . g o h e a d ( ) ; / /得到链表头指针 w h i l e ( p o i n t e r ) { / /非空链表,顺序输出字符串 p r i n t f ( " % s \ n " ,l e x . a c c e p t ( p o i n t e r ) ) ; p o i n t e r = l e x . g o n e x t ( ) ; } p o i n t e r = l e x . g o t a i l ( ) ; / /得到链表尾指针 w h i l e ( p o i n t e r ) { / /非空链表,从后向前输出字符串 p r i n t f ( " % s \ n " ,l e x . a c c e p t ( p o i n t e r ) ) ; p o i n t e r = l e x . g o p r e v ( ) ; } l e x . c l e a r ( ) ; / /释放链表 l e x . i n s e r t ( " x x x x x x " ) ; / /按字母表顺序插入字符串 l e x . i n s e r t ( " y y y y y y " ) ; l e x . i n s e r t ( " z z z z z z " ) ; l e x . i n s e r t ( " a a a a a a a " ) ; p o i n t e r = l e x . g o h e a d ( ) ; w h i l e ( p o i n t e r ) { p r i n t f ( " % s \ n " ,l e x . a c c e p t ( p o i n t e r ) ) ; p o i n t e r = l e x . g o n e x t ( ) ; } p o i n t e r = l e x . g o t a i l ( ) ; w h i l e ( p o i n t e r ) { p r i n t f ( " % s \ n " ,l e x . a c c e p t ( p o i n t e r ) ) ; p o i n t e r = l e x . g o p r e v ( ) ; } } 程序运行后,输出为: aaaaa bbbbbbbb cccccc aaaaaaaaaaaa aaaaaaaaaaaa 2 2 4 C语言程序设计 下载 base hold next next base temp next Prev next 图10-3 删除节点 图10-4 在链表尾插入一个节点cccccc bbbbbbbb a a a a a a a a a a a a x x x x x x y y y y y y z z z z z z z z z z z z y y y y y y x x x x x x a a a a a a a 程序按不同接入链表的方法,将会按正序和逆序输出各节点所存字符串。 最后,我们再重申,n e w和d e l e t e的用法为: obj_ptr=new obj_type(new_initializer); delete obj_ptr; delete [ ]obj_ptr; 其中,o b j _ p t r是对象指针或是变量指针; o b j _ t y p e是类类型或变量类型;若n e w _ i n i t i a l i z e r 不空,则表示分配空间的大小。若 o b j _ p t r是指针,则通过“ delete obj_ptr;”来释放空间;若 o b j _ p t r是指针数组,则通过“delete [ ]obj_ptr;”来释放所占内存空间。 10.5 派生类与继承类 在C + +中派生类是指从某一类派生出新类的过程,它能继承基类或父类的属性和功能,所 以,我们也称派生类为继承类。派生或继承的过程类似与我们在C语言中的一些代码的可重用。 各种C的编译版本事先为使用者开发出尽可能多的标准函数,以方便用户使用,使用者无需了 解函数实现的具体细节,就能方便灵活地使用。 在软件的开发过程中,要充分利用系统提供的各种资源,以减少开发人员的劳动。 C + +对 系统或用户开发的代码即类的实现补充了更为广大的发展空间,既可以做类代码的再利用, 也可以做包含继承性的类的派生;既可以做某一个类的继承或派生,也可以做多个类的继承 或派生,这就是我们要谈到的单继承的派生和多继承的派生。 10.5.1 单继承的派生类 通过基类或父类继承产生新类的过程称派生,新类则称为派生类,旧的代码或旧类称为 基类。 从一个类派生出另一个类的语法非常简单: class base{⋯⋯ } ; ⋯⋯ class derived:base{⋯⋯ } ; ⋯⋯ 对于类b a s e来说,它作为基类,应有完整的定义和说明,只有名字,不能作为一个基类。 所有基类成员相对派生类来说都是局部成员,换句话说,对派生类是隐蔽的、不可访问的, 如果需要对基类成员进行访问,则需在基类的类名前加上访问限制符如下: class base{⋯⋯ }; 第1 0章 C + +入门 2 2 5下载⋯⋯ class derived:public base{⋯⋯ } ; ⋯⋯ 这种派生或继承的方法也称为公有派生。存取访问限制符分为 p u b l i c、p r i v a t e、p r o t e c t e d 三种,也就是对基类的派生或继承有三种,分别说明如下。 1. 声明一个基类为p u b l i c 存取访问符p u b l i c使基类成员保持基类成员的特性,原来是 p u b l i c的成员,在派生类中继 续保持公有,原为 p r i v a t e的成员, 在派生类中继续保持私有,原来是 p r o t e c t e d的成员,在派生 类中继续保持其保护特性。 2. 声明一个基类为p r i v a t e 存取访问符p r i v a t e使基类成员中原为 p u b l i c和p r o t e c t e d的成员派生为私有成员,而原为私 有的成员对派生类来说,则是隐蔽的,不透明的,不可访问的。 3. 声明一个基类是p r o t e c t e d 存取访问符p r o t e c t e d使基类成员中原为 p u b l i c的成员成为派生类中的 p r o t e c t e d成员,原为 p r o t e c t e d的成员成为派生类中的p r i v a t e成员。 [例1 0 - 11] 利用一单链表派生为一堆栈,实现堆栈的出入功能。我们通过创建一个工程 项目来实现: 接下来分段介绍: 头文件l i s t . h清单: const int Max_elem = 10; class List / /定义单链表 { int *list; // 整型数组 int nmax; // 数组大小 int nelem; // 数组下标 p u b l i c : List(int n = Max_elem) {list = new int[n]; nmax = n; nelem = 0;}; / /单链表的构造函数(长度为1 0的整型数组) ~List() {delete list;}; / /析构函数 int put_elem(int, int); / /成员函数 int get_elem(int&, int); void setn(int n) {nelem = n;}; int getn() {return nelem;}; void incn() {if (nelem < nmax) ++nelem;}; int getmax() {return nmax;}; void print(); } ; 2 2 6 C语言程序设计 下载 l i s t . h s t a c k . h l i s t . c p p s t a c k . c p p e x a m . c p p s t a c k . p r j文件l i s t . c p p清单: #include #include "list.h" int List::put_elem(int elem, int pos) / /在数组的指定位置安放一整数 { if (0 <= pos && pos < nmax) { list[pos] = elem; //安放一整数 return 0; } e l s e return -1; // 出错返回 } int List::get_elem(int& elem, int pos) / /在指定位置获取元素 { if (0 <= pos && pos < nmax) { elem = list[pos]; // 取数组元素 return 0; } e l s e return -1; // 出错返回 } void List::print() / /顺序输出数组元素 { for (int i = 0; i < nelem; ++i) cout << list[i] << " "; c o u t < < " \ n " } 派生类s t a c k的定义,文件s t a c k . h清单: #include "list.h" class Stack : public List / /关于基类的共有派生 { int top; / /栈顶指针 p u b l i c : Stack() {top = 0;}; / /构造函数 Stack(int n) : List(n) {top = 0;}; / /重载的构造函数,包括对基类的初始化 int push(int elem); / /压栈 int pop(int& elem); / /出栈 void print(); / /输出 } ; 派生类s t a c k的成员函数定义,文件s t a c k . c p p清单: #include #include "stack.h" int Stack::push(int elem) / /压栈 第1 0章 C + +入门 2 2 7下载{ int m = getmax(); if (top < m) { p u t _ e l e m ( e l e m , t o p + + ) ; return 0; } e l s e return -1; } int Stack::pop(int& elem) / /出栈 { if (top > 0) { g e t _ e l e m ( e l e m , - - t o p ) ; return 0; } e l s e return -1; } void Stack::print() / /输出 { int elem; for (int i = top-1; i >= 0; --i) { // 按先进后出的顺序 g e t _ e l e m ( e l e m , i ) ; cout << elem << " "; } cout<<"\n" } 最后给出m a i n函数e x a m . c p p实现堆栈的操作: #include "stack.h" m a i n ( ) { Stack s(5); / /定义堆栈Stack 类的对象s ,堆栈大小为5 int i = 0; // 插入1 ~ 5 while (s.push(i+1) == 0) + + i ; s . p r i n t ( ) ; / /输出 return 0; } 程序运行的结果为 : 5 4 3 2 1 上述程序在定义类对象s时,调用了类的构造函数Stack(int n) : List(n) {top = 0;}进行初始 化,由于是派生类,还需对基类进行初始化,当然需要调用基类的构造函数 List(int n = 2 2 8 C语言程序设计 下载Max_elem) {list = new int[n]; nmax = n; nelem = 0;}使其基类初始化为能存放5个元素的整型数 组作为堆栈。通过对基类的公有派生过程,类 s t a c k实际具有成员相当于基类的全部成员与派 生类定义的成员总和,我们同样可以在派生类中对基类成员进行调用,只要满足派生类对基 类的访问要求即可。下面对程序的 m a i n函数进行修改,充分利用派生类中的各成员。修改后 的e x a m . c p p如下: #include "stack.h" m a i n ( ) { Stack s(5); int i = 0; // Insert the numbers 1 through 5 while (s.push(i+1) == 0) + + i ; s . p r i n t ( ) ; List l(5); / /定义基类对象并初始化 i = 0; / /调用基类成员写入1 ~ 5 在整型数组中 while (l.put_elem(i+1,i) == 0) + + i ; l . s e t n ( i ) ; l . p r i n t ( ) ; / /输出数组各元素 i = 0; / /利用派生类对象调用基类成员 while (s.put_elem(i+1,i) == 0) + + i ; s . s e t n ( i ) ; s . L i s t : : p r i n t ( ) ; / /派生类对象访问基类的同名成员函数 return 0; } 重新运行程序,得输出为: 5 4 3 2 1 1 2 3 4 5 1 2 3 4 5 由于派生类与基类均有p r i n t ( ),所以为避免冲突通过派生类访问基类的同名函数,则需要 在基类成员的前面加上类的限制符,即“派生类对象 .基类名::成员”,在程序中为 s . L i s t . p r i n t ( ) . 还有一种情况也是我们应当引起重视的: class A{......}; class B:public A{......}; class C:public B{......}; 这种结构是按层次进行派生,每层派生类都只有一个直接基类,类 C继承类A和类B的成 员特性,当然受到派生访问控制符的限制。 下面的例子是一个单基多级派生的问题。定义一个基类 L o c a t i o n用于定义点坐标,在此基 础上公有派生出类Point, 以完成一个定位象素点的输出。由于 P o i n t类继承了基类L o c a t i o n的坐 标点,我们在此基础之上公有派生类 C i r c l e,利用此坐标点做圆心在屏幕上绘 制一个圆,并 第1 0章 C + +入门 2 2 9下载画出大小不一的、圆心、半径均不同的各种圆。程序设计的思路是:首先定义一个基本类 L o c a t i o n,它是针对一个象素点的坐标及初始化:在此基础上派生一个类 P o i n t,该类具有关 于点的属性及绘制点的基本操作。最后,定义一个公有派生类 C i r c l e画圆。 [例10-12] 做工程项目: 文件p o i n t . h清单 enum Boolean {false, true}; / /定义枚举类型 class Location { / /基类Location ,用于设置点坐标。 p r o t e c t e d : // 允许派生类访问的保护段成员 int X; / /坐标点 int Y; p u b l i c : // 允许派生类访问 Location(int InitX, int InitY); / /构造函数 int GetX(); int GetY(); } ; class Point : public Location // 从类L o c a t i o n 一级派生,用于绘制点。 p r o t e c t e d : Boolean Visible; // 下级派生类可以访问的p r o t e c t e d 段 p u b l i c : Point(int InitX, int InitY); // 构造函数 void Show(); void Hide(); Boolean IsVisible(); void MoveTo(int NewX, int NewY); } ; 类P o i n t是从基类L o c a t i o n公有派生而来,对基类中的 protected 段和public 段成员保持属性 不变。下面在p o i n t 2 . c p p文件中对基类和一级派生类各成员函数进行定义。 文件p o i n t 2 . c p p清单 #include "point.h" #include // 类Location 的成员函数 Location::Location(int InitX, int InitY) { //构造函数 X = InitX; / /设置点 Y = InitY; } ; int Location::GetX(void) { / /类Location 的成员函数,返回点的x return X; } ; int Location::GetY(void) { / /类Location 的成员函数,返回点的Y return Y; } ; 2 3 0 C语言程序设计 下载 p o i n t . h p o i n t 2 . c p p c i r c l e . c p p d e m o . p r j/ /类Point 的成员函数的定义 // 类Point 的构造函数,包括对基类的初始化. Point::Point(int InitX, int InitY) : Location(InitX,InitY) { Visible = false; // make invisible by default } ; void Point::Show(void) { Visible = true; putpixel(X, Y, getcolor()); // 用当前字符颜色写一个像素点 } ; void Point::Hide(void) {/ /擦除一个像素点 Visible = false; putpixel(X, Y, getbkcolor()); // 用屏幕背景色画一个像素点 } ; Boolean Point::IsVisible(void) { return Visible; } ; void Point::MoveTo(int NewX, int NewY) { / /移动屏幕上的一点 Hide(); // 擦除点 X = NewX; // 改变坐标X和Y到新位置 Y = NewY; Show(); // 在新位置显示点 } ; 文件c i r c l e . c p p清单 #include // graphics library declarations #include "point.h" // Location and Point class declarations #include // for getch() function // link with point2.obj and graphics.lib class Circle : Point { // 从类P o i n t 和类L o c a t i o n 的二级派生 int Radius; // 私有成员 p u b l i c : 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); // 利用标准函数画圆 } 第1 0章 C + +入门 2 3 1下载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); // 定义类c i r c l e 的对象 MyCircle.Show(); // 显示圆 getch(); // 等待一个按键 MyCircle.MoveTo(200, 250); // 移动圆心到(2 0 0 , 2 5 0 )、重画圆。 g e t c h ( ) ; // 按一键 MyCircle.Expand(50); // 增加圆半径5 0,放大圆 g e t c h ( ) ; MyCircle.Contract(75); // 缩小圆 g e t c h ( ) ; c l o s e g r a p h ( ) ; // 关闭图形系统。 return 0; } 运行上述程序:会看到一个圆,在键盘上按键,圆移动;再按任一键,圆放大;再按任 一键,圆缩小。 2 3 2 C语言程序设计 下载10.5.2 多继承的派生类 派生类只有一个基类时,称为单基继承或单基派生;若具有多个基类时,称为多基继承 或多基派生。那么多基继承或多基派生在语法上与单继承有所不同,其语法结构为: class A{......}; . . . . . . class B{......}; . . . . . . class c:public A,public B{......}; 由类A和类B共同派生出新类C,这样一来,类C继承了类A和类B的成员。 在前面的例子中,我们定义了点,并继承点的特性后再画圆。由于派生类只有一个直接 基类,所以称为单继承。 [例10-13] 通过上述画圆的派生过程,我们在圆内写一个字符串,也就是要作多基派生。 先定义一个G M e s s a g e的类,该类完成在 x和y坐标点处开始写一个字符串,利用前面定义 的C i r c l e的类,显示一个圆。在此基础上,再定义一个新类 M C i r c l e,既要画圆,又要写字符 串,应具有GMe s s a g e类和C i r c l e类的共同特性。见图1 0 - 5。 图 10-5 第1 0章 C + +入门 2 3 3下载 P o i n t . h P o i n t 2 . c p p M c i r c l e . c p p 定义项目C I R C L E S T R . P R J Class Point: Public Location {.... Class Location int x; int y; .... Class Location int x; int y; .... Class GMessage: Public Location {.... Class Mcircle: Circle, GMessage {.... Class Circle: Public Point {..../* 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的派生 p r o t e c t e d : Boolean Visible; // 可继承的受保护成员 p u b l i c : 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 包含P o i n t 类和L o c a t i o n 类的说明 #include "point.h" #include // 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; 2 3 4 C语言程序设计 下载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 library declarations #include "point.h" // Location and Point class declarations #include // for string functions #include // 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 / /多重派生 p r o t e c t e d : int Radius; p u b l i c : Circle(int InitX, int InitY, int InitRadius); void Show(void); } ; class GMessage : public Location // 在图形屏幕显示字符串 char *msg; // 被显示信息 int Font; // 文字字体 int Field; // 字型 p u b l i c : // 构造函数初始化 GMessage(int msgX, int msgY, int MsgFont, int FieldSize, char *text); void Show(void); // 显示信息 } ; class MCircle : Circle, GMessage { // 多类继承 p u b l i c : MCircle(int mcircX, int mcircY, int mcircRadius, int Font, char *msg); void Show(void); // 画带字符串的圆 第1 0章 C + +入门 2 3 5下载} ; // Circle类的成员函数 / / C i r c l e 类的构造函数 Circle::Circle(int InitX, int InitY, int InitRadius) : Point (InitX, InitY) // 构造函数的初始化 / /包括对基类构造函数的初始化 { Radius = InitRadius; } ; void Circle::Show(void) { Visible = true; circle(X, Y, Radius); // 画圆 } // Gmessage类的成员函数 / / G m e s s a g e 类的构造函数的初始化 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类的成员函数 / / M c i r c l e 类的构造函数 MCircle::MCircle(int mcircX, int mcircY, int mcircRadius, int Font, char *msg) : Circle (mcircX, mcircY, mcircRadius), G M e s s a g e ( m c i r c X , m c i r c Y , F o n t , 2 * m c i r c R a d i u s , m s g ) / /多继承应处理其多个基类的构造函数 { } void MCircle::Show(void) { Circle::Show(); // 画圆 GMessage::Show(); //写字符串 } main() //画圆并写入字符串 { int graphdriver = DETECT, graphmode; 2 3 6 C语言程序设计 下载initgraph(&graphdriver, &graphmode, "..\\bgi"); setbkcolor(15); //背景色 setcolor(4); //前景色 MCircle Small(250, 100, 25, SANS_SERIF_FONT, "You"); S m a l l . S h o w ( ) ; MCircle Medium(250, 150, 100, TRIPLEX_FONT, "World"); M e d i u m . S h o w ( ) ; MCircle Large(250, 250, 225, GOTHIC_FONT, "Universe"); L a r g e . S h o w ( ) ; g e t c h ( ) ; c l o s e g r a p h ( ) ; return 0; } 运行程序显示为: 第1 0章 C + +入门 2 3 7下载下载 附录A 常用字符与ASCII代码对照表 A S C I I值 字 符 A S C I I值 字 符 A S C I I值 字 符 3 2 [ s p a c e ] 6 4 @ 9 6 ` 3 3 ! 6 5 A 9 7 a 3 4 " 6 6 B 9 8 b 3 5 # 6 7 C 9 9 c 3 6 $ 6 8 D 1 0 0 d 3 7 % 6 9 E 1 0 1 e 3 8 & 7 0 F 1 0 2 f 3 9 ‘ 7 1 G 1 0 3 g 4 0 ( 7 2 H 1 0 4 h 4 1 ) 7 3 I 1 0 5 i 4 2 * 7 4 J 1 0 6 j 4 3 + 7 5 K 1 0 7 k 4 4 , 7 6 L 1 0 8 l 4 5 - 7 7 M 1 0 9 m 4 6 . 7 8 N 11 0 n 4 7 / 7 9 O 111 o 4 8 0 8 0 P 11 2 p 4 9 1 8 1 Q 11 3 q 5 0 2 8 2 R 11 4 r 5 1 3 8 3 S 11 5 s 5 2 4 8 4 T 11 6 t 5 3 5 8 5 U 11 7 u 5 4 6 8 6 V 11 8 v 5 5 7 8 7 W 11 9 w 5 6 8 8 8 X 1 2 0 x 5 7 9 8 9 Y 1 2 1 y 5 8 : 9 0 Z 1 2 2 z 5 9 ; 9 1 [ 1 2 3 { 6 0 < 9 2 \ 1 2 4 | 6 1 = 9 3 ] 1 2 5 } 6 2 > 9 4 ^ 1 2 6 ~ 6 3 ? 9 5 _ 1 2 7 *下载 附录B C库函数 数学函数 使用数学函数时,应在该源文件中使用: #include "math.h" 函 数 名 函数类型和形参类型 功 能 返 回 值 说 明 a c o s double acos(x) 计算结果 计 x 应 在 - 1 ~ double x; 1范围内 a s i n double asin(x) 计算结果 计 x 应 在 - 1 ~ double x; 1范围内 a t a n double .atan(x) 计算结果 double x; c o s double cos(x) 计算结果 计 x 的 单 位 为 double x; 弧度 c o s h double cosh(x) 计算结果 double x; e x p double exp(x) 计算结果 double x; f a b s double fabs(x) 计算结果 double x; f l o o r double floor (x) 该整数的双 double x; 精度实数 f m o d double fmod(x,y) 返回余数双 double x,y; 精度数 f r e x p double frexp(val,eptr) 返回数字部 double val; 分x 0 . 5≤ x < 1 int *eptr; l o g double log(x) 计算结果 double x; l o g 1 0 double log10(x) 计算结果 double x; m o d f double modf(val,iptr) Va l的小数部分 double val; double *iptr; p o w double pow (x,y) 计算结果 double x,y; s i n double sin (x) 计算结果 计x单位为弧度 double x; s i n h double sinh (x) 计算结果 double x; s q r t double sqrt (x) 计算结果 计x应≥ 0 double x; t a n double tan (x) 计算结果 计x单位为弧度 double x; t a n h double tanh (x) 计算结果 double x; 计计算s i n- 1( x )的值 计计算c o s- 1( x )的值 计计算t a n- 1( x )的值 计计算c o s ( x )的值 计计算x的双曲余弦 c o s h ( x ) 的值 计求ex的值 计求x的绝对值 计求出不大于x的最大整数 计求整数x / y的余数 计把双精度数 v a l分解为数 字部分(尾数) x和以 2为 底的指数 n,即v a l = x×2n, n存放在e p t r指向的变量中 求l o gex ,即ln x 计求l o g1 0x 计把双精度数 v a l分解为整 数部分和小数部分,把整 数部分存到i p t r指向的单元 计计算xy的值 计计算s i n x的值 计 计算 x的双曲正弦函数 sinh(x) 的值 计计算根号下 计计算tan(x) 的值 计 计算 x的双曲正切函数 t a n h ( x )的值 x字符函数和字符串函数 ANSI C标准要求在使用字符串函数时要包含头文件 ' s t r i n g . h ' ,使用字符函数时要包含 ' c t y p e . h '。 函 数 名 函数类型和形参类型 功 能 返 回 值 包 含 文 件 i s d i g h t int isdight(ch) 检检查c h是否是数字0~9 是,返回1;不是,返回 0 c t y p e . h int ch i s l o w e r int islower(ch) 检检查c h是否小写字母a~z 是,返回1;不是,返回 0 c t y p e . h int ch i s u p p e r int isupper(ch) 检检查c h是否小写字母A~Z 是,返回1;不是,返回 0 c t y p e . h int ch s t r c a t c h a r * s t r c a t ( s t r 1 , s t r 2 ) 检把字符串s t r 2接到s t r 1的后 s t r 1 s t r i n g . h char *str1,*str2 面,s t r 1最后的‘\ 0’去掉 s t r c m p int strcmp)(str1,str2) 检比较两个字符号串 s t r 1 < s t r 2返回负数 s t r i n g . h char *str1,*str2 s t r 1 = s t r 2返回0 s t r 1 > s t r 2返回正数 s t r c p y char * strcpy(str1,Str2) 检拷贝s t r 2串到s t r 1中 s t r 1 s t r i n g . h char *str1,*str2 t o l o w e r int tolower(ch) 检将c h字符转换为小写字符 返回小写字母 c t y p e . h t o u p p e r int toupper(ch) 检将c h字符转换为大写字符 返回大写字母 c t y p e . h 输入输出函数 使用以下函数,应在源文件中使用 s t d i o . h 函 数 名 函数类型和形参类型 功 能 返 回 值 f c l o s e int fclose(fp) FILE *fp f g e t c int fgetc(fp) FILE *fp f g e t s int fgets(buf,n,fp) FILE *fp f o p e n FILE *fopen(filename,mode) char *filename,*mode f p r i n t f int fprintf(fp,format,arg _ l i s t ) FILE *fp; char *format s p u t c int fputc(ch,fp) char ch; FILE *fp 2 4 0 C语言程序设计 下载 关关闭 f p所指文 件,释放文件缓 冲区 关从f p所指文件 读取一个字符 关从f p所指文件 读取一个长度为 (n - 1)的字符串, 存入b u f 关以m o d e方式打 开f i l e n a m e文件 关把a rg _ l i s t的值 以f o r m a t指定的 格式输出到文件 中 关将字符 c h输出 到f p所指的文件 关有错,返回非0 关无错,返回0 关无错,返回所 得字符,有错, 返回E O F 关返回地址 b u f , 若遇文件结束或 出 错 , 返 回 N U L L 关成功,返回一 个文件指针,失 败,返回0 关输出字符的个 数 关成功,返回该 字符,失败,返(续) 函 数 名 函数类型和形参类型 功 能 返 回 值 f p u t s int fputs(str, f p ) char *str, F I L E * f p f r e a d int fread(buf,size,n,fp) char *buf; int size; int n; FILE *fp f s c a n f int fscanf(fp,format,arg _ l i s t ) FILE *fp; char *format; f w r i t e int fwrite(buf,size,n,fp) char *buf; int size; int n; FILE *fp g e t c h a r int getchar p r i n t f int printf(format,arg _ l i s t ) char *format p u t c h a r int putchar(ch) char ch p u t s int puts(str) char *str s c a n f int scanf(format,arg _ l i s t ) char *format 字符屏幕和图形功能函数 字符屏幕处理函数的头部信息在 c o n i o . h中,图形系统的有关函数和原型在 g r a p h i c s . h中。 函 数 名 函数类型和形参类型 功 能 说 明 a r c void arc(x, y, start,end, radius) g r a p h i c s . h int x,y, s t a r t , e n d , r a d i u s b a r void bar(left,top,right,bottom) g r a p h i c s . h int left,top,right,bottom c i r c l e void circle(x,y, r a d i u s ) g r a p h i c s . h int x,y, r a d i u s 附录B C库函数 2 4 1下载 将字符串 s t r写到 f p所指文件 成从f p指向的文 件读取n个长度为 s i z e的数据项, 存到b u f 成从f p所到之处 指的文件按f o r m a t 给定的格式输入 数据到 a rg _ l i s t所 指的内存 成将b u f所指向的 n个s i z e字节输出 到f p所指文件 成从键盘输入一 个字符 成将输出项a rg _ l i s t 的值输出到标准 输出设备上 成输出字符 c h到 标准输出设备 成输出字符串st r 到标准输出设备 成从标准输入设 备按f o r m a t格式输 入数据到 a rg _ l i s t 所指内存 成成功,返回 0失 败,返回非0 成返回实际读取 的数据项个数。 读到文件结束或 出错返回0 成实际输入的数 据个数 成实际写入的数 据项的个数 成所读字符 成输出字符的个 数 成输出字符c h . 成成功,返回换 行符;失败,返 回E O F 成读入并赋个 a rg _list数据个数。遇 文件结束,返回 E O F,出错返回0 以以r a d i u s为半径,以x , y为 圆心,从 s t a r t到e n d画一弧 线 以以左上角 l e f t , t o p到右下 角r i g h t , b o t t o m画一矩形条 以( x , y )为圆心,以r a d i u s为 半径画一个圆 以以( x , y )为圆心,以r a d i u s 为半径画一个圆(续) 函 数 名 函数类型和形参类型 功 能 说 明 c l o s e g r a p h void closegraph(void) g r a p h i c s . h c l r s c r void clrscr() c o n i o . h c p u t s int cputs(str) c o n i o . h const char *str d e t e c g r a p h void detecgraph(drive,mode) g r a p h i c s . h int *drive,*mode f l o o d f i l l void floodfill(x,y, b o r d e r ) g r a p h i c s . h int x,y, b o r d e r g e t b k c o l o r int far getbkcolor(void) g r a p h i c s . h g e t c o l o r int getcolor g r a p h i c s . h g e t f i l l p a t t e r n void far getfillpattren(pa) g r a p h i c s . h char far *pa; g e t g r a p h m o d e int getgraphmode() g r a p h i c s . h g e t i m a g e void far getimage( left,top,right,bottom,buf) g r a p h i c s . h int left,top,right,bottom;void far *buf; g e t t e x t int gettext(left,top,right,bottom,buf) c o n i o . h int left,top,right,bottom;char *buf; g o t o x y void gotoxy(x,y) c o n i o . h int x,y i m a g e s i z e unsigned far imagesize g r a p h i c s . h (left,top,right, bottom,) int left,top,right,bottom; i n i t g r a p h void initgraph(drive,mode,path) g r a p h i c s . h int *drive,*mode;char *path; l i n e void line(sx,sy, e x , e y ) g r a p h i c s . h int sx,sy, e x , e y, e n d u ; o u t t e x t void outtext(str) g r a p h i c s . h char *str; p u t t e x t int puttext(left,top,right,bottom,buf) int left,top,right,bottom;char *buf; r e c t a n g l e void rectangle(left,top,right,bottom) g r a p h i c s . h int left,top,right,bottom; s e t b k c o l o r void setbkcolor(color)int color g r a p h i c s . h 2 4 2 C语言程序设计 下载 以关闭图形工作方式,释 放用于保存图形驱动器和 字体的系统内存 以清除整个当前字符窗口, 将光标定到左上角( 1,1) 处 以把字符串 s t r输出到当前 字符窗口 以确定图形适配器的类型 以用图形块中给定点和形 状块边界线的当前颜色和 模式,填充该图形块 以返回当前背景颜色 以返回当前画线颜色 以 填写由 p a指向的数组, 填写内容为构成当前填充 图案的8个字节 以返回当前图形模式 以把屏幕图形部分拷贝到 b u f指向的内存。左上角为 left,top;右下角为right,bottom 以从左上角度 l e f t . t o p到右 下角 r i g h t , b o t t o m的矩形区 上的字符拷贝到内存 以把字符屏幕上的光标移 动到x,y 处 以返回存储一块屏幕图形 所需的存储器字节数。该 块屏幕左上角为 l e f t , t o p ;右 下角为r i g h t , b o t t o m 以把d r i v e所指的图形驱动 器装入内存,屏显模式有 m o d e确定,图形驱动器路 径有碍p a t h给定 以从( s x , s y )到( e x , e y )画一直 线 以在光标处显示一字符串 s t r 以把由g e t t e x t ( )储存到内存 b u f的字符拷贝到左上角和 右下角的区域 以用当前画线的颜色画一 个左上角为 l e f t , t o p和右下 角r i g h t , b o t t o m的矩形 以改变背景色为 c o l o r所指 颜色(续) 函 数 名 函数类型和形参类型 功 能 说 明 s e t c o l o r void setcolor(color)int color; g r a p h i c s . h s e t f i l l s t y l e void far setfillstyle(pa,color) g r a p h i c s . h int pa,color; s e t t e x t s t y l e void far settextstyle(font,direct,size) g r a p h i c s . h int font,direct,size; t e x t b a c k g r o u n d void textbackground(col)int col; c o n i o . h t e x t c o l o r void textcolor(color)int color; c o n i o . h w i n d o w Void window(left,top,right,bottom) c o n i o . h int left,top,right,bottom; 动态存储分配 A N S I标准建议在头文件s t d l i b . h中包含动态存储分配库函数,但有许多的 C编译用m a l l o c . h 包含。使用时,请查阅。 函 数 名 函数类型和形参类型 功 能 返 回 值 f r e e void free(p) 释放 p所占的内存区 void *p; m a l l o c void *malloc(size) 分配s i z e字节的存储区 被被分配的内存区的地址, unsigned size 如内存不够,返回 0 类型转换函数 函 数 名 函数类型和形参类型 功 能 返 回 值 a t o f float atof(char *str) 把由s t r指向的字符串转换为实型 f l o a t a t o i int atoi(char *str) 把由s t r指向的字符串转换为整型 i n t a t o l long atol(char *str) 把由s t r指向的字符串转换为长整型 long int 附录B C库函数 2 4 3下载 以设置当前画线颜色 以为各种图形设置填充式 样和颜色 以为图形字符输出设置当 前的字体、方向和字符大 小 设置字符屏幕的背景 设置字符屏幕下的字符颜 色 用于建立字符窗口

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 20 金币 [ 分享文档获得金币 ] 3 人已下载

下载文档

相关文档