datasheet
超过460,000+ 应用技术资源下载
pdf

《嗨翻C语言》迷你书

  • 1星
  • 日期: 2014-04-07
  • 大小: 40.27MB
  • 所需积分:1分
  • 下载次数:160
  • favicon收藏
  • rep举报
  • 分享
  • free评论
标签: 嗨翻C语言

《嗨翻C语言》迷你书.

你能从《嗨翻C语言》这本书中学到什么?

  你有没有想过可以轻松学习C语言?《嗨翻C语言》将会带给你一次这样的全新学习体验。本书贯以有趣的故事情节、生动形象的图片,以及不拘一格、丰富多样的练习和测试,时刻激励、吸引、启发你在解决问题的同时获取新的知识。你将在快乐的气氛中学习语言基础、指针和指针运算、动态存储器管理等核心主题,以及多线程和网络编程这些高级主题。在掌握语言的基本知识之后,你还将学习如何使用编译器、make工具和其他知识来解决实际问题。

  这本书有什么特别之处?

  《嗨翻C语言》运用认知科学和学习理论的最新成果,精心为你打造了一次多感官的学习体验,绝对能够嗨翻你的大脑,激发你的学习热情。

  它的特别之处是:

  用图片等可视化手段,提高学习效率;

  使用对话和有个性的叙述风格,讲故事而不是照本宣科;

  调动读者左右半脑和各种感官,让学习者思考得更深入;

  吸引并抓住读者的注意力,让学习新技术一点都不枯燥。

