ITPub博客

首页 > 应用开发 > C/C++ > C语言入门经典(第5版)

C语言入门经典(第5版)

原创 C/C++ 作者:qinghuawenkang 时间:2018-10-24 16:52:15 0 删除 编辑

C 语言入门经典
(第 5 版)
[美] Ivor Horton 著
杨 浩 译
清华大学出版社
北 京

Ivor Horton
Beginning C, Fifth Edition
EISBN
978-1-4302-4881-1
Original English language edition published by Apress Media. Copyright © 2013 by Apress Media.
Simplified Chinese-Language edition copyright © 2013 by Tsinghua University Press. All rights
reserved.
本书中文简体字版由 Apress 出版公司授权清华大学出版社出版。未经出版者书面许可,不得以任何方
式复制或抄袭本书内容。
北京市版权局著作权合同登记号 图字: 01-2013-5119
本书封面贴有清华大学出版社防伪标签,无标签者不得销售。
版权所有,侵权必究。侵权举报电话:
010-62782989 13701121933
图书在版编目 (CIP) 数据
C 语言入门经典(第 5 版) / (美) 霍尔顿(Horton, I.) 著;杨浩 译. —北京:清华大学出版社, 2013
(2018.5 重印)
书名原文: Beginning C, Fifth Edition
ISBN 978-7-302-34341-7
Ⅰ. ①C… Ⅱ. ①霍… ②杨… Ⅲ. ①C 语言—程序设计 Ⅳ. ①TP312
中国版本图书馆 CIP 数据核字(2013)第 255156 号
责任编辑:王 军 于 平
装帧设计:牛艳敏
责任校对:邱晓玉
责任印制:沈 露
出版发行:清华大学出版社
网 址: ,
地 址:北京清华大学学研大厦 A 座 邮 编: 100084
社 总 机: 010-62770175 邮 购: 010-62786544
投稿与读者服务: 010-62776969, c-service@tup.tsinghua.edu.cn
质 量 反 馈: 010-62772015, zhiliang@tup.tsinghua.edu.cn
印 刷 者:清华大学印刷厂
装 订 者:三河市溧源装订厂
经 销:全国新华书店
开 本: 185mm×260mm 印 张: 37.75 字 数: 872 千字
版 次: 2013 年 11 月第 1 版 印 次: 2018 年 5 月第 18 次印刷
印 数: 62001~67000
定 价: 79.80 元
————————————————————————————————————————————
产品编号: 052455-02

作 者 简 介
Ivor Horton
原本是一名数学家,因听闻信息技术工作轻松且收入丰厚而踏足其中。
尽管现实情况常常是工作辛苦且收入相对一般, 但他至今依然坚持从事计算机工作。 Ivor
在不同的时期从事过各种类型的工作,包括程序设计、系统设计、咨询顾问以及管理和
实现一些颇为复杂的项目。 Ivor 对于将计算机系统设计和实现应用在各种行业工程设计
和运营管理方面有着十分丰富的经验。他能够运用多种编程语言开发特定用途的应用程
序,同时还为科研人员和工程人员提供教学,以帮助他们完成这类工作。多年来,他一
直撰写编程方面的书籍,近期作品包括 C、 C++和 Java 教程。在写书与指导他人之余,
一般他会选择钓鱼、旅行和享受生活。

技术审稿人简介
Marc Gregoire
是一名来自比利时的软件工程师。他毕业于比利时天主教鲁汶大
学(Catholic University of Leuven),并拥有该校的计算机工程学硕士学位。一年后,他以
优异成绩获得了同一所大学的人工智能专业硕士学位。毕业后, Marc 开始在一家名为
Ordina Belgium 的软件咨询公司工作。作为一名咨询师,他主要为西门子及诺基亚西门
子网络公司提供服务, 工作内容包括帮助电信运营商在 Solaris 上运行关键性的 2G 与 3G
软件。这项工作需要身处国际范围的团队(跨度从南美地区和美国到欧洲、中东地区、非
洲以及亚洲)中完成。现在, Marc 在 Nikon Metrology 从事 3D 激光扫描软件开发工作。
Marc 在 C/C++方面经验丰富,尤其精通 Microsoft VC++和 MFC 框架。此外, Marc
也喜爱 C#,并使用 PHP 创建网页。除了主要对 Windows 开发感兴趣之外, Marc 还对开
发 Linux 平台上 24/7 全天候运行的 C++程序颇有心得(例如, EIB 家庭自动化软件)。
自 2007 年 4 月起,Marc 就因他在 Visual C++方面的丰富经验而屡屡荣获每年的 Microsoft
的年度 MVP(Most Valuable Professional,最有价值专家)奖。
Marc 是比利时 C++用户群组()的创始人,并经常以 Marc G 的代号活
跃在 CodeGuru 论坛中。他还创建了一些免费软件和共享软件,并发布在了他的网站
上。此外, Marc 还在 /blog/上维护自己的博客。

致 谢
作者只不过是一个大型团队中将书复印成册之人。我想感谢整个 Apress 编辑部与产
品组自始至终的帮助与支持。我要感谢 Jonathan Gennick 对于启动本书新版本所做的努力,
感谢 Jill Balzano 在整个编辑过程中耐心地帮助解决我遇到的各种难题。
我还要感谢我的技术编辑 Marc Gregoire,感谢他帮助审核文字和检查所有的代码片
段与示例。他找错的本领实在了得,他的许多建设性的评论与深思熟虑的建议无疑使本
书变成了一本更好的教程。

前 言
欢迎使用《C 语言入门经典(第 5 版)》。研读本书,你就可以成为一位称职的 C 语言
程序员。从许多方面来说, C 语言都是学习程序设计的理想起步语言。 C 语言很简洁,
因此无须学习大量的语法便能够开始编写真正的应用程序。除了简明易学以外,它还是
一门功能非常强大的语言,并被专业人士广泛应用在各种领域。 C 语言的强大之处主要
体现在,它能够应用于各类层次的开发中,从设备驱动程序和操作系统组件到大规模应
用程序,它都能胜任。此外, C 语言还可以适用于相对较新的手机应用程序开发上。
几乎所有计算机都包含 C 语言编译器,因此,当你学会了 C 语言,就可以在任何环
境下进行编程。 最后一点, 掌握 C 语言可以为理解面向对象的 C++语言奠定良好的基础。
在作者眼中,有抱负的程序员必将面对三重障碍,即掌握遍布程序设计语言中的各
类术语、理解如何使用语言元素(而不仅仅只是知道它们的概念)以及领会如何在实际场
景中应用该语言。本书的目的就是将这些障碍降到最低限度。
术语是专业人士及优秀业余爱好者之间的交流必不可少的,因此有必要掌握它们。
本书将确保你理解这些术语,并自如地在各种环境下使用它们。这样才能更有效地使用
大多数软件产品附带的文档,且能轻松地阅读和学习大部分程序设计语言相关的著作。
理解语言元素的语法和作用固然是学习 C 语言过程中的一个重要部分,但认识语言
特性如何工作及应用也同等重要。本书不仅采用了代码片段,还在每个章节中使用一些
实际应用示例展示语言特性如何应用于特定的问题。这些示例提供了实践的基础,读者
可以通过改动代码观察修改后的结果。
理解特定背景下的程序设计不仅只是应用个别语言元素。为了帮助读者理解它们,
本书大部分章节之后都给出了一个较为复杂的应用程序,以应用本章之前学到的知识。
这些程序可以帮助你获得开发应用程序的能力与信心,了解如何联合以及更大范围地应
用语言元素。最重要的是,它们能让你了解设计实际应用程序与管理实际代码会碰到的
问题。
不管学习什么程序设计语言,有几件事情都要意识到。首先,虽然要学的东西很多,
但是掌握它们之后,你就会有极大的成就感。其次,学习的过程很有趣,你会深深地体
会到这点;第三,只有通过动手实践才能学会编程,这也是本书贯彻的思想。最后,在
学习的过程中,肯定会时不时犯许多错误和感到沮丧。当觉得自己完全停滞时,你要做
的就是坚持。最终你一定会体验到成功的喜悦,并且回头想想时,你会觉得它也并没有
你想象中的那么难。

