
本书的目的是让读者正确、全面、深入、轻松地掌握C语言程序设计技术。本书严格遵循ANSI C标准,包含了ANSI C提供的一些重要的但常被遗漏的内容(如宏运算符#、printf函数的格式符%*.*s)。本书的程序例题力求典型、有趣。在介绍程序时强调算法的思路,突出理性,并借助一题多解讲解程序改进的必要性和常用方法。本书强调兴趣、基础、技巧和创新,其中包含了笔者在程序设计方面的研究工作,如解析法、拟人法、对称法、递归树、递归公式法和后继序列法,它们的作用是简化程序设计工作和加深对程序的理解。
本书可作为高等院校本科生和专科生的C语言程序设计课程的教材,也可供研究生和软件设计人员参考。
走进任何一个大型书店,你会发现在所有图书中最多的是计算机书籍,计算机书籍中最多的是C语言方面的。究竟有多少?一千本,两千本?难以统计。这也说明了,C语言极其成功,应用极广。
这么多的C语言著作,应该是各具特色,有感而发。但是找一本满意的C语言教材也并非易事。笔者从2000年开始讲授过十几次C语言,使用过多种教材,都会发现这样那样的问题,不得不对于课程内容进行修改、增删。教师麻烦点倒也应该,学生往往不得要领,于是决定按自己的理解写一本C语言教材。笔者曾从事程序设计多年,积累了一点C语言编程经验。经过3年的总结、编写,得到了眼前这本书。是否实现初衷,不得而知,欢迎评说。
1.本书写作思路
C语言教材应包含两部分内容:C语言的正确知识和程序设计的正确方法。本书强调以下想法:
(1)教科书是世界上最重要的一类书,因为它面对的是正在学习的学生。一本好的教科书就是一盏指路明灯,一本坏的教科书就是一个陷阱,无异于图财害命。教科书必须是正确、规范、精彩的。特别像 C语言这样的书,C语言往往是读者的第一门程序设计语言。如果学好了,再学习其他语言就会轻而易举;如果学不好,重学一遍或者再学其他语言都难以得到好的效果。在IT就业市场上求职者爆棚,而企业求贤若渴。问题主要归结为我们的社会大环境和教育体系,但是也多少关系到我们教材的水平。教材的作者不能着眼于名利,不能为书而书,而且也不能一知半解。
(2)忠实遵守ANSI C标准。标准是判断知识正确与否的依据。本书采用ANSI C标 准,这是世界上多数C语言教材的共识。至于更新的C99标准,当前还不适用于教材,因为它还没有普及,而且它对ANSI C的改进主要体现在量变而非质变上。符合标准并不困难,只需要多查阅标准文本,只是多一点麻烦。例如,多数教材都把–456这样的负数当做整数常量,ANSI C规定十进制整数常量开始于非0数字,所以–456只能是常量表达式。再如,许多书在介绍不同类型的数据混合运算时采取的类型转换规则仍然是传统C(也叫做K&R C)的规则,其特点是没有float型运算。而C标准说“在标准C语言之前实现要求把所有float类型的值转换成double类型之后再进行运算,在标准C中可以直接使用float类型进行运算”。还有,某本影响极大的教材说“ANSI 标准允许switch后面的表达式为任何类型”,令人好生奇怪,一查标准发现不是这样。为避免此类错误,本书在写作过程中一直参考ANSI C标准[3]、中国国家C语言标准[4]、C语言作者的原著[1]、C语言参考手册[5]和C语言大全[6],以保证所有环节都同标准一致。
(3)C语言有一些重要而有用的特色功能,构成了C语言的特点,但是大多数教材却把它们忽略了,本书予以足够重视。例如二进位级运算,大部分教材只给出c=a&b之类的练习,缺少有说服力的应用例子,学生无法理解它们存在的价值,这种讲法等于没讲。再如,printf的格式串中有一个起“变长显示”作用的“*”,能简化程序,恰当使用会收到神奇的效果,其他语言也有类似功能,可惜国内教材都未提及。
(4)C语言不难学,但想学好也不容易,必须努力。本书是一本C语言的入门教材,不是休闲读物,也不是科普读物。所谓入门,要求掌握基本知识、基本技能,为进一步学习程序设计打好基础。有人说,C语言入门容易深入难。对于抱着了解目的的读者,当然可以大致浏览,不必认真,但是谁也不能轻轻松松地取得成功。C语言内容丰富,功能强大,不花气力,入门也难。有的教材说,记住C语言45个运算符的优先级极为困难,所以索性不必记了,需要时多加括号好了。笔者以为此言不妥,“态度决定一切”,只要有认真的态度,记住优先级并非难事(难度相当于记一个手机号码),而且受益无穷。如果连这点力气都不肯付出,恐难成正果。
(5)“深入浅出”是所有教材的目标,不深入就不能浅出。比如对于单目的“–”运算符,通常认为不言自明无须解释。对于有符号整数,“–”意味着“求负”。但是对于无符号整数就不能这样解释了,因为无符号数无负可求。所以“–”整数是对整数机器表示的求补,为了搞清“–”就必须搞清数据的机器表示。数据的机器表示是计算机运算的基础,也是程序设计的基础。掌握机器表示是学习C语言的指针、位运算、结构、联合等内容的前提,也有助于理解高级计算机应用(如图形、音乐)的原理。
(6)初学编程总要有一个照猫画虎的阶段,例题的质量尤为关键。初学者对于程序的好坏还没有判断能力,他们会把书中的例题当做楷模,当做好程序的标准,所以教科书的例题程序必须尽量好一点,无懈可击。笔者初学编程时常被书本上例题的精彩所震撼,可惜现今很难重温这种感觉了,不能不说是当代学生的遗憾。比如,“链表插入”问题,我们的教材都按4种情况处理,冗长繁琐、平庸乏味。而国外教材只分两种情况,甚至整合成一种情况,程序简单、精彩、易懂。“语不惊人死不休”(杜甫语),编程也一样。程序编好了,必须不断改进。这应该是编程必不可少的一道工序。少了这道工序只能产生蹩脚的程序和平庸的程序员。
(7)程序设计是一种工程活动,正像盖大楼一样必须以一种科学有序的方式进行,必须进行充分的准备和论证,不能贸然上马。30年前计算机还是一种昂贵稀缺的资源,程序员们为了节省上机时间都会预先反复论证、思考,容易产生高质量的程序。随着计算机的迅猛发展,程序员反倒愈来愈性急,一接到题目马上上机,欲速则不达,结果自然是一批又臭又长的程序垃圾。本书强调程序设计中准备工作——理解问题和算法设计的重要性。所有例题都突出构思,突出理性,“好程序是想出来的”。另外,笔者认为“一题多解”是程序设计的必由之路,没有比较就没有鉴别,所以只要可能一个题目都会给出多种答案。目的不仅在于介绍多种算法,更重要的是培养思考和选择的习惯。
(8)作为程序员,笔者追求的程序风格是清晰和简短。一个程序单位应该尽量做到一览无余,在屏幕成为程序的唯一载体的情况下更应如此。流行说法是段落用空行隔开,花括号独占一行,一行只写一个语句等。这样容易把短篇写成了长篇,程序员难于从整体上把握程序的脉络,难编难调。书中的程序实例力求充分利用空间:允许在一行上书写多个关系密切的语句;除第1行是指明文件名和功能的注释,其余注释都放在一行的右端;对于需要进一步解释的行用数字标明,程序正文之后以注释形式列出程序运行时屏幕上出现的输入输出。
(9)本书强调实践性,主张一切都需要用计算机检验。利用计算机学习计算机知识应该是现代人的基本素质。本书采用了国内常用的两种C语言编程环境——Turbo C 2.0(简称TC)和Microsoft Visual C++ 6.0(简称VC),所有程序都给出了在两种环境中的运行会话,包括命令行参数、多文件程序。这些会话都是取自“截屏”,容易再现。
2.本书约定
本书采取以下体例方面的约定:
(1)程序例题的文件名,采取章号_题号.c的形式,如 6_7.c表示这是第6章的第7个程序例题。如果此程序还有其他版本,则后缀以字母,如6_7a.c是程序6_7.c的第一个修改版,有的程序可能有多个修改版本。
(2)几乎所有例题程序都以注释形式给出了程序运行时的人机会话过程,其中的输入部分用黑体标明。在C程序中输入输出的字符串里可以包含汉字,但考虑到在有的Windows环境下用汉字可能有困难,所以本书例题的字符串一律不包含汉字。
(3)所有例题程序都可以在Turbo C 2.0和Microsoft Visual C++ 6.0上运行。如果程序在这两个系统上表现有所不同,则运行会话将给出两种表现,并用TC和VC标明。
(4)书中插入了3类矩形框:注意、编程经验、评论,都取自笔者的编程实践。之所以放在矩形框中,是避免影响上下文的连贯性。
(5)在介绍某种语法形式时可能采取如下的写法:
类型 数组变量名[行数][列数]={初始化符,…},…;
下划线指明可以省略的部分。
(6)对于程序的改动不大的修改版本,为节省篇幅,经常只给出改动部分,并用下划线标明。例如:
/*11_1a.c 右上拐角矩阵*/
k= i+j<=n+1 ? i : n+1-j ; /*4 计算k值*/
是程序例题11_1.c的第一个修改版本,改动只有此一行。
(7)全等号“≡”不是C语言的合法字符,在本书中用来表示“等价于”或“其值为”。例如,(int)a+b≡((int)a)+b,2/3≡0。
3.本书内容
本书分为11章,内容如下:
第1章“概述”:简要介绍程序设计语言C的历史、语言特点,给出几个典型例题,简要介绍C语言开发环境Turbo C(简称TC)和Visual C++(简称VC),让读者初步了解C语言程序的构成和开发步骤,特别是程序排错方法。对于TC和VC的介绍,没有列举它们各菜单选项的功能,而是只给出为编写和排错C语言程序要采取的做法。这样的做法可能不止一种,这里给出的是笔者认为最简单的一种。
第2章“数据类型:变量和常量”:数据类型是程序设计语言的基础,本章介绍了C语言的数据类型中的基本类型,特别强调了整型和浮点型数据在内存中的表示方法,给出了在TC和VC上利用观察窗口取得数据机器表示的方法。在这一章也初步介绍了指针变量的概念。
第3章“运算符和表达式”:本章首先介绍C语言运算符的共同属性,而后逐一介绍多数运算符,而把关系运算符和条件运算符放在第4章介绍,移位运算符和按位运算符放在第8章介绍。表达式是构成C语言语句的基本成分,表达式的值是由所含的变量、常量和函数返回值以及运算符引起的类型转换决定的。本章目标是让读者能够正确确定表达式的值及其类型,所以详细介绍了变量和常量的定义方法和类型确定方法,运算符的操作和类型转换。在这一章把运算符的属性归结为3种:优先级、结合性、副作用,给出了一个17字的口诀,有助于记忆45个运算符的3种属性。
第4章“编程初步”:主要介绍基本I/O函数scanf和printf的调用方法,宏的定义和调用,以及顺序结构编程方法。程序给用户提供的界面是程序质量的一个重要方面,所以这里把scanf和printf描述得比较详细,读者初学时不必深究,以后涉及时再来查阅。宏是简化程序的有效手段,使用也很简单,本书用得很多。通常教材对宏只是一带而过,本章比较详细地介绍了宏的定义方法,特别说明了ANSI C增加的、多数教材没有提及的宏运算符“#”的用法。
第5章“控制语句”:这一章首先介绍结构化程序设计的概念,引出3种基本结构,而后详细介绍分支结构和循环结构所使用的全部语句。最后讨论了程序风格和表达算法的工具——Warnier图。国内教材从20世纪50年代开始一直用流程图和NS图表示程序的算法,国外从20世纪70年代就很少使用了。作为一种尝试,本书采用Warnier图。Warnier图画起来方便,能够很好地描述自顶向下的程序开发过程,得到业界专家的普遍肯定。
第6章“数组与指针”:介绍一维数组、二维数组、字符数组和字符串,以及它们与指针的关系。只要涉及指针,就容易引起混乱。笔者认为,避免混乱的药方就是画图,画图是学习程序设计的好方法。知识重要,获取知识的方法更重要。本章画了很多图,希望读者能够建立起画图的习惯,能根据需要画出正确的图来。
第7章“函数”:介绍函数的定义方法和调用方法,以及由函数引发的各种问题,如函数参数、函数返回值、变量的作用域和存储类、递归函数。在介绍递归函数时使用了“递归公式法”,把难以捉摸的递归函数设计问题转换成简单的、机械性的翻译工作。而在理解递归函数时提出“递归树”方法,并且给出了通用的递归树显示方法。另外,由于功能强大的scanf和printf容易引起误解,这里解释了它们的实现方法,有助于理清使用方面的 问题。
第8章“结构与联合”:介绍结构类型和结构类型变量,指针和结构的关系,并提出联合概念。由于位段的定义形式同结构和联合类似,所以放在这里介绍。而二进位级运算是C语言的一大特点,通常教材中很少有这方面的应用例题,这里给出了几个有启发性的位级运算例题。
第9章“文件”:应用程序离不开文件操作,但是通常文件放在最后一章介绍,本书提前了一点。本章强调实用性,用一系列实例介绍常用的文件相关的I/O函数,包括文件数据的排序、数据的累积等。
第10章“内存分配和动态链表”:动态内存分配也是C语言的特点,本章介绍了几种相关的函数,并作为动态内存分配的实例,给出了动态一维数组和二维数组的实现方法。动态链表是动态内存分配的重要应用,本章对动态链表的结点插入问题的4种解法进行了比较,指出“求同存异”是编程的必要步骤,也是程序员必备的能力。学生信息管理系统是本书最大的例题,能够复习到以前各章的所有内容。
第11章“算法初步”:在学习了C语言课程之后,学生往往处在一种尴尬处境,掌握了C语言的知识但还不能承担应用程序的开发工作。程序的核心是算法,语言只是表达形式而已。C语言的教材不仅要介绍C语言知识,还要通过例题教会学生设计算法。本章详细讨论了3方面问题:矩阵显示问题、日历问题、组合生成问题,目的是使读者看到隐藏在寻求算法的过程中的思考方法,希望能为读者搭建一级继续攀登的阶梯。
本书配有教学课件,欢迎有需要的读者来清华大学出版社网站下载。
本书的第1~第4章由戴南编写,第5~第7章由朱玉龙编写,第8~第11章由朱彤编写。另外,在本书编写过程中得到南京师范大学计算机学院领导和同事的支持和帮助,在此深表感谢。
尽管本书包含了笔者数十年的工作和思考,但由于笔者水平有限,书中难免仍有疏漏谬误之处,欢迎读者对本书提出宝贵意见。
编 者
2010年12月
??
??
??
??
C程序设计
第1章 概述 1
1.1 计算机与程序设计语言 1
1.2 C语言的发展和标准 2
1.3 C语言的特点 3
1.4 C语言的基本概念 4
1.4.1 程序的结构 5
1.4.2 变量、常量和表达式 7
1.4.3 自定义函数 9
1.4.4 标准函数 10
1.5 C语言程序的组成 11
1.5.1 C语言程序的结构 11
1.5.2 C语言的字符集 12
1.5.3 C语言的记号 12
1.6 C语言程序的开发步骤 13
1.7 Turbo C 2.0 14
1.8 Visual C++ 20
习题1 25
第2章 数据类型:变量和常量 27
2.1 变量 28
2.2 整型变量 29
2.3 整型常量 30
2.4 浮点变量和浮点常量 32
2.5 类型修饰符const 33
2.6 指针类型 33
2.7 字符串常量 35
2.8 符号常量 35
2.9 枚举类型 35
2.10 整型和浮点型数据的机器表示 36
2.10.1 整型数据的机器表示 36
2.10.2 浮点型数据的机器表示 38
习题2 39
第3章 运算符和表达式 42
3.1 运算符的属性 42
3.1.1 优先级 42
3.1.2 结合性 42
3.1.3 副作用 43
3.1.4 左值表达式 43
3.2 类型转换 43
3.2.1 类型转换场合 43
3.2.2 类型转换方法 45
3.2.3 运算符表 47
3.3 运算符的功能 48
3.3.1 括号运算符 48
3.3.2 sizeof运算符 49
3.3.3 算术类运算符 50
3.3.4 指针类运算符 54
习题3 56
第4章 编程初步 58
4.1 语句 58
4.1.1 空语句 58
4.1.2 表达式语句 58
4.1.3 复合语句 59
4.1.4 逗号运算符 60
4.1.5 控制语句 60
4.2 预处理命令 60
4.2.1 定义宏命令#define 61
4.2.2 文件纳入命令#include 64
4.2.3 条件编译命令#if 65
4.3 格式化输入和输出 67
4.3.1 格式化输出函数printf 68
4.3.2 格式化输入函数scanf 72
4.3.3 字符输入输出函数 getchar和putchar 76
4.4 程序例题 76
习题4 79
第5章 控制语句 81
5.1 程序开发步骤 81
5.2 结构化程序设计 82
5.3 关系表达式和逻辑表达式 83
5.3.1 关系运算符 83
5.3.2 逻辑运算符 84
5.3.3 关系表达式和逻辑表达式的简化 86
5.4 分支语句 86
5.4.1 if语句 86
5.4.2 条件运算符 90
5.4.3 switch语句 91
5.4.4 标准的字符操作 96
5.5 循环语句 97
5.5.1 for语句 97
5.5.2 while语句 100
5.5.3 do语句 103
5.5.4 循环的嵌套 104
5.6 其他控制语句 105
5.6.1 break和continue语句 105
5.6.2 goto语句 107
5.7 编程风格 108
5.7.1 命名约定 108
5.7.2 表达式 110
5.7.3 语句排列 111
5.7.4 什么是好程序 112
5.8 用Warnier图表示算法 112
5.9 程序例题 114
习题5 125
第6章 数组与指针 131
6.1 一维数组 131
6.2 一维数组的使用 132
6.3 一维数组与指针 135
6.4 指针变量的运算 136
6.5 从键盘输入数组元素 137
6.6 查找与排序 140
6.6.1 查找 140
6.6.2 排序 144
6.7 二维数组 147
6.7.1 定义二维数组 147
6.7.2 二维数组的初始化 147
6.7.3 多维数组 149
6.8 二维数组与指针 149
6.9 字符数组与字符串 151
6.9.1 字符数组 151
6.9.2 字符数组和字符指针 153
6.9.3 处理多个字符串 156
习题6 159
第7章 函数 162
7.1 概述 162
7.2 函数的定义 163
7.2.1 函数首部 163
7.2.2 函数体 163
7.3 函数的调用 165
7.4 函数的声明——函数原型 166
7.5 函数的参数和返回值 167
7.5.1 形参取基本数据类型 168
7.5.2 形参取指针类型 170
7.5.3 使用指针型的形参传递数组地址 171
7.5.4 函数指针作为形参 174
7.5.5 传递多维数组 177
7.6 变量的作用域、生存期和存储类 177
7.6.1 变量的作用域——局部变量和全局变量 177
7.6.2 存储类 178
7.6.3 变量的初始化 179
7.6.4 函数的存储类 183
7.7 函数的递归调用 183
7.7.1 递归函数的公式化方法 184
7.7.2 理解递归函数 184
7.7.3 绘制递归树 187
7.7.4 自动生成递归树 187
7.7.5 递归函数的非递归化 189
7.8 字符串函数 190
7.8.1 字符串输入函数 190
7.8.2 字符串输出函数 193
7.8.3 字符串操作函数 194
7.9 返回地址的函数 197
7.10 复杂声明和类型定义 198
7.10.1 理解复杂声明 198
7.10.2 类型定义 199
7.11 关于scanf和printf函数 200
7.12 命令行参数 202
习题7 204
第8章 结构与联合 208
8.1 概述 208
8.2 定义结构类型和结构变量 208
8.3 结构类型变量的运算 210
8.3.1 访问结构变量的成员 210
8.3.2 对结构变量的整体运算 211
8.4 结构类型成员和结构指针类型成员 212
8.5 结构类型和函数 213
8.6 联合 216
8.7 二进位级运算 219
8.7.1 按位运算符 219
8.7.2 移位运算符 221
8.7.3 二进位级运算的编程例题 222
8.8 位段 230
习题8 233
第9章 文件 236
9.1 C文件概述 236
9.1.1 FILE类型 237
9.1.2 文件的当前位置 237
9.1.3 文件的操作 237
9.2 文件的打开与关闭 238
9.2.1 文件打开函数fopen 238
9.2.2 文件关闭函数fclose 239
9.3 文件的读写 240
9.3.1 字符读写函数fgetc和fputc 240
9.3.2 字符串读写函数fgets和fputs 242
9.3.3 格式化读写函数fscanf和fprintf 243
9.3.4 内存块读写函数fread和fwrite 245
9.4 与文件当前位置相关的函数 248
习题9 252
第10章 内存分配和动态链表 256
10.1 动态内存分配 256
10.2 动态数组 258
10.3 链表概念 263
10.4 动态链表 264
10.5 学生信息管理系统 270
习题10 276
第11章 算法初步 279
11.1 显示矩阵 279
11.1.1 解析法 279
11.1.2 对称性 285
11.1.3 拟人法 290
11.2 日历问题 294
11.2.1 今天是星期几 294
11.2.2 显示月历 296
11.2.3 显示年历 297
11.3 计算组合 302
11.3.1 计算组合数 302
11.3.2 显示组合序列 303
11.3.3 多叉递归——简化递归公式 305
11.3.4 显示完整组合 307
11.3.5 回溯法 308
11.3.6 后继序列法 309
11.3.7 后继序列法(01数组) 309
11.3.8 后继序列法(二进位序列) 310
习题11 311
附录A C语言运算符表 314
附录B 头文件myhfile.h 315
附录C ASCII代码表 316
附录D ANSI C标准库函数 317
参考文献 321
??
??
??
??
C程序设计