“笨办法”学C语言

978-7-115-47730-9
作者: [美] 泽德 A. 肖(Zed A. Shaw)
译者: 王巍巍
编辑: 杨海玲
分类: C语言

图书目录:

详情

这本书的目标是让读者掌握足够的C语言技能,从而可以自己用C语言些程序或者修改别人的C语言代码,成为一个名优秀的程序员,但这并不完全是一本讲C语言编程的书,书中还重点关注防御性编程。 本书以习题的方式引导读者一步一步学习编程,结构非常简单,共包括52个习题,每一个习题都重点讲解一个重要的主题,多数是以代码开始,然后解释代码的编写,再运行并测试程序,最后给出附加任务。

图书摘要

版权信息

书名:“笨办法”学C语言

ISBN:978-7-115-47730-9

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

著    [美] 泽德 A. 肖(Zed A. Shaw)

译    王巍巍

责任编辑 杨海玲

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315


这本书的目标是让读者掌握足够的C语言技能,从而可以自己用C语言编写程序或者修改别人的C语言代码,成为一名优秀的程序员。但这并不完全是一本讲C语言编程的书,书中还重点介绍防御性编程。本书以习题的方式引导读者一步一步学习编程,结构非常简单,共包括52个习题,每一个习题都重点讲解一个重要的主题,多数是以代码开始,然后解释代码的编写,再运行并测试程序,最后给出附加任务。此外,每个习题都配套教学视频。

本书是写给至少学过一门编程语言的读者的,本书有趣、简单,并且讲解方法独特,让读者了解众多C语言的基础知识和C程序中常见的缺陷,在慢慢增强自己的技术能力的同时,深入了解怎样破坏程序,以及怎样让代码更安全。


Authorized translation from the English language edition, entitled LEARN C THE HARD WAY: PRACTICAL EXERCISES ON THE COMPUTATIONAL SUBJECTS YOU KEEP AVOIDING (LIKE C), 1st Edition, ISBN: 0321884922 by SHAW, ZED A., published by Pearson Education, Inc, Copyright © 2016 Zed A. Shaw.

All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc.

CHINESE SIMPLIFIED language edition published by POSTS AND TELECOMMUNICATIONS PRESS, Copyright © 2018.

本书中文简体字版由Pearson Education Inc.授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

本书封面贴有Pearson Education(培生教育出版集团)激光防伪标签,无标签者不得销售。

版权所有,侵权必究。


王巍巍是一名受软件和编程的吸引,从硬件测试做到软件测试,又从软件测试做到软件开发的IT从业人员。代码和翻译是他的两大爱好,此外他还喜欢在网上撰写和翻译一些不着边际的话题和文章。如果读者对书中的内容有疑问,或者发现了书中的错误,再或者只是想随便聊聊,请通过电子邮件(wangweiwei@outlook.com)与他联系。


大部分技术书籍都在教你一些具体的东西:某门语言、某种技术、某个框架、某个工具……你看完书,大致模拟演练一下,然后就可以把这样东西写在简历里。你把简历发给心仪的公司,就幸运地获得了一份新工作。你在新公司解决了一些技术问题,但也积累了一些技术债务。你解决的问题为你带来更多经验并铺平了你前进的道路,积累的债务你也不用担心,因为没过几年,你要么已经成功升职,没人敢让你还债,要么已经成功跳槽,没人能找到你还债了。

这就是每一名上进的程序员的上升之路,用一个时髦的词汇讲,这算得上个人成长的“增长黑客”之路。当然,等这本书出版的时候,这个词也许跟“给力”一样,被扫到互联网的垃圾堆里了。

这本书不是这样的,作者竟然找了一门已经过时的编程语言,来教你一些没法写在简历里的东西。作者疯了?

其实这本书和作者的其他书一样,表面上是在教你编程语言,实际上是在教你编程的思维方式和最佳实践。这些东西在学校的课堂里讲得不多,市面上的书籍讲这个的就更少。工作时间长了,你也许会遇到一种人,他们技术水平似乎挺不错,很多东西都能讲出些门道,但写的代码质量却不太理想。这样的人,也许就是缺了这么一课。这一课很多人都是在工作实战中通过栽跟头补上的,但是现在你可以通过这本书补上。

这本书的价值就是在于让初级水平的程序员也能写出牢靠可用的代码。沉下心打好基础,未来的路才会更顺畅。

关于这本书涉及的具体话题,请参考作者的前言“这不完全是一本C语言的书”,我就不在此赘述了。

在这里我要感谢人民邮电出版社编辑的辛勤劳动,并且感谢王以恒小朋友没有跟他爸爸我抢电脑玩。

如有问题或者建议,欢迎和我联系,我的邮箱是wangweiwei@outlook.com。


我要感谢3类人,是他们让我的书以现在的面目问世:恨我的人、帮我的人,还有绘画的人。