如何使用本书
作者认为动手实践是学习编程最好的方法,很快你就会编写第一个程序了。每一章
都会有几个将理论应用于实践的程序,它们也是本书的核心所在。建议读者手工键入并
运行书中的示例,因为手工键入可以极大地帮助记忆语言元素。此外,你还应当尝试解
决每章末尾的所有练习题。当你第一次将一个程序运行成功,尤其是在解决自己的问题
后,你会感觉到很大的成就感和惊人的进步速度,那时你一定会觉得一切都挺值得。
刚开始,学习的进展不会太快,不过随着逐渐深入,你的学习速度会越来越快。每
一章都会涉及许多基础知识,因此在学习新的内容之前,需要花些时间确保理解前面学
习过的所有知识。实践各部分的代码,并尝试实现自己的想法,这是学习程序设计语言
的一个重要部分。尝试修改书中的程序,看看还能让它们做些什么,那才是有趣之处。
不要害怕尝试,如果某些地方不太明白,尝试输入一些变体,看看会出现什么情况。出
错并没什么大不了,你会从出错中学到很多知识。一个不错的方法是彻底通读每一章,
了解各章的范围,然后回过头来过一遍所有的示例。
你可能会觉得某些章末尾的练习题非常难。如果第一次没有完全搞明白,不用担心。
之所以第一次觉得困难是因为它们通常都是将你所学的知识应用到了相对复杂的问题
中。如果你实在觉得困难的话,可以略过它们继续学习下一章,然后再回头研究这些程
序。你甚至可以阅读完整本书再考虑它们。尽管如此,如果你能完成练习的话,说明你
取得了真正的进步。
本书读者对象
《C 语言入门经典(第 5 版)》的目的是教会读者如何尽可能简单快速地编写有用的
程序。在阅读完全书后,读者会彻底了解 C 语言编程。这本教程面向的是那些之前编过
一些程序,了解背后的概念,并且希望通过学习 C 语言进一步扩展知识的读者。尽管如
此,本书并未假设读者拥有先前的编程知识,因此如果你刚刚接触编程,本书依然是你
的不错选择。
使用本书的条件
要使用本书,你需要一台安装 C 编译器和库的计算机以执行书中的示例,以及一个
程序文本编译器用于创建源代码文件。你使用的编译器应支持目前 C 语言国际标准
(ISO/IEC 9899:2011,也被称为 C11)。你还需要一个用于创建和修改代码的编辑器,可以
采用纯文本编辑器(如记事本(Notepad)或 vi)创建源文件。不过,采用专为编辑 C 语言代
码设计的编辑器会更有帮助。
以下是作者推荐的两款 C 语言编译器,均为免费软件:
● GNU C 编译器, GCC,可从 下载,它支持多种不同的操作系统
环境。

● 面向 Microsoft Windows 的 Pelles C 编译器,可从
pellesc/下载,它提供了一个非常棒的集成开发环境(IDE)。
本书采用的约定
本书的文本和布局采用了许多不同的样式,以便区分各种不同的信息。大多数样式
表达的含义都很明显。程序代码样式如下:
int main(void)
{ printf("Beginning C\n");
return 0;
}
如果代码片段是从前面的实例修改而来,修改过的代码行就用粗体显示,如下所示:
i int main(void)
{
printf("Beginning C by Ivor Horton\n");
return 0;
}
当代码出现在文本中时,它的样式会有所不同,如: double
程序代码中还是用了各种“括号”。它们之间的差别非常重要,不同称呼。本书中称
()为圆括号, {}为大括号, []为方括号。

目 录
1 C 语言编程 .............................1
1.1 C 语言 .........................................1
1.2 标准库.........................................2
1.3 学习 C .........................................2
1.4 创建 C 程序 ................................2
1.4.1 编辑 ........................................ 2
1.4.2 编译 ........................................ 3
1.4.3 链接 ........................................ 4
1.4.4 执行 ........................................ 4
1.5 创建第一个程序.........................5
1.6 编辑第一个程序.........................5
1.7 处理错误.....................................6
1.8 剖析一个简单的程序.................7
1.8.1 注释 ........................................ 7
1.8.2 预处理指令 ............................ 8
1.8.3 定义 main()函数..................... 9
1.8.4 关键字 .................................. 10
1.8.5 函数体 .................................. 10
1.8.6 输出信息 .............................. 11
1.8.7 参数 ...................................... 11
1.8.8 控制符 .................................. 11
1.8.9 三字母序列 .......................... 13
1.9 预处理器...................................14
1.10 用 C 语言开发程序 ................14
1.10.1 了解问题 ............................ 14
1.10.2 详细设计 ............................ 15
1.10.3 实施 .................................... 15
1.10.4 测试 .................................... 15
1.11 函数及模块化编程 .................16
1.12 常见错误.................................19
1.13 要点.........................................19
1.14 小结.........................................20
1.15 习题.........................................20
2 章 编程初步 .............................. 21
2.1 计算机的内存 ...........................21
2.2 什么是变量 ...............................23
2.3 存储整数的变量 .......................24
2.3.1 变量的使用 .......................... 28
2.3.2 变量的初始化 ...................... 29
2.4 变量与内存 ...............................36
2.4.1 带符号的整数类型 .............. 36
2.4.2 无符号的整数类型 .............. 37
2.4.3 指定整数常量 ...................... 37
2.5 使用浮点数 ...............................39
2.6 浮点数变量 ...............................41
2.6.1 使用浮点数完成除法
运算 ......................................
42
2.6.2 控制输出中的小数位数 ...... 43
2.6.3 控制输出的字段宽度 .......... 43
2.7 较复杂的表达式 .......................44
2.8 定义命名常量 ...........................46
2.8.1 极限值 .................................. 49
2.8.2 sizeof 运算符........................ 51
2.9 选择正确的类型 .......................52
2.10 强制类型转换 .........................55
2.10.1 自动转换类型 .................... 56
2.10.2 隐式类型转换的规则 ........ 56
2.10.3 赋值语句中的隐式类型
转换 ......................................
57
2.11 再谈数值数据类型 .................58
2.11.1 字符类型 ............................ 58
2.11.2 字符的输入输出 ................ 59
2.11.3 枚举 .................................... 62
2.11.4 存储布尔值的变量 ............ 64
2.12 赋值操作的 op=形式..............65
2.13 数学函数 .................................66

2.14 设计一个程序.........................67
2.14.1 问题 .................................. 68
2.14.2 分析 .................................. 68
2.14.3 解决方案 .......................... 70
2.15 小结.........................................73
2.16 练习.........................................74
第 3 章 条件判断...............................75
3.1 判断过程...................................75
3.1.1 算术比较 .............................. 75
3.1.2 基本的 if 语句 ...................... 76
3.1.3 扩展 if 语句: if-else ............ 79
3.1.4 在 if 语句中使用代码块 ...... 82
3.1.5 嵌套的 if 语句 ...................... 83
3.1.6 测试字符 .............................. 85
3.1.7 逻辑运算符 .......................... 88
3.1.8 条件运算符 .......................... 91
3.1.9 运算符的优先级................... 94
3.2 多项选择问题...........................98
3.2.1 给多项选择使用 else-if
语句 ......................................
98
3.2.2 switch 语句 ........................... 99
3.2.3 goto 语句 ........................... 107
3.3 按位运算符.............................108
3.3.1 按位运算符的 op=用法 ..... 110
3.3.2 使用按位运算符................. 111
3.4 设计程序................................. 114
3.4.1 问题 .................................... 114
3.4.2 分析 .................................... 114
3.4.3 解决方案 ............................ 114
3.5 小结......................................... 118
3.6 练习......................................... 118
第 4 章 循环.................................... 119
4.1 循环......................................... 119
4.2 递增和递减运算符.................120
4.3 for 循环 ...................................120
4.4 for 循环的一般语法 ...............124
4.5 再谈递增和递减运算符.........125
4.5.1 递增运算符 ........................ 125
4.5.2 递增运算符的前置和后置
形式 ....................................
125
4.5.3 递减运算符 ........................ 126
4.6 再论 for 循环 ..........................127
4.6.1 修改 for 循环变量.............. 129
4.6.2 没有参数的 for 循环.......... 129
4.6.3 循环内的 break 语句.......... 130
4.6.4 使用 for 循环限制输入...... 132
4.6.5 生成伪随机整数 ................ 135
4.6.6 再谈循环控制选项 ............ 137
4.6.7 浮点类型的循环控制
变量 ....................................
137
4.7 while 循环 ...............................138
4.8 嵌套循环 .................................140
4.9 嵌套循环和 goto 语句 ............146
4.10 do-while 循环........................147
4.11 continue 语句.........................149
4.12 设计程序 ...............................150
4.12.1 问题 ............................... 150
4.12.2 分析 ............................... 150
4.12.3 解决方案 ....................... 151
4.13 小结 .......................................162
4.14 习题 .......................................163
第 5 章 数组 ................................... 165
5.1 数组简介 .................................165
5.1.1 不用数组的程序 ................ 165
5.1.2 什么是数组 ........................ 167
5.1.3 使用数组 ............................ 168
5.2 寻址运算符 .............................171
5.3 数组和地址 .............................173
5.4 数组的初始化 .........................174
5.5 确定数组的大小 .....................175
5.6 多维数组 .................................176
5.7 多维数组的初始化 .................178
5.8 变长数组 .................................184
5.9 设计一个程序 .........................186

