图书简介:
第0章 从0开始 1
0.0 什么是编程 1
0.0.0 计算机的工作原理 1
0.0.1 内存中的程序来自何处 3
0.0.2 可执行文件的制作 3
0.1 怎样用C语言编程 6
0.1.0 学习C语言编程的条件 6
0.1.1 编写最简单的C程序 7
0.2 输出字符序列 11
0.2.0 输出简单字符序列 11
0.2.1 输出特殊字符 13
0.2.2 C语言的“单词” 16
第1章 整数类型及表达式 21
1.0 输出整数 21
1.0.0 输出整数问题 21
1.0.1 十进制常量 21
1.0.2 用printf()函数输出整数常量 22
1.0.3 整数常量的其他写法 23
1.1 整数的五则运算 23
1.1.0 整数的加减运算 23
1.1.1 整数的乘法运算 24
1.1.2 标识符 26
1.1.3 整数的除法运算 28
1.1.4 数据类型的概念 30
1.2 让程序拥有记忆 31
1.2.0 填数问题 31
1.2.1 用变量思考 37
1.3 程序在运行时输入数据 40
1.3.0 通过调用scanf()函数输入数据 40
1.3.1 scanf()函数的应用 41
1.3.2 scanf()函数的误用 44
1.4 其他整数类型 46
1.4.0 int类型回顾 46
1.4.1 unsigned类型 47
1.4.2 其他几对整数类型 49
1.4.3 字符类型 52
1.4.4 有关字符类型的问题 55
1.5 运算符和表达式 66
1.5.0 运算符 66
1.5.1 表达式 69
1.5.2 运算符的优先级和结合性 74
第2章 顺序与选择 77
2.0 几个问题 77
2.0.0 分橘子问题 77
2.0.1 找对手问题 79
2.0.2 大数相加问题 81
2.0.3 程序概述 83
2.1 语句概述 87
2.1.0 什么是语句 87
2.1.1 什么不是语句 89
2.1.2 关于语句的误区 89
2.2 if语句 90
2.2.0 判断整数是否是奇数 90
2.2.1 判断整数是奇数还是偶数 94
2.2.2 讨论 97
2.3 判断常用的几种运算 99
2.3.0 判断是否是3的倍数 99
2.3.1 判断各位数字是否全相同 103
2.3.2 条件运算符 108
2.3.3 关系运算符 110
2.3.4 冒泡法排序 116
2.4 switch语句 119
2.4.0 求第几天 119
2.4.1 求星期几 125
2.4.2 对switch语句的进一步说明和讨论 128
第3章 循环与近似计算 137
3.0 while语句 137
3.0.0 海盗问题 137
3.0.1 统计字符个数 140
3.0.2 四进制数转十进制数 142
3.0.3 序点的概念 145
3.1 do-while语句 149
3.1.0 统计字符个数 149
3.1.1 求逆序数和位数 151
3.2 for语句 155
3.2.0 由数字求整数 155
3.2.1 Fibonacci数列问题 160
3.2.2 第二种for语句 162
3.3 不规则的循环 163
3.3.0 判断素数 163
3.3.1 字符转换输出 167
3.4 穷举法 170
3.4.0 做对了多少道题 170
3.4.1 换零钱 172
3.4.2 谁是凶手 175
3.5 goto语句 178
3.6 浮点类型及应用 178
3.6.0 浮点数的基本概念 178
3.6.1 C语言中的实浮点类型 179
3.6.2 求调和级数的近似值 182
3.6.3 求平方根 184
3.6.4 求sin函数值 186
第4章 函数与指针 188
4.0 函数调用、函数声明与函数定义 188
4.0.0 函数调用(Function call) 188
4.0.1 函数声明(Function declaration) 189
4.0.2 函数定义(Function definition) 189
4.1 函数与结构化程序设计 191
4.1.0 结构化程序设计原则 191
4.1.1 完数问题 191
4.2 作用域和生存期 197
4.2.0 作用域 197
4.2.1 函数名的作用域 197
4.2.2 两种不易被察觉的错误写法 197
4.2.3 局部变量的作用域 198
4.2.4 生存期的概念 199
4.3 递归 202
4.3.0 什么是递归 202
4.3.1 求阶乘 203
4.3.2 求最大公约数 206
4.3.3 Hanoi塔问题 207
4.3.4 关于Hanoi塔的新问题 211
4.4 指向数据对象的指针概述 215
4.4.0 约分问题 215
4.4.1 指向数据对象的指针 217
4.4.2 在递归计数问题中的应用 224
第5章 数组与指针 227
5.0 两个简单的问题 227
5.0.0 摘苹果问题 227
5.0.1 分糖果问题 231
5.1 学会数数 234
5.1.0 翻卡片问题 234
5.1.1 筛法求素数表 236
5.2 向函数传递数组 238
5.2.0 筛法求素数表 238
5.2.1 排序问题 241
5.3 数组名的性质和指针的运算 244
5.3.0 数组名的类型 244
5.3.1 指向数据对象指针的运算 247
5.4 指向虚无的指针及函数形式的宏定义 256
5.4.0 分组问题 256
5.4.1 改进的写法 259
5.4.2 函数形式的宏定义 260
第6章 字符串及数组构成的数组 264
6.0 字符串、字符数组及指向字符的指针 264
6.0.0 字符串及裸串 264
6.0.1 字符串的输入与存储 268
6.1 字符串的常见操作及应用 269
6.1.0 求字符串长度 270
6.1.1 比较两个字符串的大小 271
6.1.2 scanf()函数中的转换 274
6.1.3 字符处理库函数 278
6.2 常用的字符串函数 279
6.2.0 字符串处理库函数 279
6.2.1 sscanf()与sprintf()函数 281
6.2.2 restrict关键字(C99)及memcpy()函数集 281
6.2.3 字符串转换库函数 282
6.3 由数组构成的数组 283
6.3.0 幻方问题 284
6.3.1 由数组构成的数组的数据类型 287
6.3.2 向函数传递由数组构成的数组 291
6.4 main()函数的参数 297
6.4.0 指向指针的指针 297
6.4.1 main()函数的另一种写法 297
6.4.2 求和问题 299
第7章 结构体、共用体及枚举类型 301
7.0 结构体类型 301
7.0.0 输出分数最高的学生信息 301
7.0.1 改进 304
7.0.2 进一步改进 308
7.0.3 成绩统计 311
7.1 共用体类型 315
7.1.0 概述 315
7.1.1 对double类型的解析 316
7.2 位运算 318
7.2.0 求反码 318
7.2.1 输出整数的二进制形式 319
7.2.2 按位异或运算——“^” 321
7.2.3 按位或运算——“|” 322
7.2.4 查找凶手 323
7.2.5 输出二进制形式的数值 324
7.2.6 位段 325
7.3 枚举类型 327
7.3.0 概述 327
7.3.1 球的颜色问题 328
7.4 _Bool类型 330
7.4.0 _Bool类型概述 330
7.4.1 stdbool.h文件 330
第8章 数据类型的深入讨论 332
8.0 指向函数的指针 332
8.0.0 局部排序 332
8.0.1 qsort()库函数的应用 340
8.0.2 bsearch()库函数的应用 344
8.1 复杂数据类型的构造方法和解读 345
8.1.0 复杂数据类型的构造方法 345
8.1.1 复杂数据类型的解读 350
8.1.2 typedef 352
8.2 更自由地使用内存 354
8.2.0 计算100! 354
8.2.1 动态分配内存函数 357
8.2.2 改进的写法 358
8.2.3 用链表解决问题 361
第9章 输入与输出 367
9.0 面向文件的输入与输出 367
9.0.0 把程序输出写入文件 367
9.0.1 C程序怎样读文件 371
9.0.2 格式化输入/输出的格式 372
9.0.3 fprintf()与printf()函数的等效性 377
9.1 文件、流、FILE及FILE * 378
9.1.0 文件 378
9.1.1 流 378
9.1.2 FILE 378
9.1.3 FILE * 379
9.1.4 文本流和二进制流 379
9.1.5 自动打开的流 380
9.1.6 EOF 380
9.1.7 其他几个用于文本文件的输入/输出函数 380
9.2 二进制文件的读/写 381
9.2.0 二进制流 381
9.2.1 用fwrite()函数写二进制文件 382
9.2.2 用fread()函数读二进制文件 383
9.2.3 feof()与ferror()函数 384
9.3 定位问题 385
9.3.0 ftell()函数 386
9.3.1 fseek()函数 386
9.3.2 rewind()函数 386
9.3.3 fgetpos()与fsetpos()函数 386
第10章 程序组织与编译预处理 387
10.0 编译预处理简介 387
10.0.0 预处理的一般特点 387
10.0.1 预处理的几个阶段 388
10.1 文件包含 388
10.1.0 #include预处理命令 388
10.1.1 用途 389
10.2 宏定义与宏替换 389
10.2.0 类似对象的宏 390
10.2.1 类似函数的宏 390
10.2.2 预处理单词 392
10.3 预处理命令的其他话题 393
10.3.0 再谈宏 393
10.3.1 其他预处理命令 395
10.4 使用外部变量 396
10.4.0 外部变量 396
10.4.1 static函数 399
第11章 标准库简介 400
11.0 使用标准库的一些常识 400
11.0.0 标准头与标准头文件 400
11.0.1 使用库的禁忌 401
11.0.2 并存的宏与函数 401
11.0.3 函数定义域问题 402
11.1 对语言的补充 402
11.1.0 stddef.h 402
11.1.1 iso646.h 403
11.1.2 limits.h和float.h 404
11.1.3 stdarg.h 404
11.1.4 stdbool.h(C99) 404
11.1.5 stdint.h(C99) 404
11.2 stdio.h 406
11.2.0 数据类型 406
11.2.1 宏 406
11.2.2 函数 406
11.3 stdlib.h 408
11.3.0 数值转换函数 408
11.3.1 伪随机数序列生成函数 409
11.3.2 内存管理函数 409
11.3.3 环境通信函数 409
11.3.4 查找与排序函数 412
11.3.5 整数算术函数 412
11.3.6 多字节、宽字节字符和字符串转换函数 413
11.4 string.h 413
11.5 数值计算 413
11.5.0 math.h(C89) 413
11.5.1 math.h(C99) 414
11.5.2 complex.h(C99) 418
11.5.3 tgmath.h(C99) 418
11.5.4 fenv.h(C99) 418
附录A C语言的关键字 420
附录B C语言的数据类型 421
附录C ASCII码表 422
附录D C语言的运算符 423
附录E Dev-C++使用简介 424
参考文献 426
展开
除了数学爱好,对一个有能力的程序员来说,出色地掌握
自己的母语是最宝贵的财富。
——Edsger Wybe Dijkstra
在程序设计教学中最重要的问题是什么?毫无疑问,就是如何教的问题。
或许有人觉得,如何学也是很重要的问题,然而,任何人都不可能无中生有地学习程序设计,至少要依靠书籍或视频等学习资料,而这些学习资料的质量在很大程度上将决定学习者的学习质量和学习效率,所以归根到底,起决定性作用的还是如何教的问题。
那么程序设计究竟应该如何教?
问题驱动
首先需要以问题为驱动。
这是因为C语言是用来解决问题的,而不是用来制造问题的。脱离了解决问题,C语言的知识体系毫无用处。
所以“程序设计”课程最为忌讳的就是从所谓的“知识点”出发来组织教学。这方面的教训有很多,比如,为了说明赋值运算符的结合性,有些教学者苦心孤诣地硬造出“a+=
a-=a*a”这样荒唐的表达式并进行自以为是的解释,殊不知这种表达式本身就违背C语言表达式的定义规范。C语言把这种错误叫作Undefined behavior,意思是C语言没有规定这种代码会产生何种行为,即这种代码没有确定的行为定义。
出现这种教学误导的原因不仅是教学者对C语言的了解不够、一知半解地想当然,更主要的是其不是用C语言来解决问题的,而是坐井观天地用C语言来制造问题的。如果围绕解决问题开展教学,则根本不会出现如此的教学误导,因为没有任何问题会驱使编程者构造“a+=a-=a*a”这么荒唐的表达式。
为此,本书在教学安排上,绝大多数情况下从问题开始,通过对问题的分析得到解决的方案,然后自然地引入C语言支持解决方案所提供的具体语言手段,进而展开对C语言的基本概念和编程知识的介绍。
书中所有的问题都是经过精心设计的,目的是展示如何对C语言相关内容进行恰如其分的应用。
实践主导
程序设计是一个动词,因此“程序设计”是一门实践课,而不是单纯的理论课。程序设计又是一项智力活动。因此,“脑手并用”的理念应顺理成章地秉持。
正如只能通过游泳才能学会游泳一样,要想学会编程,也只能通过编程。因此编程实践环节自始至终都贯穿本书。在内容编排方面,本书对任何问题的讲解都不是为了让学习者懂得什么,而是为了让学习者能立即开始着手编写下一个问题的代码。
作为编程实践的练习是由作者精心创作、改编或收集的,这些练习与正文文本讲述的内容密切相关,以保证学习者能够及时地学以致用、巩固并真正掌握所学的相关知识。
除了实践的素材,为了达到更高效率、更高质量的实践效果,本书还提供了实践所需要的平台——Online Judge。
Online Judge本来是依据黑盒测试原理开发的服务于程序设计竞赛的平台,然而在被引入程序设计教学以后,被意外发现对程序设计教学具有极大的促进作用。首先,由于需要准确地理解题目,因此学习者更加体会到了需求分析的重要性;其次,由于程序运行结果必须与测试用例绝对吻合,因此学习者要学会精确地表述;最后,由于每个问题通常都有多组测试用例,因此学习者必须努力考虑各种情况,学会全面周到地思考问题。这些都是软件及相关专业人员需要具备的十分重要的核心专业能力和职业素养。此外,经验表明,Online Judge似乎具有一种激励机制,可以极大地调动学习者的学习积极性和主动性。为了追求测试通过带来的成就感和满足感,学习者在测试未通过后,往往都会认真思考错误原因,反复提交修改的代码,直到最后通过为止。
配套视频
和其他很多视频相比,本书提供的配套视频有如下特点。
首先,教学者不出镜,理由是教学者出镜对教学效果没有任何帮助。讲课但不“演课”是作者一贯坚持的理念。“演课”的危害相信很多教学者都深有体会并深受其害,这里就不展开说明了。
其次,讲课的主要教具是用Word做成的“电子黑板”,而不是PPT。因为PPT的速度快,学习者缺乏体会消化的时间,容易跟不上,最后导致教学效果差,此外也容易给学习者留下教学者照本宣科的不良印象。“电子黑板”的速度与黑板相仿,学习者有充分的时间记录、回味教学者所讲内容,因而更有利于学习者吸收与掌握相应知识,并且不会给学习者留下照本宣科的不良印象。“电子黑板”的另一个优点是,比起真正的黑板,“电子黑板”的内容呈现可以更为整洁、规范。
最后,配套视频展示了通过编程解决问题的完整过程,给出了良好编程风格和习惯的真实示范。
对人类学习自然语言的过程进行观察便不难发现,“模仿”是人类学习自然语言必经的一个环节,没有人可以不经过这个环节学会自然语言,更不可能不经过这个环节就能够“语出惊人”。编程也需要“模仿”,尤其是良好的编程风格和习惯及编辑技巧等方面。通常这些是用文字很难甚至无法表述清楚的,因而需要通过实际操作予以示范。很多事情,讲得再多也不懂,但往往看一眼就会了;还有很多事情,身教胜于言教,“勿以善小而不为,勿以恶小而为之”这样的理念,通常不是靠说教感悟到的,而是靠日积月累的点滴熏陶培养的。
内容安排
在内容安排上,本书力求覆盖C99。
在次序上,主要按照循序渐进和结构化程序设计原则来安排。
第0章 从0开始。标题既是本书给予初学者的明示,也是向学习者所做的“C语言从 0开始计数”的暗示。第一时间安排学习者完成最简单的C源程序并提交到Online Judge中进行测试,使得学习者以最快的速度掌握Online Judge的用法并进入编程实践状态。使用记事本编辑首个源程序可以比使用IDE(集成开发环境)更快地进入编程实践状态,同时可以让学习者了解C源程序的扩展名为“.C”,并懂得C源程序是文本文件。之后重新用IDE 完成一遍,目的是让学习者学会使用 IDE,从简单的错误修改过程中体会到什么是Debug(调试)。从程序在本地的运行结果中可以引出在标准输出设备上输出字符序列的要求,进而介绍printf()函数的用法,首先介绍简单的字符序列的输出,再介绍如何输出特殊字符。最后一个“输出头像”的练习通常能让学习者感到兴奋,多次尝试、不断修改,最后通过后则会更加有成就感。通过这种自觉自愿的多次尝试,学习者将在不知不觉中掌握如何使用IDE、C源程序的基本框架,编辑源程序的基本技巧,以及从问题到程序、从编辑到测试的完整流程。
第1章 整数类型及表达式。本章的核心目的是通过整数类型让学习者理解什么是数据类型,以及如何掌握和什么叫掌握了一种数据类型。
任何数据都属于某种数据类型。数据类型是C语言中最重要的概念,也是其最为自豪的特征,不懂数据类型基本就不会懂 C 语言。那些整天把“指针就是地址”“二级指针”挂在嘴上,以及说着“数组指针”“指针数组”或“函数指针”“指针函数”一类如同绕口令般的词语的人,无疑都属于不懂数据类型的人。一旦领悟了数据类型这个核心概念,就会觉得C语言真的不难。
本章没有介绍浮点类型,一是因为会分散主题,二是在这里浮点类型基本没有用处,无法发挥特定的优势。浮点类型主要用于近似数值计算。
本章引入的另一个概念是变量,本质上程序可以拥有记忆功能,于是,人类借助在脑海里或纸张上的记录能完成的算法,编程也能实现。
C 语言的表达式是用来求值的,任何值都属于某种数据类型,所以本章将表达式和数据类型一起介绍。除了求值,表达式还可以完成副效应,但无论是求值还是完成副效应,都与计算次序无关。运算符的优先级和结合性不是用于解决计算次序问题的,而是用于确定运算符的操作数问题的。
第2章 顺序与选择。C语言的语句是用来解决、确定运算次序问题的,本章介绍顺序与选择两种运算次序的实现。
第3章 循环与近似计算。本章介绍循环次序的实现。学习至此可以求解的问题更多,也更有趣了;浮点数开始有了用武之地。特别要注意的是,浮点数是一种近似表示,追问浮点数的准确性,无异于缘木求鱼。
第4章 函数与指针。函数的介绍应该越早越好,这是因为函数是实现结构化程序设计原则最重要也是最基本的语言手段。但在循环语句和选择语句之前讲解如何写自定义函数是不现实的,因为那样的函数基本实现不了什么功能。
函数把代码划分成了各个独立的“疆域”,由于标识符有自己的作用域,加上C语言中的函数调用只能传值,因此指针的出场成了必然。指针的一个重要作用就是在变量的作用域之外——无法直接称呼变量名的地方,提供用指向变量的指针间接称呼变量的手段。
数据类型概念的重要性在指针这种数据类型中再次凸显,不了解指针的类型,根本不可能知道如何对指针进行运算。“地址”是不可能做“*”运算的,甚至不可能做加1这样简单的运算。
第5章 数组与指针。数组必须在指针之后介绍,因为数组在多数情况下表现为指针类型,所以不懂指针,也不可能真正懂数组。即使数组元素引用 a[i]这种最常见的表达式,其中的数组名a也是作为指针类型出现的,而不是作为数组类型出现的。
数组名的性质是C语言中最为诡异的难点,因为在不同的场合下,数组名表现为不同的数据类型。作为rvalue,数组名是指向数组起始元素的指针,这句话的重要性怎么形容都不过分,一旦理解了这句话,C语言基本就没有什么别的语言难点了。
向函数传递数组是本章的一个重点。在多数情况下,向函数传递数组需要传递两个参数,只传递数组名这一个参数是一种外行写法。
第6章 字符串及数组构成的数组。字符串是含有表示结束元素的数组,因而通常只需要向函数传递一个参数,因为函数可以自行确定数组到哪里结束。
由数组构成的数组,俗称“二维数组”。但这个“二”字对我们理解这种数据类型没有任何帮助,因此“二维数组”的这个“二”和所谓的“二级指针”的“二”一样,并不能使学习者深入领会构造性数据类型的本质,本书摒弃了这种虽然常见但无益的术语。
第7章 结构体、共用体及枚举类型。结构体类型的作用,简单地说就是给几个总在一起的数据“打包”,以便数据的整体传递。要想正确使用结构体,一定要先具备识别错误使用结构体的能力。
结构体的另一个作用是构造特殊的数据结构,“数据结构”课程中的链表、树和图等数据结构经常会用到结构体。
用到共用体的问题较为少见,本章用这种类型研究了一下浮点数的内部结构。
本章还将介绍位运算的应用。位运算更多地被用于底层场合,但在应用问题中也有用武之地。
枚举类型的作用主要是美化代码、增强代码的可读性。为了美化代码、增强代码的可读性,C语言的开发者居然特意发明了一种特别的数据类型,可见美化代码、增强代码的可读性在软件开发中有多么重要。
C99新增的_Bool类型的作用与枚举类型的作用异曲同工。
第8章 数据类型的深入讨论。函数也是一种数据类型,和数组类似,函数名在不同场合下也表现为不同的数据类型,在多数场合下,函数名表现为指向函数的指针。
指向函数的指针实现了把“动作”数据化,比如可以通过向函数传递指向函数的指针来实现向函数传递“动作”,即所谓的“回调函数”。C 语言有两个库函数(qsort()和bsearch())用到了“回调函数”,本章将详细解释这两个库函数的用法。据作者观察,“团体程序设计天梯赛”中用到这两个库函数的题目似乎每年都有。
真正理解了指针、数组、函数等数据类型,掌握用它们组合构造出的数据类型并不难,无论是构造出所需要的数据类型还是解读既有数据类型的含义。
本章还将介绍动态使用内存的相关知识,以及一种简单的数据结构——链表,但严格来说,链表并不属于C语言的内容。
第9章 输入与输出。本章主要介绍如何利用外存文件保存或读取数据。本章的练习不适合布置于Online Judge上,要由学习者在本地进行评测。
第10章 程序组织与编译预处理。这些内容主要适用于规模较大的程序的组织,作者本想结合实例来讲解,但限于篇幅,只讲解一些原则。
第11章 标准库简介。标准库也是C语言的一部分,这部分的内容,通常是以表格形式呈现的,但考虑到本书的读者对象是初学者,于是作者用尽可能详尽的语言对其进行介绍,包括一些背景知识及如何应用。
致谢
编程是一项追求尽善尽美的理想主义智力活动,而写书是一项需要不断向现实主义妥协的工程。
然而仅有妥协是远远不够的,本书能得以完成,和很多人的鼓励、支持和帮助是分不开的,作者在这里向他们表示诚挚的感谢。
感谢东南大学出版社的编辑夏莉莉女士,她是本书的原点。10年前,有很多出版社找到作者编写教材,而夏女士是唯一邀请作者编写教材,只提质量要求而没有提任何包销要求的编辑。如果不是她,作者也许不会开始编写教材,也就不会有今天这本书。
在那本书出版后,作者无意间得知一位网名为“xliang9550”的网友,居然到书店用了整整一下午的时间把书“从前言到附录”通读了一遍,之后在某技术社区发表了一个简要的书评,并给予了那本书中肯且高度的评价。
这位“xliang9550”网友毫无疑问是懂C语言的,也是懂书的。自古知己难逢、知音难觅,没想到作者默默无闻地努力,千里之外竟然有素不相识的人看在眼里,并给予了热切的肯定,这让作者非常感动。在本书的写作过程中,这件事一直在激励着作者努力把书写得更好。
作者将努力联系到这位“xliang9550”网友,希望他能知道作者的感激之心,以及他对本书的贡献,并希望他能继续对拙著进行批评与指正。
汪慧君同学发现并指出了书中的一些不足之处,后期帮忙整理了本书的练习,在此表示感谢。
此外,要特别感谢本书的编辑贺志洪先生,感谢他的耐心和包容,使得本书最终得以完成,如果不是他,本书很可能早已“夭折”。
党的二十大,让作者学到了一个新词——“守正创新”。不知道为什么,作者最初读到这个词,莫名地就感到特别有共鸣。现在细想之下,“守正创新”恰恰是对作者创作这本书时那种难以言表的“冥冥之志”的揭示和总结,这正是作者期望达成的。
作者
2024年9月25日星期三于博雅居
展开