恨我的人让我的这本书变得更强,更能站住脚。他们脑子不会转弯,盲目地崇拜着C语言的旧神祗们,完全没有教学经验。要是没有他们作为反例,我也许就不会这么努力,让这本教人成为更好的程序员的书完整问世了。

帮我的人包括Debra Williams Cauley、Vicki Rowland、Elizabeth Ryan,还有整个Addison-Wesley团队,以及每一个在线指出错误和提出建议的人。他们的制作、纠错、编辑、优化工作,让这本书变得更为专业,也更为优秀。

绘画的人就是Brian、Arthur、Vesta和Sarah,他们帮我找到了一种新的自我表达方式,让我忘掉了Debra和Vicki为我设得清清楚楚而我又次次赶不上的最后期限。没有绘画,没有这些艺术家们给我的艺术礼物,我的生活就没这么丰富和有意义了。

谢谢你们所有人在我写书过程中提供的帮助。完美的书不存在,这本书也谈不上完美,不过我已经尽力让它尽可能好了。


别觉得自己上当了,这本书其实并不是一本教你C语言编程的书。你会学到编写C语言程序,但这本书给你最重要的一课是严谨的防御性编程。现在有太多的程序员会天真地假设他们写的东西没问题,但却总有一天,灾难性的失败会发生在这些东西上面。如果你主要学习和使用的都是现代编程语言来解决问题,那你尤其会碰到这种情况。阅读了这本书,并跟着做了里边的习题,你就会学会怎样写出有自卫能力的程序,它们可以防卫自己不被恶意行为和自身缺陷所伤害。

我使用C语言有一个很特别的原因:C是一门“烂”语言。C语言有很多设计选择在20世纪70年代还算颇有意义,但放到今天则毫无道理。从毫无限制到处乱用的指针,到严重没法用的NUL结尾的字符串,这些东西是C程序几乎所有安全缺陷的罪魁祸首。尽管C语言用途广泛,但我相信C语言之烂,它是最难写出安全代码的一门语言。我猜就连汇编语言都比C语言更容易写出安全代码。说句心底的大实话,我觉得大家都不该再写新的C代码了。

既然这样,那为什么我还要教你C语言呢?因为我想要让你成为一个更好、更强大的程序员。如果你要变得更好,C语言是一个极佳的选择,其原因有二。首先,C语言缺乏任何现代的安全功能,这意味着你必须更为警惕,时刻了解真正发生的事情。如果你能写出安全、健壮的C代码,那你就能用任何编程语言写出安全、健壮的代码。你在这里学到的技术,可以应用到今后你用到的任何编程语言中。其次,学习C语言让你能直接接触到如山似海的旧代码,还能教会你众多衍生语言的基本语法。一旦学了C语言,你学习C++、Java、Objective-C和JavaScript也就更容易,就连一些别的语言也会变得更加易学了。

告诉你这些不是为了把你吓跑,我计划把这本书写得非常有趣、简单而且“离经叛道”。我会给你一些用别的语言也许很少会去做的项目,通过这种方式让你从中获得乐趣。我会用我百试不爽的习题模式,让你学习C语言编程并且慢慢增强自己的能力,这种方式会让你觉得这本书很好上手。我还会教你怎样破坏程序以及怎样让你的代码变得更安全,让你知道为什么这些事情很重要,这种方式可以说是相当的“离经叛道”。你将学会怎样导致栈溢出和非法内存访问,了解众多C程序中常见的缺陷,最终知道自己面对的究竟是什么。

像我的所有书一样,这本书的通关很有挑战性,不过如果你真的通关了,那会成为一名更好且更自信的程序员。

当你学完这本书以后,你会有能力调试、阅读、修正几乎所有你遇到的C程序,还能在需要的时候写出新的、稳固的C代码。然而,我不会教你官方的C语言。你会学到C语言,也能学到如何正确使用它,但官方C语言并不是非常安全。大多数的C程序员写的代码并不稳固,其原因就在于一个叫未定义行为(undefined behavior,UB)的东西。未定义行为是美国国家标准组织(ANSI)的C语言标准中的一部分,这部分罗列了编译器能忽略你写的代码的所有方法。这份标准里真的有这么一块内容,里边写着如果你这么写代码,那么编译器就不和你玩了,它的行为就会变得不可预测。当C程序读到字符串的结尾,就会发生未定义行为,这是C语言中极其常见的一种错误。再来讲点背景吧,C语言将字符串定义为以NUL字节(为简化定义,可称为0字节)结尾的内存块。由于很多字符串都是来自程序之外,C程序会经常接收到不包含NUL字节的字符串。当发生这种情况的时候,C程序会越过字符串结尾接着读下去,读到计算机的内存中去,从而导致程序崩溃。C语言之后的每一种语言都试图避免这种情况的发生,但C却是个例外。C语言自己几乎完全不会预防未定义行为,而似乎每一个C程序员都认为这意味着他们无须应付这件事情。他们写的代码里充满了NUL字节越界的潜在可能性,当你指出这些地方以后,他们会说:“不就是未定义行为嘛,没必要费劲儿预防的。”这种对C程序中大量未定义行为的依赖,就是大部分C代码都极其不安全的原因所在。