5.9.1 问题 ...................................
186
5.9.2 分析 ................................... 186
5.9.3 解决方案 ........................... 187
5.10 小结.......................................193
5.11 习题.......................................193
第 6 章 字符串和文本的应用...........195
6.1 什么是字符串.........................195
6.2 存储字符串的变量.................197
字符串数组 ................................... 199
6.3 字符串操作.............................202
6.3.1 检查对 C11 的支持 ........... 202
6.3.2 确定字符串的长度............ 203
6.3.3 复制字符串 ....................... 204
6.3.4 连接字符串 ....................... 204
6.3.5 比较字符串 ....................... 208
6.3.6 搜索字符串 ........................ 211
6.3.7 单元化字符串.................... 215
6.3.8 将换行符读入字符串........ 219
6.4 分析和转换字符串.................221
6.4.1 转换字符的大小写形式.... 223
6.4.2 将字符串转换成数值........ 225
6.5 设计一个程序.........................227
6.5.1 问题 ................................... 227
6.5.2 分析 ................................... 227
6.5.3 解决方案 ........................... 228
6.6 小结.........................................233
6.7 习题.........................................233
第 7 章 指针....................................235
7.1 指针初探.................................235
7.1.1 声明指针 ........................... 236
7.1.2 通过指针访问值................ 237
7.1.3 使用指针 ........................... 240
7.1.4 指向常量的指针................ 244
7.1.5 常量指针 ........................... 244
7.1.6 指针的命名 ....................... 245
7.2 数组和指针.............................245
7.3 多维数组.................................248
7.3.1 多维数组和指针 ................ 252
7.3.2 访问数组元素 .................... 253
7.4 内存的使用 .............................256
7.4.1 动态内存分配: malloc()
函数 ....................................
256
7.4.2 释放动态分配的内存 ........ 257
7.4.3 用 calloc()函数分配
内存 ....................................
261
7.4.4 扩展动态分配的内存 ........ 262
7.5 使用指针处理字符串 .............265
7.5.1 使用指针数组 .................... 266
7.5.2 指针和数组记号 ................ 272
7.6 设计程序 .................................276
7.6.1 问题 .................................... 276
7.6.2 分析 .................................... 277
7.6.3 解决方案 ............................ 277
7.7 小结 .........................................284
7.8 习题 .........................................285
第 8 章 编程的结构 ........................ 287
8.1 程序的结构 .............................287
8.1.1 变量的作用域和生存期 .... 288
8.1.2 变量的作用域和函数 ........ 291
8.2 函数 .........................................291
8.2.1 定义函数 ............................ 291
8.2.2 return 语句.......................... 294
8.3 按值传递机制 .........................299
8.4 函数原型 .................................300
8.5 指针用作参数和返回值 .........301
8.5.1 常量参数 ............................ 302
8.5.2 返回指针的风险 ................ 307
8.6 小结 .........................................310
8.7 习题 .........................................310
第 9 章 函数再探 ............................ 313
9.1 函数指针 .................................313
9.1.1 声明函数指针 .................... 313
9.1.2 通过函数指针调用函数 .... 314
9.1.3 函数指针的数组 ................ 316
9.1.4 作为变元的函数指针........ 319
9.2 函数中的变量.........................321
9.2.1 静态变量:函数内部
的追踪 ...............................
321
9.2.2 在函数之间共享变量........ 323
9.3 调用自己的函数:递归.........325
9.4 变元个数可变的函数.............328
9.4.1 复制 va_list........................ 331
9.4.2 长度可变的变元列表
的基本规则 .......................
331
9.5 main()函数 ..............................332
9.6 结束程序.................................333
9.6.1 abort()函数......................... 333
9.6.2 exit()和 atexit()函数 .......... 333
9.6.3 _Exit()函数 ........................ 334
9.6.4 quick_exit()和 at_quick_exit()
函数 ...................................
334
9.7 提高性能.................................335
9.7.1 内联声明函数.................... 335
9.7.2 使用 restrict 关键字........... 335
9.7.3 _Noreturn 函数限定符 ...... 336
9.8 设计程序.................................336
9.8.1 问题 ................................... 336
9.8.2 分析 ................................... 337
9.8.3 解决方案 ........................... 338
9.9 小结.........................................351
9.10 习题.......................................352
第 10 章 基本输入和输出操作.........353
10.1 输入和输出流.......................353
10.2 标准流...................................354
10.3 键盘输入...............................354
10.3.1 格式化键盘输入 ........... 355
10.3.2 输入格式控制字符串 ... 355
10.3.3 输入格式字符串中
的字符 ...........................
360
10.3.4 输入浮点数的各种
变化 ...............................
362
10.3.5 读取十六进制和八进
制值................................
363
10.3.6 用 scanf_s()读取字符 .... 364
10.3.7 从键盘上输入字符串 .... 366
10.3.8 单个字符的键盘输入 .... 367
10.4 屏幕输出 ...............................372
10.4.1 使用 printf_s()的格式化
输出................................
372
10.4.2 转义序列........................ 375
10.4.3 整数输出........................ 375
10.4.4 输出浮点数.................... 378
10.4.5 字符输出........................ 379
10.5 其他输出函数 .......................380
10.5.1 屏幕的非格式化输出 .... 381
10.5.2 数组的格式化输出 ........ 381
10.5.3 数组的格式化输入 ........ 382
10.6 小结 .......................................382
10.7 习题 .......................................383
第 11 章 结构化数据....................... 385
11.1 数据结构:使用 struct..........385
11.1.1 定义结构类型和结构
变量................................
387
11.1.2 访问结构成员 ................ 388
11.1.3 未命名的结构 ................ 390
11.1.4 结构数组 ........................ 391
11.1.5 表达式中的结构成员 .... 393
11.1.6 结构指针 ........................ 393
11.1.7 为结构动态分配内存 .... 394
11.2 再探结构成员 .......................397
11.2.1 将一个结构作为另一个
结构的成员....................
397
11.2.2 声明结构中的结构 ........ 398
11.2.3 将结构指针用作结构
成员................................
399
11.2.4 双向链表 ........................ 403
11.2.5 结构中的位字段 ............ 406
11.3 结构与函数 ...........................407

11.3.1 结构作为函数的变元....
407
11.3.2 结构指针作为函数
变元 ...............................
408
11.3.3 作为函数返回值的
结构 ...............................
409
11.3.4 二叉树 ........................... 414
11.4 共享内存...............................421
11.5 设计程序...............................425
11.5.1 问题 ............................... 425
11.5.2 分析 ............................... 426
11.5.3 解决方案 ....................... 426
11.6 小结.......................................438
11.7 习题.......................................438
第 12 章 处理文件...........................441
12.1 文件的概念...........................441
12.1.1 文件中的位置 ............... 442
12.1.2 文件流 ........................... 442
12.2 文件访问...............................442
12.2.1 打开文件 ....................... 443
12.2.2 缓存文件操作 ............... 445
12.2.3 文件重命名 ................... 446
12.2.4 关闭文件 ....................... 447
12.2.5 删除文件 ....................... 447
12.3 写入文本文件.......................448
12.4 读取文本文件.......................449
12.5 在文本文件中读写
字符串 ..................................452
12.6 格式化文件的输入输出.......456
12.6.1 格式化文件输出 ........... 456
12.6.2 格式化文件输入 ........... 457
12.7 错误处理...............................459
12.8 再探文本文件操作模式.......460
12.9 freopen_s()函数 ....................461
12.10 二进制文件的输入输出.....462
12.10.1 以二进制模式打开
文件...........................
462
12.10.2 写入二进制文件 ....... 463
12.10.3 读取二进制文件........ 464
12.11 在文件中移动 .....................469
12.11.1 文件定位操作............ 469
12.11.2 找出我们在文件中
的位置........................
470
12.11.3 在文件中设定位置.... 471
12.12 使用临时文件 .....................477
12.12.1 创建临时文件............ 477
12.12.2 创建唯一的文件名.... 478
12.13 更新二进制文件 .................479
12.13.1 修改文件的内容........ 484
12.13.2 从键盘输入创建
记录 ...........................
485
12.13.3 将记录写入文件........ 486
12.13.4 从文件中读取记录.... 486
12.13.5 写入文件.................... 487
12.13.6 列出文件内容............ 488
12.13.7 更新已有的文件
内容 ...........................
489
12.14 文件打开模式小结 .............495
12.15 设计程序 .............................496
12.15.1 问题 ........................... 496
12.15.2 分析 ........................... 496
12.15.3 解决方案.................... 496
12.16 小结 .....................................501
12.17 习题 .....................................501
第 13 章 支持功能 .......................... 503
13.1 预处理 ...................................503
13.1.1 在程序中包含头文件 .... 503
13.1.2 定义自己的头文件 ........ 504
13.1.3 管理多个源文件 ............ 504
13.1.4 外部变量........................ 505
13.1.5 静态函数........................ 505
13.1.6 替换程序源代码 ............ 506
13.2 宏 ...........................................507
13.2.1 看起来像函数的宏 ........ 507
13.2.2 字符串作为宏参数 ........ 509

