
前言
学习编程就是学习如何用计算机解决问题。这是一个漫长而艰难的过程,充满了波折,但同时也会带来各种回报。刚开始学的时候,如果能编写程序并得出正确的结果,那么你会觉得小有收获;在继续学习的过程中,如果你发现自己竟然能解决以前不敢想象的大问题,那么你会觉得收获颇丰。
学习编程首先要从学习一门编程语言开始。本书想做的主要就是帮大家学会C语言。学习一门编程语言首先要学习它的语法,必须记住重要的关键字、标点以及基本的程序结构,并理解它们的含义。
本书所用的教学方式是告诉你一套工具、方法及操作手段,尽量降低你在学习过程中所遇问题的难度。书里的每个程序都是完整的,而且是采用新式的C语言语法编写的,同时笔者还会告诉你这个程序应该输出什么样的结果。
学习编程还有一个特殊的困难,即其中的变数太多。这里所说的“变数太多”意指编程的每个方面都会随时发生变化,而且以后会持续改变。计算机的硬件与操作系统会根据新的用法与需求而演进,计算机的编程语言也会演进,它需要克服旧版的缺陷,并提供新的特性,以解决新的问题。编程语言的用法会随着编程语言本身而发生改变。我们想把计算机放在不同的场景中使用,于是就必须让计算机能够解决新场景中的新问题。最后,使用计算机与计算机编程语言的用户及开发者自然也会变。编程语言的用法改变会让开发者用新的方式思考问题,而问题与问题的解法发生变化则会促使我们考虑可否进一步扩大编程语言的适用范围,这又推动了编程语言的下一轮发展。这个循环会一直持续下去。
C语言最早是由Dennis Ritchie(丹尼斯·里奇,1941—2011)在20世纪70年代初开发的,但目前的C语言跟那时相比已经发生了很大变化。刚开始,C是一个特别简单但功能特别强大的语言,用来开发贝尔实验室的早期UNIX操作系统。那种C语言不是给初学编程的人使用的,它要求开发者必须先掌握高深的知识与编程技巧,然后才能用C写出健壮而稳定的程序。后来,C语言的编译器变得越来越普及,而设计编译器的人也花了很大功夫来限制那些有可能引发危险的语言特性。第一个符合ANSI标准的C语言(即ANSI C)是在1989年确定的。这个版本在1999年做了大幅修订,修订后的C语言叫作C99,它添加了一些重要的特性,并澄清了C语言中的许多行为。后来,C语言又经过两次修订,这两次所得到的版本分别叫作C11和C18[1],它们都在前一版的基础上增加了少量内容,并修正了语言内部的一些问题。
目前的C语言要比早期版本更规范、更复杂,但与早期版本一样,它仍然是一门强大、高效,而且适用范围很广的语言。本书努力采用C99、C11与C18规范所定义的语法和概念来讲授C语言,并确保其中的每个程序都能在C11规范下编译并运行。以后,C18规范肯定会比现在更加流行,笔者希望,到时我们能用C18规范来顺利地编译并运行书中的程序。
就算不考虑刚才说的那些变数,我们也必须不断学习。读完本书之后,你应该能确定自己使用C语言的方式,在用C语言解决了许多问题之后,你又会发现新的东西,也就是说,你会发现,自己原来并不知道,C语言还有这样一些特性、用法以及局限。学编程实际上学的不单是编程本身,而是学习如何通过编写程序去解决问题,因此,这是一个“学习如何学习”的过程。
在阅读本书的过程中,你还会懂得一些跟C语言没有直接关系的编程概念,笔者会讲解一套通用的开发流程,而且会编写一款纸牌程序来演示如何运用这套流程。你可能对纸牌程序不感兴趣,但你应该关注的重点是这款程序是用怎样的一套流程来开发的。笔者在运用这套开发流程的时候会展示基本的实验与验证手法。
读者对象
笔者构思本书时,是想写给两类人看,一类是没学过任何编程语言的新手,另一类是虽然用过其他编程语言,但并没有接触过C语言的开发者。这两种人的需求差别很大。
对于纯粹的编程新手来说,笔者需要为他们详细讲解C语言的一些重要概念和编程方式,帮助他们成为熟练的C语言开发者。为此,笔者会细化每个概念,并通过实际的程序来演示。编程新手只需要熟悉计算机的基本操作就可以直接阅读本书,不需要提前具备其他专业知识。
对于用过其他编程语言的人来说,笔者想要在本书中完整展示C语言的语法以及一些常见的编程习惯。如果你是这类读者,那么只需要略读一下笔者解释概念的那部分内容,你应该把重点放在源代码上面。
无论你是哪类读者,都可以通过本书展示的这80多款程序,了解C语言的语法与编程风格——这种编程风格以及其中所采用的编程习惯在C语言中很常见,但在其他语言中则不一定会遇见。笔者会把在近40年的编程工作中所积累的实践经验,详细分享给大家。
本书内容
第一部分主要介绍与C语言的语法及程序结构有关的基础概念。
第1章介绍程序的开发流程,以及本书后续章节所要用到的工具。我们会用这些工具来建立、构建并运行第一个C语言程序,也就是“Hello, world!”程序。笔者还会介绍怎样给代码添加注释,以及如何用代码做实验。
第2章介绍语句和块,还会解释函数定义、函数声明以及函数原型的概念。笔者会演示如何调用函数,并告诉大家函数执行的顺序。语句、块与函数决定了C程序的结构。
第3章解释C语言如何运用数据类型,以各种方式来表示值。每种数据类型都有大小与取值范围,C语言会据此解读某个值的含义。
第4章介绍变量与常量,这两种量都用来容纳值。要想让变量具备某个值,我们必须给它赋值,笔者会介绍各种赋值方式。
第5章介绍运算,笔者会告诉大家与各种数据类型有关的运算,以及如何通过这样的运算来操作该类型的值。
第6章介绍流程控制语句,该语句会根据某个表达式的结果来决定是执行其中一组语句,还是执行另一组语句。
第7章介绍各种循环语句。笔者还会演示怎样正确使用goto,以及怎样避免滥用goto。另外,笔者还会介绍一些其他的控制循环与迭代的方法。
第8章解释命名常量与枚举,并说明它们的用法。
第二部分针对第一部分所讲的基础(或固有)数据类型进行扩展延伸,帮助大家理解更复杂的数据类型。
第9章讲解怎样用一组变量来制作结构体(structure),以表示复杂的对象。笔者会介绍可以在结构体上执行的操作,以及结构体与面向对象编程(Object-Oriented Programming,OOP)之间的关系。
第10章介绍如何给enum与struct关键字所声明出的枚举及结构体重命名,还会讲解编译器的选项及头文件。
第11章演示如何定义、初始化并访问简单的数组。笔者会讲解怎样使用循环遍历数组,还会演示如何通过函数来操作数组。
第12章会把数组从一维(也叫单维)推广到二维、三维乃至任意维度。笔者会演示如何用循环与函数来声明、初始化并访问这样的多维数组。
第13章介绍直接寻址和通过指针间接寻址这两种寻址方式之间的区别。笔者会告诉大家如何理解“指针”这一概念,还会演示怎样在函数中使用指针,以及怎样使用指向结构体的指针。
第14章讲解指针与数组之间的相似之处与区别。
第15章介绍ASCII字符集与C字符串。C字符串是一种带有两项特殊属性的数组。我们会开发一款程序,把ASCII字符集用表格的形式打印出来。笔者还会介绍C语言标准库中与字符串有关的操作。
第16章在结构体与数组等概念的基础上创建各种复杂的数据结构并综合运用。笔者会开发一款完整的扑克程序,并在此过程中演示每一种复杂的结构体。大家可以通过本章相当透彻地了解什么叫作有步骤的迭代式程序开发。
第三部分讲解如何用各种方式分配及释放(也叫解除分配)内存。
第17章介绍自动存储类与动态存储类的概念及区别,还对比了内部存储类与外部存储类这两种存储方式。笔者会演示如何用static关键字声明静态变量。
第18章介绍如何进行动态内存分配,以及如何对这样分配的内存执行各种操作。笔者会演示一款动态链表程序,还会介绍其他一些动态分配的结构体。
第四部分介绍与读取数据(也就是输入数据)及写入数据(也就是输出数据)有关的各种话题。
第19章详细介绍printf()函数的各种格式说明符,笔者会讲解与每一种固有的数据类型相对应的说明符,包括带符号的整数、不带符号的整数、单精度浮点数、双精度浮点数、字符串以及字符。
第20章演示怎样利用main()函数的argc与argv参数,来获取用户在执行本程序时通过命令行界面所输入的内容。
第21章演示如何用scanf()函数读取输入流中的值。笔者会告诉大家,printf()与scanf()所使用的格式说明符虽然相似,但实际上有很大区别。我们还会谈到内部数据转换以及无格式的(或者说,未经格式化的)输入与输出。
第22章主要谈概念。笔者会讲解文件的基本概念,并演示怎样在程序中开启和关闭文件,以及如何让用户通过命令行界面来指定有待操作的文件名。
第23章演示怎样通过getopt()函数获取用户在命令行界面所指定的各种选项(也叫开关)。笔者会扩充早前的范例程序,让它能够读取输入文件中所包含的名字,并通过链表给这些名字排序,然后将排序结果写入文件。
第五部分详细解释如何创建并管理含有多个文件的程序项目(即多文件的程序)。
第24章以第16章中创建的程序作示例,告诉大家怎样把程序代码合理地拆分到多个源文件中,让每个源文件所包含的函数都跟该文件所要操作的结构体相符。笔者还会介绍怎样高效而安全地使用预处理器。
第25章从不同的方面讲解作用域这一概念,并解释其与单文件的程序和多文件的程序有何关系。笔者会详细描述变量的作用域及函数的作用域。最后,笔者会提出一些建议,告诉大家在看完本书之后,如何继续学习C语言乃至一般的程序开发知识。
附录中包含许多参考资料,即C语言关键字、运算符优先级、某些常用GCC与CLang选项、ASCII字符集、Bstrlib库用法、Unicode概论以及对C语言标准库中各头文件所做的介绍。
怎样充分利用这书
为了学习书中的内容,你需要准备一款简单的文本编辑器、一个终端机或控制台程序[2],以及一款编译器。笔者会在第1章描述这三样工具,并告诉你如何下载与使用它们。下面的表格列出了本书对编程工具所做的技术要求。

