变量免费建论坛
免费建论坛 时间:2021-04-15 阅读:(
)
[美]IvorHorton著杨浩译清华大学出版社北京IvorHortonBeginningC,FifthEditionEISBN:978-1-4302-4881-1OriginalEnglishlanguageeditionpublishedbyApressMedia.
Copyright2013byApressMedia.
SimplifiedChinese-Languageeditioncopyright2013byTsinghuaUniversityPress.
Allrightsreserved.
本书中文简体字版由Apress出版公司授权清华大学出版社出版.
未经出版者书面许可,不得以任何方式复制或抄袭本书内容.
北京市版权局著作权合同登记号图字:01-2013-5119本书封面贴有清华大学出版社防伪标签,无标签者不得销售.
版权所有,侵权必究.
侵权举报电话:010-6278298913701121933图书在版编目(CIP)数据C语言入门经典(第5版)/(美)霍尔顿(Horton,I.
)著;杨浩译.
—北京:清华大学出版社,2013书名原文:BeginningC,FifthEditionISBN978-7-302-34341-7Ⅰ.
①C…Ⅱ.
①霍…②杨…Ⅲ.
①C语言—程序设计Ⅳ.
①TP312中国版本图书馆CIP数据核字(2013)第255156号责任编辑:王军于平装帧设计:牛艳敏责任校对:邱晓玉责任印制:出版发行:清华大学出版社网址:http://www.
tup.
com.
cn,http://www.
wqbook.
com地址:北京清华大学学研大厦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版印次:2013年11月第1次印刷印数:1~4000定价:69.
80元产品编号:作者简介IvorHorton原本是一名数学家,因听闻信息技术工作轻松且收入丰厚而踏足其中.
尽管现实情况常常是工作辛苦且收入相对一般,但他至今依然坚持从事计算机工作.
Ivor在不同的时期从事过各种类型的工作,包括程序设计、系统设计、咨询顾问以及管理和实现一些颇为复杂的项目.
Ivor对于将计算机系统设计和实现应用在各种行业工程设计和运营管理方面有着十分丰富的经验.
他能够运用多种编程语言开发特定用途的应用程序,同时还为科研人员和工程人员提供教学,以帮助他们完成这类工作.
多年来,他一直撰写编程方面的书籍,近期作品包括C、C++和Java教程.
在写书与指导他人之余,一般他会选择钓鱼、旅行和享受生活.
技术审稿人简介MarcGregoire是一名来自比利时的软件工程师.
他毕业于比利时天主教鲁汶大学(CatholicUniversityofLeuven),并拥有该校的计算机工程学硕士学位.
一年后,他以优异成绩获得了同一所大学的人工智能专业硕士学位.
毕业后,Marc开始在一家名为OrdinaBelgium的软件咨询公司工作.
作为一名咨询师,他主要为西门子及诺基亚西门子网络公司提供服务,工作内容包括帮助电信运营商在Solaris上运行关键性的2G与3G软件.
这项工作需要身处国际范围的团队(跨度从南美地区和美国到欧洲、中东地区、非洲以及亚洲)中完成.
现在,Marc在NikonMetrology从事3D激光扫描软件开发工作.
Marc在C/C++方面经验丰富,尤其精通MicrosoftVC++和MFC框架.
此外,Marc也喜爱C#,并使用PHP创建网页.
除了主要对Windows开发感兴趣之外,Marc还对开发Linux平台上24/7全天候运行的C++程序颇有心得(例如,EIB家庭自动化软件).
自2007年4月起,Marc就因他在VisualC++方面的丰富经验而屡屡荣获每年的Microsoft的年度MVP(MostValuableProfessional,最有价值专家)奖.
Marc是比利时C++用户群组(www.
becpp.
org)的创始人,并经常以MarcG的代号活跃在CodeGuru论坛中.
他还创建了一些免费软件和共享软件,并发布在了他的网站www.
nuonsoft.
com上.
此外,Marc还在www.
nuonsoft.
com/blog/上维护自己的博客.
致谢作者只不过是一个大型团队中将书复印成册之人.
我想感谢整个Apress编辑部与产品组自始至终的帮助与支持.
我要感谢JonathanGennick对于启动本书新版本所做的努力,感谢JillBalzano在整个编辑过程中耐心地帮助解决我遇到的各种难题.
我还要感谢我的技术编辑MarcGregoire,感谢他帮助审核文字和检查所有的代码片段与示例.
他找错的本领实在了得,他的许多建设性的评论与深思熟虑的建议无疑使本书变成了一本更好的教程.
前言欢迎使用《C语言入门经典(第5版)》.
研读本书,你就可以成为一位称职的C语言程序员.
从许多方面来说,C语言都是学习程序设计的理想起步语言.
C语言很简洁,因此无须学习大量的语法便能够开始编写真正的应用程序.
除了简明易学以外,它还是一门功能非常强大的语言,并被专业人士广泛应用在各种领域.
C语言的强大之处主要体现在,它能够应用于各类层次的开发中,从设备驱动程序和操作系统组件到大规模应用程序,它都能胜任.
此外,C语言还可以适用于相对较新的手机应用程序开发上.
几乎所有计算机都包含C语言编译器,因此,当你学会了C语言,就可以在任何环境下进行编程.
最后一点,掌握C语言可以为理解面向对象的C++语言奠定良好的基础.
在作者眼中,有抱负的程序员必将面对三重障碍,即掌握遍布程序设计语言中的各类术语、理解如何使用语言元素(而不仅仅只是知道它们的概念)以及领会如何在实际场景中应用该语言.
本书的目的就是将这些障碍降到最低限度.
术语是专业人士及优秀业余爱好者之间的交流必不可少的,因此有必要掌握它们.
本书将确保你理解这些术语,并自如地在各种环境下使用它们.
这样才能更有效地使用大多数软件产品附带的文档,且能轻松地阅读和学习大部分程序设计语言相关的著作.
理解语言元素的语法和作用固然是学习C语言过程中的一个重要部分,但认识语言特性如何工作及应用也同等重要.
本书不仅采用了代码片段,还在每个章节中使用一些实际应用示例展示语言特性如何应用于特定的问题.
这些示例提供了实践的基础,读者可以通过改动代码观察修改后的结果.
理解特定背景下的程序设计不仅只是应用个别语言元素.
为了帮助读者理解它们,本书大部分章节之后都给出了一个较为复杂的应用程序,以应用本章之前学到的知识.
这些程序可以帮助你获得开发应用程序的能力与信心,了解如何联合以及更大范围地应用语言元素.
最重要的是,它们能让你了解设计实际应用程序与管理实际代码会碰到的问题.
不管学习什么程序设计语言,有几件事情都要意识到.
首先,虽然要学的东西很多,但是掌握它们之后,你就会有极大的成就感.
其次,学习的过程很有趣,你会深深地体会到这点;第三,只有通过动手实践才能学会编程,这也是本书贯彻的思想.
最后,在学习的过程中,肯定会时不时犯许多错误和感到沮丧.
当觉得自己完全停滞时,你要做的就是坚持.
最终你一定会体验到成功的喜悦,并且回头想想时,你会觉得它也并没有你想象中的那么难.
C语言入门经典(第5版)X如何使用本书作者认为动手实践是学习编程最好的方法,很快你就会编写第一个程序了.
每一章都会有几个将理论应用于实践的程序,它们也是本书的核心所在.
建议读者手工键入并运行书中的示例,因为手工键入可以极大地帮助记忆语言元素.
此外,你还应当尝试解决每章末尾的所有练习题.
当你第一次将一个程序运行成功,尤其是在解决自己的问题后,你会感觉到很大的成就感和惊人的进步速度,那时你一定会觉得一切都挺值得.
刚开始,学习的进展不会太快,不过随着逐渐深入,你的学习速度会越来越快.
每一章都会涉及许多基础知识,因此在学习新的内容之前,需要花些时间确保理解前面学习过的所有知识.
实践各部分的代码,并尝试实现自己的想法,这是学习程序设计语言的一个重要部分.
尝试修改书中的程序,看看还能让它们做些什么,那才是有趣之处.
不要害怕尝试,如果某些地方不太明白,尝试输入一些变体,看看会出现什么情况.
出错并没什么大不了,你会从出错中学到很多知识.
一个不错的方法是彻底通读每一章,了解各章的范围,然后回过头来过一遍所有的示例.
你可能会觉得某些章末尾的练习题非常难.
如果第一次没有完全搞明白,不用担心.
之所以第一次觉得困难是因为它们通常都是将你所学的知识应用到了相对复杂的问题中.
如果你实在觉得困难的话,可以略过它们继续学习下一章,然后再回头研究这些程序.
你甚至可以阅读完整本书再考虑它们.
尽管如此,如果你能完成练习的话,说明你取得了真正的进步.
本书读者对象《C语言入门经典(第5版)》的目的是教会读者如何尽可能简单快速地编写有用的程序.
在阅读完全书后,读者会彻底了解C语言编程.
这本教程面向的是那些之前编过一些程序,了解背后的概念,并且希望通过学习C语言进一步扩展知识的读者.
尽管如此,本书并未假设读者拥有先前的编程知识,因此如果你刚刚接触编程,本书依然是你的不错选择.
使用本书的条件要使用本书,你需要一台安装C编译器和库的计算机以执行书中的示例,以及一个程序文本编译器用于创建源代码文件.
你使用的编译器应支持目前C语言国际标准(ISO/IEC9899:2011,也被称为C11).
你还需要一个用于创建和修改代码的编辑器,可以采用纯文本编辑器(如记事本(Notepad)或vi)创建源文件.
不过,采用专为编辑C语言代码设计的编辑器会更有帮助.
以下是作者推荐的两款C语言编译器,均为免费软件:GNUC编译器,GCC,可从http://www.
gnu.
org下载,它支持多种不同的操作系统环境.
前言XI面向MicrosoftWindows的PellesC编译器,可从http://www.
smorgasbordet.
com/pellesc/下载,它提供了一个非常棒的集成开发环境(IDE).
本书采用的约定本书的文本和布局采用了许多不同的样式,以便区分各种不同的信息.
大多数样式表达的含义都很明显.
程序代码样式如下:intmain(void){printf("BeginningC\n");return0;}如果代码片段是从前面的实例修改而来,修改过的代码行就用粗体显示,如下所示:iintmain(void){printf("BeginningCbyIvorHorton\n");return0;}当代码出现在文本中时,它的样式会有所不同,如:double.
程序代码中还是用了各种"括号".
它们之间的差别非常重要,不同称呼.
本书中称()为圆括号,{}为大括号,[]为方括号.
目录第1章C语言编程11.
1C语言.
11.
2标准库.
21.
3学习C21.
4创建C程序21.
4.
1编辑21.
4.
2编译31.
4.
3链接41.
4.
4执行41.
5创建第一个程序.
51.
6编辑第一个程序.
51.
7处理错误.
61.
8剖析一个简单的程序.
71.
8.
1注释71.
8.
2预处理指令81.
8.
3定义main()函数.
91.
8.
4关键字101.
8.
5函数体101.
8.
6输出信息111.
8.
7参数111.
8.
8控制符111.
8.
9三字母序列131.
9预处理器.
141.
10用C语言开发程序141.
10.
1了解问题141.
10.
2详细设计151.
10.
3实施151.
10.
4测试151.
11函数及模块化编程.
161.
12常见错误.
191.
13要点.
191.
14小结.
201.
15习题.
20第2章编程初步.
212.
1计算机的内存212.
2什么是变量232.
3存储整数的变量242.
3.
1变量的使用282.
3.
2变量的初始化292.
4变量与内存362.
4.
1带符号的整数类型362.
4.
2无符号的整数类型372.
4.
3指定整数常量372.
5使用浮点数392.
6浮点数变量412.
6.
1使用浮点数完成除法运算.
422.
6.
2控制输出中的小数位数.
.
.
.
.
.
432.
6.
3控制输出的字段宽度432.
7较复杂的表达式442.
8定义命名常量462.
8.
1极限值.
492.
8.
2sizeof运算符.
512.
9选择正确的类型522.
10强制类型转换552.
10.
1自动转换类型562.
10.
2隐式类型转换的规则.
.
.
.
.
.
562.
10.
3赋值语句中的隐式类型转换.
572.
11再谈数值数据类型582.
11.
1字符类型582.
11.
2字符的输入输出592.
11.
3枚举.
622.
11.
4存储布尔值的变量642.
12赋值操作的op=形式.
652.
13数学函数66C语言入门经典(第5版)XIV2.
14设计一个程序.
672.
14.
1问题682.
14.
2分析682.
14.
3解决方案702.
15小结.
732.
16练习.
74第3章条件判断.
753.
1判断过程.
753.
1.
1算术比较753.
1.
2基本的if语句763.
1.
3扩展if语句:if-else793.
1.
4在if语句中使用代码块.
.
.
.
.
.
823.
1.
5嵌套的if语句833.
1.
6测试字符853.
1.
7逻辑运算符883.
1.
8条件运算符913.
1.
9运算符的优先级.
943.
2多项选择问题.
983.
2.
1给多项选择使用else-if语句983.
2.
2switch语句.
993.
2.
3goto语句1073.
3按位运算符.
1083.
3.
1按位运算符的op=用法.
.
.
.
.
1103.
3.
2使用按位运算符.
1113.
4设计程序.
1143.
4.
1问题1143.
4.
2分析1143.
4.
3解决方案1143.
5小结.
1183.
6练习.
118第4章循环.
1194.
1循环.
1194.
2递增和递减运算符.
1204.
3for循环.
1204.
4for循环的一般语法1244.
5再谈递增和递减运算符.
1254.
5.
1递增运算符1254.
5.
2递增运算符的前置和后置形式.
1254.
5.
3递减运算符1264.
6再论for循环1274.
6.
1修改for循环变量.
1294.
6.
2没有参数的for循环.
1294.
6.
3循环内的break语句.
1304.
6.
4使用for循环限制输入.
.
.
.
.
.
1324.
6.
5生成伪随机整数1354.
6.
6再谈循环控制选项1374.
6.
7浮点类型的循环控制变量.
1374.
7while循环1384.
8嵌套循环.
1404.
9嵌套循环和goto语句.
1464.
10do-while循环.
1474.
11continue语句.
1494.
12设计程序1504.
12.
1问题1504.
12.
2分析1504.
12.
3解决方案1514.
13小结.
1624.
14习题.
163第5章数组1655.
1数组简介.
1655.
1.
1不用数组的程序1655.
1.
2什么是数组1675.
1.
3使用数组1685.
2寻址运算符1715.
3数组和地址1735.
4数组的初始化1745.
5确定数组的大小1755.
6多维数组.
1765.
7多维数组的初始化1785.
8变长数组.
1845.
9设计一个程序186目录XV5.
9.
1问题1865.
9.
2分析1865.
9.
3解决方案1875.
10小结.
1935.
11习题.
193第6章字符串和文本的应用.
1956.
1什么是字符串.
1956.
2存储字符串的变量.
1976.
3字符串操作.
2026.
3.
1检查对C11的支持2026.
3.
2确定字符串的长度.
2036.
3.
3复制字符串2046.
3.
4连接字符串2046.
3.
5比较字符串2086.
3.
6搜索字符串2116.
3.
7单元化字符串.
2156.
3.
8将换行符读入字符串.
2196.
4分析和转换字符串.
2216.
4.
1转换字符的大小写形式.
.
.
.
2236.
4.
2将字符串转换成数值.
2256.
5设计一个程序.
2276.
5.
1问题2276.
5.
2分析2276.
5.
3解决方案2286.
6小结.
2336.
7习题.
233第7章指针.
2357.
1指针初探.
2357.
1.
1声明指针2367.
1.
2通过指针访问值.
2377.
1.
3使用指针2407.
1.
4指向常量的指针.
2447.
1.
5常量指针2447.
1.
6指针的命名2457.
2数组和指针.
2457.
3多维数组.
2487.
3.
1多维数组和指针.
2527.
3.
2访问数组元素2537.
4内存的使用2567.
4.
1动态内存分配:malloc()函数.
2567.
4.
2释放动态分配的内存.
.
.
.
.
.
.
.
2577.
4.
3用calloc()函数分配内存.
2617.
4.
4扩展动态分配的内存.
.
.
.
.
.
.
.
2627.
5使用指针处理字符串2657.
5.
1使用指针数组2667.
5.
2指针和数组记号2727.
6设计程序.
2767.
6.
1问题.
2767.
6.
2分析.
2777.
6.
3解决方案2777.
7小结.
2847.
8习题.
285第8章编程的结构2878.
1程序的结构2878.
1.
1变量的作用域和生存期.
.
.
.
2888.
1.
2变量的作用域和函数.
.
.
.
.
.
.
.
2918.
2函数.
2918.
2.
1定义函数2918.
2.
2return语句.
2948.
3按值传递机制2998.
4函数原型.
3008.
5指针用作参数和返回值3018.
5.
1常量参数3028.
5.
2返回指针的风险3078.
6小结.
3108.
7习题.
310第9章函数再探.
3139.
1函数指针.
3139.
1.
1声明函数指针3139.
1.
2通过函数指针调用函数.
.
.
.
3149.
1.
3函数指针的数组3169.
1.
4作为变元的函数指针.
.
.
.
.
.
.
.
319C语言入门经典(第5版)XVI9.
2函数中的变量.
3219.
2.
1静态变量:函数内部的追踪3219.
2.
2在函数之间共享变量.
3239.
3调用自己的函数:递归.
3259.
4变元个数可变的函数.
3289.
4.
1复制va_list.
3319.
4.
2长度可变的变元列表的基本规则3319.
5main()函数3329.
6结束程序.
3339.
6.
1abort()函数.
3339.
6.
2exit()和atexit()函数.
3339.
6.
3_Exit()函数3349.
6.
4quick_exit()和at_quick_exit()函数3349.
7提高性能.
3359.
7.
1内联声明函数.
3359.
7.
2使用restrict关键字.
3359.
7.
3_Noreturn函数限定符.
.
.
.
.
.
3369.
8设计程序.
3369.
8.
1问题3369.
8.
2分析3379.
8.
3解决方案3389.
9小结.
3519.
10习题.
352第10章基本输入和输出操作.
35310.
1输入和输出流.
35310.
2标准流.
35410.
3键盘输入.
35410.
3.
1格式化键盘输入35510.
3.
2输入格式控制字符串.
35510.
3.
3输入格式字符串中的字符.
36010.
3.
4输入浮点数的各种变化.
36210.
3.
5读取十六进制和八进制值.
36310.
3.
6用scanf_s()读取字符.
.
.
.
36410.
3.
7从键盘上输入字符串.
.
.
.
36610.
3.
8单个字符的键盘输入.
.
.
.
36710.
4屏幕输出37210.
4.
1使用printf_s()的格式化输出.
37210.
4.
2转义序列.
37510.
4.
3整数输出.
37510.
4.
4输出浮点数.
37810.
4.
5字符输出.
37910.
5其他输出函数38010.
5.
1屏幕的非格式化输出.
.
.
.
38110.
5.
2数组的格式化输出.
.
.
.
.
.
.
.
38110.
5.
3数组的格式化输入.
.
.
.
.
.
.
.
38210.
6小结.
38210.
7习题.
383第11章结构化数据.
38511.
1数据结构:使用struct.
38511.
1.
1定义结构类型和结构变量.
38711.
1.
2访问结构成员38811.
1.
3未命名的结构39011.
1.
4结构数组.
39111.
1.
5表达式中的结构成员.
.
.
.
39311.
1.
6结构指针.
39311.
1.
7为结构动态分配内存.
.
.
.
39411.
2再探结构成员39711.
2.
1将一个结构作为另一个结构的成员.
39711.
2.
2声明结构中的结构.
.
.
.
.
.
.
.
39811.
2.
3将结构指针用作结构成员.
39911.
2.
4双向链表.
40311.
2.
5结构中的位字段40611.
3结构与函数407目录XVII11.
3.
1结构作为函数的变元.
.
.
.
40711.
3.
2结构指针作为函数变元.
40811.
3.
3作为函数返回值的结构.
40911.
3.
4二叉树41411.
4共享内存.
42111.
5设计程序.
42511.
5.
1问题42511.
5.
2分析42611.
5.
3解决方案42611.
6小结.
43811.
7习题.
438第12章处理文件.
44112.
1文件的概念.
44112.
1.
1文件中的位置44212.
1.
2文件流.
44212.
2文件访问.
44212.
2.
1打开文件44312.
2.
2缓存文件操作44512.
2.
3文件重命名44612.
2.
4关闭文件44712.
2.
5删除文件44712.
3写入文本文件.
44812.
4读取文本文件.
44912.
5在文本文件中读写字符串45212.
6格式化文件的输入输出.
.
.
.
.
.
.
45612.
6.
1格式化文件输出45612.
6.
2格式化文件输入45712.
7错误处理.
45912.
8再探文本文件操作模式.
.
.
.
.
.
.
46012.
9freopen_s()函数46112.
10二进制文件的输入输出.
.
.
.
.
46212.
10.
1以二进制模式打开文件.
46212.
10.
2写入二进制文件.
.
.
.
.
.
.
46312.
10.
3读取二进制文件.
.
.
.
.
.
.
.
46412.
11在文件中移动46912.
11.
1文件定位操作.
46912.
11.
2找出我们在文件中的位置47012.
11.
3在文件中设定位置.
.
.
.
47112.
12使用临时文件47712.
12.
1创建临时文件.
47712.
12.
2创建唯一的文件名.
.
.
.
47812.
13更新二进制文件47912.
13.
1修改文件的内容.
.
.
.
.
.
.
.
48412.
13.
2从键盘输入创建记录48512.
13.
3将记录写入文件.
.
.
.
.
.
.
.
48612.
13.
4从文件中读取记录.
.
.
.
48612.
13.
5写入文件.
48712.
13.
6列出文件内容.
48812.
13.
7更新已有的文件内容48912.
14文件打开模式小结49512.
15设计程序49612.
15.
1问题49612.
15.
2分析49612.
15.
3解决方案.
49612.
16小结.
50112.
17习题.
501第13章支持功能50313.
1预处理.
50313.
1.
1在程序中包含头文件.
.
.
.
50313.
1.
2定义自己的头文件.
.
.
.
.
.
.
.
50413.
1.
3管理多个源文件.
50413.
1.
4外部变量.
50513.
1.
5静态函数.
50513.
1.
6替换程序源代码.
50613.
2宏.
50713.
2.
1看起来像函数的宏.
.
.
.
.
.
.
.
50713.
2.
2字符串作为宏参数.
.
.
.
.
.
.
.
509C语言入门经典(第5版)XVIII13.
2.
3在宏展开式中结合两个变元.
51013.
3多行上的预处理器指令.
.
.
.
.
.
.
51013.
3.
1预处理器逻辑指令.
.
.
.
.
.
.
.
51113.
3.
2条件编译51113.
3.
3测试多个条件51213.
3.
4取消定义的标识符.
.
.
.
.
.
.
51213.
3.
5测试标识符的指定值的指令.
51213.
3.
6多项选择51313.
3.
7标准预处理宏51413.
4调试方法.
51513.
4.
1集成的调试器51513.
4.
2调试阶段的预处理器.
.
.
51513.
4.
3断言.
51913.
5日期和时间函数.
52113.
5.
1获取时间值52213.
5.
2获取日期52513.
5.
3确定某一天是星期几.
.
.
52913.
6小结.
53113.
7习题.
531第14章高级专用主题53314.
1使用国际字符集.
53314.
1.
1理解Unicode.
53314.
1.
2设置区域53414.
1.
3宽字符类型wchar_t.
.
.
.
.
53514.
1.
4宽字符串的操作53714.
1.
5宽字符的文件流操作.
.
.
.
54014.
1.
6存储Unicode字符的固定大小类型.
54114.
2用于可移植性的专用整数类型54514.
2.
1固定宽度的整型.
54514.
2.
2最小宽度的整型.
54514.
2.
3最大宽度的整型.
54614.
3复数类型54614.
3.
1复数基础.
54614.
3.
2复数类型和操作.
54714.
4用线程编程55014.
4.
1创建线程.
55014.
4.
2退出线程.
55114.
4.
3把一个线程连接到另一个线程上.
55214.
4.
4挂起线程.
55514.
4.
5管理线程对数据的访问.
55514.
5小结.
561附录A计算机中的数学知识.
563附录BASCII字符代码定义571附录CC语言中的保留字575附录D输入输出格式说明符577附录E标准库头文件583译者序C语言是经典的编程语言,凭借其自身简洁、灵活和功能强大等特点,自诞生以来就牢牢占据着流行编程语言的榜首.
C语言是强大的编程语言,可以进行从操作系统到设备驱动的各层次开发,可以应用在从大规模传统应用到新兴移动应用的各种领域.
C语言也是一门非常优秀的学习程序设计入门语言,其简洁性使得初学者无须学习太多语法就可以开始编写真正的应用程序,很多程序员的职业生涯就是从握手C语言开始的.
对于初学者来说,目前市面上介绍C语言入门的书籍太多了,可谓车载斗量,浩若繁星.
《C语言入门经典》则是众多C语言学习资源中的经典作品.
本书的作者是世界著名的计算机图书大师IvorHorton.
IvorHorton在IBM工作多年,具有丰富的实践经验,其著作曾帮助无数程序员步入编程的殿堂.
《C语言入门经典》作为IvorHorton的经典之作,一版再版,培养了一代又一代的程序员,对C语言的推广可谓是功不可没.
编程语言的学习十分枯燥,学习的过程也特别艰辛,但是学成之后所获得的成就感也是无与伦比的.
C语言的灵活性在很大程度上得益于指针,而指针也往往是让初学者头疼的地方,甚至成为一个梦魇.
我深知其学习的艰辛,对于自己在学习过程中的彷徨犹豫,挫折困顿,至今还历历在目.
《C语言入门经典》循序渐进、深入浅出的讲解,曾经让自己大惑不解的地方,从本书看来则是如此的理所当然,水到渠成.
恨自己没在初学之时,早点读到此书.
本书在前一版的基础上,最大的变化就是增加了一章:高级应用专题.
其中介绍了Unicode字符,可移植性的专用整数类型、复数类型以及线程编程.
而线程章节的出现极大地完善了前几版的C语言体系,可谓是一个巨大惊喜.
此外,本书在总结前几版的基础上对章节进行了更精确细微的调整,使内容在逻辑上更加合理,读起来更加流畅,更符合阅读习惯.
开篇增加了对C语言,标准库的介绍.
在第9章中增加了结束程序的详细讲解.
第10章基本的输入和输出操作中,使用新的scanf_s(),printf_s()代替了旧版的scanf()和printf().
在第12章中,引入了新的freopen_s()函数以及缓存文件的操作.
另外还有一些细微的调整与删除,使得本书更加紧凑与完美.
纸上得来终觉浅,绝知此事要躬行.
本书的作者深谙此理,在每章都有练习和习题供读者动手实践.
学习一门语言没有比动手实践更快、更好的方法了.
所以建议读者在每读完一章的时候亲自动手完成练习,而不是"读"过练习,如此方能成为理论和行动上的"巨人".
有人问大师,如何能技近乎道大师曰:读书,读好书,然后实践之.
万事无他,惟手熟尔!
在这里要感谢清华大学出版社的李阳和于平编辑,她们为本书的翻译投入了巨大的热情并付出了很多心血.
没有你们的帮助和鼓励,本书不可能顺利付梓.
对于这本经典之作,译者本着"诚惶诚恐"的态度,在翻译过程中力求"信、达、雅",但是鉴于译者水平有限,错误和失误在所难免,如有任何意见和建议,请不吝指正.
感激不尽!
本书全部章节由杨浩翻译,参与翻译活动的还有孔祥亮、陈跃华、杜思明、熊晓磊、曹汉鸣、陶晓云、王通、方峻、李小凤、曹晓松、蒋晓冬、邱培强、洪妍、李亮辉、高娟妮、曹小震、陈笑.
最后,希望读者通过阅读本书能早日步入C语言编程的殿堂,领略C语言之美!
第1章C语言编程C语言是一种功能强大、简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.
我们可以利用C语言创建程序(即一组指令),并让计算机依指令行事.
用C语言编程并不难,本书将用浅显易懂的方法介绍C语言的基础知识,读完本章,读者就可以编写第一个C语言程序了,其实C语言很简单.
本章的主要内容:C语言标准标准库的概念如何创建C程序如何组织C程序如何编写在屏幕上显示文本的程序1.
1C语言C是相当灵活的,用于执行计算机程序能完成的几乎所有任务,包括会计应用程序、字处理程序、游戏、操作系统等.
它不仅是更高级语言(如C++)的基础,目前还以ObjectiveC的形式开发手机应用程序.
ObjectiveC是标准的C加上一小部分面向对象编程功能.
C很容易学习,因为它很简洁.
因此,如果你立志成为一名程序员,最好从C语言开始学起,能快速而方便地获得编写实际应用程序的足够知识.
C语言由一个国际标准定义,目前,其最新版本由ISO/IEC9899:2011文档定义.
当前的标准一般称为C11,本书介绍的语言遵循C11标准.
但要知道,C11定义的一些语言元素是可选的.
这表示,遵循C11标准的C编译器可能没有实现该标准中的所有功能.
(编译器只是一个程序,它可以把用我们能理解的术语所编写的程序转换为计算机能理解的术语).
本书会标识出C11中的可选语言特性,这样读者就知道,自己的编译器可能不支持它.
C11编译器还有可能没有实现C11标准强制的所有语言特性.
实现新语言功能是需要时间的,所以编译器开发人员常常采用逐步接近的方式实现它们.
这也是程序可能不工作的另一个原因.
尽管如此,根据我的经验,C程序不能工作的最常见原因,至少有99.
9%的可能性是出现了错误.
C语言入门经典(第5版)21.
2标准库C的标准库也在C11标准中指定.
标准库定义了编写C程序时常常需要的常量、符号和函数.
它还提供了基本C语言的一些可选扩展.
取决于机器的特性,例如计算机的输入输出,由标准库以不依赖机器的形式实现.
这意味着,在PC中用C代码把数据写入磁盘文件的方式,与在其他计算机上相同,尽管底层的硬件处理相当不同.
库提供的标准功能包括大多数程序员都可能需要的功能,例如处理文本字符串或数学计算,这样就免除了自己实现这些功能所需的大量精力.
标准库在一系列标准文件——头文件中指定.
头文件的扩展名总是.
h.
为了使一组标准功能可用于C程序文件,只需要将对应的标准头文件包含进来,其方式在本章后面介绍.
我们编写的每个程序都会用到标准库.
附录E汇总了构成标准库的头文件.
1.
3学习C如果对编程非常陌生,则不需要学习C的某些方面,至少在刚开始时不需要学习.
这些功能比较特殊,或者不大常用.
本书把它们放在第14章,这里读者可以在熟悉其他内容后,再学习它们.
所有示例的代码都可以从Apress网站(http://www.
apress/com)上下载,但建议读者自己输入本书中的所有示例,即使它们非常简单,也要输入.
自己亲自输入,以后就不容易忘记.
不要害怕用代码进行实验.
犯错对编程而言非常有教育性.
早期犯的错误越多,学到的东西就越多.
1.
4创建C程序C程序的创建过程有4个基本步骤或过程:编辑编译链接执行这些过程很容易完成(就像翻转手掌一样简单,而且可以随时翻转),首先介绍每个过程,以及它们对创建C程序的作用.
1.
4.
1编辑编辑过程就是创建和修改C程序的源代码——我们编写的程序指令称为源代码.
有些C编译器带一个编辑器,可帮助管理程序.
通常,编辑器是提供了编写、管理、开发与测试程序的环境,有时也称为集成开发环境(IntegratedDevelopmentEnvironment,IDE).
第1章C语言编程3也可以用一般的文本编辑器来创建源文件,但它们必须将代码保存为纯文本,而没有嵌入附加的格式化数据.
不要使用字处理器(例如微软的Word),字处理器不适合编写程序代码,因为它们在保存文本时,会附加一些格式化信息.
一般来说,如果编译器系统带有编辑器,就会提供很多更便于编写及组织程序的功能.
它们通常会自动编排程序文本的格式,并将重要的语言元素以高亮颜色显示,这样不仅让程序容易阅读,还容易找到单词输入错误.
在Linux上,最常用的文本编辑器是Vim编辑器,也可以使用GNUEmacs编辑器.
对于MicrosoftWindows,可以使用许多免费(freeware)或共享(shareware)的程序设计编辑器.
这些软件提供了许多功能,例如,高亮显示特殊的语法及代码自动缩进等功能,帮助确保代码是正确的.
Emacs编辑器也有MicrosoftWindows版本.
UNIX环境的Vi和VIM编辑器也可用于Windows,甚至可以使用Notepad++(http://notepad-plus-plus.
org/).
当然,也可以购买支持C语言的专业编程开发环境,例如微软或Borland的相关产品,它们能大大提高代码编辑能力.
不过,在付款之前,最好检查一下它们支持的C级别是否符合当前的C语言标准C11.
因为现在很多编辑器产品主要面向C++开发人员,C语言只是一个次要目标.
1.
4.
2编译编译器可以将源代码转换成机器语言,在编译的过程中,会找出并报告错误.
这个阶段的输入是在编辑期间产生的文件,常称为源文件.
编译器能找出程序中很多无效或无法识别的错误,以及结构错误,例如程序的某部分永远不会执行.
编译器的输出结果称为对象代码(objectcode),存放它们的文件称为对象文件(objectfile),这些文件的扩展名在MicrosoftWindows环境中通常是.
obj,在Linux/UNIX环境中通常是.
o.
编译器可以在转换过程中找出几种不同类型的错误,它们大都会阻止对象文件的创建.
如果编译成功,就会生成一个文件,它与源文件同名,但扩展名是.
o或者.
obj.
如果在UNIX系统下工作,在命令行上编译C程序的标准命令是cc(若编译器是GNU'sNotUNIX(GNU),则命令为.
gcc).
下面是一个示例:cc-cmyprog.
c其中,myporg.
c是要编译的程序,如果省略了–c这个参数,程序还会自动链接.
成功编译的结果是生成一个对象文件.
大多数C编译器都有标准的编译选项,在命令行(如ccmyprog.
c)或集成开发环境下的菜单选项(Compile菜单选项)里都可找到.
在IDE中编译常常比使用命令行容易得多.
编译过程包括两个阶段.
第一个阶段称为预处理阶段,在此期间会修改或添加代码,第二个阶段是生成对象代码的实际编译过程.
源文件可以包含预处理宏,它们用于添加或修改C程序语句.
如果现在不理解它们,不必担心,本书后面将进行详细论述.
C语言入门经典(第5版)41.
4.
3链接链接器(linker)将源代码文件中由编译器产生的各种对象模块组合起来,再从C语言提供的程序库中添加必要的代码模块,将它们组合成一个可执行的文件.
链接器也可以检测和报告错误,例如,遗漏了程序的某个部分,或者引用了一个根本不存在的库组件.
实际上,如果程序太大,可将其拆成几个源代码文件,再用链接器连接起来.
因为很难一次编写一个很大的程序,也不可能只使用一个文件.
如果将它拆成多个小源文件,每个源文件提供程序的一部分功能,程序的开发就容易多了.
这些源文件可以分别编译,更容易避免简单输入错误的发生.
再者,整个程序可以一点一点地开发,组成程序的源文件通常会用同一个项目名称集成,这个项目名称用于引用整个程序.
程序库提供的例程可以执行非C语言的操作,从而支持和扩展了C语言.
例如,库中包含的例程支持输入、输出、计算平方根、比较两个字符串,或读取日期和时间信息等操作.
链接阶段出现错误,意味着必须重新编辑源代码;反过来,如果链接成功,就会产生一个可执行文件,但这并不一定表示程序能正常工作.
在MicrosoftWindows环境下,这个可执行文件的扩展名为.
exe;在UNIX环境下,没有扩展名,但它是一个可执行的文件类型.
多数IDE也有Build(建立)选项,它可一次完成程序的编译和链接.
1.
4.
4执行执行阶段就是当成功完成了前述3个过程后,运行程序.
但是,这个阶段可能会出现各种错误,包括输出错误及什么也不做,甚至使计算机崩溃.
不管出现哪种情况,都必须返回编辑阶段,检查并修改源代码.
在这个阶段,计算机最终会精确地执行指令.
在UNIX和Linux下,只要键入编译和链接后的文件名,即可执行程序.
在大多数IDE中,都有一个相应的菜单命令来运行或者执行已编译的程序.
这个Run命令或者Execute命令可能有自己的菜单,也可能位于Compile菜单项下.
在Windows环境中,运行程序的.
exe文件即可,这与运行其他可执行程序一样.
在任何环境及任何语言中,开发程序的编辑、编译、链接与执行这4个步骤都是一样的.
图1-1总结了创建C程序的各个过程.
编辑创建/修改程序源代码源文件对象文件可执行文件成功!
错误是否执行运行程序错误是是错误链接链接库等编译生成机器指令否否图1-1创建和执行程序第1章C语言编程51.
5创建第一个程序本节先浏览一下创建C语言程序的流程,从输入代码到执行程序的所有4个步骤.
在这个阶段,若不了解所键入的代码信息,别担心,笔者会解释每一个步骤.
试试看:C程序示例打开编辑器,输入下面的程序,请注意标点符号不要输错,第4行及最后一行的括号是大括号{},而不是方括号[]或者圆括号()——这很重要.
另外一定要键入斜杠(/),以后也会用到反斜杠(\).
最后别忘了行末的分号(;).
/*Program1.
1YourVeryFirstCProgram-DisplayingHelloWorld*/#includeintmain(void){printf("Helloworld!
");return0;}在输入了上面的源代码后,将程序保存为hello.
c.
可以用任意名字替代hello,但扩展名必须是.
c.
这个扩展名在编写C程序时是一个通用约定,它表示文件的内容是C语言源代码.
大多数C编译器都要求源文件的扩展名是.
c,否则编译器会拒绝处理它.
下面编译程序(如本章前面"编译"一节所述),链接所有必要的内容,创建一个可执行程序(如本章前面"链接"一节所述).
编译和链接一般在一个操作中完成,通常称为"构建操作".
源代码编译成功后,链接器就添加程序需要的标准库代码,为程序创建一个可执行文件.
最后,执行程序.
这有几种方式,在Windows环境下,一般只需要在WindowsExplorer中双击.
exe文件,但最好打开一个命令行窗口,输入执行它的命令,因为在程序执行完毕后,显示输出的窗口就会消失.
在所有的操作系统环境上,都可以从命令行上运行程序.
只需要启动一个命令行会话,把当前目录改为包含程序可执行文件的目录,再输入程序名,就可以执行它了.
如果没有出现错误,就大功告成了.
这个程序会在屏幕上输出如下信息:Helloworld!
1.
6编辑第一个程序我们可以修改程序,在屏幕上输出其他信息,例如可以将程序改成:/*Program1.
2YourSecondCProgram*/#includeintmain(void)C语言入门经典(第5版)6{printf("\"Ifatfirstyoudon'tsucceed,try,try,tryagain!
\"");return0;}这个版本的输出是:"Ifatfirstyoudon'tsucceed,try,try,tryagain!
"在要显示的文本中,\"序列称为转义序列(escapesequence).
文本中包含几个不同的转义序列.
\"是在文本中包含双引号的特殊方式,因为双引号通常表示字符串的开头和结尾.
转义序列\"使双引号出现在输出的开头和结尾.
如果不使用转义序列,不仅双引号不会出现在输出中,而且程序不会编译.
本章后面的"控制字符"一节将详细介绍转义序列.
修改完源代码后,可以重新编译,链接后执行.
反复练习,熟悉整个流程.
1.
7处理错误犯错乃人之常情,没什么难为情的.
幸好计算机一般不会出错,而且非常擅长于找出我们犯的错误.
编译器会列出在源代码中找到的一组错误信息(甚至比我们想象的多),通常会指出有错误的语句.
此时,我们必须返回编辑阶段,找出有错误的代码并更正.
有时一个错误会使后面本来正确的语句也出现错误.
这多半是程序的其他部分引用了错误语句定义的内容所造成的.
当然,定义语句有错,但被定义的内容不一定有错.
下面看看源代码在程序中生成了一个错误时,会是什么样的情况.
编辑第二个程序示例,将printf()行最后的分号去掉,如下所示:/*Program1.
2YourSecondCProgram*/#includeintmain(void){printf("BewaretheIdesOfMarch!
");return0;}这和第一个程序完全相同,这里把它作为练习,用编辑器输入这个示例,编译并执行.
若输入完全正确,会看到如下输出:BewaretheIdesOfMarch!
1.
8.
1注释上述示例的第一行代码如下:/*Program1.
3AnotherSimpleCProgram-DisplayingaQuotation*/这不是程序代码,因为它没有告诉电脑执行操作,它只是一个注释,告诉阅读代码的人,这个程序要做什么.
位于/*和*/之间的任意文本都是注释.
只要编译器在源文件中找到/*,就忽略它后面的内容(即使其中的文本很像程序代码),一直到表示注释结束的*/为止.
/*可以和*/放在同一行代码上,也可以放在不同的代码行上.
如果忘记包含对应的*/,编译器就会忽略/*后面的所有内容.
下面使用一个注释说明代码的作者及版权所有:/**WrittenbyIvorHorton*Copyright2012*/也可以修饰注释,使它们比较突出:*Thisisaveryimportantcomment**sopleasereadthis.
*C语言入门经典(第5版)8使用另一种记号,可以在代码行的末尾添加一个注释,如下所示:printf("BewaretheIdesofMarch!
");//Thislinedisplaysaquotation代码行上两个斜杠后面的所有内容都会被编译器忽略.
这种形式的注释没有前一种记号那么凌乱,尤其是在注释只占一行的情形下.
应养成给程序添加注释的习惯,当然程序也可以没有注释,但在编写较长的程序时,可能会忘记这个程序的作用或工作方式.
添加足够的注释,可确保日后自己(和其他程序员)能理解程序的作用和工作方式.
下面给程序再添加一些注释:/*Program1.
3AnotherSimpleCProgram-DisplayingaQuotation*/#include//Thisisapreprocessordirectiveintmain(void)//Thisidentifiesthefunctionmain(){//Thismarksthebeginningofmain()printf("BewaretheIdesofMarch!
");//Thislineoutputsaquotationreturn0;//Thisreturnscontroltotheoperatingsystem}//Thismarkstheendofmain()可以看出,使用注释是一种非常有效的方式,可以解释程序中要发生的事情.
注释可以放在程序中的任意位置,说明代码的一般作用,指定代码是如何工作的.
1.
8.
2预处理指令下面的代码行:#include//Thisisapreprocessordirective严格说来,它不是可执行程序的一部分,但它很重要,事实上程序没有它是不执行的.
符号#表示这是一个预处理指令(preprocessingdirective),告诉编译器在编译源代码之前,要先执行一些操作.
编译器在编译过程开始之前的预处理阶段处理这些指令.
预处理指令相当多,大多放于程序源文件的开头.
在这个例子中,编译器要将stdio.
h文件的内容包含进来,这个文件称为头文件(headerfile),因为它通常放在程序的开头处.
在本例中,头文件定义了C标准库中一些函数的信息,但一般情况下,头文件指定的信息应由编译器用于在程序中集成预定义函数或其他全局对象,所以有时需要创建自己的头文件,以用于程序.
本例要用到标准库中的printf()函数,所以必须包含stdio.
h头文件.
stdio.
h头文件包含了编译器理解printf()以及其他输入/输出函数所需要的信息.
名称stdio是标准输入/输出(standardinput/output)的缩写.
C语言中所有头文件的扩展名都是.
h,本书的后面会用到其他头文件.
注意:在一些系统中,头文件名是不区分大小写的,但在#include指令里,这些文件名通常是小写.
每个符合C11标准的C编译器都有一些标准的头文件.
这些头文件主要包含了与C标准库函数相关的声明.
所有符合该标准的C编译器都支持同一组标准库函数,有同一第1章C语言编程9组标准头文件,但一些编译器有额外的库函数,它们提供的功能一般是运行编译器的计算机所专用的.
注意:附录E列出了所有的标准头文件.
1.
8.
3定义main()函数下面的5行指令定义了main()函数:intmain(void)//Thisidentifiesthefunctionmain(){//Thismarksthebeginningofmain()printf("BewaretheIdesofMarch!
");//Thislineoutputsaquotationreturn0;//Thisreturnscontroltotheoperatingsystem}//Thismarkstheendofmain()函数是两个括号之间执行某组操作的一段代码.
每个C程序都由一个或多个函数组成,每个C程序都必须有一个main()函数——因为每个程序总是从这个函数开始执行.
因此假定创建、编译、链接了一个名为progname.
exe的文件.
执行它时,操作系统会执行这个程序的main()函数.
定义main()函数的第一行代码如下:intmain(void)//Thisidentifiesthefunctionmain()它定义了main()函数的起始,注意这行代码的末尾没有分号.
定义main()函数的第一行代码开头是一个关键字int,它表示main()函数的返回值的类型,关键字int表示main()函数返回一个整数值.
执行完main()函数后返回的整数值表示返回给操作系统的一个代码,它表示程序的状态.
在下面的语句中,指定了执行完main()函数后要返回的值:return0;//Thisreturnscontroltotheoperatingsystem这个return语句结束main()函数的执行,把值0返回给操作系统.
从main()函数返回0表示,程序正常终止,而返回非0值表示异常.
换言之,在程序结束时,发生了不应发生的事情.
紧跟在函数名main后的括号,带有函数main()开始执行时传递给它的信息,在这个例子里,括号内是void,表示没有给函数main()传递任何数据,后面会介绍如何将数据传递给函数main()或程序内的其他函数.
函数main()可以调用其他函数,这些函数又可以调用其他函数.
对于每个被调用的函数,都可以在函数名后面的括号中给函数传递一些信息.
在执行到函数体中的return语句时,就停止执行该函数,将控制权返回给调用函数(对于函数main(),则将控制权返回给操作系统).
一般函数会定义为有返回值或没有返回值.
函数返回一个值时,该值总是特定的类型.
对于函数main(),返回值的类型是int,即整数.
C语言入门经典(第5版)101.
8.
4关键字在C语言中,关键字是有特殊意义的字,所以在程序中不能将关键字用于其他目的.
关键字也称为保留字.
在前面的例子里,int就是一个关键字,void和return也是关键字.
C语言有许多关键字,我们在学习C语言的过程中,将逐渐熟悉这些关键字.
附录C列出了完整的C语言关键字表.
1.
8.
5函数体main()函数的一般结构如图l-2所示:main()函数的结构函数头指定函数定义的开头和函数名称起始括号定义函数体的开头函数体包含所有的语句,它们定义了函数执行时要完成的任务结束括号定义函数体的结束图l-2函数main()的结构函数体是在函数名称后面位于起始及结束两个大括号之间的代码块.
它包含了定义函数功能的所有语句.
这个例子的main()函数体非常简单,只有两个语句:{//Thismarksthebeginningofmain()printf("BewaretheIdesofMarch!
");//Thislineoutputsaquotationreturn0;//Thisreturnscontroltotheoperatingsystem}//Thismarkstheendofmain()每个函数都必须有函数体,但函数体可以是空的,仅有起始及结束两个大括号,里面没有任何语句,在这种情况下,这个函数什么也不做.
这样的函数有什么用事实上,在开发一个包含很多函数的程序时,这种函数是非常有用的.
我们可以声明一些用来解决手头问题的空函数,确定需要完成的编程工作,再为每个函数创建程序代码.
这个方法有助于条理分明地、系统地建立程序.
第1章C语言编程11注意:程序1.
3将大括号单独排为一行,并缩进大括号之间的代码.
这么做可清楚地表示括号框起来的语句块从哪里起始和结束.
大括号之间的语句通常缩进两个或多个空格,使大括号突出在前.
这是个很好的编程格式,可以使语句块更容易阅读.
代码中的大括号可以用其他方式摆放.
例如:intmain(void){printf("BewaretheIdesofMarch!
");//Thislineoutputsaquotationreturn0;}提示:无论源代码采用什么方式摆放,都要一直采用这种方式,这很重要.
1.
8.
6输出信息例子中的main()函数体包含了—个调用printf()函数的语句:printf("BewaretheIdesofMarch!
");//Thislineoutputsaquotationprintf()是—个标准的库函数,它将函数名后面引号内的信息输出到命令行上(实际上是标准输出流,默认为命令行).
在这个例子中,调用这个函数会显示双引号内的一段警示语:双引号内的字符串称为字符串字面量.
注意这行代码用分号作为结尾.
1.
8.
7参数包含在函数名(如上面语句中的printf()函数)后的圆括号内的项称为参数,它指定要传送给函数的数据.
当传送给函数的参数多于一个时,要用逗号分开.
在上面的例子中,函数的参数是双引号内的文本字符串.
如果不喜欢例子中引号内的文本,可以改用自己想输出的句子.
例如,使用如下语句:printf("Out,damnedSpot!
OutIsay!
");修改源代码后,必须再次编译及链接程序,才可执行.
注意:与C语言中所有可执行的语句一样,printf()行的末尾必须有分号(这与定义语句或指令语句不同).
这是一个很容易犯的错误,尤其是初次使用C编程的人,老是忘了分号.
1.
8.
8控制符前面的程序可以改为输出两段句子.
输入以下的代码://Program1.
4AnotherSimpleCProgram-DisplayingaQuotation#includeC语言入门经典(第5版)12intmain(void){printf("Myformulaforsuccess\nRiseearly,worklate,strikeoil.
\n");return0;}输出的结果是:MyformulaforsuccessRiseearly,worklate,strikeoil.
在printf()语句中,在文本的开头和第一句的后面,增加了字符\n,它是另一个转义序列,代表换行符.
这样输出光标就会移动到下一行,后续的输出就会显示在新行上.
反斜杠(\)在文本字符串里有特殊的意义,它表示转义序列的开始.
反斜杠后面的字符表示是哪种转义序列.
对于\n,n表示换行.
还有其他许多转义序列.
显然,反斜杠是有特殊意义的,所以需要一种方式在字符串中指定反斜杠.
为此,应使用两个反斜杠(\\).
输入以下的程序://Program1.
5AnotherSimpleCProgram-DisplayingGreatQuotations#includeintmain(void){printf("\"Itisawisefatherthatknowshisownchild.
\"\nShakespeare\n");return0;}输出的结果如下:"Itisawisefatherthatknowshisownchild.
"Shakespeare输出中包含双引号,因为在字符串中使用了双引号的转义序列.
Shakespeare显示在下一行,因为在\"的后面有\n转义序列.
在输出字符串中使用转义序列\a可以发出声音,说明发生了有趣或重要的事情.
输入以下的程序并执行://Program1.
6ASimpleCProgram–Important#includeintmain(void){printf("Becareful!
!
\n\a");return0;}这个程序的输出如下所示且带有声音.
仔细聆听,电脑的扬声器会发出鸣响.
Becareful!
!
转义序列\a表示发出鸣响.
表1-1是转义序列表.
第1章C语言编程13表1-1转义序列转义序列说明\n换行\r回车键\b退后一格\f换页\t水平制表符\v垂直制表符\a发出鸣响\插入问号()\"插入双引号(")\'插入单引号(')\\插入反斜杠(\)试着在屏幕上显示多行文本,在该文本中插入空格.
使用\n可以把文本放在多个行上,使用\t可以给文本加上空格.
本书将大量使用这些转义序列.
1.
8.
9三字母序列一般可以直接在字符串中使用问号.
\转义序列存在的唯一原因是,有9个特殊的字母序列,称为三字母序列,这是包含三个字母的序列,分别表示#{和}:=转换为#(转换为[)转换为]/转换为\转换为}'转换为^!
转换为|-转换为~在InternationalOrganizationforStandardization(ISO)不变的代码集中编写C代码时,就需要它们,因为它没有这些字符.
这可能不适用于你.
可以完全不理会它们,除非希望编写如下语句:printf("What!
\n");这个语句生成的输出如下:What|三字母序列!
会转换为|.
为了获得希望的输出,需要把上述语句写成:printf("What\!
\n");现在三字母序列不会出现,因为第二个问号用其转义序列指定.
使用三字母序列时,编译器会发出一个警告,因为通常是不应使用三字母序列的.
C语言入门经典(第5版)141.
9预处理器上述示例介绍了如何使用预处理指令,把头文件的内容包含到源文件中.
编译的预处理阶段可以做的工作远不止此.
除了指令之外,源文件还可以包含宏.
宏是提供给预处理器的指令,来添加或修改程序中的C语句.
宏可以很简单,只定义一个符号,例如INCHES_PER_FOOT,只要出现这个符号,就用12替代.
其指令如下:#defineINCHES_PER_FOOT12在源文件中包含这个指令,则代码中只要出现INCHES_PER_FOOT,就用12替代它.
例如:printf("Thereare%dinchesinafoot.
\n",INCHES_PER_FOOT);预处理后,这个语句变成:printf("Thereare%dinchesinafoot.
\n",12);INCHES_PER_FOOT不再出现,因为该符号被#define指令中指定的字符串替代.
对于源文件中的每个符号实例,都会执行这个替代.
宏也可以很复杂,根据特定的条件把大量代码添加到源文件中.
这里不进一步介绍.
第13章将详细讨论预处理器宏.
在此之前我们会遇到一些宏,那时会解释它们.
1.
10用C语言开发程序如果读者从未写过程序,对C语言开发程序的过程就不会很清楚,但它和我们日常生活的许多事务是相同的,万事开头难.
一般首先大致确定要实现的目标,接着把该目标转变成比较准确的规范.
有了这个规范后,就可以制订达到最终目标的一系列步骤了.
就好比光知道要盖房子是不够的,还得知道需要盖什么样的房子,它有多大,用什么材料,要盖在哪里.
这种详细规划也需要运用到编写程序上.
下面介绍编写程序时需要完成的基本步骤.
房子的比喻是很有帮助的,因此就利用这个比喻.
1.
10.
1了解问题第一步是弄清楚要做什么.
在不清楚应提供什么设施:多少间卧房、多少间浴室、各房间多大等等之前就开始建造房子,会有不知所措之感.
所有这些都会影响建造房子所需的材料和工作量,从而影响整个房子的成本.
一般来说,在满足需求和完成项目的有限资金、人力及时间之间总会达成某种一致.
这和开发一个任意规模的程序是相同的.
即使是很简单的问题,也必须知道有什么输入,对输入该做什么处理,要输出什么,以及输出哪种格式.
输入可以来自键盘,也可以来自磁盘文件的数据,或来自电话或网络的信息.
输出可以显示在屏幕上,或打印第1章C语言编程15出来,也可以是更新磁盘上的数据文件.
对于较复杂的程序,需要多了解程序的各个方面.
清楚地定义程序要解决的问题,对于理解制订最终方案所需的资源与努力,是绝对必要的一部分.
好好考虑这些细节,还可以确定项目是否切实可行.
对于新项目缺乏精准、详细的规范,常常使项目所花的时间和资金大大超出预算,因而中断项目的例子有很多.
1.
10.
2详细设计要建造房子,必须有详细的计划.
这些计划能让建筑工人按图施工,并详细描述房子如何建造——具体的尺寸、要使用的材料等.
还需要确定何时完成什么工作.
例如,在砌墙之前要先挖地基,所以这个计划必须把工作分为可管理的单元,以便执行起来井然有序.
写程序也是一样.
首先将程序分解成许多定义清楚且互相独立的小单元,描述这些独立单元相互沟通的方式,以及每个单元在执行时需要什么信息,从而开发出富有逻辑、相互独立的单元.
把大型程序编写为一个大单元肯定是不可行的.
1.
10.
3实施有了房子的详细设计,就可以开始工作了.
每组建筑工人必须按照进度完成他们的工作.
在下一阶段开始前,必须先检查每个阶段是否正确完成.
省略了这些检查,将可能导致整栋房子倒塌.
当然,假使程序很大,可以一次编写—部分.
一个部分完成后,再写下—部分.
每个部分都要基于详细的设计规范,在进行下一个部分之前,应尽可能详细地检查每个部分的功能.
这样,程序就会逐步完成预期的任务.
大型编程项目常常涉及一组程序员.
项目应分成相当独立的单元,分配给程序员组中的各个成员.
这样就可以同时开发几个代码单元.
如果代码单元要相互连接为一个整体,就必须精确定义代码单元与程序其余部分之间的交互.
1.
10.
4测试房子完成了,还要进行许多测试:排水设备、水电设施、暖气等.
任何部分都有可能出问题,这些问题必须解决.
这有时是一个反复的过程,一个地方的问题可能会造成其他地方出问题.
这个机制与写程序是类似的.
每个程序模块——组成程序的单元——都需要单独测试.
若它们工作不正常,就必须调试.
调试(Debugging)是一个找出程序中的问题及更正错误的过程.
调试的由来有个说法,曾经有人在查找程序的错误时,使用计算机的电路图来跟踪信息的来源及其处理方式,竟然发现计算机程序出现错误,是因为一只虫子在电脑里,让里面的线路短路而发生的,后来,bug这个词就成了程序错误的代名词.
对于简单的程序,通常只要检查代码,就可以找出错误.
然而一般来说,调试过程C语言入门经典(第5版)16通常会使用调试器临时插入一些代码,确定在出错时会发生什么.
这包括插入断点,当暂停执行,检查代码中的值.
还可以单步执行代码.
如果没有调试器,就要加入额外的程序代码,输出一些信息,来确定程序中事件的发生顺序,以及程序执行时生成的中间值.
在大型的程序里,还需要联合测试各个程序模块,因为各个模块或许能正常工作,但并不保证它能和其他模块一起正常工作.
在程序开发的这个阶段,有个专业术语叫集成测试(integrationtesting).
1.
11函数及模块化编程到目前为止,"函数"这个词已出现过好几次了,如main()、printf()、函数体等.
下面将深入研究函数是什么,为什么它们那么重要.
大多数编程语言(包含C语言)都提供了一种方法,将程序切割成多个段,各段都可以独立编写.
在C语言中,这些段称为函数.
一个函数的程序代码与其他函数是相互隔绝的.
函数与外界有一个特殊的接口,可将信息传进来,也可将函数产生的结果传出去.
这个接口在函数的第一行即在函数名的地方指定.
图1-3的简单程序例子由4个函数组成,用于分析棒球分数.
程序控制棒球队及队员输入棒球队及队员的排名输出棒球队及队员分析棒球分数的模块分析图1-3模块化编程这4个函数都完成一个指定的、定义明确的工作.
程序中操作的执行由一个模块main()总体掌控.
一个函数负责读入及检查输入数据,另一个函数进行分析.
读入及分析了数据后,第4个函数就输出球队及球员的排名.
将程序分割成多个易于管理的小单元,对编程是非常重要的,其理由如下:可以单独编写和测试每个函数,大大简化了使整个程序运转起来的过程.
几个独立的小函数比一个大函数更容易处理和理解.
库就是供人使用的函数集.
因为它们是事先写好,且经过测试,能正常工作,所以可以放心地使用,无须细究它的代码细节.
这就加快了开发程序的速度,因为第1章C语言编程17我们只需要关注自己的代码,这是C语言的一个基本组成部分.
C语言中丰富的函数库大大增强了C语言的能力.
也可以编写自己的函数库,应用于自己感兴趣的程序类型.
如果发现经常编写某个函数,就可以编写它的通用版本,以满足自己的需求,并将它加入自己的库中.
以后需要用到这个函数时,就可使用它的库版本了.
在开发包含几千到几百万行代码的大型程序时,可以由一些程序设计团队来进行,每个团队负责一个指定的函数子组,最后把它们组成完整的程序.
第8章将详细介绍C函数.
C程序的结构在本质上就是函数的结构,本章的第一个例子就用到一个标准的库函数printf().
注意:在其他一些编程语言中,用术语"方法"表示自包含的代码单元.
因此方法的含义与函数相同.
试试看:将所学的知识用于实践下面的例子将前面学到的知识用于实践.
首先,看看下面的代码,检查自己是否理解它的作用.
然后输入这些代码,编译、链接并执行,看看会发生什么.
//Program1.
7Alongerprogram#include//Includetheheaderfileforinputandoutputintmain(void){printf("Hithere!
\n\n\nThisprogramisabit");printf("longerthantheothers.
");printf("\nButreallyit'sonlymoretext.
\n\n\n\a\a");printf("Hey,waitaminute!
!
Whatwasthat\n\n");printf("\t1.
\tAbird\n");printf("\t2.
\tAplane\n");printf("\t3.
\tAcontrolcharacter\n");printf("\n\t\t\b\bAndhowwillthislookwhenitprintsout\n\n");return0;}输出如下:Hithere!
Thisprogramisabitlongerthantheothers.
Butreallyit'sonlymoretext.
Hey,waitaminute!
!
Whatwasthat1.
Abird2.
Aplane3.
AcontrolcharacterAndhowwillthislookwhenitprintsoutC语言入门经典(第5版)18代码的说明这个程序看起来有点复杂,这只是因为括号内的文本字符串包含了许多转义序列.
每个文本字符串都由一对双引号括起来.
但这个程序只是连续调用printf()函数,说明屏幕输出是由传送给printf()函数的数据所控制.
本例通过预处理指令包含了标准库中的stdio.
h文件:#include//Includetheheaderfileforinputandoutput这是一个预处理指令,因为它以符号#开头.
stdio.
h文件提供了使用printf()函数所需的定义.
然后,定义main()函数头,指定它返回一个整数值:intmain(void)括号中的void关键字表示不给main()函数传递信息.
下一行的大括号表示其下是函数体:{下一行语句调用标准库函数printf(),将"Hithere!
"输出到屏幕上,接着空两行,输出"Thisprogramisabit".
printf("Hithere!
\n\n\nThisprogramisabit");空两行是由3个转义序列\n生成的.
转义序列\n会把字符显示在新行上.
第一个转义序列\n结束了包含"Hithere!
"的行,之后的两个转义序列\n生成两个空行,文本"Thisprogramisabit"显示在第4行上.
这行代码在屏幕上生成了4行输出.
下一个printf()生成的输出跟在上一个printf()输出的最后一个字符后面.
下面的语句输出文本"longerthantheothers.
",其中的第一个字符是一个空白:printf("longerthantheothers.
");这个输出跟在上一个输出的后面,紧临bit中的t.
所以在文本的开头需要一个空格,否则计算机就会显示"Thisprogramisabitlongerthantheothers.
",这不是我们希望的结果.
下一个语句在输出前会先换行,因为双引号中文本字符串的开头是\n:printf("\nButreallyitSonlymoretext.
\n\n\n\a\a");显示完文本后会空两行(因为有3个\n转义序列),然后发出两次鸣响.
下一个屏幕输出从空的第二行开始.
下一个输出语句如下:printf("Hey,waitaminute!
!
Whatwasthat\n\n");输出文本后空一行.
其后的输出在空的这行开始.
第1章C语言编程19以下3行语句各插入一个制表符,显示一个数字后,再插入另一个制表符,之后是一些文本,结束后换行.
这样,输出更容易阅读:printf("\t1.
\tAbird\n");printf("\t2.
\rAplane\n");printf("\t3.
\tAcontrolcharacter\n");这几个语句会生成3行带编号的输出.
下一语句先输出一个换行符,所以在前面输出的后面是一个空行,然后输出两个制表符和两个空格,接着退回两个空格,最后显示文本并换行:printf("\n\t\t\b\bAndhowwillthislookwhenitprintsout\n\n");函数体中的最后一个语句如下:return0;这个语句结束main()的执行,把0返回给操作系统.
结束大括号表示函数体结束:}注意:输出中制表符和退格的实际效果随编译器的不同而不同.
1.
12常见错误错误是生活中的一部分.
用C语言编写计算机程序时,必须用编译器将源代码转换成机器码,所以必须用非常严格的规则控制使用C语言的方式.
漏掉一个该有的逗点,或添加不该有的分号,编译器都不会将程序转换成机器码.
即使实践了多年,程序中也很容易出现输入错误.
这些错误可能在编译或链接程序时找出.
但有些错误可能使程序执行时,表面上看起来正常,却不定时地出错,这就需要花很多时间来跟踪错误了.
当然,不是只有输入错误会带来问题,具体实施时也常常会发现问题.
在处理程序中复杂的判断结构时,很容易出现逻辑错误.
从语言的观点看,程序是正确的,编译及运行也正确,但得不到正确的结果.
这类错误最难查找.
1.
13要点温习第一个程序是个不错的方法,图1-4列出了重点.
C语言入门经典(第5版)20这表示注释的开头表示main()返回一个整数值这表示注释的结尾//后面的所有内容都是注释,因此编译器会忽略它们是一个标准头文件,使用输入输出函数如printf()时需要包含它这个大括号表示main()函数体的结尾这个大括号表示main()函数体的开头函数体是包含在最外层括号中的所有代码这个语句结束main()的执行,把控制权返回给操作系统//后面的所有内容都是注释,因此编译器会忽略它们Include指令把外部文件的内容包含到源文件中图1-4简单程序的要素1.
14小结本章编写了几个C程序.
我们学习了许多基础知识,本章的重点是介绍一些基本概念,而不是详细探讨C程序语言.
现在读者应该对编写、编译及链接程序很有信心了.
也许读者目前对如何构建C程序只有模糊的概念.
以后学了更多的C语言知识,编写了一些程序后,就会清楚明白了.
下一章将学习较复杂的内容,而不只是用printf()输出文本.
我们要处理信息,得到更有趣的结果.
另外,printf()不只是显示文本字符串,它还有其他用途.
1.
15习题以下的习题能让读者测试本章所学的成果.
如果有不懂的地方,可以翻看本章的内容,还可以从Apress网站http://www.
apress.
com的SourceCode/Download部分下载答案,但这应是最后一种方法.
习题1.
1编写一个程序,用两个printf()语句分别输出自己的名字及地址.
习题1.
2将上一个练习改成所有的输出只用一个printf()语句.
习题1.
3编写一个程序,输出下列文本,格式如下所示:"It'sfreezinginhere,"hesaidcoldly.
第2章编程初步现在读者一定很渴望编写程序,让计算机与外界进行实际的交互.
我们不希望程序只能做打字员的工作,显示包含在程序代码中的固定信息.
的确,编程的内涵远不止此.
理想情况下,我们应能从键盘上输入数据,让程序把它们存储在某个地方,这会让程序更具多样性.
程序可以访问和处理这些数据,而且每次执行时,都可以处理不同的数据值.
每次运行程序时输入不同的信息正是整个编程业的关键.
在程序中存储数据项的地方是可以变化的,所以叫做变量(variable),而这正是本章的主题.
本章的主要内容:内存的用法及变量的概念在C中如何计算变量的不同类型及其用途强制类型转换的概念及其使用场合编写一个程序,计算树木的高度2.
1计算机的内存首先看看计算机如何存储程序要处理的数据.
为此,就要了解计算机的内存,在开始编写第一个程序之前,先简要介绍计算机的内存.
计算机执行程序时,组成程序的指令和程序所操作的数据都必须存储到某个地方.
这个地方就是机器的内存,也称为主内存(mainmemory),或随机访问存储器(RandomAccessMemory,RAM).
RAM是易失性存储器.
关闭PC后,RAM的内容就会丢失.
PC把一个或多个磁盘驱动器作为其永久存储器.
要在程序结束执行后存储起来的任何数据,都应打印出来或写入磁盘,因为程序结束时,存储在RAM中的结果就会丢失.
可以将计算机的RAM想象成一排井然有序的盒子.
每个盒子都有两个状态:满为l,空为0.
因此每个盒子代表—个二进制数:0或1.
计算机有时用真(true)和假(false)表示它们:1是真,0是假.
每个盒子称为—个位(bit),即二进制数(binarydigit)的缩写.
注意:如果读者不记得或从来没学过二进制数,可参阅附录A.
但如果不明白这些内容,不用担心,因为这里的重点是计算机只能处理0与1,而不能直接处理十进制数.
程序使用的所有数据(包括程序指令)都是由二进制数组成的.
C语言入门经典(第5版)22为了方便起见,内存中的位以8个为—组,每组的8位称为一个字节(byte).
为了使用字节的内容,每个字节用一个数字表示,第—个字节用0表示,第二个字节用1表示,直到计算机内存的最后—个字节.
字节的这个标记称为字节的地址(address).
因此,每个字节的地址都是唯一的.
每栋房子都有一个唯一的街道地址.
同样,字节的地址唯—地表示计算机内存中的字节.
总之,内存的最小单位是位(bit),将8个位组合为一组,称为字节(byte).
每个字节都有唯一的地址.
字节地址从0开始.
位只能是0或1,如图2-1所示.
这是一个字节存储地址存储地址另一个字节内容内容这是一个字节存储地址存储地址另一个字节内容内容空满满满满空空空空空空空空空空空图2-1内存中的字节计算机内存的常用单位是千字节(KB)、兆字节(MB)、千兆字节(GB).
大型磁盘驱动器使用兆兆字节(TB).
这些单位的意义如下:1KB是1024字节.
1MB是1024KB,也就是1048576字节.
1GB是1024MB,也就是1073741841字节.
1TB是1024GB,也就是1099511627776字节.
如果PC有1GB的RAM,字节地址就是0~1073741841.
为什么不使用更简单的整数,例如千、百万或亿因为从0到1023共1024个数字,而在二进制中,1023的10个位刚好全是l:1111111111,它是一个非常方便的二进制数.
1000是很好用的十进制数,但是在二进制的计算机里就不再那么方便了,它是1111101000.
因此以KB(1024字节)为单位,是为了方便计算机使用.
同样,MB需要20个位,GB需要30个位.
但是硬盘的容量可能出现混乱.
磁盘制造商常常宣称他们生产的磁盘的容量是256GB或1TB,而实际上这两个数字表示2560亿字节及1万亿字节.
当然,2560亿字节只有231MB,而1万亿字节只有911GB,所以磁盘制造商给出的硬盘容量有误导作用.
有了字节的概念,下面看看如何在程序里使用这些内存.
第2章编程初步232.
2什么是变量变量是计算机里一块特定的内存,它是由一个或多个连续的字节所组成,一般是1、2、4、8或16字节.
每个变量都有一个名称,可以用该名称表示内存的这个位置,以提取它包含的数据或存储一个新数值.
下面编写一个程序,用第1章介绍的printf()函数显示你的薪水.
假设你的薪水是10000元/月,则很容易编写这个程序.
//Program2.
1WhatisaVariable#includeintmain(void){printf("Mysalaryis$10000");return0;}这个程序的工作方式不需要多做解释,它和第一章开发的程序差不多.
如何修改这个程序,让它能够根据存储在内存中的值,定制要显示的信息这有几种方法,它们有一个共同点:使用变量.
在这个例子里,可以分配一块名为salary的内存,把值10000存储在该变量中.
要显示薪水时,可以使用给变量指定的名称salary,将存储在其中的值10000显示出来.
程序用到变量名时,计算机就会访问存储在其中的值.
变量的使用次数是不受限制的.
当薪水改变时,只要改变salary变量存储的值,整个程序就会使用新的值.
当然,在计算机中,所有的值都存储为二进制数.
程序中变量的数量是没有限制的.
在程序执行过程中,每个变量包含的值由程序的指令来决定.
变量的值不是固定的,而可以随时改变,且没有次数的限制.
注意:变量可以有一个或多个字节,那么,计算机如何知道变量有多少个字节下一节会提到,每个变量都有类型来指定变量可以存储的数据种类.
变量的类型决定了为它分配多少个字节.
变量的命名给变量指定的名称一般称为变量名.
变量的命名是很有弹性的.
它可以是一个或多个大写或小写字母、数字和下划线(_)(有时下划线也算作字母),但要以字母开头.
下面是一些正确的变量名:RadiusdiameterAuntie_MayKnotted_WoolD678变量名不能以数字开头,所以8_Ball和6_pack都是不合法的名称.
变量名只能包含字母、下划线和数字,所以Hash!
及Mary-Lou都不能用作变量名.
Mary-Lou是一个常C语言入门经典(第5版)24见的错误,但是Mary_Lou就是可以接受的.
变量名中不能有空格,所以MaryLou会被视为两个变量名Mary和Lou.
以一或两个下划线开头的变量名常用在头文件中,所以在给变量命名时,不要将下划线用作第一个字符,以免和标准库里的变量名冲突.
例如最好避免使用_this和_that这样的变量名.
变量名的另一个要点是,变量名是区分大小写的,因此Democrat和democrat是不同的.
可以在上述限制内随意指定变量名,但最好使变量名有助于了解该变量包含的内容,例如用变量名x来存储薪水信息就不好,而使用变量名salary就好得多,对其用途不会有什么疑义.
警告:变量名可以包含的字符数取决于编译器,遵循C语言标准的编译器至少支持31个字符,只要不超过这个长度就没问题.
建议变量名不要超过这个长度,因为这样的变量名比较繁琐,代码也难以理解.
有些编译器会截短过长的变量名.
2.
3存储整数的变量变量有几种不同的类型,每种变量都用于存储特定类型的数据.
有几种变量可存储整数、非整数的数值和字符.
一些类型存储特定的数据(例如整数),它们之间的区别是它们占用的内存量和可以存储的数值范围.
首先看看用于存储整数的变量.
整数是没有小数点的数字.
下面是一个例子:12310,999,000,00020,000881这些数值是整数,但这对程序而言并不完全正确.
整数是不能包含逗号的,所以第二个值在程序里应该写成10999000000,第三个值应写成20000.
下面是一些不是整数的例子:1.
234999.
92.
0–0.
00053.
141592652.
0一般算作整数,但是计算机不将它算作整数,因为它带有小数点.
在程序里,必须把这个数字写作2,不带小数点.
在C程序中,整数总是写成不带小数点的数字,如果数字中有小数点,就不是整数,而是浮点数,详见后面的内容.
在详细讨论整型变量之前,先看看程序里一个简单的变量,学习变量的用法.
试试看:使用变量回到输出薪水的例子.
将前面的程序改为使用一个int型变量://Program2.
2Usingavariable#includeintmain(void){intsalary;//Declareavariablecalledsalary第2章编程初步25salary=10000;//Store10000insalaryprintf("Mysalaryis%d.
\n",salary);return0;}输入这个例子,编译、链接并执行,会得到下面的结果:Mysalaryis10000.
代码的说明前三行和前一个例子相同,下面看看新的语句.
用来存放薪水的变量声明语句如下:intsalary;//Declareavariablecalledsalary这个语句称为变量声明,因为它声明了变量的名称.
在这个程序中,变量名是salary.
警告:变量声明语句以分号结束.
如果漏掉分号,程序编译时会产生错误.
变量声明也指定了这个变量存储的数据类型,这里使用关键字int指定,salary用来存放一个整数.
关键字int放在变量名称之前.
这是可用于存储整数的几个类型之一.
如后面所述,声明存储其他数据类型的变量时,要使用另一个关键字指定数据类型,其方式大致相同.
注意:关键字是特殊的C保留字,对编译器有特殊的意义.
不能将它们用作变量名称或代码中的其他实体,否则编译器会生成错误消息.
变量声明也称为变量的定义,因为它分配了一些存储空间,来存储整数值,该整数可以用变量名salary来引用.
注意:声明引入了一个变量名,定义则给变量分配存储空间.
有这个区别的原因在本书后面会很清楚.
当然,现在还未指定变量salary的值,所以此刻该变量包含一个垃圾值,即上次使用这块内存空间时遗留在此的值.
下一个语句是:salary=10000;//Store10000insalary这是一个简单的算术赋值语句,它将等号右边的数值存储到等号左边的变量中.
这里声明了变量salary,它的值是10000.
将右边的值10000存储到左边的变量salary中.
等号"="称为赋值运算符,它将右边的值赋予左边的变量.
C语言入门经典(第5版)26然后是熟悉的printf()语句,但这里的用法和之前稍有不同:printf("Mysalaryis%d.
",salary);括号内有两个参数,用逗号分开.
参数是传递给函数的值.
在这个程序语句中,传给printf()函数的两个参数如下:参数1是一个控制字符串,用来控制其后的参数输出以什么方式显示,它是放在双引号内的字符串,也称为格式字符串,因为它指定了输出数据的格式.
参数2是变量名salary.
这个变量值的显示方式是由第一个参数——控制字符串来确定.
这个控制字符串和前一个例子相当类似,都包含一些要显示的文本.
但在本例的这个字符串中有一个%d,它称为变量值的转换说明符(conversionspecifier).
转换说明符确定变量在屏幕上的显示方式,换言之,它们指定最初的二进制值转换为什么形式,显示在屏幕上.
在本例中使用了d,它是应用于整数值的十进制说明符,表示第二个参数salary输出为一个十进制数.
注意:转换说明符总是以%字符开头,以便printf()函数识别出它们.
控制字符串中的%总是表示转换说明符的开头,所以如果要输出%字符,就必须用转义序列%%.
试试看:使用更多的变量试试一个稍大的程序://Program2.
3Usingmorevariables#includeintmain(void){intbrothers;//Declareavariablecalledbrothersintbrides;//andavariablecalledbridesbrothers=7;//Store7inthevariablebrothersbrides=7;//Store7inthevariablebrides//Displaysomeoutputprintf("%dbridesfor%dbrothers\n",brides,brothers);return0;}执行程序的结果如下:7bridesfor7brothers;代码的说明这个程序和前一个例子相当类似.
首先声明两个变量brothers和brides,语句如下:intbrothers;//Declareavariablecalledbrothersintbrides;//andavariablecalledbrides第2章编程初步27两个变量都声明为int类型,都存储整数值.
注意,它们在两个语句中声明.
由于这两个变量的类型相同,故可以将它们放在同一行代码上声明:intbrothers,brides;在一个语句中声明多个变量时,必须用逗号将数据类型后面的变量名分开,该语句要用分号结束.
这是一种很方便的格式,但有一个缺点:每个变量的作用不很明显,因为它们全放在一行代码上,不能加入注释来描述每个变量.
因此可以将它们分成两行,语句如下:intbrothers,//Declareavariablecalledbrothersbrides;//andavariablecalledbrides将语句分成两行,就可以加入注释了.
这些注释会被编译器忽略,因此和最初没加入注释的语句相同.
可以将C语句分成好几行.
分号决定语句的结束,而不是代码行的结束.
当然也可以编写两个声明语句.
一般最好在一个语句中定义一个变量.
变量声明常常放在函数的可执行语句的开头,但这不是必须的.
一般把要在一块代码中使用的变量声明放在该起始括号的后面.
之后的两个语句给两个变量赋值7:brothers=7;//Store7inthevariablebrothersbrides=7;//Store7inthevariablebrides注意,声明这些变量的语句放在上述语句之前.
如果遗漏了某个声明,或把声明语句放在后面,程序就不会编译.
变量在其声明之前在代码中是不存在的,必须总是在使用变量之前声明它.
下一个语句调用printf()函数,它的第一个参数是一个控制字符串,以显示一行文本.
这个字符串还包含规范,指定后续参数的值如何解释和显示在文本中.
这个控制字符串中的两个转换说明符%d会分别被printf()函数的第二个参数brides和第三个参数brothers的值取代:printf("%dbridesfor%dbrothers\n",brides,brothers);转换说明符按顺序被printf()函数的第二个参数brides和第三个参数brothers的值取代:变量brides的值对应第一个%d,变量brothers的值对应第二个%d.
如果将设置变量值的语句改为如下所示,将会更清楚:brothers=8;//Store8inthevariablebrothersbrides=4;//Store4inthevariablebrides在这个比较明确的例子中,printf()语句会清楚地显示变量和转换说明符的对应关系,因为输出如下所示:4bridesfor8brothers为了演示变量名是区分大小写的,修改printf()函数,使其中一个变量名以大写字母C语言入门经典(第5版)28开头,如下所示://Program2.
3AUsingmorevariables#includeintmain(void){intbrothers;//Declareavariablecalledbrothersintbrides;//andavariablecalledbridesbrothers=7;//Store7inthevariablebrothersbrides=7;//Store7inthevariablebrides//Displaysomeoutputprintf("%dbridesfor%dbrothers\n",Brides,brothers);return0;}编译这个版本的程序时,会得到一个错误信息.
编译器把brides和Brides解释为两个不同的变量,所以它不理解Brides这个变量,因为没有声明它.
这是一个常见的错误,如前所述,打字和拼写错误是出错的一个主要原因.
变量必须在使用之前声明,否则编译器就无法识别,将该语句标识为错误.
2.
3.
1变量的使用前面介绍了如何声明及命名变量,但这和在第一章学到的知识相比并没有太多用处.
下面编写另一个程序,在产生输出前使用变量的值.
试试看:作一个简单的计算这个程序用变量的值做简单的计算://Program2.
4Simplecalculations#includeintmain(void){inttotal_pets;intcats;intdogs;intponies;intothers;//Setthenumberofeachkindofpetcats=2;dogs=1;ponies=1;others=46;//Calculatethetotalnumberofpetstotal_pets=cats+dogs+ponies+others;printf("Wehave%dpetsintotal\n",total_pets);//Outputtheresult第2章编程初步29return0;}执行程序的结果如下:Wehave50petsintotal代码的说明与前面的例子一样,大括号中的所有语句都有相同的缩进量,这说明这些语句都包含在这对大括号中.
应当仿效此法组织程序,使位于一对大括号之间的一组语句有相同的缩进量,使程序更易于理解.
首先,定义5个int类型的变量:inttotal_pets;intcats;intdogs;intponies;intothers;因为这些变量都用来存放动物的数量,它们肯定是整数,所以都声明为int类型.
下面用4个赋值语句给变量指定特定的值:cats=2;dogs=1;ponies=1;others=46;现在,变量Total_Pets还没有设定明确的值,它的值是使用其他变量进行计算的结果:total_pets=cats+dogs+ponies+others;在这个算术语句中,把每个变量的值加在一起,计算出赋值运算符右边的所有宠物数的总和,再将这个总和存储到赋值运算符左边的变量Total_Pets中.
这个新值替代了存储在变量Total_Pets中的旧值.
printf()语句显示了变量Total_Pets的值,即计算结果:printf("Wehave%dpetsintotal\n",total_pets);试着改变某些宠物的值,或增加一些宠物.
记住要声明它们,给它们设定数值,将它们加进变量Total_Pets中.
2.
3.
2变量的初始化在上面的例子,用下面的语句声明每个变量:intcats;//ThenumberofcatsaspetsC语言入门经典(第5版)30用下面的语句设定变量Cats的值:Cats=2;将变量Cats的值设为2.
这个语句执行之前,变量的值是什么它可以是任何数.
第一个语句创建了变量Cats,但它的值是上一个程序在那块内存中留下的数值.
其后的赋值语句将变量Cats的值设置为2.
但最好在声明变量时,就初始化它,语句如下所示:intCats=2;这个语句将变量Cat声明为int类型,并设定初值为2.
声明变量时就初始化它一般是很好的做法.
它可避免对初始值的怀疑,当程序运作不正常时,它有助于追踪错误.
避免在创建变量时使用垃圾值,可以减少程序出错时计算机崩溃的机会.
随意使用垃圾值可能导致各种问题,因此从现在起,就养成初始化变量的好习惯,即使是0也好.
上面的程序是第一个真正做了些事情的程序.
它非常简单,仅仅相加了几个数字,但这是非常重要的一步.
它是运用算术语句进行运算的一个基本例子.
下面介绍一些更复杂的计算.
1.
基本算术运算在C语言中,算术语句的格式如下:变量名=算术表达式;赋值运算符右边的算术表达式指定使用变量中存储的值和/或明确给出的数字,以及算术运算符如加(+)、减(-)、乘(*)及除(/)进行计算.
在算术表达式中也可以使用其他运算符,如后面所述.
前面例子中的算术语句如下:total_pets=cats+dogs+ponies+others;这个语句先计算等号右边的算术表达式,再将所得的结果存到左边的变量中.
在C语言中,符号"="定义了一个动作,而不是像数学中那样说明两边相等.
它指定将右边表达式的结果存到左边的变量中.
因此可以编写下面的语句:total_pets=total_pets+2;以数学的观点来看,它是很荒唐的,但对编程而言它是正确的.
假定重新编写程序,添加上面的语句.
添加了这个语句的程序段如下:total_pets=cats+dogs+ponies+others;total_pets=total_pets+2;printf("Thetotalnumberofpetsis:%d",total_pets);在执行完第一个语句后,Total_Pets的值是50.
之后,第二行提取Total_Pets的值,给该值加2,再将结果存储回变量Total_Pets.
因此最后显示出来的总数是52.
注意:在赋值运算中,先计算等号右边的表达式,然后将结果存到等号左边的变量中.
新第2章编程初步31的值取代赋值运算符左边的变量中的原值.
赋值运算符左边的变量称为lvalue,因为在这个位置可以存储一个值.
执行赋值运算符右边的表达式所得的值称为rvalue,因为它是计算表达式所得的一个值.
计算结果是数值的表达式称为算术表达式,下面都是算术表达式:31+2total_petscats+dogs–ponies-data计算这些表达式,都会得到一个数值.
注意,变量名也是一个表达式,它的计算结果是一个值,即该变量包含的值.
最后一个例子的值是data的负值,所以如果data包含-5,表达式-data的值就是5.
当然,data的值仍是-5.
稍后将详细讨论如何构建表达式,并学习运算规则.
这里先用基本算术运算符做一些简单的例子.
表2-1列出了这些算术运算符.
表2-1基本算术运算符运算符动作+加-减*乘/除%取模(Modulus)应用运算符的数据项一般称为操作数,两边的操作数都是整数时,所有这些运算符都生成整数结果.
前面没有提到过取模运算符.
它用运算符左边的表达式值去除运算符右边的表达式值,并求出其余数,所以有时称为余数运算符.
表达式12%5的结果是2.
因为12除以5的余数是2.
下一节将详细介绍.
所有这些运算符的工作方式都与我们的常识相同,只有除法运算符例外,它应用于整数时有点不直观.
下面进行一些算术运算.
注意:应用运算符的值称为操作数.
需要两个操作数的运算符(如%)称为二元运算符.
应用于一个值的运算符称为一元运算符.
因此-在表达式a-b中是二元运算符,在表达式-data中是一元运算符.
试试看:减和乘下面基于食物的程序演示了减法和乘法://Program2.
5Calculationswithcookies#includeintmain(void){intcookies=5;intcookie_calories=125;//CaloriespercookieC语言入门经典(第5版)32inttotal_eaten=0;//Totalcookieseateninteaten=2;//Numbertobeeatencookies=cookies-eaten;//Subtractnumbereatenfromcookiestotal_eaten=total_eaten+eaten;printf("\nIhaveeaten%dcookies.
Thereare%dcookiesleft",eaten,cookies);eaten=3;//Newvalueforcookieseatencookies=cookies-eaten;//Subtractnumbereatenfromcookiestotal_eaten=total_eaten+eaten;printf("\nIhaveeaten%dmore.
Nowthereare%dcookiesleft\n",eaten,cookies);printf("\nTotalenergyconsumedis%dcalories.
\n",total_eaten*cookie_calories);return0;}这个程序产生如下输出:Ihaveeaten2cookies.
Thereare3cookiesleftIhaveeaten3more.
Nowthereare0cookiesleftTotalenergyconsumedis625calories.
代码的说明首先声明并初始化3个int类型的变量:intcookies=5;intcookie_calories=125;//Caloriespercookieinttotal_eaten=0;//Totalcookieseaten在程序中,使用变量total_eaten计算吃掉的饼干总数,所以要将它初始化为0.
下一个声明并初始化的变量存储吃掉的饼干数,如下:inteaten=2;//Numbertobeeaten用减法运算符从cookies中减掉eaten:cookies=cookies-eaten;//Subtractnumbereatenfromcookies减法运算的结果存回cookies变量,所以cookies的值变成3.
因为吃掉了一些饼干,所以要给total_eaten增加吃掉的饼干数:total_eaten=total_eaten+eaten;将eaten变量的当前值2加到total_eaten的当前值0上,结果存储回变量total_eaten.
printf()语句显示剩下的饼干数:printf("\nIhaveeaten%dcookies.
Thereare%dcookiesleft",eaten,cookies);这个语句在一行上放不下,所以在printf()的第一个参数后的逗号后面,将该语句的其他内容放在下一行上.
可以像这样分拆语句,使程序易于理解,或放在屏幕的指定宽第2章编程初步33度之内.
注意不能用这种方式拆分第一个字符串参数.
不能在字符串的中间放置换行符.
需要将字符串拆开成两行或多行时,一行上的每一段字符串必须有自己的一对双引号.
例如,上面的语句可以写成:printf("\nIhaveeaten%dcookies.
""Thereare%dcookiesleft",eaten,cookies);如果两个或多个字符串彼此相邻,编译器会将它们连接起来,构成一个字符串.
用整数值的转换说明符%d将eaten和cookies的值显示出来.
在输出字符串中,eaten的值取代第一个%d,cookies的值取代第二个%d.
字符串在显示之前会先换行,因为开头处有一个\n.
下一个语句将变量eaten的值设为一个新值:eaten=3;//Newvalueforcookiestobeeaten新值3取代eaten变量中的旧值2.
然后完成和以前一样的操作序列:cookies=cookies-eaten;//Subtractnumbereatenfromcookiestotal_eaten=total_eaten+eaten;printf("\nIhaveeaten%dmore.
Nowthereare%dcookiesleft\n",eaten,cookies);最后,在执行return语句,结束程序前,计算并显示被吃掉饼干的卡路里数:printf("\nTotalenergyconsumedis%dcalories.
\n",total_eaten*cookie_calories);printf()函数的第二个参数是一个算术表达式,而不是变量.
编译器会将表达式total_eaten*cookie_calories的计算结果存储到一个临时变量中,再把该值作为第二个参数传送给printf()函数.
函数的参数总是可以使用算术表达式,只要其计算结果是需要的类型即可.
下面看看除法和取模运算符.
试试看:除法和取模运算符假设你有一罐饼干(其中有45块饼干)和7个孩子.
要把饼干平分给每个孩子,计算每个孩子可得到几块饼干,分完后剩下几块饼干.
//Program2.
6Cookiesandkids#includeintmain(void){intcookies=45;//Numberofcookiesinthejarintchildren=7;//Numberofchildrenintcookies_per_child=0;//Numberofcookiesperchildintcookies_left_over=0;//Numberofcookiesleftover//Calculatehowmanycookieseachchildgetswhentheyaredividedupcookies_per_child=cookies/children;//NumberofcookiesperchildC语言入门经典(第5版)34printf("Youhave%dchildrenand%dcookies\n",children,cookies);printf("Giveeachchild%dcookies.
\n",cookies_per_child);//Calculatehowmanycookiesareleftovercookies_left_over=cookies%children;printf("Thereare%dcookiesleftover.
\n",cookies_left_over);return0;}执行程序后的输出:Youhave7childrenand45cookiesGiveeachchild6cookies.
Thereare3cookiesleftover.
代码的说明下面一步一步地解释这个程序.
下面的语句声明并初始化4个整数变量:cookies、children、cookies_per_child、cookies_left_over:intcookies=45;//Numberofcookiesinthejarintchildren=7;//Numberofchildrenintcookies_per_child=0;//Numberofcookiesperchildintcookies_left_over=0;//Numberofcookiesleftover使用除号运算符"/"将饼干数量除以孩子的数量,得到每个孩子分得的饼干数:cookies_per_child=cookies/children;//Numberofcookiesperchild下面两个语句输出结果,即cookies/children变量的值:printf("Youhave%dchildrenand%dcookies\n",children,cookies);printf("Giveeachchild%dcookies.
\n",cookies_per_child);从输出结果可以看出,cookies_per_child的值是6.
这是因为当操作数是整数时,除法运算符总是得到整数值.
45除以7的结果是6,余3.
下面的语句用取模运算符计算余数:cookies_left_over=cookies%children;赋值运算符右边的表达式计算cookies除以children得到的余数.
最后一个语句输出余数:printf("Thereare%dcookiesleftover.
\n",cookies_left_over);2.
深入了解整数除法当一个操作数是负数时,使用除法和模数运算符的结果是什么在执行除法运算时,如果操作数不同号,结果就是负数.
因此,表达式-45/7和45/-7的结果相同,都是-6.
如果操作数同号,都是正数或都是负数,结果就是正数.
因此45/7和-45/-7结果都是6.
至于模数运算符,不管操作数是否同号,其结果总是和左操作数的符号相同.
因此45%-7第2章编程初步35等于3,-45/7等于-3,-45/-7也等于-3.
3.
一元运算符例如,乘法运算符是一个二元运算符.
因为它有两个操作数,其结果是一个操作数乘以另一个操作数.
还有一些运算符是一元运算符,即它们只需一个操作数.
后面将介绍更多的例子.
但现在看看一个最常用的一元运算符.
4.
一元减号运算符前面使用的运算符都是二元运算符,因为它们都操作两个数据项.
C语言中也有操作一个数据项的一元运算符.
一元减号运算符就是一个例子.
若操作数为负,它就生成正的结果,若操作数为正,它就生成负的结果.
要了解一元减号运算符的使用场合,考虑一下追踪银行账号.
假定我们在银行存了200元.
在簿子里用两列记录这笔钱的收支情况,一列记录付出的费用,另一列记录得到的收入,支出列是负数,收入列是正数.
我们决定购买一片价值50元的CD和一本价值25元的书.
假使一切顺利,从银行的初始值中减掉支出的75元后,就得到了余额.
表2-2说明这些项的记录情况.
表2-2收入与支出记录项收入支出存款余额支票收入$200$200CD$50$150书$25$125结余$200$75$125如果将这些数字存储到变量中,可以将收入及支出都输入为正数,只有计算余额时,才会把这些数字变成负数.
为此,可以将一个负号(-)放在变量名的前面.
要把总支出输出为负数,可编写如下语句:intexpenditure=75;printf("Yourbalancehaschangedby%d.
",-expenditure);这会产生如下结果:Yourbalancehaschangedby-75.
负号表示花掉了这笔钱,而不是赚了.
注意,表达式-expenditure不会改变expenditure变量的值,它仍然是75.
这个表达式的值是-75.
在表达式-expenditure中,一元减号运算符指定了一个动作,其结果是翻转expenditure变量的符号:将负数变成正数,将正数变成负数.
这和编写一个负数(如-75或-1.
25)时使用的负号运算符是不同的.
此时,负号不表示一个动作,程序执行时,不需要执行指令.
它只是告诉编译器,在程序里创建一个负的常量.
C语言入门经典(第5版)362.
4变量与内存前面介绍了整数变量,但未考虑过它们占用多少内存空间.
每次声明给定类型的变量时,编译器都会给它分配一块足够大的内存空间,来保存该类型的变量.
相同类型的不同变量总是占据相同大小的内存(字节数).
但不同类型的变量需要分配的内存空间就不一样了.
本章的开头介绍了,计算机的内存组织为字节.
每个变量都会占据一定数量的内存字节,那么存储整数需要几个字节这取决于整数值有多大.
一个字节能存储-128~+127的整数.
这对于前面的例子而言已经足够,但是如何存储一双及膝的长筒袜上的平均针脚数一个字节就不够了.
另一方面,如果要记录一个人在两分钟中能吃掉的汉堡包个数,一个字节就足够了,此时分配更多的字节就是浪费内存了.
因此在C语言中有不同类型的变量来存储不同类型的数字,其中一个就是整数.
整数变量还有几种不同的变体,以存储不同范围的整数.
2.
4.
1带符号的整数类型有5种基本的变量类型可以声明为存储带符号的整数值(无符号的整数值参见下一节).
每种类型都用不同的关键字或关键字组合来指定,如表2-3所示.
表2-3整数变量类型的名称类型名称字节数signedchar1shortint2int4longint4longlongint8下面是这些类型的变量声明:shortshoe_size;inthouse_number;longlongstar_count;类型名称short、long和longlong可以用作shortint、longint和longlongint的缩写,前面还可以带有signed关键字.
但是,这些类型几乎总是用表2-3列出的缩写形式.
Int类型也可以写作signedint,但不常用.
表2-3列出了每个类型的字节数,但这些变量类型所占的内存空间,以及可以存储的取值范围,取决于所使用的编译器.
很容易确定编译器允许的极限值,因为它们在limits.
h头文件中定义,本章后面会介绍.
第2章编程初步372.
4.
2无符号的整数类型有些数据总是正的,例如河滩上的鹅卵石数目.
对于每个存储带符号整数的类型,都有一个对应的类型来存储无符号的整数,它们占用的内存空间与无符号类型相同.
每个无符号的类型名称都与带符号的类型名称相同,但要在前面加上关键字unsigned.
表2-4列出了可用的无符号整数类型.
表2-4无符号整数类型的名称类型名称字节数unsignedchar1unsignedshortint或unsignedshort2unsignedint4unsignedlongint或unsignedlong4unsignedlonglongint或unsignedlonglong8如果位数给定,可以表示的数值就是固定的.
32位的变量可以表示4294967295个不同的值.
因此,使用无符号类型所提供的值不会多于对应的带符号类型,但其表示的数字比对应的带符号类型大一倍.
下面是声明无符号整型变量的示例:unsignedintcount;unsignedlongpopulation;注意:如果变量的类型不同,但占用相同的字节数,则它们仍是不同的.
Long和int类型占用相同的内存量,但它们仍是不同的类型.
2.
4.
3指定整数常量整数变量有不同的类型,整数常量也有不同的类型.
例如,如果将整数写成100,它的类型就是int.
如果要确保它是long类型,就必须在这个数值的后面加上一个大写L或小写l.
所以,long类型的整数100应写为100L.
虽然写为100l也是合法的,但应尽量避免,因为小写字母l与数字1很难辨别.
声明并初始化Big_Number的语句如下:longBig_Number=1287600L;负整数常量的定义要用负号,例如:intdecrease=-4;longbelow_sea_level=-100000L;C语言入门经典(第5版)38将整数常量指定为longlong类型时,应添加两个L:longlongreally_big_number=123456789LL;如前所述,将常量指定为无符号类型时,应添加U,如下所示:unsignedintcount=100U;unsignedlongvalue=999999999UL;要存储取值范围最大的整数,可以按如下方式定义变量:unsignedlonglongmetersPerLightYear=9460730472580800ULL;ULL指定,初始值的类型是unsignedlonglong.
1.
十六进制常量也可以用十六进制编写整数,即以16为基底.
十六进制的数字等价于十进制的0~15,表示方式是0~9和A~F(或a~f).
因为需要一种方式区分十进制的99和十六进制的99,所以在十六进制数的前面加上0x或0X.
因此在程序中,十六进制的99可以编写成0x99或0X99.
十六进制常量也可以有后缀.
下面是十六进制常量的一些示例:0xFFFF0xdead0xfade0xFade0x123456EE0xafL0xFABABULL最后一个示例的类型是unsignedlonglong,倒数第二个示例的类型是long.
十六进制常量常用来表示位模式,因为每一个十六进制的数对应于4个二进制位.
两个十六进制的数指定一个字节.
第3章介绍的按位运算符一般与十六进制常量一起用于定义掩码.
如果不熟悉十六进制,可以参阅附录A.
2.
八进制常量八进制数以8为基底.
八进制数字为0~7,对应于二进制中的3位.
八进制数起源于计算机内存采用36位字的时代,那时一个字是3位的组合.
因此,36位二进制字可以写成12个八进制数.
八进制数目前很少使用,需要知道它们,以免错误地指定八进制数.
以0开头的整数常量,例如014,会被编译器看作八进制数.
因此,014等价于十进制的12,而不是十进制的14.
所以,不要在整数中加上前导0,除非要指定八进制数.
很少需要使用八进制数.
3.
默认的整数常量类型如前所述,没有后缀的整数常量默认为int类型,但如果该值太大,在int类型中放不下,该怎么办对于这种情形,编译器创建了一个常量类型,根据值是否有后缀,来判断该值是否是十进制.
表2-5列出了编译器如何判断各种情形下的整数类型.
第2章编程初步39表2-5无符号整数类型的名称后缀十进制常量八或十六进制常量无1.
int2.
long3.
longlong1.
int2.
unsignedint3.
long4.
unsignedlong5.
longlong6.
unsignedlonglongU1.
unsignedint2.
unsignedlong3.
unsignedlonglong1.
unsignedint2.
unsignedlong3.
unsignedlonglongL1.
long2.
longlong1.
long2.
unsignedlong3.
longlong4.
unsignedlonglongUL1.
unsignedlong2.
unsignedlonglong1.
unsignedlong2.
unsignedlonglongLL1.
longlong1.
longlong2.
unsignedlonglongULL1.
unsignedlonglong1.
unsignedlonglong编译器选择容纳该值的第一种类型,如表中各项的数字所示.
例如,后缀为u或U的十六进制常量默认为unsignedint,否则就是unsignedlong.
如果这个取值范围太小,就采用unsignedlonglong类型.
当然,如果给变量指定的初始值在变量类型的取值范围中放不下,编译器就会发出一个错误消息.
2.
5使用浮点数浮点数包含的值带小数点,也可以表示分数和整数.
下面是浮点数的例子:1.
60.
000087655.
899100.
0最后一个常量是整数,但它存储为浮点数,因为存在小数点.
由于浮点数的表示方式,它的位数是固定的.
这会限制浮点数的精确度,是一个缺点,然而它的取值范围要比整数大得多.
浮点数通常表示为一个小数值乘以10的次方.
例如前面的每一个浮点数都可以采用表2-6的方式来表示.
表2-6浮点数表示法数值使用指数表示法在C语言中也可以写成1.
60.
16*1010.
16E10.
000080.
8*10-40.
8E-47655.
8990.
7655899*1040.
7655899E4100.
01.
0*1021.
0E2C语言入门经典(第5版)40中间列说明,左列的数如何用指数表示法来表示,但在C语言中不使用这种方式:而是表示这些数值的一个替代方法.
右列说明了中间列的数字在C语言中的表示法.
这些数字中的E表示指数,也可以使用小写e.
当然在程序中编写这些数字时可以不用指数,而使用左列的方式,但对于非常大或非常小的数字,指数形式比较方便.
0.
5E-15当然比0.
0000000000000005更好.
浮点数的表示浮点数的内部表示有点复杂.
如果对计算机的内部不感兴趣,可以跳过这一节.
这里包含本节,是因为理解计算机如何处理浮点数,可以更好地明白浮点数为什么有这样的值域.
图2-2显示了在IntelPC的内存中,浮点数如何存储在4字节的字中.
指数尾数4字节的字图2-2内存中的浮点数这是一个单精度浮点数,在内存中占用4字节.
该值包含三部分:符号位,正值为0,负值为18位的指数23位的尾数尾数包含浮点数中的小数,占用23位.
它假定为一个形式为1.
bbb.
.
.
b的二进制值,二进制点的右边有23位.
因此,尾数的值总是大于等于1,小于2.
那么,如何把24位值放在23位中,其实这很简单.
最左边的一位总是1,所以不需要存储.
采用这种方式,可以给精度提供一个额外的二进制数字.
指数是一个无符号的8位值,所以指数值可以是0~255.
浮点数的实际值是尾数乘以2的指数幂2exp,其中exp是指数值.
使用负的指数值可以表示很小的分数.
为了包含这个浮点数表示,给浮点数的实际指数加上127,这将允许把-127~128的值表示为8位无符号值.
因此指数为-6会存储为121,指数为6会存储为133.
但还有几个复杂的问题.
实际指数为-127,而存储的指数是0,这是一种特殊情况.
浮点数0表示为尾数和指数的所有位都是0,所以实际指数为-127时,不能用于其他值.
另一个复杂的问题是,最好能检测出除0的情形.
于是系统保留了另外两个特殊值,来表示+无穷大和-无穷大,它们分别是正数和负数除以0的结果.
正数除以0的结果是符号位为0,所有指数位是1,所有尾数位是0.
这个值很特殊,表示+无穷大,不是1*2128,且所有尾数位是0.
负数除以0的结果是这个值取负,所以-1*2128也是一个特殊值.
最后一个复杂的问题是,最好能表示0除以0的结果.
这称为NotaNumber(NaN).
这个保留值的所有指数位是1,尾数的首位是1或0,这取决于NaN只是一个NaN,允第2章编程初步41许继续执行,还是一个发出信号的NaN,在代码中生成一个可中断执行的异常.
NaN在尾数中有一个前导0时,则其他尾数位中的至少一位是1,就可以把它与无穷大区分开.
警告:因为计算机把浮点数存储为二进制尾数和二进制指数的组合体,所以一些十进制的小数值不能用这种方式精确地表示.
尾数中二进制点右边的二进制位,例如.
1、.
01、.
001、.
0001等,等于十进制分数1/2、1/4、1/8、1/16等.
所以二进制尾数的分数部分只能表示这些十进制分数的子集之和.
可以看出,1/3或1/5等值不能用二进制尾数精确地表示,因为二进制小数不能精确地组合为这些值.
2.
6浮点数变量浮点数变量类型只能存储浮点数.
表2-7是3种不同的浮点数变量.
表2-7浮点数变量类型关键字字节数数值范围f1oat4±3.
4E±38(精确到6到7位小数)double8±1.
7E±308(精确到15位小数)longdouble12±1.
19E±4932(精确到18位小数)这是浮点数类型通常占用的字节数和取值范围.
与整数一样,这些数所占用的字节数和取值范围取决于机器和编译器.
在一些编译器上,类型longdouble和double相同.
注意,小数的精确位数只是一个大约的数,因为浮点数在内部是以二进制方式存储的,十进制的浮点数在二进制中并不总是有精确的表示形式.
浮点数变量的声明方式和整数变量类似.
只需要给浮点数类型使用对应的关键字即可:floatradius;doublebiggest;如果需要存储至多有7位精确值的数(范围从10-38到10+38),就应需要使用float类型的变量.
类型float的值称为单精度浮点数.
从表2-6中得知,它占用4个字节.
使用类型double的变量可以存储双精度浮点数.
类型double的变量占用8个字节,有15位精确值,范围从10-308到10+308.
它足以满足大多数的需求.
但某些特殊的应用程序需要更精确、更大的范围,此时可以使用longdouble,但这取决于编译器.
编写一个类型为float的常量,需要在数值的末尾添加一个f,以区别double类型.
用下面的语句初始化前面的两个变量:floatradius=2.
5f;doublebiggest=123E30;变量radius的初值是2.
5,变量biggest初始化为123后面加30个零.
任何数,只要有小数点,就是double类型,除非加了f,使它变为float类型.
当用E或e指定指数值C语言入门经典(第5版)42时,这个常量就不需要包含小数点.
例如1E3f是float类型,3E8是double类型.
要声明longdouble类型的常量,需要在数字的末尾添加一个大写L或小写l,例如:longdoublehuge=1234567.
89123L;2.
6.
1使用浮点数完成除法运算如前所见,使用整数操作数进行除法运算时,通常会得到整数结果.
除非除法运算的左操作数刚好是右操作数的整数倍,否则其结果是不正确的.
当然,在将饼干分给孩子们的例子中,整数除法运算的方式是没问题的,但将10尺长的厚板均分成4块时,就有问题了.
这时就需要用到浮点数了.
使用浮点数进行除法运算,会得到正确的结果——至少是一个精确到固定位数的值.
下一个例子说明如何使用float类型的变量进行除法运算.
试试看:使用float类型值的除法这个例子用一个浮点数除以另一个浮点数,然后显示其结果://Program2.
7Divisionwithfloatvalues#includeintmain(void){floatplank_length=10.
0f;//Infeetfloatpiece_count=4.
0f;//Numberofequalpiecesfloatpiece_length=0.
0f;//Lengthofapieceinfeetpiece_length=plank_length/piece_count;printf("Aplank%ffeetlongcanbecutinto%fpieces%ffeetlong.
\n",plank_length,piece_count,piece_length);return0;}程序的结果输出如下:Aplank10.
000000feetlongcanbecutinto4.
000000pieces2.
500000feetlong.
代码的说明如何平均切割木板是很容易理解的.
注意,在printf()语句中为float类型的值使用了新的格式说明符.
printf("Aplank%ffeetlongcanbecutinto%fpieces%ffeetlong.
\n",plank_length,piece_count,piece_length);使用格式说明符%f显示浮点数.
格式说明符一般必须对应输出的值的类型.
如果使用格式说明符%d输出float类型的值,就会得到一个垃圾值.
因为浮点数会解释为整数.
同样,如果使用%输出整数类型的值,也会得到垃圾值.
第2章编程初步432.
6.
2控制输出中的小数位数在上个例子的输出中有太多不必要的0.
擅长使用量尺和锯子,并不说明能用长度为2.
500000的量尺切割木板,更不用说用2.
500001长度的量尺了.
可以用格式说明符指定小数点后面的位数.
例如,要使输出的小数点后有两位数,可以使用格式说明符%.
2f.
如果小数点后需要有3位数,则可以使用%.
3f.
可以修改上一个例子中的printf()语句,生成更适当的结果:printf("Aplank%.
2ffeetlongcanbecutinto%.
0fpieces%.
2ffeetlong.
\n",plank_length,piece_count,piece_length);第一个格式说明符对应于变量plank_length,其结果的小数点后有两位数.
第二个格式说明符指定小数点后没有数字,这很合理,因为piece_count是整数.
最后一个格式说明符和第一个相同.
因此执行这个版本的例子,输出如下:Aplank10.
00feetlongcanbecutinto4pieces2.
50feetlong.
这样看起来舒服多了.
当然,使piece_count是整数类型会更好.
2.
6.
3控制输出的字段宽度输出的字段宽度是输出值所使用的总字符数(包括空格),在这个程序中,它是默认的.
printf()函数确定了输出值需要占用多少个字符位置,小数点后的位数由我们指定,并将它用作字段宽度.
但我们可以自己确定字段宽度,也可以自己确定小数位数.
如果要求输出一列排列整齐的数值,就应确定固定的字段宽度.
如果让printf()函数指定字段宽度,输出的数字列就不整齐.
用于浮点数的格式说明符的一般形式是:%[width][.
precision][modifier]f其中,方括号不包含在格式说明符中.
它们包含的内容是可选的,所以可省略width、.
precision或modifier,或它们的任意组合.
width值是一个整数,指定输出的总字符数(包括空格),即字段宽度.
precision值也是一个整数,指定小数点后的位数.
当输出值的类型是longdouble时,modifier部分就是L,否则就省略它.
可以重写上个例子的printf()调用,指定字段宽度及小数点后的位数,例如:printf("A%8.
2fplankfootcanbecutinto%5.
0fpieces%6.
2ffeetlong.
\n",plank_length,piece_count,piece_length);上面的代码略微修改了文本,使之能放在书页上.
现在,第一个值的字段宽度为8,小数点后有2位数.
第二个值是切割的总片数,其字段宽度为5个字符,且没有小数部分.
第三个值的字段宽度为6,小数点后有2位数.
指定字段宽度时,数值默认为右对齐.
如果希望数值左对齐,只需要在%的后面添加一个负号.
例如,格式说明符%-10.
4f将输出一个左对齐的浮点数,其字段宽度为10个字符,小数点后有4位数.
C语言入门经典(第5版)44注意,也可以对整数值指定字段宽度及对齐方式.
例如%-15d指定一个整数是左对齐,其字段宽度为15个字符.
还有其他格式说明符,以后会学习它们.
用前面的例子试试各种不同的输出,尤其是看看字段宽度太小时会出现什么情况.
2.
7较复杂的表达式算术要比两个数相除复杂得多.
事实上,如果要进行复杂的算术运算,也可以使用笔和纸.
对于较复杂的计算,需要更多地控制表达式的计算顺序.
括号可以提供这方面的能力.
当遇到错综复杂的情况时,括号还有助于使表达式更清晰.
在算术表达式中可以使用括号,其使用次数不受限制.
包含在括号中的子表达式的计算顺序是:从最内层的括号开始计算到最外层的括号,对于运算符的优先级,一般规则是先乘除后加减.
因此,表达式2*(3+3*(5+4))的值是60.
首先计算表达式5+4,得到9.
然后乘以3,得到27.
之后加上3,得到30,最后乘以2,得到60.
可以加入空格,将操作数和运算符分开,使算术表达式的可读性更高.
需要使代码更紧凑时,则可以删除空格.
无论采用哪种方式,编译器都不会受到影响,因为编译器会忽略空格.
如果根据优先级规则,无法确定表达式的计算顺序,通常可以加进一些括号,确保生成需要的结果.
试试看:算术运算这次要利用输入的直径计算一个圆桌的周长及面积.
计算圆的周长及面积时,其数学公式要使用π或pi(周长=2πr,面积=πr2,其中r是半径).
如果不记得这些公式,也不用担心.
这不是数学课本,所以只要理解程序是如何运作的即可.
//Program2.
8calculationsonatable#includeintmain(void){floatradius=0.
0f;//Theradiusofthetablefloatdiameter=0.
0f;//Thediameterofthetablefloatcircumference=0.
0f;//Thecircumferenceofthetablefloatarea=0.
0f;//TheareaofthetablefloatPi=3.
14159265f;printf("Inputthediameterofthetable:");scanf("%f",&diameter);//Readthediameterfromthekeyboardradius=diameter/2.
0f;//Calculatetheradiuscircumference=2.
0f*Pi*radius;//Calculatethecircumferencearea=Pi*radius*radius;//Calculatetheareaprintf("\nThecircumferenceis%.
2f",circumference);printf("\nTheareais%.
2f\n",area);return0;}第2章编程初步45这个程序的输出如下:Inputthediameterofthetable:6Thecircumferenceis18.
85.
Theareais28.
27.
代码的说明在第一个printf()之前,这个程序看起来和以前的例子很类似:floatradius=0.
0f;//Theradiusofthetablefloatdiameter=0.
0f;//Thediameterofthetablefloatcircumference=0.
0f;//Thecircumferenceofthetablefloatarea=0.
0f;//TheareaofthetablefloatPi=3.
14159265f;上述语句声明并初始化了5个变量,其中Pi有固定的数值.
注意,所有的初值都在末尾添加了f,因为这是float类型的初值.
若没有f的话,它们的类型就是double.
不过在这里,它们仍然可行,但是编译器需要进行一些不必要的转换,将类型double转换为类型float.
Pi值的位数太多,类型float存储不下,所以编译器提取其最左边的部分,使之能放在float类型中.
下一条语句输出一个从键盘上输入数据的提示:printf("Inputthediameterofthetable:");下一条语句读取圆桌的直径.
这需要使用一个新的标准库函数scanf():scanf("%f",&diameter);//Readthediameterfromthekeyboardscanf()是另一个需要包含头文件stdio.
h的函数.
它专门处理键盘输入,提取通过键盘输入的数据,按照第一个参数指定的方式解释它,第一个参数是放在双引号内的一个控制字符串.
在这里,这个控制字符串是%f.
因为读取的值是float类型.
scanf()将这个数存入第二个参数指定的变量diameter中.
第一个参数是一个控制字符串,和printf()函数的用法类似,但它控制的是输入,而不是输出.
第10章将详细介绍scanf()函数,附录D总结了所有的控制字符串.
注意,变量名diameter前的&是个新东西,它称为寻址运算符,它允许scanf()函数将读入的数值存进变量diameter.
它的做法和将参数值传给函数是一样的.
这里不详细解释它;第8章会详细说明.
唯一要记住的是,使用函数scanf()时,要在变量前加上寻址运算符&,而使用printf()函数时不添加它.
在函数scanf()的控制字符串中,%字符表示某数据项的格式说明符的开头.
%字符后面的f表示输入一个浮点数.
在控制字符串中一般有几个格式说明符,它们按顺序确定了函数中后面各参数的数据类型.
在scanf()的控制字符串后面有多少个参数,控制字符串就有多少个格式说明符,本书的后面将介绍scanf()函数的更多运用,表2-8列出了读取各种类型的数据时所使用的格式说明符:C语言入门经典(第5版)46表2-8读取数据的格式说明符操作需要的控制字符串读取short类型的数值%hd读取int类型的数值%d读取long类型的数值%ld读取float类型的数值%f或%e读取double类型的数值%lf或%le在%ld和%lf格式说明符中,l是小写的L.
别忘了一定要在接收输入值的变量名前加上&.
另外,如果使用了错误的格式说明符,如使用%d读取float类型的数据,变量中的数值就不正确,但系统不会提示存储了一个垃圾值.
接下来的3条语句计算结果:radius=diameter/2.
0f;//Calculatetheradiuscircumference=2.
0f*Pi*radius;//Calculatethecircumferencearea=Pi*radius*radius;//Calculatethearea第一条语句计算半径,将输入的直径除以2.
第二条语句用计算出来的半径计算桌子的周长.
第三条语句计算面积.
注意,如果忘了2.
0f中的f,编译器就会显示一个警告消息.
这是因为如果没有f,常量的类型就是double,于是在一个表达式中混用了不同的类型.
后面会详细描述这个问题.
可以编写如下语句来计算周长和面积:circumference=2.
0f*Pi*(diameter/2.
0f);//Calculatethecircumferencearea=Pi*(diameter/2.
0f)*(diameter/2.
0f);//Calculatethearea每个语句中的圆括号可以确保先计算半径的值,也有助于更清楚地说明正在计算的是半径的值.
这些语句的缺点在于对半径的计算潜在地执行了三次,而实际上仅需要计算一次.
智能的编译器可以优化这种代码,让半径的计算仅执行一次.
下面的两个语句输出计算后的数值:printf("Thecircumferenceis%.
2f.
",circumference);printf("Theareais%.
2f.
\n",area);这两个printf()语句用格式说明符%.
2f输出变量circumference和area的值.
这个格式说明符指定输出的值在小数点后面有两位数.
默认的字段宽度足以容纳要显示的变量值.
当然,可以执行这个程序,给直径输入任意值.
试着输入各种不同形式的浮点数,例如输入1E1f.
2.
8定义命名常量前面的例子将Pi定义为变量,但它是一个不会改变的常量,π的值是一个不循环的无限小数,其值总是固定不变.
唯一的问题是,在指定它时精确到几位数.
最好确保它第2章编程初步47的值在程序中保持不变,使之不会因错误而改变.
这有两种方法.
第一是将Pi定义为一个符号,在程序编译期间用π的值取代它.
此时,Pi不是一个变量,而是它表示的值的一个别名.
试试看:定义一个常量下面将PI指定为一个数值的别名://Program2.
9Moreroundtables#include#definePI3.
14159f//DefinitionofthesymbolPIintmain(void){floatradius=0.
0f;floatdiameter=0.
0f;floatcircumference=0.
0f;floatarea=0.
0f;printf("Inputthediameterofatable:");scanf("%f",&diameter);radius=diameter/2.
0f;circumference=2.
0f*PI*radius;area=PI*radius*radius;printf("\nThecircumferenceis%.
2f.
",circumference);printf("\nTheareais%.
2f.
\n",area);return0;}这个输出和前面的例子完全相同.
代码的说明在注释和头文件的#include指令之后,有一个预处理指令:#definePI3.
14159f//DefinitionofthesymbolPI这里将PI定义为一个要被3.
14159f取代的符号.
使用PI而不是Pi,是因为在C语言中有一个通用的约定:#define语句中的标识符都是大写.
只要在程序里的表达式中引用PI,预处理器就会用#define指令中的数值取代它.
所有的取代动作都在程序编译之前完成.
程序开始编译时,不再包含PI这个符号了,因为所有的PI都用#define指令中的数值取代了.
这些动作都是在编译器处理时在内部发生的,源程序没有改变,仍包含符号PI.
警告:预处理器在替代代码中的符号时,不会考虑它是否有意义.
如果在替代字符串中出错,例如,如果编写了3.
14.
159f,预处理器仍会用它替代每个PI,而程序不会编译.
C语言入门经典(第5版)48第二种方法是将Pi定义成变量,但告诉编译器,它的值是固定的,不能改变.
声明变量时,在变量名前加上const关键字,可以固化变量的值,例如:constfloatPi=3.
14159f;//DefinesthevalueofPiasfixed以这种方式定义Pi的优点是,Pi现在定义为指定类型的一个常量值.
在前面的例子中,PI只是一个字符序列,替代代码中的所有PI.
在Pi的声明中添加关键字const,会使编译器检查代码是否试图改变它的值.
这么做的代码会被标记为错误,且编译失败.
下面是它的一个例子.
试试看:定义一个其值固定的变量在前面的例子中使用一个常量,但代码短一些://Program2.
10Roundtablesagainbutshorter#includeintmain(void){floatdiameter=0.
0f;//Thediameterofatablefloatradius=0.
0f;//TheradiusofatableconstfloatPi=3.
14159f;//DefinesthevalueofPiasfixedprintf("Inputthediameterofthetable:");scanf("%f",&diameter);radius=diameter/2.
0f;printf("\nThecircumferenceis%.
2f.
",2.
0f*Pi*radius);printf("\nTheareais%.
2f.
\n",Pi*radius*radius);return0;}代码的说明下面是Pi变量的声明:constfloatPi=3.
14159f;//DefinesthevalueofPiasfixed这个语句声明了变量Pi,并给它定义一个数值;Pi在这里还是变量,但它的初始值是不可改变的.
这是const修饰符的功劳.
它可应用在声明任何类型的变量的语句中,固化该变量的值.
编译器会检查代码是否试图改变声明为const的变量,如果发现有这种情况,编译器就会做出提示.
可以设法骗过编译器,去改变const变量,但这违反了使用const的初衷.
下面两个语句输出程序的结果:printf("\nThecircumferenceis%.
2f.
",2.
0f*Pi*radius);printf("\nTheareais%.
2f.
\n",Pi*radius*radius);在这个例子中,不再用变量存储周长及面积.
现在这些表达式显示为printf()函数的参数,它们的值会直接传给函数printf().
第2章编程初步49如前所述,传给函数的值可以是表达式的计算结果,此时,编译器会创建一个临时变量,来存储这个值,再传给函数.
之后这个临时变量就被删除.
这很好,只要不在其他地方使用这些数值即可.
2.
8.
1极限值当然,一定要确定程序中给定的整数类型可以存储的极限值.
如前所述,头文件定义的符号表示每种类型的极限值.
表2-9列出了对应于每种带符号整数类型的极限值符号名.
表2-9表示整数类型的极限值的符号类型下限上限charCHAR_MINCHAR_MAXshortSHRT_MINSHRT_MAXintINT_MININT_MAXlongLONG_MINLONG_MAXlonglongLLONG_MINLLONG_MAX无符号整数类型的下限都是0,所以它们没有特定的符号.
无符号整数类型的上限的符号分别是UCHAR_MAX、USHRT_MAX、UINT_MAX、ULONG_MAX和ULLONG_MAX.
要在程序中使用这些符号,必须在源文件中添加头文件的#include指令:#include可以用最大值初始化一个int变量,如下所示:intnumber=INT_MAX;这个语句把number的值设置为最大值,编译器会利用该最大值编译代码.
头文件定义了表示浮点数的符号,其中一些的技术含量很高,所以这里只介绍我们感兴趣的符号.
3种浮点数类型可以表示的最大正值和最小正值如表2-10所示.
还可以使用FLT_DIG、DBL_DIG和LDBL_DIG符号,它们指定了对应类型的二进制尾数可以表示的小数位数.
下面用一个例子来说明如何使用表示整数和浮点数的符号.
表2-10表示浮点数类型的极限值的符号类型下限上限floatFLT_MINFLT_MAXdoubleDBL_MINDBL_MAXlongdoubleLDBL_MINLDBL_MAXC语言入门经典(第5版)50试试看:找出极限值这个程序输出头文件中定义的符号的对应值.
//Program2.
11Findingthelimits#include//Forcommandlineinputandoutput#include//Forlimitsonintegertypes#include//Forlimitsonfloating-pointtypesintmain(void){printf("Variablesoftypecharstorevaluesfrom%dto%d\n",CHAR_MIN,CHAR_MAX);printf("Variablesoftypeunsignedcharstorevaluesfrom0to%u\n",UCHAR_MAX);printf("Variablesoftypeshortstorevaluesfrom%dto%d\n",SHRT_MIN,SHRT_MAX);printf("Variablesoftypeunsignedshortstorevaluesfrom0to%u\n",USHRT_MAX);printf("Variablesoftypeintstorevaluesfrom%dto%d\n",INT_MIN,INT_MAX);printf("Variablesoftypeunsignedintstorevaluesfrom0to%u\n",UINT_MAX);printf("Variablesoftypelongstorevaluesfrom%ldto%ld\n",LONG_MIN,LONG_MAX);printf("Variablesoftypeunsignedlongstorevaluesfrom0to%lu\n",ULONG_MAX);printf("Variablesoftypelonglongstorevaluesfrom%lldto%lld\n",LLONG_MIN,LLONG_MAX);printf("Variablesoftypeunsignedlonglongstorevaluesfrom0to%llu\n",ULLONG_MAX);printf("\nThesizeofthesmallestpositivenon-zerovalueoftypefloatis%.
3e\n",FLT_MIN);printf("Thesizeofthelargestvalueoftypefloatis%.
3e\n",FLT_MAX);printf("Thesizeofthesmallestnon-zerovalueoftypedoubleis%.
3e\n",DBL_MIN);printf("Thesizeofthelargestvalueoftypedoubleis%.
3e\n",DBL_MAX);printf("Thesizeofthesmallestnon-zerovalueoftypelongdoubleis%.
3Le\n",LDBL_MIN);printf("Thesizeofthelargestvalueoftypelongdoubleis%.
3Le\n",LDBL_MAX);printf("\nVariablesoftypefloatprovide%udecimaldigitsprecision.
\n",FLT_DIG);printf("Variablesoftypedoubleprovide%udecimaldigitsprecision.
\n",DBL_DIG);printf("Variablesoftypelongdoubleprovide%udecimaldigitsprecision.
\n",LDBL_DIG);return0;}结果如下所示:Variablesoftypecharstorevaluesfrom-128to127Variablesoftypeunsignedcharstorevaluesfrom0to255Variablesoftypeshortstorevaluesfrom-32768to32767Variablesoftypeunsignedshortstorevaluesfrom0to65535Variablesoftypeintstorevaluesfrom-2147483648to2147483647Variablesoftypeunsignedintstorevaluesfrom0to4294967295Variablesoftypelongstorevaluesfrom-2147483648to2147483647Variablesoftypeunsignedlongstorevaluesfrom0to4294967295Variablesoftypelonglongstorevaluesfrom-9223372036854775808to9223372036854775807Variablesoftypeunsignedlonglongstorevaluesfrom0to18446744073709551615Thesizeofthesmallestpositivenon-zerovalueoftypefloatis1.
175e-038Thesizeofthelargestvalueoftypefloatis3.
403e+038Thesizeofthesmallestnon-zerovalueoftypedoubleis2.
225e-308Thesizeofthelargestvalueoftypedoubleis1.
798e+308Thesizeofthesmallestnon-zerovalueoftypelongdoubleis3.
362e-4932Thesizeofthelargestvalueoftypelongdoubleis1.
190e+4932第2章编程初步51Variablesoftypefloatprovide6decimaldigitsprecision.
Variablesoftypedoubleprovide15decimaldigitsprecision.
Variablesoftypelongdoubleprovide18decimaldigitsprecision.
代码的说明在一系列的printf()函数调用中,输出和头文件定义的符号的值.
计算机中的数值总是受限于该机器可以存储的值域,这些符号的值表示每种数值类型的极限值.
这里用说明符%u输出无符号整数值.
如果用%d输出无符号类型的最大值,则最左边的位(带符号类型的符号位)为1的数值就得不到正确的解释.
对浮点数的极限值使用说明符%e,表示这个数值是指数形式.
同时指定精确到小数点后的3位数,因为这里的输出不需要非常精确.
printf()函数显示的值是longdouble类型时,需要使用L修饰符.
L必须是大写,这里没有使用小写字母l.
%f说明符表示没有指数的数值,它对于非常大或非常小的数来说相当不方便.
在这个例子中试一试,就会明白其含义.
2.
8.
2sizeof运算符使用sizeof运算符可以确定给定的类型占据多少字节.
当然,在C语言中sizeof是一个关键字.
表达式sizeof(int)会得到int类型的变量所占的字节数,所得的值是一个size_t类型的整数.
size_t类型在标准头文件(和其他头文件)中定义,对应于一个基本整数类型.
但是,与size_t类型对应的类型可能在不同的C库中有所不同,所以最好使用size_t变量存储sizeof运算符生成的值,即使知道它对应的基本类型,也应如此.
下面的语句是存储用sizeof运算符计算所得的数值:size_tsize=sizeof(longlong);也可以将sizeof运算符用于表达式,其结果是表达式的计算结果所占据的字节数.
通常该表达式是某种类型的变量.
除了确定某个基本类型的值占用的内存空间之外,sizeof运算符还有其他用途,但这里只使用它确定每种类型占用的字节数.
试试看:确定给定类型占用的字节数这个程序会输出每个数值类型占多少字节://Program2.
12Findingthesizeofatype#includeintmain(void){printf("Variablesoftypecharoccupy%ubytes\n",sizeof(char));printf("Variablesoftypeshortoccupy%ubytes\n",sizeof(short));printf("Variablesoftypeintoccupy%ubytes\n",sizeof(int));printf("Variablesoftypelongoccupy%ubytes\n",sizeof(long));printf("Variablesoftypelonglongoccupy%ubytes\n",sizeof(longlong));printf("Variablesoftypefloatoccupy%ubytes\n",sizeof(float));printf("Variablesoftypedoubleoccupy%ubytes\n",sizeof(double));C语言入门经典(第5版)52printf("Variablesoftypelongdoubleoccupy%ubytes\n",sizeof(longdouble));return0;}输出如下:Variablesoftypecharoccupy1bytesVariablesoftypeshortoccupy2bytesVariablesoftypeintoccupy4bytesVariablesoftypelongoccupy4bytesVariablesoftypelonglongoccupy8bytesVariablesoftypefloatoccupy4bytesVariablesoftypedoubleoccupy8bytesVariablesoftypelongdoubleoccupy12bytes代码的说明因为sizeof运算符的结果是一个无符号整数,所以用%u说明符输出它.
注意,使用表达式sizeofvar_name也可以得到变量var_name占用的字节数.
显然,在关键字sizeof和变量名之间的空格是必不可少的.
现在已经知道编译器给每个数值类型指定的极限值和占用的字节数了.
注意:如果希望把sizeof运算符应用于一个类型,则该类型名必须放在括号中,例如sizeof(longdouble).
将sizeof运算符应用于表达式时,括号就是可选的.
2.
9选择正确的类型必须仔细选择在计算过程中使用的变量类型,使之能包含我们期望的值.
如果使用了错误的类型,程序就可能出现很难检测出来的错误.
这最好用一个例子来说明.
试试看:变量的正确类型下面的例子说明,如果给变量选择了不适当的类型,程序就会出错.
//Program2.
13Choosingthecorrecttypeforthejob1#includeintmain(void){constfloatRevenue_Per_150=4.
5f;shortJanSold=23500;//StocksoldinJanuaryshortFebSold=19300;//StocksoldinFebruaryshortMarSold=21600;//StocksoldinMarchfloatRevQuarter=0.
0f;//SalesforthequartershortQuarterSold=JanSold+FebSold+MarSold;//Calculatequarterlytotal//Outputmonthlysalesandtotalforthequarterprintf("Stocksoldin\nJan:%d\nFeb:%d\nMar:%d\n",JanSold,FebSold,MarSold);第2章编程初步53printf("Totalstocksoldinfirstquarter:%d\n",QuarterSold);//CalculatethetotalrevenueforthequarterandoutputitRevQuarter=QuarterSold/150*Revenue_Per_150;printf("Salesrevenuethisquarteris:$%.
2f\n",RevQuarter);return0;}这些都是相当简单的计算,一季度的总销售量应是64400,它只是将每个月的销售量加在一起.
但运行这个程序,输出如下:StocksoldinJan:23500Feb:19300Mar:21600Totalstocksoldinfirstquarter:-1136Salesrevenuethisquarteris:$-31.
50显然,结果不正确.
把3个较大的正数加在一起,不应得到一个负值.
代码的说明首先,代码定义了一个要在计算中使用的常量:constfloatRevenue_Per_150=4.
5f;这个语句定义了每销售150个产品的收入.
这没有什么错误.
接着,声明4个变量,并给它们赋予初值:shortJanSold=23500;//StocksoldinJanuaryshortFebSold=19300;//StocksoldinFebruaryshortMarSold=21600;//StocksoldinMarchfloatRevQuarter=0.
0f;//Salesforthequarter前3个变量的类型是short,足以存储初值了.
RevQuarter变量是float类型,因为我们希望季度收入在小数点后有两位数.
下一个语句声明QuarterSold变量,并存储每月销售量的总和:shortQuarterSold=JanSold+FebSold+MarSold;//Calculatequarterlytotal事实上,结果错误的原因是QuarterSold变量的声明错误.
该变量声明为short类型,其初始值指定为3个月销售量的总和.
这个总和是64400,而程序输出了一个负数.
这个语句一定有错误.
问题的原因是,我们试图在QuarterSold变量中存储对short类型而言过大的数字.
short变量能存储的最大值是32767,计算机不能正确解释QuarterSold的值,所以输出了一个负值.
另一个考虑是季度销售量不会是负数,也许使用无符号的类型会更合适.
这个问题的解决方法是给QuarterSold变量使用unsignedlong类型,来存储非常大的数字.
还可以把存储每月销售量的变量指定为无符号.
C语言入门经典(第5版)54解决问题修改程序,再次运行它.
只需要修改main()函数体中的5行代码.
修改的新程序如下://Program2.
14Choosingthecorrecttypeforthejob2#includeintmain(void){constfloatRevenue_Per_150=4.
5f;unsignedshortJanSold=23500;//StocksoldinJanuaryunsignedshortFebSold=19300;//StocksoldinFebruaryunsignedshortMarSold=21600;//StocksoldinMarchfloatRevQuarter=0.
0f;//SalesforthequarterunsignedlongQuarterSold=JanSold+FebSold+MarSold;//Calculatequarterlytotal//Outputmonthlysalesandtotalforthequarterprintf("Stocksoldin\nJan:%d\nFeb:%d\nMar:%d\n",JanSold,FebSold,MarSold);printf("Totalstocksoldinfirstquarter:%ld\n",QuarterSold);//CalculatethetotalrevenueforthequarterandoutputitRevQuarter=QuarterSold/150*Revenue_Per_150;printf("Salesrevenuethisquarteris:$%.
2f\n",RevQuarter);return0;}运行这个程序,这次的输出是正确的:StocksoldinJan:23500Feb:19300Mar:21600Totalstocksoldinfirstquarter:64400Salesrevenuethisquarteris:$1930.
50一季度的销售量是正确的,收入也是正确的.
注意,这里使用%ld输出总销售量,这就告诉编译器,使用long类型输出这个值.
检查程序,用计算器计算出收入.
得到的结果应是$1932,少了$1.
50,这个数字虽然不大,但对于会计而言,这就是一个错误,必须找到少了的$1.
50.
程序在计算收入值时,发生了什么RevQuarter=QuarterSold/150*Revenue_Per_150;这个语句给RevQuarter赋值,该值是等号右边表达式的结果.
根据本章前面介绍的优先级规则,一步步地计算该表达式.
这是一个非常简单的表达式,只需要从左向右计算,因为乘除的优先级相同.
下面列出计算过程:QuarterSold/150计算为64400/150,结果应为429.
333.
这里有问题.
QuarterSold是一个整数,所以计算机将除法运算的结果四舍五入为一个整数,舍弃了.
333.
所以,在下一步计算中,结果会有出入.
429*Revenue_Per_150计算为429*4.
5,结果为1930.
50.
第2章编程初步55知道哪里有错误后,如何更正它可以将所有的变量都改为浮点数类型,但这违背了使用整数的初衷.
输入的数字是整数,所以将它们存储到整数变量中.
有较简单的解决方法吗有两种.
第一种是重写如下的语句:RevQuarter=Revenue_Per_150*QuarterSold/150;这个语句先执行乘法运算,因为对混合的操作数执行算术运算时,编译器会自动把整数操作数转换为浮点数,所以结果是float类型.
对该结果除以150,该操作也在float值上执行,并将150转换为150f.
于是,结果就是正确的.
第二种解决方法是把150.
0作为除数.
于是在除法运算执行之前,把被除数转换为浮点数.
我们不仅需要理解在不同类型的操作数上如何执行算术运算,还要理解如何控制类型的转换.
在C语言中,可以将一种类型显式地转换为另一种类型.
2.
10强制类型转换在程序2.
14计算季度收入的表达式中,可以控制操作的执行,得到正确的结果:RevQuarter=QuarterSold/150*Revenue_Per_150;要使结果正确,必须修改这个语句,以浮点数的方式计算表达式.
如果可以把QuarterSold的值转换为float类型,该表达式就会以浮点数的方式计算,问题就解决了.
要把变量从一种类型转换为另一种类型,应把目标类型放在变量前面的括号中.
因此,正确计算结果的表达式应如下所示:RevQuarter=(float)QuarterSold/150*Revenue_Per_150;这就是我们需要的表达式:在正确的地方使用正确类型的变量.
当希望保留除法结果的小数部分时,不应使用整数运算.
一种类型显式转换为另一种类型的过程称为强制类型转换(cast).
当然,可以把表达式的结果从一种类型强制转换为另一种类型.
此时,应把表达式放在括号中,例如:doubleresult=0.
0;inta=5;intb=8;result=(double)(a+b)/2-(a+b)/(double)(a*a+b*b);把(a+b)的计算结果转换为double,就确保除2运算用浮点数的方式进行.
值2会转换为double类型,与除法运算的左操作数类型相同.
把除数(a*a+b*b)的整数结果强制转换为double,其效果与第二个除法运算类似;在执行除法运算之前,把左操作数的值转换为double类型.
C语言入门经典(第5版)562.
10.
1自动转换类型该程序的第二个版本的输出如下:Salesrevenuethisquarteris:$1930.
50即使表达式中没有显式转换类型,结果也是浮点数形式,但它仍是错误的.
结果是浮点数,是因为二元运算符要求其操作数有相同的类型.
编译器在处理涉及不同类型的值操作时,会自动把其中一个操作数的类型转换为另一个操作数的类型.
在二元算术运算中使用不同类型的操作数,编译器就会把其中一个值域较小的操作数类型转换为另一个操作数的类型,这称为隐式类型转换(implicitconversion).
再看看前面计算收入的表达式:QuarterSold/150*Revenue_Per_150它计算为64400(int)/150(int),,结果是429(int),再将429(int转换为float)乘以4.
5(float),得到1930.
5(float).
当二元运算符处理不同类型(包括不同的整数类型)的操作数时,总是会进行隐式类型转换.
对于上述第一个操作,两个数字都是int类型,所以结果也是int类型.
对于上述第二个操作,第一个值的类型是int,第二个值的类型是float.
而int类型的值域小于float类型,所以自动将int类型的值转换为float类型.
只要算术表达式中有混合类型的变量,C编译器就会使用一组特殊的规则,确定表达式如何计算.
下面就介绍这些规则.
2.
10.
2隐式类型转换的规则确定二元运算中的哪个操作数要转换为另一个操作数的类型时,其机制相当简单.
其基本规则是,将值域较小的操作数类型转换为另一个操作数类型,但在一些情况下,两个操作数都要转换类型.
为了准确地表述这些规则,需要比上述更复杂的描述,所以可以忽略一些细节,在以后需要时再考虑它们.
如果读者想了解全部规则,应继续阅读下去.
编译器按顺序采用如下规则,确定要使用的隐式类型转换:(1)如果一个操作数的类型是longdouble,就把另一个操作数转换为longdouble类型.
(2)否则,如果一个操作数的类型是double,就把另一个操作数转换为double类型.
(3)否则,如果一个操作数的类型是float,就把另一个操作数转换为float类型.
(4)否则,如果两个操作数的类型都是带符号的整数或无符号的整数,就把级别较低的操作数转换为另一个操作数的类型.
无符号整数类型的级别从低到高为:signedchar,short,int,long,longlong每个无符号整数类型的级别都与对应的带符号整数类型相同,所以unsignedint类型第2章编程初步57的级别与int类型相同.
(5)否则,如果带符号整数类型的操作数级别低于无符号整数类型的级别,就把带符号整数类型的操作数转换为无符号整数类型.
(6)否则,如果带符号整数类型的值域包含了无符号整数类型所表示的值,就把无符号整数类型转换为带符号整数类型.
(7)否则,两个操作数都转换为带符号整数类型对应的无符号整数类型.
2.
10.
3赋值语句中的隐式类型转换赋值运算符右边的表达式值与其左边的变量有不同的类型时,也可以进行隐式类型转换.
在一些情况下,这会截短数值,丢失数据.
例如,如果赋值操作将float或double类型的值存储在int或long类型的变量中,float或double的小数部分就会丢失,只存储整数部分.
如下面的代码所示:intnumber=0;floatvalue=2.
5f;number=value;存储在number中的值是2.
这几行代码把decimal的值(2.
5)赋予int类型的变量number,就丢失了小数部分.
5,只存储了2.
赋值语句可能丢失信息,因为必须进行隐式类型转换,而编译器通常会为此发出一个警告.
但是,代码仍可以编译,所以程序可能会得到不正确的结果.
当需要在代码中进行可能导致丢失信息的类型转换时,最好使用显式类型转换.
下面的例子将说明赋值操作中的类型转换规则,代码如下:doubleprice=10.
0;//Productpriceperunitlongcount=5L;//Numberofitemsfloatship_cost=2.
5F;//Shippingcostperorderintdiscount=15;//Discountaspercentagelongdoubletotal_cost=(count*price+ship_cost)*((100L-discount)/100.
0F);这些语句声明了4个变量,并根据给这些变量设置的值计算某个订单的总价.
这里选择的类型主要用于演示隐式类型转换,它们不表示正常环境下的正确类型选择.
下面看看最后一个语句如何计算total_cost的值:(1)先计算count*price,再将count隐式转换为double类型,以进行乘法运算,结果是double类型,这源于第2个规则.
(2)接着将ship_cost加到前一个操作的结果中.
为此,要将ship_cost的值转换为前一个结果的类型double.
这个转换也源于第2个规则.
(3)然后计算表达式100L-discount,为此,要将discount的值转换为减法操作中另一个操作数的类型long.
这源于第4个规则,结果是long类型.
(4)之后把上一个操作的结果(long类型)转换为float类型,再除以100.
0F(float类型).
这源于第3个规则,结果是float类型.
(5)将第2步的结果除以第4步的结果,为此,要将上一个操作的float值转换为C语言入门经典(第5版)58double类型,这源于第3个规则,结果是double类型.
(6)最后,将上述结果存储在total_cost变量中,作为赋值操作的结果.
当操作数的类型不同时,赋值操作总是要把右操作数的结果转换为左操作数的类型,所以上述操作的结果会转换为longdouble类型.
编译器不会发出警告,因为double类型的所有值都可以表示为longdouble类型.
警告:如果在代码中必须进行许多强制类型转换,存储数据的类型就可能选错了.
2.
11再谈数值数据类型为了完整论述数值数据类型,下面讨论一些前面未提及的内容.
第一个未涉及的类型是char.
char类型的变量可以存储单个字符的代码.
它只能存储一个字符代码(即一个整数),所以被看为整数类型.
可以像其他整数类型那样处理char类型存储的值,因此可以在算术运算中使用它.
2.
11.
1字符类型在所有数据的类型中,char类型占用的内存空间最少.
它一般只需一个字节.
存储在char类型变量的整数可以表示为带符号或无符号的值,这取决于编译器.
若表示为无符号的类型,则存储在char类型变量的值可以是0~255.
若表示为带符号的类型,则存储在char类型变量的值可以是–128~127.
当然,这两个值域对应相同的位模式:00000000到11111111.
对于无符号的值,这8位都是数据位,所以00000000对应于0,11111111对应于255.
对于带符号的值,最左边的1位是符号位,所以–128的二进制值是10000000,0的二进制值是00000000,127的二进制值是01111111.
值11111111是一个带符号的二进制值,其对应的十进制值是–1.
从表示字符代码(位模式)的角度来看,char类型是否带符号并不重要.
重要的是何时对char类型的值执行算术运算.
可以给char类型的变量指定字符常量,作为其初始值.
字符常量是一个放在单引号中的字符.
下面是一些例子:charletter='A';chardigit='9';charexclamation='!
';也可以使用转义序列指定字符常量,例如:charnewline='\n';chartab='\t';charsingle_quote='\'';当然,上面的每个语句都把变量设置为单引号内的字符代码.
实际的代码值取决于第2章编程初步59计算机环境,但最常见的是美国标准信息交换码(ASCII).
ASCII字符集参见附录B.
还可以用整数值初始化char类型的变量,只要该值在编译器许可的char类型的值域内即可,如下面的例子:charcharacter=74;//ASCIIcodefortheletterJchar类型的变量有双重性:可以把它解释为一个字符,也可以解释为一个整数.
下面的例子对char类型的值进行算术运算:charletter='C';//lettercontainsthedecimalcodevalue67letter=letter+3;//letternowcontains70,whichis'F'因此,可以对char类型的值进行算术运算,同时仍把它当做一个字符.
注意:无论char类型是实现为带符号还是不带符号的类型,char、signedchar和unsignedchar类型都是不同的,需要进行转换,才能把一种类型映射到另一种类型.
2.
11.
2字符的输入输出使用scanf()函数和格式说明符%c,可以从键盘上读取单个字符,将它存储在char类型的变量中,例如:charch=0;scanf("%c",&ch);//Readonecharacter如前所述,在使用scanf()函数的源文件中,必须给头文件添加#include指令:#include要使用printf()函数将单个字符输出到命令行上,也可以使用格式说明符%c:printf("Thecharacteris%c\n",ch);当然,也可以输出该字符的数值:printf("Thecharacteris%candthecodevalueis%d\n",ch,ch);这个语句会把ch的值输出为一个字符和一个数值.
试试看:字符的建立编程新手可能想知道,计算机如何知道它处理的是字符还是整数事实是计算机并不知道.
这就好像Alice使用HumptyDumpty(矮胖的人)时,会说,"我使用这个单词时,就意味着我给它赋予了"矮胖的人"这个含义.
"同样,内存中的一个数据项的含义是我们赋予它的.
包含值70的字节是一个整数,把70看作字母J的代码也是正确的.
下面的例子会说明这一点.
这个例子使用转换说明符%c,它指定将char类型的值输出为一个字符,而不是一个整数.
C语言入门经典(第5版)60//Program2.
15Charactersandnumbers#includeintmain(void){charfirst='T';charsecond=63;printf("Thefirstexampleasaletterlookslikethis-%c\n",first);printf("Thefirstexampleasanumberlookslikethis-%d\n",first);printf("Thesecondexampleasaletterlookslikethis-%c\n",second);printf("Thesecondexampleasanumberlookslikethis-%d\n",second);return0;}这个程序的输出如下:Thefirstexampleasaletterlookslikethis-TThefirstexampleasanumberlookslikethis-84Thesecondexampleasaletterlookslikethis-Thesecondexampleasanumberlookslikethis-63代码的说明这个程序首先声明了两个char类型的变量:charfirst='T';charsecond=63;把第一个变量初始化为一个字符处理,第二个变量初始化为一个整数.
接下来的4个语句以两种方式输出每个变量的值:printf("Thefirstexampleasaletterlookslikethis-%c\n",first);printf("Thefirstexampleasanumberlookslikethis-%d\n",first);printf("Thesecondexampleasaletterlookslikethis-%c\n",second);printf("Thesecondexampleasanumberlookslikethis-%d\n",second);%c转换说明符将变量的内容解释为单个字符,%d说明符把它解释为一个整数.
输出的数值是对应字符的代码.
这个例子中的这些代码都是ASCII码.
在大多数情况下字符代码都是ASCII码,所以本书都使用ASCII码.
提示:如前所述,并不是所有的计算机都使用ASCII字符集,所以可能会得到与上述不同的值.
但只要给字符常量使用了符号字符,无论采用什么字符编码,都会得到所需的字符.
用格式说明符%x替代%d,就可以把char类型变量的整数值输出为十六进制值.
第2章编程初步61试试看:用字符的对应整数值进行算术运算下面的例子将算术运算应用于char类型的值://Program2.
16Usingtypechar#includeintmain(void){charfirst='A';charsecond='B';charlast='Z';charnumber=40;charex1=first+2;//Add2to'A'charex2=second-1;//Subtract1from'B'charex3=last+2;//Add2to'Z'printf("Charactervalues%-5c%-5c%-5c\n",ex1,ex2,ex3);printf("Numericalequivalents%-5d%-5d%-5d\n",ex1,ex2,ex3);printf("Thenumber%disthecodeforthecharacter%c\n",number,number);return0;}运行这个程序,输出如下:CharactervaluesCA\Numericalequivalents676592Thenumber40isthecodeforthecharacter(代码的说明这个程序说明了如何对初始化为字符的char变量进行算术运算.
main()函数体中的前3个语句如下:charfirst='A';charsecond='B';charlast='Z';这些语句把变量first、second和last初始化为字符值.
这些变量的数值是各个字符对应的ASCII码.
它们可以看做数值和字符,所以可以对它们执行算术运算.
下一个语句用一个整数值初始化char类型的变量:charnumber=40;初始值必须在单字节变量可以存储的值域内.
对于笔者的编译器,char是一个带符号的类型,所以其值必须在128~127之间.
当然,也可以将该变量的内容解释为字符.
在这个例子中,它是一个ASCII码为40的字符,即左括号.
接下来的3个语句又声明了3个char类型的变量:charex1=first+2;//Add2to'A'charex2=second-1;//Subtract1from'B'charex3=last+2;//Add2to'Z'这些语句根据变量first、second和last中存储的值计算出新值,也就计算出了对应的新字符.
这些表达式的结果存储在变量ex1、ex2和ex3中.
C语言入门经典(第5版)62之后的两个语句以两种不同的方式输出3个变量ex1、ex2和ex3:printf("Charactervalues%-5c%-5c%-5c\n",ex1,ex2,ex3);printf("Numericalequivalents%-5d%-5d%-5d\n",ex1,ex2,ex3);第一个语句使用%5c转换说明符把所存储的值解释为字符.
它指定把值输出为字符,且左对齐,字符宽度为5.
第二个语句又输出了这些变量,但这次使用%5d说明符把这些值解释为整数.
对齐方式和字符宽度与第一个语句相同,但%5d中的d指定输出是一个整数.
在这两行输出中,第一行显示3个字符,第二行显示它们的ASCII码.
最后一行代码将number变量输出为一个字符和一个整数:printf("Thenumber%disthecodeforthecharacter%c\n",number,number);变量要输出两次,只需要编写两次即可——printf()函数的第二和第三个参数.
它先输出一个整数,再输出一个字符.
对字符执行算术运算的功能是很有用的.
例如,要把大写字母转换为小写,只要给大写字母加上'a'-'A'的结果(ASCII码32)即可.
要把小写字母转换为大写,只要减去'a'-'A'.
附录B列出了字母字符的十进制ASCII值.
当然,这个操作要求a~z和A~Z的字符代码是连续的整数.
如果计算机使用的字符编码不是连续的整数,就不能这么做.
注意:标准库ctype.
h头文件提供的toupper()和tolower()函数可以把字符转换为大写和小写.
2.
11.
3枚举在编程时,常常希望变量存储一组可能值中的一个.
例如一个变量存储表示当前月份的值.
这个变量应只存储12个可能值中的一个,分别对应于1~12月.
C语言中的枚举(enumeration)就用于这种情形.
利用枚举,可以定义一个新的整数类型,该类型变量的值域是我们指定的几个可能值.
下面的语句定义了一个枚举类型Weekday:enumWeekday{Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};这个语句定义了一个类型,而不是变量.
新类型的名称Weekday跟在关键字enum的后面,这个类型名称称为枚举的标记.
Weekday类型的变量值可以是类型名称后面的大括号中的名称指定的任意值.
这些名称叫做枚举器(enumerator)或枚举常量(enumerationconstant),其数量可任意.
每个枚举器都用我们赋予的唯一名称来指定,编译器会把int类型的整数值赋予每个名称.
枚举是一个整数类型,因为指定的枚举器对应不同的整数值,这些整数默认从0开始,每个枚举器的值都比它之前的枚举器大1.
因此在这个例子中,Monday到Sunday对应0~6.
可以声明Weekday类型的一个新变量,并初始化它,如下所示:enumWeekdaytoday=Wednesday;第2章编程初步63这个语句声明了一个变量today,将它初始化为Wednesday.
由于枚举器有默认值,所以Wednesday对应2.
用于枚举类型变量的整数类型是由实现代码确定的,选择什么类型取决于枚举器的个数.
也可以在定义枚举类型时,声明该类型的变量.
下面的语句就定义了一个枚举类型和两个变量:enumWeekday{Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday}today,tomorrow;这个语句声明了枚举类型Weekday,定义了该类型的两个变量today和tomorrow.
还可以在同一个语句中初始化变量,如下所示:enumWeekday{Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday}today=Monday,tomorrow=Tuesday;这个语句把变量today和tomorrow初始化为Monday和Tuesday.
枚举类型的变量是整数类型,所以可以在算术表达式中使用.
前面的语句还可以写为:enumWeekday{Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday}today=Monday,tomorrow=today+1;tomorrow的初始值比today大1.
但是,在执行这个操作时,要确保算术运算的结果是一个有效的枚举值.
注意:可以给枚举类型指定一组可能的值,但没有检查机制来确保程序只使用这些值.
所以程序员要确保只为给定的枚举类型使用有效的枚举值.
一种方式是只给枚举类型的变量赋予枚举常量名.
1.
选择枚举值可以给任意或所有枚举器明确指定自己的整数值.
尽管枚举器使用的名称必须唯一,但枚举器的值不要求是唯一的.
除非有特殊的原因让某些枚举器的值相同,否则一般应确保这些值也是唯一的.
下面的例子定义了Weekday类型,使其枚举器的值从1开始:enumWeekday{Monday=1,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};枚举器Monday到Sunday的对应值是1~7.
在明确指定了值的枚举器后面,枚举器会被赋予连续的整数值.
这可能使枚举器有相同的值,如下面的例子所示:enumWeekday{Monday=5,Tuesday=4,Wednesday,Thursday=10,Friday=3,Saturday,Sunday};Monday、Tuesday、Thursday和Friday明确指定了值,Wednesday设置为Tuesday+1,所以它是5,Monday与它相同.
同样,Saturday和Sunday设置为4和5,所以它们的值也是重复的.
完全可以这么做,但除非有很好的理由使一些枚举常量的值相同,否则这容易出现混淆.
C语言入门经典(第5版)64只要希望变量有限定数量的可能值,就可以使用枚举.
下面是定义枚举的另一个例子:enumSuit{clubs=10,diamonds,hearts,spades};enumSuitcard_suit=diamonds;第一个语句定义了枚举类型Suit,这个类型的变量可以有括号中的4个值的任意一个.
第二个语句定义了Suit类型的一个变量,把它初始化为diamonds,其对应的值是11.
还可以定义一个枚举,表示扑克牌的面值,如下所示:enumFaceValue{two=2,three,four,five,six,seven,eight,nine,ten,jack,queen,king,ace};在这个枚举中,枚举器的整数值匹配扑克牌的面值,其中ace的值最高.
在输出枚举类型的变量值时,会得到数值.
如果要输出枚举器的名称,必须提供相应的程序逻辑,详见下一章的内容.
2.
未命名的枚举类型在创建枚举类型的变量时,可以不指定标记,这样就没有枚举类型名了.
例如:enum{red,orange,yellow,green,blue,indigo,violet}shirt_color;这里没有标记,所以这个语句定义了一个未命名的枚举类型,其可能的枚举器包括从red到violet.
该语句还声明了未命名类型的变量shirt_color.
可以用通常的方式给shirt_color赋值:shirt_color=blue;显然,未命名枚举类型的主要限制是,必须在定义该类型的语句中声明它的所有变量.
由于没有类型名,因此无法在代码的后面定义该类型的其他变量.
2.
11.
4存储布尔值的变量_Bool类型存储布尔值.
布尔值一般是比较的结果true或false;第3章将学习比较操作,并使用其结果做出判断.
_Bool类型的变量值可以是0或1,对应于布尔值false和true.
由于值0和1是整数,所以_Bool类型也被看为整数类型.
声明_Bool变量的方式与声明其他整数类型一样,例如:_Boolvalid=1;//Booleanvariableinitializedtotrue_Bool并不是一个理想的类型名称.
名称bool看起来更简洁、可读性更高,但布尔类型是最近才引入C语言的,所以选择类型名称_Bool,可以最大限度地减少与已有代码冲突的可能性.
如果把bool选作类型名称,则在将bool作为一种内置类型的编译器上,使用bool名称的程序大都不会编译.
尽管如此,仍可以使用bool作为类型名称,只需在使用它的源文件中给标准头文件添加#include指令即可.
除了把bool定义为_Bool的对应名称之外,第2章编程初步65头文件还定义了符号true和false,分别对应1和0.
因此,如果在源文件中包含了这个头文件,就可以将上面的声明语句改写为:_Boolvalid=1;//Booleanvariableinitializedtotrue这似乎比上面的版本清晰得多,所以最好包含头文件,除非有特殊的理由.
本书的其余部分使用bool表示布尔类型,但需要包含相应的头文件,其基本类型名称是_Bool.
可以在布尔值和其他数值类型之间进行类型转换.
非零数值转换为bool类型时,会得到1(true),0就转换为0(false).
如果在算术表达式中使用bool变量,编译器就会在需要时插入隐式类型转换.
bool类型的级别低于其他类型,所以在涉及bool类型和另一个类型的操作中,bool值会转换为另一个值的类型.
这里不详细介绍如何使用布尔变量,具体内容详见下一章.
2.
12赋值操作的op=形式C语言是一种非常简洁的语言,提供了一些操作的缩写形式.
考虑下面的代码:number=number+10;这类赋值操作是给一个变量递增或递减一个数字,它非常常见,所以有一个缩写形式:number+=10;变量名后面的+=运算符是op=运算符家族中的一员.
这个语句等价于上面的语句,但输入量少了许多.
op=中的op可以是任意算术运算符:如果number的值是10,就可以编写如下语句:number*=3;//numberwillbesettonumber*3whichis30number/=3;//numberwillbesettonumber/3whichis3number%=3;//numberwillbesettonumber%3whichis1op=中的op也可以是其他几个运算符:第3章将介绍这些运算符.
op=运算符的工作方式都相同.
如果有如下形式的语句:lhsop=rhs;其中rhs表示op=运算符右边的表达式,该语句的作用与如下形式的语句相同:lhs=lhsop(rhs);C语言入门经典(第5版)66注意rhs表达式的括号,它表示op应用于整个rhs表达式的计算结果值.
为了加强理解,下面看几个例子.
下面的语句:variable*=12;等价于:variable=variable*12;现在给一个整数变量加1有两种方式.
下面的两个语句都给count加1:count=count+1;countd+=1;下一章将介绍这个操作的另一种方式.
有这么多选择,使编写C程序的人数无法统计.
op=运算符中的op应用于rhs表达式的计算结果,所以如下语句:a/=b+1;等价于:a=a/(b+1);到目前为止,我们的计算能力比较受限.
现在只能使用一组非常基本的算术运算符.
而使用标准库的功能可以大大提升计算能力.
所以在进入本章的最后一个例子之前,先看看标准库提供的一些数学函数.
2.
13数学函数math.
h头文件包含各种数学函数的声明.
为了了解这些数学函数,下面介绍最常用的函数.
所有的函数都返回一个double类型的值.
表2-11列出了各种用于进行数值计算的函数,它们都需要double类型的参数.
表2-11用于进行数值计算的函数函数操作floor(x)返回不大于x(double类型)的最大整数ceil(x)返回不小于x(double类型)的最小整数fabs(x)返回x的绝对值log(x)返回x的自然对数(底为e)log10(x)返回x的对数(底为10)exp(x)返回ex的值sqrt(x)返回x的平方根pow(x)返回xy的值第2章编程初步67给函数名的末尾添加f或l,就得到处理float和longdouble类型的函数版本,所以ceilf()应用于float值,sqrtl()应用于longdouble值.
下面是使用这些函数的一些例子:doublex=2.
25;doubleless=0.
0;doublemore=0.
0;doubleroot=0.
0;less=floor(x);//Resultis2.
0more=ceil(x);//Resultis3.
0root=sqrt(x);//Resultis1.
5还有一些三角函数,如表2-12所示.
给函数名的末尾添加f或l,就得到处理float和longdouble类型的函数版本,参数和返回值的类型也是float、double或longdouble,角度表示为弧度.
表2-12三角函数函数操作sin(x)x(弧度值)的正弦cos(x)x的余弦tan(x)x的正切如果使用三角法,这些函数的用法非常简单.
下面是一些例子:doubleangle=45.
0;//Angleindegreesdoublepi=3.
14159265;doublesine=0.
0;doublecosine=0.
0;sine=sin(pi*angle/180.
0);//Angleconvertedtoradianscosine=sin(pi*angle/180.
0);//Angleconvertedtoradians180°等于1弧度,所以以度数表示的角度除以180,再乘以PI的值,就得到其弧度值,这些函数都要求使用弧度值.
还可以使用反三角函数:asin()、acos()和atan(),以及双曲线函数sinh()、cosh()和tanh().
如果要使用这些函数,必须在程序中包含math.
h头文件.
如果不需要使用这些函数,就可以跳过本节.
2.
14设计一个程序下面设计本章末的一个真实例子,来试用一些数值类型.
这里将从头开始编写一个程序,涉及编程的所有基本要素,包括问题的初始描述、问题的分析、解决方案的准备、编写程序、运行程序,以及测试它,确保它正常工作.
该过程的每一步都会引入新问题,而不仅仅是纸上谈兵.
C语言入门经典(第5版)682.
14.
1问题许多人都对树的高度很感兴趣.
如果将树砍倒,量出它的高度,就可以确定离树多远才是安全的.
这对于患有神经衰弱的人来说非常重要.
问题是如何不使用非常长的梯子,就可以确定树的高度,因为长梯也会对人和树枝带来危险.
为了确定树的高度,可以向朋友求助,最好找一个个子比较矮的朋友,除非自己比较矮,此时需要一个个子比较高的朋友.
假定要测量的树比自己和朋友都高.
比自己还矮的树很容易测量出其高度,除非这棵树长满了刺.
2.
14.
2分析现实问题很少能用适合于编程的方式来表达.
在编写代码之前,需要确保完全理解了问题及其解决方式.
只有这样,才能估计出创建解决方案所需的时间和精力.
分析阶段应增强对问题的理解,确定解决它的逻辑过程.
一般这需要大量的工作,这包括找出问题阐述中模糊或遗漏的细节.
只有全面理解了问题,才能开始以适合编程的形式表达解决方案.
我们打算用一个简单的图形和两个人(一高一矮)的身高来确定树的高度.
首先给高个子命名为Lofty,矮个子命名为Shorty.
为了得到比较精确的结果,高个子应明显高于矮个子.
否则高个子可以考虑站在一个箱子上.
图2-3给出了解决这个问题的思路.
图2-3树的高度确定树的高度是很简单的.
如果知道图中h1和h2的值(它们分别是Shorty和Lofty的高度)以及d1和d2(它们分别是Shorty与Lofty之间的距离和Lofty与树之间的距离),就可以计算出树的高度.
使用相似三角形的特性就可以求出树的高度,如图2-4所示.
因为三角形是相似的,所以height1:distance1=height2:distance2.
使用这个关系,就可以通过Shorty和Lofty的身高,以及他们与树之间的距离求出树的高度,如图2-5所示.
第2章编程初步69图2-4相似三角形树的高度Lofty头部的高度在相似的三角形ADE和ABC中,高与底之比是相等的因此,树的高度计算公式如下:地平线Shorty眼睛的高度图2-5计算树的高度三角形ADE和ABC与图2-4相同.
由于这两个三角形相似,则一个三角形任意一边的长度除以另一个三角形的对应边长度,结果总是相等的.
所以可以使用图2-5底部的等式计算出树的高度.
这说明,在程序中,可以使用如下4个值计算出树的高度:Shorty与Lofty之间的距离,即图中的d1.
用shorty_to_lofty变量存储这个值.
Lofty与树之间的距离,即图中的d2.
用lofty_to_tree变量存储这个值.
从地平线到Lofty头部的高度,即图中的h2,用lofty变量存储这个值.
从地平线到Shorty眼睛的高度,即图中的h1,用shorty变量存储这个值.
接着,把这些值放在计算树高的等式中.
首先要把这4个值输入计算机.
接着使用其比值计算出树的高度,最后输出答案.
步骤如下:(1)输入需要的值.
(2)使用图2-5中的等式计算树的高度.
(3)显示答案.
C语言入门经典(第5版)702.
14.
3解决方案本节列出解决问题的步骤.
1.
步骤1第一步获取计算树高需要的值.
这意味着必须包含stdio.
h头文件,因为需要使用printf()和scanf()函数.
接着确定存储这些值的变量.
之后,就可以使用printf()提示输入数字,使用scanf()从键盘上读取值.
为了方便用户,把高个子和矮个子的身高输入为英尺英寸值.
但在程序中,高度和距离使用相同的单位会更方便,所以应将所有的数字都转换为英寸值.
我们需要两个变量存储Shorty和Lofty的身高(英寸值),还需要一个变量存储Shorty和Lofty之间的距离,需要另一个变量存储Lofty与树之间的距离,当然,这两个距离值都以英寸为单位.
在输入过程中,首先将Lofty的身高输入为一个整数英尺值和一个英寸值,在此过程中要提示用户输入每个值.
为此可以使用另外两个变量,一个存储英尺值,另一个存储英寸值.
接着把它们转换为英寸值,将结果存储在为Lofty身高保留的变量中.
对Shorty的身高进行相同的处理(但只输入从地平线到Shorty眼睛的高度),最后处理他们之间的距离.
对于Lofty与树之间的距离,可以只使用整数英尺值,因为这已经足够准确了——还要把距离转换为英寸值.
对于每个输入的英尺值和英寸值,可以使用相同的变量.
所以下面是程序的第一部分://Program2.
17Calculatingtheheightofatree#includeintmain(void){longshorty=0L;//Shorty'sheightinincheslonglofty=0L;//Lofty'sheightinincheslongfeet=0L;longinches=0L;longshorty_to_lofty=0L;//DistancefromShortytoLoftyinincheslonglofty_to_tree=0L;//DistancefromLoftytothetreeininchesconstlonginches_per_foot=12L;//GetLofty'sheightprintf("EnterLofty'sheighttothetopofhis/herhead,inwholefeet:");scanf("%ld",&feet);printf(".
.
.
andtheninches:");scanf("%ld",&inches);lofty=feet*inches_per_foot+inches;//GetShorty'sheightuptohis/hereyesprintf("EnterShorty'sheightuptohis/hereyes,inwholefeet:");scanf("%ld",&feet);printf(".
.
.
andtheninches:");scanf("%ld",&inches);shorty=feet*inches_per_foot+inches;第2章编程初步71//GetthedistancefromShortytoLoftyprintf("EnterthedistancebetweenShortyandLofty,inwholefeet:");scanf("%ld",&feet);printf(".
.
.
andtheninches:");scanf("%ld",&inches);shorty_to_lofty=feet*inches_per_foot+inches;//GetthedistancefromLoftytothetreeprintf("FinallyenterthedistancefromLoftytothetreetothenearestfoot:");scanf("%ld",&feet);lofty_to_tree=feet*inches_per_foot;//Thecodetocalculatetheheightofthetreewillgohere//Thecodetodisplaytheresultwillgoherereturn0;}注意,代码进行了缩进,以便于阅读.
这不是必须的,但如果要在未来修改程序,这么做更便于确定程序的工作方式.
应总是给程序添加注释,以帮助理解程序.
至少要清楚地说明变量的用途,解释程序的基本逻辑.
使用一个声明为const的变量将英尺转换为英寸.
该变量的名称是inches_per_foot,说明了它在代码中使用时会发生什么.
这要比明确使用12这个数字好得多.
这里处理的是英尺和英寸,大多数人都知道,12英寸是1英尺.
但在其他环境下,数值常量的重要性没有这么明显.
如果在计算薪水的程序中使用0.
22,它的含义就不是很明显.
因此,这个计算相当难理解.
如果创建一个const变量tax_rate,把它初始化为0.
22,就不会有理解障碍了.
2.
步骤2有了需要的所有数据后,就可以计算树的高度了.
只需利用变量的值,实现计算树高的等式即可.
这里需要声明另一个变量来存储树的高度.
为此,添加如下粗体的代码://Program2.
18Calculatingtheheightofatree#includeintmain(void){longshorty=0L;//Shorty'sheightinincheslonglofty=0L;//Lofty'sheightinincheslongfeet=0L;longinches=0L;longshorty_to_lofty=0L;//DistancefromShortytoLoftyinincheslonglofty_to_tree=0L;//DistancefromLoftytothetreeinincheslongtree_height=0L;//Heightofthetreeininchesconstlonginches_per_foot=12L;//GetLofty'sheightprintf("EnterLofty'sheighttothetopofhis/herhead,inwholefeet:");scanf("%ld",&feet);C语言入门经典(第5版)72printf(".
.
.
andtheninches:");scanf("%ld",&inches);lofty=feet*inches_per_foot+inches;//GetShorty'sheightuptohis/hereyesprintf("EnterShorty'sheightuptohis/hereyes,inwholefeet:");scanf("%ld",&feet);printf(".
.
.
andtheninches:");scanf("%ld",&inches);shorty=feet*inches_per_foot+inches;//GetthedistancefromShortytoLoftyprintf("EnterthedistancebetweenShortyandLofty,inwholefeet:");scanf("%ld",&feet);printf(".
.
.
andtheninches:");scanf("%ld",&inches);shorty_to_lofty=feet*inches_per_foot+inches;//GetthedistancefromLoftytothetreeprintf("FinallyenterthedistancefromLoftytothetreetothenearestfoot:");scanf("%ld",&feet);lofty_to_tree=feet*inches_per_foot;//Calculatetheheightofthetreeininchestree_height=shorty+(shorty_to_lofty+lofty_to_tree)*(lofty-shorty)/shorty_to_lofty;//Thecodetodisplaytheresultwillgoherereturn0;}计算树高的语句与图中的等式相同.
这有点繁琐,但直接转换为程序中的语句,以计算树高.
3.
步骤3最后,输出答案.
为了以最容易理解的形式显示结果,应把存储在tree_height中的结果(英寸值)转换为英尺和英寸值://Program2.
18Calculatingtheheightofatree#includeintmain(void){longshorty=0L;//Shorty'sheightinincheslonglofty=0L;//Lofty'sheightinincheslongfeet=0L;longinches=0L;longshorty_to_lofty=0L;//DistancefromShortytoLoftyinincheslonglofty_to_tree=0L;//DistancefromLoftytothetreeinincheslongtree_height=0L;//Heightofthetreeininchesconstlonginches_per_foot=12L;//GetLofty'sheightprintf("EnterLofty'sheighttothetopofhis/herhead,inwholefeet:");第2章编程初步73scanf("%ld",&feet);printf(".
.
.
andtheninches:");scanf("%ld",&inches);lofty=feet*inches_per_foot+inches;//GetShorty'sheightuptohis/hereyesprintf("EnterShorty'sheightuptohis/hereyes,inwholefeet:");scanf("%ld",&feet);printf(".
.
.
andtheninches:");scanf("%ld",&inches);shorty=feet*inches_per_foot+inches;//GetthedistancefromShortytoLoftyprintf("EnterthedistancebetweenShortyandLofty,inwholefeet:");scanf("%ld",&feet);printf(".
.
.
andtheninches:");scanf("%ld",&inches);shorty_to_lofty=feet*inches_per_foot+inches;//GetthedistancefromLoftytothetreeprintf("FinallyenterthedistancefromLoftytothetreetothenearestfoot:");scanf("%ld",&feet);lofty_to_tree=feet*inches_per_foot;//Calculatetheheightofthetreeininchestree_height=shorty+(shorty_to_lofty+lofty_to_tree)*(lofty-shorty)/shorty_to_lofty;//Displaytheresultinfeetandinchesprintf("Theheightofthetreeis%ldfeetand%ldinches.
\n",tree_height/inches_per_foot,tree_height%inches_per_foot);return0;}程序的输出如下:EnterLofty'sheighttothetopofhis/herhead,inwholefeetfirst:6.
.
.
andtheninches:2EnterShorty'sheightuptohis/hereyes,inwholefeet:4.
.
.
andtheninches:6EnterthedistancebetweenShortyandLofty,inwholefeet:5.
.
.
andtheninches:0Finallyenterthedistancetothetreetothenearestfoot:20Theheightofthetreeis12feetand10inches.
2.
15小结本章介绍了许多基础知识,讨论了C程序的构建方式、各种算术运算、如何选择合适的变量类型等.
除了算术运算之外,还学习了输入输出功能,通过scanf()将值输入变量,通过printf()函数把文本、字符值和数值变量输出到屏幕上.
读者可能不能第一次就掌握所有这些内容,但可以在需要时复习本章.
下一章将开始学习如何根据输入值做出判断,控制程序的执行.
这是创建有趣且专C语言入门经典(第5版)74业化程序的关键.
表2-13总结了前面介绍的变量类型.
在学习本书的过程中,可以随时复习这些内容.
表2-13变量类型和值域类型字节数值域char1–128~+127或0~+255unsignedchar10~+255short2–32768~+32767unsignedshort20~+65,535int4–32768~+32767或–2147438648~+2147438647unsignedint40~+65535或0~+4294967295long4–2147438648~+2147438647unsignedlong40~+4294967295longlong8–9223372036854775808到+9223372036854775807unsignedlonglong80~+18446744073709551615float4±3.
4E±38(6位)double8±1.
7E±308(15位)longdouble12±1.
2E±4932(19位)本章还介绍并使用了printf()函数的数据输出格式说明符,完整的说明符列表请参见附录D.
附录D还描述了输入格式说明符,它们用于控制使用scanf()函数从键盘上读取数据时这些数据的解释方式.
当无法确定如何处理输入或输出数据时,可以参阅附录D.
2.
16练习以下的习题可测试读者对本章的掌握情况.
如果有不懂的地方,可以翻看本章的内容.
还可以从Apress网站http://www.
apress.
com的SourceCode/Download部分下载答案,但这应是最后一种方法.
习题2.
1编写一个程序,提示用户用英寸输入一个距离,然后将该距离值输出为码、英尺和英寸的形式(12英寸是1英尺,3英尺是1码).
习题2.
2编写一个程序,提示用户用英尺和英寸输入一个房间的长和宽,然后计算并输出面积,单位是平方码,精度为小数点后有两位数.
习题2.
3一个产品有两种版本:其一是标准版,价格是$3.
5,其二是豪华版,价格是$5.
5.
编写一个程序,使用学到的知识提示用户输入产品的版本和数量,然后根据输入的产品数量,计算并输出价格.
习题2.
4编写一个程序,提示用户从键盘输入一个星期的薪水(以美元为单位)和工作时数,它们均为浮点数,然后计算并输出每个小时的平均薪水,输出格式如下所示:Youraveragehourlypayrateis7dollarsand54cents.
最近很多网站都遭受到了伪墙/假墙攻击,导致网站流量大跌,间歇性打不开网站。这是一种新型的攻击方式,攻击者利用GWF规则漏洞,使用国内服务器绑定host的方式来触发GWF的自动过滤机制,造成GWF暂时性屏蔽你的网站和服务器IP(大概15分钟左右),使你的网站在国内无法打开,如果攻击请求不断,那么你的网站就会是一个一直无法正常访问的状态。常规解决办法:1,快速备案后使用国内服务器,2,使用国内免备案服...
农历春节将至,腾讯云开启了热门爆款云产品首单特惠秒杀活动,上海/北京/广州1核2G云服务器首年仅38元起,上架了新的首单优惠活动,每天三场秒杀,长期有效,其中轻量应用服务器2G内存5M带宽仅需年费38元起,其他产品比如CDN流量包、短信包、MySQL、直播流量包、标准存储等等产品也参与活动,腾讯云官网已注册且完成实名认证的国内站用户均可参与。活动页面:https://cloud.tencent.c...
优林怎么样?优林好不好?优林 是一家国人VPS主机商,成立于2016年,主营国内外服务器产品。云服务器基于hyper-v和kvm虚拟架构,国内速度还不错。今天优林给我们带来促销的是国内东北地区哈尔滨云服务器!全部是独享带宽!首月5折 续费5折续费!地区CPU内存硬盘带宽价格购买哈尔滨电信2核2G50G1M53元直达链接哈尔滨电信4核4G50G1M83元直达链接哈尔滨电信8核8G50G1M131元直...
免费建论坛为你推荐
操作httpapple.com.cn苹果官网怎么查序列号wordpress模板wordpress后台默认模板管理在哪里?空间文章空间里一些比较好的文章。。cisco2960配置思科的交换机怎么配置filezillaserverFileZilla Server 搭建的FTP服务器360arp防火墙在哪arp防火墙在哪开额- -360里是哪个?滴滴估值500亿滴滴拉屎 App 为何能估值 100 亿美金?是怎么计算出来的抢米网什么意思抢小米手机加多宝和王老吉加多宝和王老吉什么关系 王老吉和加多宝哪个正宗
租服务器 dns是什么 新世界机房 parseerror 免费全能空间 三拼域名 hostker 卡巴斯基试用版 可外链相册 万网空间购买 in域名 环聊 海外空间 西安服务器托管 酸酸乳 腾讯网盘 阿里云邮箱申请 脚本大全 时间服务器 apachetomcat 更多