13.2.3 在宏展开式中结合两个
变元 ...............................
510
13.3 多行上的预处理器指令.......510
13.3.1 预处理器逻辑指令 ........ 511
13.3.2 条件编译 ........................ 511
13.3.3 测试多个条件 ............... 512
13.3.4 取消定义的标识符 ....... 512
13.3.5 测试标识符的指定值
的指令 ...........................
512
13.3.6 多项选择 ....................... 513
13.3.7 标准预处理宏 ............... 514
13.4 调试方法...............................515
13.4.1 集成的调试器 ............... 515
13.4.2 调试阶段的预处理器 ... 515
13.4.3 断言 ............................... 519
13.5 日期和时间函数...................521
13.5.1 获取时间值 ................... 522
13.5.2 获取日期 ....................... 525
13.5.3 确定某一天是星期几 ... 529
13.6 小结.......................................531
13.7 习题.......................................531
第 14 章 高级专用主题 ...................533
14.1 使用国际字符集...................533
14.1.1 理解 Unicode................. 533
14.1.2 设置区域 ....................... 534
14.1.3 宽字符类型 wchar_t ..... 535
14.1.4 宽字符串的操作 ........... 537
14.1.5 宽字符的文件流操作 .... 540
14.1.6 存储 Unicode 字符的
固定大小类型................
541
14.2 用于可移植性的专用整数
类型 ......................................545
14.2.1 固定宽度的整型 ............ 545
14.2.2 最小宽度的整型 ............ 545
14.2.3 最大宽度的整型 ............ 546
14.3 复数类型 ...............................546
14.3.1 复数基础........................ 546
14.3.2 复数类型和操作 ............ 547
14.4 用线程编程 ...........................550
14.4.1 创建线程........................ 550
14.4.2 退出线程........................ 551
14.4.3 把一个线程连接到另一个
线程上............................
552
14.4.4 挂起线程........................ 555
14.4.5 管理线程对数据
的访问............................
555
14.5 小结 .......................................561
附录 A 计算机中的数学知识........... 563
附录 B ASCII 字符代码定义 ........... 571
附录 C C 语言中的保留字 .............. 575
附录 D 输入输出格式指定符 .......... 577
附录 E 标准库头文件 ..................... 583


第 1 章
C 语言编程
C 语言是一种功能强大、简洁的计算机语言,通过它可以编写程序,指挥计算机完
成指定的任务。我们可以利用 C 语言创建程序(即一组指令),并让计算机依指令行事。
用 C 语言编程并不难, 本书将用浅显易懂的方法介绍 C 语言的基础知识, 读完本章,
读者就可以编写第一个 C 语言程序了,其实 C 语言很简单。
本章的主要内容:
● C 语言标准
● 标准库的概念
● 如何创建 C 程序
● 如何组织 C 程序
● 如何编写在屏幕上显示文本的程序
1.1 C 语言
C 是相当灵活的,用于执行计算机程序能完成的几乎所有任务,包括会计应用程序、
字处理程序、游戏、操作系统等。它不仅是更高级语言(如 C++)的基础,目前还以 Objective
C 的形式开发手机应用程序。 Objective C 是标准的 C 加上一小部分面向对象编程功能。
C 很容易学习,因为它很简洁。因此,如果你立志成为一名程序员,最好从 C 语言开始
学起,能快速而方便地获得编写实际应用程序的足够知识。
C 语言由一个国际标准定义,目前,其最新版本由 ISO/IEC 9899:2011 文档定义。当
前的标准一般称为 C11,本书介绍的语言遵循 C11 标准。但要知道, C11 定义的一些语
言元素是可选的。这表示, 遵循 C11 标准的 C 编译器可能没有实现该标准中的所有功能。
(编译器只是一个程序, 它可以把用我们能理解的术语所编写的程序转换为计算机能理解
的术语)。本书会标识出 C11 中的可选语言特性,这样读者就知道,自己的编译器可能不
支持它。
C11 编译器还有可能没有实现 C11 标准强制的所有语言特性。实现新语言功能是需
要时间的,所以编译器开发人员常常采用逐步接近的方式实现它们。这也是程序可能不
工作的另一个原因。尽管如此,根据我的经验, C 程序不能工作的最常见原因,至少有
99.9%的可能性是出现了错误。

1.2 标准库
C 的标准库也在 C11 标准中指定。标准库定义了编写 C 程序时常常需要的常量、符
号和函数。它还提供了基本 C 语言的一些可选扩展。取决于机器的特性,例如计算机的
输入输出,由标准库以不依赖机器的形式实现。这意味着,在 PC 中用 C 代码把数据写
入磁盘文件的方式,与在其他计算机上相同,尽管底层的硬件处理相当不同。库提供的
标准功能包括大多数程序员都可能需要的功能,例如处理文本字符串或数学计算,这样
就免除了自己实现这些功能所需的大量精力。
标准库在一系列标准文件——头文件中指定。头文件的扩展名总是.h。为了使一组标
准功能可用于 C 程序文件,只需要将对应的标准头文件包含进来,其方式在本章后面介
绍。我们编写的每个程序都会用到标准库。附录 E 汇总了构成标准库的头文件。
1.3 学习 C
如果对编程非常陌生,则不需要学习 C 的某些方面,至少在刚开始时不需要学习。
这些功能比较特殊,或者不大常用。本书把它们放在第 14 章,这里读者可以在熟悉其他
内容后,再学习它们。
所有示例的代码都可以从 Apress 网站()上下载,但建议读者自
己输入本书中的所有示例,即使它们非常简单,也要输入。自己亲自输入,以后就不容
易忘记。不要害怕用代码进行实验。犯错对编程而言非常有教育性。早期犯的错误越多,
学到的东西就越多。
1.4 创建 C 程序
C 程序的创建过程有 4 个基本步骤或过程:
● 编辑
● 编译
● 链接
● 执行
这些过程很容易完成(就像翻转手掌一样简单,而且可以随时翻转),首先介绍每个
过程,以及它们对创建 C 程序的作用。
1.4.1 编辑
编辑过程就是创建和修改 C 程序的源代码——我们编写的程序指令称为源代码。有
些 C 编译器带一个编辑器,可帮助管理程序。通常,编辑器是提供了编写、管理、开发
与测试程序的环境,有时也称为集成开发环境(Integrated Development Environment, IDE)。

也可以用一般的文本编辑器来创建源文件,但它们必须将代码保存为纯文本,而没
有嵌入附加的格式化数据。不要使用字处理器(例如微软的 Word),字处理器不适合编写
程序代码,因为它们在保存文本时,会附加一些格式化信息。一般来说,如果编译器系
统带有编辑器,就会提供很多更便于编写及组织程序的功能。它们通常会自动编排程序
文本的格式,并将重要的语言元素以高亮颜色显示,这样不仅让程序容易阅读,还容易
找到单词输入错误。
在 Linux 上,最常用的文本编辑器是 Vim 编辑器,也可以使用 GNU Emacs 编辑器。
对于 Microsoft Windows,可以使用许多免费(freeware)或共享(shareware)的程序设计编辑
器。这些软件提供了许多功能,例如,高亮显示特殊的语法及代码自动缩进等功能,帮
助确保代码是正确的。 Emacs 编辑器也有 Microsoft Windows 版本。 UNIX 环境的 Vi 和
VIM 编辑器也可用于 Windows,甚至可以使用 Notepad++()。
当然,也可以购买支持 C 语言的专业编程开发环境,例如微软或 Borland 的相关产
品,它们能大大提高代码编辑能力。不过,在付款之前,最好检查一下它们支持的 C 级
别是否符合当前的 C 语言标准 C11。因为现在很多编辑器产品主要面向 C++开发人员,
C 语言只是一个次要目标。
1.4.2 编译
编译器可以将源代码转换成机器语言,在编译的过程中,会找出并报告错误。这个
阶段的输入是在编辑期间产生的文件,常称为源文件。
编译器能找出程序中很多无效或无法识别的错误,以及结构错误,例如程序的某部
分永远不会执行。编译器的输出结果称为对象代码(object code),存放它们的文件称为对
象文件(object file),这些文件的扩展名在 Microsoft Windows 环境中通常是.obj,在
Linux/UNIX 环境中通常是.o。编译器可以在转换过程中找出几种不同类型的错误,它们
大都会阻止对象文件的创建。
如果编译成功,就会生成一个文件,它与源文件同名,但扩展名是.o 或者.obj。
如果在 UNIX 系统下工作,在命令行上编译 C 程序的标准命令是 cc(若编译器是
GNU’s Not UNIX(GNU),则命令为.gcc)。下面是一个示例:
cc -c myprog.c
其中, myporg.c 是要编译的程序,如果省略了–c 这个参数,程序还会自动链接。
成功编译的结果是生成一个对象文件。
大多数 C 编译器都有标准的编译选项,在命令行(如 cc myprog.c)或集成开发环境下
的菜单选项(Compile 菜单选项)里都可找到。在 IDE 中编译常常比使用命令行容易得多。
编译过程包括两个阶段。第一个阶段称为预处理阶段,在此期间会修改或添加代码,
第二个阶段是生成对象代码的实际编译过程。源文件可以包含预处理宏,它们用于添加
或修改 C 程序语句。如果现在不理解它们,不必担心,本书后面将进行详细论述。

