从问题到程序练习
前言
程序设计在计算机科学教育中的重要性是无庸置疑的。人们一直在思考第一门程序设计课应该教授什么?其教材应该是怎样的?在用C语言作为第一门程序设计课的教学语言的过程中,我编写了这个教材,是对于自己教学经验的总结。
在教学过程中学生们提出了许多问题。它们使我看到,作为这门课程所用的教材(或适合自学者的程序设计读物),需要对学习中可能遇到的问题给出合理而充分的解释,无论是关于C语 言本身,还是关于程序设计过程,以使读者和学生在阅读或复习时能从中了解问题的实质,而不是自己在模糊中去设法感悟。这些对于帮助初学者尽快进入程序领域 是非常重要的,也使教师在教学中有更大的自由度,更灵活地选择实例和教学方式。此外,由于程序设计的性质,基础书籍和课程中的实例不应采用“提出问题,给 出解答,再加点解释”的简单三步形式,而应着重帮助初学者认识程序设计活动的实质,理解从问题到程序的思考过程。本书在这些方面做了些努力,包含了许多解 释和较详细的说明。
本书编写的一个想法是要反映程序 设计的两个重要特征,其科学性和工程性。科学性指程序的构造过程应有充分的科学依据:分解要解决的问题,看清各部分的意义和互相联系,这些都需要编程者对 程序实现过程有科学的认识,有关研究发展形成了程序的理论。虽然初级课程或书籍里不能去深入讨论有关理论和成果,但却必须反映其精神实质,使初学者从开始 就接触程序及程序语言的一些本质性问题。这一点,无论对于提高读者对计算机科学技术认识水平,还是对他们的进一步深入学习,都是非常重要的。
由于计算机的日益普及,不少人在 进大学前就有了用计算机的经验,有些人已将计算机玩得很熟,甚至写过许多程序。但也应看到另一面:在学习大学课程前,学生中对程序及程序设计已建立起较正 确的认识的人很少。即使是对计算机已经很熟的学生,也有许多观点需要建立,或有错误观点需要纠正。这也是本课程中应该特别强调科学性的原因。
我发现学生常有一些对计算机实质 的不正确认识,有些看法可能他们也没有清楚的意识。例如有些学生在思想深处把计算机看成一种很不驯服的东西,程序设计在其头脑深处就是设计一些计谋, “骗”计算机帮人干活。有些学生把程序设计看成是玩某种计算机游戏,这里到处是陷阱和妖魔,需要在试探和失败中找一条绕过障碍的路。
日益完善的程序开发环境也可能成 为学习程序设计的障碍。很好的环境造就出一类学生,他们对系统玩得很熟,键盘也打得很快。遇到程序工作时不做细致分析和设计,粗一想就动手,很快写出一大 堆代码,随后进入一种三步工作循环:运行、追踪、打补丁改错。简单程序很快也就这样做出来了,但质量都很差,一个简单问题常常写出一大篇,自己也说不清解 决问题的过程。即使熟悉程序的人,不花点工夫也难判断这类程序的对错,错在哪里。问题复杂时,这些同学就遇到了困难,写出的程序很难通过测试,弄不清错误 出在那儿,该怎样修改。这种程序像建在沙滩上的大楼,无论如何修补都不行,最合理的建议就是推倒重来。这些情况的提示是:从简单程序开始就应该注意程序设 计的正确途径。
上述情况都说明在书籍和课程中强 调程序设计过程的科学性的必要。本书从开始就强调问题的分析和分解;反复讨论了函数抽象方法,讨论程序实例时也特别注意这方面问题,后面章节不断有进一步 的论述。书中一些地方还通过程序实例介绍了程序理论中的一些更深入的问题,如通过对程序运行时间的测试介绍计算过程的基本性质(复杂性);通过对循环过程 是否完成了所需工作的分析,介绍“循环不变关系”的概念和意义等。当然,对这些问题都没有深入讨论,只是希望读者了解有关情况,作为思考程序问题的线索。
程序设计的另一方面特征是其工程 性。这里不是指规模大,参加人员多等等,而是从另一个角度看问题。在工程设计中需要对问题的分析和理解,寻找可能解决方案,对各种方案做出评价和选择。应 对所做的选择有清醒的认识(它们的优点和缺点,是否对要解决问题的某些方面有所偏向或不足等),并进一步确定具体实施方案。这些问题在程序设计中都
有直接 反映。如果不是把学习程序设计的目的放在解决几道典型程序题目上,而是要帮助学习者提高能力、真正理解程序设计过程的话,这些问题都需要特别强调。
本书的叙述和实例都力图强调这样 的观点:对于一个程序问题,并没有需要记住的标准答案。由于问题分析时的不同考虑,程序设计过程中的不同决定或选择,对同一问题,人们常会得到许多合理的 “能解决问题”的程序。这些程序常是各有长短,可能有侧重点不同,也可能反映了对问题的不同认识。我们应特别注意解决问题的方法,包括如何分析问题,如何 逐步把要解决的问题弄得更清楚明确,如何寻找可能的求解途径,把复杂问题分解为相对简单的步骤,如何确定可用程序语言结构并从中做出选择等。这里的每一步 都可能产生分支,而在做选择时,则应该弄清选择的后果,无论是收获还是损失。
工程中往往没有十全十美的选择, 更多的是权衡和折衷。这些对程序设计也是本质性的。本书对许多实例给出了较详细的分析过程,有时对一个问题给出多个解答,有时指出了其他可能性:还可能如 何看问题,还可能如何做等等。还常常给读者提出一些问题,希望读者发挥自己的思维能力和主观能动性。各章节后面的练习也试图反映这些看法。
这样做的目的是希望读者能建立一 种认识:程序设计是这样一种工作,这里并没有什么“标准答案”。程序设计过程中要追求的是比较好的“正确”答案,而书籍中给出的答案(包括本书)不过是作 者对问题理解和分析的反映,还有很大可能的选择。进一步说,我们还希望读者养成这样的习惯:在看别人的样例程序时,应分析其中隐含了程序作者的哪些考虑和 选择,哪些是合理的有价值的(或不合理的没有价值的)?哪些地方还可能有什么选择?沿着其他选择走下去可以得到什么(或失去什么)?如此等等。如果读者能 养成这种思考习惯,必将从中受益无穷。当然,这并不是说书中的例子不重要。恰恰相反,正因为在程序设计过程中有许多选择的可能性,书中应当给出一些合理的 好的选择,供读者参考。对于初等的入门书籍,应当同时说明为什么这样做的理由。如果可能,还应当指出采取这些选择带来的问题(缺点、造成的限制等)。
正如本书的书名所言,程序设计是 “从问题到程序”的工作过程。这个工作既要求遵循严格的科学方法,又要求谨慎灵活的工程能力。要很好地完成程序设计工作,编程序的人既需要充分发挥自己的 聪明才智,又需要有细致认真、一丝不苟的工作态度。即使是将来不从事计算机程序方面的工作,通过这个课程得到的锻炼也可能非常重要,尤其对于理科学生,这 个课程可能对于弥补其工程方面训练的不足有所帮助。
前些年第一门程序设计课常用PASCAL、FORTRAN或BASIC等语言。目前这一课程的教学语言已转向C语言或其他类C语言(如C++等)。从作为第一门程序设计课的教学语言的角度看,没有任何程序语言具有无可比拟的天然优势,选择任何语言都应考虑其有利方面,也需要克服这样做带来的不利之处。本书是以C作为基础语言编写的教材。
选择C语言作为教学语言的主要理由有:C语言是目前实际程序设计工作中使用最广泛的语言之一,它包含了程序设计需要理解和使用的基本程序机理和主要机制。掌握这些机制就可以理解程序与程序设计的主要问题,完成程序练习,得到有关的知识积累和能力锻炼。目前有许多软件系统是用C编写的,或基本上是用C编写的。以C语言作为基本语言,不但能学习程序设计,同时也能掌握一种实用的程序设计工具。另一方面,程序设计是计算机领域的基础课程,学习C语言程序设计之后,进一步学习后续课程都比较方便。C语言适合(也正在被)作为计算机领域许多后续课程的教学语言。第三,C语言是一种很灵活的语言,用C语言写程序时常需要了解一些细节,这是人们对用C语言作为基础课语言的一个主要疑虑。但问题也有另一面,通过对C语言程序设计的较好理解,学习者可能对计算机领域的许多问题有更深的认识。这个问题实际上对课程组织、教材和教师提出了较高的要求。此外,C语言的程序既可以在较高层次上做,也可以在较低级的层次上做,学生可能从中更多了解程序设计过程中的各种问题。此外,许多后续程序语言从C语言借鉴了想法和描述方式,有些本
身就是C语言的扩充和发展,C语言的知识对于进一步了解掌握许多其他语言,包括未来的新语言都是有价值的。
在撰写本书时我心中有几个努力追求的目标,列在这里供读者和同行参考:
1. 假定读者(学生)没有程序设计经验,或只有很少经验。因此书中对在学习程序设计可能遇到的各种基本问题,各种概念和观点都应该尽量给出清楚的解释。这一考虑的目的是希望本书适合作为第一门程序设计和
C语言程序设计的教材。
2. 以讲授程序设计为基本线索,同时对
C语言做深入的介绍和解释。本书希望强调如何认识程序、写程序,如何用C语 言写程序。因此对从问题出发,经过分析逐步写出程序的过程有许多深入讨论。书中实例强调的是问题分析和分解,设计求解过程,找出主要步骤,确定函数抽象, 找出循环,选择语言结构,最后写出程序的过程。书中不少实例给出了在不同考虑下可能形成的多种解法,以帮助读者理解程序设计的真谛。
3. 强调好的程序设 计风格,强调通过函数抽象建立起清晰的程序结构的重要性。书中很早就介绍了函数概念,从库函数使用到简单函数的书写,再到函数的确切定义。书中特别强调程 序的结构性、可读性、易修改性,程序实例也努力遵循这些原则,使其简洁清楚易读。书中还根据进展和遇到的问题分析了一些不良程序设计习惯及其危害。
4. 注意强调“好的”
C程序设计及C语言描述方式。由于历史原因C语言成为一个不太严格的语言。如不注意,用C写的程序常会隐含不易发现的错误,这是把C作为第一个语言时需要解决的问题*。在ANSI C标准的基础上,存在着一套写“好的”C程序的方式。本书力图坚持ANSI C所倡导的正确程序写法,强调如何写更可靠、不易包藏隐含错误的C程序的各方面问题,并通过实例说明了应该如何写和不应该如何写等等。在坚持了上面这些原则的基础上,书中也介绍了C语言的许多实用程序设计技术。总之,本书希望强调的是如何写出正确、清晰、简洁、高效的C程序。
5. 对C语言的各种结构和机制都有较细致介绍,因为其中不少问题反应了相关领域的普遍性知识和情况。本书力图对许多问题给出细致解释,不仅讲它们是怎样的,还提供了背景理由,以帮助读者理解问题实质。书中对程序设计和C语言中反映出的程序语言和程序设计的一般性问题,及计算机科学中的一般性问题也做了适度介绍和解释。
本书包括如下各章和若干附录:
第一章,程序设计与C语言。这章首先介绍了程序与程序语言的概念,C语言的发展及其特点。用一个小例子介绍了C程序的形式,C程序的加工和执行。最后介绍程序设计与开发过程,其中的各种问题,如调试、追踪、错误的排除等。
第二章,数据对象与计算。讨论程序语言的许多最基本概念,包括:字符集、标识符和关键字,数据与类型,数据表示,运算符、表达式与计算过程,数学函数库的使用等。
第三章,变量、函数和控制结构。讨论基本程序设计的一些问题,包括基本语句与复合结构,变量的概念和使用,简单函数的定义,程序中逻辑条件的描述与使用等。最后介绍了几种基本的控制结构。
第四章,基本程序设计技术。本章首先分析了循环程序设计的基本问题,通过一系列程序实例,分析了循环的构造过程。此后介绍了C语言其他控制结构及其使用。
第五章,C语言程序结构。本章讨论了C语言许多具有一般性的重要问题,主要是C程序结构,函数概念及有关的问题,预处理命令和预处理过程等。
第六章,数据对象的顺序组合:数组。介绍数组概念、定义和使用,数组的处理,数组与函数的关系,两维和多维数组等。
第七章,指针。首先介绍指针的概念和指针变量的使用,介绍了C语言中指针与数组的关系等,多维数组作为参数的通用函数等。而后讨论了动态存储管理,类型定义,指向函数的指针等一系列问题。
第八章,输入输出与文件。本章讨论了文件的概念,与输入输出有关的各种问题,标准库的输入输出功能,以及输入输出的程序设计问题。
第九章,结构及其他数据机制。介绍结构(struct)、联合(union)、枚举(enum)等数据定义机制的意义及在程序中的使用。随后简单介绍了链接结构的概念。
第十章,程序开发。本章讨论程序设计和开发中的许多一般性问题和技术。包括C程序的分块开发问题等。
第十一章,标准库函数。介绍标准库提供的各方面功能及其相关知识。
最后有几个附录和一个比较详细的索引。
本书以标准C语言为基础,有关内容不依赖于任何具体C语言系统。因此在这个课程中(本书阅读中)可用任何符合ANSI C标准的C系统作为程序工作环境。目前国内常用的微机上的各种C语言系统,工作站或其他计算机上的C系统都可以用。虽然软件厂商在不断推出新版本的程序开发环境,但从学习基本程序设计的角度看,新版本的开发环境未必比早些的版本有多大改善,但通常它们更复杂、占用更多的计算机资源,尤其是初学者入门更困难。由于本书的所有例子程序都按ANSI C标准书写,习题中不牵涉到任何具体的系统环境。因此本书的学习并不要求复杂的环境支持,建议读者使用比较简单基本的系统作为学习工具,例如国内使用较多的TURBO C 2.0系统等,一些公开的免费C语言系统等。本书的最后附了对TURBO C 2.0系统的简单使用说明。
我特别感谢北京大学数学学院和北京大学理科试验班参加过我的课程的同学们和参加过课程辅导的研究生们,是他们的思考和提出的问题给了我许多启示,使我更深入地理解了许多问题。我也要感谢我的家人与同事在这些年工作中给我的支持。
虽然本书包含了我几年的工作和思考,但书中难免有大的或小的错误,这些都由我个人负责。希望读者能把发现的问题告诉我,也希望同行们对本书提出宝贵意见。
练习1
1. 设法找一找有关程序语言发展的书籍或者文章,或者计算机辞典的有关条目,读一读,了解程序语言的历史、发展、现状等方面的情况。
2. 设法找到
ANSI C标准或者中国国家标准GB/T 15272-94《程序设计语言C》,浏览这些标准的目录,了解在定义一个程序语言时需要说明哪些东西。
3. 熟悉自己学习
C语言程序设计时准备使用的编译系统或者集成开发环境,了解这个系统的基本使用方法、基本操作(命令式或窗口菜单的图形界面方式),弄清楚如何取得联机帮助信息。设法找到并翻阅这一系统的手册,了解手册的结构和各个部分的基本内容。了解在该系统中编一个简单程序的基本步骤。
4. 输入本章正文中给出的简单
C程序例子(注意程序格式),在自己所用的系统中做出一个C语言源程序文件;对这个源程序进行加工,得到对应的可执行程序;运行这个程序,看一看它的效果(输出了什么信息等)。
5. 在第4题完成的程序里各种地方随便加入一些空格、制表符、换行等字符,看看在什么情况下会出现问题,什么情况下不会。如果出错,请仔细考察系统产生的出错信息和自己所做修改的关系,学习阅读系统的错误信息行。
6. 对第4题完成的正确程序中随意做一点修改(正文中提出的错误例子,或者其他修改),看看编译加工过程中会得到什么信息。弄清错误信息行和自己所做修改的联系,随后重新把程序修改正确。重复上述步骤。
7. 修改上述程序里位于两个双引号之间的字符序列,看看程序的输出发生了什么变化。修改程序使它能输出你想要的东西。
8. 如果你所用的系统中可以用中文,在上述程序的一对双引号之间写一句中文,看看程序能否正确加工,程序的输出是什么,能否正确输出中文信息。
练习2
1. 指出下面的哪些字符序列不是合法的标识符:
_abc x+- 3x1 Xf_1__4 Eoof___
a$#24 x__x__2 bg--1 ____ I am
2. 手工计算下列表达式的值:
1)125 + 0125 2)0XAF - 0XFA
3)24 * 3 / 5 + 6 4)36 + - (5 - 23) / 4
5)35 * 012 + 27 / 4 / 7 * (12 - 4)
3. 在下面表达式的计算过程中,在什么地方将发生类型转换,各个转换是从什么类型转换到什么类型,表达式计算的结果是什么?
1)3 * (2L + 4.5f) - 012 + 44
2)3 * (int)sqrt(34) - sin(6) * 5 + 0x2AF
3)cos(2.5f + 4) - 6 *27L + 1526 - 2.4L
4. 写程序计算第3题中各个表达式的值。
5. 写程序计算下面各个表达式的值:
1)
5)
8) 2) 3) 6) 9) 4) 7) 10) 7)
6. 已知铁的比重是
7.86,金的比重是19.3。写几个简单程序,分别计算出直径100毫米和150毫米的铁球与金球的重量。
7. 写程序计算的两个根,考虑用合适的方式输出。(提示:对这个具体问
的值,以此作为已知信息,就可以写出程序了。) 题上,由于人可以先计算出判别式
8. 在计算机上试验本章正文中的一些程序。对它们做一些修改,观察程序加工和运行的情况,并对程序的行为做出解释。
9. 在一个能正确工作工作的输出整数结果的程序里,将
printf的相应转换描述改为 %f 或者 %ld,看看会出现什么问题。在一个能正确工作工作的输出双精度结果的程序里,将printf的相应转换描述改为 %d 或者 %ld,看看会出现什么问题。
练习3
1. 下面的字符序列中哪些不是合法的变量名:
-abc __aa for pp.288 to be
6. 对第4题完成的正确程序中随意做一点修改(正文中提出的错误例子,或者其他修改),看看编译加工过程中会得到什么信息。弄清错误信息行和自己所做修改的联系,随后重新把程序修改正确。重复上述步骤。
7. 修改上述程序里位于两个双引号之间的字符序列,看看程序的输出发生了什么变化。修改程序使它能输出你想要的东西。
8. 如果你所用的系统中可以用中文,在上述程序的一对双引号之间写一句中文,看看程序能否正确加工,程序的输出是什么,能否正确输出中文信息。
练习2
1. 指出下面的哪些字符序列不是合法的标识符:
_abc x+- 3x1 Xf_1__4 Eoof___
a$#24 x__x__2 bg--1 ____ I am
2. 手工计算下列表达式的值:
1)125 + 0125 2)0XAF - 0XFA
3)24 * 3 / 5 + 6 4)36 + - (5 - 23) / 4
5)35 * 012 + 27 / 4 / 7 * (12 - 4)
3. 在下面表达式的计算过程中,在什么地方将发生类型转换,各个转换是从什么类型转换到什么类型,表达式计算的结果是什么?
1)3 * (2L + 4.5f) - 012 + 44
2)3 * (int)sqrt(34) - sin(6) * 5 + 0x2AF
3)cos(2.5f + 4) - 6 *27L + 1526 - 2.4L
4. 写程序计算第3题中各个表达式的值。
5. 写程序计算下面各个表达式的值:
1)
5)
8) 2) 3) 6) 9) 4) 7) 10) 7)
6. 已知铁的比重是
7.86,金的比重是19.3。写几个简单程序,分别计算出直径100毫米和150毫米的铁球与金球的重量。
7. 写程序计算的两个根,考虑用合适的方式输出。(提示:对这个具体问
的值,以此作为已知信息,就可以写出程序了。) 题上,由于人可以先计算出判别式
8. 在计算机上试验本章正文中的一些程序。对它们做一些修改,观察程序加工和运行的情况,并对程序的行为做出解释。
9. 在一个能正确工作工作的输出整数结果的程序里,将
printf的相应转换描述改为 %f 或者 %ld,看看会出现什么问题。在一个能正确工作工作的输出双精度结果的程序里,将printf的相应转换描述改为 %d 或者 %ld,看看会出现什么问题。
练习3
1. 下面的字符序列中哪些不是合法的变量名:
-abc __aa for pp.288 to be
IBM/PC
while ms-c r24_s25 #micro __a__b m%ust a"bc tihs _345
2. 假设整型变量a的值是1,b的值是2,c的值是3,在这种情况下分别执行下面各个语句,写出执行对应语句后整型变量u的值。
1) u = a ? b : c;
2) u = (a = 2) ? b + a : c + a;
3. 假设整型变量a的值是1,b的值是2,c的值是0,写出下面各个表达式的值。
1) a && !((b || c) && !a)
2) !(a && b) || c ? a || b : a && b && c
3) !(a + b
4. 下面程序在执行时,哪些地方将发生类型转换?程序打印的值是什么?
int f (int n, float m) {
return (m + n) / 4;
}
int main (void) {
float y = 3;
printf("%d\n", f(y, y + 1));
return 0;
}
5. 在计算机上试验本章正文中的一些程序。对它们做一些修改,观察程序加工和运行的情况,并对程序的行为做出解释。
6. 定义求圆球的体积、求圆球的表面积、求圆柱体的体积、求圆柱体的表面积的函数。
7. 1)不用函数,直接写一个主程序计算并输出直径为100毫米和150毫米的金、银、铜、铁、锡球的重量(以kg为单位输出)。
2)重新完成上面程序,先定义一个带有两个参数的函数,它能求出直径为x的比重为y的圆球的重量,而后在主程序里调用这个函数完成所需工作。将这样得到的解与不用函数的解比较,比较它们的长度、容易出错的程度。假设现在要求修改所用圆周率的精度,考虑用两种方式写程序的修改难度。
3)请写程序,求出边长为100毫米和150毫米的金、银、铜、铁、锡立方体的重量。你可以利用前面的程序吗?是否很容易修改前面程序,完成这一计算?比较不用函数的解法和使用函数的解法在易修改和重复使用方面的效用。
8. 如果四边形四个边的长度分别为、、、,一对对角之和为,则其面积为:
其中。定义一个函数计算任意四边形的面积。设有一个四边形,其四条
,写程序计算它的面积。 边边长分别为3、4、5、5,一对对角之和为
9. 定义函数:double tmax(double, double, double),它返回三个参数中最大的一个。写一个主函数试验各种参数情况。
10. 写函数,它以两个电阻的值作为参数,求出并联的电阻值。
11. 修改已知四边长求四边形面积的函数,增加对各种参数错误情况的检查和处理(如返回值0),用各种实例数据检查你的函数否检查出所有可能的错误情况。
12. 分析本章正文中给出的求二次方程根的函数,看它缺乏对哪些特殊情况的处理。补充这些处理,在需要时输出适当的信息,使之成为一个更完整的函数。写一个主函数,用各种特殊情况和一般情况测试所完成的函数。
13. 写一个简单程序,它输出从1到10的整数。
14. 写一个简单程序,它输出从10到-10的整数。
15. 写一个两个整型参数的简单函数,它输出从第一个整数到第二个整数为止的整数序列。
16. 用定义函数double power(double x, int n),它求出x的n次幂。用主函数试验很大的n值(例如令x值为1),看看会出现什么情况;用大的x和n值,看看发生浮点数计算溢出时会出现什么情况。
17. 写一个程序,它在0~90度之间每隔5度输出一行数据,打印一个表。每行中包括5个项目:角度数,以及它所对应的正弦、余弦、正切、余切函数值。
18. 查看有关公式,写求解并输出一元三次方程的根的函数。
19. 写出求等差级数的和
的函数。两种循环结构给出函数定义,再利用等差级数求和公式给出函数定义。
20. 请到查出银行一年定期存款的利率和5年定期存款的利率。假定现在要存入100元钱,存款到期后立即将利息与本金一起再次存入。请写出程序,计算按每次存一年和按照每次存5年,总共存50年后两种存款方式的得款总额。对两种情况都每隔5年输出一次当时的总金额。
21. 写一个程序打印出2的顺序各次幂。让它打印出2的前30个幂,看看会出现什么情况。用一个条件为真的循环打印2的各次幂,看看会出现什么情况。
练习4
1. 1)写出通过递推方式求200之内的完全平方数的程序;2)写出只使用加法的求完全平方数的程序;3)写出求1000之内的完全立方数的程序,请参考书中实例的写法和上面的两种写法,分别写出相应的求立方数的版本。
2. 试验正文中乌龟旅行的实例,看看在你所用的C系统上得到什么样的结果。从数学教科书中找出有关调和级数的理论结论,并将它与我们的试验做一个比较。
3. 写一个程序,计算并输出Fibonacci序列中一系列的相邻项之比。确定一个范围,观察输出的结果,能够得到什么结论(这个比的序列可能有极限吗?极限是什么)。查阅有关资料,了解有关的理论结果。
4. 写函数计算 1! + 2! + ... + k!。用主函数试验函数对一系列k值计算出的结果。你写出的函数对1到10计算结果都正确吗?如果出现错误,弄清楚是什么原因。这个程序能对k = 30得到正确结果吗?(另外,你能只用一重循环完成函数的定义吗?)
5. 写函数计算:,公式中有n层嵌套。利用这个函数打印 x = 1.0、2.0、„、20.0,n = 10时的函数值表。
6. 实现书中讨论的验证哥德巴赫猜想的程序,用不同的n对 6~n 的范围试验该程序。去掉程序中的打印输出语句,增加计时功能,对不同的n运行程序,考察程序的运行时间,画出一条曲线,说明运行时间与n的关系。
7. 设法(从文献中)找到其他更有效的素数判断方法并实现对应函数。在一个数值比较大的整数区间试验书上给出的函数和你写的其他函数,利用它们打印出这一区间中的所有素数。你所试验的几种方法在工作效率上有明显差异吗?(为程序计时)
8. 定义函数:void prt_factors(int),它对正整数实参输出其所有的因子。
9. 定义函数:void prt_pfactors(int),它对正整数实际参数,输出其所有的素因子(多重因子重复输出);对于负参数,首先输出-1,然后输出所有因子。
10. 已知
,利用该公式编程序求
的近似值,看用这个和式的前多少项求出
,令程序输出三项数据:计算得到的和,由这个和改为
、并重新试验,用计时的近似值与3.14159165的误差小于
求出的 的近似值,求得该和所用得项数。把
方式总结出误差减小与执行时间之间的关系。
11. 修改书中计算sin值的函数,使之能输出计算中循环执行的次数。用不同的数值(一个比一个大)试验这一函数,观察出现的情况。你看到出现溢出的情况了吗?(为试验方便,你应该写一个适用的驱动程序)
12. 已知
asinh(double) 计算
的近似值。 (x
13. 考虑不用函数(例如isprime)直接写出对6~200的偶数验证哥德巴赫猜想的程序(利用循环、条件、break语句等,不使用goto语句)。将这样写出的程序与用定义函数的方式写出的程序比较。例如考察两个函数的行数、字节数,其中各种控制结构的嵌套深度,控制结构使用的个数等。
14. 辗转相减求最大公约数的递归定义是(其中):
利用这个定义,用递归和循环方式各写出一个求最大公约数的函数。
15. 对一些n值试验河内塔程序,给它们计时。据此估计你所用的计算机搬完64个金盘需要多长时间。如果僧侣们1秒钟搬金盘1次,搬完64个金盘需要多少时间?将这一时间与科学家对宇宙的估计寿命做个比较,据此评价僧侣们的说法。
16. 一个三位的十进制整数,如果它的三个数位数字的立方和等于这个数的数值,那么它就被称为一个“水仙花数”。定义函数判断一个整数是水仙花数,并利用这个函数打印出所有的水仙花数。
17. 对一个整数,如果其所有因子(包括因子1在内)之和正好等于这个数,那么就称它为“完全数”。因子之和小于自身的数称为“亏数”;因子之和大于自身的数称为“盈数”。写一个函数,当其参数是亏数时返回负值,是完全数时返回0,是盈数时返回正值。利用这个函数求出1000以内的所有完全数(实际上只有1、6、28、496)。为这个程序计时:从100开始每隔100做一次计算,写一个循环,输出各次计算花的时间。再从1000开始,每隔1000做一次计算直到10000为止,输出对程序执行计时的值。利用所定义的函数对一段区间的整数做一个分类,输出其中各个数所属的类。
18. 写程序由标准输入得到一系列三个一组的数,把每组数作为三角形的三边长,计算三角形的面积。注意在程序里检查输入数据,对不能构成三角形的情况给出错误信息。仔细分析自己的程序,能否检查出所有不合理数据。用不同数据运行试验。
19. 写一个程序,它读入一系列整数,最后输出其中最大的两个数。
20. 写一个程序,它输出所读入的一系列整数的平均值。假定给它的第一个数并不是数据,而是用于说明数据的项数。
21. 假设程序由输入得到的一系列正实数是一条折线在x等于0,1,2,„的对应值(数据的数目事先并未确定),请求出这一折线与x轴之间区域的面积。
22. 能够组成直角三角形三个边的最小一组整数是3、4、5。写程序求出在一定范围里所有可以组成直角三角形三个边的整数组,输出三个一组的整数。设法避免重复的组。
23. 改造本章正文中讨论的计算器程序:
1. 增加计算一行后的处理,使出现在正确表达式之后的任意字符序列都不会影响下一表达式的计算(抛弃正确表达式之后的字符)。
2. 修改程序,使之在数值、加号之间出现任意个空格时都能正确计算。
3. 修改程序,使之不仅能处理加法,还能处理其他算术运算符(提示:记录运算符,用if或者switch判断、计算和输出)。
24. 仿照本章中的单词统计程序,写一个统计C程序里标识符个数的程序。程序里可以利用标准库提供的字符分类函数:
int isalpha(int c) 当c是字母的编码时,返回非0值;
int isdigit(int c) 当c是数字的编码时,返回非0值。
25. 使用这两个函数时,应在程序文件前部写 #include 。
26. 定义下面的计算规则:遇到1就终止;如果遇到1之外的奇数,求出它乘3加1的值作为下一个数;如果遇到偶数,求出它除以2的值作为下一个数。问题是:对每个正整数,反复使用这套规则,最终是否都能得到1。也就是问,按照这个规则定义的递推序列是否对每个正整数都终止(这个著名问题至今也没有结论)。请按上面的规则定义一个函数,它返回对参数计算直到终止所经历的计算步数。对一系列参数试验这个函数。注意:序列中的数可能变得很大,请设法检查溢出,遇到溢出时打印信息。
27. 考察目前银行对各种期限的储蓄利率。写一个函数,它能够计算出对给定的时间(例如8年,10.5年等,以半年为基本单位)如何分段将能够得到最大的收益。
28. 为本章正文中的某些程序实例写出好用的测试驱动程序。为你所做的某些练习写出好用的测试驱动程序,并借助于它们仔细检查你所定义的函数。
练习5
1. 检查你所用的C语言系统,弄清其中各种数值类型的表示范围。
2. 利用字符分类函数,写一个程序统计一个文件里出现的十进制整数的个数。另写一个程序统计文件中出现的十六进制整数的个数。
3. 一对实数可以表示平面上一个点的坐标。一系列实数对,当数对的第一个值为递增时,将它们表示的平面点连接,可得到一条与 x轴同方向的折线。由一条这种折线、该折线两端引x轴的垂线、x轴本身能够形成一个封闭区域的边界。写一个程序,它接受一系列由标准输入得到的数对,计算出该区域的面积。用一对零(两个0.0)表示输入结束。假定输入的数对中x值总是递增的,y的值均不为负。如果y值可以为负,修改程序使它也能计算正确。
4. 定义函数判断一个点与坐标原点的距离是否小于1,是否在单位圆内。写一个通过蒙特卡罗方法计算圆周率值的程序:每次计算随机生成两个0与1之间的实数(利用标准库随机数生成函数产生这种实数),看这两个值形成的点是否在单位圆内。生成一系列随机点,统计单位圆内的点数与总点数,看它们之比的4倍是否趋向值。生成100、200、„、1000个数据点做试验。
5. 1)利用本章字符图形的基本函数,写出打印实心和空心三角形、正方形、六边形的通用函数(设计适当的参数)。设法写出能打印出空心和实心圆形(或者椭圆形)的函数。
2)利用本章有关字符图形的基本函数,定义一些画各种字符图形的有用的功能函数,并利用它们画出一些你所感兴趣的图形。
3)改写上述函数,使它能用某个由参数给定的符号做输出。改造上述函数,使利用它能够用字符A在若干输出行中拼出一个大的A。对其他几个字母、数字做同样的事。
6. 修改正文中的函数format,使它带一个参数n,并能以n次调用形成一个周期,前n-1次调用各输出一个空格,第n次调用输出一个换行符。
7. 设计一些程序,设法检查5.4.5节定义的random函数的随机性是否令人满意。
8. 写出一些数学函数的定义,并用它们和你自己确定的区间去试验5.4.7节中定义的求根函数。检查所得到的根是否令人满意。
9. 请实现一积分函数,使它能求出一个定义好的数学函数在某区间的数值积分值。试采用矩形方法、梯形方法,考察它们在不同分割情况下的表现,加细分割对积分值的影响。用不同的数学函数试验程序。如果函数在积分区间出现奇点,程序将出现什么问题?考虑有什么处理办法。
10. 用牛顿叠代法求方程
是
的根的迭代公式是:,公式里的
的微商。由某个初始值 出发,反复使用上面公式可以求出方程根的近似值。 二分法求根的初始情况与弦线法类似,也是从两端点函数值异号的一个区间开始,每次求出区间中点,从中点函数值的符号决定取左边半区间还是右边半区间作为缩短的区间。不断重复这个手续,直到得到满意的结果。
写出用牛顿叠代法、二分法求函数根的函数。用它们求一些方程(多项式方程或包含超越函数的方程)在某点附近或某对点之间的根。考察方程情况与循环次数的关系(这就需要统计程序循环执行的次数)。
11. 请写出如下程序:
1)统计输入文件(由标准输入得到)的行数和空格数。
2)分别统计输入文件里的元音字母和辅音字母。
3)分别统计输入文件里的非空白字符个数和非空的行数。
12. 写出如下几个程序,完成从标准输入到标准输出的复制:
1)在遇到的连续空格字符时只输出一个空格字符。
2)每个词放在一行。
3)把输入中的每个tab字符换成4个空格字符。
4)把输入中的连续4个空格字符换成一个tab字符。
13. 写出下面的处理C语言源程序的程序,它们都由标准输入取得信息(通过重新定向可以把C源文件送给这些程序),结果送到标准输出。注意,在被处理程序里可能出现注释、字符串常量(如"/*")、字符常量(如'"')、字符或者串字符里的换义字符(例如字符串"Here the \" is not the end of a string")等,应当考虑如何处理。编译加工各程序转换的结果,以检查程序的正确性。
1)写程序读入C源程序,向标准输出写删除程序中所有的注释后的结果。
2)写程序删除C程序中所有无用字符,使其达到最短并保持程序意义不变。
3)修改前面完成的统计C源程序中标识符总个数的程序,考虑程序中的注释、字符串、字符常量等的影响,保证函数统计结果的正确性。
14. 要取得0到n - 1的随机数,一个可能的办法是用表达式rand() % n。这种方法得到的数足够随机吗?取n值为2、3、4等做试验。如果在某种情况下得到的结果不够随机,请设法找出一种利用能rand()得到令人满意的随机数的方法。
15. 请写出函数int setbits(int x, int p, int n, int y),它求出把整数x从左端数第p位开始的n个位用y的最右边n个位替换,而其他的位都不变,得到的整数。
16. 请写出函数int right_rot(int x, int n),它求出将x向右方向循环移n位得到的数(右循环移位:总把从右端移出的位值填补到左边空出的位)。
17. 请设计一些加密和解密方法,并定义出相应的加密程序和解密程序。 注意:由于加密和解密是将产生出变换后的字节编码,有时你会遇到一些奇怪的现象。例如,你的加密程序可能将某个字符转换成换行符号或者制表符。另一更特殊 的情况是,将某个字符转换成了文件结束符。这些都可能导致你无法正常显示文件的内容,或者影响你的解密程序的正常运行。要克服文件结束符带来问题,请用feof函数判断文件结束(要想这样做,请阅读本书后面有关文件处理的讨论)。
18. HTML网页中有一部分是真正的网页信息内容,另外有大量的形式是由一对“”括起的HTML标注。(1) 请写一个程序,它能删除由输入得到的HTML网页里的所有标注,只留下基本信息。(2) 设法留下原页面里的换行信息,使得到的结果更容易读。
19. 在HTML网页里常常有一些对其他网页的链接。请写一个程序,它能抽取出网页中所有的链接,一行一个地显示出来。
第6章练习
1. 写一个程序,它能够计算并输出杨辉三角形(帕斯卡三角形)前面的n行。
2. 写程序统计输入文件中各字符出现的频率,并打印输出一个频率图,用形象的方式显示各个字符在文件中出现的数目。首先写出用横向的图显示的程序,然后考虑如何做出纵向的字符频率图。
3. 写一个程序,它从标准输入读入字符文件,输出其中所有的字符数不超过10个的行。
4. 修改正文中的“筛法”程序,使它在循环中确定了素数时就直接输出。将这样写出的程序与正文中的程序做一些比较(从简单,清晰等各种角度)。
5. 1)写一个把数字字符串转换成整数的函数,它只有一个字符数组参数。2)为这个函数增加一个表示“基数”的参数。3)类似地,写一个把合乎C语言实数文字量形式的字符串转换成一个双精度数的函数。
6. 写函数 squeeze(char s1[], char s2[]),它从字符串s1中删除串s2里包含的所有字符(而且保证剩下的字符仍然按照原来顺序连续排列,形成字符串)。
7. 写一个函数,它判断一个整数(或浮点数)是否在一个数组中出现。如果出现,给出第一次出现位置的下标;不出现时给出值-1。
8. 写一个函数,它统计出一个整数在一个数组(都通过参数提供)里出现的次数。
9. 设有n个人围成一个圆圈,从编号m的人开始由1开始报数,每个正好报到数k的人退出游戏,后面的一个人重新由1开始报数。求出最后剩下的那个人的编号。
10. 写程序读入正文文件,并统计其中各种长度的单词出现次数。设法用比较形象的方式显示统计结果。这是一项很简单的文字材料统计工作。
11. 写一个程序,它读入一个文件,输出其中最长的词(参考前面章节对“词”的定义,你也可以自己规定,例如规定词是由字母开头的字母数字序列)。考虑用函数的局部变量或者外边变量实现程序的两个版本。
12. 1)实现一个求由数组表示的多项式的值的函数; 2)写一个求数值数组中最大值的函数; 3)用上面的函数求出一个多项式在一系列等距点的值,利用其中的最大值将前一步得到的所有值进行规格化(这样可以防止产生的图形太长或太宽),并设法用星号在屏幕上显示多项式的近似图形。
13. 回文是从前向后和从后向前读起来都一样的句子。例如英文中的:
amanaplanacanalpanama 其原文是:A man, a plan, a canal, Panama。再比如中文的“落
叶秋叶落”等。写一个函数,它能够判断一个字符串是否为一个回文。如果你的C系统能够处理中文的字符串,也为中文定义一个函数。
14. 设法写一个程序,对于给定整数n,它能输出1到n之间的数的所有不同排列。
15. 1)请写一个程序,它输入一个学生成绩文件,输出按照每10分一个成绩段的学生人数。2)请写一个程序,它输入一个学生成绩文件,输出其中的不及格(小于60分)、及格(60≤X<75)、良好(75≤X<85)、优秀(85≤X)的学生人数。
16. 修改正文中有关有关求最长行程序实例,使之“总能”正确输出最长行的长度,输出的总是最长的那一行或者那一行中前面一段(如果该行超过数组容量)。
17. 按照文中有关定义处理多维数组的函数的规定,写出求4×4的矩阵和、数乘,求其行列式值(合适的方式是采用高斯消去法)的函数。
18. 根据需要扩展文中定义的通用的带检查的输入函数。
19. 按照本章最后的讨论实现一个C语言关键字的统计程序。回答讨论中所提出的问题。修改你的程序,使它能够正确处理任何C程序。
第7章练习
1. 把前面用数组方式定义的一些函数改写为用指针运算的方式定义。
2. 把前面练习中定义的各种求根函数改写为使用函数指针的函数。
3. 写一个程序,其命令行包括一个字符串参数,运行中由标准输入读入一系列正文行。该程序把所有输入行依次输出,并在那些包含字符串的行前面标一个星号。
4. 1)写一个函数,它检查两个字符串是否由同样一些字符组成。2)写一个函数,它判断一个字符串是不是可以由另一个字符串通过重排字符而得到,例如,dare,read和dear都有这种关系。
5. 1)修改第五章的猜数程序,通过命令行参数为它提供数的范围。2)将前面的某些程序改造成为使用命令行参数的,通过命令行为程序提供运行的数据。
6. 矩阵是数值计算中最常用的结构之一,在程序里可以很自然地采用两维数组表示。请按照本章提出的处理两维数组的通用函数的技术,写出几个通用函数实 现矩阵的常用操作,包括数乘、矩阵加法、矩阵乘法、矩阵转置、行列式求值等。(提示:实现矩阵行列式的求值请采用消去法(高斯消去法),将矩阵变换为三角 矩阵后完成计算。)
7. 利用函数指针功能,写出利用梯形法求数值积分的函数。
8. 辛普生方法是用二次曲线逼近被积函数的数值积分方法,有辛普生公式:
积分区间划分为2m个等分小区间,。用函数指针实现按这一公式计算数值积分的函数,请考虑采用能根据情况自动调整区间分割数的方法。
9. 魔阵是由1到这些整数排成方阵,其中每一行、每一列和两个对角线上的数之和相同。写程序构造出3阶和4阶的魔阵。
下面是一个构造奇数阶魔阵的通用算法:首先把1放在第一行中间。当数k放好后,考虑数k+1的安放,总把它放在向上一行、向右一个位置。下面是各种特殊情况的处理:
1)要从最上一行向上,那么就转移到最下一行;2)要从最右一列向右,那么就转移到最左一列;3)如果企图放数的位置已经有了数,那么就把这个数放在它前面一个数的下面。 写程序实现这个算法。另外写一个函数,它能检查一个n阶方阵是否为一个魔阵。
10. 写一个小游戏程序,它内部存储着一些英语单词(在写程序时给定单词集合)。程序运行中每次由这些单词中随机地选出一个,要求游戏者猜。做游戏者反复询问某些字母是否出现在单词里面,程序给出回答。直至人猜出这个单词(或者放弃)。
11. 修改前一题提出的猜单词程序,设计实现某种较好的选词策略和提示策略。
12. 写一个函数select(int n, double a[], double b[], double x),它将数组b中大于等于x的数顺序复制到数组a中。假定n是两个数组的大小。请分别用数组写法和指针写法完成这一工作。
13. 写一个程序,它可以输入并保存至多100个长度不超过80个字符的字符行。在读完所有的输入之后,它先输出其中长度不超过40个字符的行,而后输出其他行。考虑下面两种实现方式:1)用两维字符数组保存字符行;2)用一个字符指针数组,将字符行保存在通过动态分配的存储块里。
14. 完善本章中处理任意数量学生成绩的程序,完成文中提出的各种改造,并加入你所设想的合理的进一步改造。
15. 修改7.6节的筛法程序,将分配数组的工作移到实现筛法的函数sieve里,使它返回做好的数组,并对主函数做适当改造。从程序的清晰性和功能分配的合理性等方面比较这两种实现方式。
16. 请定义:1)一个数组类型,该类型的数组包含10个元素,其元素是函数指针,被指函数都是有一个double参数并返回double值(请直接定义,并将写出的定义与借助于MFP的定义做一个比较);2)一个函数的原型说明,它的参数是一个指向“数学函数”的指针和一个double,返回值是指向数组的指针。这种数组中包含着12个double值(请分别采用直接的方式和预先定义其他类型的方式定义它)。
17. 多项式可以数组表示,例如用一个n+2个元素的整数数组a表示一个n次的整系数多项式,其中的在a[0] 中保存多项式最高次的次数(由此也可以决定多项式的项数),其余元素顺序表示一个多项式的0次项、1次项、2次项等等的系数,多项式的变量隐含假定为x。这种数组可以通过动态存储分配创建。请基于这种表示方式实现一元多项式的输入(自己设计一种输入方式,输出可尽可能采用类似数学中的表示形式(例如输出为
3+2x+4x^2,系数为0的项不输出)。请进一步实现各种多项式运算,完成一个简单的一元多项式计算系统。
18. 假定所用C语言支持的整数类型long(32位)无法满足目前应用的需要,我们需要一种64位的整数。请设法利用现有的功能实现64位整数的表示、输入、输出和计算。
第8章练习
1. 写一个程序打印乘法九九表。利用格式控制保证表的的项能很好对齐。
2. 写一个程序,其命令行要求有三个参数。该程序把这些参数看成文件名,完成的工作是把前两个文件的内容连接起来,存放到第三个文件里(文件连接)。
3. 将前面一些程序改写为采用文件输入输出的实现的,通过命令行或者输入语句为程序提供运行的数据或输入输出的文件名。
4. 修改前一章有关猜单词游戏的练习,让程序从文件中读入一批单词,文件的名字从命令行得到。如果调用函数时未提供文件名,则向用户要求文件名。
5. 修改前面的学生成绩统计和直方图生成程序。假定成绩文件中的每一行是一个学生的姓名(例如,连续字符序列表示的汉语拼音姓名)和相应成绩。程序首先输入文件,而后先输出成绩不及格的学生姓名和成绩,再输出成绩及格的学生姓名和成绩。
6. 修改学生成绩统计和直方图生成程序。假定文件里的每行记录了一个学生的姓名和几门课程的成绩,程序读入这种文件,计算出每个同学的平均成绩,而后:首先输出平均成绩不及格的同学名单和对应成绩;而后输出及格的同学名单和对应成绩。
7. 假设电价分段计费,不同时段每度电的单价不同。每个用户的用电记录在文件里存为一段,其中的第一行是用户名,随后的每个记录项包括两个浮点数据, 第一个数据是一段时间的用电度数,第二个数据是每度的单价,都由空格或者换行分隔。请写出一个完整的程序,它不断要求人提供输入文件的名字,对每个文件算 出各个用户应付的电费并输出结果。程序结束时输出所有用户应付的总电费额。这个程序在启动后应能处理任意多个文件,并使用户能在处理完希望处理的文件之后 控制程序结束。
8. 写一个程序,其命令行有两个文件名参数,在第一个文件里是一个单词表,第二个文件是被处理的文件。程序生成一个新文件,其中包含单词表中的每个单词,并附以各单词在被处理文件里出现位置的行号(可能有多个行号,但应当避免重复)。
9. 文中的背单词程序用两维数组记录单词。为了防止单词超出范围,那里定义了一个很大的常量WDLEN,而实际上许多单词和对应正文词所需的存储都远远小于这个量。请修改这一程序,用一个指针char* 的数组代替那里的两维数组。在读入英文单词和中文词时用较大的char数组作为临时存储,而后通过动态分配的方法为每个英文或正文单词字符串建立适当大小的数组,并将字符串复制进去。为了这种改动,程序的其他部分还可以保持原样吗?
第9章练习
1. 请定义如下计算平面点与其他图形之间关系的函数:
1. 计算一个点是否在一个圆之内;
2. 计算一个点与一个圆之间的距离(该点与圆周上最近一点之间的距离);
3. 判断一个点是否在一个矩形内部;
4. 计算一个点与一个矩形之间的距离(该点与矩形上最近一点之间的距离);
5. 计算两个圆之间的距离(如果相切或者互相有重叠则认为距离是0);
6. 计算两个矩形之间的距离;
7. 计算一个矩形与一个圆之间的距离。
2. 定义一个表示时间的结构(包括时、分、秒成员)类型,定义由这种类型的参数计算总秒数的函数。
3. 定义一个表示日期的结构类型,然后定义下面函数:
1. 由这种类型计算一天是该年的第几天的函数(注意闰年问题);
2. 比较两个日期的大小的函数;
3. 计算两个日期的天数差的函数;
4. 定义以一个日期和一个天数为参数的函数,它能算出某日期若干天之后的日期;
5. 定义计算某日期之前若干天的日期的函数;
6. 其他你认为重要的日期函数。
用这些函数计算:到本世纪末还有多少天,今天是本世纪的第多少天,你自己已经生活了多少天,离你的下一个生日还有多少天,你最近的生日与今天的天数差,从孔子出生到今天已经有多少天,等等。
4. 定义几个比较两个身份证大小的函数:以出生年月日作为标准;以身份证号作为标准,以姓名的编码作为标准。考虑应该使用结构参数,还是使用结构指针参数。
5. 完成正文中设计实现的复数结构及其相关函数,再为它增加一些有用的函数。设计一个交互式的复数计算系统界面主函数。
6. 以两个整数为成员的结构可以表示有理数。定义计算这种有理数加、减、乘、除运算的函数。函数的返回值如何定义比较合理?
7. 开发一个小型信息查询系统。当这个系统运行时,人输入一个城市的名字(拼音),它就能给出由你所在城市到所输入城市的铁路距离,火车票价。(如果要求的是对输入的一对城市给出有关信息,你能够完成这个小系统吗?)
8. 考虑定义一个计算台球位置的函数,这个函数的一个参数应当是一个表示台球状态的结构,其成员至少包括:台球直径、当前位置,速度向量。把台球桌的 大小作为程序常量,定义计算t时间后台球状态的函数。进一步考虑桌面阻力因素和台球桌边沿的弹性系数,修改前面定义的函数,使其能够更好地反映现实世界的 情况。考虑利用这个函数做一个有趣的程序实例。
9. 完成正文中基于结构的学生成绩处理程序。提供按照成绩排序输出的功能。
10. 完成正文中的账目管理系统。
11. 对你所完成的账目管理系统做一些扩充,例如:
1. 增加用直方图形式显示账目变动曲线的情况,可以考虑按时间周期的收入情况、支出情况和余额情况等。
2. 增加对本次处理的一系列账目的总的统计。
3. 提供对一段时期的各种情况的统计功能。
4. 采用动态存储管理的方式分配账目"数组",保证能够处理任意大小的账目。
12. 对你所完成的账目管理系统做一些修改,使用户在启动程序时可以通过命令行参数提供一批文件名,程序在启动后自动装入这些文件里的账目记录,而后接 受用户命令完成各种统计工作。增加一个命令,使程序可以要求新文件,并将新文件的内容附加到程序已经装入的账目条目之后。再增加一个命令,使程序可以将程 序里当前保存的账目全部输出到用户指定的文件里。
13. 修改账目管理系统,使它在每次启动后首先去查看名字为account.sav的文件。如果这个文件存在,本系统就将列在该文件里的账目装入。用户提供的其他文件的内容都附加在程序里已有账目内容之后。在程序结束时将当时数组里保存的账目重新存入上述文件,以备本程序下次再用。注意,请在保存之前将整个数组按照日期排序。
14. 参考上面的账目管理系统,设计一个学生成绩管理系统。它应该能:
1. 启动时自动读入一个学生成绩文件。
2. 读入新的学生成绩文件,并将其中的成绩记录在有关学生的原有成绩记录之后(可以假定每个学生总共的课程数不超过某个事先定义好的常量,因此一个学生的所有成绩可以存在一个数组里,作为学生记录的一部分)。这里可能出现新的学生和新的课程;
3. 每次运行完成后能将程序中所有学生成绩记录保存到将来能自动读入的文件。
4. 增加各种有用的统计输出功能。(在完成这一工作时,可以考虑或者不考虑课程名。如果考虑课程名,可以维持一个课程名与成绩记录位置的对照表)。
15. 扩充正文中的单词统计程序,使之能处理任意长度的单词。(提示:请考虑用动态分配的存储块保存单词)
16. 如果文件中的单词很多,记录它们的链接表就会非常长,查找确定一个单词所需的时间也会大大增加。考虑下面的改进技术:
1. 定义一个指针数组LINK table[21],数组里的每个指针指向一个结点表;
2. 将长度为i的单词都存入指针table[i]所指向的表里(table[0]闲置);
3. 在words[20]里保存长度大于等于20的所有单词。
这种方式可以减少程序处理每个单词所需的时间。请采用这一技术重新实现单词计数程序。通过处理很大的文本,比较这一实现与本章正文中的实现的执行情况。
17. 在实现单词计数时(假设为英文单词)考虑下面的改进技术:
1. 定义一个指针数组LINK table[26],数组里的每个指针指向一个结点表;
2. 按照单词的首字母将读入的单词分别存入指针数组table的不同元素所指向的链接表里,在读入单词后查找存入表之前将单词里所有大写字母改为小写字母。
通过处理很大的文本,比较这一实现与本章正文中所提出的实现的性能。
第11章练习
1. 请写出两个函数,它们实现类似strspn和strcspn的功能,确定字符串里满足一定条件的最长前缀的长度,但判断的依据通过一个函数指针参数得到。该指针的实参应是以char为参数返回int值的函数(谓词),在char满足要求时返回非0值,不满足要求是返回0值。这种函数与标准库ctype.h里定义的字符分类函数类似。(注意,标准库ctype.h里的"字符分类函数"通常是用宏定义实现的,因此不能用于作为函数指针参数的实参。)
2. 自己实现标准库函数strtok的功能。请给函数另起一个名字,以免与库函数冲突。(提示:请考虑使用静态局部变量)。
3. 写一个函数,它有一个双精度数组参数和不定个数的整数参数,它能够根据所给的一组整参数值(作为数组元素下标)由数组里取出对应的元素,求出这些元素的和。
4. 请设法只使用标准库函数getchar实现scanf的功能。
5. 请设法只使用标准库函数putchar实现printf的功能。
6. 试写一个C源程序的预处理程序。先完成处理#include命令(如果考虑系统头文件,那么就需要其他信息。可以只考虑当前目录下的文件包含问题)的工作,然后考虑简单宏命令的处理和条件编译的处理,最后考虑带参数宏定义的处理。