要想在Linux操作系统中安装GCC,你需要执行这样的命令:
□如果你使用的是基于RPM的Linux系统,例如RedHat、Fedora或CentOS等,那么请打开Terminal窗口(终端窗口或终端机窗口),并执行下列命令:

□如果你使用的是Debian Linux,那么请打开Terminal窗口,并执行下列命令:

为了判断自己是否正确安装了GCC或Clang,请在Terminal窗口中执行下列命令:

无论你看的是本书电子版还是纸质版,笔者都建议你亲手把代码敲一遍。手工输入完代码之后,你再去跟GitHub网站上面的代码库进行对比(稍后会给出本书的代码库地址)。这样做可以减少因复制和粘贴而引发的代码错误。
如果你是编程新手,那么准备好开发工具之后,还必须了解如何阅读编程方面的书籍。在读此类书籍时,你应该采用如下方法:
1.通读全章,大致了解本章所要展示的概念。
2.将本章再读一遍,这次阅读时,每遇到一个程序,就把该程序的代码打出来,然后运行,以确保自己所看到的结果跟书中讲的相同。如果没有得到应有的结果,那就试着弄清楚自己的程序跟书中的程序有什么区别(找到区别之后,修改程序让它得出与本书相符的结果)。编程与数学类似,都必须做练习,只有这样才能学会编程。只观察代码是学不会编程的,你必须亲自编写代码才行。
3.用心记住本章所提到的关键字和语法。这可以大幅提升学习速度。
4.精准地思考。编程语言对语法要求相当严格,这是尤其需要注意的。你必须仔细地考虑问题,有时为了解决某个问题,你需要费尽心思才能想出正确的步骤。
5.将本章提到的概念与范例程序复习一遍,把不懂的地方记下来。
如果你以前用过别的编程语言,那么在初学C语言时笔者还是强烈建议你能够先把每章的正文和其中的范例程序略读一遍。然后,再把程序代码录入自己的计算机,并确保程序的运行结果正确无误。这样能够帮助你迅速掌握C语言的语法及编程习惯。
在阅读编程方面的书籍时,你必须先了解所读的书属于哪一类,只有这样,你才能用最合适的办法来阅读。我们见到的计算机编程书籍一般有以下几类:
□概念书(讲述编程概念的书籍):这一类书籍会从底层概念与设计理念的角度来讲述。Kernighan与Ritchie所写的The C Programming Language(《C程序设计语言》)就属于此类。
□教科书(教材):这一类书籍通常会把编程语言的每个主要方面都讲到,有时还会讲解得极其详细,而且通常包含许多代码片段。Paul Deitel与Harvey Deitel写的书[3],以及K.N.King写的C Programming: A Modern Approach(《C语言程序设计:现代方法》)就属于此类。这些书籍通常用在正式的编程课中。
□参考书:这一类书籍会详细描述语法规则。Harbison与Steele写的C:A Reference Manual(《C语言参考手册》)就属于此类。
□“菜谱书”(教程):这一类书籍会给出具体的解决方案,告诉你怎样用某种语言来解决某些问题。Perry写的Advanced C Programming by Example、Van Der Linden写的Expert C Programming: Deep Secrets(《C专家编程》)以及Sedgewick写的Algorithms in C(《算法:C语言实现》)就属于此类。
□专题书:这一类书籍深入研究编程语言的某个或某些方面。Reek写的Pointers in C(《C和指针》)就属于此类。
□实践书:这一类书籍会告诉你怎样用某种语言(例如C语言)解决一般的编程问题。Hanson写的C Interfaces and Implementations(《C语言接口与实现》)与Klemens写的21st Century C: C Tips from the New School(《C程序设计新思维》)就属于此类。
每一类书籍的读法都不同。比方说,概念书可能只读一遍就好,但参考书则应该放在手边反复翻阅,另外你可能还得准备一些教程(即“菜谱书”)用来解决有可能遇到的某些问题。
笔者希望本书能够同时具备C语言“菜谱书”、参考书与实践书的功能。说它是菜谱书,是因为书中提供的程序代码都是能够运行的代码,你可以根据运行结果,判断你所采用的C语言编译器在当前使用的操作系统上所表现出的行为。说它是参考书,是因为书中提供了足够多的C语言知识,让你能够把它作为初次接触C语言时的参考资料。说它是实践书,是因为笔者想要在书中给大家演示编写C语言代码的最佳方式。
笔者还希望你在阅读完本书之后,能够继续阅读其他书籍。在甄选后续的书籍时,至少应该选择针对C99标准而写的书,如果用的是C11或C18标准,那就更好了。早于C99标准的代码基本上都是旧式的代码,从C99开始,我们有了更加高效的C语言编程方式。
下载示例代码及图像
本书的代码包也托管在GitHub上,地址为:https://github.com/PacktPubli-shing/Learn-C-Programming。如果代码有更新,笔者会把新版代码放在GitHub代码仓库中。
你也可以访问https://github.com/PacktPublishing/查看其他代码包,那里还有丰富的图书与视频资源。
书中的截屏与图表可以在https://static.packt-cdn.com/downloads/9781789349917_ ColorImages.pdf下载。
本书约定
下面是本书的排版约定。
CodeInText(代码体):文本中的代码、数据库表名、文件夹名/目录名、文件名、文件扩展名、路径名、举例时所用的URL、用户输入的内容,以及Twitter账号名,都印刷成代码体。例如:“友情提示,这个需要匹配的地方涉及int main()与return 0;这两行代码”。
整块的代码,印刷成下面这样:

如果笔者要强调某块代码中的某一部分,那么相应的代码行或代码项会印刷成粗体:

需要在命令行界面输入的命令,或者命令所输出的内容,则会以如下形式印刷:

Bold(粗体):表示某个新术语或重要词汇,以及屏幕上的关键文字。比方说,菜单或对话框中的文字就会印刷成粗体。例如:“这是个有用的程序,因为它会把一些东西输出到Terminal(终端/终端机),也就是consoe(控制台)”。
警示信息或者重要的评注信息。
提示与技巧。
[1] 也叫作C17。——译者注
[2] 也就是俗称的命令行界面或命令行窗口。——译者注
[3] 例如C How To Program(《C语言大学教程》)、C++ How to Program(《C++大学教程》)等。——译者注