Head First C 图灵程序设计丛书 嗨翻C语言 传授C编程专家的 内功心法 避免尴尬的 指针错误 玩转 C标准库 用make改变 你的生活 苏在可变参数 函数的帮助下 变得八面玲珑 重现经典 街机游戏 [美]David Dawn Griffiths Griffiths 著 程亦超 译 2013.7.29 1:39:45 PM hfc_00.indd 1 嗨翻C语言 C语言的书比航天飞机驾驶手册都难 懂,要是有本更容易理解的书就好了, 但我知道这是在痴人 说梦…… David Griffiths [美] 著 Dawn Griffiths 程亦超 译 Beijing • Cambridge • Farnham • Kln • Sebastopol • Tokyo O’Reilly Media, Inc.授权人民邮电出版社出版 13-8-1 下午3:22 图书在版编目(CIP)数据 嗨翻C语言 /(美)格里菲思(Griffiths, D.), (美)格里菲思(Griffiths, D.)著;程亦超译. —北 京:人民邮电出版社,2013.9 (图灵程序设计丛书) 书名原文:Head First C ISBN 978-7-115-31884-8 I. ①嗨… II. ①格… ②程… III. ①C语言-程序设 计 IV. ①TP312 中国版本图书馆CIP数据核字(2013)第137860号 内容提要 本书向读者提供了C语言的完整学习体验。全书分为三个部分:第1章到第4章是基础知识,包括基本语法、指 针、字符串、小工具与源文件;第5章到第8章为进阶内容,有结构、联合、数据结构、堆、函数指针、动/静态链 接;最后四章是高级主题,内容涵盖了系统调用、进程间通信、网络编程和多线程。每部分结束后还有一个有趣 的实验,可以提高读者的实际操作能力。此外,书中还包含大量的图片、示例和代码,有助于读者对于知识的理 解和把握。 本书适用于C开发人员以及对C语言感兴趣的初学者。 ◆著 [美]David Griffiths,Dawn Griffiths 译 程亦超 责任编辑 李松峰 执行编辑 李洁 ◆ 人民邮电出版社出版发行 北京市崇文区夕照寺街14号 邮编 100061 电子邮件 315@ptpress.com.cn 网址 http://www.ptpress.com.cn 北京 印刷 ◆ 开本:880×1230 1/20 印张:31.5 字数:781千字 2013年9月第1版 印数:1-5 000册 2013年9月北京第1次印刷 著作权合同登记号 图字:01-2012-4881 号 定价:99.00元 读者服务热线:(010) 51095186转604 印装质量热线:(010) 67129223 反盗版热线:(010) 67171154 广告经营许可证:京崇工商广字第0021号 hfc_00.indd   2 13-8-8   上午11:09 版权声明 Copyright©2012 David Griffiths and Dawn Griffiths. Simplified Chinese Edition, jointly published by O’Reilly Media, Inc. and Posts & Telecom Press, 2013. Authorized translation of the English edition, 2012 O’Reilly Media, Inc., the owner of all rights to publish and sell the same. All rights reserved including the rights of reproduction in whole or in part in any form. 英文原版由O’Reilly Media, Inc. 出版2012。 简体中文版由人民邮电出版社出版 2013。英文原版的翻译得到O’Reilly Media, Inc.的授权。此简体中文版的出 版和销售得到出版权和销售权的所有者 —— O’Reilly Media, Inc.的许可。 版权所有,未得书面许可,本书的任何部分和全部不得以任何形式重制。 hfc_00.indd 3 13-8-1 下午3:22 O’Reilly Media, Inc.介绍 O’Reilly Media通过图书、杂志、在线服务、调查研究和会议等方式传播创新知识。自1978年开始,O’Reilly一直都是 前沿发展的见证者和推动者。超级极客们正在开创着未来,而我们关注真正重要的技术趋势——通过放大那些“细微 的信号”来刺激社会对新科技的应用。作为技术社区中活跃的参与者,O’Reilly的发展充满了对创新的倡导、创造和 发扬光大。 O’Reilly为软件开发人员带来革命性的“动物书”;创建第一个商业网站(GNN);组织了影响深远的开放源代码峰 会,以至于开源软件运动以此命名;创立了Make杂志,从而成为DIY革命的主要先锋;公司一如既往地通过多种形 式缔结信息与人的纽带。O’Reilly的会议和峰会集聚了众多超级极客和高瞻远瞩的商业领袖,共同描绘出开创新产业 的革命性思想。作为技术人士获取信息的选择,O’Reilly现在还将先锋专家的知识传递给普通的计算机用户。无论是 通过书籍出版,在线服务或者面授课程,每一项O’Reilly的产品都反映了公司不可动摇的理念——信息是激发创新的 力量。 业界评论 “O’Reilly Radar博客有口皆碑。” ——Wired “O’Reilly凭借一系列(真希望当初我也想到了)非凡想法建立了数百万美元的业务。” ——Business 2.0 “O’Reilly Conference是聚集关键思想领袖的绝对典范。” ——CRN “一本O’Reilly的书就代表一个有用、有前途、需要学习的主题。” ——Irish Times “Tim是位特立独行的商人,他不光放眼于最长远、最广阔的视野并且切实地按照Yogi Berra的建议去做 了:‘如果你在路上遇到岔路口,走小路(岔路)。’回顾过去Tim似乎每一次都选择了小路,而且有几 次都是一闪即逝的机会,尽管大路也不错。” ——Linux Journal hfc_00.indd 4 13-8-1 下午3:22 谨以此书献给C语言之父Dennis Ritchie(1941~2011) hfc_00.indd 5 13-8-1 下午3:22 对Head First丛书的赞誉 “Kathy和Bert的《深入浅出Java》把书本变成了图形界面。作者通过一种诙谐、嬉皮的调调,把学 习Java变成了一个充满未知的过程,我总忍不住好奇地想:‘作者接下来会干嘛?’” ——Warren Keuffel,《软件开发杂志》 “《深入浅出Java》用引人入胜的手法带你走进Java世界的大门,书中没有令人望而却步的‘课后习 题’,而是设置了很多实践环节。很少有教科书能像这本书一样在做到机智、幽默、嬉皮和实用的 同时,还能教会你怎么使用对象序列化和网络发布协议。” ——Dr. Dan Russell,IBM Almaden研究中心用户科学与体验组主任、斯坦福大学人工智 能讲师 “《深入浅出Java》单刀直入,玩世不恭,妙趣横生,引人入胜,你一定能从中学到东西!” ——Ken Arnold,前Sun公司高级工程师、《Java编程语言》合著者(另一个作者是Java 之父James Gosling) “举重若轻,犹如把千斤重的书本从我心头卸下。” ——Ward Cunningham,Wiki之父、Hillside Group创始人 “这本书非常适合我们这些喜欢新技术的程序员,它对实际的开发很有参考价值,没有枯燥乏味 的‘学究腔’,读罢感到神清气爽。” ——Travis Kalanick,Scour and Red Swoosh Member创始人、MIT TR100会员 “过去世界上有三种书:用来买的书,用来收藏的书,用来放在桌子上的书。直到O’Reilly和Head First团队的出现,世界上有了第四种书——Head First系列的书——满是折角、破损不堪、随身携 带的书。我把《深入浅出SQL》放在了触手可及的地方。而且,就连我审稿用的PDF文档也被我 莫名其妙地翻坏了。” ——Bill Sawyer,Oracle ATG课程主管 “这本书条理分明、幽默风趣、货真价实,即使不是程序员也能从这本书中学到解决问题的方法。” ——Cory Doctorow,Boing Boing合作编辑、Down and Out in the Magic Kingdom及 Someone Comes to Town, Someone Leaves Town作者 vi hfc_00.indd 6 13-8-1 下午3:22 “我一拿到这本书就开始读了起来,欲罢不能,这本书实在太酷了!不仅有趣,涵盖了那么多东西, 还抓住了要点,叫人毕生难忘。” ——Erich Gamma,IBM杰出工程师、《设计模式》合作者 “是我读过最有趣也是最具智慧的一本关于软件设计的书。” ——Aaron LaBerge,ESPN.com技术副总监 “过去人们需要反复试验才能学到的东西现在已经浓缩为了一本引人入胜的书。” ——Mike Davidson,Newsvine公司CEO “每一章都围绕着优雅的设计展开,每一个概念在传达智慧的同时也不失实用。” ——Ken Goldstein,迪士尼在线执行副总裁 “我爱Head Frist HTML with CSS & XHTML,它寓教于乐!” ——Sally Applin,UI设计师、艺术家 “过去我在看设计模式的书时总是晕乎乎的,恨不得头悬梁锥刺股,但这本书却让我体会到了学习 设计模式的乐趣。” “当其他书还在老和尚念经时,这本书已经开始高声歌唱:‘摇滚吧,宝贝!’” ——Eric Wuehler “爱死这本书了,我当着老婆的面吻了它。” ——Satish Kumar hfc_00.indd 7 vii 13-8-1 下午3:22 对本书的赞誉 “《嗨翻C语言》可能很快就会被证明是学习C语言的最佳书籍。我觉得它会成为每所大学C语言的 标准教材。很多编程书籍因循守旧。不过这本书却使用了完全不同的方式。它将教你如何成为一名 真正的C程序员。” ——Dave Kitabjian,NetCarrier Telecom软件开发总监 “《嗨翻C语言》是一本用经典‘Head First’的方式轻松介绍C语言的教材。图片、笑话、练习以及 实践让读者逐渐并稳固地掌握C语言的基础知识……由此,读者可以进入Posix和Linux系统编程中 更高级的技术殿堂。” ——Vince Milner,软件工程师 viii hfc_00.indd 8 13-8-1 下午3:22 作者 《嗨翻C语言》的作者 David Griffiths Dawn Griffiths David Griffiths 他12岁时看到一部介绍Seymour Papert工作的纪录 片,从此踏上编程之路。15岁那年实现了Papert的 LOGO编程语言。大学专业是理论数学,毕业后开始 编程,并成为一名专栏作家。现在有三个头衔:敏 捷教练、程序员和车库管理员。能够用十多种编程 语言编程,但只精通其中的一种。写作、编程、辅 导之余,David喜欢和心爱的妻子——也是本书的合 著者Dawn一起旅行。 在写《嗨翻C语言》之前,David写过两本Head First 系列的书:Head First Rails和Head First Programming。 你可以在Twitter上“粉”David: http://twitter.com/dogriffiths。 Dawn Griffiths 在英国一所顶尖的大学开始了她的数学生涯,获得 了数学系的荣誉学位,毕业以后投身软件开发行业, 迄今已经有15年的IT行业从业经验。 在和David一起写《嗨翻C语言》之前,Dawn曾写 过两本Head First系列的书(《深入浅出统计学》和 Head First 2D Geometry),同时还主持过该系列其他 几本书。 除了为Head First系列写书,Dawn对太极拳、跑步、 编蕾丝和烹饪也很有研究。她十分享受和丈夫在一 起旅行的时光。 hfc_00.indd 9 ix 13-8-1 下午3:22 译者序 1969年“阿波罗11号”登月成功。贝尔实验室中一个叫Ken Thompson的年轻人为了一圆翱游太空的梦想,在当 时的Multics①系统上写了一个叫《星际之旅》的游戏。但当时大型机的机时费很贵,每玩一次公司就要为此支付 75美金,于是Thompson打起了小型机PDP-7的主意。但当时的PDP-7只有一个简陋的运行时系统,不支持多用 户,为了能双人对战,Thompson找来Dennis Ritchie一起开发新的操作系统。 他们只花了一个月的时间就用汇编语言写出了操作系统的原型。同事Peter Neumann看到后,戏称这个系统为 Unics②。Unix这个名字典出于此。 1971年,第一版的Unix已经能够支持两名用户在PDP-11上玩《星际之旅》了,但因为当时的Unix是用汇编语言 写的,无法移植到其他机器上,所以他们决定用高级语言重写Unix,可当时的高级语言无论从运行效率还是功 能上都无法满足他们的需要。Thompson先是在BCPL的基础上萃取出了B语言,Ritchie又在B的基础上进行了重 新设计,这才有了今天大名鼎鼎的C语言。 而现在你手上的就是一本关于C语言的书。 本书分为三个部分。 ● 第1章到第4章是基础知识,包括基本语法、指针、字符串、小工具与源文件。 ● 第5章到第8章为进阶内容,有结构、联合、数据结构、堆、函数指针、动/静态链接。 ● 最后四章是高级主题,内容涵盖了系统调用、进程间通信、网络编程和多线程。 每部分结束后还用一个实验来提高读者的动手能力。 本书最大的特点是每次在引出新概念前都会先提出一个问题,让读者在知道怎样做(how)之前先知道为什么 这么做(why),并在解决问题的过程中不断提出新问题,让读者去解决,从而加深理解;书中还设有很多 “问答”环节,提出并回答了一些读者在学习过程中可能会遇到的问题。除此之外,作者还使用了大量拟人手法, 例如让编译器化身公众人物在访谈节目中现身说法,抑或让静态库和动态库对簿公堂。谈笑风生间,它们的特 点,跃然纸上。无论你是音乐发烧友、推理迷,还是填字游戏爱好者,都可以在这本书中找到吸引你的元素。 两个改变世界的发明起初不过是为了一个游戏,从这个角度看,这本同样趣味十足的《嗨翻C语言》,能否算是 对于这种精神的一种延续呢? ① Multics全称为MULTiplexed Information and Computing System(多路信息计算系统)是1964年由贝尔实验室、MIT和通用电气共同研 发的一套安装在大型机上的多人多任务操作系统。因为工作进度缓慢,贝尔实验室于1969年退出该计划。 ② 意思是UNiplexed Information and Computing System(单路信息计算系统),用来影射Multics。 x hfc_00.indd 10 13-8-1 下午3:22 我在翻译的过程中力求真实传达作者的意图,无论是一个技术上的概念还是一段幽默。为了减轻阅读压力,我 还将书中部分代码中的字符串也译为了中文,希望不是画蛇添足。 最后,感谢王琛、邱瑀庭等好友提出的建议;感谢作者David Griffiths耐心解答我提出的每一个问题。感谢图灵 的李洁、李松峰、傅志红老师以及各位审读老师提供的帮助与支持。 程亦超 2012年12月17日 hfc_00.indd 11 xi 13-8-1 下午3:22 O’Reilly的其他相关图书 C in a Nutshell Practical C Programming C Pocket Reference Algorithms with C Secure Programming Cookbook for C and C++ O’Reilly Head First系列的其他图书 Head First Programming Head First Rails Head First JavaTM Head First Object-Oriented Analysis and Design (OOA&D) Head First HTML5 Programming Head First HTML with CSS and XHTML Head First Design Patterns Head First Servlets and JSP Head First EJB Head First PMP Head First SQL Head First Software Development Head First JavaScript Head First Ajax Head First Statistics Head First 2D Geometry Head First Algebra Head First PHP & MySQL Head First Mobile Web Head First Web Design xii hfc_00.indd 12 13-8-1 下午3:22 hfc_00.indd 13 目录 目录(精简版) 引子 xxxi 1 C语言入门:进入C语言的世界 1 2 存储器和指针:指向何方? 41 2.5 字符串:字符串原理 83 3 创建小工具:做一件事并把它做好 103 4 使用多个源文件:分而治之 157 C语言实验室1:Arduino 207 5 结构、联合与位字段:创建自己的结构 217 6 数据结构与动态存储:牵线搭桥 267 7 高级函数:发挥函数的极限 311 8 静态库与动态库:热插拔代码 351 C语言实验室2:OpenCV 389 9 进程与系统调用:打破疆界 397 10 进程间通信:沟通的艺术 429 11 网络与套接字:金窝,银窝,不如127.0.0.1的草窝 467 12 线程:平行世界 501 C语言实验室3:爆破彗星 523 i 饭后甜点:十大遗漏知识点 539 ii 话题汇总:总复习 553 目录(完整版) 引子 让大脑重视C语言。现在你正试着学习某些东西,为了不让学习卡壳,你 的大脑也在帮你的忙,大脑在想:“最好把空间留给重要的事,比如什么 动物是危险的?裸体滑雪是不是一个坏主意?”那么怎么才能欺骗你的大 脑,让它认为学好C关系到你下半生的幸福呢? 本书为谁而写 我们知道你在想什么 元认知 驯服你的大脑 用户须知 技术审校团队 致谢 xxxii xxxiii xxxv xxxvii xxxviii xl xli xiii 13-8-1 下午3:22 目录 1 C语言入门 进入C语言的世界 想知道计算机在想什么? 你需要为一款新游戏编写高性能的代码吗?你需要为Arduino编程吗?你需要在 iPhone应用中使用高级的第三方库吗?如果是的话,C语言就可以帮上忙了。相 比其他大多数语言,C语言的工作层次更低,因此理解C语言可以让你更清楚程 序在运行时到底发生了什么,C语言还可以帮助你更好地理解其他语言。来吧, 拿起编译器,很快你就能入门了。 C语言用来创建空间小、速度快的程序 2 完整的C程序长啥样? 5 如何运行程序? 9 两类命令 14 到目前为止的代码 15 用C语言算牌? 17 布尔运算 18 现在的代码 25 随时转向的命运列车 26 有时一次还不够…… 29 所有循环的结构都相同…… 30 用break语句退出循环…… 31 C语言工具箱 40 xiv hfc_00.indd 14 13-8-1 下午3:22 目录 2 存储器和指针 指向何方? 如果真的想玩转C语言,就需要理解C语言如何操纵存储器。 C语言在如何使用存储器方面赋予了你更多的掌控权。在本章中,你将揭开存储 器神秘的面纱,看到读写变量时到底发生了什么;学习数组的工作原理,以及怎 样避免烦人的存储器错误;最重要的是,你将看到掌握指针和存储器寻址对成为 一名地道的C程序员来讲有多么重要。 C代码包含指针 深入挖掘存储器 栈 和指针起航 试着传递指向变量的指针 使用存储器指针 怎么把字符串传给函数? 数组变量好比指针…… 堆 运行代码时,计算机在想什么 数组变量与指针又不完全相同 为什么数组从0开始 为什么指针有类型 全局量 用指针输入数据 使用scanf()时要小心 常量 除了scanf()还可以用fgets() 字符串字面值不能更新 LINK A6, #VARSIZE MOVEM.L DO-D7/A1-A5,-(SP) 代码 MOVE.L SP, SAVESTK(A6) MOVE.L SP, SAVEAS(A6) MOVE.L GRAFGLOBALS(A5),AO ... 如果想修改字符串,就复制它 把存储器保存在大脑里 C语言工具箱 报告船长,一 帆风顺! 向坎昆前进! 到达! 放春假咯! 42 43 44 47 48 53 54 55 59 61 62 65 66 67 72 74 80 81 latitude 32 31 4 100 000 xv hfc_00.indd 15 13-8-1 下午3:22 目录 2.5 字符串 字符串原理 字符串不只是用来读取的。 在C语言中字符串其实就是char数组,这你已经知道了,问题是字符串能用来干 嘛?该string.h出场了。string.h是C标准库的一员,它负责处理字符串。如果想要 连接、比较或复制字符串,string.h中的函数就可以派上用场了。在本章中,你将 学会如何创建字符串数组,并近距离观察如何使用strstr()函数搜索字符串。 不顾一切找Frank 84 创建数组的数组 85 找到包含搜索文本的字符串 86 使用strstr()函数 89 该审查代码了 94 “数组的数组”和“指针的数组” 98 C语言工具箱 101 比较两个字符串 复制字符串 xvi string.h 搜索字符串 切割字符串 hfc_00.indd 16 13-8-1 下午3:22 目录 3 创建小工具 做一件事并把它做好 操作系统都有小工具。 C语言小工具执行特定的小任务,例如读写文件、过滤数据。如果想要完成 更复杂的任务,可以把多个工具链接在一起。那么如何构建小工具呢?本章 中,你会看到创建小工具的基本要素并学会控制命令行选项、操纵信息流、 重定向,并很快建立自己的工具。 小工具可以解决大问题 104 程序如何工作 108 但没有使用文件…… 109 可以用重定向 110 隆重推出标准错误 120 默认情况下,标准错误会发送到显示器 121 fprintf()打印到数据流 122 用fprintf()修改代码吧 123 灵活的小工具 128 切莫修改geo2json工具 129 一个任务对应一个工具 130 用管道连接输入与输出 131 bermuda工具 132 输出多个文件 137 创建自己的数据流 138 main()可以做得更多 141 由库代劳 149 C语言工具箱 156 标准输入来自键盘 标准错误发送到显 示器 标准输出发送到显 示器 xvii hfc_00.indd 17 13-8-1 下午3:22 目录 4 使用多个源文件 分而治之 大程序不等于大源文件。 你能想象一个企业级的程序如果只有一个源文件,维护起来有多么困难与耗时吗? 在本章中,你将学习怎样把源代码分解为易于管理的小模块,然后把它们合成一 个大程序,同时还将了解数据类型的更多细节,并结识一个新朋友:make。 简明数据类型指南 162 勿以小杯盛大物 163 使用类型转换把float值存进整型变量 164 不好啦,兼职演员来了…… 168 代码到底怎么了 169 编译器不喜欢惊喜 171 声明与定义分离 173 gcc -c 创建第一个头文件 174 如果有共同特性…… 182 把代码分成多个文件 183 编译的幕后花絮 184 共享代码需要自己的头文件 186 又不是造火箭……还真是! 189 不要重新编译所有文件 190 首先,把源代码编译为目标文件 191 记不住修改了哪些文件 196 用make工具自动化构建 198 make是如何工作的 199 用makefile向make描述代码 200 火箭升空! 205 C语言工具箱 206 gcc -o xviii hfc_00.indd 18 13-8-1 下午3:22 目录 5 结构、联合与位字段 创建自己的结构 生活可比数字复杂多了。 到 目前为止,你只接触过C语言的基本数据类型,但如果想表示数字、文本以 外的其他东西呢,或为现实世界中的事物建立模型,怎么办?结构将帮你创 建自己的结构,模拟现实世界中错综复杂的事物。在本章中,你将学习如何把 基本数据类型组成结构以及用联合处理生活的不确定性。如果你想简单地模 拟“是”或“非”,可以用位字段。 有时要传很多数据 218 窃窃私语 219 用结构创建结构化数据类型 220 只要把“鱼”给函数就行了 221 使用“.”运算符读取结构字段 222 结构中的结构 227 如何更新结构 236 代码克隆了乌龟 238 你需要结构指针 239 (*t).age和*t.age 240 同一类事物,不同数据类型 246 联合可以有效使用存储器空间 247 如何使用联合 248 枚举变量保存符号 255 它是Myrtle…… 有时你想控制某一位 261 ……但传给函数 位字段的位数可调 262 的是它的克隆 C语言工具箱 266 xx hfc_00.indd 20 Turtle “t” 13-8-1 下午3:22 目录 6 数据结构与动态存储 牵线搭桥 一个结构根本不够。 为了模拟复杂的数据需求,通常需要把结构链接在一起。在本章中,你将学习如 何用结构指针把自定义数据类型连接成复杂的大型数据结构,将通过创建链表来 探索其中的基本原理;同时还将通过在堆上动态地分配空间来学习如何让数据结 构处理可变数量的数据,并在完成工作后释放空间;如果你嫌清理工作太麻烦, 可以学习一下怎么用valgrind。 Craggy 保存可变数量的数据 268 链表就是一连串的数据 269 在链表中插入数据 270 创建递归结构 271 用C语言创建岛屿…… 272 在链表中插入值 273 用堆进行动态存储 278 有用有还 279 用malloc()申请存储器…… 280 用strdup()修复代码 286 用完后释放存储器 290 SPIES系统综述 300 软件取证:使用valgrind 302 反复使用valgrind,收集更多证据 303 推敲证据 304 最终审判 307 C语言工具箱 堆上4 204 853 309 号单元、32字 节大小的数据 Shutter Isla Nublar xxi hfc_00.indd 21 13-8-1 下午3:22 目录 7 高级函数 发挥函数的极限 基本函数很好用,但有时需要更多功能。 到目前为止,你只关注了一些基本的东西,为了达成目标,需要更多的功能 与灵活性。本章你将学习如何把函数作为参数传递,从而提高代码的智商, 并学会用比较器函数排序,最后还将学会使用可变参数函数让代码伸缩自如。 寻找真命天子…… 把代码传给函数 把函数名告诉find() 函数名是指向函数的指针 …… ……没有函数类型 如何创建函数指针 用C标准库排序 用函数指针设置顺序 分手信自动生成器 创建函数指针数组 让函数能伸能缩 C语言工具箱 312 316 317 318 319 320 325 326 334 338 343 350 测试机 xxii hfc_00.indd 22 13-8-1 下午3:22 目录 8 静态库与动态库 热插拔代码 你已经见识过标准库的威力。 是时候在代码中发挥这种威力了。在本章中,你将学会创建自己的库,以及在多 个程序中复用相同代码;还将掌握编程大师的秘诀——通过动态库在运行时共享 代码;最后你将写出易于扩展并可以有效管理的代码。 值得信赖的代码 352 尖括号代表标准头文件 354 如何共享代码? 355 共享.h头文件 356 用完整路径名共享.o目标文件 357 存档中包含多个.o文件 358 用ar命令创建存档 359 最后编译其他程序 360 Head First健身房全球化战略 365 计算卡路里 366 事情可没那么简单…… 369 程序由碎片组成…… 370 在运行时动态链接 372 .a能在运行时链接吗? 373 首先,创建目标文件 374 一种平台一个叫法 375 C语言工具箱 387 葡萄干、面粉、黄 油、 鱼…… 他是超人?不是。 蝙蝠侠?也不是。 他是带有元信息的 可重定位目标文件。 hfc_00.indd 23 xxiii 13-8-1 下午3:22 目录 9 进程与系统调用 打破疆界 打破常规。 你已经学会了通过在命令行连接小工具的方式建立复杂的程序。但如果你想 在代码中使用其他程序怎么办?本章中你将学会如何用系统服务来创建和控 制进程,让程序发电子邮件、上网和使用任何已经安装过的程序。本章的最 后,你将得到超越C语言的力量。 操作系统热线电话 398 黑客入侵了…… 402 岂止是安全问题 403 exec()给你更多控制权 404 exec()函数有很多 405 数组函数:execv()、execvp()、execve() 406 传递环境变量 407 大多数系统调用以相同方式出错 408 用RSS读新闻 416 exec()是程序中最后一行代码 420 用fork()+exec()运行子进程 421 这是 C语言工具箱 427 newshound 进程。 分别为三条新闻源运 行了独立的进程。 newshound 子进程将同时运行。 hfc_00.indd 25 xxv 13-8-1 下午3:22 目录 10 进程间通信 沟通的艺术 创建进程只是个开始。 如果你想控制运行中的进程,向进程发送数据或读取它的输出,该怎么办?通过 进程间通信,进程可以合力完成某件工作。我们将向你展示如何让程序与系统中 其他程序通信,从而提升它的战斗力。 输入输出重定向 430 进程内部一瞥 431 重定向即替换数据流 432 fileno()返回描述符号 433 有时需要等待…… 438 家书抵万金 442 用管道连接进程 443 案例研究:在浏览器中打开新闻 444 子进程 445 父进程 445 在浏览器中打开网页 446 进程之死 451 捕捉信号然后运行自己的代码 452 用sigaction()来注册sigaction 453 #include 使用信号处理器 454 用kill发送信号 457 int main() 打电话叫程序起床 458 { C语言工具箱 466 char name[30]; printf("Enter your name: "); fgets(name, 30, stdin); printf("Hello %s\n", name); return 0; } File Edit Window Help > ./greetings Enter your name: ^C > 只要按Ctrl-C,程序就会停止运行,为 什么会这样? xxvi hfc_00.indd 26 13-8-1 下午3:22 目录 11 网络与套接字 金窝,银窝,不如127.0.0.1的草窝 不同计算机上的程序需要对话。 你已经学习了怎么用I/O与文件通信,还学习了如何让同一台计算机上的两个 进程通信,现在你将走向世界舞台,让C程序通过互联网和世界各地的其他程 序通信。本章的最后你将创建具有服务器和客户端功能的程序。 互联网knock-knock服务器 468 knock-knock服务器概述 469 BLAB:服务器连接网络四部曲 470 套接字不是传统意义上的数据流 472 服务器有时不能正常启动 476 妈妈说要检查错误 477 从客户端读取数据 478 一次只能服务一个人 485 为每个客户端fork()一个子进程 486 自己动手写网络客户端 490 主动权在客户端手中 491 创建IP地址套接字 492 getaddrinfo()获取域名的地址 493 C语言工具箱 500 Telnet 客户端 服务器将同时与多 Telnet 客户端 个客户端对话。 服务器 客户端与服务器之间展 开一段结构化对话,叫做 协议。 Telnet 客户端 xxvii hfc_00.indd 27 13-8-1 下午3:22 目录 12 线程 平行世界 程序经常需要同时做几件事。 POSIX线程可以派生几段并行执行的代码,从而提高代码的响应速度。但是要小 心!线程虽然很强大,但它们之间可能发生冲突。本章你将学习如何用红绿灯来 防止代码发生车祸。最终你将学会创建POSIX线程,并使用同步机制来保护共享 数据的安全。 A 两辆车分别代表两个线 程,它们想访问同一个 共享变量。 B xxviii 任务是串行的……还是…… 502 ……进程不是唯一答案 503 普通进程一次只做一件事 504 多雇几名员工:使用线程 505 如何创建线程? 506 用pthread_create创建线程 507 线程不安全 512 增设红绿灯 513 用互斥锁来管理交通 514 C语言工具箱 521 共享变量 红绿灯防止两个线程同时访问共享 变量。 hfc_00.indd 28 13-8-1 下午3:22 目录 i 饭后甜点 十大遗漏知识点 革命尚未成功,同志还需努力。 我们认为你还需要知道一些事,如果不讲,总觉得哪里不对劲,但我们又不希望 这本书重得只有大力士才提得动,所以我们只做简单介绍。在你放下这本书前, 尽情地享用这些“美味佳肴”吧。 #1. 运算符 540 #2. 预处理指令 542 #3. static关键字 543 gcc #4. 数据类型的大小 544 #5. 自动化测试 545 #6. 再谈gcc 546 #7. 再谈make 548 #8. 开发工具 550 #9. 创建GUI 551 #10. 参考资料 552 ii 话题汇总 总复习 将C语言的特性尽收眼底。 我们把本书中出现过的所有关于C语言的话题和原理都汇总在了这里。把它们过 一遍,看你还记得多少。每条话题都标明了来源章节号。如果你想不起来,很容 进程间通信 进程间通信 易就能找到原文,甚至还可以把它们剪下来贴在墙上。 第9章 第9章 system()会把字符串当 成命令运行。 fork()复制当前进程。 fork()+exec()创 建子进程。 第9章 execl() = 参数列表 execle() = 参数列表 + 环境变量 execlp() = 参数列表 + 搜索PATH execv() = 参数数组 execve() = 参数数组 + 环境变量 execvp() = 参数数组 + 搜索PATH 第9章 进程可以用管道通信。 pipe()创建通信管道。 第10章 第10章 exit()立即终止程序。 waitpid()等待进程结 束。 第10章 第10章 xxx hfc_00.indd 30 13-8-1 下午3:23 引子 如何使用本书 引子 真不敢相信他们把这 些内容放进了C语言的 书中! hfc_00.indd 31 的这吗本?书是为你准备 份的对不人于错来每的说个礼,花物它钱。都购是买一 “在他本们节为中什,么我要们把回这答些了内读容者放最进关C心语的言问的题书:中?” 你现在的位置 4  xxxi 13-8-1 下午3:23 如何使用本书 本书为谁而写? 如果下列问题你都回答“是”: 1 你会用其他语言编程吗? 2 你想要掌握C语言,并用它创造软件业的神话,成为 亿万富翁,然后在私人小岛上安享晚年吗? 听里起之来行有,些始遥于不足可下及”,,但不“是千吗? 3 比起枯燥乏味的讲座你更喜欢动手并将所学付诸实践 吗? 那么这本书就是为你准备的。 谁与本书无缘? 下列问题只要有一个你回答“是”: 1 你正在寻找C语言的简介或工具书? 2 你宁可在大庭广众下和黑猩猩接吻也不愿意吸纳新的 知识?你坚信C语言的书应该无所不能而且一定是刻 板无趣的吗? 请放下这本书,向后转,前进50步。 xxxii  引子 hfc_00.indd 32 书有写市…信给场…用任部我卡何友们就人情也可看提收以的示支购,:票买只本。这要书本你是 13-8-1 下午3:23 引子 我们知道你在想什么 “C语言的书怎么可以这么恶搞?” “那些图片是干嘛的?” “我真的可以这样学习C语言么?” 我们也知道你的大脑在想什么 重大要脑的认。为这是 大脑渴望新奇的事物,它总是在搜索、扫描和等待不同寻常的东西。大 脑生来如此,正是它的这种特性我们才长葆活力。 大脑怎样处理那些老生常谈、平淡无奇的事物呢?它会想尽一切方法 阻止它们妨碍自己的真正工作——记住那些重要的事情。大脑不会浪 费脑细胞去记忆无聊的事情,它们被“这件事显然不重要”给滤掉 了。 大脑又怎么知道哪些事情是重要的呢?假设你去郊游,突然有只老虎 跳到你面前,你的大脑和身体会发生哪些反应? 神经元触发、情绪激动、肾上腺素激增。 大脑于是立刻知道…… 太好了,只剩下 600页天书了! 这些一定很重要!千万别忘记! 但如果你在绝对安全的环境中学 习, 比如 你正在 家或 图书 馆复 习迎 考 ,大不脑值认得为记这忆些。 或奉老板之命在一周内掌握某项艰深的技术。 大脑为了帮助你,会阻止那些明显不重要的东西占用稀缺资源。资源 应该用来存放真正重要的东西,比如老虎、火灾和“千万别在 Facebook上发布自己的裸照”。但你又不能对你的大脑说:“拜 托,无论这本书有多无聊,我有多么不情愿,请务必将这些东西 记下来。” hfc_00.indd 33 你现在的位置 4  xxxiii 13-8-1 下午3:23 如何使用本书 我们将Head First的读者视为学习者。 怎式的样的文才死字能记全学,部到根背东据下西认来?知。首我科先们学,知、你道神必如经须何生理激物解活学书你和的中教大内育脑容心,,理让然学你后的有确最效保新地不研学会究习忘,。记学。习这不并仅不仅代是表把填书鸭上 H可提片使第取注e让为到视高下a用一代意读了d好化面8对人照,F9者让奇。或%i话称r本一深读s和)图另t式和宣场学入者受。片一和对科生习思解到图比页个话,动守考决启片单上性的使有则。问发让,纯化形用趣:这题。事学的的式生的么、为情习文语把餐活说得此更者字言内会化吧出,加解更风,容的,结你容决容格还直语除论需易相易是。接言非和要理关记一最讲,你形应解问忆场近给轻积成对,题、严的学松极新一把的学肃一生一刺的系能文习的项听点激知列力字起学研,,自识挑将放来术究胜学己,战提在更讲发人生的就、高相有座现一的神要练两关效?,筹考经让习倍图果相!试元他。、片(比你成,们发的知于觉否绩充人内识传得则提满深的部统哪你高动思回或的的个了力的想附授大更4、提和0近课脑容%亲问转,方。只易身和化而式用是引参活率不,讲个起与动可是使摆故你、来以图用设事的感刺。 激引大的打事这会你起脑、情情里别的并只有感,人的左保会挑也牌不情右持注战记。会感脑读意性得你的指和者与的让能技的各的众技你否术是种注不术有记时惊感意同变所住那讶官。、得感一种、。人有不触件“好人再趣的舍事奇都枯、事我情、有燥怪情其和有这乏异。谁这趣样味、我”件、的,夺并的事疑经大人不优本问验脑眼是越身,:学球在感的以明习和说。情及明起出忠感解想来人犬色决要就意八彩难认会料公有题真很的和很后学快东主大油习。西人关然,。之系而但一间。生是旦催你的一学人记成看习泪得就书一下你感就种的在,犯全故乎和困新事的学。, xxxiv  引子 hfc_00.indd 34 13-8-1 下午3:23 元认知:思考的思考 如果你真心想学习,并且想要学得更快、更深,那你就应该注意你是如何 注意的,思考你是如何思考的,学习你是如何学习的。 绝大多数人在成长的过程中没有受过元认知或学习理论方面的教育。我们 都知道要学习,却不知该如何学习。 假设你阅读本书的目的是为了学习编程,但又不想花太多时间。如果你想 应用你读到的东西,你就要记住它们,为此,你必须先理解它们。为了让 这本书(以及其他某本书或任何一段学习经验)的价值最大化,你就要对 大脑负责。 秘诀在于让你的大脑认为你正在学习一样很重要的东西,和老虎一样 重要,甚至关系到你下半生的幸福。不然,当你在埋首苦读之时, 你的大脑却在努力地排斥吸纳新的知识。 引子 怎么才能忽悠大脑记 住这些东西…… 如何让大脑将编程视为洪水猛兽? 既有沉闷缓慢的方法,也有快速有效的方式。慢的方法就是不断 重复,即使是世界上最乏味的东西,只要反复背它个几百遍,终 归能够记住。当你背到第1907遍的时候,大脑说:“既然你看了一遍又一遍, 姑且认为它很重要吧!” 快的方法是用各种方法增加大脑活动,尤其是不同类型的大脑活动。上一页中我 们已经提到了几种方法,它们已经被证明是帮助大脑工作的有效方法。例如,研究 表明将文字置于它所描述的图片内部(而不是页面中其他的地方,比如标题或正文 中),有助于让大脑弄清文字与图片是如何关联的,这会触发更多的神经元。更多 的神经元被触发意味着你的大脑更有可能认为它们是重要的事情,也就更有可能记 住这些事情。 对话之所以能够帮助学习,是因为人们在对话时为了能接上对方的话,注意力比平 时更集中。神奇的是,大脑并不介意这种对话是发生在你与书本之间的。相反,如 果行文风格是那种正儿八经的调调,大脑就会以为你正坐在死气沉沉的教室听老师 讲解一种二十年前就已经淘汰了的技术,自然打不起精神。 图片和对话只是开始,好戏还在后头…… hfc_00.indd 35 你现在的位置 4  xxxv 13-8-1 下午3:23 如何使用本书 我们做了什么 图片,大脑喜欢图片而不是文字,对大脑而言,一图胜千言。在图片和文字配合使用 时,我们会把文字嵌入图片内部,因为当文字出现在它所描述的图片内部,而不是标 题或正文中时,大脑的认知效果更好。 重复,即把同一样东西以不同的方法、媒介、感官体验呈现给读者,这样做的好处是, 相同的内容可能被大脑的不同区域所保存,增加了它被记忆的可能性。 用你意想不到的方式来使用概念和图片,谁让大脑喜欢猎奇呢?同时,我们在使用图 片和概念时会加入一些情感色彩,因为大脑会受情感的左右。让你心有所感的事情更 有可能被记住,即便这种感觉不过是些小幽默、小惊讶或小趣味。 个性化、对话式的表达方式,当大脑发现你在对话而不是被动地听讲时,注意力会更 集中,即便这种对话不是真的。 超过80个活动,“光看不练假把势”,比起单纯地阅读,当你在做事情时大脑能学到 和记住更多的东西。为了满足绝大多数读者的需要,我们将习题的难度设定在中等偏 上,即稍微有一点挑战,但大部分人都有能力完成。 保持学习方法的多样性,每个人喜欢不同的学习方法,有人喜欢按部就班,有人喜欢 在深入理解细节之前先对整体有所把握,还有的人喜欢直接看例子。不管你属于哪一 种,如果以不同的形式展现相同的内容,每个人都将受益。 同时利用你的左脑与右脑,因为越多的脑细胞参与到学习中,你学到和记住东西的可 能性就越大,集中注意力的时间也就越长。左右脑轮流上岗,一个工作的时候另一个 休息,可以让你在长时间的学习中始终保持高效的状态。 书中写入了含有多种观点的故事和练习,当大脑被迫做出评估和判断时,它便会进入 更深层次的学习。 挑战题,比如一些练习题和需要想一想才能回答的问题。大脑只有在运转起来的时候 才能学习和记忆。看别人吃饭,自己的肚子是不会饱的!我们能做的只是保证你的力 气用对地方,而不是把脑细胞浪费在处理难以理解的例子、糟糕的文法、拗口的术语 以及过于简略的表述上。 使用人,在很多故事、例子和图片中都会出现人,因为你是人,所以你的大脑会更加 注意人,而不是东西。 xxxvi  引子 hfc_00.indd 36 13-8-1 下午3:23 引子 为了驯服大脑,你需要做的事情 俗话说:“师父领进门,修行在个人。”这些小技巧只是 开始,你需要倾听你的大脑,找出哪些技巧对你有效,哪 些是无用功,一切只有试了才知道! 沿虚线剪下,贴在冰箱上 时刻提醒自己。 1 慢慢来,理解越深,背的就越少。 不要走马观花,时常停下来,想一想。看到一个 问题,不要直接去翻答案,想象考试时真的碰到 这道题该怎么办。大脑想得越深,学习和记忆的 效果就越好。 2 做练习,写笔记。 我们设置了很多习题,但如果你不做,就好比请 别人代你吃饭,你永远也不会饱。不要盯着题目 看,写点什么下来,研究表明,学习时进行一些 身体活动可以起到促进效果。 3 阅读“这里没有蠢问题”单元。 每一个都要读!它们不是附录,而是本书的核心 内容,千万不要跳过它们。 6 多喝水。 大脑喜欢在充满水的环境中工作,脱水(等到 你感到口渴说明你已经脱水了)会使你的认知 功能下降。 7 倾听大脑。 时刻注意你的大脑是否已经过载,当你发现自 己读不进去或前读后忘时,就到了该休息一下 的时候了。一旦过了这个点,你的学习效率就 会大打折扣,并影响你的进度。 8 心有所感。 你要让大脑知道这件事很重要。试着进入故事 布置的场景,根据自己的理解为每一张图片添 加注释。埋怨一个蹩脚的笑话不好笑,总比没 有想法要好。 4 让阅读本书成为你睡前最后一件事,至少是最后一 件有挑战的事。 学习的一部分(尤其是将短期记忆转变为长期记忆 的那部分)发生在你把书本放下以后。大脑需要一 点时间对知识进行消化,如果你在这段时间里又学 习了新的东西,可能会把之前学到的忘掉。 5 大声读出来。 朗读会使大脑的另一部分也活跃起来,如果你尝试 理解或记忆某件事情,那么应大声地把它说出来。 向另一个人解释,你会学得更快,并在讲解的过程 中萌生一些新的想法。 9 多写代码! 学习C语言只有一种方法:多写代码。这是本书 的主旋律。编程是一种技能,掌握它的唯一方法 就是练习。为此,我们提供了很多练习的机会: 每一章都有一些习题提出问题让你去解决,不要 跳过它们——解题也是学习的一部分!实在不会 做偷看一下答案也无伤大雅(谁没有提笔忘字的 时候呢?),不过一定要在看答案前自己先做一 遍。在你进入下一章的学习之前一定要保证上一 章的程序能够正确运行。 你现在的位置 4  xxxvii hfc_00.indd 37 13-8-1 下午3:23 如何使用本书 用户须知 这是一段学习体验,而不是一本工具书。因此我们扫除了你在学习过程中可能会遇到的 一切障碍。第一遍阅读时,请从头看起,因为本书对你的知识背景做了一些假设。 我们假设你是C语言的新手,但不是对编程一窍不通。 我们假设你以前写过一些程序,不一定要很多,但至少已经接触过其他语言(比如 JavaScript)中的一些基本概念,例如循环、变量。C是一种不怎么“高级”的高级语 言,所以如果你一点编程经验都没有,那么在学习这本书之前应该找本别的书来看看, 强烈推荐Head First Programming。 你需要在电脑上安装C编译器。 这 本 书 中 我 们 使 用 了 g c c( G N U 编 译 器 套 装 ) , 它 不 但 功 能 十 分 强 大 , 而 且 还 是 免 费 的。你需要确保你的电脑上已经安装了gcc。如果你的操作系统是Linux,恭喜你, 你已经拥有了gcc;如果你使用的是Mac,你需要安装Xcode开发工具,你可以从苹果 应用商店或苹果官网下载;如果你使用的是Windows操作系统,有两种选择:一种是 Cygwin(http://www.cygwin.com),它可以完全模拟UNIX环境,自然也就包括了gcc; 如果你只是想创建能够在Windows下运行的程序,MinGW(http://www.mingw.org)可能 更符合你的需要。 书中所有代码都是跨操作系统平台的,我们极力避免写出只能在一种操作系统中才能运 行的代码。但在极个别情况中,不同操作系统上的实现可能会略有不同,但我们会指出 来。 我们从教你一些C语言的基本概念开始,然后就带你上战场了。 第1章会介绍C语言的基础知识,有了这些东西打底,到第2章时你就能写一些有实际用 途、十分有趣的程序了。其余章节会逐步提高你的编程技巧。一眨眼的功夫,你就从一 个C语言菜鸟成长为一名武林高手了。 xxxviii  引子 hfc_00.indd 38 13-8-1 下午3:23 引子 不要跳过任何活动。 习题和活动不是附加题,它们是这本书的核心内容。它们中有的是为了帮助你记 忆,有的是为了便于理解,还有一些为了让你学以致用,总之,不要跳过任何习 题。 重复是有意的,而且是重要的。 Head First系列与其他技术书的最大不同在于我们希望你真的能够学到东西,而且 看完书之后还能记得它们。绝大多数工具书不以记忆为目的,但这本书的核心是学 习,为了加强你的记忆,相同的概念可能重复出现好几遍。 例子尽可能简洁。 读者告诉我们在一个200行的例子中寻找2行能说明问题的代码是一件十分头疼的事 儿。本书中的绝大部分示例代码都很短,这样你需要学习的部分也就清楚简洁。别 指望这些代码经久耐用,它们甚至不是完整的,它们是专门为了学习而写的,因此 功能不一定完整。 “脑力风暴”没有答案。 一部分“脑力风暴”练习没有正确答案,另一部分“脑力风暴”练习答案不唯一, 你需要心里有数,而在一些练习中,你会找到一些提示,它们将指引你走向胜利之 门。 hfc_00.indd 39 你现在的位置 4  xxxix 13-8-1 下午3:23 审校团队 技术审校团队 Dave Kitabjian Vince Milner 技术审校人员 Dave Kitabjian,电气工程和计算机工程的双学位,拥有20年的咨询、集成、架构经验,曾为多家世界500强公 司和高科技创业公司的客户提供信息系统解决方案。工作之余,Dave喜欢弹吉他、练钢琴、陪伴太太和三个孩 子。 Vince Milner,干了20多年的C(和其他语言)程序员,在多种平台上工作过。虽然在攻读数学系硕士学位,但 下棋时却屡屡输给六岁孩童。 xl  引子 hfc_00.indd 40 13-8-1 下午3:23 引子 致谢 编辑 首先,要感谢的人是Brian Sawyer,是他让我们写了这本书。 Brian在每一个环节都十分信任我们,让我们有足够的自由去 试验新的想法,在截稿日期到来的那几天也没有太抓狂。 Brian Sawyer O’Reilly团队 感谢以下这些人一路以来对我们的帮助:Karen Shaner找图片的本领堪称一绝,有 她在事情就好办多了;在波士顿,Laurie Petrycki让我们吃得很好,大大鼓舞了我 们的斗志;Brian Jepson带我们走进了Arduino的神奇世界;首发小组制作出这本 书的第一版电子版供人下载;最后要感谢Rachel Monaghan和制作小组严格把关, 他们是幕后英雄。你们个个都是好样的。 家人、朋友和同事 我们在这次的Head First之旅中认识了很多朋友,感谢Lou Barr、Brett McLaughlin 和Sanders Kleinfeld教了我们很多东西! David:感谢Andy Parker、Joe Broughton、Carl Jacques、Simon Jones和其他朋友, 十分抱歉我在忙于写作的这段时间里疏远了你们。 Dawn:要不是亲朋好友们的鼎力支持,这本书可没那么容易写成!特别要感谢爸 爸妈妈、Carl、Steve、Gill、Jacqui、Joyce和Paul,真心感谢你们的支持和鼓励! 没有他们就没有这本书 我们的技术审查团队完成了一系列了不起的工作,让我们少走了很多弯路,并确 保我们写的东西都是对的。同样感谢那些对首发试读版给予反馈的人,是你们让 这本书变得更好。 最后,向这个伟大系列丛书的创始人Kathy Sierra和Bert Bates致谢! hfc_00.indd 41 你现在的位置 4  xli 13-8-1 下午3:23 safari在线图书 S af a r i®在线图书 Safari Books Online(www.safaribooksonline.com)是一所按需出版的数 字图书馆,它以图书和视频的形式出版世界一流技术、商务作家的专业 作品。Safari Books Online是技术专家、软件开发人员、Web设计师、商务和创意人士从事 科学研究、解决问题、学习和进行认证培训的主要资源。 Safari Books Online向组织机构、政府机关和个人提供各种产品组合和价格方案。订阅者可 通过具有完整搜索功能的数据库获取O’Reilly Media、Prentice Hall Professional、AddisonWesley Professional、Microsoft Press、Sams、Que、Peachpit Press、Focal Press、Cisco Press、John Wiley & Sons、Syngress、Morgan Kaufmann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、Manning、New Riders、McGraw-Hill、Jones & Bartlett、Course Technology以及其他几十家出版社的上千种图书、培训视频和预出版书稿。想要了解更多 Safari Books Online的信息,请访问我们的网站。 xlii  引子 hfc_00.indd 42 13-8-1 下午3:23 1 C语言入门 进入C语言的世界 难道你不喜欢深蓝色的大 海么?快投身C的海洋吧, 这里的水儿惹人爱! 想知道计算机在想什么吗? 你需要为一款新游戏编写高性能的代码吗?你需要为Arduino编程吗?你需要在 iPhone应用中使用高级的第三方库吗?如果是的话,C语言就可以帮上忙了。相 比其他大多数语言,C语言的工作层次更低,因此理解C语言可以让你更清楚程序 在运行时到底发生了什么,C语言还可以帮助你更好地理解其他语言。来吧,拿 起编译器,很快你就能入门了。 这是新的一章 1 hfc_01-cs3.indd 1 2013.7.26 2:10:48 PM C语言如何工作 C语言用来创建空间小、速度快的程序 C语言旨在创建空间小、速度快的程序。它比其他大多数语言的抽象 层次更低,也就是说用C语言写的代码更加接近机器语言。 C语言的工作方式 计算机只理解一种语言——机器代码,即一串二进制0、1流。 你可以在编译器的帮助下将C代码转化为机器代码。 #include int main() { puts("C Rocks!"); return 0; } rocks.c 1 源代码 从创建一个源文件 开始,源文件中是 供人阅读的C代码。 File Edit Window Help Compile > gcc rocks.c -o rocks > 2 编译 通过编译器运行源代 码,编译器会检查错 误,一旦它觉得没问 题,就会编译源代码。 在文W件i叫ndroocwkss中.ex,e,这而个 非rocks。 rocks 3 输出 编译器会创建一个叫可执 行文件的新文件,文件中 是机器代码,即计算机能 够理解的0、1流,而这个 文件就是可以运行的程序。 为了写出速度快、空间小、可移植性高的程序,人 们常使用C语言。绝大多数的操作系统、其他计 算机语言和大多数游戏软件都是用C语言写的。 你可能会遇到三种C标准。ANSI C始于20世纪80年 代后期,适用于最古老的代码;1999年开始的C99 标准有了很大的改进;在2011年发布的最新标准 C11中,加入了一些很酷、很新的语言特性。不同 版本的标准之间差别不是很大,如果碰到我们会 指出。 2   第1章 hfc_01-cs3.indd 2 2013.7.26 2:10:49 PM C语言入门 猜一猜这些代码片段分别会做什么。 描述一下你认为这些代码会做什么。 int card_count = 11; if (card_count > 10) puts("这副牌赢面很大,我要加注!"); int c = 10; while (c > 0) { puts("我决不在课堂上写代码!"); c = c - 1; } /* 假设人名小于20个字符。 */ char ex[20]; puts("输入男友的名字:"); scanf("%19s", ex); printf("亲爱的%s,我们分手吧。\n", ex); char suit = 'H'; switch(suit) { case 'C': puts("梅花(Clubs)"); break; case 'D': puts("方块(Diamonds)"); break; case 'H': puts("红心(Hearts)"); break; default: puts("黑桃(Spades)"); } 你现在的位置 4   3 hfc_01-cs3.indd 3 2013.7.26 2:10:49 PM 代码片段揭秘 即使你还不能全部理解,也不用担心,稍后我们会详细地解释这 里的每样东西。 int card_count = 11; 整型(integer)是一个整数。 if (card_count > 10) puts("这副牌赢面很大,我要加注!"); 在命令提示符或终端显示字符串。 创建整型变量并将其设为11。 计数超过10? 是的话就在命令提示符显示消息。 int c = 10; 花括号定义了 while (c > 0) { 块语句。 puts("我决不在课堂上写代码!"); c = c - 1; } 创建整型变量并将其设为10。 只要值为正…… ……显示消息…… ……计数减小。 这是一段需要重复执行的代码的尾部。 /* 假设人名小于20个字符。 */ char ex[20]; puts("输入男友的名字: "); scanf("%19s", ex); 表示“将用户输入的任意 字符保存在ex数组中”。 printf("亲爱的%s,我们分手吧。\n", ex); 在此处插入这个字符串代替%s。 char suit = 'H'; switch(suit) { case 'C': switch语句检查 一个变量的不同取值。 puts("梅花(Clubs)"); break; case 'D': puts("方块(Diamonds)"); break; case 'H': puts("红心(Hearts)"); break; default: puts("黑桃(Spades)"); } 4   第1章 这是注释。 创建一个有20个字符的数组。 在屏幕上显示消息。 将用户输入的内容保存在数组中。 显示一条包含输入文本的消息。 创建字符变量,保存字母“H”。 查看变量的值。 是“C”吗? 是则显示“梅花(Clubs)”这个词。 然后跳过其他检查。 是“D”吗? 是则显示“方块(Diamonds)”这个词。 然后跳过其他检查。 是“H”吗? 是则显示“红心(Hearts)”这个词。 然后跳过其他检查。 否则…… 显示“黑桃(Spades)”这个词。 检测到此结束。 hfc_01-cs3.indd 4 2013.7.26 2:10:50 PM 完整的C程序长啥样? 为了创建完整的程序,需要在C源文件中输入代码。任何文本 编辑器都可以创建C源文件,它们的文件名通常以.c结尾。 虽然这只是惯例,但应该遵守。 我们来看一个典型的C源文件。 C语言入门 1 C程序通常以注释开头。 注释描述了文件中这段代码的意图,也可能包含一些许可证或者版权 信息。在这个地方(或文件的任何地方)添加注释不是必需的,但加 上是个好的做法,也是大多数C程序员希望看到的。 注释以/*开始。 这些*号可加可不加,这里加上它 们只是为了好看。 注释以*/结尾。 2 接下去是include部分。 C语言是一种很小的 语言,如果不使用外 部库,它几乎什么也 干不了。为了告诉编 译器程序要使用哪些 外部代码,需要包含 (include)相关库的头 文件。stdio.h是最常见 的头文件,stdio库中 包含了那些能在终端读 写数据的代码。 /* * 计算牌盒中牌数量的程序。 * 本代码使用“拉斯维加斯公共许可证”。 * (c)2014, 学院21点扑克游戏小组。 */ #include int main() { int decks; puts("输入有几副牌"); scanf("%i", &decks); if (decks < 1) { puts("无效的副数"); return 1; } printf("一共有%i张牌\n", (decks * 52)); return 0; } 3 在源文件中找到的最后一样东西是函数。 所有的C代码都在函数中运行。对任何C程序来讲,最重要的函数是 main()函数。main()函数是程序中所有代码的起点。 让我们仔细研究一下main()函数。 你现在的位置 4   5 hfc_01-cs3.indd 5 2013.7.26 2:10:50 PM 主函数 main()函数聚焦 计算机会从main()函数①开始运行程序。它的名字很重要:如果没 有一个叫main()的函数,程序就无法启动。 main()函数的返回类型是int。这是什么意思呢?当计算机在运行程序时, 它需要一些方法来判断程序是否运行成功,计算机正是通过检查main()函 数的返回值来做到这一点。如果让main()函数返回0,就表明程序运行成 功;如果让它返回其他值,就表示程序在运行时出了问题。 这是返回类型:main函数的 返回类型必须是int。 int main() 因为这个函数叫“main”,程序将从这里开始运行。 { 只要有参数,就应该在这里提到它们。 int decks; puts("输入有几副牌"); scanf("%i", &decks); 函数体总是被 花括号包围。 if (decks < 1) { puts("无效的副数"); return 1; } printf("一共有%i张牌\n", (decks * 52)); return 0; } 函数名在返回类型之后出现,如果函数有参数,可以跟在函数名后面。最后 是函数体,函数体必须被花括号包围。 百宝箱 printf()函数用于显示格式化输出,它用变量的值来替换格 式符,像这样:将第一个参数作为 字符串插到这里。 第一个参数。 printf("%s说计数是%i", "阿星", 21); 将第二个参数作为整型插到这里。 第二个参数。 当调用printf()时,可以包含任意数量的参数,但确保每个参数都要有 一个对应的%格式符。 如状令或果态提在想e,示cLh检可i符on查u以中%xE或程在输rrM序Wo入ar的icLn:终de退ov端we出sl中命% 输入: echo $? 6   第1章 ① 在早期的ANSI C标准中,main()函数可以是void类型。但是在C99标准 中main函数的返回类型必须是int。——译者注 hfc_01-cs3.indd 6 2013.7.26 2:10:51 PM 代码冰箱贴 学院21点扑克游戏小组的队员写了一些代码贴在寝室的冰箱上,但有人把 冰箱贴弄乱了!你能用这些冰箱贴重组代码吗? /* * 计算牌面点数的程序。 * 使用“拉斯维加斯公开许可证”。 * (c)2014学院21点扑克游戏小组。 */ C语言入门 { 输入两个字符作为 牌名。 main() char card_name[3]; puts("输入牌名: "); scanf("%2s", card_name); int val = 0; if (card_name[0] == 'K') { val = 10; } else if (card_name[0] == 'Q') { ; ; val = 11 int 'J' #include 'A' } else if (card_name[0] == ){ val = 10; } (card_name[0] == ){ return } else { 将文本转为数值。 val = atoi(card_name); } printf("这张牌的点数是: %i\n", val); 0; } else #include val = 10 if 你现在的位置 4   7 hfc_01-cs3.indd 7 2013.7.26 2:10:51 PM 冰箱贴归位 代码冰箱贴解答 学院21点扑克游戏小组的队员写了一些代码贴在寝室的冰箱上,但有人把冰箱 贴弄乱了!请用这些冰箱贴重组代码。 /* * 计算牌面点数的程序。 * 使用“拉斯维加斯公开许可证”。 * (c)2014学院21点扑克游戏小组。 */ #include #include int main() { char card_name[3]; puts("输入牌名: "); scanf("%2s", card_name); int val = 0; if (card_name[0] == 'K') { val = 10; } else if (card_name[0] == 'Q') { val = 10 ; } else if (card_name[0] == 'J' ){ val = 10; else } if (card_name[0] == 'A' ) { val = 11 ; } else { val = atoi(card_name); } printf("这张牌的点数是: %i\n", val); return 0; } 8   第1章 这里没有 蠢问题 问:card_name[0]是什 么意思? 答 : 它是用户输入的第一 个字符。如果用户输入了10,那 么card_name[0]就将是1。 问:总是得用/*和*/写注 释吗? 答 : 如果你的编译器支持 C99标准,就可以用/ / 开始注 释。编译器会将这一行的其余部 分当做注释处理。 问:怎么才能知道我的编 译器支持哪种标准? 答:你可以查看编译器的文 档。对gcc来讲,ANSI C、C99 和C11这三种标准它全部支持。 hfc_01-cs3.indd 8 2013.7.26 2:10:52 PM C语言入门 如何运行程序? C语言是一种编译型语言,也就是说计算机不会直接解释代码,而是需要将给人阅读的源代 码转化(或编译)为机器能够理解的机器代码,这样计算机才能够执行。 为了编译代码,需要一个叫编译器的程序。GNU编译器套件(GNU Compiler Collection), 也叫gcc,是最流行的C编译器之一。gcc可以在很多操作系统中使用,而且除了C语言, 它还可以编译很多其他语言,最重要的是,它是完全免费的。 下面是用gcc编译并运行程序的过程: 1 将前一页那道“代码冰箱贴”练习中的代码保存在一个叫 cards.c的文件中。 2 在命令提示符或终端中使用gcc cards.c -o cards命令 进行编译。 File Edit Window Help Compile > gcc cards.c -o cards > 将cards.c编译为一个 叫cards的文件。 3 在Windows命令提示符中输入cards或在Mac和Linux终端 中输入./cards运行程序。 File Edit Window Help Compile > ./cards 输入牌名: C源代码文件通常 以.c结尾。 cards.c cards.c cards 如果在Windows中, 这个文件将是cards. exe。 百宝箱 在大部分机器中,可以用下面这个技巧来编译并运行代码: 这里的&&表示:如果成功,就做…… gcc zork.c -o zork && ./zork 在Windows中,应 该输入zork而不 是./zork。 这条命令只有在编译成功的情况下才会运行新程序,一旦编译过程 中出了问题,它就会跳过运行程序这一步,仅仅在屏幕上显示错误消 息。 动手吧! 现在就应该创建cards.c文 件,然后编译它。随着本章内 容 的 展 开,我 们 会 在 它 的 基 础上逐步改进。 你现在的位置 4   9 hfc_01-cs3.indd 9 2013.7.26 2:10:52 PM 试驾 试驾 让我们来看看程序能否成功编译和运行。在你的机器上打开命令提示 符或终端,试试吧! 这行命令编译了代码并 创建了cards程序。 这行命令运行了程序。如果 你在Windows中,别加./。 再次运行程序。 用户输入了牌的 名字…… ……然后程序显示了 这张牌的点数。 File Edit Window Help 21 > gcc cards.c -o cards > ./cards 输入牌名: Q 这张牌的点数是: 10 > ./cards 输入牌名: A 这张牌的点数是: 11 > ./cards 输入牌名: 7 这张牌的点数是: 7 合别回成忘上一了一步:页(可看忘以看了把。怎编)么译。弄和?运翻行 程序工作了! 恭喜!你已经成功编译并运行了C程序。gcc编译器从cards.c中提取出 了供人阅读的源代码,并将其转换为cards程序中机器才能理解的机器 代码。如果你用的是Mac或Linux,计算机会在一个叫cards的文件中 创建机器代码;而在Windows中,所有程序的扩展名必须是.exe,因此 这个文件叫cards.exe。 这里没有 蠢问题 问:为什么我在Linux和Mac中运行程序时必须在程序前加上./? 答:因为在类Unix操作系统中,运行程序必须指定程序所在的目录,除非 程序的目录已经列在了PATH环境变量中。 10   第1章 hfc_01-cs3.indd 10 2013.7.26 2:10:53 PM C语言入门 等等,我还是不明白,我们让用 户输入牌名时使用了字符数组, 为什么要用字符数组?为什么不用 字符串(s t r i ng)或其他东西? C语言不支持现成的字符串。 但有很多C语言的扩展库 提供字符串。 C语言比其他大多数语言的抽象层次更低,因此它不提供 字符串,而是用了相似的东西来代替:以字符为元素的数 组。如果你用过其他语言,一定已经见过数组了,数组 就是一张有名有姓的事物清单,所以card_name只是一 个变量名,用来引用你在命令提示符输入的那张字符列表 的。把card_name定义为大小为2个字符的数组,就可以 用card_name[0]和card_name[1]分别引用第一和第 二个字符。为了理解字符串的工作原理,让我们深入计算 机的存储器,看看C语言是如何处理文本的…… hfc_01-cs3.indd 11 你现在的位置 4   11 2013.7.26 2:10:53 PM 字符串理论 字符串聚焦 字符串只是字符数组,当C语言看到一个这样的字符串时: s = "Shatner" 会把它当做一个数组读取,而这个数组是由一个个独立的字符组成的:在C语言中,就是这样定义数组的。 s = {'S', 'h', 'a', 't', 'n', 'e', 'r'} 字符串中的每个字符是数组中的一个元素,这就是为什么可以通过索引 来引用字符串中的某个字符,比如s[0]、s[1]。 别在字符串的尽头掉下去 当C语言想要读取字符串中的内容时,会发生什么呢?比如说它想打印字 符串吧。在如今的很多语言中,计算机会时刻记录数组的大小,但C语言 比大多数语言更低层,它无法确切地知道数组有多长,如果C语言想在屏 幕上显示字符串,它就需要知道什么时候会到达字符数组的尾部,为此 C语言加入了哨兵字符。 哨兵字符是一个出现在字符串末尾的附加字符,它的值为\0。每当计算 机需要读取字符串的内容时,它会逐一扫描字符数组中的所有元素,直 到碰到\0,也就是说当计算机看到下面这个字符串时: S h a ... s[0] s[1] s[2] '\0' 当C语言 看到\0 就知道该 停了。 s = "Shatner" 存储器中实际保存的是: S h a t n e r \0 s[0] s[1] s[2] s[3] s[4] s[5] s[6] s[7] 这就是为什么我们要在代码中像这样定义card_name变量: \0是ASCII字符, 它的值为0。 C程序员通常叫它 空(NULL)字符。 char card_name[3]; 字符串card_name只需要记录1到2个字符,但因为字符串要以哨兵字符结尾,所以 我们必须把数组的大小定义为3,以放下一个额外的字符。 12   第1章 hfc_01-cs3.indd 12 2013.7.26 2:10:53 PM C语言入门 这里没有 蠢问题 问 : 问: 问 : 为什么字符要从0开始编 C语言居然不知道数组有多 字符串字面值和字符数组 号?为什么不是1? 长? 有没有区别? 答 : 答 : 答 : 字符的索引值是一个偏移 是的,虽然编译器有时可 只有一个区别:字符串字 量:它表示当前要引用的这个字符到 以通过分析代码计算出数组的长度, 面值是常量。 数组中第一个字符之间有多少字符。 问:为什么要这样做? 答 : 计算机在存储器中以连续 字节的形式保存字符,并利用索引计 算出字符在存储器中的位置。如果计 但一般情况下,C语言希望你来记录 数组的长度。 问:单、双引号有区别吗? 答 : 有区别,单引号通常用来 表示单个字符,而双引号通常用来表 问:那是什么意思? 答 : 也就是说这些字符一旦创 建完毕,就不能再修改它们。 问:如果我改了会怎么样? 算机知道c[0]位于存储器1 000 000 示字符串。 号单元,那么就可以很快地计算出 c[96]在1 000 000 + 96号单元。 问:我应该用双引号(")定义 字符串,还是以显式字符数组的形式 问 : 为什么要设立哨兵字符? 定义字符串? 答:这取决于编译器,gcc通常 会显示总线错误(bus error)。 问:总线错误?那是什么东西? 难道计算机就不知道字符串的长度 吗? 答 : 通常不知道。记录数组的 长度不是C语言的强项,字符串其实 答 : 通常应该用双引号来定义 字符串。用双引号定义的字符串叫字 符串字面值(string literal),比起字 符数组,它输入起来也更方便。 答:C语言采取不同的方式在存 储器中保存字符串字面值。总线错误 意味着程序无法更新那一块存储器空 间。 就是个数组。 虎口拔牙 “等号”不一定表示等于。 把teeth的 值设为4 在C语言中,“等号”(=) 如果想要增加或减小变量 用来赋值(assignment), 的值,可以用+=和-=这两 而“双等号”用来检查两个 个赋值运算符,它们让代码 值是否相等。 看起来更简短。 teeth = 4; teeth加2 teeth += 2; teeth == 4; teeth -= 2; 最后,如果想要对变量的 值加1或减1,可以用++ 和--。 teeth++; 加1 teeth--; 减1 检查teeth的值是 不是4 teeth减2 你现在的位置 4   13 hfc_01-cs3.indd 13 2013.7.26 2:10:55 PM 做事情 两类命令 到目前为止你看到的所有命令都可以分为以下两类。 做事情 C语言中大部分命令都是语句。简单的语句是一些动作,它们做事情, 或告诉我们事情。你已经见过定义变量的语句、从键盘读取输入的语 句以及向屏幕显示数据的语句。 split_hand(); 这是一条简单的语句。 当把很多语句组合在一起,就创建出了块语句。块语句是由花括号围 起来的一组命令。 这些命令被花括号 包围,因此形成了 块语句。 { deal_first_card(); deal_second_card(); cards_in_hand = 2; } 只有条件为真才去做事情 例如if这样的控制语句在运行代码之前会检查条件: if (value_of_hand <= 16) 这是条件。 hit(); else stand(); 如果条件为真,就执行这条语句。 如果条件为假,就执行这条语句。 当条件为真时,if语句一般要做好几件事情,因此if语句通常和块 语句一起使用: if (dealer_card == 6) { double_down(); hit(); } 如果条件为真,这两条命令都 会运行。它们组合进了一条块 语句中。 戴还是不戴? 块语句能像处理一条语句那 样处理一批语句。在C语言 中,if条件语句如下: if (countdown == 0) do_this_thing(); 这条if条件语句运行了一条语 句,如果想要在if中运行多条 语句呢?只要用花括号把这些 语句包起来就行了,C语言会 把它们当做一条语句处理: if (x == 2) { call_whitehouse(); sell_oil(); x = 0; } C程序员喜欢保持代码的简洁, 因此大多数人会省略if条件语 句和while循环语句中的花括 号,比起写: if (x == 2) { puts("Do something"); } 大多数C程序员更喜欢写成: if (x == 2) puts("Do something"); 14   第1章 hfc_01-cs3.indd 14 2013.7.26 2:10:56 PM 到目前为止的代码 /* * 计算牌面点数的程序。 * 使用“拉斯维加斯公开许可证”。 * 学院21点扑克游戏小组。 */ #include #include int main() { char card_name[3]; puts("输入牌名: "); scanf("%2s", card_name); int val = 0; if (card_name[0] == 'K') { val = 10; } else if (card_name[0] == 'Q') { val = 10; } else if (card_name[0] == 'J') { val = 10; } else if (card_name[0] == 'A') { val = 11; } else { val = atoi(card_name); } printf("这张牌的点数是: %i\n", val); return 0; } C语言入门 我在想,程序会检查点 数的范围吗?如果会就 方便了…… hfc_01-cs3.indd 15 你现在的位置 4   15 2013.7.26 2:10:58 PM 专栏广告 教你如何致富 刘富强21点扑克技能专修学校 嗨!最近还好吗?你是个聪明 人,我看得出来,因为我也是 聪明人,只有聪明人才能发现 这是一张大牌,因此这副牌中 就少了一张高点数的牌,于是 你将计数减1: 聪明人,不是吗?听着,我正 在干一件低风险高回报的事情, 是一张Q  count – 1 我想让你加入,为什么?谁让 我是个好人呢!看清楚了,我 是算牌专家,赌神高进正是在 但如果是4那样的小牌,计数 就加1: 下。你问我什么是算牌?好吧, 对我来说,算牌是一种事业。 是一张4  count + 1 如果想学到更多的东西,今天 就加入我的21点扑克技能专修 学校,除了算牌,你还能学到: * 如何利用“凯利公式”使赌 注的价值最大化 * 如何与监赌人员周旋 * 如何去除真丝西服上的奶酪 污渍 * 如何搭配花格子图案的服饰 说真的,算牌是一种提高21点 胜率的方法。在21点这种扑克 游戏中,如果牌盒中高点数的 大牌有10和人头牌(J、Q和 K),小牌有3、4、5和6。 详情请联系刀仔,由21点扑克 技能专修学校转交。 牌足够多,胜利的天平就会倒 向玩家——也就是你! 对每一张大牌和小牌都如此操 作,直到计数器变得很大,你 算牌可以帮助你记录还剩几张 高点数牌。假设开始计数为0, 发牌员发给你的第一张牌是Q, 就把现金押到下一盘,然后赢 得干净利落!很快你就比我的 三姨太更有钱了! 16   第1章 hfc_01-cs3.indd 16 2013.7.26 2:10:59 PM 用C语言算牌? 算牌是一种提高21点获胜几率的方法。在发牌时不断更新计数,玩家 就可以计算出下大注和下小注的最佳时机。虽然这是一项强大的技术, 但它真的非常简单。 C语言入门 我们已经有了做这 件事的代码。 用一个变量来表示 计数就行了。 在这里我们必须检查好几个 值……真的有这个必要吗? 计算牌面的点数。 点计数数在加31到。6之间(包括3和6)吗? 否则…… 计数是减101、。J、Q或K中的一个? 我们怎么检查一个值 >=3且<=6?需要检查 两次吗? 用C语言来写算牌程序有多难?你已经见过了测试一个 条件的方法,但算牌算法需要检查好几个条件:需要在 检查一个数>=3的同时检查它<=6。 所以需要一组操作将多个条件组合在一起。 hfc_01-cs3.indd 17 你现在的位置 4   17 2013.7.26 2:11:00 PM 表示范围 布尔运算 到目前为止,你已经见过了if语句,它检查一个条件是否为真。如果我们 想要检查多个条件?或检查一个条件非真呢? &&检查两个条件都为真 只有当给出的两个条件同时为真时,与运算(&&)的结果才为真。 if ((dealer_up_card == 6) && (hand == 11)) double_down(); 只有两个条件都为真,这段代码 才会运行。 与运算的效率很高,因为如果第一个条件为假,计算机就不会自寻烦恼地去 计算第二个条件,因为它知道如果第一个条件为假,那么整个条件也一定为 假。 ||检查两个条件中只要有一个为真 两个条件中只要有一个为真时,或运算(||)的结果就是真。 if (cupcakes_in_fridge || chips_on_table) eat_food(); 只要有一个为真。 如果第一个条件为真,计算机就不会自找麻烦地去计算第二个条 件,因为它知道只要第一个条件为真,整个条件也一定为真。 !把条件的值反过来 !是非运算,它将一个条件的值取反。 if (!brad_on_phone) !表示“非”。 answer_phone(); 百宝箱 在C语言中,布尔值 是用数字表示的。对 C语言来讲,数字0代表假的值。 那什么数字代表真呢?任何不等 于0的数字都将被当成真处理,因 此下面的C代码也没错: int people_moshing = 34; if (people_moshing) take_off_glasses(); 事实上,C程序常用它作为“检 查某个变量不为0”的简写。 18   第1章 hfc_01-cs3.indd 18 2013.7.26 2:11:01 PM hfc_01-cs3.indd 19 C语言入门 为了让程序能用来算牌,请做一些修改。如果牌的点数在3到6之间,程序需要显示一条消 息;如果牌是10、J、Q或K,则需要显示不同消息。 int main() { char card_name[3]; puts("输入牌名: "); scanf("%2s", card_name); int val = 0; if (card_name[0] == 'K') { val = 10; } else if (card_name[0] == 'Q') { val = 10; } else if (card_name[0] == 'J') { val = 10; } else if (card_name[0] == 'A') { val = 11; } else { val = atoi(card_name); } /* 检查牌的点数是否在3到6之间 */ if puts("计数增加"); /* 否则,检查牌是否是10、J、Q或K */ else if puts("计数减少"); return 0; } C标准礼貌指南 ANSI C标准没有用来表示真和假的值,C程序 把0这个值当做假处理,把0以外的任何值当做 真处理。C99标准则允许在程序中使用true和 false关键字。但编译器还是会把它们当做1和0 这两个值来处理。 你现在的位置 4   19 2013.7.26 2:11:02 PM 算牌程序 为了让程序能用来算牌,请做一些修改。如果牌的点数在3到6之间,程序需要显示一条消 息;如果牌是10、J、Q或K,则需要显示不同消息。 int main() { char card_name[3]; puts("输入牌名: "); scanf("%2s", card_name); int val = 0; if (card_name[0] == 'K') { val = 10; } else if (card_name[0] == 'Q') { val = 10; } else if (card_name[0] == 'J') { val = 10; } else if (card_name[0] == 'A') { val = 11; } else { val = atoi(card_name); } 这个条件有好几种 写法。 /* 检查牌的点数是否在3到6之间 */ if ((val > 2) && (val < 7)) puts("计数增加"); /* 否则,检查牌是否是10、J、Q或K*/ 这里只需要一个条件, else if (val == 10) 你发现了吗? puts("计数减少"); return 0; } 这里没有 蠢问题 问: 问: 为什么不能只写一个|和&? 那还要|和&干什么呢? 问:那是什么意思? 答: 答 : 答: 也不是不行。&和|操作符 对逻辑表达式求值只是它 6 & 4等于 4,是因为当对6 总是计算两个条件,而&&和||可以 们的一个用处,它们还能对数字的某 (二进制数110)和4(二进制数100) 跳过第二个条件。 一位进行布尔运算。 的每个二进制位布尔与时,就会得到 4(二进制数100)。 20   第1章 hfc_01-cs3.indd 20 2013.7.26 2:11:03 PM 试驾 现在编译并运行程序,看看会发生什么: 并这运行行命代令码用。来编译 我们多次运行程 序,以检查程 序在取不同值的 情况下有不同的 输出。 File Edit Window Help FiveOfSpades > gcc cards.c -o cards && ./cards 输入牌名: Q 计数减少 > ./cards 输入牌名: 8 > ./cards 输入牌名: 3 计数增加 > 代码正确运行。通过布尔运算符将多个条件组合在一起,就可以 检查取值是否在某个范围内,而不仅仅是一个值。现在算牌器 已经初具雏形。 计算机说牌的点数还很 低。计数增加了!加注! 加注! 隐形通信设备。 C语言入门 hfc_01-cs3.indd 21 你你现现在在的的位位置置4   21 2013.7.26 2:11:04 PM gcc访谈 编译器大曝光 本周访谈:gcc的奉献 Head First:gcc,非常谢谢您在百忙之中抽出 时间接受我们的采访。 gcc:小事一桩,很高兴能参加你们的节目。 Head First:gcc,听说你会说很多种语言,是 真的吗? gcc:我熟练地掌握了600多万种沟通方式…… Head First:真的假的? gcc:呵呵,开玩笑啦,不过我的确会说很多种 语言,除了C语言,我还会C++和Objective-C,对 Pascal、Fortran和PL/I等语言也有一定研究,Go语 言我也略知一二…… Head First:在硬件方面,听说你可以生成很多 平台的机器代码? gcc:几乎任何处理器。一般而言,每当硬件工 程师新创造了一种处理器,他要做的第一件事情 就是让我在上面运行。 Head First:这种灵活性简直不可思议,请问你 是怎么办到的呢? Head First:那么另外一种性格呢? gcc:我还有一个后端,一个将中间代码转化为 多种平台的机器代码的系统。每种操作系统都有 自己特定的可执行文件格式,但我都知道…… Head First:可是人们通常仅仅将你描述为翻译 器,你认为这公平吗?毕竟翻译不是你的全部。 gcc:是的,除了简单的翻译之外我还干很多事 情,例如我会发现代码中的错误。 Head First:能举些例子吗? gcc:我能够检查明显的错误,例如变量名拼错 了;我也能找到不容易发现的错误,例如变量的 重复定义;当程序员用已经存在的函数名去命名 变量时,我也会发出警告,等等。 Head First:也就是说你会检查代码的质量? gcc:没错,不仅仅是质量,还有性能。如果我 发现循环中的某段代码提到循环外面执行时也一 样正确,我会默默移动它。 Head First:你真的干了很多活! gcc:我的秘诀就是拥有双重性格。我有一个前 端,这个部分的我可以理解某种类型的源代码。 Head First:比如用C语言写的源代码? gcc:是的,但我一向低调行事。 Head First:gcc,谢谢你接受我们的采访。 gcc:没错,我的前端能够将这种语言转化为一 种中间代码,所有的语言前端都能够生成同一种 代码。 22   第1章 hfc_01-cs3.indd 22 2013.7.26 2:11:04 PM 变身编译器 这页上的每个C文件都代表一个完整的源文 件。你的工作是扮演编译器,并决定它们能 否编译成功。如果不能,说明原因。如果你 想得到附加分,说明程序编译以 后的运行结果,以及它们是否 能按预期工作。 A #include int main() { int card = 1; if (card > 1) card = card - 1; if (card < 7) puts("小牌"); else { puts("Ace!"); } return 0; } B #include int main() { int card = 1; if (card > 1) { card = card - 1; if (card < 7) puts("小牌"); else puts("Ace!"); } return 0; } C语言入门 C #include int main() { int card = 1; if (card > 1) { card = card - 1; if (card < 7) puts("小牌"); } else puts("Ace!"); return 0; } D #include int main() { int card = 1; if (card > 1) { card = card - 1; if (card < 7) puts("小牌"); else puts("Ace!"); return 0; } 你现在的位置 4   23 hfc_01-cs3.indd 23 2013.7.26 2:11:04 PM 编译后的代码 变身编译器解答 这页上的每个C文件都代表一个完整的源文 件。你的工作是扮演编译器,并决定它们能 否编译成功。如果不能,说明原因。如果你 想得到附加分,说明程序编译以 后的运行结果,以及它们是否 能按预期工作。 A #include 编译成功。程序显示“小牌”, 但程序不能正确工作,因为else int main() 匹配了错误的if。 { int card = 1; if (card > 1) card = card - 1; if (card < 7) puts("小牌"); else { puts("Ace!"); } return 0; } B #include 编译成功。程序什么也没显 示,它不能正确工作,因为 else匹配了错误的if。 int main() { int card = 1; if (card > 1) { card = card - 1; if (card < 7) puts("小牌"); else puts("Ace!"); } return 0; } 24   第1章 C #include int main() { int card = 1; if (card > 1) { card = card - 1; if (card < 7) puts("小牌"); } else puts("Ace!"); return 0; 编译成功。程序显 } 示“Ace!”,正确! D #include int main() { int card = 1; if (card > 1) { card = card - 1; if (card < 7) puts("小牌"); else puts("Ace!"); return 0; } 编译失败。因为花括号 不匹配。 hfc_01-cs3.indd 24 2013.7.26 2:11:04 PM 现在的代码 int main() { char card_name[3]; puts("输入牌名: "); scanf("%2s", card_name); int val = 0; if (card_name[0] == 'K') { val = 10; } else if (card_name[0] == 'Q') { val = 10; } else if (card_name[0] == 'J') { val = 10; } else if (card_name[0] == 'A') { val = 11; } else { val = atoi(card_name); } /* 检查牌的点数是否在3到6之间 */ if ((val > 2) && (val < 7)) puts("计数增加"); /* 否则,检查牌是否为10、J、Q或K */ else if (val == 10) puts("计数减少"); return 0; } 嗯……有没有什么办法可以改进这一连串的 i f语句呢?它们都检查了c a rd_n ame[0]的值, 并且都把变量val设为了10。不知道在C语言 中有没有更高效的语法。 C程序经常需要多次检查同一个值,并且在每一种情况中执行非常类似 的代码片段。 可以使用一连串的if语句,这没有错,但对于这种逻辑,C语言提供了 替代的写法。 C语言可以用switch语句进行逻辑测试。 hfc_01-cs3.indd 25 C语言入门 你现在的位置 4   25 2013.7.26 2:11:05 PM switch语句 随时转向的命运列车 有时候当你在写条件逻辑时,需要一次又一次地检查同一个变 量的值。为了避免写许许多多的if语句,C语言提供了另一种 选择:switch语句。 switch语句和if语句有些像,但它可以测试一个变量的多种 取值: switch(train) { case 37: 如果train == 37,winnings加50, 然后跳到终点。 winnings = winnings + 50; break; case 65: 如果train == 65,winnings加80, 再加20,然后跳到终点。 puts("头等奖!"); winnings = winnings + 80; case 12: winnings = winnings + 20; break; 如果train == 12, winnings加20。 default: winnings = 0; 对于其他任意train的值,winnings归零。 } 当计算机遇到switch语句,它会检查给出的值,然后寻找匹 配的case。找到后,它会运行case之后的所有代码直到遇 到break语句。计算机会一直运行下去直到有人吩咐它退出 switch语句。 读性。 漏掉break会让代码出错。 大部分C程序在每个case段的末尾 都有一条break语句,这样做虽然 会有失效率,但可以提高代码的可 26   第1章 hfc_01-cs3.indd 26 2013.7.26 2:11:06 PM 让我们再看一下cards程序中的那段代码: int val = 0; if (card_name[0] == 'K') { val = 10; } else if (card_name[0] == 'Q') { val = 10; } else if (card_name[0] == 'J') { val = 10; } else if (card_name[0] == 'A') { val = 11; } else { val = atoi(card_name); } 你能用switch语句重写这段代码吗?把你的答案写在下面吧: C语言入门 hfc_01-cs3.indd 27 你现在的位置 4   27 2013.7.26 2:11:06 PM 使用switch 请用switch语句重写代码。 int val = 0; if (card_name[0] == 'K') { val = 10; } else if (card_name[0] == 'Q') { val = 10; } else if (card_name[0] == 'J') { val = 10; } else if (card_name[0] == 'A') { val = 11; } else { val = atoi(card_name); } int val = 0; switch(card_name[0]) { case ‘K’: case ‘Q’: case ‘J’: val = 10; break; case ‘A’: val = 11; break; default: val = atoi(card_name); } 要点 ƒ switch语句可以取代一连串 的if语句。 ƒ switch语句检查一个单独的 值。 ƒ 计算机会在第一个匹配的 case语句处开始执行代码。 ƒ 在遇到break或到达switch 语句的末尾前,代码会一直 运行。 ƒ 核对是否把break放对了地 方,否则switch语句就会出 错。 这里没有 蠢问题 问: 问: 为什么我要用switch语 s w it c h语句只能检查变 句取代if? 量吗?它能检查值吗? 答: 答: 当需要多次检查同一变量 能,switch语句仅仅检查 时,使用switch语句会更方便。 两个值是否相等。 问: 问: 使用switch语句有什么 我能在s w it c h语句中检 好处? 查字符串吗? 答: 答: 有这几个好处。第一,让 不能用switch语句检查字 代码更清晰,一段代码处理一个变 符串或任何形式的数组,switch语 量 的 结 构 , 结 构 一 目 了 然 , 相 反 , 句只能检查值。 一连串的i f 语句就没那么清晰了; 第二,可以用下落逻辑在不同的分 支之间复用代码。 28   第1章 hfc_01-cs3.indd 28 2013.7.26 2:11:07 PM 有时一次还不够…… 你已经学习了很多关于C语言的知识,但仍有一些重要的东西需要 了解。你已经知道了如何根据不同的情况写程序,但有一样基本的 东西你还没见过,如果想让程序反复做一件事,怎么做? 两张牌?程序 还做不到…… 在C语言中使用while循环 循环是一种特殊类型的控制语句。控制语句决定一段代码是否运 行,而循环语句决定了一段代码运行几次。 C语言中最基本的循环结构是while循环,只要循环条件一直为 真,while循环就会一次又一次、周而复始地运行代码。 C语言入门 在运行循环体前检查条件。 while (<某个条件>) { 循环体在花括号里。} ... /* 在这里做一些事情 */ 如果循环体中只有一行代码, 你可以不加花括号。 当程序执行到循环体的末尾,计算机就会检查 循环条件是否仍为真。如果是,就会再次执行 循环体中的代码。 今天你do while了吗? while循环还有一种形式,它总是在循环体运行 后才检查循环的条件,也就是说循环体至少会被 执行一次,我们叫它do...while循环: do { /* 买彩票 */ } while(have_not_won); while (more_balls) keep_juggling(); 你现在的位置 4   29 hfc_01-cs3.indd 29 2013.7.26 2:11:08 PM for循环 所有循环的结构都相同…… 当重复执行一段代码时,可以用while循环。但循环的结构 总是如出一辙。 ¥ 在循环开始前做一些简单的工作,比如设置计数器。 ¥ 在每一轮的循环开始前进行条件测试。 ¥ 在每一轮的循环结束后做一些工作,如更新计数器。 下面这个例子是一个从1数到10的while循环: 这是循环启动代码。 int counter = 1; 这是循环更新代码, while (counter < 11) { 这是循环条件。 它用来在循环体的 printf("%i个枣\n", counter); 末尾更新计数器。 counter++; } 变别量忘的了值:加cou1”nt。er++表示“把counter 所有循环都是这样的三部曲:首先为循环准备变量,其次在 每一轮的循环前检查条件,最后在循环末尾更新计数器或实 现类似功能。 ……for循环让事情变得更简单 因为这个模式是通用的,C语言的设计者就创造了for循环, 它让代码看起来更简洁。同样的代码如果用for循环来写: 这是每次循环执行前对条件 进行检查的代码。 int counter; 这是每次循环后运行的 代码。 for (counter = 1; counter < 11; counter++) { 初始化循环变量。 printf("%i个枣\n", counter); } 因为循环体中只有一行代码,可以去掉花括号。 C程序大量使用for循环,至少要和while循环用得一样多。 for循环不但可以减少代码的行数,而且便于其他C程序员阅 读,因为所有用来控制循环的代码——控制counter变量值 的代码——现在都放到了for语句中,并从循环体中分离了出 来。 每个for循环的循 环体里都需要有点 东西。 30   第1章 hfc_01-cs3.indd 30 2013.7.26 2:11:09 PM 用bre ak语句退出循环…… 但如果想在循环中的某个地方跳出循环呢?当然,可以重新调 整代码的结构,但更简单的方法是,使用break语句直接跳出循 环: while (feeling_hungry) { eat_cake(); if (feeling_queasy) { /* 从while循环中跳出 */ break; } drink_coffee(); } “break”直接跳出 循环。 break语句可以直接退出当前循环,跳过循环体中break之 后的所有语句。break非常有用,因为它有时是结束循环最 简单有效的方法,但应该避免滥用break,因为它们会降低 代码的可读性。 ……用continue继续循环 如果想跳过循环体的其余部分,然后回到循环的开始,那么 continue语句就是你的最佳伴侣: while (feeling_hungry) { if (not_lunch_yet) { /* 回到循环条件 */ continue; “continue”带你回到循环 } 的开始。 eat_cake(); } C语言入门 break语句可以用 来退出循环语句和 switch语句。 使用break时看清 你在哪里,并不是所有地方都能 够使用break。 古墓谜案 break不能从if语句中退出。 1990年1月15日,AT&T的长途电 话系统死机,造成6万人无法使 用电话服务。起因是一个负责写 电路交换部分C代码的开发人员 企图用break从if语句中退出, 但break不能从if语句中退出。 相反,程序跳过了整段代码,引 起了这个bug,令7千万次电话呼 叫在9个多小时内无法接通…… 你现在的位置 4   31 hfc_01-cs3.indd 31 2013.7.26 2:11:10 PM 编写函数 函数聚焦 在试验新学的循环“咒语”前,我们绕道去看一眼函数。 到目前为止,在你写过的每个程序中,都必须创建一个函 数——main()函数: 这个函数返回 一个整型值。 int main() { 这是函数的名字。 这对括号中什么都没有。 函数体——做事情的那部分。 函数体被 花括号包 围。 puts("人生须臾,芳华易逝"); return 0; } 事情做完以后,返回一个值。 在C语言中几乎所有函数都有着相同的格式。例如在下面这个 程序中,main()函数调用了一个自定义函数。 #include 返回整型值 int larger(int a, int b) { if (a > b) return a; 这个函数接收两个参数:a和b。这两 个参数都是整型。 return b; } int main() { 在这里调用函数 int greatest = larger(100, 1000); printf("%i最大!\n", greatest); return 0; } larger()函数与main()函数有一点区别,它接收参数(argument或 parameter)。参数是一个局部变量,函数从调用它的代码那里得到参 数的值。larger()函数要接收两个参数:a和b,它返回两个参数中 较大那个的值。 C标准礼貌 指南 main()这一函数的返回类 型是int,因此必须在函数 结束前包含一条return语 句。即使不加,代码也能编 译通过,但会收到编译器 的警告。支持C99标准的编 译器会在你忘记的时候插 入一条return语句。如果 你想让编译器遵循C99标准, 可以使用-std=99选项。 32   第1章 hfc_01-cs3.indd 32 2013.7.26 2:11:11 PM void函数聚焦 在C语言中,大部分函数都有一个返回值。但有时候,想 要创建的函数中并没有有用的信息需要返回。它更多只是做了一些 事,而不是计算出一个结果。通常情况下,函数都需要包含一条 return语句,但只要把函数的返回类型声明为void,没有return 语句也无妨。 返回类型为void表 示这个函数不会返 回任何东西。 void complain() { puts("我真的不快乐"); } 因为是void函数,所以没有必要写 return语句。 在C语言中,关键字void意味着无所谓,一旦告诉C编译器你不关 心函数的返回值,函数就不需要有return语句。 C语言入门 这里没有 蠢问题 问 : 如果我创建了一个 void函数,是否就意味它一定 不能有return语句? 答 : 你还是可以包含 return语句,但编译器很可能 会产生一条警告消息。而且在 void函数中包含return语句没 有任何意义。① 问 : 真的吗?为什么没有 意义? 答 : 因为如果你试图读取 void函数的值,编译器会报错。 链式赋值 在C语言中,几乎每样东 西都有返回值,不仅仅是 赋值表达 式“x = 4” 的值是4。 所以现在y也设为了4。 y = (x = 4); 函 数 调 用 , 就 连 赋 值 表 这行代码同时将x 和y 的值设为了4。事 达式也有返回值。例如下面这条语句: 实上,可以去掉括号,缩短代码的长度: x = 4; y = x = 4; 它 把 数 字 4 赋 值 给 变 量 。 有 趣 的 是 表 达 你经常会在需要给多个变量赋相同值的代码 式“x = 4 ”本身也有一个值,这个值是4, 中看到链式赋值。 即赋给x的值。为什么说这个东西很有用 呢?因为你可以用它来做一些很酷的事 情,比如把多条赋值语句链在一起写: ① 在void函数中的return语句有时可以用来提前退出函数。——译者注 你现在的位置 4   33 hfc_01-cs3.indd 33 2013.7.26 2:11:12 PM 弄乱的消息 弄乱的 消息 下面列出了一个C语言的小程序。程序少了一块代码,你的任务是将候选代 码块(左)和它对应的输出结果进行配对。有的输出结果可能一次都用不 到,有的可能用到好几次。用直线把候选代码块和它所对应的命令行输出 连接起来。 #include int main() { int x = 0; int y = 0; while (x < 5) { 候选代码放在这里。 printf("%i%i ", x, y); x = x + 1; } return 0; } 候选项: y = x - y; 可能的输出结果: 22 46 y = y + x; 将每一个候选项 和可能的输出结 果配对。 y = y + 2; if (y > 4) y = y - 1; x = x + 1; y = y + x; 11 34 59 02 14 26 38 02 14 36 48 00 11 21 32 42 if (y < 5) { x = x + 1; if (y < 3) x = x - 1; } y = y + 2; 11 21 32 42 53 00 11 23 36 410 02 14 25 36 47 34   第1章 hfc_01-cs3.indd 34 2013.7.26 2:11:12 PM C语言入门 既然你已经知道了怎么创建while循环,请修改程序让它在游戏期间保持计数。每 发一张牌就显示一次计数,如果玩家输入X就终止程序,如果玩家输入了错误的值 (如11或24)就显示错误消息。 #include #include int main() { char card_name[3]; int count = 0; do { puts("输入牌名: "); scanf("%2s", card_name); int val = 0; switch(card_name[0]) { case 'K': case 'Q': case 'J': val = 10; break; case 'A': val = 11; break; case 'X': 你将在这里做什么? default: 如果val不在1到10之间,就显示 val = atoi(card_name); 一条错误消息。你还应该跳过循 环体的其余部分,然后再试一次。 } if ((val > 2) && (val < 7)) { 计数加1。 count++; } else if (val == 10) { 计数减1。 count--; } printf("当前的计数: %i\n", count); } while ( ); 如果用户输入了X,就停止程序。 return 0; } 你现在的位置 4   35 hfc_01-cs3.indd 35 2013.7.26 2:11:12 PM 消息归位 弄乱的 消息解答 下面列出了一个C语言的小程序。程序少了一块代码,你的任务是将候选代 码块(左)和它对应的输出结果进行配对。有的输出结果可能一次都用不 到,有的可能用到好几次。请用直线把候选代码块和它所对应的命令行输 出连接起来。 #include int main() { int x = 0; int y = 0; while (x < 5) { 候选代码放在这里。 printf("%i%i ", x, y); x = x + 1; } return 0; } 候选项: y = x - y; 可能的输出结果: 22 46 y = y + x; y = y + 2; if (y > 4) y = y - 1; x = x + 1; y = y + x; 11 34 59 02 14 26 38 02 14 36 48 00 11 21 32 42 if (y < 5) { x = x + 1; if (y < 3) x = x - 1; } y = y + 2; 36   第1章 11 21 32 42 53 00 11 23 36 410 02 14 25 36 47 hfc_01-cs3.indd 36 2013.7.26 2:11:13 PM C语言入门 既然你已经知道了怎么创建while循环,请修改程序让它在游戏期间保持计数。每 发一张牌就显示一次计数,如果玩家输入X就终止程序,如果玩家输入了错误的值 (如11或24)就显示错误消息。 #include #include int main() { char card_name[3]; int count = 0; do { puts("输入牌名: "); scanf("%2s", card_name); int val = 0; switch(card_name[0]) { case 'K': case 'Q': case 'J': val = 10; break; case 'A': val = 11; break; case 'X': 在我这们里需用要b用recaokn不tin能u退e回出到循循环环,开因始为,我然们后现再在次位检于查sw循it环ch条语件句。中。 continue; default: 这只是这个条件的 一种写法。 在因这为个你地想方让还循需环要继一续个下c去on。tinue, } val = atoi(card_name); if ((val < 1) || (val > 10)) { puts(“我无法理解这个值!"); continue; } if ((val > 2) && (val < 7)) { count++; } else if (val == 10) { count--; } printf("当前的计数: %i\n", } while ( card_name[0] != ‘X’ count); ); 需要检查第一个字符是否是X。 return 0; } 你现在的位置 4   37 hfc_01-cs3.indd 37 2013.7.26 2:11:13 PM 试驾 试驾 既然算牌程序已经完成了,是时候带它出去兜兜风了,您意下如 何?觉得它能工作吗? 别忘了:如果在Windows中, 就不需要加“./”。 这条命令将编译 并运行程序。 现在我们就要 检查一下输入 是否正确了。 计数在增加! File Edit Window Help GoneLoopy > gcc card_counter.c -o card_counter && ./card_counter 输入牌名: 4 当前的计数: 1 输入牌名: K 当前的计数: 0 输入牌名: 3 当前的计数: 1 输入牌名: 5 当前的计数: 2 输入牌名: 23 我无法理解这个值! 输入牌名: 6 当前的计数: 3 输入牌名: 5 当前的计数: 4 输入牌名: 3 当前的计数: 5 当计数变得很大 输入牌名: 时,我就增加赌注。 X 我发财啦! 算牌程序工作了! 你已经完成了第一个C程序。借助C语言的语句、循环、条件的威力, 你已经创造了一个具有完整功能的算牌器。 干得好! 免责声明:用计算机算牌在很多州是犯法的,赌场那群家伙可不是好惹 的。所以千万别那么做,好吗? 38   第1章 hfc_01-cs3.indd 38 2013.7.26 2:11:14 PM C语言入门 这里没有 蠢问题 问 : 问: 问: C语言为什么需要编译? 什么是面向对象?我们在 为什么是“套装”?难道 其他一些语言就不需要编译,比如 本书中会学吗? 不止C语言一种吗? JavaScript,是吗? 答 : 答 : 面向对象是一种对抗软件 GNU编译器套装可以用来 答 : 为 了 让 代 码 执 行 起 来 更 复杂性的技术,我们在本书中不会做 编译很多语言,而C语言可能是人们 快,C语言需要编译。尽管有些语言 专门研究。 在应用gcc时使用最多的语言。 不是编译型语言,但它们中的一些, 问 : 问 : 像JavaScript和Python,为了提高速度 C语言为什么看起来很像 我能创建一个永无止尽的 通常会在幕后使用一些编译技术。 JavaScript、Java和C#等语言? 循环吗? 问: 答: 答 : C++是另一个版本的C语言 C语言的语法非常简洁,因 可以,如果循环条件的值 吗? 此影响了很多其他语言。 是1,循环就会永无止尽地运行下去。 答: 问: 问 : 不是,虽然C++的设计初衷 gcc这三个字母分别代表什 创建一个永无止尽的循环 是为了扩展C,但现在看来远不止如 么含义? 是个好主意吗? 答 : 此,人们最初创造C++和Objective-C 都是为了用C语言写面向对象的程序。 GNU编译器套装(GNU Compiler Collection)。 答 : 有时候是,通常在一些诸 如网络服务器的程序中会使用无限循 环(一个永无止尽的循环),程序会 反复地做一件事直到有人停止它。但 大部分的程序员使用循环是为了让它 们在某个时刻停止。 要点 ƒ 只要条件为真,while循环就会运行代 码。 ƒ do-while循环和while循环十分类 似,不过至少执行一次代码。 ƒ 某些循环用for来写更简洁。 ƒ 可以用break在任意时刻退出循环。 ƒ return语句会从函数返回一个值。 ƒ void函数不需要return语句。 ƒ 在C语言中,所有表达式都有值。 ƒ 赋值表达式有一个值,因此可以把它们 链在一起写(x = y = 0)。 ƒ 可以用continue随时跳到循环条件处。 你现在的位置 4   39 hfc_01-cs3.indd 39 2013.7.26 2:11:14 PM 第1章 C语言工具箱 C语言工具箱 你已经学完了第1章,C语言的基础知 识现在已经加入你的工具箱中了。关 于本书的提示工具条的完整列表,请见 附录ii。 简单的语 句就是命 令。 块语句被 {和}包围。 #外用的in部来代c代l输码ud码入)e将(输包如出含 进来。 如果条件为 真,if语句 就会运行代 码。 可以用&&和 ||把多个条件 组合在一起。 gcc是最流 行的C编译 器。 源文件的文 结件尾名。应该以.c c表ou示n计t+数+ 加1。 count--表 示计数减1。 s个效w变地itc量检h语的查句多了高种一 取值。 函个都每数m需个。ai要程n(一序) 需之要前在先运编行译C 程序。 成提运编用可功是行译&以。必&程之在操须序后命作编,马令符译前上行在-中输o指出定文了件。 只真会要,重条w复h件执il为e行就 代码。 do-while 至少执行一 次代码。 用for写循环 更简洁。 40   第1章 hfc_01-cs3.indd 40 2013.7.26 2:11:15 PM i 饭后甜点 十大遗漏知识点 看,我们还剩下那么 多好吃的…… 革命尚未成功,同志还需努力。 我们认为你还需要知道一些事,如果不讲,总觉得哪里不对劲,但我们又不希望这 本书重得只有大力士才提得动,所以我们只做简单介绍。在你放下这本书前,尽情 地享用这些“美味佳肴”吧。 hfc_app1.indd 539 这是附录  539 13-7-26 下午2:57 运算符 #1. 运算符 我们在本书中使用了一些运算符,例如基本的算术运算符+、-、 *和/。C语言中还有很多其他运算符,它们可以让你生活得更简单。 递增与递减 递增将数字加1,递减将数字减1。这两种运算在C代码中出镜率很 高,经常用来在循环中增减计数器的值,为此C语言提供了四个简 单的表达式来简化这两种运算: 递增1,返回新值。 ++i 递增1,返回旧值。 i++ 递减1,返回新值。 --i 递减1,返回旧值。 i-- 这些表达式都会改变i的值,++和--的位置决定了表达式返回 i的原始值还是新值,例如: int i = 3; int j = i++; 这行代码执行以后,j==3,i==4。 三目运算符 如果想在条件为真时返回某个值,而在条件为假时返回另一个 值,怎么做? if (x == 1) return 2; else return 3; C语言中有一个三目运算符可以把以上代码压缩成一行: return (x == 1) ? 2 : 3; 最后是条件为假时表达式的值 540  附录i 先是条件 然后是条件为真时表达式的值 hfc_app1.indd 540 13-7-26 下午2:57 饭后甜点 位运算 C语言可以用来编写底层代码,为此它提供了一组位运算符: 运算符 ~a a&b a|b a^b << >> 说明 a中所有位都取反 a中的位“与”b中的位 a中的位“或”b中的位 a中的位“异或”b中的位 位左移(值增加) 位右移(值减小) <<运算符可以用来快速地将某个整型值乘以2的幂,但小心 千万别溢出。 用逗号分割表达式 for循环在每次循环的末尾执行代码: for (i = 0; i < 10; i++) 每次循环的末尾都会出现递增操作。 但如果你想在循环末尾执行多个运算怎么办?可以使用逗号 运算符: for (i = 0; i < 10; i++, j++) 递增i和j。 之所以要有逗号运算符是因为有时你不想用分号来分割表达 式。 hfc_app1.indd 541 你现在的位置 4  541 13-7-26 下午2:57 预处理指令 #2. 预处理指令 每次你在编译一个包含头文件的程序时都使用了预处理指令: #include 预处理指令 预处理器会扫描C源文件然后生成一个修改过的版本,编译器 会使用这个修改后的文件编译程序。对#include这条指令来 说,预处理器会插入stdio.h文件的内容。指令总是出现在行首, 以 井 号 ( #) 字 符 开 头 。 除 了 #i n c lu d e, 用 得 最 多 的 指 令 就 是#define: #define DAYS_OF_THE_WEEK 7 ... printf("一星期有%i天\n", DAYS_OF_THE_WEEK); #define指令创建了一个宏,预处理器会扫描整个C源文件然 后把宏的名字替换为它的值。宏不是变量,因为它的值在运行 时无法改变。宏在程序编译前就被替换掉了,你甚至可以创建 功能类似函数的宏: x是宏的参数。 #define ADD_ONE(x) ((x) + 1) 要注意在宏中加括号。 ... printf("答案是 %i\n", ADD_ONE(3)); 将输出“答案是4”。 在程序编译前,预处理器会用((3) + 1)替换ADD_ONE(3)。 条件编译 你还可以用预处理器来实现条件编译。条件编译可以开、 关部分源代码: 如果SPANISH这个宏存在 #ifdef SPANISH char *greeting = "Hola"; ……就包含这段代码。. #else char *greeting = "Hello"; 否则就包含这段。 #endif SPANISH宏定义与否会改变这段代码的编译方式。 542  附录i hfc_app1.indd 542 13-7-26 下午2:57 #3. static关键字 想象你要创建一个带有计数功能的函数,可以这么写: int count = 0; 用来记录调用的次数 int counter() { return ++count; 每次都递增count } 这段代码有什么问题吗?它使用了一个叫count的全局变量。 因为count在全局作用域,所以其他函数可以修改它的值。如 果你在写一个大型程序,就需要小心控制全局变量的个数,因 为它们可能导致代码出错。好在C语言允许你创建只能在函数 局部作用域访问的全局变量: 虽然count是全局 变量,但它只能 在函数内部访问。 int counter() { static关键字表示将在两次 counter()函数调用期间保持该变 量的值。 static int count = 0; return ++count; } static关键字会把变量保存在存储器中的全局量区,但是当 其他函数试图访问count变量时编译器会抛出错误。 用static定义私有变量或函数 也可以在函数外使用static关键字,它表示“只有这个.c文件 中的代码能使用这个变量(或函数)”。例如: static int days = 365; 只能在这个源文件中使用这个变量。 只能在这个源文件 中使用这个函数。 static void update_account(int x) { ... } static关键字用来控制变量或函数的作用域,防止其他代码 以意想不到的方式访问你的数据或函数。 饭后甜点 你现在的位置 4  543 hfc_app1.indd 543 13-7-26 下午2:57 大小 #4. 数据类型的大小 你已经知道了怎么用sizeof运算符来查看数据类型在存储器中 的大小,但如果你想知道数据类保存的值的范围呢?例如,你 知道int在你的机器上占4字节,但int变量能保存的最大正数 和最小负数分别是多少呢?理论上可以通过它占用的字节数计 算出来,但这很麻烦。 为此可以使用定义在limits.h头文件中的宏。如果你想知道 long可以保存的最大值,可以使用LONG_MAX宏。short可以 保存的最小负数呢?用SHRT_MIN。下面这个例子显示了int 和short的范围: #include #include int main() { printf("On this machine an int takes up %lu bytes\n", sizeof(int)); printf("And ints can store values from %i to %i\n", INT_MIN, INT_MAX); printf("And shorts can store values from %i to %i\n", SHRT_MIN, SHRT_MAX); return 0; } File Edit Window Help HowBigIsBig On this machine an int takes up 4 bytes And ints can store values from -2147483648 to 2147483647 And shorts can store values from -32768 to 32767 宏 的 名 字 取 自 数 据 类 型 :I N T(i n t),S H R T(s h o r t),L O N G(l o n g ), CHAR(char),FLT(float)和DBL(double)。 你 可 以 在 它 们 后 面 加 上 _ M A X( 最 大 正 数 ) 或 _ M I N( 最 小 负 数 ) 。 如 果 想 查 看 更 具 体 的 数 据 类 型 , 还 可 以 加 上 前 缀 U(unsigned)、S(signed)或L(long)。 544  附录i hfc_app1.indd 544 13-7-26 下午2:57 #5.自动化测试 测试代码的重要性不言而喻,如果能把测试的过程自动化,生 活会变得更轻松。几乎所有程序员现在都在使用自动化测 试,C语言的测试框架也不胜枚举,而在Head First实验室最受 欢迎的是AceUnit: http://aceunit.sourceforge.net/projects/aceunit AceUnit和其他语言中的xUnit框架很像(比如NUnit和JUnit)。 如果你写的是命令行工具,用的是Unix的命令行,还有一个好 用的工具叫shunit2。 http://code.google.com/p/shunit2/ shunit2允许创建shell脚本来测试脚本和命令。 饭后甜点 hfc_app1.indd 545 你现在的位置 4  545 13-7-26 下午2:57 gcc #6. 再谈gcc 本书你都在用gcc,但只使用了gcc最基本的功能,其实它可 以做更多的事。gcc就像一把瑞士军刀,它有很多特性,利用 这些特性你可以严格控制它生成的代码。 优化 gcc为了提高代码的性能会做很多工作。如果gcc发现你在循 环中对一个变量赋了相同的值,它就会把赋值语句移动到循环 外;如果一个小函数只在少数几个地方用到了,gcc就会把它 转化为内联代码,然后插到程序中。 虽然gcc可以做很多优化,但绝大多数优化选项默认是关闭的。 为什么?因为优化需要花很长时间,如果你尚处于开发阶段, 通常希望快速编译代码。一旦准备发布代码,就可以打开优化 选项。gcc一共有四个级别的优化: gcc 标志 -O -O2 -O3 -Ofast 描述 如果在gcc命令中加上-O(字母O)标志,就能得到第一级别的优 化。 如果想提升优化等级,降低编译速度,就选择-O2。 如果想再升一级,就选-O3,它会使用-O和-O2中的所有优化,再 附加一些额外的优化。 -Ofast会打开最高级别的优化,同时编译速度也会降到最低。谨慎 使用-Ofast,因为它生成的代码可能和C标准相去甚远。 546  附录i hfc_app1.indd 546 13-7-26 下午2:57 警告 如果代码没有严重错误,但做了一些可疑的事情,比如把一个 类型的值赋给一个错误类型的变量,编译器就会显示警告。你 可以用-Wall选项提高警告检查的门槛: gcc fred.c -Wall -o fred -Wall选项表示“所有警告(All Warnings)”,但因为一些历 史原因,-Wall其实并不会显示所有的警告。如果你想让gcc 那么做,就必须加上-Wextra选项: gcc fred.c -Wall -Wextra -o fred 如果你希望遵循严格的编译,就可以使用-Werror选项,只要 有一个警告,编译就会失败: gcc fred.c -Werror -o fred 它表示“把警告当错误处理”。 当多人开发同一个项目时,-Werror就显得特别有用,因为它 可以维持代码的质量。 更多gcc选项请参阅: http://gcc.gnu.org/onlinedocs/gcc 饭后甜点 hfc_app1.indd 547 你现在的位置 4  547 13-7-26 下午2:57 make #7. 再谈make make是构建C程序的强大工具,但在本书中你只使用过一些 简单的命令。为了看到更多make神奇的功能,请阅读Robert Mecklenburg的《GNU Make项目管理》: http://shop.oreilly.com/product/9780596006105.do 这里先列举make的一些特性。 变量 变量可以大大缩短你的makefile,例如你想把一组标准的命令 行选项传给gcc,就可以把它们定义成变量: CFLAGS = -Wall -Wextra -v fred: fred.c gcc fred.c $(CFLAGS) -o fred 可以用等号(=)定义变量,然后用$(...)读取变量的值。 使用%、^和 @ 很多编译命令看起来都很像: fred: fred.c gcc fred.c -Wall -o fred 这时你可以用%符号写一条更通用的“目标/生成方法”: 假设你想根据<文件>.c创 建<文件>。 $^是依赖项的值(.c文 件)。 %: %.c gcc $^ -Wall -o $@ $@是目标的名字。 这些符号看起来有些奇怪。假设你想创建一个叫fred的文件, 这条规则会让make去寻找一个叫fred.c的文件,然后生成方法 会运行一条gcc命令,用依赖项(由特殊符号$^给出)创建目 标fred(由$@给出)。 548  附录i hfc_app1.indd 548 13-7-26 下午2:57 隐式规则 make工具对编译过程一清二楚,即使你不告诉它如何构建 文件,它也可以使用隐式规则自行构建。例如,你有一个 叫fred.c的文件,但没有makefile,可以用以下命令编译它: cc是gcc的另一 个名字。 File Edit Window Help MakeMyDay > make fred cc fred.c -o fred 即使我们不告诉make怎么 编译,它同样创建了编译 命令。 这是一条隐式规则。 原因是make内置了一批生成方法。关于make的更多信息,请 参见: http://www.gnu.org/software/make/ 饭后甜点 hfc_app1.indd 549 你现在的位置 4  549 13-7-26 下午2:57 开发工具 #8. 开发工具 当你在写C代码时,八成会对性能和稳定性有很高要求。如果 你用gcc编译代码,很有可能对以下这些GNU工具感兴趣: gdb gdb(GNU Project Debugger,GNU调试器)允许你在程序运 行期间研究它的代码。如果你想找出代码中隐蔽的错误,会发 现它特别有用。gd b既可以在命令行中使用,也可在Xcode或 Guile那样的IDE中使用。 http://sourceware.org/gdb/download/onlinedocs/gdb/index.html gprof 如果你的程序没有预期的那么快,就有必要分析一下它的性 能。gprof(GNU Profiler,GNU分析器)可以告诉你程序中 哪个部分是最慢的,这样你就能进行适当优化。gprof会修改 程序,修改后的程序在结束时会生成一份性能报告,然后你可 以用gprof命令行工具分析它,找到程序的瓶颈所在。 http://sourceware.org/binutils/docs/gprof gcov 还有一个分析工具叫gcov(GNU Coverage,GNU覆盖率测试 工具)。gprof用来检查你代码的性能,而gcov用来检查代码 中哪些部分运行了,哪些部分没运行。这在写自动化测试时特 别有用,因为你需要保证测试代码覆盖了所有你想覆盖的代码。 http://gcc.gnu.org/onlinedocs/gcc/Gcov.html 550  附录i hfc_app1.indd 550 13-7-26 下午2:57 #9. 创建GUI 你在本书前12章中都没有创建GUI程序,而在实验室中用 Allegro和OpenCV库写过两个显示简易窗口的程序。在不同 的操作系统中,GUI的创建方式有着天壤之别。 L i n u x——GT K Linux有很多库可以用来创建GUI程序,其中最有名的要属 GTK+(GIMP toolkit,GIMP工具包): http://www.gtk.org/ GTK+常用于Linux程序中,但你也可以在Windows和Mac中 使用它。 Windows Windows自带了十分高级的GUI库。Windows编程是非常专业 的领域,在你开始创建GUI程序前,可能需要花一点时间来 学习Windows API(Application Programming Interface,应用 程序编程接口)。越来越多Windows程序开始用基于C的语言 来开发,例如C#和C++。以下是Windows编程的在线介绍: http://www.winprog.org/tutorial/ M a c——C a r b o n 苹果的GUI系统叫Aqua。如果你想在Mac上用C语言写GUI程 序,可以用Carbon库,不过更时髦的方式是用Cocoa库,它需 要用C语言的另一个后代Objective-C来编程。现在你已经来到 了本书的终点,正是学习Objective-C的大好时机,Head First 实验室的人对“书呆子牧场”出品的Mac编程书籍和课程爱不 释手: http://www.bignerdranch.com/ hfc_app1.indd 551 饭后甜点 你现在的位置 4  551 13-7-26 下午2:57 参考资料 #10. 参考资料 以下是一些热门的C编程书籍和网站。 《C程序设计语言》 Brian W. Kernighan,Dennis M. Ritchie著 C语言的开山之作, C程序员应该人手一本。 《C语言参考手册》 Samuel P. Harbison,Guy L. Steele Jr.著 很好的C语言参考书,你在写代码时一定希望边上放着这本书。 《C专家编程》 Peter van der Linden著 如果你想了解更多高级C语言编程技巧就去看Peter van der Linden 的这部佳作。 《实用C语言编程》 Steve Oualline著 这本书列出了一些实用的C语言开发细节。 网站 关于C标准: http://pubs.opengroup.org/onlinepubs/9699919799/ 更多C教程: http://www.cprogramming.com/ 综合参考资料: http://www.cppreference.com/ 综合C编程教程: http://www.crasseux.com/books/ctutorial/ 552  附录i hfc_app1.indd 552 13-7-26 下午2:57 嗨翻C语言 编程语言/C 你能从这本书中学到什么? 你有没有想过可以轻松学习C语言?《嗨翻C语言》将会带给你一次这样的全新学习 体验。本书贯以有趣的故事情节、生动形象的图片,以及不拘一格、丰富多样的练 习和测试,时刻激励、吸引、启发你在解决问题的同时获取新的知识。你将在快乐 的气氛中学习语言基础、指针和指针运算、动态存储器管理等核心主题,以及多线 程和网络编程这些高级主题。在掌握语言的基本知识之后,你还将学习如何使用编 译器、make工具和其他知识来解决实际问题。 栈 深入理解计算机 的存储器 堆 共享变量 创建可以与外界 通信的进程 全局量 常量 LINK A6, #VARSIZE MOVEM.L DO-D7/A1-A5,-(SP) MOVE.L SP, SAVESTK(A6) MOVE.L SP, SAVEAS(A6) 代码 MOVE.L GRAFGLOBALS(A5),AO ... 学习如何避免 线程间的冲突 这本书有什么特别之处? 《嗨翻C语言》运用认知科学和学习理论的最新成果,精心为你打造了一次多感官的 学习体验,绝对能够嗨翻你的大脑,激发你的学习热情。它的特别之处是: ■ 用图片等可视化手段,提高学习效率; ■ 使用对话和有个性的叙述风格,讲故事而不是照本宣科; ■ 调动读者左右半脑和各种感官,让学习者思考得更深入; ■ 吸引并抓住读者的注意力,让学习新技术一点都不枯燥。 “《嗨翻C语言》可能很快就 会被证明是学习C语言的最 佳书籍。我觉得它会成为每 所大学C语言的标准教材。 很多编程书籍因循守旧。不 过这本书却使用了完全不同 的方式。它将教你如何成 为一名真正的C程序员。” ——Dave Kitabjian, NetCarrier Telecom 软件开发总监 “《嗨翻C语言》是一本用经 典‘Head First’的方式轻 松介绍C语言的教材。图片、 笑话、练习以及实践让读者 逐渐并稳固地掌握C语言的 基础知识……由此,读者可 以进入Posix和Linux系统编 程中更高级的技术殿堂。” ——Vince Milner, 软件工程师 封面设计:Karen Montgomery 张健 图灵社区:www.ituring.com.cn 新浪微博: @ 图灵教育 @ 图灵社区 反馈/投稿/推荐信箱:contact@turingbook.com 热线:(010)51095186转604 分类建议 计算机/编程语言/C语言 oreilly.com.cn headfirstlabs.com 人民邮电出版社网址:www.ptpress.com.cn O’Reilly Media, Inc.授权人民邮电出版社出版 ISBN 978-7-115-31884-8 定价:99.00元 此简体中文版仅限于中国大陆(不包含中国香港、澳门特别行政区和中国台湾地区)销售发行 This Authorized Edition for sale only in the territory of People's Republic of China (excluding Hong Kong, Macao and Taiwan) hf_c-ptp.indd 1
更多简介内容

评论

下载专区


TI最新应用解决方案

工业电子 汽车电子 个人电子
$(function(){ var appid = $(".select li a").data("channel"); $(".select li a").click(function(){ var appid = $(this).data("channel"); $('.select dt').html($(this).html()); $('#channel').val(appid); }) })