为什么要学习C 语言?这个语言未来的前途如何?在IT 技术飞速发展的今天,这门语言会不会很快过时?在开始学习C 语言之前,我相信大家一定对这些问题比较关心。这些问题还不能发到论坛上去问。因为只要涉及编程语言优劣的讨论,马上就会引起口水大战,如蛤蟆吵坑一般。
对语言优劣的讨论,一般都是一些新手或菜鸟愿意参与,因为他们刚刚接触到一些有趣的特性,如锦衣夜行,憋屈得厉害。一旦发现有个机会,马上把自己刚发现的一点东西拿出来晒一晒,同时对自己不用的语言再踩上几脚。高手一般都是冷眼旁观,不会去参与这种无聊的论战。而真正的大师,如C 语言之父Ritchie,对C 语言的评价却是“quirky, flawed”。所以你看,你的水平已经被你的言行暴露出来。任何一个论坛,你都会发现众多的对C 语言的批评之声,如数组越界、指针误用等。正所谓“好事不出门,坏事传千里”。事实上,这个世界上只有两种产品,一种是没人用的,另外一种是被人骂的,从这个意义上来说,C 语言是成功的。
从哲学上来说,任何事物都有自己的特性,都有优缺点。正所谓针无双面利,语言也如此。每个语言都有其优缺点和适用的范围,所以没有好和不好的区别。你要掌握每种语言的特点,然后针对特定的问题,选择最合适的语言,也就是说,不能脱离具体的任务而孤立地评判语言。
你猜对了,C 语言的前身确实是B 语言。在20 世纪70 年代,C 语言还不叫做C 语言,而是叫做New B 语言。中国人一般比较讲究起名,认为名字起好了,人的命运就会跟着变好。其实老外也有这个癖好,老外有一多半的名字都来源于圣经,都恨不得和上帝多少沾点关系。我们从New B 的中文谐音可以看出,这门刚刚诞生的语言必将纵横江湖,自成一派。
C 语言和UNIX 来源于一个失败的操作系统项目,这个失败的操作系统项目为C 语言和UNIX 提供了很多反面的教训,从而为C 语言和UNIX 的成功打下了坚实的基础。伴随着C 语言的普及,ANSI 最终接管了C 语言的标准制定和维护工作,所以现在我们使用的是ANSI C 语言。
由于一开始脱胎于UNIX 操作系统,C 语言中的很多特性更倾向于硬件。例如,C 语言中有现在我们几乎不会用到的register 关键字,用来描述CPU 的寄存器。C 语言中的数组从0 开始计数,而不是1。这让很多新手非常迷惑,你通过int a[100]定义了一个长度为100 的数组,但是却不能访问a[100]这个元素。但是数组从0 开始却可以让编译器更喜欢。虽然很多人建议ANSI 让数组从1 开始,但是标准就是标准,就算是错了,也不能轻易改动,因为毕竟不好解决与旧代码的兼容问题。数组从1 开始这个建议经过多次否决后,改革派依然不死心,最后提出了数组从0.5 开始这个折中的提议,ANSI 经过认真地思考,最终还是拒绝了。
说了这么多,其实就是想表达一个意思,C 语言一定不是完美的,一定有令人不满意的地方,但是也没办法。很多东西在历史中形成,你必须尊重并接纳它的历史。借用电影《肖申克的救赎》中的一句经典台词“你恨它,然后你适应它,最后你离不开它。”
历史就说这么多,毕竟在20 世纪70 年代的时候我还小,最喜欢的东西不是计算机,而是滚铁环。如果想了解C 语言更详细的历史,可以去问问周围的老人,或者上网查一查相关的C 语言的历史。
下面我们来客观地看看各种语言的真实表现。图2-1 和图2-2 来源于http://www.tiobe.com,其中的统计数据基于全球范围内众多的有经验的工程师、课程、工作机会及搜索引擎等,所以比较客观。图2-1 显示的是2011 年10 月份时的统计结论。可以看出,目前,C 语言与Java 语言基本持平,两者相加已经占据了大约40%的份额,如果再加上C 语言的后继语言C++的话,份额已经占据了将近半壁江山。
图2-1 语言流行度数据
图2-2中最上面的折线代表Java,排在第二位的就是C 语言。从图中我们可以看出,就算是Java 语言呈下降趋势,C 语言依然保持相对稳定。这是因为目前有大量的C 语言源代码,同时UNIX 依然是最成功、最稳定、应用最广泛的商用服务器操作系统,请注意,这里我没有用“之一”这个词。有了这些宝贵的资源和UNIX 的市场份额,C 语言的生命力会非常长,在我们的有生之年,C 语言应该不会过时。
图2-2 语言流行度历史数据
兼听则明,偏听则暗。下面从另外一个侧面看看各种语言的表现,那就是看从GitHub 上统计的哪种编程语言挨骂最多。关于GitHub 是什么,我在本章的2.4.3 节会有介绍。目前你只需要知道这个网站上有非常、非常、非常多的软件项目就可以了。
程序员一般都是性情中人,当用某种语言编程的时候,如果感到不爽,就会在提交程序的时候忍不住骂上两句。一个非常有闲心的开发人员在GitHub上抓取100 万条提交信息(commit),并扫描其中的脏话。他把脏话限于乔治·卡林的七大脏词①,而后根据编程语言种类统计结果。为确保编程语言的流行程度不影响统计结果,对每种编程语言都抓取了等量提交信息,结果如图2-3 所示。
注释:① 由于内容过于火爆,这里不转述了,自己用狗哥查去!
图2-3 哪种编程语言最招人骂
在图2-3 中,C++折桂,但只是领先一点点,Ruby 和JavaScript 紧随其后,此为第一军团。
紧随其后的是 C 语言、Java 和C#组成的第二军团,所遭受的骂声比例下降了很多。
第三军团为Python 和PHP,使用这两种语言的程序员,要么非常高兴使用自己所选择的语言,要么非常和善(我猜也可能是使用这两种语言的女程序员居多的缘故)。
当然了,他们没有在提交时开骂,并不等于他们不会开骂。正如一位评论者在看到结果后评论到:“虽然我用Python 编程,但我都是在痛骂IE 的。”其实Ruby 和JavaScript 被骂的很多原因我估计也和IE 有关。至于哪句骂人的话折桂,经过大家讨论,目前大家最喜欢的一句是:“去TMD,咱们就这样发布。”不得不佩服,霸气外漏!
正反面的信息都摆在那里了,从各方面看,C 语言表现得都不错。该说的我都说了,剩下的就是你们自己做出选择。最后送给大家一句话,“No reason is needed to love 第2章编程基础知识C. Anything can be a reason not to love C.”翻译过来就是:“如果你爱C,不需要理由;如果你不爱C,任何事都是理由。”
伴随着物联网的兴起,嵌入式设备大行其道,对C 语言程序员的需求也越来越多。与此伴随的是,C 语言程序员的薪水也水涨船高。在我C 语言课程的第一堂课上,我告诉我的学生,一个优秀的C 程序员,年薪大概会是30 万元。课后很多同学围拢了过来,主要的问题集中在“我怎样才能学好C 语言?”。其实,我知道他们大部分都不是冲着C 语言来的。
一个同学问我,“我什么也不会,怎么编程序啊?”其实这个问题我可以简单换一下语序回答,那就是“你什么也不编,怎么能会啊!”。我们每个人都是从一点儿不会开始的,一点儿不会一点儿都不可怕,可怕的就是从不动手,或者认为不会所以没法动手,那样就一辈子停留在不会这个阶段了。不管你会不会,马上动手做,然后产生问题,解决问题,进步,再产生问题,再解决问题,再进步……恭喜你,你离30 万越来越近了。还不开窍吗?让我们做个实验,你会说,我想编,但是用什么编呢?好了,恭喜你,你已经有了宝贵的第一个问题:“用什么编”,上网查查,问问同学,如果答案都不满意,给老师写邮件。一旦解决了,你就进步了。
另外一个同学问我:“哪里有简单的、好的资料资源?这样我就可以快速入门了。”这个答案也很简单,在网上!任何资料,如果你看得懂,同时对你有帮助,那就是好的资料。但是注意,一定要带着问题去查,关于如何产生问题,参考上一段。
有的同学问,我学了很久C 语言,但是为什么我的水平还是不高呢?我记得有一个故事,小狗问妈妈,幸福在哪里,妈妈说:“幸福就在你尾巴上。”小狗不解,妈妈说:“如果你一直追幸福,就只会在原地打转;如果你一直往前走,幸福就会跟着你了。”幸福如此,钱也这样。盖茨和乔布斯都不是奔钱去的,但是结果你也看到了。所以我猜,C 语言的能力,应该也遵循这个规律,你唯一需要做的就是一直走下去。
学好 C 语言,无论是老师,还是书籍,他们的作用都是有限的。老师讲多少遍,知识还是老师的,不是你的。你只有自己实践一遍,有了感性认识,知识才是你的。通过读书来学习C 语言,就像你通过枕着一本书睡觉而获取知识一样可笑。
任何语言的学习,提高,乃至成为“绝顶”①高手,都是通过一行、一行又一行的代码敲击而成,都是通过不断地错误、不断地修改、不断地思考而完成的。只有这样,才能达到程序猿的最高状态:“静若瘫痪,动若癫痫”。
注释:① 程序员是一个比较辛苦的工作,会经常熬夜,这里的“绝顶”不解释了,你懂得!
一本好书可以让你少走弯路,但是绝对不能把你送到顶点。有的国家,导师被翻译成Inspirer 而不是Supervisor,比起后者,我更喜欢前者的翻译。记住,学习的主角是你,不是老师。老师只是一个点着的火柴,如果你是汽油,老师会点燃你;如果你是水,你会浇灭老师。
学习 C 语言的时候,不要只是学习语法,而是要借助这门语言,学习到语言背后的内涵,这样才是正确的学习之道。例如,任何语言都需要变量、函数,任何语言都需要解决命名冲突、访问控制等问题。所以,C 语言背后的计算机思维和理念才是你真正应该关注的。如果学好了C 语言,了解其神韵,你再学习其它的语言会很快。
不知不觉中,废话已经说了这么多。如果必须用一句话来回答如何学习C 语言这一问题,我选择这句话,那就是“Dirty Hands!”
当我们很小的时候,我们倾向于使用图形界面,但是当我们长大以后,却逐步开始使用更具效率的文字界面。作为一个程序开发人员,也应该遵循这个成长的轨迹。文字界面简洁、高效。例如,如果表示1 万个苹果,图形界面就力不从心了,但是文字界面只需要几个文字而已。
在计算机学科,个人电脑上用得最多的基于文字界面的操作系统就是Linux 了。对一个程序开发人员,Linux 具有三个优点。
效率
Linux 支持shell 脚本编程,同时还有3000 多个命令,其中的某些命令,例如awk 和sed,本身就可以写一本书,而且书的厚度完全可以挡住一把AK47 步枪射出的子弹。如此丰富和强大的命令,再配合管道和重定向技术,使得Linux 可以快速地完成很多复杂的工作。例如,给定下面一段英文的文本。
If you tallied up the strengths and weaknesses of Linux and Windows, which OS would come out ahead? According to Jack Wallen, superiority in security, flexibility, interoperability, community, and command-line power (among other things) put Linux well ahead. See if you agree with his assessment
我们想统计出每一个英文单词出现的频度,不包含英文的标点符号。这个过程在自然语言处理(Nature Language Processing)领域有一个专业的术语,叫Unigram 分析。我们可以通过下面的Linux 命令快速地完成这个任务。
tr A-z a-z <a.txt | tr -d ".,?"|tr -s '.' '\012'|sort|uniq -c
这里对命令的内容不做解释。一句话,如果能熟练使用Linux,Windows 下别人也许还没有打开窗口,你这里就已经完成任务了。Linux 可以提供巨大的空间供你发挥,用句官场上的话来说就是“这里的水,深着呢!”
国际交流
国外计算机学科大部分用Linux,Windows 在中国相当地普及,原因你懂得。当你试图和国外的程序员交流的时候,或者下载外国工程师的项目的时候,你会发现他们都是在Linux 下开发的。当我读研究生的时候,我就经常下载外国人的源代码进行学习,经常会发现tar.gz 这种后缀名的文件,搞得我很是困惑,这个到底是什么东东?直到我出国后才发现,外国的大学计算机学科里面基本都用Linux 系统。并不是tar.gz 文件奇怪,只是自己孤陋寡闻,用句时髦的英文叫OUT 了。如果从一开始就在Linux 下开发,就不会有我这种困惑,与外国人交流起来也会比较流畅。
程序员理念
所有的程序员都应该追求一个最基本的理念,那就是让机器适应人,而不是人去适应机器。Windows 隐藏了太多的细节,你没有办法去改变它,所以你必须去适应它。有的人说,Windows 是一个金碧辉煌的大坑,一旦掉进去,就很难再爬上来。Linux 不同,它的所有源代码都是公开的,可以按照你的意愿去修改它,让它来适应你。当然,这个过程绝不是一帆风顺的,但是至少我能保证一点,在这个过程中,你会学习到很多、很多的知识。与Windows 相比,Linux 是一段崎岖的山路,如果一直走下去,会看到更美的风景,最后一览众山小。
Linux 还有很多其它的好处,比如说,不用担心病毒。据说全世界针对Linux 的病毒,用一双手就能数得过来,而且这些病毒还都是针对服务器的。Linux 另外一个比较有诱惑力的好处体现在职场上。能在Linux 上熟练编程的程序员薪水较高,能编写Linux 内核以及驱动程序的程序员更是凤毛麟角,炙手可热。比起那些站在大街上举着“油工,木工,Java 编程”牌子的IT 民工,不知要强多少倍。具体其他的好处,大家可以参考本书网站上“扩展内容”网页中的“why linux is better”链接。
这个世界上没有完美的语言,也没有完美的操作系统。Windows 属于用起来很容易但是开发起来很难的系统,跟Linux 正相反。Linux 最让人恐惧的就是它比较陡的学习曲线,尤其是对新手来说,看你能不能咬牙hold 住。坚持使用Linux 半年,你就会明显感到Linux 在开发工作上和Windows 的差距了。
为了降低大家学习Linux 的难度,我推荐一本书,那就是《鸟哥的Linux 私房菜》[11],网上有对应的电子版,不过我还是推荐你买一本,这样既保护了自己的视力,又能对作者的劳动给予一定的回报,鼓励他出更好的书。几十块钱可能就是你一次打车的钱,如果用来买好书,我保证你会走得更远。
另外,实话实说,Linux 并不适用于办公室文秘工作。如果你每天的大部分时间都花在农牧业上,例如,种菜和偷菜、切割水果或者是用小鸟去砸猪,Linux 也不适合你。
工欲善其事,必先利其器。为了更加方便地进行C 语言的开发,接下来我介绍一些编写程序时常用的开发工具。
Windows 下的Visual Studio 之一枝独秀
在 Windows 下利用C 语言做开发,基本上没有别的选择,首选的就是微软公司的Visual Studio,通常简称为VS,翻译成中文就是“为啥”。它是一个集成开发环境(IDE),目前版本是2010。我最喜欢用的两个特性就是代码提示和代码卷起。
具体的内容大家可以参考本书网站上“扩展内容”网页中的“Visual Studio IDE简介(C++)”链接。
如果电脑比较旧,推荐安装VC6.0。这是Visual Studio 一个较旧、但是非常经典的版本,也是我的初恋。男人都有初恋情结,所以我一直忘不了她,但是当我用过其它VS 的不同版本后,发现她们的用法其实大同小异。
请注意,本书后面的所有程序,都是利用VS2010 版本编译和运行的。如果你在自己的平台上运行书中的源代码与书中的结果不一致,请来信告诉我,我会非常感谢你!
Code Blocks 是一款开源免费的C/C++集成开发环境。比起商用的VS2010,功能和用户体验上都要逊色很多。有一点需要注意,Code Blocks 中的项目一定要保存在英文路径名中,否则程序不支持调试。我强烈呼吁微软把开发工具VS 免费,因为无论是程序猿,还是程序媛,都不是太富裕。
Linux 下开发工具之百花齐放
Linux 下,比较主流的IDE 就应该算是Kdevelop,不过比起VS,易用性还是相对差一些。除IDE 以外,Linux 下很多老手都使用“编辑器+make+GCC+GDB”这样的搭配。Linux 下的编辑器主要分为两大类,有模式编辑器和无模式编辑器。
有模式编辑器的特点是分为各种运行模式,最知名的有模式编辑器莫过于 Vi、Vim。例如Vim 编辑器具有普通模式、可视模式、插入模式。不能在可视模式中输入字符,同样也不能在插入模式中复制粘贴。有模式的好处是,同样的按键在不同模式中具有不同的功能,从而避免了复杂的快捷键组合;缺点是需要经常来回切换模式,新手很容易被搞晕。所以坊间流传这样一个段子,如何生成一个随机的字符串?那就是让一个新手使用Vim 编辑器!
无模式编辑器的特点是随时都可以编辑文字(像是Windows 中的记事本程序)。典型的无模式编辑器有nano、Emacs。无模式编辑器为了完成对应的功能,需要众多的快捷键组合。
Vim 和Emacs 之间的论战源远流长。不过在我眼中,这两大编辑器都是神器。如果你非常习惯图形界面的操作,刚接触这种基于命令行的编辑器多少会有点不适应。不过没关系,任何东西,如果用熟了,都很方便。而且上面我已经介绍过,基于命令行的操作界面,在效率上确实要比图形化的界面高,至少可以手不离键盘,也避免了患上“鼠标手”。关于Vim 的使用,《鸟哥的私房菜》中有对应的介绍。
Mac下Xcode之梅花暗香
前面我提到过,本书一共有两处来源于乐学网我课程上的论坛,接下来的引用内容就是第二个。原帖很长,这里我只简单地截取一小部分。
Mac 系统在中国总的来说没有Windows 那么普及。不知我们强大的软件学院有没有同学像前几天的我一样弄了台Mac 而为配不上C 语言编译软件而发愁。这种问题其实完全不必担心。Mac 自然有属于它的强大的编程软件,本着“Mac 在手,天下任我走”的执着信念,Xcode 终于被我挖了出来
Xcode 是苹果公司的官方编程软件,强大强大很强大,支持C、C++、Core Data、Core Foundation、Core service、Foundation 等多种语言(其实我也不知道后面究竟属不属于语言反正是照下拉菜单抄的,吼吼)。可为苹果机及iPhone、iPad、iTouch、iPod 等编译软件,其附属程序还可设计图形界面(貌似是这样)。
首先打开Xcode 界面(一般Xcode 都是购机预装的,找不到的话去磁盘里搜索下“Xcode”或是在赠送的碟片中。实在没有就去App store 下载,正版免费,不过比较大)
然后……
由于后面的图片太多,我就不全放到本书中了。全部的内容大家可以参考本书网站上“扩展内容”网页中的“Mac 在手,天下任我走”链接。
程序员比较讨厌干什么呢?大部分程序员都会说他们讨厌给自己的代码写文档。这倒不是因为程序员懒惰,而是因为程序员一般都喜欢干“编程序”的工作,而不太喜欢干非“编程序”的工作。面对问题,思考解决方法是最具有挑战的部分,实现这些方法的时候,也需要克服一些工程的障碍。等完成了编码工作后,需要把整个过程用文档阐述一遍的时候,大部分程序员就提不起什么兴趣了。我想大家都是有这个体会的。我们小的时候,学校组织上动物园去玩总让人很兴奋,但是回来以后还要求再写一篇日记,这个就有点让人倒胃口了。同时程序员都比较忙,一般都会想,“等我完成这段功能,我就写文档”。根据我多年的经验,这种“等我……我就……”的情况,一般“我就……”的部分最后都会被忽略。文档的另外一个潜在的问题就是与代码的同步问题。如果修改了代码,但是并没有及时地更新文档,那么你就给用户留下了一份错误的文档,这个时候,还不如不留任何文档。既然如此,何必自找麻烦呢?
如果程序员比较讨厌写文档,那么程序员最讨厌什么呢?程序员最讨厌的是,别人的代码中没有文档。这多少有点自相矛盾的说法至少说明了文档还是很有用的,尤其是在团队开发的环境中。那么有没有一些自动化的工具可以帮助程序员来自动、同步地生成一些文档呢?这样就可以不占用程序员们宝贵的时间了。
当然有了,有需求就有市场。文档的相关工具也有很多,有比较专业的商用工具,例如Atomineer Pro Documentation。同时也有一些免费的开源软件。我个人比较常用Visual Studio 集成开发工具,所以我一般采用的是GhostDoc+Doxygen 的组合。这两个工具中,前者用于直接从代码生成XML 格式的注释,后者把XML 格式的注释生成样式更加美观的HTML、CHM 或者其他常见格式的文档。当然自动生成的注释只是根据代码的命名来“猜”出具体的注释内容,有的时候质量并不令人满意,但是至少你有了一个可供修改的模板。具体的效果可以参考图2-4。只需要选中“Add”这个函数名,然后点击右键,在上下文菜单中单击“Document This”,Add 函数上面就自动生成了XML 格式的注释。可以看出,自动生成的内容确实不是十分准确。不过至少提供了一个基础的模板,你可以根据内容再进一步修改。如果你能遵守一些基本的命名规范,那么GhostDoc“猜”出来的的内容还是比较准确的。例如本例中,如果你能把Add 函数命名成一个动词加上一个名词的形式,例如GetSum,那么GhostDoc 自动生成的注释的内容就会好很多了。
GohostDoc 的Pro 版本需要付费,而GohostDoc 的基本版是免费的。个人感觉对于一般的文档工作,GohostDoc 的基本版就可以应付了。毕竟程序员的主要工作还是编程序,而不是写文档。
图2-4 利用GhostDoc 自动生成XML 格式注释
虽然生成了XML 注释,但是看起来并不是十分美观和方便。另外对于其他用户来说,如果不能把源代码给他看,那么他们也看不到这段XML 格式的注释。这个时候,我们可以使用Doxygen 工具来把XML 格式的注释自动抽取出来,生成HTML 或者CHM 格式的文件。例如,上面生成的注释,利用Doxygen 工具生成的CHM 文件如图2-5 所示。
Doxygen 的具体使用可以参考Google,这里我不再详细描述了。我想提醒大家的是,整个文档生成过程中,我甚至没有敲击过一下键盘,如此美观、规范的文档已经被自动地生成了出来。
图2-5 由XML 格式注释自动生成CHM 文件
目前,拥有两台电脑是一件非常平常的事情,例如,家里有一台,办公室有一台。两台电脑的工作环境下,我保证你一定发生过悲惨的事,那就是用电脑A 的旧文件去覆盖电脑B 的新文件,以致于你的工作成果丢失。或者是电脑A 和电脑B 上都是新文件,但是你不知道哪个才是你想要的。不瞒你说,我就经常被这件事搞得焦头烂额。直到我发现了Total Commander 软件,其中有一个同步文件夹功能,才算是初步搞定了这个问题。
现在想象一下,一个人,两台电脑就会带来同步的问题,如果10 个人,20 台电脑呢?如果这些人同时工作在一个项目中,结果是可想而知的。现在的开源软件开发者遍布全球各地,该如何进行管理呢?解决这些问题的关键就是版本控制软件。它能保证所有的开发人员协同开发,当出现冲突的时候,版本控制工具会阻止互相覆盖,直到开发人员正确地处理了所有的冲突。
有的时候,我们突然会有某种冲动,给自己的项目增加一个功能或者修改一部分代码,然后你就开始工作。三天以后,你突然发现你想增加的功能非常不切实际,或者是修改后的代码还没有原来的代码好。这个时候你唯一的想法就是如何能恢复到三天前的状态。但是这个时候,如果没有备份原来的文件,修改回原来的样子并不容易。有了版本控制软件,你就拥有了一部时间机器。你可以随时恢复到任何你以前提交过的状态,把任何尝试带来的破坏降到最低。
如果评选对程序员最有用的工具,我一定把票投给版本控制软件,它像一个好管家、好助手、好秘书。版本控制软件无论是对个人开发,还是团队开发,都非常重要。在短短的十几年,它已经快速地发展了三代,从最开始的CVS,到改良的Subversion,直到目前最流行的版本控制软件Git。
大名鼎鼎的Git 是由Linux 之父开发的,其主要的特点就是支持分布开发和分支开发。《Git 权威指南》[11]是一本非常好的书籍。能叫并且敢叫“权威”两字,也并非浪得虚名。我以前一直对中文书抱有成见,所以我轻易不敢自己动笔,恐怕写了也没人看。直到我最近看到了几本比较好的中文计算机书籍,才改变了我的看法,《Git 权威指南》就是几本比较好的中文计算机书籍之一。既然他们能写好,并且获得了成功,那么我也能。毛主席曾经教育我们说:“凡事就怕认真二字。”而现在物是人非,人的理念改成了“认真你就输了”。其实,可以把这两条综合起来,那样你就会有一个正确的态度,那就是“编程序中不认真你就输了,生活中认真你就输了!”
对学生来说,学习编程最好的方法之一就是阅读源代码。源代码是程序员之间交流的最好语言。上哪里去找又多又好的源代码呢?www.github.com 无疑是目前最流行、软件最丰富的网站。它是一个基于Git 版本控制软件、利用互联网对项目进行版本控制和管理的网站。目前它有大约一百万个项目。你可以在上面建立自己的项目,同时可以随时fork 别人的项目,从而以一个开发者的身份,参与到别人的项目中去。
下面我要把前面介绍的各种工具综合运用起来,介绍一种我目前遵循的开发C 语言的基本框架和流程。为了把这种流程说清楚,我首先给出整体图,如图2-6 所示。然后再仔细解释图中的各个部分的功能。
图2-6 开发流程图示
C 语言一般都用来完成程序核心的功能,一般我们不用C 语言编写界面或者编写一个完整的面向最终用户的应用。所以一般最后生成的产品主要是一个动态链接库或者是一个COM 组件,图2-6 就是基于这种情况。图中的数字代表的是开发流程的操作顺序,详细的描述如下。
1)将功能加到对应的.c、.h 或者.cpp 源文件中。把源文件放到一个统一的目录中,如 source 目录。如果在Windows 下,你要利用Visual Studio 建立不同的项目,然后把source 中的文件加到不同的项目中。如果在Linux 下,你可以利用makefile 来描述不同的项目。这样做的好处在于,在开发的过程中,保证对应的源文件只保留一份。如果有两份,非常容易导致两份文件的不同步。这种不同步带来的潜在的bug,有的时候会给你带来非常痛苦的回忆。这一点,我有深刻的体会。
2)利用Visual Studio 或者makefile 建立一个测试项目,测试源文件中各项功能的正确性。
3)测试正确后,编译生成动态链接库.DLL 或者.so。
4)建立内部测试项目,测试生成的.DLL 或者.so 是否正确。这一部分主要测试参数传递部分,以及动态链接库的动态装载或静态编译部分是否正确。
5)测试正确后,将源代码放到Git 版本控制下,生成新的提交,同时利用DoxyGen 生成与之对应的、同步的文档。
6)利用微软的ATL 生成COM 组件。
7)将生成的DLL 和COM 组件给其他公司的软件客户使用。
请注意,这种布局和流程只是我个人使用的一种规范,并不是什么学术界或者工业界的标准,所以你只需要把它当成一个参考。你要根据实际开发的项目和应用场景,来制定出符合自己的、效率最高、最能有效避免bug 的一种开发布局和流程。
风格涉及到很多东西,如果把范围定义得宽泛点,一切不涉及到对错的,都可以归入风格问题。例如,变量和函数等标识符的起名、函数的长度、缩进和大括号的位置、注释的数量和格式等,下面分别进行介绍。对于编程风格这个东西,就像做人的风格一样,忌讳两种情况,一种是没有风格,另外一种是总换风格。你要保持住相对正确的编程风格,套用一句时髦点的话,就叫“时尚在变,但风格永存”。
给变量起名和给自己的孩子起名一样,都想最好地表达出变量的特点,同时又不和别的名字重名。C 语言中,只能用数字、字母和下划线命名一个变量,而且不能以数字开头,所以起名的候选空间并不大。为了能描述变量的用处,你一定避免不了长变量名,例如speedoftraintoshanghai。阅读这种变量名的时候,你一定抑制不住想骂街的冲动。就算这个变量是你自己起的,一周以后,当你再次阅读,也会抱怨自己,程序员何苦为难程序员!为了避免长变量名阅读困难的问题,业界发明了两种常用的命名方法,一种叫做驼峰方法,就是每个单词的首字母大写,如SpeedOfTrainToShanghai。另外一种就是利用下划线,如speed_ train_shanghai。我个人比较倾向使用下划线。这里需要注意一下,你不能写成speed of train to shanghai,如果这样写,编译器会大声地向你咆哮(error)。
同样,一般临时的循环变量都采用单字母,如 i、j、k 等。如果编写一个超过30 行的程序,程序中一定有变量i,我猜。
大写字母在英文中一般是为了突出,它本意是大喊大叫的意思。在C 语言中,大写字母的变量通常代表一个常量,或者是利用typedef 重新定义的类型,如typedef int WORD;,这样就可以用WORD 来定义一个变量了,如WORD i;,他与int i;等价。一般情况下,typedef 多用于定义结构体类型。
原则上,变量起名应该用英文,但是也应该灵活掌握。以前我参与了一个项目,铁路上的“三阀一杆试验台”,有一个变量叫做“空重车阀压力”。当确定不会有外国友人一起开发这个软件以后,完全可以用变量名kzcfyl 来表述它。这个时候,如果偏要用英文,就有点找别扭了。
给变量起名的时候也有一些回避的原则,例如系统中常用的函数名就应该选择避开。想象一下,如果编写的一个函数取名叫做printf,当调用printf 的时候,会发生什么?同时,一般系统中的变量名都是以下划线开始的,所以在给变量或者函数起名的时候,应该避免以下划线开头。
缩进是为了清楚地定义一个语句块的开始和结束。为了能够说明缩进带来的好处,我需要给出一段不使用缩进的程序对比一下,如程序2-1 第1~2 行所示。面对没有缩进的这一大坨程序,很多人会脱口而出“shit!”的。
程序2-1 有无缩进的区别
1 for(i=0;i<100;i++){ /没有缩进/
2 if(i%2==0){sum+=i;}}
3 for(i=0;i<100;i++) /好的缩进风格/
4 {
5 if(i%2==0){
6 sum+=i;
7 }
8 }
对比第3~8 行有缩进的程序,我们可以看出缩进对于帮助阅读和理解的效果还是非常明显的,所以缩进对于每个程序员来说,都是一种应该秉承的风格。
关于缩进的距离,就不那么严格了,默认的Tab 为8 个字符,有的程序员认为这样缩进的距离有点大,如果语句块嵌套带来了超过3 个缩进的话,代码会离右面比较近。
我建议缩进的距离为4~8 个字符,这里没有统一的值,只要能比较明显地区分出语句块的层次就行。太大的缩进距离虽然明显,但要求你的屏幕够大,同时你的脖子够灵活。
语句块通常都包含在一对大括号中,我们在后面会对语句块进一步介绍。关于大括号的风格有三种,分别是与上一个语句同一行、独自成一行和与下一个语句同一行,如程序2-2 所示。
程序2-2 大括号的三种风格
1 if(…){ /与上一个语句同一行 /
2 …
3 }
4 if(…) /独自成一行 /
5 {
6 …
7 }
8 if(…) /与下一个语句同一行/
9 {…
10 }
这三种风格都各有优缺点,也是“风格战争”的焦点战场。我一般选择“独自成一行”这种风格,如果使用小屏幕的笔记本来编程序的话,也会选用“与上一个语句同一行”这种风格。第三种风格我从来不用。风格这东西就是这样,你形成一个自己的风格,并且一直坚持住就可以了。
一个函数到底应该多长呢?1 行的函数可以吗?1 万行的函数可以吗?对编译器来说,都是可以的。这里所说的风格,其实都是针对程序员来说。
程序员是人,没有错,程序员确实也是人!所以程序员也有人的一些共有的限制,如人的大脑不能同时处理两件事,不能记住7 件以上待做的事情,不能合理处理超过150 个好友的关系等。一旦超过了这个数量,结果将是一团糟,如果你认为自己足够聪明,已经突破了人类的期限,你可以试试,结果就是你将花费100 倍的时间去修补混乱带来的错误。
正是有了这样的限制,所以我们不应该写一个很长的函数,这样不仅写起来困难,而且会带来很多隐含的冲突和问题。
1986 年,IBM 在OS/360 的研究结果中指出,大多数有错误的函数都大于500 行。长函数不仅容易出现问题,而且问题的查找和调试都非常困难。把所有的功能都塞到一个函数里面也不符合模块化编程的理念。模块化编程的一个基本思想就是分解,把不同的功能分解到不同的模块中,模块可以是不同的.c 文件,也可以是不同的库。模块中不同的功能再继续分解到不同的函数中。一般情况下,哪怕是一个中小规模的任务,构建多个.c 文件,并把不同的功能分配到对应的.c 文件中不同的函数里也是正常并且正确的。
最后的一个建议就是一个函数应该只完成一个功能,并且长度最好局限在一个屏幕内,一个函数内部的变量最好不要超过7 个,这样编写和查看都非常方便。相关功能的函数放到一个.c 文件,以便日后管理和复用。如果你不想把源码给别人复用的话,可以把对应的.c 文件编译成一个库文件。多文件项目也有一些需要注意的技术问题,具体的细节在第7章再给大家介绍。
编程序时,我们应该使用等宽字体,这是因为在编程序的时候,缩进和对齐是非常重要的,而等宽字体比较容易对齐。另外,程序语言中括号“ (”,花括号“{”,方括号“ [”等用得相当频繁,使用等宽字体,他们不仅清晰,而且和字符的宽度相等,看起来更明显和舒服。同时,等宽字体中,字母‘l’,‘i’和数字‘1’也能比较清晰地区分出来。
最古老的等宽字体为Courier 或Courier New。基本上所有的系统都自带了这种字体,很多终端和编辑器都默认使用这种字体。很多人不太喜欢这种字体,对它有一句评论,“久不见牡丹会以仙人掌为美。”
图2-7 给出了两种字体的对比图,左面的字体为Lucida Sans Typewriter,右面的字体为Courier New。我自己对审美一窍不通,所以我自己从来不买衣服。如果让我在牡丹和仙人掌之间作选择,我也会选仙人掌,因为它比较好养活。现在一些学校推行通识教育,我更是举双手赞成,应该给我们这些普通青年好好扫扫盲,争取早日完成普通青年向文艺青年的转变。
图2-7 两种字体的效果比较
这里我把常用的等宽字体的名称给大家列出来,你们可以根据自己的喜好自由选择,毕竟各花入各眼,不要人云亦云,要找到自己的那杯茶。
• Monaco
• Lucida Console
• Lucida Sans Typewriter
• Consolas
• Courier New
风格不是规则,风格不会带来错误问题,而规则会。针对风格人们有两点认识的误区,一是认为既然不会引起错误,那么可以完全不遵守。事实上,遵循好的风格可以有效地避免犯错误,并会使无论是编写代码还是阅读别人的代码,都非常有效率。例如,代码的缩进,函数的长度,标识符的起名等,还是应该遵循本书介绍的风格的。关于风格另外一个误区就是严格遵循各种风格,事无巨细。风格说应该给函数加注释,那么我们就给函数Sum 加上注释“求和”。我的神啊!这不是池塘冒泡—多鱼(余)吗?
我的结论是这样,有些重要的风格,如缩进、变量起名等是必须应该遵循的,其他的小的风格,可以通过一种叫做“重构”的技术,来动态地执行。例如,我们都知道程序中突然出现一个数字常量(一般我们把它称为“幻数”)是一种不好的风格,如程序2-3 片段1 中的10 这个数,如果在片段1 中,10 只出现了一次,我看没必要再定义一个常量来代替它。
但是在程序2-3 的片段2 中,用10 定义了一个数组,没关系,你可以继续写,当你第二次用到10 这个数的时候,你就应该有点不舒服了。当你第三次用到10这个数字的时候,你就应该下决心,定义一个宏define N 10,然后把10 全部都替换成N。这种后期动态修改代码的思想就叫重构,具体的内容大家可以参考《重构》[13]一书。
程序2-3 重构的例子
1 /片段1/
2 char a[10] = "hello";
3 /片段2/
4 char a[10] = "hello";
5 for(i = 0;i<10;i++)
6 {
7 fun(a,10);
8 }
关于风格,我就简单说这么多,图2-8 中给出了一段简单的具有良好风格的源代码例子,可以看出其中有缩进,有注释,也有空行用来分割不同的源代码段落。另外变量的命名也非常的准确和清晰。
图2-8 布局和风格都比较清晰的一段程序