我写代码时会试图避免未定义行为,避免的方法就是让我写的代码要么不触发未定义行为,要么能防止未定义行为。后来我发现这是一个不可能的任务,因为未定义行为实在太多了,到处都是各种互相关联的陷阱,形成一个难解的戈耳狄俄斯之结。在你学习这本书的过程中,我会指出各种你会触发未定义行为的方法,告诉你可能的话该如何避免,以及如何在别人的代码中触发可能的未定义行为。不过你应该记住,要完全避免未定义行为这种带着近乎随机性质的东西是几乎不可能的,你也只能尽力而为。

警告 

你会发现C语言的铁粉会拿未定义行为这个话题来欺负你。有一类C程序员,他们写的C程序不多,但他们记住了所有的未定义行为,并以此来欺负编程新手的智商。别理这样的人。大部分时候,他们并不是真正会写程序的C程序员,他们目空一切,出言不逊,只会没完没了考你。他们所做的一切,都只是为了证明自己高人一等,而不是为了帮忙解决问题。如果你需要有人帮忙指点一下你的C代码,给我的邮箱help@learncodethehardway.org写封邮件就好了,我很乐意帮你。

未定义行为的存在,是你学习C语言的又一个理由,如果你想成为一名更好的程序员,这会对你很有帮助。如果你能用我教你的方法写出良好稳固的C代码,那你就能应付任何语言。C语言还有积极的一面,它在很多方面都是一门真正优美的语言。尽管它功能强大,语法却简单到令人难以置信。在差不多45年里,众多语言都复制了C语言的语法,这不是没有原因的。C语言会给你提供很多,使用到的技术却极少。学完C语言后你会发现,这门语言既优雅美丽,同时又有几分丑陋。C语言很老了,它就像一块纪念碑,远看雄伟壮丽,近看会有很多的裂缝和缺陷。

我知道我用的C挺稳固,因为我花了20年撰写整洁、稳固的C代码,它们支撑了大型程序的运作,而且基本没怎么出过问题。我的C代码支撑了Twitter和Airbnb等公司的业务,这些代码也许已经处理过数万亿个事务。它们极少出问题或受到安全攻击。在许多年里我的代码都支撑着Ruby on Rails的Web世界,它一直运行完美,甚至还防止过安全攻击,而别的Web服务器程序常被各种简单的攻击攻陷。

我的C代码编写风格是很稳固的,不过更重要的是我写C代码时的意识状态,这应该是每一个程序员都应具备的东西。我在着手C语言或者任何别的编程语言时,会时刻想着尽可能预防错误的发生,并且会带着凡事皆不会顺利的想法。别的程序员,就连那些据说很厉害的程序员,也会在写代码时假设一切顺利,而后则需要依赖未定义行为或操作系统来拉自己一把,这二者作为解决方案都是不及格的。你只需要记住,如果有人告诉你,说我在这本书里教你的不是“真正的C语言”,那么你可以考察一下他们的编程历史,如果他们的纪录没我这么好,那么没准儿你还可以用我教你的方法,给他们展示一下为什么他们的代码安全性不太好。

那么是不是我的代码就完美了呢?当然不是。这可是C代码。写出完美的C代码是不可能的,其实用任何语言写出完美的代码都是不可能的。编程的乐趣和烦恼,有一半都和这一点有关。我可以把别人的代码批得一文不值,别人也可以把我的代码贬得一无是处。所有的代码都是有缺点的,但我假设自己的代码总有缺陷,然后去避免这些缺陷,那么事情就不一样了。如果你学完了这本书,我给你的礼物就是教会你防御性编程的思维模式,这种意识在20年里为我带来了不少好处,让我写出了高质量、健壮的软件。

这本书的目的是让你掌握足够的C语言技能,从而可以自己写软件,或者修改别人的C代码。学完这本书以后,你应该去阅读Brian Kernighan和Dennis Ritchie的《C语言编程设计(第2版)》,英文书名为C Programming Language, Second Edition,这是C语言发明者写的一本书,又称作K&R C。我将教会你的是以下内容:

等你完成了最后一个习题,你将会拥有充足的“弹药”,用来应对基本的系统软件、库以及别的小型项目。

本书针对的是至少学过一门编程语言的人。如果你还没学过编程语言,我建议你通过我的《“笨办法”学Python》(Learn Python the Hard Way)开始学习,它是一本专为初学者写的书,是一本非常有效的编程入门书。读过《“笨办法”学Python》以后,你就可以开始看这本书了。

对于已经学过编程的人来说,本书一开始也许看上去有些奇怪。别的书里边会有大段大段的讲解,然后让你时不时写一点儿代码。这本书不一样,每一个习题都有视频讲解,你一上手就要输入代码,然后我再向你解释你输入的内容。这种形式更为有效,因为用抽象的形式解释你不熟悉的东西你会很难理解,而如果你已经做过了一次,我解释起来就更为容易了。