1.4.3 链接
链接器(linker)将源代码文件中由编译器产生的各种对象模块组合起来,再从 C 语言
提供的程序库中添加必要的代码模块,将它们组合成一个可执行的文件。链接器也可以
检测和报告错误,例如,遗漏了程序的某个部分,或者引用了一个根本不存在的库组件。
实际上,如果程序太大,可将其拆成几个源代码文件,再用链接器连接起来。因为
很难一次编写一个很大的程序,也不可能只使用一个文件。如果将它拆成多个小源文件,
每个源文件提供程序的一部分功能,程序的开发就容易多了。这些源文件可以分别编译,
更容易避免简单输入错误的发生。再者,整个程序可以一点一点地开发,组成程序的源
文件通常会用同一个项目名称集成,这个项目名称用于引用整个程序。
程序库提供的例程可以执行非 C 语言的操作,从而支持和扩展了 C 语言。例如,库
中包含的例程支持输入、输出、计算平方根、比较两个字符串,或读取日期和时间信息
等操作。
链接阶段出现错误,意味着必须重新编辑源代码;反过来,如果链接成功,就会产
生一个可执行文件,但这并不一定表示程序能正常工作。在 Microsoft Windows 环境下,
这个可执行文件的扩展名为.exe;在 UNIX 环境下,没有扩展名,但它是一个可执行的
文件类型。多数 IDE 也有 Build(建立)选项,它可一次完成程序的编译和链接。
1.4.4 执行
执行阶段就是当成功完成了前述 3 个
过程后,运行程序。但是,这个阶段可能
会出现各种错误,包括输出错误及什么也
不做,甚至使计算机崩溃。不管出现哪种
情况,都必须返回编辑阶段,检查并修改
源代码。
在这个阶段,计算机最终会精确地执
行指令。在 UNIX 和 Linux 下,只要键入编
译和链接后的文件名,即可执行程序。在
大多数 IDE 中,都有一个相应的菜单命令
来运行或者执行已编译的程序。这个 Run
命令或者 Execute 命令可能有自己的菜单,
也可能位于 Compile 菜单项下。在 Windows
环境中,运行程序的.exe 文件即可,这与运
行其他可执行程序一样。
在任何环境及任何语言中,开发程序
的编辑、编译、链接与执行这 4 个步骤都
是一样的。图 1-1 总结了创建 C 程序的各
个过程。
编辑
创建/修改
程序源代码
源文件
对象文件
可执行文件
成功!
是 错误?

执行
运行程序
是 错误?

错误?
链接
链接库等
编译
生成机器指令
否 否

图 1-1 创建和执行程序

1.5 创建第一个程序
本节先浏览一下创建 C 语言程序的流程,从输入代码到执行程序的所有 4 个步骤。
在这个阶段,若不了解所键入的代码信息,别担心,笔者会解释每一个步骤。
试试看:
C 程序示例
打开编辑器,输入下面的程序,请注意标点符号不要输错,第 4 行及最后一行的括
号是大括号{},而不是方括号[]或者圆括号()——这很重要。另外一定要键入斜杠(/),以
后也会用到反斜杠(\)。最后别忘了行末的分号(;)。
/* Program 1.1 Your Very First C Program - Displaying Hello World */
#include <stdio.h>
int main(void)
{
printf("Hello world! ");
return 0;
}
在输入了上面的源代码后,将程序保存为 hello.c。可以用任意名字替代 hello,但扩
展名必须是.c。这个扩展名在编写 C 程序时是一个通用约定,它表示文件的内容是 C 语
言源代码。大多数 C 编译器都要求源文件的扩展名是.c,否则编译器会拒绝处理它。
下面编译程序(如本章前面“编译”一节所述),链接所有必要的内容,创建一个可
执行程序(如本章前面“链接”一节所述)。编译和链接一般在一个操作中完成,通常称
为“构建操作”。源代码编译成功后,链接器就添加程序需要的标准库代码,为程序创建
一个可执行文件。
最后,执行程序。这有几种方式,在 Windows 环境下,一般只需要在 Windows Explorer
中双击.exe 文件,但最好打开一个命令行窗口,输入执行它的命令,因为在程序执行完
毕后,显示输出的窗口就会消失。在所有的操作系统环境上,都可以从命令行上运行程
序。只需要启动一个命令行会话,把当前目录改为包含程序可执行文件的目录,再输入
程序名,就可以执行它了。
如果没有出现错误,就大功告成了。这个程序会在屏幕上输出如下信息:
Hello world!
1.6 编辑第一个程序
我们可以修改程序,在屏幕上输出其他信息,例如可以将程序改成:
/*Program 1.2 Your Second C Program */
#include <stdio.h>
int main(void)

{
printf("\"If at first you don't succeed, try, try, try again!\"");
return 0;
}
这个版本的输出是:
"If at first you don't succeed try try try again! "
在要显示的文本中, \”序列称为转义序列(escape sequence)。文本中包含几个不同的
转义序列。 \”是在文本中包含双引号的特殊方式,因为双引号通常表示字符串的开头和
结尾。转义序列\”使双引号出现在输出的开头和结尾。如果不使用转义序列,不仅双引
号不会出现在输出中,而且程序不会编译。本章后面的“控制字符”一节将详细介绍转
义序列。
修改完源代码后,可以重新编译,链接后执行。反复练习,熟悉整个流程。
1.7 处理错误
犯错乃人之常情,没什么难为情的。幸好计算机一般不会出错,而且非常擅长于找
出我们犯的错误。编译器会列出在源代码中找到的一组错误信息(甚至比我们想象的多),
通常会指出有错误的语句。此时,我们必须返回编辑阶段,找出有错误的代码并更正。
有时一个错误会使后面本来正确的语句也出现错误。这多半是程序的其他部分引用
了错误语句定义的内容所造成的。当然,定义语句有错,但被定义的内容不一定有错。
下面看看源代码在程序中生成了一个错误时,会是什么样的情况。编辑第二个程序
示例,将 printf()行最后的分号去掉,如下所示:
/*Program 1.2 Your Second C Program */
#include <stdio.h)
int main(void)
{
printf("\"If at first you don't succeed, try, try, try again!\"")
return 0;
}
编译这个程序后,会看到错误信息,具体信息随编译器的不同而略有区别。下面是
一个比较常见的错误信息:
Syntax error : missing ';' before '}'
HELLO.C - 1 error(s), 0 warning(s)
编译器能精确地指出错误及其出处,在这里, printf()行的结尾处需要一个分号。在
开始编写程序时,可能有很多错误是简单的拼写错误造成的。还很容易忘了逗号、括号,
或按错了键。没关系,许多有经验的老手也常犯这种错误。
如前所述,有时一点小错误会造成大灾难,编译器会显示许多不同的错误信息。不
要被错误的数量吓倒,仔细看过每一个错误信息后,返回并改掉错误部分,不懂的先不