由于本书结构独特,你必须在学习时遵守几条规则。

如果你遵守这些规则,照着书里的内容去做了,最后还没有学会编写C代码,那么至少你已经尝试过了。C语言并不适合每一个人,但尝试的过程会让你成为一个更好的程序员。

本书为每一个习题都配了视频[1],很多情况下一个习题会包含多个视频。这些视频至关重要,能让你完全领会本书的教学方法。这样做是因为撰写C代码的很多问题都是交互问题,交互的对象是失败、调试、命令等东西。Python和Ruby这样的编程语言中,代码要运行就运行了。C语言中要让代码运行,要修正问题,需要的交互要多得多。有些话题用视频讲解也更容易,比如指针和内存管理,在视频中我可以演示机器真正是如何工作的。

我建议你先看视频再做习题,除非我另有指示。在有的习题中,我使用一个视频演示问题,再用另一个视频来演示解决方案。在另外的大部分习题中,我使用视频作为讲座,然后你完成练习,弄懂课题。

我猜你是从一门菜鸟语言来到这里的(我只是在调侃而已,你应该看得出来)。要么你来自像Python或者Ruby这样“还算能用”的语言,这些语言让思维不清、半吊子、瞎鼓捣的人也能写出能运行的程序来;要么你用过Lisp这样的编程语言,这些编程语言假装计算机是某个纯函数的仙境,四周还装了五彩的婴儿墙;要么你学过Prolog,因而认为整个世界应该只是一个供你上下求索的数据库。更糟糕的还在后面,我打赌你还用过某个集成开发环境(integrated development environment,IDE),所以你的脑子充满里记忆空洞,如果你不是每敲3个字符就按一次Ctrl+Space的话,我怕你连一个完整的函数名称都敲不出来。

不管背景如何,你都可能有以下4样技能有待提高。

如果你平时使用IDE的话,尤其如此。不过大体来说我发现程序员略读的时候太多了,从而导致理解性阅读能力有些问题。他们将代码扫视一遍就觉得自己读懂了,其实不然。其他编程语言还提供了各种工具,从而避免让程序员直接撰写代码,所以一旦面对C这样的编程语言,他们就立马崩溃了。最简单的办法就是要理解每个人都有这样的问题,解决方案就是强迫自己慢下来,更加细致地去读写代码。一开始你也许会觉着很痛苦、很烦躁,那就增加自己休息的频率,最后你会觉得这其实也很容易做到。

这方面没有人能做得好,这也是产生劣质软件的最大成因。其他编程语言会让不够专注的你蒙混过关,但C语言却要求你完全聚精会神,因为C语言直接和计算机打交道,而计算机又是极其挑剔的。在C的语境中没有“有点儿像”或是“差不多”这样的说法,所以你需要专注。反复检查你的工作。在证明正确之前,要先假设一切都可能是错的。

用过其他编程语言的程序员有一个问题,就是他们的大脑已经被训练成可以发现那种语言中的差异,而不是C语言中的差异。当你在对比你的代码和标准答案时,你的视线会直接跳过那些你认为不重要或不熟悉的部分。我给你的解决办法是:强迫自己观察自己的错误,如果你的代码跟本书中的代码不是一字不差,那它就是错的。

我喜欢其他更简单的编程语言,因为我可以“胡搞乱来”。我把想法敲出来,然后就能直接在编译器里看到结果。这些语言可以让你很方便地尝试新的想法,但你有没有发现:如果你一直用“乱改直到能用”的方法写代码,到头来就是什么都不能用了。C语言对你要求比较高,因为它要求你先计划好要创建的东西。当然你也可以偶尔胡乱弄弄,但和其他编程语言相比,你需要在更早的阶段就开始认真做计划。在你写代码之前,我会教你如何规划程序的关键部分,希望这能同时使你成为一个更优秀的程序员。即使是很小的计划也能让你的后续工作更为顺利。

在学习C语言的过程中,你将被迫更早、更多地应对这些问题,所以学习C语言更能让你成为一名更好的程序员。你不能对自己写的东西思维不清,否则什么都不会做出来。C语言的优势是,作为一门简单的语言,你可以自己把它弄明白,因此如果你要学习机器的工作原理,并增强这些核心的编程技能的话,C语言是上佳的选择。

[1] 本书配套视频读者可扫各习题首页标题边上的二维码在线观看。——编者注


老规矩,第一个练习是习题0,讲的是怎样配置你的计算机,为本书后续学习做好准备工作。在这个习题中,你将为自己的计算机安装相应的工具包和软件。

Linux很可能是C语言开发最好配置的操作系统。对于Debian系统,你只要从命令行运行下面这条命令:

$ sudo apt−get install build−essential

下面是在基于RPM的系统(如Fedora、RedHat或者CentOS 7)上安装同样的配置所使用的命令:

$ sudo yum groupinstall development−tools

如果你使用的是不同的Linux变体,只要搜索一下“C development tools”(C开发工具)加上你的Linux名称,就能找到所需要的工具。安装好以后,你可以在命令行输入:

$ cc --version

这样就能看到你安装的是什么编译器(compiler)。你最有可能安装的是GNU C Compiler (GCC),不过,如果你安装的和本书中用到的不一样,那也没关系。你还可以试着安装一下Clang C 编译器,参考一下你Linux系统的Clang’s Getting Started文档,如果这些文档不适用,那就上网搜索一下。

在Mac OS X上安装就更容易了。你需要先从苹果公司官网下载最新版的Xcode,或者找出你的安装DVD,通过DVD进行安装。下载文件很大,可能永远都下载不完,所以我建议你使用DVD。此外,上网搜索一下“installing Xcode”(安装Xcode),看看安装说明。你还可以使用App Store(应用商店)来安装Xcode,步骤和你安装别的应用一样,这种方式安装以后,你还可以自动收到更新。

输入下面的命令以确保你的C编译器能正常工作:

$ cc --version

你应该能看到你正在使用的Clang C编译器的版本,不过如果你的Xcode版本较老,那么你可能已经给你安装了GCC。本书中二者皆可使用。

对于微软公司的Windows,推荐使用Cygwin系统来获取一系列标准的UNIX软件开发工具。除了Cygwin,你还可以选择MinGW系统,该系统更为精简,不过应该也可以使用。我要警告你的是,微软公司似乎在他们的开发工具中正逐渐取消对C语言的支持,所以如果用微软的编译器构建本书中的代码,你可能会遇到问题。

还有一个比较高级的选项,那就是用VirtualBox安装一个Linux发行版,在你的Windows机器上运行完整的Linux系统。这样还有一个好处:就算把虚拟机折腾坏,也不用担心你的Windows系统会受到影响。这也是学习使用Linux的一个好机会——学习Linux不仅有趣,对提高你的编程技能也有帮助。现在众多分布式计算和云平台企业的主要操作系统都是Linux。学习Linux可以让你增长知识,了解计算行业的未来。

程序员的文本编辑器选择真是一言难尽。对初学者来说,我会直接告诉他使用gedit,因为它既简单又适合编程使用。然而,它在有些多语言环境下会出问题,而且如果你已经写过一阵子程序的话,很有可能你已经有了一个自己喜欢的文本编辑器。

考虑到这一点,我还是建议你在自己的平台上试用几个标准的程序员文本编辑器,然后挑一个喜欢的。如果你用过gedit而且挺喜欢,那就继续用吧。如果你想要尝尝鲜,那就快速试试,然后选一个接着用。

最重要的事情是不要困在选择最佳编辑器的尝试中。文本编辑器都有这样或者那样的缺点。你只需要选一个持续使用就好,如果找到别的喜欢的编辑器,那就也试试。不要到头来把时间都消耗在配置编辑器以及使其更完美上面。

下面是一些值得尝试的编辑器。

市面上的编辑器很多,也许一人分一种都有多余的,以上只是一些我知道挺好用的免费编辑器,你可以找其中几个试试,或者你也可以试试商业版的编辑器,直到找到你喜欢的为止。

不要使用IDE

警告 

学习编程语言的时候不要使用集成开发环境(Integrated Development Environmen,IDE)。尽管它们是你完成任务的好帮手,但它们提供的帮助可能会阻碍你真正学会编程语言。以我的经验,不少编程高手都不使用IDE,而且写代码的速度和使用IDE不相上下。我还发现,使用IDE写出的代码质量会比较差。我不知道为什么会这样,不过如果你想要深入、稳固地掌握一门编程语言,那我强烈建议你在学习的时候不要使用IDE。

学会使用专业的程序员文本编辑器也是一项有用的职业技能。如果你需要依赖IDE,那你只能等拿到新的IDE以后才能开始学习新的编程语言。这会阻碍你学习新的流行语言,让你无法抢到前面,从而增加你的职业成本。使用通用的文本编辑器,你可以在任何时候使用任何编程语言写代码,不需要等待IDE的支持。通用文本编辑器意味着你可以自由探索,看情况自主决定自己的职业生涯。


一切安装好以后,你需要确认编译器能正常运行。最简单的办法就是写一个C程序。由于你已经至少学过一门编程语言,我相信你可以用这个小巧而涵盖面广的例子作为开始。

ex1.c

1    #include <stdio.h>
2    
3    /* This is a comment. */
4    int main(int argc, char *argv[])
5    {
6       int distance = 100;
7  
8       // this is also a comment
9       printf("You are %d miles away.\n", distance);
10 
11      return 0;
12   }

如果你运行这段代码有问题,那就先看看我在视频里是怎样做的。

在这段代码中有一些C语言的特性,你在输入代码的时候也许没弄明白。我来快速地逐行解释一下,然后我们就可以通过练习进一步更好地理解这些部分。详解中有的内容看不懂也没关系,我只是让你快速深入接触一下C语言。相信我,在本书的后面,你将学会所有这些概念。