管它,然后再编译一次源文件,就会发现错误一次比一次少。
返回编辑器,重新输入分号,再编译,看看有没有其他错误,如果没有错误,程序
就可以执行了。
1.8 剖析一个简单的程序
编写并编译了第一个程序后,下面是另一个非常类似的例子,了解各行代码的作用:
/* Program 1.3 Another Simple C Program - Displaying a Quotation */
#include <stdio.h>
int main(void)
{
printf("Beware the Ides Of March!")

return 0;
}
这和第一个程序完全相同,这里把它作为练习,用编辑器输入这个示例,编译并执
行。若输入完全正确,会看到如下输出:
Beware the Ides Of March!
1.8.1 注释
上述示例的第一行代码如下:
/* Program 1.3 Another Simple C Program - Displaying a Quotation */
这不是程序代码,因为它没有告诉电脑执行操作,它只是一个注释,告诉阅读代码
的人,这个程序要做什么。位于/*和*/之间的任意文本都是注释。只要编译器在源文件中
找到/*,就忽略它后面的内容(即使其中的文本很像程序代码),一直到表示注释结束的*/
为止。 /*可以和*/放在同一行代码上,也可以放在不同的代码行上。如果忘记包含对应的
*/,编译器就会忽略/*后面的所有内容。下面使用一个注释说明代码的作者及版权所有:
/*
* Written by Ivor Horton
* Copyright 2012
*/
也可以修饰注释,使它们比较突出:
/******************************************* *
* This isavery important comment *
* so please read this. *
******************************************* * /
使用另一种记号,可以在代码行的末尾添加一个注释,如下所示:
printf("Beware the Ides of March!"); // This line displays a quotation
代码行上两个斜杠后面的所有内容都会被编译器忽略。这种形式的注释没有前一种
记号那么凌乱,尤其是在注释只占一行的情形下。
应养成给程序添加注释的习惯,当然程序也可以没有注释,但在编写较长的程序时,
可能会忘记这个程序的作用或工作方式。添加足够的注释,可确保日后自己(和其他程序
员)能理解程序的作用和工作方式。
下面给程序再添加一些注释:
/* Program 1.3 Another Simple C Program - Displaying a Quotation */
#include <stdio.h> // This is a preprocessor directive
int main(void) // This identifies the function main()
{ // This marks the beginning of main()
printf("Beware the Ides of March!"); // This line outputs a quotation
return 0; // This returns control to the operating system
} // This marks the end of main()
可以看出,使用注释是一种非常有效的方式,可以解释程序中要发生的事情。注释
可以放在程序中的任意位置,说明代码的一般作用,指定代码是如何工作的。
1.8.2 预处理指令
下面的代码行:

#include <stdio.h> // This is a preprocessor directive
严格说来,它不是可执行程序的一部分,但它很重要,事实上程序没有它是不执行
的。符号#表示这是一个预处理指令(preprocessing directive),告诉编译器在编译源代码之
前,要先执行一些操作。编译器在编译过程开始之前的预处理阶段处理这些指令。预处
理指令相当多,大多放于程序源文件的开头。
在这个例子中,编译器要将 stdio.h 文件的内容包含进来,这个文件称为头文件(header
file),因为它通常放在程序的开头处。在本例中,头文件定义了 C 标准库中一些函数的
信息,但一般情况下,头文件指定的信息应由编译器用于在程序中集成预定义函数或其
他全局对象,所以有时需要创建自己的头文件,以用于程序。本例要用到标准库中的
printf()函数,所以必须包含 stdio.h 头文件。 stdio.h 头文件包含了编译器理解 printf()以及
其他输入/输出函数所需要的信息。名称 stdio 是标准输入/输出(standard input/output)的缩
写。 C 语言中所有头文件的扩展名都是.h,本书的后面会用到其他头文件。
注意:
在一些系统中,头文件名是不区分大小写的,但在#include 指令里,这些文件名通
常是小写。
每个符合 C11 标准的 C 编译器都有一些标准的头文件。这些头文件主要包含了与 C
标准库函数相关的声明。所有符合该标准的 C 编译器都支持同一组标准库函数,有同一


组标准头文件,但一些编译器有额外的库函数,它们提供的功能一般是运行编译器的计
算机所专用的。
注意:
附录 E 列出了所有的标准头文件。
1.8.3 定义 main()函数
下面的 5 行指令定义了 main()函数:
int main(void) // This identifies the function main()
{ // This marks the beginning of main()
printf("Beware the Ides of March!"); // This line outputs a quotation
return 0; // This returns control to the operating system
} // This marks the end of main()
函数是两个括号之间执行某组操作的一段代码。每个 C 程序都由一个或多个函数组
成,每个 C 程序都必须有一个 main()函数——因为每个程序总是从这个函数开始执行。
因此假定创建、编译、链接了一个名为 progname.exe 的文件。执行它时,操作系统会执
行这个程序的 main()函数。
定义 main()函数的第一行代码如下:
int main(void) // This identifies the function main()
它定义了 main()函数的起始,注意这行代码的末尾没有分号。定义 main()函数的第
一行代码开头是一个关键字 int,它表示 main()函数的返回值的类型,关键字 int 表示 main()
函数返回一个整数值。执行完 main()函数后返回的整数值表示返回给操作系统的一个代
码,它表示程序的状态。在下面的语句中,指定了执行完 main()函数后要返回的值:
return 0; // This returns control to the operating system
这个 return 语句结束 main()函数的执行,把值 0 返回给操作系统。从 main()函数返
回 0 表示,程序正常终止,而返回非 0 值表示异常。换言之,在程序结束时,发生了不
应发生的事情。
紧跟在函数名 main 后的括号,带有函数 main()开始执行时传递给它的信息,在这个
例子里,括号内是 void,表示没有给函数 main()传递任何数据,后面会介绍如何将数据
传递给函数 main()或程序内的其他函数。
函数 main()可以调用其他函数,这些函数又可以调用其他函数。对于每个被调用的
函数,都可以在函数名后面的括号中给函数传递一些信息。在执行到函数体中的 return
语句时,就停止执行该函数,将控制权返回给调用函数(对于函数 main(),则将控制权返
回给操作系统)。一般函数会定义为有返回值或没有返回值。函数返回一个值时,该值总
是特定的类型。对于函数 main(),返回值的类型是 int,即整数。

1.8.4 关键字
在 C 语言中, 关键字是有特殊意义的字, 所以在程序中不能将关键字用于其他目的。
关键字也称为保留字。在前面的例子里, int 就是一个关键字, void 和 return 也是关键字。
C 语言有许多关键字,我们在学习 C 语言的过程中,将逐渐熟悉这些关键字。附录 C 列
出了完整的 C 语言关键字表。
1.8.5 函数体
main()函数的一般结构如图 l-2 所示:
main()函数的结构
函数头
指定函数定义的开头和函数名称
起始括号
定义函数体的开头
函数体
包含所有的语句,它们定义了函数
执行时要完成的任务
结束括号
定义函数体的结束

图 l-2 函数 main()的结构
函数体是在函数名称后面位于起始及结束两个大括号之间的代码块。它包含了定义
函数功能的所有语句。这个例子的 main()函数体非常简单,只有两个语句:
{ // This marks the beginning of main()
printf("Beware the Ides of March!"); // This line outputs a quotation
return 0; // This returns control to the operating system
} // This marks the end of main()
每个函数都必须有函数体,但函数体可以是空的,仅有起始及结束两个大括号,里
面没有任何语句,在这种情况下,这个函数什么也不做。
这样的函数有什么用?事实上,在开发一个包含很多函数的程序时,这种函数是非
常有用的。我们可以声明一些用来解决手头问题的空函数,确定需要完成的编程工作,
再为每个函数创建程序代码。这个方法有助于条理分明地、系统地建立程序。

注意:
程序 1.3 将大括号单独排为一行,并缩进大括号之间的代码。这么做可清楚地表示
括号框起来的语句块从哪里起始和结束。大括号之间的语句通常缩进两个或多个空格,
使大括号突出在前。这是个很好的编程格式,可以使语句块更容易阅读。
代码中的大括号可以用其他方式摆放。例如:
int main(void) {
printf("Beware the Ides of March!"); // This line outputs a quotation
return 0;
}
提示:
无论源代码采用什么方式摆放,都要一直采用这种方式,这很重要。
1.8.6 输出信息
例子中的 main()函数体包含了—个调用 printf()函数的语句:
printf("Beware the Ides of March!"); // This line outputs a quotation
printf()是—个标准的库函数,它将函数名后面引号内的信息输出到命令行上(实际上
是标准输出流,默认为命令行)。在这个例子中,调用这个函数会显示双引号内的一段警
示语:双引号内的字符串称为字符串字面量。注意这行代码用分号作为结尾。
1.8.7 参数
包含在函数名(如上面语句中的 printf()函数)后的圆括号内的项称为参数,它指定要
传送给函数的数据。当传送给函数的参数多于一个时,要用逗号分开。
在上面的例子中,函数的参数是双引号内的文本字符串。如果不喜欢例子中引号内
的文本,可以改用自己想输出的句子。例如,使用如下语句:
printf("Out, damned Spot! Out I say!");
修改源代码后,必须再次编译及链接程序,才可执行。
注意:
与 C 语言中所有可执行的语句一样, printf()行的末尾必须有分号(这与定义语句或指
令语句不同)。这是一个很容易犯的错误,尤其是初次使用 C 编程的人,老是忘了分号。
1.8.8 控制符
前面的程序可以改为输出两段句子。输入以下的代码:
// Program 1.4 Another Simple C Program - Displaying a Quotation
#include <stdio.h>