对上述代码的逐行解释如下。

这个逐行解释部分信息量很大,所以你要逐行研究一下,确保自己至少大致明白代码所做的事情。你可能无法全部都弄懂,对于不懂的部分,你可以大致猜测一下,然后我们继续学习。

你可以将代码存到一个名为ex1.c的文件中,然后运行这里所示的命令。如果你不确定怎么做,那就看看这个习题的视频中我是怎么做的。

习题1会话

$ make ex1
cc -Wall -g ex1.c -o ex1
$ ./ex1
You are 100 miles away.
$

第一个命令用到了make,这个工具知道如何构建C程序(以及众多其他程序)。你运行并给它一个ex1的参数,这相当于告诉make让它去寻找ex1.c文件,运行编译器对其进行构建,并将生成的结果放到一个叫ex1的文件中。这个ex1是一个可执行文件,你可以用./ex1来运行它,然后它会为你输出结果。

在本书中,可能的情况下,我会为每一个程序提供一节内容,教你如何破坏程序。我会让你对程序做一些奇特的事情,用古怪的方法运行程序,或者修改代码,让程序崩溃或者出现编译错误。

对于这个程序,我要求你在里边随便删除一些东西,但依然还能让它编译通过。猜测一下哪些内容可以删除,然后重新编译,会看到什么错误。


我们将使用一个叫make的程序来简化习题代码的构建。make是一个颇有历史的程序,因此它知道如何构建许多类型的软件。在这个习题中,我将教会你一些Makefile语法,不多不少,足够让你继续接下来的课程,再后面的一个习题会教你更多Makefile的复杂用法。

make的工作原理是,你声明依赖,然后描述如何构建程序,或者依靠make程序的内部相关知识来构建最常见的软件。这个程序里边有几十年的知识积累,知道根据某些文件去构建其他文件的方法。在上一个习题中你已经使用命令做过这件事情了:

$ make ex1
# or this one too
$ CFLAGS="-Wall" make ex1

在第一个命令里,你告诉make:“我要创建一个叫ex1的文件。”然后make程序按下面的方式提出了问题并完成了任务。

1.ex1文件是不是已经存在了?

2.不存在。好的,是不是还有一个以ex1开头的文件?

3.是,这个文件是ex1.c。我知道怎样构建.c文件吗?

4.知道。我要执行cc ex1.c -o ex1命令。

5.那么我就使用cc构建ex1.c,给你生成一个ex1文件。

上面列出的第二条命令展示了将修饰符(modifier)传递给make命令的一种方法。如果你不熟悉UNIX shell的工作原理,可以创建这些环境变量,它们会被你运行的程序取到。根据你所用的shell类型,有时你可以使用export CFLAGS="-Wall"这样的命令。不过你也可以直接把它放到你要运行的命令前面,这样的话,这个环境变量将只作用于你运行的那条命令。

在这个例子中,我使用了CFLAGS="-Wall" make ex1,这样它就会为make通常运行的cc命令加上-Wall选项。这个命令行选项让编译器cc汇报所有的警告信息(然而事无常态,它无法准确地给出所有可能的警告)。

仅通过这种方式使用make,你其实也可以走很远,不过让我们学习一下如何创建Makefile,这样你会更好地理解make的功能。现在来创建一个文件,里边只包含如下内容。

ex2.1.mak

CFLAGS=-Wall -g

clean:
    rm –f ex1

将该文件存到你的当前目录中,命名为Makefilemake程序会自动假设Makefile的存在,并且会去运行这个文件。

警告 

确保你输入的只是制表符(Tab),而不是制表符和空格的混合。

这个Makefile文件向你展示了make的一些新功能。首先,我们在该文件中设置了CFLAGS,这样我们就无须再次设置它了,另外它还添加了-g标志,用来启用调试。然后,我们有一个叫clean的区块,用来告诉make如何清理我们的小项目。

确保该文件和你的ex1.c处于同一目录下,然后运行下面的命令:

$ make clean
$ make ex1

如果一切顺利,你应该看到以下结果[1]

习题2会话

$ make clean
rm -f ex1
$ make ex1
cc -Wall -g ex1.c -o ex1
ex1.c: In function 'main':
ex1.c:3: warning: implicit declaration of function 'printf'
$

这里你可以看到,我运行了make clean,告诉make运行我们的clean目标。再去看看Makefile,你会发现在这个命令下面,我添加了缩进,然后将shell命令放到那里让make为我运行。你可以在这里放尽可能多的命令,所以make是一个了不起的自动化工具。

警告 

如果你修改好ex1.c,让它里面包含了#include <stdio.h>这句,那么你的输出中应该不会有这个关于printf的警告(虽叫警告,其实相当于一个错误)。因为我的代码没有改对,所以就有这个错误。

注意,尽管我们没有在Makefile中提到ex1make依然知道怎样构建它,并且会使用我们的特殊设置。