int main(void)
{
printf("My formula for success?\nRise early, work late, strike oil.\n");
return 0;
}
输出的结果是:
My formula for success?
Rise early, work late, strike oil.
在 printf()语句中,在文本的开头和第一句的后面,增加了字符\n,它是另一个转义序
列,代表换行符。这样输出光标就会移动到下一行,后续的输出就会显示在新行上。
反斜杠(\)在文本字符串里有特殊的意义,它表示转义序列的开始。反斜杠后面的字符
表示是哪种转义序列。对于\n, n 表示换行。还有其他许多转义序列。显然,反斜杠是有
特殊意义的,所以需要一种方式在字符串中指定反斜杠。为此,应使用两个反斜杠(\\)。
输入以下的程序:
// Program 1.5 Another Simple C Program - Displaying Great Quotations
#include <stdio.h>
int main(void)
{
printf("\"It is a wise father that knows his own child.\"\nShakespeare\n");
return 0;
}
输出的结果如下:
"It is a wise father that knows his own child."
Shakespeare
输出中包含双引号,因为在字符串中使用了双引号的转义序列。 Shakespeare 显示在
下一行,因为在\”的后面有\n 转义序列。
在输出字符串中使用转义序列\a 可以发出声音,说明发生了有趣或重要的事情。输
入以下的程序并执行:
// Program 1.6 A Simple C Program – Important
#include <stdio.h>
int main(void)
{
printf("Be careful!!\n\a");
return 0;
}
这个程序的输出如下所示且带有声音。仔细聆听,电脑的扬声器会发出鸣响。
Be careful!!
转义序列\a 表示发出鸣响。表 1-1 是转义序列表。
表 1-1 转义序列

转 义 序 列 说 明
\n 换行
\r 回车键
\b 退后一格
\f 换页
\t 水平制表符
\v 垂直制表符
\a 发出鸣响
\? 插入问号(?)
\" 插入双引号(")
\' 插入单引号(')
\\ 插入反斜杠(\)


试着在屏幕上显示多行文本,在该文本中插入空格。使用 \n 可以把文本放在多个行
上,使用\t 可以给文本加上空格。本书将大量使用这些转义序列。
1.8.9 三字母序列
一般可以直接在字符串中使用问号。 \?转义序列存在的唯一原因是,有 9 个特殊的
字母序列,称为三字母序列,这是包含三个字母的序列,分别表示#、 [、 ]、 \、 ^、 ~、 \、
{和}:
??=转换为# ??(转换为[ ??)转换为]
??/转换为\ ??<转换为{ ??>转换为}
??'转换为^ ??!转换为| ??-转换为~
在 International Organization for Standardization(ISO)不变的代码集中编写 C 代码时,
就需要它们,因为它没有这些字符。这可能不适用于你。可以完全不理会它们,除非希
望编写如下语句:
printf("What??!\n");
这个语句生成的输出如下:
What|
三字母序列??!会转换为|。为了获得希望的输出,需要把上述语句写成:
printf("What?\?!\n");
现在三字母序列不会出现,因为第二个问号用其转义序列指定。使用三字母序列时,
编译器会发出一个警告,因为通常是不应使用三字母序列的。

1.9 预处理器
上述示例介绍了如何使用预处理指令,把头文件的内容包含到源文件中。编译的预
处理阶段可以做的工作远不止此。除了指令之外,源文件还可以包含宏。宏是提供给预
处理器的指令,来添加或修改程序中的 C 语句。宏可以很简单,只定义一个符号,例如
INCHES_PER_FOOT,只要出现这个符号,就用 12 替代。其指令如下:
#define INCHES_PER_FOOT 12
在源文件中包含这个指令,则代码中只要出现 INCHES_PER_FOOT,就用 12 替代
它。例如:
printf("There are %d inches in a foot.\n", INCHES_PER_FOOT);
预处理后,这个语句变成:
printf("There are %d inches in a foot.\n", 12);
INCHES_PER_FOOT 不再出现,因为该符号被#define 指令中指定的字符串替代。对
于源文件中的每个符号实例,都会执行这个替代。
宏也可以很复杂,根据特定的条件把大量代码添加到源文件中。这里不进一步介绍。
第 13 章将详细讨论预处理器宏。在此之前我们会遇到一些宏,那时会解释它们。
1.10 C 语言开发程序
如果读者从未写过程序,对 C 语言开发程序的过程就不会很清楚,但它和我们日常
生活的许多事务是相同的,万事开头难。一般首先大致确定要实现的目标,接着把该目
标转变成比较准确的规范。有了这个规范后,就可以制订达到最终目标的一系列步骤了。
就好比光知道要盖房子是不够的,还得知道需要盖什么样的房子,它有多大,用什么材
料,要盖在哪里。这种详细规划也需要运用到编写程序上。下面介绍编写程序时需要完
成的基本步骤。房子的比喻是很有帮助的,因此就利用这个比喻。
1.10.1 了解问题
第一步是弄清楚要做什么。在不清楚应提供什么设施:多少间卧房、多少间浴室、
各房间多大等等之前就开始建造房子,会有不知所措之感。所有这些都会影响建造房子
所需的材料和工作量,从而影响整个房子的成本。一般来说,在满足需求和完成项目的
有限资金、人力及时间之间总会达成某种一致。
这和开发一个任意规模的程序是相同的。即使是很简单的问题,也必须知道有什么
输入,对输入该做什么处理,要输出什么,以及输出哪种格式。输入可以来自键盘,也
可以来自磁盘文件的数据,或来自电话或网络的信息。输出可以显示在屏幕上,或打印

出来,也可以是更新磁盘上的数据文件。
对于较复杂的程序,需要多了解程序的各个方面。清楚地定义程序要解决的问题,
对于理解制订最终方案所需的资源与努力,是绝对必要的一部分。好好考虑这些细节,
还可以确定项目是否切实可行。对于新项目缺乏精准、详细的规范,常常使项目所花的
时间和资金大大超出预算,因而中断项目的例子有很多。
1.10.2 详细设计
要建造房子,必须有详细的计划。这些计划能让建筑工人按图施工,并详细描述房
子如何建造——具体的尺寸、要使用的材料等。还需要确定何时完成什么工作。例如,
在砌墙之前要先挖地基,所以这个计划必须把工作分为可管理的单元,以便执行起来井
然有序。
写程序也是一样。首先将程序分解成许多定义清楚且互相独立的小单元,描述这些
独立单元相互沟通的方式,以及每个单元在执行时需要什么信息,从而开发出富有逻辑、
相互独立的单元。把大型程序编写为一个大单元肯定是不可行的。
1.10.3 实施
有了房子的详细设计,就可以开始工作了。每组建筑工人必须按照进度完成他们的
工作。在下一阶段开始前,必须先检查每个阶段是否正确完成。省略了这些检查,将可
能导致整栋房子倒塌。
当然,假使程序很大,可以一次编写—部分。一个部分完成后,再写下—部分。每
个部分都要基于详细的设计规范,在进行下一个部分之前,应尽可能详细地检查每个部
分的功能。这样,程序就会逐步完成预期的任务。
大型编程项目常常涉及一组程序员。项目应分成相当独立的单元,分配给程序员组
中的各个成员。这样就可以同时开发几个代码单元。如果代码单元要相互连接为一个整
体,就必须精确定义代码单元与程序其余部分之间的交互。
1.10.4 测试
房子完成了,还要进行许多测试:排水设备、水电设施、暖气等。任何部分都有可
能出问题,这些问题必须解决。这有时是一个反复的过程,一个地方的问题可能会造成
其他地方出问题。
这个机制与写程序是类似的。每个程序模块——组成程序的单元——都需要单独测
试。若它们工作不正常,就必须调试。调试(Debugging)是一个找出程序中的问题及更正
错误的过程。调试的由来有个说法,曾经有人在查找程序的错误时,使用计算机的电路
图来跟踪信息的来源及其处理方式,竟然发现计算机程序出现错误,是因为一只虫子在
电脑里,让里面的线路短路而发生的,后来, bug 这个词就成了程序错误的代名词。
对于简单的程序,通常只要检查代码,就可以找出错误。然而一般来说,调试过程