前面的内容足够带你上道了,不过首先让我们用一种特别的方式来破坏Makefile,给你看看会发生什么事情。找到rm –f ex1这行,将缩进取消(将其移到最左边),然后看看会发生什么。重新运行make clean,你会看到类似下面这样的出错信息:

$ make clean
Makefile:4: *** missing separator.  Stop.

一定别忘记缩进,如果你看到这样的奇怪错误,那就重复检查一下,看你是不是一致地使用了制表符,因为有些版本的make程序非常挑剔。

[1]这里的警告是提醒`exl.c`中缺少`#include``…`一行,而警告行号为3,表明此处提到的`exl.c`并不是习题1中的`exl.c`。——译者注


留着前面的Makefile文件,它可以帮你找出错误,我们后面还会在其中加入内容,让它实现更多的自动化任务。

很多编程语言都使用C语言的方式来格式化输出,所以就让我们试一下。

ex3.c

 1    #include <stdio.h>
 2    
 3    int main()
 4    {
 5        int age = 10;
 6        int height = 72;
 7    
 8        printf("I am %d years old.\n", age);
 9        printf("I am %d inches tall.\n", height);
10    
11        return 0;
12    }

写好了代码,用老方法make ex3来构建程序,然后再运行一遍。确保你修正了所有警告。

这个习题代码不多,其中的门道却不少。我们来逐行看一下。

结果就是我们给了printf几个参数,然后它创建了一个新字符串,并将其打印到终端。

完整构建以后,你应该看到类似下面的内容。

习题3会话

$ make ex3
cc -Wall -g    ex3.c    -o ex3
$ ./ex3
I am 10 years old.
I am 72 inches tall.
$

很快我将不再告诉你去运行make,也不会再给你展示构建时的输出,所以要确保你弄对了这些内容,并且一切运行正常。

在每个习题的附加任务中,我可能都会让你自己搜索信息,弄懂一些东西。作为一个自力更生的程序员,这是一件很重要的事情。如果你总是在自己研究之前就跑去问别人,那么你就无法学会独立解决问题。你将时时刻刻都需要依赖别人,无法对自己的技能树立起信心。

改掉这个坏习惯的方法就是,强迫自己先回答自己的问题,然后再去确认自己的答案是正确的。你要试着把东西弄坏,对自己的答案进行实验,并且自己做研究。

对于这个习题,我要求你上网找出printf的所有转义字符和格式化序列。转义字符是\n\t之类的符号,它们分别让你打印出新行或者制表符。格式化序列是%s或者%d这样的符号,它们让你打印出字符串或者整数。把它们都找出来,学习如何修改它们,看看你能用它们做什么样的“精度”和宽度调整。

从现在起,这种作业会在附加任务中出现,你应当做一做。

试试用下面的这些方法破坏这段程序,在你的计算机上,程序可能会崩溃,也可能不会。

习题3错误会话

# edit ex3.c to break printf
$ make ex3
cc -Wall -g    ex3.c -o ex3
ex3.c: In function 'main':
ex3.c:8: warning: too few arguments for format
ex3.c:5: warning: unused variable 'age'
$ ./ex3
I am -919092456 years old.
I am 72 inches tall.
# edit ex3.c again to fix printf, but don't init age
$ make ex3
cc -Wall -g    ex3.c -o ex3
ex3.c: In function 'main':
ex3.c:8: warning: 'age' is used uninitialized in this function
$ ./ex3
I am 0 years old.
I am 72 inches tall.
$


这个习题以视频为中心,我将向你展示如何使用你计算机中的调试器来调试程序、发现错误,甚至调试当前正在运行的进程。请观看本书的配套视频,学习这一主题的更多内容。

下面列出了GNU调试器(GNU Debugger,GDB)的一些简单技巧。

视频对于学习使用调试器来说是不错,但你工作时还是需要命令行。下面是我在视频中用到的GDB命令的一个快速参考,供你日后查看。

在OS X中,GDB已经不复存在,你需要使用一个类似的叫LLDB调试器的程序。命令几乎都是一样的,下面是LLDB的快速参考。

你还可以上网搜索GDB和LLDB的快速参考卡片和教程。


学习第一门编程语言的时候,你很可能是读过一本书,输入了你不太懂的代码,然后试图弄懂它们的原理。我写的其他书大多是这个样子,这对初学者非常有效。初学的时候,对于有一些复杂的主题,你需要在弄懂它们之前先学会怎么用,因此这是一个简单的学习方式。

然而,一旦你已经学过了一门编程语言,这种慢速摸索语法的方法就不那么有效了。这样学习语言是可以的,但是有一种更快的方法让你学会编程技能,并且建立起使用的信心。这种学习编程的方法像是魔术,但你要相信我,它的效果出奇地好。

学习C语言的时候,我想要你首先记住所有的基本符号和语法,然后将它们用到一系列的习题中。这种方法和你学习人类语言的过程很相似:记忆单词和语法,然后将记住的东西用到对话中。只要一开始下功夫简单记住一些东西,你就有了足够的基础知识,以后读写C代码就更容易了。

警告

有的人极其反对背诵记忆。一般他们会说这会抹杀你的想象力,让你变成呆子。其实不会,我就是一个活的证据。我会画油画,会弹吉他,会制作吉他,会唱歌,会写代码,会写书,而且我背过很多东西。因此,这种说法不但毫无根据,而且会破坏学习效率。别把他们的话当回事儿。

最好的记忆方法过程其实很简单。

1.创建一系列的速记卡,将符号写在一面,将描述写在另一面。你还可以使用一个叫Anki的程序在计算机上完成这件事。我喜欢自己制作速记卡,因为制作的过程也有助于记忆。

2.将速记卡打乱,然后一张一张开始浏览,先只看其中的一面,努力想想另一面的内容,别着急看答案。

3.如果无法想起另一面的内容,那就看看答案,然后复述答案,再把卡片放到单独的一摞里边。

4.看完所有的卡片以后,你手头就有两摞卡片了:一摞是你能快速记起的,另一摞是你没有记住的。拿起没记住的那一摞,下功夫努力去记这些卡片。

5.一个阶段结束以后(通常是15~30分钟),你手头还是会有一摞没记住的卡片。将这些卡片随身携带,只要有空,就背一会儿里边的内容。

记忆的技巧有很多,不过我发现,这是让你能做到即时想起你需要能立即使用的东西的最好方法。C语言的符号、关键字、语法是你需要即时想起的东西,所以这个方法最适用。

另外还要记住,你需要做到卡片的双面记忆。你应该能做到通过描述知道对应的符号,也要能从符号知道它的描述。

最后,你不需要专门停下来去背这些运算符。最好的方法是将其和书中的习题结合起来,以便对记忆的内容进行应用。关于这一点参见下一个习题。

首先要列出的是算术运算符,与几乎每一种编程语言里的算术运算符都很像。写卡片的时候,描述中要写上它是算术运算符,并说明它的具体功能。

算术运算符

描述

+

-

*

/

%

取模

++

自增

--

自减

关系运算符用于测试等值性,它们在各种编程语言中也都很常见。

关系运算符

描述

==

等于

!=

不等于

>

大于

<

小于

>=

大于等于

<=

小于等于

逻辑运算符用于逻辑测试,它们的功能你应该已经知道了。唯一特殊的是逻辑三元运算符(logical ternary),你将会在本书的后面学到。

逻辑运算符

描述

&&

逻辑与

||

逻辑或

!

逻辑非

?:

条件运算符/逻辑三元运算符

按位运算符做的事在现代代码中不常见到。它们会用各种方式改变构成字节和其他数据结构的位。我不会在本书中讲这些,不过在一些特定类型底层系统中,它们用起来会非常顺手。

按位运算符

描述

&

按位与

|

按位或

^

按位异或

~

按位取反

<<

按位左移

>>

按位右移

赋值运算符的作用是将表达式赋给变量,不过C语言中很多运算符都可以和赋值合并使用。因而,当我说“与等”(and-equal),我说的是按位运算符,而不是逻辑运算符。

赋值运算符

描述

=

赋值(等)

+=

加后赋值(加等)

- =

减后赋值(减等)

*=

乘后赋值(乘等)

/=

除后赋值(除等)

%=

取模后赋值(取模等)

<<=

按位左移后赋值(左移等)

>>=

按位右移后赋值(右移等)

&=

按位与后赋值(与等)

^ =

按位异或后赋值(异或等)

|=

按位或后赋值(或等)

我把下面的操作叫数据运算符,不过它们其实处理的是指针、成员访问,以及C语言的各种数据结构的元素。

数据运算符

描述

sizeof()

获取……的大小

[]

数组下标

&

……的地址

*

……的值

->

结构体解引用

.

结构体引用

最后还有一些杂项符号,它们要么用途多变(如,),要么由于各种原因没法归类,所以一并列在下面。

杂项运算符

描述

,

逗号

( )

圆括号

{ }

花括号

:

冒号

//

单行注释开始

/*

多行注释开始

*/

多行注释结束

一边学习速记卡,一边继续阅读本书。如果你每次学习之前花15~30分钟攻读速记卡,每天睡前也花15~30分钟,那么应该用不了几个星期你就能都记住了。


相关图书

代码审计——C/C++实践
代码审计——C/C++实践
大规模C++软件开发 卷1:过程与架构
大规模C++软件开发 卷1:过程与架构
C/C++代码调试的艺术(第2版)
C/C++代码调试的艺术(第2版)
C/C++程序设计竞赛真题实战特训教程(图解版)
C/C++程序设计竞赛真题实战特训教程(图解版)
C程序设计教程(第9版)
C程序设计教程(第9版)
C/C++函数与算法速查宝典
C/C++函数与算法速查宝典

相关文章

相关课程