通常会使用调试器临时插入一些代码,确定在出错时会发生什么。这包括插入断点,当
暂停执行,检查代码中的值。还可以单步执行代码。如果没有调试器,就要加入额外的
程序代码,输出一些信息,来确定程序中事件的发生顺序,以及程序执行时生成的中间
值。在大型的程序里,还需要联合测试各个程序模块,因为各个模块或许能正常工作,
但并不保证它能和其他模块一起正常工作。在程序开发的这个阶段,有个专业术语叫集
成测试(integration testing)。
1.11 函数及模块化编程
到目前为止,“函数”这个词已出现过好几次了,如 main()、 printf()、函数体等。下
面将深入研究函数是什么,为什么它们那么重要。
大多数编程语言(包含 C 语言)都提供了一种方法,将程序切割成多个段,各段都可
以独立编写。在 C 语言中,这些段称为函数。一个函数的程序代码与其他函数是相互隔
绝的。函数与外界有一个特殊的接口,可将信息传进来,也可将函数产生的结果传出去。
这个接口在函数的第一行即在函数名的地方指定。
图 1-3 的简单程序例子由 4 个函数组成,用于分析棒球分数。
程序控制
棒球队及队员输入
棒球队及队员
的排名输出
棒球队及队员分析
棒球分数的模块分析

图 1-3 模块化编程
这 4 个函数都完成一个指定的、定义明确的工作。程序中操作的执行由一个模块
main()总体掌控。一个函数负责读入及检查输入数据,另一个函数进行分析。读入及分
析了数据后,第 4 个函数就输出球队及球员的排名。
将程序分割成多个易于管理的小单元,对编程是非常重要的,其理由如下:
● 可以单独编写和测试每个函数,大大简化了使整个程序运转起来的过程。
● 几个独立的小函数比一个大函数更容易处理和理解。
● 库就是供人使用的函数集。因为它们是事先写好,且经过测试,能正常工作,所
以可以放心地使用,无须细究它的代码细节。这就加快了开发程序的速度,因为

我们只需要关注自己的代码,这是 C 语言的一个基本组成部分。 C 语言中丰富
的函数库大大增强了 C 语言的能力。
● 也可以编写自己的函数库, 应用于自己感兴趣的程序类型。 如果发现经常编写某
个函数, 就可以编写它的通用版本, 以满足自己的需求, 并将它加入自己的库中。
以后需要用到这个函数时,就可使用它的库版本了。
● 在开发包含几千到几百万行代码的大型程序时,可以由一些程序设计团队来进
行,每个团队负责一个指定的函数子组,最后把它们组成完整的程序。
第 8 章将详细介绍 C 函数。 C 程序的结构在本质上就是函数的结构,本章的第一个
例子就用到一个标准的库函数 printf()。
注意:
在其他一些编程语言中,用术语“方法”表示自包含的代码单元。因此方法的含义
与函数相同。
试试看:将所学的知识用于实践
下面的例子将前面学到的知识用于实践。首先,看看下面的代码,检查自己是否理
解它的作用。然后输入这些代码,编译、链接并执行,看看会发生什么。
// Program 1.7 A longer program
#include <stdio.h> // Include the header file for input and output
int main(void)
{
printf("Hi there!\n\n\nThis program is a bit");
printf(" longer than the others.");
printf("\nBut really it's only more text.\n\n\n\a\a");
printf("Hey, wait a minute!! What was that???\n\n");
printf("\t1.\tA bird?\n");
printf("\t2.\tA plane?\n");
printf("\t3.\tA control character?\n");
printf("\n\t\t\b\bAnd how will this look when it prints out?\n\n");
return 0;
}
输出如下:
Hi there!
This program is a bit longer than the others.
But really it's only more text.
Hey, wait a minute!! What was that???
1. A bird?
2. A plane?
3. A control character?
And how will this look when it prints out?

代码的说明
这个程序看起来有点复杂,这只是因为括号内的文本字符串包含了许多转义序列。
每个文本字符串都由一对双引号括起来。但这个程序只是连续调用 printf()函数,说明屏
幕输出是由传送给 printf()函数的数据所控制。
本例通过预处理指令包含了标准库中的 stdio.h 文件:

#include <stdio.h> // Include the header file for input and output
这是一个预处理指令,因为它以符号#开头。 stdio.h 文件提供了使用 printf()函数所
需的定义。
然后,定义 main()函数头,指定它返回一个整数值:
int main(void)
括号中的 void 关键字表示不给 main()函数传递信息。下一行的大括号表示其下是函
数体:
{
下一行语句调用标准库函数 printf(),将“ Hi there!”输出到屏幕上,接着空两行,
输出“ This program is a bit”。
printf("Hi there!\n\n\nThis program is a bit");
空两行是由 3 个转义序列\n 生成的。转义序列\n 会把字符显示在新行上。第一个转
义序列\n 结束了包含“ Hi there!” 的行, 之后的两个转义序列\n 生成两个空行, 文本“ This
program is a bit”显示在第 4 行上。这行代码在屏幕上生成了 4 行输出。
下一个 printf()生成的输出跟在上一个 printf()输出的最后一个字符后面。下面的语句
输出文本“ longer than the others.”,其中的第一个字符是一个空白:
printf(" longer than the others.");
这个输出跟在上一个输出的后面,紧临 bit 中的 t。所以在文本的开头需要一个空格,否
则计算机就会显示“ This program is a bitlonger than the others.”,这不是我们希望的结果。
下一个语句在输出前会先换行,因为双引号中文本字符串的开头是\n:
printf("\nBut really itS only more text.\n\n\n\a\a");
显示完文本后会空两行(因为有 3 个\n 转义序列),然后发出两次鸣响。下一个屏幕
输出从空的第二行开始。
下一个输出语句如下:
printf("Hey, wait a minute!! What was that???\n\n");
输出文本后空一行。其后的输出在空的这行开始。


以下
3 行语句各插入一个制表符,显示一个数字后,再插入另一个制表符,之后是
一些文本,结束后换行。这样,输出更容易阅读:
printf("\t1.\tA bird?\n");
printf("\t2.\tA plane?\n");
printf("\t3.\tA control character?\n");
这几个语句会生成 3 行带编号的输出。
下一语句先输出一个换行符,所以在前面输出的后面是一个空行,然后输出两个制
表符和两个空格,接着退回两个空格,最后显示文本并换行:
printf("\n\t\t\b\bAnd how will this look when it prints out?\n\n");
函数体中的最后一个语句如下:
return 0;
这个语句结束 main() 的执行,把 0 返回给操作系统。
结束大括号表示函数体结束:
}
注意:
输出中制表符和退格的实际效果随编译器的不同而不同。
1.12 常见错误
错误是生活中的一部分。用 C 语言编写计算机程序时,必须用编译器将源代码转换
成机器码,所以必须用非常严格的规则控制使用
C 语言的方式。漏掉一个该有的逗点,
或添加不该有的分号,编译器都不会将程序转换成机器码。
即使实践了多年,程序中也很容易出现输入错误。这些错误可能在编译或链接程序
时找出。但有些错误可能使程序执行时,表面上看起来正常,却不定时地出错,这就需
要花很多时间来跟踪错误了。
当然,不是只有输入错误会带来问题,具体实施时也常常会发现问题。在处理程序
中复杂的判断结构时,很容易出现逻辑错误。从语言的观点看,程序是正确的,编译及
运行也正确,但得不到正确的结果。这类错误最难查找。
1.13 要点
温习第一个程序是个不错的方法,图 1-4 列出了重点。
这表示注释的开头
表示 main()返回一个整数值
这表示注释的结尾
//后面的所有内容都是注释,因此
编译器会忽略它们
是一个标准头文件,使用输入输
出函数如 printf()时需要包含它
这个大括号表示 main()函数体
的结尾
这个大括号表示 main()函数体
的开头
函数体是包含在最外层括号中
的所有代码
这个语句结束 main()的执行, 把
控制权返回给操作系统
//后面的所有内容都是注释,因
此编译器会忽略它们
Include 指令把外部文件的内容包
含到源文件中

图 1-4 简单程序的要素
1.14 小结
本章编写了几个 C 程序。我们学习了许多基础知识,本章的重点是介绍一些基本概
念,而不是详细探讨 C 程序语言。现在读者应该对编写、编译及链接程序很有信心了。
也许读者目前对如何构建 C 程序只有模糊的概念。以后学了更多的 C 语言知识,编写了
一些程序后,就会清楚明白了。
下一章将学习较复杂的内容,而不只是用 printf()输出文本。我们要处理信息,得到
更有趣的结果。另外, printf()不只是显示文本字符串,它还有其他用途。
1.15 习题
以下的习题能让读者测试本章所学的成果。如果有不懂的地方,可以翻看本章的内
容,还可以从 Apress 网站 的 Source Code/Download 部分下载答案,
但这应是最后一种方法。
习题 1.1 编写一个程序,用两个 printf()语句分别输出自己的名字及地址。
习题 1.2 将上一个练习改成所有的输出只用一个 printf()语句。
习题 1.3 编写一个程序,输出下列文本,格式如下所示:
"It's freezing in here," he said coldly.

购买地址:

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/26421423/viewspace-2217397/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
分享计算机前沿技术和国外计算机先进技术书籍。

注册时间:2011-11-08

  • 博文量
    73
  • 访问量
    119770