菜鸟学编程(C语言版)

上传人:qt****68 文档编号:44246530 上传时间:2018-06-09 格式:DOC 页数:92 大小:1.22MB
返回 下载 相关 举报
菜鸟学编程(C语言版)_第1页
第1页 / 共92页
菜鸟学编程(C语言版)_第2页
第2页 / 共92页
菜鸟学编程(C语言版)_第3页
第3页 / 共92页
菜鸟学编程(C语言版)_第4页
第4页 / 共92页
菜鸟学编程(C语言版)_第5页
第5页 / 共92页
点击查看更多>>
资源描述

《菜鸟学编程(C语言版)》由会员分享,可在线阅读,更多相关《菜鸟学编程(C语言版)(92页珍藏版)》请在金锄头文库上搜索。

1、 菜鸟学编程 五袋老丐著 第一章 计算机的基本工作原理(初)一种有着神奇的“魔力”和“智能”的人造设备,正在迅速地、彻底地、默默无闻或者令人震惊地改变和丰富我们所生活的大千世界。这个看起来很不起眼的,在一些场合被称为“电脑”的电器设备,是如何具有如此神奇的“魔力”和“智能”的?本章将带你开始解开这个谜。11节介绍理想厨房系统,1.2节通过一个炒菜实例讲解理想厨房各部件是如何密切配合工作的。1.3是一张理想厨房系统与计算机系统的对照表1.4到1.8介绍了二进制、计算机、机器指令和指令集、数和码。1.09对计算机的重要特点进行了概括性的总结、1.11是计算机与理想厨房的一些重要区别。其中1.2、1

2、.3、1.4、1.9和1.11是本章重点。电子数字计算机从发明到现在,不过大约70年的时间。然而计算机的发明、改进和普及,却彻底地变革了人类社会。计算机本身也变得越来越复杂、快速、小巧、种类繁多。但大多数计算机都遵循冯.诺伊曼体系结构。这为我们理解大多数计算机的基本工作原理提供了方便。本章是全书的重要基础。通过本章,为你真正揭开计算机究竟如何工作的神秘面纱。(但是要直到12.4、12.5节,才能彻底揭开计算机硬件如何在操作系统这个系统软件的调度管理下,并行运行多道程序的内在奥秘。)纠正你对计算机可能存在的一些错误认识和看法。并且为第2、12、13章的学习打下牢固的知识基础。本章不需要任何的预备

3、知识,只要你能把本章认真仔细地学习两遍,你就能够真正懂得看似极为神秘莫测、奥妙无穷,到目前为止人类有史以来最伟大、最神奇而又最为复杂的发明计算机(又称为“电脑”。这个伟大发明,汇集了几代人中很多杰出人士的聪明才智和研究成果)是如何工作的基本原理。为达此目的,笔者付出了极大的努力,找到了一种比较好的比喻方法理想厨房系统,由此开始我们的真正理解计算机工作原理的,激动人心的探索旅程。1.1理想厨房系统:理想厨房系统,是一个通过顺序执行菜谱中的各个加工步骤,把原材料加工成菜肴的系统。它由硬件(理想厨房、自动冰箱和三条传送带)和软件(菜谱)组成。1) 硬件部分:理想厨房系统,主要由以下三个硬件部件(即实

4、物部件)构成理想厨房、自动冰箱和三条传送带。自动冰箱:由非常多的大小一样的格子组成,每个格子都有一个唯一的编号,这个编号是从0开始逐1递增的。自动冰箱负责临时保存菜谱、原材料和菜肴。是不是很奇怪?菜谱竟然要和原材料一道,统一存放在冰箱中!三条传送带:负责理想厨房与自动冰箱之间的通信及物品(菜谱中的加工步骤、原料和菜肴)传送。理想厨房:负责根据从菜谱中取到的加工步骤,进行炒菜以及进行相关的控制工作。2) 软件部分:菜谱是理想厨房系统中一个无重量、无体积、不会损坏、可经常更换的,但又极为重要的软件部件(即信息流部件)。菜谱由一个一个的加工步骤顺序组成。注意:术语“理想厨房系统”与“理想厨房”是有区

5、别的,理想厨房只是理想厨房系统中的一个组成部分。理想厨房系统的构成简图如图1.1所示:理想厨房 自动冰箱 碟名 碟中物品 格子编号 格子(存放加工步骤或原料)R0R1 厨具R2 厨师PC 厨房管理员IR 材料传送带地址传送带控制传送带 01234567 图1.1理想厨房系统的构成简图(但图中没有放入软件)理想厨房中各种碟子的作用R0、R1、R2碟是一些通用临时存放碟,用来临时存放从冰箱中取来的原材料或经过加工了的半成品或成品。PC碟:存放一个大于或等于0的整数值,这个值表示:下一个要执行的加工步骤,位于自动冰箱的哪一格中。IR碟:用来存放从冰箱中刚取过来的一个(立刻要执行的)加工步骤。 理想厨

6、房执行菜谱中的加工步骤,其流程完全是周期性的。 厨房管理员首先要根据PC碟中的值,通过三套传送带的协调工作,到自动冰箱的指定格中去取菜谱中的一个加工步骤。取到理想厨房并把它放到IR碟中后,PC碟中的值将会加上1(这是为取下一个加工步骤做好准备)。然后,厨房管理员阅读并分析IR碟中刚取到的加工步骤。根据此加工步骤的指示,去做以下七类工作中的一种: 1通过三套传送带,命令自动冰箱把指定地址格子中的(炒菜加工步骤马上要用到的)原材料(通过材料传送带)传送到理想厨房中来即取物品;2命令厨师按照指令(即加工步骤)的要求,对原材料作一个基本加工操作(做“炒”,“蒸”,“煮”等基本操作步骤中的一个炒菜动作)

7、即加工原材料。 3通过向三套传送带向自动冰箱发命令,把某个碟子中的成品(或半成品)送回到冰箱指定的格子中存放即存物品; 4在厨房的各个碟子或炊具之间传送原料或半成品在厨房内部进行物品传送 ;5根据某个状态碟中的具体条件(比如:加工步骤的预定时间到了、温度够了),不按正常顺序取出并且执行下一条指令,而是根据此指令中给出的(冰箱格子的相对或绝对)地址,跳转到那一格中去取下一条指令即进行(有条件的)跳转。6命令配菜员为某菜配原材料;即输入(在本章不讨论,见第12章)。7命令传菜生端菜给顾客;即输出(在本章不讨论)。 一个加工步骤执行完后,理想厨房立即自动进行下一个完全类似的、新的 取加工步骤执行加工

8、步骤的周期。 注意:为了解说简洁起见,在以下叙述中,我们经常把一个加工步骤称为一条指令,因为一个加工步骤就是一条指导理想厨房如何工作的命令。理想厨房一次只能执行一条指令。一条指令的执行周期,可分为取指子周期和执行子周期这两个阶段。下面我们通过一个实例来讲述理想厨房系统的工作机制。这是本章的一个重点,因为计算机的工作原理,与之极其相似。1.2理想厨房系统的一个炒菜实例(此节最好看光盘中的视频)121现在,我们通过炒制一道香菇炒菜心的例子来说明理想厨房系统的工作过程。 首先,把香菇放在冰箱地址为5的格子中,把菜心放在冰箱地址为6的格子中,冰箱地址为7号的格子,预留给炒好的菜使用。首先,把香菇放在冰

9、箱地址为5的格子中,把菜心放在冰箱地址为6的格子中,冰箱地址为7号的格子,预留给炒好的菜使用。 菜谱的所有加工步骤(又称为指令)从冰箱地址0号格开始依次按照顺序存放,编写香菇炒菜心的菜谱如下:地址0的格子中: 取地址5(中的物品)到R0碟;地址1的格子中: 取地址6(中的物品)到R1碟;地址2的格子中: 将R0碟和R1碟倒入炒锅中炒好倒回R0碟;地址3的格子中: 送R0碟(中的物品)到地址7中; 可见,一共有4个加工步骤。开始时理想厨房系统状态如下图1.3(注意:冰箱格子以及理想厨房碟子中存放的物品都用了斜体字)理想厨房 自动冰箱 碟名 碟中物品 地址 冰箱格子中物品R0R1 厨具R2 厨师P

10、C 0 厨房管理员IR 材料传送带地址传送带0控制传送带 取01234567取地址5到R0碟取地址6到R1碟;将R0和R1炒好倒入R2碟;送R2碟到地址7中;香菇菜心 图1.3菜谱设置完毕后,理想厨房系统开始自动化的工作。1),厨房管理员根据PC碟子中的数字“0”,知道要到冰箱地址为0的格子中取第一条指令(即加工步骤)。于是,厨房管理员向控制传送带上送出一个“取”信号,然后马上将PC碟中的数字“0”复制后放到地址传送带上。这两个信号都会到达自动冰箱。自动冰箱收到这两个信号后,将0号格的内容复印件“取地址5到R0碟”放到材料传送带上,送往理想厨房。理想厨房收到后,将这条指令放到IR碟中。然后,厨

11、房管理员将PC碟中的原来值增加1,以便为取下一条指令做好准备。取指令周期完成后,理想厨房系统处于如下图1.4状态: 理想厨房 自动冰箱 碟名 碟中物品 地址 冰箱格子中物品R0R1 厨具R2 厨师PC 1 厨房管理员IR 取地址5到R0碟 材料传送带地址传送带控制传送带01234567取地址5到R0碟取地址6到R1碟;将R0和R1炒好倒入R2碟;送R2碟到地址7中;香菇菜心 图1.4 厨房管理员读到指令存放碟(即IR碟)中的加工步骤后,知道要到冰箱地址号为5的格子中去取原材料,并且取来后要放到R0碟中。因此,管理员向控制传送带上送出一个“取”信号,然后马上将5这个数放到地址传送带上。 冰箱收到

12、“取”信号后,知道理想厨房要取物品。然后,冰箱从地址传送带得到了5,于是冰箱将地址为5的格子中的物品“香菇”取出来,放到材料传送带上。 材料传送带上的物品“香菇”,传到理想厨房后,按照指令的要求(通过厨房内部的传送带)送到了R0碟中。第一条指令执行完后,理想厨房系统处于如下图1.5所示的状态:理想厨房 自动冰箱 碟名 碟中物品 地址 冰箱格子中物品R0 香菇R1 厨具R2 厨师PC 1 厨房管理员IR 取地址5到R0碟 材料传送带 地址传送带 5控制传送带 取01234567取地址5到R0碟取地址6到R1碟;将R0和R1炒好倒入R2碟;送R2碟到地址7中;香菇菜心图1.52)接下来,开始执行下

13、一条指令的取指周期,类似于前一条指令,在取指周期完成后,理想厨房系统处于如下图1.6状态:理想厨房 自动冰箱 碟名 碟中物品 地址 冰箱格子中物品R0 香菇R1 厨具R2 厨师PC 2 厨房管理员IR 取地址6到R1碟; 材料传送带地址传送带 1控制传送带 取01234567取地址5到R0碟取地址6到R1碟;将R0和R1炒好倒入R2碟;送R2碟到地址7中;香菇菜心图1.6管理员分析指令存放碟中的加工步骤(指令)后,知道要到冰箱地址为6的格子中去取原材料,并且放到R1碟中。因此,管理员向控制传送带上发一个“取”信号,然后马上将6这个数放到地址传送带上。 冰箱收到“取”信号后,知道理想厨房要取东西

14、,然后,冰箱从地址传送带得到了6,于是冰箱将地址为6的格子中的物品“菜心”取出,放到材料传送带上。 材料传送带上的物品“菜心”,传到理想厨房后,按照指令的要求通过厨房内部的传送带被自动送到R1碟中。第二条指令执行完后理想厨房系统处于如下图1.7状态: 理想厨房 自动冰箱碟名 碟中物品 地址 冰箱格子中物品R0 香菇R1 菜心 厨具R2 厨师PC 2 厨房管理员IR 取地址6到R1碟; 材料传送带地址传送带 6控制传送带 取01234567取地址5到R0碟取地址6到R1碟;将R0和R1炒好倒入R2碟;送R2碟到地址7中;香菇菜心图1.73)在第3条指令的取指周期完成后,理想厨房系统处于如下图1.

15、8状态:理想厨房 自动冰箱碟名 碟中物品 地址 冰箱格子中物品R0 香菇R1 菜心 厨具R2 厨师PC 3 厨房管理员IR将R0和R1炒好倒入R2碟;材料传送带地址传送带 2控制传送带01234567取地址5到R0碟取地址6到R1碟;将R0和R1炒好倒入R2碟;送R2碟到地址7中;香菇菜心 图 1.8 厨房管理员看到IR碟中的内容后,命令厨师将R0碟和R1碟中的原材料倒入锅中炒好后,倒入R2碟中。完成后如图1.9理想厨房 自动冰箱碟名 碟中物品 地址 冰箱格子中物品R0香菇R1菜心 厨具R2香菇菜心 厨师PC 3 厨房管理员IR将R0和R1炒好倒入R2碟;材料传送带地址传送带控制传送带0123

16、4567取地址5到R0碟取地址6到R1碟;将R0和R1炒好倒入R2碟;送R2碟到地址7中;香菇菜心 图1.94)第4条指令在取指令周期完成后,理想厨房系统,处于如下图1.10状态:理想厨房 自动冰箱碟名 碟中物品 地址 冰箱格子中物品R0香菇R1菜心 厨具R2香菇菜心 厨师PC 4 厨房管理员IR送R2碟到地址7中;材料传送带地址传送带控制传送带01234567取地址5到R0碟取地址6到R1碟;将R0和R1炒好倒入R2碟;送R2碟到地址7中;香菇菜心 图1.10下面开始执行“送R2碟到地址7中”这条指令。厨房管理员分析指令存放碟中的加工步骤后,知道要将R2碟中的物品,送到冰箱地址为7的格子中去

17、存放。于是,管理员向控制传送带上发一个“存”信号,然后马上将7这个数放到地址传送带上,最后将R2碟中的物品“香菇菜心”放到材料传送带上,送往冰箱。 冰箱收到“存”信号后,知道理想厨房要存放物品,然后,冰箱从地址传送带得到了7,于是自动冰箱(的机械手)在材料传送带旁,等待从理想厨房R2碟传来物品“香菇菜心”,一旦到达,自动冰箱就将其取下,并将其存放到地址号为7的格子中。完成后系统状态如图1.11:理想厨房 自动冰箱碟名 碟中物品 地址 冰箱格子中物品R0香菇R1菜心 厨具R2香菇菜心 厨师PC 4 厨房管理员IR送R2碟到地址7中;材料传送带地址传送带控制传送带01234567取地址5中到R0碟

18、取地址6中到R1碟;将R0和R1炒好倒入R2碟;送R2碟到地址7中;香菇菜心香菇菜心 图1.11至此,香菇菜心这道菜终于大功告成了。122 现在,我们通过一些问答,对已经学到的重点知识作一个概括和讨论:问题1.理想厨房将要执行的炒某道菜的所有指令是以何种形式,存放在何处的?答:理想厨房将要执行的加工某道菜的全部指令(即加工步骤),必须按照加工的先后顺序,由小地址号到大地址号连续地存放在自动冰箱的多个格子中。也就是说:如果正在执行的指令,所在的地址是n的话;下一条要执行的指令,一定是在地址为n1的冰箱的格子中除非刚刚执行的是一条跳转指令。 问题2.理想厨房如何才能从自动冰箱取到一条指令?取到指令

19、后存放到何处?答:为了取一条指令,厨房管理员首先要把PC碟中的数据(假设是8)复制后放到地址传送带上,送往自动冰箱;与此同时,要在控制传送带上发送一个“取”信号,送往自动冰箱;然后,厨房管理员还要把PC碟中的数据加上1(PC碟中的数据现在变成了9,以便为取下一条指令作准备)。 自动冰箱根据从控制传送带上得到的“取”信号,知道理想厨房要取东西;根据从地址传送带上得到的地址信号:8。自动冰箱由此知道理想厨房要取第8格中的物品。于是自动冰箱将第8格的物品取出,放到材料传送带上。厨房管理员收到自动冰箱从材料传送带上传来的第8格中指令后,把它放在IR碟中。由以上过程可知,哪怕是要完成取一条指令这样的一件

20、微不足道的小事,对于理想厨房系统来说也是非常麻烦的。厨房管理员、PC碟、地址传送带、控制传送带、自动冰箱、材料传送带、IR碟,缺一不可,而且相互之间要密切配合、协调工作才能完成此事。厨房管理员在其中起着核心控制作用。 与取指令类似,理想厨房在执行一条指令时,经常也是如此的麻烦和白痴(比如从冰箱取原材料的指令或送菜肴到冰箱的指令)。问题3.如果没有保存在冰箱中的菜谱,理想厨房的厨师是否能够炒出一道菜来?答:决对不会!厨师只会在厨房管理员的命令下,每次仅仅做 “炒”、 “煎”、“蒸”、“烤”、“炸”和“煮”等一些固定数量的基本操作中的一个基本操作。在这个厨师的大脑中,没有任何一道菜的全部加工过程。

21、他也学不会新的基本炒菜动作。他只会一次次地、忠实地、快速地按照厨房管理员的命令,每次只做一个单一步骤的加工操作。而厨房管理员向厨师发出的加工命令,也都是来自于他所取到的菜谱中的指令。没有保存在冰箱中的菜谱,理想厨房系统中的管理员和厨师根本加工不出任何一道菜肴来。理想厨房系统的炒菜“智能”来自于菜谱的编写者。正是由于人们编出来的可以让理想厨房执行的各种各样的菜谱,才使得原本白痴般的、能力极为有限的理想厨房系统在炒菜方面显得几乎无所不能!问题4 编写特殊菜谱很困难吗?答:确实很难编写。要想理想厨房系统做出任何一道菜肴,都要编写出它能自动执行的特殊菜谱,这种菜谱要非常精确、无二义性。在这种菜谱中,要

22、用冰箱地址号来取代原料的名称(这导致了菜谱很难看懂)。还要知道,厨师究竟会做哪几种基本炒菜动作、厨房管理员懂得哪几种加工命令。此外,原料所放的位置不同,菜谱的内容也就有所不同,与给普通人看的菜谱有很大不同。问题5 你能简要叙述一下,厨房管理员的工作步骤吗?答:厨房管理员的工作完全是周期性的,即他永远在做:取指令分析指令发出控制命令要求各部件执行指令这一种周期性的动作。只要一启动,理想厨房系统永远在按照这个周期性的动作,一条一条的顺序的取指令并执行指令(如果遇到跳转指令,则跳转到指定地址去,继续取下一条指令),不停地快速运行着,直到停机或发生严重故障为止。理想厨房系统的工作原理,到此已经全部介绍

23、完毕。学完理想厨房系统的基本工作原理后,从整体上把握计算机的基本工作原理,就变成为一个比较轻松的名词替换的小游戏了。1.3理想厨房系统与计算机系统术语对照表理想厨房的工作原理,与计算机的工作原理是极为类似的。下面首先给出两个系统之间的术语对照表,见表1.1。表1.1术语对照表理想厨房系统电子数字计算机(简称计算机) 1.硬件设备自动冰箱(包含多个大小相等的格子)内存(又称为主存,包含很多大小相等的基本存储单元) (冰箱中的)一个格子(内存中的)一个基本存储单元材料传送带数据总线地址传送带地址总线控制信号传送带控制总线理想厨房(包含以下设备)CPU(或称微处理器,包含以下部件)厨师及炒菜设备算术

24、逻辑单元ALU(又称为运算器)厨房管理员控制单元(又称为控制器)通用临时存放碟通用寄存器指令地址存放碟PC指令地址寄存器(又称为程序计数器PC)指令存放碟IR指令寄存器(又称为IR寄存器) 状态存放碟状态寄存器专用加工容器专用寄存器采购员及配菜员输入设备(键盘、鼠标、网卡、U盘等)传菜生输出设备(显示器、打印机、网卡、U盘等)自动仓库外存(硬盘、U盘,但同时也属于输入输出设备) 2.软硬件之间的接口(编写菜谱或程序的基本要素)冰箱格子的地址(即编号)内存中基本存储单元的地址(即编号)厨师可做的各种炒菜的基本动作(蒸、炒、炸、煮等)运算器可进行的各种基本运算(算术运算、逻辑运算等)碟子的名称寄存

25、器的编码或代号 理想厨房可以执行的所有各种加工动作(指令)该类型计算机的指令集3.软件特殊菜谱(机器语言形式的) 程序加工步骤(机器)指令原材料数据 炒好的菜信息(或称为结果)精确的普通菜谱高级语言程序(又称为源程序或源代码) 简要的普通菜谱伪代码4. 系统的使用者编写特殊菜谱者用机器语言编程的程序员 编写精确的普通菜谱者用高级程序设计语言编程的程序员理想厨房系统的大堂经理和顾客计算机的用户在计算机上运行一个程序时,上表中列出的计算机的各个部件会协同工作,完成任务(参见1.6节)。如果没有对理想厨房工作原理的详细讲解,由于出现了太多新的专业术语和名词,人们决对很难在短时间内,全面把握和理解这个

26、计算机系统的工作原理的(对于任何一个初学者,这都是一件极其困难的事)。1.4预备知识:二进制及相关知识简介: 人类有十个手指,所以偏爱十进制记数法。可是,在人造数据处理设备中,使用十进制记数法通常却是愚蠢的选择。虽然理论上可以使用任何进制(比如十进制)为基础来制造计算机,但现代计算机都是采用二进制的数字电子信号(制造电子计算机为何采用数字信号而不用模拟信号,不用其他进制而采用二进制,请参见本书附录D )进行工作的机器。也就是说,在现代计算机的内部,只能以二进制的形式来存储、传输需要执行的指令和需要加工的数据。 为了从底层彻底把握计算机的基本工作原理,读者必须事先对二进制的相关知识要有所了解。以

27、下进行简要介绍,更深入一些的讨论请参见大学计算机基础或微机原理等书籍。1.4.1 二进制数的概念及其数制之间的转换 首先,来看一张部分十进制数与二进制数的数值对照表: 十进制数 所对应的二进制数 (所对应的十六进制数)0 0 01 1 12 10 23 11 34 100 45 101 56 110 67 111 78 1000 89 1001 910 1010 A11 1011 B12 1100 C13 1101 D14 1110 E15 1111 F16 10000 10表 1.2 部分十进制数与二进制数(和十六进制数)的数值对照表一般情况下,用n位二进制可表示的最大正整数值是2n-1。比

28、如:4位二进制可表示的最大正整数是24-1=15(即1111)2 。可见,二进制只能用两个数字“0”和“1”来进行计数。二进制加法运算的重要规则是: 1+1=10 ,即两个1相加产生向高位的进位。左边是高位数,右边是低位数(此外,其它加法规则还有:1+0=1、0+1=0、0+0=0)。我们经常用一对圆括号括住一个数值,并在圆括号外面加一个数字下标,来表示一个数是几进制数。比如(1011)10是一个十进制数;而(1011)2是一个二进制数。142将二进制整数转化成十进制整数一个十进制的整数,其数值可用以下展开式来表示:比如3785(3785)10=3103+7102+8101+5100 (1)我

29、们把(1)式中10的几次方称为权重,权重左边的乘数称为系数。(1)式中共有4个系数,从左到右依次是:“3”“7”“8”“5”。可见,在表示数值数据时,越左边的系数权重越大。权重中的基数(即底数)与该进制是一样大的,在这里都是10。类似的,一个二进制数,其数值也可用以下展开式来表示:比如二进制数1011(1011)2= 123+022+121 +120 (2)此二进制数的值,等于十进制的18+04+12+11=8+2+1=(11)10 (3) 由此可以得到:二进制整数转化成十进制整数的一般方法:只要将该二进制整数(即1011)展开后的(2)式中的每一位(小于2)的系数值,乘以这一位的转化成十进制

30、数后的权重(即2的几次方),然后再将逐个乘积项的数值(用十进制加法规则)相加起来即可。深入一步:以上这种二进制整数转化成十进制整数的方法,其实适合将一个任意R进制数转变成十进制数。比如有8进制数(305)8,就可展开为: (305)8=382+081+580 = 364+08+51=(197)10143 将十进制整数转化成二进制数:1. 将十进制数转换成二进制数的短除法: 把一个整数(比如89)从十进制转换成二进制,需要用新基数2除这个数(89),余数(1)是结果左边的下一位数字,商(44)是新的被除数,整个过程直到商为0时终止。短除法就是按照以上规则,把要转换的十进制整数不断的除以2然后取余

31、数,商作为新的被除数,直到商为0的时候结束。然后把余数倒着写出来。例如: 把84转换成二进制数 1244 222 211 25 22 21 20 89 0 0 1 1 0 1即: (89)10= (1011001)2深入一步 :任意的R进制实数表示法一般情况下,任何一个R进制实数,都可以紧凑地表示为:(R通常是2,8,10,16中的某个数) (+ rnrn-1r1r0 . r-1r-2.r-m)R (其中的任何一个ri都是0到R-1之间的一个整数)将任意一个R进制数扩大R倍(即乘以R),只需将小数点右移一位即可;类似地,将任意一个R进制数缩小R倍(即除以R),只需将小数点左移一位即可。其中R=

32、10是人们最为熟悉的十进制数,这种数的表示法,主要是由古代印度人发明的。任何一个R进制数,也都可以用多项式的展开表示为:+(rnRn+ rn-1Rn-1+ r1R1+ r0R0 + r-1R-1+ r-2R-2+ r-mR-m) (ri称为系数,Ri称为权重)R进制数值的多项式展开表示法中,不使用小数点。一个数的各种进制表示法之间的数值是一样大的,只不过表现形式不同而已。注意:短除法也适用于将一个十进制整数转换为一个任意R(R2)进制的整数,只需将除数由2替换为R即可(参见例题 )。可选练习:将十进制整数89用短除法转化为8进制整数。*1.4.4将十进制纯小数转化成二进制纯小数10进制纯小数转

33、换为2进制纯小数的转换过程,与整数的进制转换过程有些类似而又有些相反:不是用新的基数2除这个数,而是用新基数2去乘它。乘法的进位将成为答案右边的下一位数字,乘法结果中的小数部分将成为新的被乘数,整个过程直到乘法结果中的小数部分为0时终止。例如:把十进制小数0.375转换成二进制小数: 0.3752 = 0.750 0 /进位0,小数点后第一位 0.752 = 1.50 1 /进位1,小数点后第二位 0.5 2 =1.0 1 /进位1,小数点后第三位 所以,(0.375)10=(0.011)21.5预备知识:数和码的含义和区别 如果计算机仅仅只能够对一些数进行数值运算(在计算机刚发明的早期年代确

34、实是如此),那么它的应用范围就很窄。然而,现代计算机的应用范围却是极其广泛的。根本原因在于:现代计算机不仅能对数进行运算,还能对各种各样的“码”进行处理。所以,我们想要真正懂得计算机并且学好编程,就不仅要熟悉二进制的“数”,还必须对二进制的“码”也有一个比较清晰的整体了解。以下这些内容虽然比较繁琐,然而理解起来却并不太困难。151十进制的数和码:我们通过一个例子,先来来说明十进制数字系统中数与码的区别如果3785用于表示数,则越高位(即越左边的位)的数字越重要(因为权重越大,在十进制数3785中,“3”的权重是103,而“5”的权重是100)。 而3785用于表示非数值的码,则每一位都同样重要

35、。码值仅相差一位,所表示的文字(或代表的事物)就可以有巨大的区别(比如:3785可代表汉字“前”,而3786可代表汉字“后”)。 十进制的数字串“3785”,既可以表示一个值为三千七百八十五的十进制数,也可以表示一个码为3785的汉字(或者表示任何别的什么10000个同类型事物中的码值为3785的一个特定事物)。 对于数值可以进行各种数学运算,而对于表示非数值的码进行数学运算,通常毫无意义。 152 二进制的位、位串、字节 与十进制一样,二进制数与二进制码也有类似的区别。只不过在二进制中,只能用0和1组成的一个二进制数字串,来构成任何大小的数值或者表示具有任何含义的码。我们先来熟悉一些与二进制

36、有关的术语。位:单个二进制数字不是0就是1,再没有别的可能数字我们将其称为“位”(bit)。位串及其长度:任意多个二进制“位”顺序排列在一起(比如:11011011100),我们称之为位串(有的教科书称为“位模式”)。位串中数字的个数,我们称为位串的长度。字节8位二进制位串在计算机、通信及其大量相关应用中,人们最为关心的是长度为8位的二进制位串。这是由于现代的绝大多数计算机和数字化的通信网络设备,都是以8位二进制位串作为计量(部件的)数据存储容量和(传输过程中的)数据传输量的一种基本单位。我们把 一个8位二进制位串称为一个字节(Byte)。深入一步:二进制数据存储和传输中的一些常用单位“字节”

37、 (Byte)这个基本单位虽然是“位”这个最小二进制单位的8倍,但在很多场合仍然显得太小,更大的常用单位有(用B来表示Byte):千字节: 1KB=1024B 兆字节: 1MB=1024KB吉字节: 1GB=1024MB每种单位之间都是1024倍的关系,而不是1000倍的关系。所以,我们常常会看到一些数据存储设备标出它的数据存储容量是多少个KB、MB或GB;或者一根网线(或者一块网卡)标出它的数据传输量是每秒钟传输多少个KB、MB或GB。153 二进制的数和码如果用一个位来表示整数值,只能表示0和1这两个值中之一。大于1的整数值用一个“位”表示不了。如果用单个的“位”来表示码,则只能用来对(同

38、属一种类型的)两种不同事物进行编码。比如:用0表示“假”, 用1表示“真”; 用0表示“否”, 用1表示“是”;或者用 0 表示动物“狗”,用 1 表示动物“猫”;等等。如果用长度为2的一个位串来表示整数值,则能够表示00(即0)、01(即1)、10、11这4个整数值中的某一个。如果用长度为2的位串来进行编码,由于有4(即22)个码值可以用,则能够用来对属于同一类型的4个不同的事物(或状态)进行编码。比如 酸,甜,苦,辣、牛,马,羊,猪、加,减,乘,除、A,B,C,D、赵,钱,孙,李等等。深入一步:编码和解码的一个实例 通过制定编码规则,比如:可以用00表示“D”、01表示“C”、 10表示“

39、B”、 11表示“A” ,这就构成了一张表示4个字符的编码解码表(注1),见表1.2 。(注1):如果把00,01,10,11看成4个未婚男士(的代号),A,B,C,D看成4个未婚女士(的代号)。所谓的“编码解码表”,只不过是所有男士与女士之间的一张快速配对表而已。用严格的数学术语来讲,这实质上是两个集合00,01,10,11与 A,B,C,D 的所有元素之间的一张一对一的映射表。 二进制码字符00 D01 C10 B11 A表1.2 表示4个字符的编码解码表有了编码解码表,先通过对字符串“CAB”进行编码,就可以用一些码值构成的二进制位串“011110”来间接地存储和传输这个字符串。因为这样

40、既安全,又方便(注2)。到达目的地后,接收方也要有同样的一张字符的“编码解码表”,才能将这种接收到的二进制位串,翻译成它的本来意义。比如将二进制位串011110翻译成字符串“CAB”。这个过程就称为解码。 编码 发送 接收 解码 CAB011110011110CAB 图: 字符串的编码、发送、接收、解码全过程(注2)一个二进制位串,用二进制的电子数字信号是很容易表示、存储和传输的。而任何用文字表达的非数值数据,不通过某种编码,是无法直接用二进制的电子数字信号来表示、存储和传送的。如果用一个字节来表示无符号的整数,则能够表示28=256个整数值。分别是从0到255,对应于二进制数从(000000

41、00)2到(11111111)2 。 如果用一个字节来进行编码,则能够用来对256种同类事物进行编码。计算机中一种常用的对英文文本(即文件)中常用字符的编码规则,通常是采用一个字节来对英文字符进行编码的“ASCII码表”(细节参见附录B)。借助于ASCII码表,我们就可对一个英文文本进行编码,转换成一串长长的二进制ASCII码形式的位串后(但实质上还是一个英文文本),送到计算机中进行编辑、加工、保存和(通过网络)进行远程通信。 在不必区分加工处理对象到底是数还是码的场合,我们可将其统称为数据。有了这些预备知识,现在我们终于可以整体而又简明地介绍计算机的工作原理了。16电子数字计算机:电子数字计

42、算机(以后简称为计算机)是一个通过执行程序,把数据加工成信息的电子数字设备。类似于理想厨房系统,计算机也是由硬件(中央处理单元、内存和三套总线、外围设备)和软件(程序)组成。1.6.1计算机的硬件 计算机硬件主要由以下四个实物部件构成:中央处理单元、内存、三套总线和外围设备。内存(相当于自动冰箱):内存主要是由极大量的大小相同的基本存储单元(相当于自动冰箱的格子)组成。每个基本存储单元都具有一个从0开始顺序递增的唯一编号,称为内存基本存储单元的地址(相当于冰箱格子的编号),但在很多书中(包括本书)经常将其简称为内存地址。现代计算机,大多数是以字节(而不是以更小的“位”)作为基本存储单元,来分割

43、内存的。内存负责临时保存正在执行(和将要执行)的程序的指令序列、程序要加工的数据和处理结果。这些内容在内存中,全部都是以二进制位串形式存放的。如下图1.14所示: 内存地址 内存基本存储单元中 存放的位串(即字节)110101101101001110110101011011110101110100000000100000000000000010000001000000001000001000000000100001000000000010000101 图 1.14 内存片段示意图:由具有唯一地址的大量内存基本存储单元构成的内存。在每个基本存储单元中,可以存放长度为一个字节的指令或数据。一条指令

44、(或者一个数据)如果太长,一个基本存储单元(注)存放不下,就要用内存地址连续的几个基本存储单元来存放。(注)不少教科书把内存的一个基本存储单元称作为一个字节。n个基本存储单元就称为n个字节。三套总线(相当于三条传送带):分别是数据总线、地址总线和控制总线,负责中央处理单元与内存之间的通信及数据(包括程序中的指令、数据和信息)传送。中央处理单元,简称CPU(相当于理想厨房):负责通过三套总线,到内存中去取程序(相当于菜谱)中的指令(相当于加工步骤),并根据指令对数据(相当于原材料)进行运算处理(相当于加工原材料)以及进行相关的控制工作。中央处理单元中的主要部件有:控制器或称为控制单元(相当于厨房

45、管理员)、运算器(即ALU,相当于厨师加炊具)和一些寄存器(相当于厨房中的碟子)。外围设备(相当于配菜员或传菜生)包括输入设备(键盘、鼠标、网卡、数码相机、麦克风等)、输出设备(显示器、打印机、网卡、音箱等)和外存(硬盘、U盘、光盘等)。包含机械部件的外围设备,其工作速度比计算机中的纯电子器件(CPU、内存、三套总线)慢得多。在本章中,我们暂不对外围设备的工作原理进行讨论(在最后一章进行讲解)。计算机结构示意图,如图1.13所示地址总线数据总线控制总线CPU内存I/O设备I/O设备 图1.13计算机结构示意图 要替换1.6.2计算机的软件 构成计算机的部件中,还有一个无重量的、无体积的、不会损

46、坏、可经常更换的,极为重要的软件部件,即命令计算机如何进行工作的信息流程序。程序是由一条条指令顺序组成的指令序列(相当于菜谱中的一系列加工步骤)。 构成程序的指令序列,必须按执行的先后顺序依次存放在内存中。计算机的构成简图(此图没有包括外围设备)如图1.15所示:中央处理单元(CPU) 内存 寄存器名 寄存器中物品 地址 存储单元中存放程序或数据R0R1 R2 运算器 PC 控制器IR 数据总线地址总线控制总线 000001010011100101110111 指令1指令2指令3数据数据图1.15计算机系统的构成简图图1.15所示计算机的CPU中各种寄存器的作用:R0、R1、R2是一些通用寄存

47、器,用来临时存放从内存取来的数据或运算得到的结果。PC寄存器:存放下一条要执行的指令位于内存的哪个存储单元中。IR寄存器:用来存放刚从内存取过来的马上要执行的指令。 1.6.3计算机指令执行的全过程指令的格式 任何类型的计算机,指令的通常格式为: 操作码 操作码告诉计算机要做什么事,操作数告诉计算机对存放在哪里的数(或什么数)做这件事。操作码是所有指令必须有的,一般的指令通常有一到三个操作数。但有些指令没有操作数(这通常是由于这种指令的操作数默认是在某个或某几个寄存器中)。我们在第12章将再次深入一步讨论这个问题。 比如在一台极简单的计算机上,可以用操作码00001表示加法、00010表示减法

48、,等等。注意:指令在计算机内部实际上都是二进制位串。指令执行的全过程与理想厨房执行加工步骤类似,中央处理单元(CPU)执行指令的过程,也完全是周期性的。控制器首先要根据PC寄存器中的值(这个值指示将要执行的指令,位于内存的哪一个存储单元中),通过三套总线密切配合,到内存中去取这条指令。取到CPU并把它放到指令寄存器 (即IR寄存器) 之后,PC寄存器中的原来值,将会加上L(L是刚取到的这条指令的字节数),以便为取下一条指令做准备。 控制器对取到的该条指令进行译码,然后根据这条指令的指示,通过总线命令计算机的各部件,去执行以下七大类型指令(类似于所有英文单词分为动词、名词、形容词等几大类)中的一

49、种: 1通过三套总线,命令内存,把指定地址存储单元中的(运算过程中马上要用到的)数据,送到中央处理单元中(的某个寄存器)来。(取数据指令)2命令运算器按照指令的要求,对数据作一次基本运算(注1)。(数值运算类指令) 注1:基本运算包括:算术运算(加、减、乘、除)、逻辑运算(与、或、非)和移位运算等等。3通过向三套总线向内存发命令,把某个寄存器中的运算结果(或中间运算结果)送回到内存中(由内存地址)指定的存储单元中存放即保存结果。(存数据指令) 4在中央处理单元中的各个寄存器之间传送数据在中央处理单元内部进行数据传送(数据移动指令) ;5根据某个状态寄存器中的某个条件(比如上一条指令的运算结果大

50、于零),条件不成立根据PC的值正常取出并执行下一条指令;如果条件成立,就不按正常顺序取出并且执行下一条指令,而是根据此指令中给出的内存的(偏移或绝对)地址,修改PC寄存器中的数值,从而实现跳转(注2);(跳转类指令) 注2:这是有条件跳转指令。还有一种无条件跳转指令,是不需任何条件就进行跳转的。进一步的讨论参见1.10的第(7)点。6通过接口电路,命令输出设备输出一个数据(注3)。(输入指令)7通过接口电路,命令输入设备输入一个数据(注3)。(输出指令)绝大多数计算机都具有这七大类指令,高级一些的计算机指令的类型还更多。 一条指令运行完毕后, CPU立即自动进行下一个形式上完全类似的、新的 取

51、指令执行指令 的周期。一条指令的运行周期,可分为取指子周期和执行子周期这两个阶段。由控制器发起的,反复、自动、快速进行的取指令执行指令周期,是计算机中完成各种任务的核心动作(这就象反复、自动进行的心脏搏动周期,是人体进行各种活动的核心运动一样)。1.7计算机中的指令集和机器语言程序:计算机的指令集一台计算机能够执行的指令类型的总数是有限的(就象英文单词总数是有限的)。随着计算机类型的不同,大约在几十种到几百种之间。所有这些不同种类指令的集合,称为该类型计算机的指令集(类似于英文单词表)。虽然指令集中的指令数量是有限的。然而,用指令集中的有限种指令,可以编写出来的程序(即指令序列)的数量却是无限

52、的(就像用有限的英文单词可以写出无限多的英文文章类似)。 问题1.一台计算机的指令集中共有20条指令,至少要用多少位的二进制位串来表示这些指令的操作码?是否存在非法指令?机器语言程序用这种二进制的指令集中的指令编写出来的完成某个数据处理工作的指令序列,称为机器语言程序。机器语言程序是可以直接在计算机上运行的唯一一种程序。因为只有它,才可以直接转变为计算机的数字电路中运行时所需要的驱动各条电路中的低电压电平(比如用02伏当作为二进制“0”)或高电压电平(比如用25伏当作为二进制“1”)。不同类型的计算机所具有的指令集是不同的。 比如:在一台A计算机上,操作码00010表示的是加法指令;而在另一台

53、不同类型的B计算机上,操作码00010表示的却可能是无条件跳转指令;再有一台C计算机,它所有的操作码可能不用是5位二进制位串,而是用的8位二进制位串。也就是说,为一种类型计算机编写的机器语言程序,是不能够拿到另一种不同类型的计算上去运行的。任何一类计算机都有自己的“方言”。这是用机器语言进行编程的无奈之处。1.8程序实例讲解(片断)与练习:现在,我们可以用上述这些计算机系统的术语,来取代理想厨房系统的工作原理叙述中所用到的理想厨房系统的相应术语。那么它恰好就变成了对计算机系统的工作原理的一个简要地、然而又是整体地介绍( 对计算机工作原理的更深入一些的阐述,可参见第12章 )。这种简要性和整体性

54、,对于我们从整体上完全把握计算机系统的基本工作原理,是极为重要的。下面,请读者通过做以下这个练习,来熟悉计算机的基本工作原理。例题1.1 :将第一个数2存放在内存地址为5的基本存储单元中。将第二个数11存放在内存地址为6的基本存储单元中,程序的指令序列,存放在从地址0开始到地址3结束的内存基本存储单元中。内存地址03的基本存储单元中存放的指令序列,将完成一个211的加法运算,并将结果存放到内存地址为7的存储单元中。 CPU 内存 寄存器 数据 地址 内存单元中的指令或数据R0R1 算术逻辑单元R2 PC 0 控制单元IR 数据总线地址总线控制总线01234567取地址5到R0寄存器取地址6到R

55、1寄存器将R0和R1相加存入R2寄存器送R2寄存器到地址7中211 图1.16 示意图(注意:在计算机内存中的指令、数据和内存地址,实际上都只能是二进制位串形式的。此图只是一个计算机结构和工作原理的示意图,所以用了一些陈述性的汉语句子来代替那些二进制的机器指令;用了10进制的数据取代了那些二进制的数据。关于机器语言形式的指令、数据和程序的运行,请参见第12章)下面对这个程序的运行过程进行讲解(片断):控制单元根据PC寄存器中的数字“0”,知道要到内存地址为0的基本存储单元中去取指令。于是,控制单元向控制总线上送出一个“取”信号,然后马上将PC寄存器的数字“0”复制后放到地址总线上。这两个信号都

56、会到达内存。内存收到这两个信号后,将地址为0的基本存储单元的内容“取地址5到R0寄存器”复制后放到数据总线上,送往CPU,CPU收到后自动放到指令寄存器IR中,然后,控制单元将PC寄存器中的值增加1(以便为取下一条指令做好准备);取指令周期完成后计算机处于如下图1.17状态: CPU 内存 寄存器 数据 地址 基本存储单元中的内容R0R1 算术逻辑单元R2 PC 1 控制单元IR 取地址5到R0寄存器 数据总线地址总线控制总线01234567取地址5到R0寄存器取地址6到R1寄存器;将R0和R1相加存入R2;送R2寄存器到地址7中;211 图1.17 下面开始这条取来指令的执行周期; 。重要练

57、习:下面请你模仿理想厨房的炒菜实例1.2节,继续讲述计算机是如何执行指令,将两数相加的结果13,存放在内存地址为7的基本存储单元中的。建议读者都要做此重要练习。1.9计算机的重要特点下面,请你认真考虑一下与1.2节末类似的计算机中的相关的以下重要问题:问题1. 计算机将要执行的某道程序的所有指令是以何种形式,存放在何处的?问题2. CPU如何才能从内存中取到一条指令?取到指令后存放到了CPU的何处?问题3. 编写机器语言程序很困难吗?问题4. 你能简要叙述一下,CPU中控制器的工作步骤吗?现在,我们来进一步归纳、总结这个比较陌生的计算机系统中的我们感兴趣的其他一些重要特点:(1)存储在内存中的

58、,由指令序列构成的程序,是计算机能够完成任何一件数据处理任务的必不可少的“控制信息流”。所有的硬件部件都是在控制单元通过控制总线发出的命令下进行协调工作的,然而控制单元向计算机各部件发出的所有控制命令,全部都是来自于程序中的指令。没有取到内存中的(下)一条指令,控制单元根本不知道(下)一次操作要做什么事。没有程序在内存中执行的计算机是一个彻头彻尾的“白痴”。它不会(不能)做任何有意义的工作。通过以下图1.18的类比,读者将更清楚计算机系统中各种成分的地位和作用:尤其是程序员和程序的作用。程序员的智慧转移到了各种各样的程序中,与计算机的强大计算能力、记忆能力、(借助于网络的)通信交流能力完美结合

59、起来,才使得计算机看起来似乎无所不能!参谋长程序员(位于计算机外部)作战任务程序所要完成任务参谋长发布的一系列宏观命令程序员编写的程序(指令序列)一条宏观命令 一条指令作战参谋:将取到的一条宏观命令,分解为具体命令控制单元:将取到的一条指令分解成相关各部件要完成的具体命令通过通信线路:发布具体命令通过控制总线:发布具体命令接受并完成具体命令的各类部队接受并执行具体命令的计算机其他各部件敌人数据 图1.18计算机中各种成分所起的作用(作战部队和计算机的类比)同一台计算机可以进行各种性质完全不同的数据处理工作。计算机的硬件通常不必作任何更换,只要加载不同的软件(程序和数据)到内存中加以执行即可。(

60、2)单核CPU的计算机(多核CPU的概念,请参考第十二章),CPU每次只能执行一条指令。一条指令只能做一次两个(或一个)数的运算(算术运算、逻辑运算或位运算)、存一个数、取一个数、输入一个数、输出一个数、在寄存器之间移动数据、进行跳转(跳转指令)等等(必须是指令集中的一条,不能是非法指令)。一句话:一条指令能够做的事微不足道。这就意味着:不论一个程序有多长、多么复杂,构成该程序的指令,全部都是由指令集中区区几十种到几百种指令按不同顺序排列而组成的。换言之,从底层的机器指令层面上来看,计算机所能做的全部工作都是极其低级的、琐碎的、种类极为有限的。计算机的硬件只会快速地执行程序中的(属于指令集中的

61、)指令序列,它根本不知道运行在其上的各种程序究竟有何区别、有何精妙之处。任何程序在硬件“看来”,都不过是指令集中为数有限的几十到几百种指令的不同排列组合而已。它根本不会“知道”,它自己的工作表现,竟然会让很多高智商的“人”为之痴迷、震惊和感到困惑不解。因此,程序所要完成的各种宏观任务与计算机硬件只能执行的微观任务(指令)之间存在一条语义表达上的巨大鸿沟。用机器语言来编写程序的程序员,必须将他的整体编程思路一直细化、直到分解成为计算机能够执行的微观的一条条的指令为止。这使得用机器语言来编写大型程序变得极其困难,实际上几乎是不可能。(3)跳转指令的执行方式 计算机中有两类跳转指令。一类是无条件跳转

62、指令,一类是有条件跳转指令。无条件跳转指令的格式一般是:跳转到 偏移地址(或绝对地址) 跳转到代表跳转指令的操作码,后面跟的是偏移地址,偏移地址是一个可正可负的整数(而绝对地址就是一个大于等于0的真实内存地址)。控制单元执行带偏移地址的跳转指令很简单,它只需将此偏移地址与PC寄存器中的原来值相加,得到一个新的正整数,再把此数重新放到PC寄存器中,这条跳转指令就执行完了。 举例来说:PC寄存器中现在是15,要执行的无条件跳转指令是: 跳转到 -4 ( 跳转到 偏移地址),则执行此执令后,PC寄存器中的值改变为15-4=11了。下一条要执行的指令现在是在内存地址为11的存储单元中,而不再是在地址为

63、15的存储单元中了。如果是跳转指令是 跳转到 绝对地址 ,这种格式。则该跳转指令执行起来更为简单,只要将该绝对地址直接存入到PC寄存器中,跳转指令的任务就做完了。有条件跳转指令的格式一般是:条件成立跳转到 偏移地址(或绝对地址) 有条件跳转指令是在某个条件成立时才进行跳转,条件不成立时就不进行跳转(即不改变PC寄存器中的当前值)。所有这些条件值都是存放在CPU的一个状态寄存器中(比如上一条指令运算结果大于0,是一个条件,用状态寄存器中的一个位来记录这件事)。细节请参见第12章。不过读者特别要注意的是:就象磁带的快进或倒带,不会改变录音机的磁带必须顺序向前播放才能听到音乐的基本事实。计算机也完全

64、类似,不论是执行了一条向前跳转指令还是执行了一条向后跳转指令,这只是一个瞬间的事。不会改变计算机的CPU是按照地址由小到大连续地、周期性地取指令执行的这个基本特点。(4)因为控制单元是由固定在半导体硅芯片内的,很多个晶体管逻辑门电路为主要部件所组成的电子器件。根本不可能走出CPU,去打开内存进行查看。因此,控制单元根本不知道,每次总共有几个程序要运行。也不知道一个程序是从内存的哪一地址开始到哪一地址结束的。甚至连内存本身也不知道:哪一个内存单元中是数据,哪一个内存单元是指令(换句话说,内存把指令、数据和运算结果同等对待)。 正是由于这个根本原因,所以对计算机系统所有软硬件资源的调度和管理,主要

65、不是以改进CPU等硬件的方来进行。采用的措施是为计算机系统编写和配备一个特殊的管理程序为主的方式,而对硬件仅仅作少量的改进(主要是引入中断机制)。这个特殊的超大型管理程序被称为操作系统(又被称为系统软件)。参见第12章12.4节。(5)内存单元拷贝了这个数据进行运算和处理);也可以将一个数据或结果,放到内存的任意一个存储单元中,这一存储单元原先存放的数据(或指令)会立即消失得无影无踪。我们说,原来的数据被新的数据覆盖掉了。 这是因为:存放在内存中地址为 0000000011000010的基本存储单元中的一个字节:10010011,其实一般就是: 高低低高低低高高 的8个电容的电压值。这8个电压

66、值当然可以通过导线传送到数据总线上去,内存中的这8个电压值仍然存在;但是,如果将数据总线上传来的01001001这一个字节,保存在内存地址为0000000011000010的基本存储单元中,这8个电容的电压值当然要变成:低高低低高低低高 了。原来保存的数据自然被新的数据覆盖掉了。 不过计算机一旦关机,内存中的所有程序和数据都会立刻全部消失。注意:以上所指的内存仅仅是RAM内存芯片。只读的内存芯片ROM中的所有程序和数据在计算机关机(停电)后仍然存在,而且ROM内存芯片中的程序和数据都是不可更改的。所以,现代的PC计算机(即个人计算机)和笔记本电脑,往往把那些几乎所有应用程序都要经常使用的各种公

67、共的基本输入输出程序段,存放在了各种ROM中。与基本输入输出有关的程序段称为BIOS-即基本输入输出系统,主要存放在一个称为主板(main board)的印刷电路板上的(固定在主板上的)ROM内存芯片中。110 理想厨房系统与计算机系统之间的重要区别 如上所述,这两类系统是如此的类似,那么它们之间重要的不同点有哪些呢?1、理想厨房是用来将原材料加工成食物的;而计算机系统是用来加工数据来得到有用的信息的。一个加工具体的物质,一个加工抽像的数据。-加工对像完全不同。点评:由物质构成的实物与抽象的信息之间的区别由物质构成的实物,有重量和能量,会损坏;而信息则没有重量和能量,不会损坏;信息只能由于载体

68、损坏或受到干扰而遭到破坏、丢失或失真。人造的信息既可以用多种实物作为载体来保存和传送,也可以用几乎是无重量的场(比如电磁场)作为载体来进行通信交流。未加工的天然信息存在于自然现象或天然物品之中。人类社会中充满各种个样的信息。信息只对生物起作用。同一种信息对不同的生物有不同的作用。同一种信息对于同一种生物的不同个体也可以有不同的作用。原始的未加工的批量数据中可以蕴含着大量的、各种各样的信息。信息中还可以提炼出更高级的信息。一个自然现象或社会现象中,可以包含多种多样的信息。知识只不过是很多信息的有条理的组合。 人类由于有大脑这个收集、保存和处理信息的功能强大的器官,从而成为生物进化中的佼佼者(但人

69、类如果不小心的话,也很可能成为大自然的近期淘汰目标)。不过,人类的大脑保存和处理信息的能力,受到人类个体的寿命、疾病、体能、大脑的记忆力、大脑的计算能力的严重制约。在保存、处理、提炼和交流信息方面,计算机成为人类的得力助手。点评:智能机器人与计算机之间的关系智能机器人并不等同于计算机,虽然智机器人的大脑是由计算机构成的。所以,即使智能机器人能够加工物质,我们也决不能说,计算机能够加工物质。 2 在理想厨房系统中,加工步骤与加工的原材料虽然都存放在冰箱的格子中,但两者间的区别却是巨大的。因为前者是抽象的信息,而后者是物质的食物。然而但在电子计算机中,指令和数据所起的作用虽然很不一样,但其实两者都

70、是用二进制的位串来表示的抽象的信息(只是两者的目的地不一样:指令被送到IR寄存器中,数据通常是通过执行指令取到通用寄存器中参与运算),两者在表现形式上并无本质区别。正是因为这一重大差别,才使得我们用人类更容易理解的汇编语言或高级程序设计语言(参见下一章)来为电子计算机编写程序成为可能。计算机实际上只能懂得和执行用二进制的机器语言编写出来的程序(称为可执行程序,程序文件名的扩展名为.exe或.com)。我们用高级程序设计语言编写出来的程序,一开始是作为一个机器语言程序编译程序(所谓编译程序,就是一种计算机专家编写出来的特别有用的程序,它可以把人们用高级语言编写的程序,翻译加工成机器语言的程序)的

71、等待加工的数据(即一串ACSII码形式的文本),存放在内存中的,被称为源程序。经过编译程序加工后的最终结果,则是一个我们所需要的机器语言的程序 。注意如下所示的两个(不同程序的)运行阶段: 第一阶段: 编译程序运行阶段运行的机器语言程序: 编译程序; 原始数据: 求100个数中的最大数的高级语言源程序(以ASC码的二进制文本形式出现的C语言程序)。 加工结果: 求100个数中的最大数的机器语言程序;(以二进制机器指令形式表现的程序)第二阶段: 应用程序运行阶段 运行的机器语言程序: 求100个数中的最大数的机器语言程序;原始数据: 待求最大数的100个数(这100个数在计算机内部是以二进制形式

72、出现的);加工结果: 100个数中的最大数; 注意:第一阶段的加工结果,在第二阶段摇身一变,成为运行的程序。这也正是冯诺伊曼结构的计算机(注:还有其它类型结构的计算机,但目前只是占少数)将程序和数据都放在统一编址的內存中的真正目的可以把一个程序的加工结果(数据)转变成程序。小知识 内存中存放的ASCII码形式的高级编程语言的程序片断:(假定C语言的源程序作为文字型数据存放在内存地址为0处开始 #include 以ASCII文本的形式存放在内存中,等待编译程序将其加工为机器指令形式的指令) 每个基本存储单存储单元地址 元中存储的内容(每一行右边的8个小格)00000000001000110000

73、00010110100100000010011011100000001101100011000001000110110100000101011101010000011001100011000001110110010100001000 . 表1.2部分内存结构示意图:在ASCII码表(参见附录2)中我们可以查到:以下字符的ASCII码表示 字符 十进制表示 二进制表示 # 035 00100011 i 105 01101001 n 110 01101110 c 099 01100011 l 108 01101100 u 117 01110101 d 100 01100100 e 101 0110

74、0101 和表2.1相对照,可以证实内存中的存储情况,确实是将文本形式的C语言源程序(关于高级程序设计语言,本书后面会专门介绍)的开头一部分“#include”以二进制的ASCII码的方式存放在了内存中(对于字符的进一步介绍请参看第2章)。 位于内存(及硬盘文件)中的人们用C语言编写源程序,无非就是一串长长的(二进制形式的)ASCII码字符串的文本,其中并没有任何计算机可以直接执行的机器语言指令。 人们用高级语言编写的程序称为源程序(或称为源代码),源程序是不能直接在计算机上运行的,因为其中不包含任何机器语言的指令序列。目前,比较常用的计算机高级语言有C/C+,Java,Pascal,Basi

75、c,C#,fortran等,由于英语目前是国际通用语,至今为止计算机的发明和不断改进,主要也来自美欧等国,所以各种计算机高级语言(的关键字)基本上用的都是英文单词。常用的各种语言之间差别不大,学会其中了一种语言,就很容易掌握另一种(不过,Lisp,Forth这些函数式语言与以上所列这些命令型或面向对象型语言有较大的区别)。小知识:解释程序将高级语言翻译成机器语言的,还有一种解释程序。这种程序每次将高级语言编写的一条语句翻译成一条或多条机器语言的指令,然后马上运行这一段指令。然后接着翻译下一句3、 CPU一次只能做两个数(或一个数)的运算,这是由于算术逻辑单元ALU本身的结构特点所决定的,请看下

76、图,一次最多只能有两个数据进入运算器得到运算;而厨师可以一次同时将几种原料倒入一个容器中加工。图1.20算术逻辑单元(ALU)的编程结构图:1.11本章要点:1计算机组成结构:计算机系统是由硬件(CPU、内存、三套总线和外围设备)和软件(程序和数据)组成的。三套总线负责各部件之间的通信(控制信号、地址信号)及数据传输。2计算机能做什么:除了运行程序将数据加工成信息后存储或输出之外,计算机不会做任何事。3软件格式要求二进制位串:在计算机内部,程序指令、能够处理的数和码,一切都是二进制的位串。所有数字、文字、图象、声音都必须编码成二进制的位串,才能够输入到计算机内部进行处理、传输、存储。输出时必须

77、通过解码,人们才能看到(或听到)所需要的信息。4程序运行前提:将要运行的程序和要处理的数据,都必须统一存放在内存中(冯.诺伊曼结构)。外存(比如硬盘等)中的程序,必须以某种方式加载到内存中之后才能得到执行。5指令集-同一类计算机懂得的单词表:指令集是一台计算机能够执行的各种类型指令的集合。不同类型计算机的指令集是不同的。一条指令能做的事,都是极为简单低级的。6控制器-指令的译码、执行中心,运算器-数据的加工中心:程序中的指令必须从内存取到 CPU中,才能得到译码和执行,数据也必须送到 CPU中才能得到运算处理。7计算机的心跳:取指-执行周期。取指-执行周期,是计算机的核心操作,是由CPU中的控

78、制器发起和反复执行的,但需要各相关部件密切配合才能够完成。8核心控制流:指令控制器各部件为执行指令协调工作 。计算机的所有各部件,都是在控制器发出的命令下进行协调工作的。而控制器的命令则来源于对取到指令(序列)的译码。9计算机智能的来源-程序员。如果说计算机有智能的话,那么它的智能来自于程序的编写者。运行不同的程序,计算机就具有不同的智能。10内存主要特点-用字节分割、按地址存取。巨量的内存空间是以字节为基本单位来进行分割的(容量通常为几百到几亿字节)。每个字节都有唯一的一个顺序编号-内存地址。11跳转指令的本质-改变PC寄存器的值。跳转指令是一种极为重要的指令,它是通过无条件(或条件成立时才

79、)改变PC寄存器的值,来改变CPU下一条要执行的指令的。 12指令序列要按执行顺序连续存放。指令序列是按照在内存中存放的地址号,由小到大连续执行的。跳转指令只不过是重新确定下一次顺序执行的起点在内存中的哪个位置而已。13别忘存盘:内存RAM中的所有内容关机后都不复存在。所以要将程序运行得到的结果及时存盘-即存放到外存(硬盘等)的文件中,外存中的数据即使停电也可长久保存。14源程序-计算机硬件不懂的外语,编译程序-翻译员。用高级程序设计语言编写的源程序必须由编译程序(或解释程序)翻译成机器语言程序后,才能由计算机执行。15计算机人工智能的特点:超快的计算能力(每秒至少几亿次计算)、超强的记忆能力

80、(轻松记下整个大图书馆的全部信息)、各方面的潜在专家(运行这方面的高质量程序)。112 待解决的问题学完本章后,读者对计算机是如何工作的,应当已经有了一个整体上的了解。但仍然还有不少疑问,比如:1高速缓存是什么?与我们的编程工作有何关系?2什么是多核CPU? 多核CPU从编程角度看与单核CPU有何区别?3输入输出设备到底是如何工作的?与我们的编程工作有何关系?4什么是指令的预取?什么是指令执行的流水线技术?与我们的编程工作有何关系?5多道相互之间无关的应用程序,怎样才能做到同时位于内存中,并且在操作系统的调度管理下同时运行?等等。这些比较复杂的问题将在第12章讲解。下一章开始,我们将致力于如何

81、在计算机上用高级语言编程?并讲解编译程序是如何将我们用高级语言编写的程序,转变成机器语言程序的大体过程。113 本章习题:1指令地址(PC)寄存器,起了什么作用?它位于哪里?取完一条指令后,它的值一定加1吗?可以不要此寄存器吗?为什么?2指令(IR)寄存器,是用来干什么的?它位于哪里?3控制器的主要功能是什么?它位于哪里?4运算器的主要功能是什么?它位于哪里?5地址总线是单向的还是双向的?6数据总线,是否只能传输数据?不能传送指令?7是否有这样的指令,在其执行子周期不必用到内存与CPU间的三套总线?8. 在CPU中,哪些寄存器是必不可少的,哪些寄存器是可多可少的?9不看术语对照表,请将以下两边

82、的术语连线:冰箱格子的编号 内存的基本存储单元冰箱的一个格子 数据编写普通菜谱的人 状态寄存器特殊菜谱 输出设备加工步骤 用机器语言编程的程序员理想厨房 信息厨师及炒菜设备 输入设备厨房管理员 指令地址寄存器指令存放碟IR 指令寄存器普通菜谱 CPU专用加工容器 专用寄存器编写特殊菜谱的人 基本运算指令地址存放碟PC 内存地址原材料 高级语言程序采购员及配菜员 算术逻辑单元传菜生 控制单元炒好的菜 算术逻辑单元可进行的各种运算厨师可做的各种炒菜的 用高级程序设计语言编程的程序员基本动作 计算机系统理想厨房 机器语言程序状态存放碟 指令 10请你用计算机的术语,详细叙述控制单元通过三套总线到内存

83、取指令的步骤。11PC寄存器中的当前值是(01011011)2,一条跳转指令中的操作数是(00101011)2,此跳转指令执行完后,PC寄存器的中值变成了多少? 假设跳转指令中的操作数是偏移量。12.将十进制整数134转化成二进制整数。13为何计算机硬件能够永不停息(只要不关机)的运行程序?14机器语言程序(指令序列)这个“软件”为何能够命令计算机硬件工作?哪一个硬件部件起了“阅读理解”程序中指令的关键作用。它又是如何指挥计算机的其他部件来执行这一条指令的?15200GB的硬盘能够存放在多少万汉字?每个汉字占两个字节。本章参考文献:1美Ron White 著 宋铁英 陈河南 等译 计算机工作原

84、理图示教程清华大学出版社。此书生动有趣,插图很精美,适合初学者全面了解PC计算机的工作原理。2美Douglas E. Comer 著方存正译 计算机系统结构精髓清华大学出版社。本书通俗易懂、深入浅出、语言流畅、适合自学。3美Nell Dale John Lewis 著 张欣等译计算机科学概论机械工业出版社。4美 计算机系统要素电子工业出版社。在更高层次上,让你全面把握整个计算机系统的工作原理。但内容比较深,不太适合初学者。5王爽 著汇编语言(第2版)清华大学出版社。汇编语言中的一本入门级的好书。6美J.Gleen Brooksheer著 刘艺等译计算机科学概论人民邮电出版社。第二章C语言的基本

85、概念(基础篇) 本章讲解的一些重要知识,将在下一章作进一步的展开。本章着重讲解与语法要素相关的基本概念、技术内幕和简单应用。21节阐述了本书重点讲解C89标准而不是C99标准的原因;2.2介绍了子程序的定义和概念(链接、调用和返回) 2.3从粗粒度(函数)到中粒度(语句等)简述了C语言程序的构成成分:函数、定义、语句、编译预处理命令、注释等。C程序的基本构建模块函数将转变成机器语言的子程序。使读者对C程序有一个宏观认识。从2.4节到2.10节从细粒度到中粒度讲解了如何由C字符集中字符构成标识符、分隔符、关键字、常量、变量和运算符;以及如何由这些成分构成C程序(函数体)的最基本的成分:定义、表达

86、式和语句。26和2.7对输入输出作了简介。其中2.2节子程序 2.5节 变量 2.9节 表达式、2.10节 赋值语句是本章重点。在开始系统学习用高级程序设计语言编程之前,为了让你对计算机的强大计算能力有一个切身感受,请你按照附录A的上机指导的说明去运行本书中的一个程序:例题 ,求十万以内的所有素数(即质数)。你目前不必绞尽脑汁去弄明白这个程序的道理,只要你观察一下,10万以内的所有素数,普通的PC计算机(或笔记本电脑)是不是在十几秒钟内就把结果全部显示出来了?!而且通过采用更好的算法,这个问题可以在34秒钟就全部搞定!然后请你自己估计一下,如果不用计算机,你一个人做这件事要多少年才能不出一点差

87、错地完成?!计算机的超强超快计算能力、数据处理能力、巨量的数据存储能力、(借助于网络的)强大的信息传输能力和在当代社会所起的无可替代的重要作用,是你必须学好编程的根本原因和强劲动力。 2.1.1 C语言特点简介 本书选定用C语言来讲解基本编程思想和编程技巧,首先是由于当前最流行的高级编程语言比如C+,Java,C# 都是由C语言衍生出来的。因而,所有这些语言的基础语法部分,都与C语言大同小异。此外,目前很多重要的系统软件(比如操作系统)中的相当多的代码,也是用C语言编写的。通过对C语言的学习,有利于我们加深对硬件和系统软件工作原理的全面认识。在当代,C语言已成为计算机领域的一门通用语言。练习1

88、:上网查找资料并阅读参考书:C语言程序设计现代方法美K.N.King著、吕秀锋译 人民邮电出版社,归纳出C语言的主要优点和缺点。*练习2:高级程序设计语言的范式主要有四种:命令型(或称为过程型)、函数型、面向对象型、逻辑型。上网查找资料并翻阅参考书计算机科学概论,大致了解各种范式语言的特点。问题1. C程序设计语言是谁设计出来的?答:是由美国贝尔实验室的DRitchie 于19721973年间在B语言的基础上设计出来的。2.1.2 C语言标准简介C语言自从DRitchie设计出来以后,得到了迅速发展和普及,自从DRitchie与他的同事Brian W.Kernighan合写了一本名著The C

89、 Programming Language (见本章参考文献),这本书实际上成了早期C语言的标准。这个标准称为“K&R的经典C”或简称为“经典C”。很多早期开发出来的程序,是遵守这个事实上的标准的。然而这个标准尚有一些明显不足。 为了C语言的标准化和健康发展,美国国家标准协会经过长期努力于1989年制定颁布了一个C语言标准,被称为ANSI C89标准,简称C89标准。对经典C进行了一些改进和完善。 随后于1999年又颁布了C语言的一个新标准。C99标准主要做了什么修订和扩充,请参见本书附录。本书正文部分一般不涉及C99标准讨论,是出于两点考虑:第一.C99标准目前仍有不少C编译器不支持,因此使

90、用C99标准编出来的程序可移值性不太好。第二.作为一本C语言的入门书籍,用C89标准基本上就足够了。为了节省篇幅,本书也不介绍K&R的经典C,要了解C89标准与经典C有何区别,可以参考美K.N.King著的C语言程序设计现代方法。2.2 预备知识: 子程序2.2.1结构化处理流程(以下论述为简单起见,假设每条指令都只占一个字节的内存单元) 任何一个完成一个独立工作的连续的机器语言程序(指令序列)段,都可称为“处理流程”。我们把只有一个入口和一个出口的处理流程(下例中的从地址11到18中的程序段),称为结构化处理流程。一个入口的含意是:在该程序段之外,不允许有任何跳转指令,跳入到该程序段的内部(

91、下例中的地址12到18不允许跳入),而只允许跳转到该程序段的入口处(下例中的地址11);一个出口的含意是:在该程序段中,只允许最后一条指令(下例中地址18中的指令8)是跳转到该程序段外部的跳转指令。其他指令(下例中的指令1到7)即使是跳转指令,也只能在该程序段内部跳来跳去。这就是结构化处理流程。 内存地址11入口12131415161718出口19指令指令1指令2指令3指令4指令5指令6指令7指令8说明只允许外部的跳转指令跳转到此处仅此条指令可以跳转到程序段外部图21结构化处理流程A示意图(地址11到地址18中的一段程序是一个结构化处理流程)2.2.2 子程序在上述图2.1这个从地址11到18

92、这8条指令构成的结构化处理流程A中,其实还可以允许有这样的一种特别的跳转指令(比如是地址为13中的指令),跳转到另一个特殊的结构化处理流程B的入口处(地址为204)。这个结构化处理流程B的特殊之处在于:它的最后一条指令是一条无条件跳转指令,而且恰好是一条跳回到结构化处理流程A中尚未执行的第一条指令之处的跳转指令(即208中的指令为:跳转到 地址14 )。如图2.2所示:10111213141516171819指令入口,允许外部指令跳转到此处跳转到204出口结构化处理流程A 204205206207208 出口入口跳转到 地址14结构化处理流程B:由地址204到208 中的指令序列构成图2.2

93、子程序的链接、调用和返回的示意图图2.2说明:结构化处理流程B被“链接”到了结构化处理流程A的第13条指令处。执行地址13中的跳转指令就是“调用” 结构化处理流程B。执行地址208中的跳转指令就是“返回”到结构化处理流程A。 一段结构化处理流程的最大特点是:将其进行一些微小的调整后,就可以链接到任何一个程序(段)之中。子程序:我们将一段功能独立、可以链接到任何别的程序(或程序段)中的结构化处理流程,称为子程序。如图2.3所示的结构化处理流程B就称为子程序。子程序的调用:某个子程序被一个程序“调用”,指的就是该子程序被链接到这个程序之中开始得到运行。主调程序:调用子程序的程序(程序段)被称为主调

94、程序。如图2.2所示的结构化处理流程A就是主调程序。子程序的返回:执行子程序中的跳转指令重新回到主调程序的被中断执行之处,称为返回。 如图2.3中执行地址208中的跳转指令(跳转到 地址14)就称为“返回”。2.2.3 子程序“调用”与“返回”中的数据传递但是,如果主调程序需要传递一些数据给被调用的子程序(这是很常见的情况),被调用的子程序也需要将运算处理结果回传给主调程序(这也是很常见的情况),仅仅靠两条跳转指令的“链接”就完成程序段之间的调用和返回是绝对不行的。首先,子程序要能够接收主调程序传递给它的数据。也要能够将处理结果回传给主调程序。使得主调程序和被调子程序之间能够交换数据的最直接的

95、方式是:让主调程序和被调用子程序共享一段存放数据的内存空间。这种方式适合于主调程序和被调子程序之间需要大量传输数据的情况。主调程序和被调用子程序之间另一种比较好的、常用的双向传递少量数据的方式是:采用一种特殊的“栈”来进行(可参见下面的深入一步)。其次,被调用子程序返回后,还不能影响主调程序的继续正常运行,这里存在一个“保留现场”和“恢复现场”的问题(感兴趣的读者,请参见下面的深入一步)。我们在初学时对于子程序调用时数据的双向传递、保存现场和恢复现场这两个比较深入的课题,不必刨根问底,只要大致有些了解就够了。我们用C语言编写程序中的主要构件函数,将会由编译程序自动转变为子程序,这两个麻烦的问题

96、编译程序会自动帮助我们解决(然而用汇编或机器语言编程可就没有这么幸运)。本书后面会在合适的时候重新深入探讨这两个问题(参见第7章函数、第12章提高部分)。深入一步:子程序调用和返回时的重要角色“栈”的功能:传递参数、保存现场和恢复现场采用内存中的一种特殊的“栈”结构(“栈”其实是用编写程序的方式,在内存中“构造”出来的一块“后进先出”的内存区域)在主调程序和被调子程序之间传递数据,主要是为了方便程序的嵌套调用(即在被调用的子程序段中,还可以调用另一个子程序)、主调程序和被调用子程序之间,只要利用存放在CPU的SP寄存器(称为栈顶指针寄存器,大多数计算机的CPU中都有这样类似的一个寄存器)中的一

97、个数据“栈顶地址”,就可通过“进栈“和”出栈“的方式,利用栈来传递多个数据。被调用的子程序,首先可以利用“进栈”操作的指令来保留主调程序的“现场信息”。在返回主调程序之前,还要通过“出栈” 操作来恢复主调程序的“现场信息”。 所谓“现场信息”,是指在调用子程序前(即执行跳转指令之前的那一瞬间)CPU中各寄存器中的当前值。这些值如果不事先备份到内存中的某处(比如“栈”中),其中有些数值就很有可能被调用的子程序在指令执行过程中覆盖掉了。这样一来,即使通过再次跳转返回到了主调程序,主调程序也不能正常继续运行下去。因为主调程序的“现场信息”被破坏掉了。 因此,要用机器语言(或汇编语言)编写一个可以被别

98、的程序调用的通用子程序也不是一件简单的事。如果你编写的程序段不能保留和恢复主调程序的现场信息,就不能称其为子程序。哪怕这个程序段在其他方面做得非常出色。这个程序段也不能够被别的程序“调用”。幸运的是,在我们用高级语言编写可以翻译成子程序的函数()时,参数传递的细节问题、保留和恢复主调程序的现场信息,都不需要编程者来考虑。这些麻烦的然而是例行的事务,编译程序都为我们包办代替了。 子程序调用时利用栈来进行数据双向传递、保存现场和恢复现场的细节问题,请参见第12章提高部分。23 C语言程序的构成成分:我们通过以下演示程序2.1来探讨C语言源程序的构成成分(该程序的功能在学完本章后,读者自然应该能够懂

99、得,现在不必为此感到烦恼)。演示程序2.1 输入圆球的半径,求圆球的体积。C语言源程序如下:0 /*1 作者: 何勤2 编写日期: 2009-12-253 功能: 输入圆球的半径,求圆球的体积4 演示程序2.15 */6 #include 7 #define PI 3.1416 /*指定符号常量PI的值为 3.1416 */8 int main()9 10 float r ; /*变量定义:圆球的半径r*/11 float v ; /*变量定义:圆球的体积v*/12 13 printf(“请输入圆球的半径,以米为单位n”); /*语句,输出提示信息*/14 scanf(”%f”,&r); /*

100、语句,输入库函数scanf 的调用语句*/15 v=(4.0/3.0) * PI * r * r * r ; /*语句,赋值语句,求圆球的体积v*/16 printf(“圆球的体积是:%f立方米n” , v ); /*语句,用printf输出圆球的体积*/17 return 0 ; /*返回语句,返回到操作系统*/18 注意:最左边的行号是为了说明方便而加上的,实际C语言源程序中是不能有的。2.3.1 C语言源程序的宏观构成成分函数定义一个C语言的源程序文件的宏观构成成分是函数定义。演示程序2.1从第8行到第18行,是一个(函数名为main的)函数定义。一个C语言源程序文件,一般由多个函数定义

101、顺序组成,其中必须有一个main函数。程序的运行都是从系统调用main()函数开始的。但是本书直到第7章,才讨论由两个以上函数组成的C语言源程序应该如何编写。main函数又称为主函数。函数定义经常简称为函数。 C语言源程序中的函数,除了main()函数比较特殊外(它被编译程序翻译成机器语言的主程序,这个主程序将由操作系统调用),其他函数都会被编译程序翻译成机器语言形式的子程序。函数之间(其实是其相应的机器语言的主程序和子程序之间、子程序和子程序之间)都是可以互相调用的(函数调用的细节问题,将在第7章函数讲解)。2.3.2函数定义=函数首部+函数体任何函数定义,都是由函数首部(演示程序2.1中的

102、第8行。以下几小节中的行号,都是指演示程序2.1中的行号)和函数体(第9到第18行)构成。函数首部由:返回值类型 函数名(参数表)构成。例如: int main()函数体必须用大括号“”和“”括住。深入一步:C语言中的函数概念与数学中的函数概念的对比 注意C语言中函数的概念与数学中学过的函数概念之间是既相似又有区别的:数学中的函数必须先给予该函数的自变量一个数值(或几个数值,如果是多元函数的话),然后才可进行由函数解析式规定的运算,最后得到一个函数值(此函数值一定有)。 但是在C语言中,对“函数”的要求则更为宽松。C语言中的一个函数在被别的函数调用时,主调函数(相当于机器语言的主调程序)通常要

103、传递几个称作为“参数”的数据给这个被调用函数(这是常见的情况);但是在某些时候,主调函数也可以不传递任何参数给这个被调用函数(这取决于被调用函数是否需要调用参数)。 C语言中的一个函数在被别的函数调用后,可以将该函数的程序段运行后得到的一个结果,返回给调用它的主调函数(这相当于数学中的函数值。在C语言中,这要使用return语句将结果带回给主调函数,return语句的格式为: return 返回值; );但是,被调用函数也可以不返回任何结果值给主调函数(被调用函数只要为主调函数完成一个特定任务就可以了)。 2.3.3函数体的基本构成成分:定义序列+语句序列C语言程序中,任何函数的函数体的基本构

104、成成分是:定义序列(第10、11行的左半部)和语句序列(第13行到第17行的左半部)。这两种成分都要以分号结束(复合语句除外,参见第4章)。在C语言函数的函数体中,所有定义必须位于语句序列之前(其他不少高级语言可以在任意的位置进行变量的定义)。C语言程序中的定义序列,被编译程序转变成了机器语言中的数据序列(包括分配和安排这些数据的内存单元所需附加执行的一些指令);而C语言源程序(函数体)中的语句序列,则被编译程序翻译成了对该数据序列进行运算处理的机器指令序列(即子程序)。翻译后的机器指令序列和数据序列,在程序运行之前都必须在内存中(或者由操作系统从外存加载到内存中)。2.3.4 C语言源程序的

105、次要构成成分:编译预处理命令、注释和声明C语言源程序的次要构成成分是:编译预处理命令、注释和声明(详见第7章)。编译预处理命令和注释是不以分号结束的语言成分。编译预处理命令(演示程序2.1的第6行、和第7行的左边部分)在正式进行编译之前,编译预处理程序将根据源程序中的编译预处理命令,对源程序文件进行一些辅助性的文本插入(#include命令)、替换(#define命令)和编辑(参见第 章)等工作。 #include命令,是文件包含命令,负责将头文件(文件的扩展名为 .h )中的文本,插入到你的源程序中#include命令所指示的位置上。每一条编译预处理命令都是以“#”开始,不以分号结束的。每条

106、编译预处理命令必须书写在一行上。编译预处理命令,通常放在C源程序文件的开始处。注释(演示程序2.1中从0行到5行,以及每行右半部以/*开始,以*/结束的内容,都是注释)注释是以“/*”开始,以“*/”结束的字符序列,注释中可以出现非ASCII码的本地字符(比如中文)。源程序中的注释是给人看的,而不是给编译程序“看”的。图 2.3 注释是给人看的,语句是给计算机看的 注释会被编译程序忽略。但良好的注释使源程序更易被人们读懂,这将使源程序更容易维护、修改和进行交流。注释的常用方式有两种:一种是注释内容独自占据多行,对注释以下的一段程序或整个源程序文件进行说明;另一种是出现在一行的右边,对同一行左边

107、的内容进行说明,参见演示程序2.1。 修改源程序的语句时,一定不要忘了修改相应的注释。不然的话,这段未经修改的注释很可能使以后的程序维护者或阅读者不知所措、深受其害。深入一步单行注释以 “/”开始,它的使用比较方便、不易出错,虽然在不少C编译器中可使用,但它不是C89标准规定的(”/”是C99标准规定的单行注释)。在调试程序即查找程序有无错误、是否完善时,经常将一段代码暂时变为注释。注释“/*”和“*/”不支持嵌套使用。所以我们无法将已经含有这种注释的一段源程序不加修改的变成注释。不要忘记书写注释完毕时的结束符号“*/”。最好首先输入一对 “/*” “*/” ,然后再在两者之间插入注释文本。2

108、.3.5 C语言源程序(文件)的宏观规范结构以上这些构成成分,通常是以如下规范方式来构成一个C语言源程序的:#include /头文件包含命令,不以分号结束#include /其他头文件包含命令#define PI 3.1416 /宏定义,不以分号结束#define /全局变量(参见第7章)或类型定义 / 声明(参见第7章),以分号结束int main() /函数首部,不以分号结束 定义序列; /必须在语句序列前面。所有定义均以分号结束 语句序列; /除了复合语句外,其它语句均以分号结束 return 0;返回值类形 (参数表) /函数首部,不以分号结束 定义序列; 语句序列; 深入一步:函数

109、定义的先后顺序是任意的。主函数 main()可以出现在程序中的任意位置。在C语言中,函数定义不能嵌套。即:不允许在一个函数定义的函数体中,出现另一个函数定义。其他高级语言不一定有此限制。 定义序列一般出现在函数体中的最前部,但是也可以位于函数定义的外部(详见第7章:函数)。语句序列只能出现在函数体的内部,并且要位于该函数体中定义序列之后。虽然C语言标准没有要求,但建议读者不要把多条语句书写在同一行上。这会为程序的单步调试带来定位错误语句的困难,因为程序的单步调试运行时是以一行程序为一步来推进的。2.3.6库函数和库函数调用:库函数:库函数是编译程序(又称为编译器)附带的,一批事先编写好了的,可

110、以被任何别的C语言程序“调用”的,通用的、命名了的子程序。 所有编译器附带的库函数组成了一个标准函数库,与编译程序一道打包发行。安装了C语言的编译程序后,我们就能在编写程序时,调用这些库函数。 函数调用(function call):在我们编写的C语言源程序的语句中,可以调用库函数为我们的程序做一些复杂的(通常是辅助性的)运算和处理工作,函数的调用方式为:库函数名(参数列表)参数列表必须要用圆括号括住,其中参数列表中的参数如果多于一个,参数之间要用逗号隔开。比如要求出5.6的4次方是多少,就要以这种方式调用pow库函数: pow(5.6 , 4) ;将2开平方,就要调用库函数:sqrt(2)。

111、编程时调用了库函数,这些事先编译好的、命名了的子程序,就可以通过“编译”步骤之后的链接这一步骤,链接到你所编写的程序之中,共同组成一个可执行的机器语言程序(可执行程序文件的扩展名是 .exe )。这样就可以大大减少编写程序的工作量。 调用库函数时不要忘记包含相应的头文件。C语言中的各种常用库函数的调用方法说明,请参见附录C。库函数名用的都是小写英文字符构成的标识符。C语言中,常用的数学库函数如下:(注意:调用以下这些数学库函数时,不要忘记加上头文件包含命令:#include )sqrt(x)求x的平方根sin(x)正铉函数cos(x)余铉函数exp(x)以e为底的指数函数log(x)以e为底的

112、对数函数log10(x)以e为底的对数函数fabs(x)求x的绝对值fmod(x,y)求x/y的余数floor(x)求不大于x的最大整数pow(x, y)求x的y次幂 表2.1常用数学库函数演示程序2.2 编写程序,在屏幕上显示一行文字: Hello World! #includeint main() printf(“ Hello World!n”) ; return 0 ;编译并运行此程序,在显示屏上我们将看到 Hello World!本例题程序只有两条语句:一条是格式化输出库函数printf()的调用语句。这条语句的最基本的功能是:将输出库函数printf()中的一个调用参数双引号括住的一

113、串字符,按照原样输出在显示屏上。格式控制串:输出库函数printf()中双引号括住的这一串字符,称为格式控制串(有时简称为格式串)。这是调用printf()库函数时必须要填写的一个最重要的参数。格式控制串中末尾的两个字符 n 不是按照原样在显示屏上输出的普通字符,所以没有显示在屏幕上,其作用是通知输出设备(显示器或打印机)换行。另一条语句是 return 0 ; 语句,这条语句的作用是在程序运行完后,正常返回到操作系统。2.3.8 C语言源程序的编辑、编译、连接和调试过程(参见附录A):1. (用编辑软件)编辑输入源程序,并保存在扩展名为 .c的文件中; 2. 对源程序进行编译前的预处理工作;

114、3. 用编译器对(经过编译预处理的)源程序进行编译;4. 根据编译器给出的错误和警告回到第1步修改程序中的语法错误; 5. 编译无错后,生程目标文件(文件扩展名为 .obj);6. 连接程序将用户程序与库函数的子程序段(以及启动程序段)进行链接,生成可执行程序(文件扩展名为.exe);7. 试运行此可执行程序,发现链接或运行时的错误则回到第1步进行修改;8. 结束调试过程,注意保存并保管好无错误的源程序文件(有必要的话可将其刻在光盘上),日后进行软件维护这是必须的。 插入C语言源程序的编辑、编译、连接和调试过程示意图注意:在VC+6 .0集成开发环境下,第2步、第3步(还可以包括第5和第6步如

115、果程序没有语法错误的话)是一次性完成的(参见附录A)。在不同编程环境下,C语言源程序的编辑、编译、连接和调试过程是有一些区别的。本书不作详细介绍,请读者自己查阅相关资料。有了上述对C语言构成成分的大致整体了解后,就让我们正式踏上漫长的、充满陷阱、知识、挑战,开始时有些枯燥乏味,但以后有着无穷乐趣的系统学习编程的“捷径”(不走弯路,通过一本书就能真正掌握和积累大量编程的基本思路和技巧,就是最短最快的捷径)之路吧!24 C语言的基本语法在23节中,我们从粗粒度到中粒度,大致了解了C语言源程序的宏观构成成分、基本构成成分和次要构成成分。从本节开始,我们将从另一个方向即从细粒度到中粒度来论述:(1)由

116、C语言的最小语义单位字符集(类似于自然语言中拉丁语系的字母表)如何构成C语言程序的基本要素:标识符、关键字、常量、变量、运算符、函数调用。(2)由C语言程序的基本要素,如何构成C程序中的基本构成成分:各种定义、各种类型的表达式和各种类型的语句。但在本章中,仅仅介绍几种基本类型变量定义;一种最常用的表达式算术表达式;三种最常用的语句赋值语句和格式化输出输入库函数调用语句。2.4.1正文部分:除了字符串(包括格式串,见2. x 节 printf() 和scanf() 函数)、注释部分以外的C语言源程序的其它部分,称为C语言的源程序的正文部分。2.4.2 C语言的字符集26个大小写英文字母:AZ,

117、az;阿拉伯数字字符:09;特殊字符: ! # % & * _ + = - | / “ ; . , ( ) ? : 空格字符(对应键盘上的最长键Space) 以上所有这些字符构成了C语言的字符集。书写C语言的源程序的正文部分,只能使用C语言字符集中的字符构成,其它任何字符不得使用。字符集中的任何字符,都只能在英文半角方式下进行输入。常见的编程错误误用中文输入法下输入的符号。最容易用错的是逗号 “,” 、分号“;”、圆括号 “(” 、“)”和双引号 “ ” ”。2.4.3标识符标识符是以C语言字符集中的26个大小写英文字母、阿拉伯数字0到9和下划线_构成的字符序列;其中从左边开始的第一个字符不能

118、是数字。在C语言中,只能使用符合标识符规定的名字来对变量()、符号常量()、函数、语句标号命名。判断一个字符串是否标识符还是非常容易的:首先,看这个字符串之中是否出了英文字母、阿拉伯数字、下划线以外的任何字符(容易看错的是负号-,误把它当成下划线_;此还有空格“ ”);其次,看它左边第一个字符是否数字。你可以通过做以下练习进一步熟悉标识符的命名规则:练习.下列四组字符串中都可以用作C语言程序中的标识符的是( )。 A、print _3d db8 aBc B、Iam one_half start$it 3paiC、str_1 Cpp a3 while D、Pxq My-book *p1 His.

119、age答: A 。定义标识符还必须注意以下几点1 C语言对标识符的大小写敏感,也就是说main与Main,printf与Printf都是不同的标识符。main和printf都是C语言中的已经规定的函数名,所以,我们只能用main来作为主函数名,而不能用Main或MAIN来作为主函数名。2 C89标准可识别标识符的长度是32位,但它受到各种不同版本的编译器的实现限制。例如在某一版本的编译器中,标识符的有效长度是8位,那么所有前8位(即靠左边的8位)相同的标识符将被该编译器当作是同一个标识符。3 在满足标识符规定的前提下,标识符虽然可由编程者随意给定,但还是用“见其名而知其意”的标识符来为变量、符

120、号常量、函数等命名更好。 2.4.4关键字 “关键字”有的教科书又称为“保留字”,指的是有少量的特定的标识符(英语单词)被编译程序保留下来,表示特殊的规定意义。编译程序一旦“读到”你在源程序中写下的关键字,就会明白你要它做何事。因此,我们在编程时不能把关键字定义为我们自己的程序中想要用的标识符。关键字主要分为两大类。其中一些关键字是用来告诉编译程序:如何为命名了的数据(即变量)分配存储该数据所必须的内存空间的这是用于构成定义(变量定义或类型定义)的关键字。比如:int、float、char (参见2.5) 。另有一些关键字是用来告诉编译程序:我们要计算机如何有选择地运行程序中的一些语句的这是用

121、于构成语句的关键字。 比如: if 、while、return 、break、goto 等。在 ANSI C89标准中共有32个关键字,所有关键字列表如下: breakautocasecharconstcontinuedefaultdoelsedoubleenumextxrnfloatforgotoiflongintregisterreturnshortsignedsizeofstaticswitchstructtypedefunionunsignedvoidvolatilewhile 表2.2 C89标准的关键字关键字注意不要误用大写字符,比如If 、 While、 Switch 、Floa

122、t 都不是关键字。不要将编译预处理命令、库函数名与关键字混为一谈。在程序中这三者起的作用有明显区别。2.4.5 C语言源程序中的分隔符C语言源程序中可使用的分隔符有三个:空格、回车/换行、逗号。同类项之间要用逗号分隔;关键字和标识符之间要用空格或回车/换行来分隔,不能用逗号。比如: int mum , age ; 关键字和标识符之间,一定至少要有一个空格隔开。例如,以下方式就是错误的: intmum , age ;C编译器会把intnum 当作一个标识符来对待,然后就会报告有错。在C语言中,合理地使用“多余的”空格和空行(即不起语法分隔作用的空格和空行),可以使得程序更清晰、易懂、优美(比如:

123、在加法运算符 + 的两边,分别加一个空格:a + b 就比不加空格:a+b 要好看些)。程序写得太紧凑(一行中书写多个语句),并不是好事。这样的程序既不好懂,也难以调试。2.4.6 C语言中的常量 现实世界中,有一些不变量,经常出现在自然科学、工程科学等问题中。比如圆周率、重力加速度、阿伏加德罗常数等。在编写C语言程序时,我们可以用常量来表示这一类数值不变的量。 C语言程序中常量的书写规则与我们日常工作的书写规则有些区别。你必须严格按照C语言的规范要求来书写,否则,C编译器无法将其转变成机器指令能够使用的二进制形式的常量。C语言中常量分为数值常量和符号常量两种。程序中应当多使用符号常量,尽量不

124、要用意义不太清楚的数值常量。 数值常量最常用的数值常量有以下几种:(1)十进制整型常量 567、-425 、0 等, 是没有小数分量的数值。可以带有正负号,正号可以省略不写。注意:整型常量的各数字之间不能出现空格或逗号。(2) 实型常量由正负号、数字、小数点构成。比如:+0.543、5.43. 、-543.0、0.0 、0.6667 、 .386 都是十进制常量。在数的左边可以加上正负号。(3)字符常量 a 、K、 + 、 8 、 等 ,都是字符常量。是用单引号括起来的ASCII字符中的所有单个字符(参见附录B )。在C语言中,空格 也是一个字符(即按下键盘上最长的键所产生的字符)。 注意:数

125、值常量6和数字字符 6是不一样的。数值常量6在计算机内部表示为二进制的110,而数字字符6经过查表可知,它的ASCII码值为 56,等于二进制数 111000 。字符常量在程序中书写时,一定要用单引号(有的教科书称为撇号)括住它。为的是与程序中可能出现的(用来做变量名或常量名的)单个字符的标识符区分开。 ASCII字符集中,码值从0到31的字符,都是一些控制打印和通信用的特殊字符,这些特殊字符,无法直接用通常方式在程序中书写出来。对于这一类字符,就必须用转义字符来表示。所有转义字符都必须以 开始。前面演示程序2.2介绍过的n就是一个表示换行的转义字符。所有其他转义字符请参见下一章。字符常量属于

126、一种从表面上看来是非数值型的量,但其实在计算机的内存中,它通常就是一个以ASCII码形式存储的占用一个字节内存的二进制整数。字符型量是用计算机进行文字处理的基础。(4)字符串常量:”567”,”hello!”等,是用双引号括起来的连续多个字符。我们在程序中看到的字符串常量,其实在内存中,不过是在多个连续的字节中存放的一串二进制的ASCII码而已。注意:不要将单个字符构成的字符串当成字符常量。例如:”a”是字符串常量,而不是字符常量。符号常量符号常量,一般由大写英文字符组成的标识符构成,用#define来进行定义, 比如: #define PI 3.14 这样一来,源程序中所有出现的标识符 PI

127、 的地方,都会被编译预处理程序用 3.14 替代。使用符号常量,使得修改常量的值变得非常方便(比如只要改:#define PI 3.1416 ,则程序中多处出现的PI 现在全都会被编译预处理程序替换成 3.1416 ),程序的可读性也更好。25变量 不象机器语言程序,在高级语言源程序中,我们是不能直接用数据所在的内存单元的地址来表示,我们要对哪个数据进行操作和运算了。为什么?因为在操作系统成为计算机软硬件资源的大管家的环境下,任何其他程序要使用内存单元来存放数据,都必须事先向系统(编译程序+操作系统)提出申请。我们的程序既要使用内存单元来存放程序中需要加工处理的数据,系统软件(编译程序+操作系

128、统)的存在,又使得我们的源程序无法直接使用具体的内存单元来存放要加工的数据。对于这个两难问题怎么办呢?现实生活中的一个类似问题的解决方案,给了我们很大启发。 很多单位大门口成排的邮箱,是由小到大连续编了号的(即邮箱号)。其中有不少已经被住户通过收发室老头间接地占用了。见表3.1(这张表住户看不到)邮箱号邮箱名邮箱类型邮箱号邮箱名邮箱类型1李逵报刊6八戒报刊2黛玉信件738貂蝉信件4唐僧信件95时迁报刊10表3.1申请之前的邮箱安排表 所有邮箱都由收发室的老头统一管理和进行存取。那么新来的住户如何才能通过老头来间接使用邮箱呢?他必须先按照收发室老头的规定,申请一个邮箱。格式要求如下:邮箱类型 邮

129、箱名 于是新来的住户按照此格式填写了一个申请,送给老头: 信件 吕布老头收到他的申请后,查看一下邮箱安排表,找到没有用户使用的一个信件类邮箱(3号邮箱)。然后将邮箱名“吕布”、 邮箱类型“信件”,填入这张邮箱安排表中。 邮箱号邮箱名邮箱类型邮箱号邮箱名邮箱类型1李逵信件6八戒报刊2黛玉信件73吕布信件8貂蝉信件4唐僧信件95时迁报刊10 表3.2受理申请之后的邮箱安排表这样一来,将来收到名为吕布的任何信件,健忘的老头都会查看邮箱安排表后,将其放进3号邮箱中;用户前来取信件,只要报出自己的邮箱名:吕布,老头查看邮箱安排表后,就将3号邮箱中的信件取出给他。只要这个新来者愿意,他可用更多的邮箱名向老

130、头申请多个各类邮箱为自己所用。 任何用户根本不(必)知道自己的邮箱号是多少,只要通过邮箱名就可借助于老头间接存取邮箱中的邮件。换句话说,使用邮箱名就等价于使用邮箱号来定位并存取邮箱。这样一来,虽然邮箱都没有锁,但多个住户同时使用多个邮箱再也不会发生邮箱资源的冲突了。这样做的另一个好处是:当一个住户搬走后,邮箱可以很方便地回收,以便给别的新住户使用(只要修改邮箱安排表即可)。收发室的老头系统(操作系统+编译程序)新来的住户你编写的一个源程序(或函数)用 邮箱类型 邮箱名 向老头申请邮箱用 变量类型 变量名 向系统申请内存单元邮箱安排表(邮箱号-邮箱名-邮箱类型) 内存空间安排表(内存地址-变量名

131、-变量类型) 邮箱号(变量所对应的)内存单元的地址邮箱名变量名邮箱(变量所对应的)内存单元邮件变量的值 表3.3 术语对照表251变量的定义:在高级编程语言中,向系统申请一个存储数据的内存单元,称为定义变量。通过这种方式,让系统为我们的源程序中所起的变量名,在内存中选择并分配合适的内存单元。变量的内存单元一旦分配完毕,在源程序的语句中,就可以通过这个变量名,来存取相对应的内存单元中的数据(这个数据被称为变量的值)了。点评:通过抽象的变量名,来存取变量的值;而不是通过直接指明内存地址来存取内存单元中的数据。这就是用高级编程语言进行编程的鲜明特色和最大优点。以上这个类比中,一个最大的不同之处在于:

132、收发室老头不仅要建立并经常修改邮箱安排表,还要根据邮箱安排表进行邮件的收发工作;而编译程序所做的建立并经常修改变量内存安排表的这个工作与老头类似(如何通过编写一个程序,在内存中建立一张表,并如何查找、修改这张表,请参见例题XX )。但是,编译程序并不负责具体的数据存取工作,它只是通过查找这张变量内存安排表,把源程序语句序列中所有用变量名表示的数据存取工作,翻译成机器语言中的用内存地址表示的数据存取指令。对于基本类型的变量,定义变量的方式为:类型名 变量名; 例如: int age ; 定义多个变量的方式为:类型名 变量名列表; 例如: int age , num ,sum ; 变量名一般要用标

133、识符来命名。在变量名列表中的多个变量名之间要用逗号隔开。与邮箱有不同的类型(信件邮箱、报刊邮箱)相似,在高级语言中,存储数据的内存单元也有不同的类型。常用的基本类型主要有:int(整型) 、float(单精度实型)、char(字符型)等。各种类型的变量定义简述如下:(基本型)整型变量:在C语言中,用类型关键字int来定义。 在程序运行时,需要内存单元,存储只以整数值形式出现的(其值可以发生变化的)数据,要用此类型来定义变量。比如: int number, age ; 定义了两个整型变量number 和age。任何一个int 型变量,在VisualC+ 6.0编译环境下被系统分配了4个字节作为变

134、量的存储单元,取值范围是在-2147483648到 2147483647之间;而在Turbo C 2.0编译环境下,被系统分配了2个字节作为变量的存储单元,取值在-32768到32767之间。单精度浮点型变量 : 在C语言中,用类型关键字 float来定义。在程序运行时,需要内存单元存储以实数形式(即有小数分量)出现的量(比如34.1,-678.34等),要用此类型来定义变量。 比如 float score; 定义了一个单精度实型变量score。 单精度的实型变量是有符号数,在Visual C+6.0开发环境中,也被系统分配了4个字节作为变量的存储单元。单精度浮点型变量的精度是十进制的7位。也

135、就是说,只有数值中的高7位数字肯定是正确无误的。深入一步单精度浮点型(即float型)变量在内存中的存储方式请参见第三章提高部分。float型变量的优点是取值范围远比int型变量大(大约正数是在1.1710-38到3.41038之间,负数取值范围与正数是对称的)。然而与int型变量相比, float型变量的缺点是:运算速度不如int型变量快,存入的数据也是一个近似值。字符型变量 用类型关键字char来定义在程序运行时,需要用内存单元,存储(其值可以发生变化的)ASCII字符,要用此类型来定义变量。 比如 char ch1,ch2; 定义了两个字符变量ch1和ch2。任何字符型的变量,在内存中都

136、被系统分配了一个字节的存储单元,用来存放该变量所对应ASCII字符的码值。所以,字符变量本质上是一种取值范围很小的整型变量。 252变量类型的作用在很多高级语言中,由于具有多种不同类型的变量可供选择,给我们使用高级语言编程带来了极大的便利(比机器硬件本身提供的数据类型要多,这是使用机器语言或汇编语言编程时所没有的优点)。使我们在编程时,仅需根据应用问题中数据的外在形式(通常是十进制的)、取值范围、数据类型和精度来决定,到底要采用哪(几)种类型的变量来进行编程。而不必关心变量的存取、变量的数值转换等底层的细节问题。 “变量类型”的技术内幕对于不同类型的变量,编译程序分配安排给变量的内存单元字节数

137、很可能不一样、数据的外部表现形式与机内存储形式不一样、选用的运算指令类型不一样(比如对于实型量加法,编译程序选用浮点数加法运算指令;而对于整型量加法,则选择整数加法运算指令)、输入输出变量值的转换工作不一样、变量取值的允许范围可能不一样、允许进行的运算也不一样;但上述所有这些不一样(除了最后两项需要编程者注意外),大多都不需要编程者来具体操心。这些原本极为琐碎的基础性的编程工作,只要我们恰当地定义了变量的类型,并在程序语句中合理的使用变量,编译程序(包括标准输入输出库函数)基本上都为我们代劳了。2.5.4变量的初始化变量的初始化,就是存入一个初始数据在变量对应的存储单元中。在C语言中,变量一定

138、要先进行定义,这样才可以获得由系统分配的内存单元。变量定义后,还一定要进行初始化,也就是要将一个数据存放在变量对应的内存单元中。然后该变量(由于有了一个有效的值)才能用来参与计算或进行输出。否则,变量所对应的内存单元中(以前运行别的程序遗留下)的垃圾数据,就会在程序的运算或输出时被误用,造成程序运行错误。最简单的初始化变量的方法,是在定义时,在变量后面用等号“=”给它一个初始值。初始值必须是常量(或常量表达式)。比如如下变量定义:int number , sum = 100 ; char ch1 = a , ch2;float area=65.432 ;这三条变量定义的“变量内存取值示意图”如

139、下: number sum ch1 ch2 area垃圾100 a垃圾65.432 图 2.4变量内存取值示意图这样一来,除了变量 number和ch2 中的值是垃圾数据外,int型变量sum和char型变量ch1和floatt型变量area都得以初始化了。深入一步 :为何说图2.4只是变量内存取值示意图?答:变量在计算机内存中真实的存储并不是如图2.4所示那样的。这只是从高级语言编程者的视角来看的内存存储数据的抽象模型(如果你想进行系统编程或嵌入式编程,对数据的某些二进制位进行操作,这种模型将无能为力)。在内存真实数据存储模型中,有符号整型数是以补码型式存放的;字符数据是以它所对应的二进制的

140、ASCII码来存放的。而单精度实型数(在大多数计算机中)是以IEEE标准规定的二进制的“符号位+指数位+尾数位”的余127码形式存放的。(参见第三章提高部分)。初始化变量的有三种方法。初始化变量的其他两种方法,是使用scanf()输入库函数的函数调用语句和赋值语句,(请参见第 页)26变量值的输出 变量的值如果不从内存单元中取出来(通常还要经过进制转换或解码),通过输出设备输送到计算机的外部,这个值对外界就不起任何作用。格式化输出库函数printf()不仅可以原样输出格式串中的内容到显示屏上,还可以通过调用printf()函数输出变量的值到显示屏上。调用的一般形式为: printf(参数1,参

141、数2,参数3,参数n); 其中 参数1 是用双引号括住的格式控制串 ,参数2,参数3,参数n 是输出项列表。 图2.5 printf()的作用:将数据送到显示屏上演示程序 2.4 #ncludemain()int num ; float x ;char ch;num = 123; /将123存入整型变量num对应的内存单元中x=34.678 /将34.678存入实型变量num对应的内存单元中ch=+; /将字符常量+存入字符变量ch对应的内存单元中 printf(%d %f %cn, num, x,ch); /将三个变量的值从内存中取出来,在显示屏上输出程序运行结果为:123 34.67800

142、0 +格式控制串是用双引号括起来的字符串。其中:%d 是第一个输出项num的格式符,以有符号的十进制形式输出整数num(正数不输出符号)%f 是第二个输出项x的格式符, 以小数形式输出float实数x,默认输出6位小数。 %c 是第三个输出项ch的格式符, 以字符形式输出变量ch的值。格式符与输出项的类型必须匹配,见下表 格式符 输出项(变量)类型 %d int %f float %c char 表2.2格式符与输出项(变量)类型的匹配注意:在格式串中,三个格式符之间都加了一个空格。如果将其连在一起写“%d%f%c”则输出数据就会紧密地连在一起: 12334.678000+ 这是决对不可取的。

143、为了清晰起见,格式控制串中除了格式说明之外,还可以有一些按照原样输出的普通字符。例如: printf(“num=%d,x=%f,ch=%cn, num ,x,ch ); 这样的输出结果为: num=123, x=34.678000, ch=+ /这里故意将普通字符倾斜显示如果变量num代表车牌号,x表示车速,而ch表示车是否要加油的话,则使用以下的printf()函数调用语句更为优秀: printf(“车牌号为%d,车速是%f千米/小时,加油标志:%cn, num ,x ,ch);输出结果为: 车牌号为123,车速是34.678000千米/小时,加油标志:+在格式控制串中,还可以加上前面讲过的

144、换行符号n 例: printf(“ num=%dn x=%fn ch=%cn”,num , x,ch );输出结果为: num=123 x=34.678000 ch=+域宽和精度 %m.nf :对于实数,要想改变默认的小数点后面显示6位的格式。可将格式符%f改写为 %.nf例如: printf(x=%.2f,x);则输出结果为: x=34.68 /4舍5入只保留2位小数加上对域宽的规定为7位,则变为:printf(x=%7.2f,x);输出结果为: x= 34.68 说明: 34.68左边多出两个空格,整个变量x的输出域宽(包括数字、小数点和空格一共)占了7位。如果指定域宽小于输出项的实际宽度

145、,则指定的域宽不起作用。常见编程错误如有变量定义: float x=12.34 ; int m=56; 则以下两条语句有很常见的编程错误: printf(x=%d, x); printf(x=%f, m); 正确的语句应当是: printf(x=%f, x); printf(x=%d, m); 实型量不能用格式符%d匹配,整型量不能用格式符%f匹配,这是初学者经常犯的编程错误。有了以上这些必要知识,现在我们终于可以通过编程来做一些比较有意义的事了。例题2.1求住宅的每月物管费用 已知每平方米每个月的物管费用是1.3元人民币,求86.5平方米的一套住宅每月要交的物管费用是多少。程序分析:每月的单

146、位面积物管费用1.3元是一个常量,很少会改变,我们不妨用常量。住宅面积要用一个float型的变量tenement_area来表示,这是因为程序要处理的每套住宅的面积都可能不一样。该住宅每月应交的物管费用也还要用一个变量,显然还是应当用float类型来定义,该变量不妨用标识符management_cost 。 一套住宅每月的物管费用应当是:1.3tenement_area 。经过上述分析,我们可以编出如下程序来:0 #include1int main()2 3 float tenement_area=86.5 ; /*定义实型变量时初始化为86.5*/ 4 float management_co

147、st ; /*定义实型变量物管费用*/5 management_cost=1.3 * tenement_area ; /* 求出每月物管费用*/6 printf(每月物管费用为:%.2f元n , management_cost ) ; 7 return 0;8 运行此程序,得到的结果是: 每月物管费用为:112.45元 程序说明:在C语言程序中,我们要用C字符集中的 “*” 替换“ ” 表示要做乘法运算。这样一来,就可通过第5行的语句management_cost=1.3 * tenement_area ; 求出每月要交的物管费用来,并且存放在变量management_cost中。因为这条语句

148、的含义是:计算出式子1.3 * tenement_area的值来,并把它保存在变量management_cost所对应的内存单元中。但是,此程序还存在一个亟待解决的问题: 这个程序不经过修改,只能计算面积为86.5平方米的住宅每月的物管费用。如果要计算其它面积住宅的每月物管费用,必须在每次运行前修改程序的第3行并重新编译。这对程序使用者来说非常麻烦。如果在程序每次运行时,能够通过键盘将实际的住宅面积输入到变量tenement_area中,那么这个程序每一次运行就可以算出一套实际住宅的每月物管费用。这样就可以不必每次都修改程序。 所以,在程序运行时,将程序变量需要的当前值,通过键盘输入到内存指定

149、单元中去是非常重要的、必不可少的。下面这一节将讨论:如何调用格式化输入库scanf()函数,在程序运行时,通过键盘输入指定变量的值。解决上述至关重要的问题。2.7格式化输入库函数scanf()的用法(一) 在程序运行过程中,我们想要暂时中断一下程序运行,通过键盘输入数据到变量所对应的内存单元中,就要在程序中事先写好输入库函数调用语句。scanf()是格式化输入库函数,这是一种常用到的输入库函数。 格式化输入库函数scanf()的调用,可以使得程序运行暂停下来,等待用户从键盘输入数据。程序用户可以通过键盘输入一个数值(输完数据后,要按下回车键:Enter键或Return键),scanf()函数就

150、会将这个输入值(经过转换)存放到变量所对应的内存单元中。 不过在scanf()函数调用时,第一个参数是相应的格式符%d,%f或%c,它们与要输入的变量类型的简单对应关系如下表: 格式符 输入变量的类型 %d int %f float %c char表2.3格式符与输入变量类型的匹配 用库函数scanf()的调用通过键盘输入一个变量值的最简单形式是: scanf(“格式符”,&变量); 例如:scanf(%f , & tenement_area ) ; /假设tenement_area是float型变量 scanf(%d , & age ) ; /假设age是int型变量 scanf(%c ,

151、& ch ) ; /假设ch是 char型变量演示程序 2.5 #ncludemain()int num ; float x ;char ch;printf(请输入总人数n);scanf(%d , & num ) ;printf(请输入现在气温n);scanf (%f ,&x ) ; printf(请输入等级(A、B、C)n); scanf(%c ,& ch ) ; printf(总人数是:%dn气温是:%.2fn 等级是:%cn , num, x,ch); /输出变量值 程序运行后,人机之间的交互过程如下:请输入总人数 /计算机显示的输入提示18 /程序用户在键盘上的输入 符号 “” 表示按

152、下回车键请输入现在气温 /计算机显示的输入提示23.6 /用户在键盘上的输入请输入等级(A、B、C) /计算机显示的输入提示B /用户在键盘上的输入总人数是:18 /计算机显示的输出结果气温是:23.60 /计算机显示的输出结果等级是:B /计算机显示的输出结果注意:紧靠输入变量左边的符号 “&” 是一个取地址符号。这个取地址符号,在scanf()函数调用时,最容易漏写。常见编程错误:漏写变量值输入时的符号“&”。是scanf()函数调用时最常见的严重编程错误。这个错误很可能使程序崩溃。例如: scanf( %f, tenement_area ) ; 错误! scanf(%f , &tenem

153、ent_area ) ; 正确! 切记:scanf函数调用中的输入项不是变量,而是变量的地址,&是取地址符号。多个变量值的输入:多个变量值的输入scanf()函数的调用方式如下:scanf(“格式符1格式符2格式符3”,&变量1,&变量2, &变量3) ; 对于scanf()函数调用,格式串中格式符的个数要与输入项一样多,类型也要一一匹配。即格式符1要与变量1的类型匹配、格式符2要与变量2的类型匹配,等等。注意:用一次scanf()函数调用,输入多个非字符类型的变量,程序运行时几个输入数据之间要用空格隔开,输完最后一个数据后,按下回车键(但字符变量是例外,几个字符变量的值要连着用键盘输入,字符

154、之间不要用空格隔开,因为空格本身也是一个字符)。字符变量与其他数值变量最好不要用同一个scanf()函数调用输入。 以上这一结论对于printf()也完全适用,只需改动一个字:对于printf()函数调用,格式串中格式符的个数要与输出项一样多,类型也要一一匹配。与printf()类似,在scanf()函数调用时,整型量也不能用格式符%f匹配,实型量也不能用格式符%d匹配。这也是初学者经常犯的编程错误。例题2.1的进一步完善有了上述这些知识,我们现在可以来完善例题2.1了。请读者先不看答案,自己独立做出来,然后与本书所给的以下答案比较一下。经过修改完善的例题2.1如下所示:1 #include

155、2 #define UNIT_PRICE 1.3 /*单价定义为符号常量*/3 int main()4 5 float tenement_area ; /*定义变量:住宅面积*/6 float management_cost ; /*定义变量:每月物管费用*/7 printf(请输入住宅面积,单位是平方米n);8 scanf(%f , & tenement_area );9 management_cost= UNIT_PRICE * tenement_area ; /* 求出每月物管费用*/10 printf(住宅面积% 6.2f平方米 ,物管费% .2f元n , tenement_area

156、, management_cost ) ;11 return 0;12 编译通过后,请重复两次运行此程序,每次输入的面积不同。看看程序是否输出了相应不同的物管费用?然后,再请你将程序中的以下语句:printf(“请输入住宅面积,单位是平方米,输完后请按下回车键n”);注释掉(即将其变为注释),然后重新编译运行此程序。感受一下有什么不同?程序是不是变得对程序的使用者不友好了?通过在编程时调用输入库函数,才使程序能够及时从外部(可以是人使用键盘)得到变量的当前新值。使得程序可以在运行时与用户进行交互,实现“人机对话”。问题1:如果漏写了例题2.1中第8行的scanf()调用语句,结果将如何?问题2

157、:能否将变量 tenement_area 定义为int型? 为什么?28 运算符 我们已经在例题2.1的程序中用过了C语言的乘法运算符 * 。本节将比较系统介绍C语言中的常用运算符。用高级语言编程时,我们不能用具体计算机的机器指令来命令计算机,对数据执行何种运算类指令了。但是怎样表达,我们想对以变量或常量形式出现在源程序中的数据,进行何种运算呢?那就是运算符的作用。用运算符把各种运算量(变量、常量、函数调用等)结合起来,构成表达式(见下面讨论)。用这种形式化、抽象化的方法来告诉编译程序,我们想要用计算机对哪些数据、依照什么样的顺序、执行哪些种类的运算。深入一步:运算符的技术内幕C语言中的运算符

158、共有三十四种(见附录:运算符的优先级和结合性)。运算符规定的运算,最终都将由编译器翻译成的机器指令来具体执行。运算符与运算类的机器指令之间并不是一一对应的。有些运算符规定的运算,用一条机器指令即可实现;有些运算符规定的运算,需要多条机器指令来实现;而有些新型的运算类机器指令并无任何对应的高级语言的运算符(想用这一类指令编程只能用汇编语言。在C语言程序中,可以调用由汇编语言编写的子程序)。最常用的运算符分为三大类:算术运算符、关系运算符(见第4章)、逻辑运算符(见第4章)。各种算术运算符见下表 表2.x各种算术运算符运算符运算举例结果- (一元运算符)负号-a (a的值为3)-3 +加11+51

159、6-减11-56*乘法11*555/整数除法(右边不能是0)11/52 (这是整数除法)/实数除法(右边不能是0)11.0/5.02.2 (这是实数除法)%取模(右边不能是0)11%5 1 (11除以5的余数)一元运算符只需要一个运算量;而二元运算符需要左右两个运算量参与运算才能得到运算结果。演示程序2.6 算术运算符的使用: 1 #include 2 int main() 3 4 int i = 11 , j = 3 ;5 printf( %dn , -j); 6 printf( %dn , i-j); 7 printf( %dn , i*j ); /试一试:将i*j改写为ij 8 prin

160、tf( %dn , i/j ); /这一行的输出结果是3,而不是你想要的3.667或4 9 printf( %dn , i%j) ; /这一行的输出结果是2 10 return 0; 11 上机练习1:请你再将第4行改为 float i =11.0 , j =-3.0 ,然后还要将所有printf()中的%d替换成%f ,看看结果如何?上机练习2:请你再将第4行改为: int i = 11 , j =-3 ; 看看会有什么变化?使用算术运算符,有以下几点需要注意:1初学者最容易漏写乘法运算符(比如:将2*x*y ,误写为2xy )。2不要把实数除法误用为整数除法(比如:错把1.0/3.0 写成

161、1/3 。1/3的值是0 )。一般的,如果m和n都是正整型量,则结果是舍弃了小数部分的整数商。3不要将数值0作为除数,这将导致程序无法运行下去而崩溃。4 运算结果是整型,输出时要用%d与之匹配;运算结果是实型,输出时要用%f与之匹配。千万不能用错。5取模运算符%的运算量要求是整型量(注)。6注意运算结果不要溢出(即不要超过数值的取值范围)。取模运算符“%”的作用非常大,在后面章节你能看到,在处理一些周期性问题或者将一个多位数据分解成一个个的单个位的数字时离不开它。到此为止,我们对C语言的基本语法要素进行了简要的、着重于概念和技术内幕的讲解。从下面开始将看到,这些基本语法要素的各种组合,将可以构

162、成各种各样的表达式。表达式是构成大多数频繁使用的C语言语句(赋值语句、选择结构语句、循环结构语句、return语句等)的重要组成部分。29表达式所谓表达式,是用一个或多个运算符将运算量(包括常量、变量、函数调用、子表达式)连接起来的,可计算出一个明确数值的式子。所有命令型高级程序设计语言中,最常用的表达式分为3类,算术表达式,关系表达式和逻辑表达式。算术表达式将在下一节讲解,后两类表达式将在第4章介绍。在表达式中,还可以使用圆括号来改变运算符固有的先后运算顺序圆括号内的子表达式优先进行运算。一些类型的表达式举例如下: (5.0 / 9.0)* (f - 32) (算术表达式 ) a * a +

163、 b * b 2 * a * b * cos ( alfa ) (算术表达式) ( b sqrt( b * b 4 * a * c ) / (2 * a) (算术表达式 )i 1.2) & (x = a & ch = z )(逻辑表达式,含义是:判断ch是否小写英文字母) 在命令型高级语言中,实际上只能用表达式和语句这种抽象的、形式化的方式来命令计算机进行数据处理工作。除此以外别无他法。单个常量、单个变量和单个(有返回值的)函数调用,都是最简单形式的表达式(比如变量x,可以看成是表达式x+0 )。用printf()函数调用输出表达式的值printf(参数1,参数2,参数3,参数n); 其中 参

164、数1 是用双引号括住的格式控制串 ,参数2,参数3,参数n 是输出项列表。输出项通常情况下是表达式,变量只不过是一种最简单的表达式而已。注意:表达式的运算结果是整型,用printf()输出时要用%d与之匹配;运算结果是实型,用printf()输出时要用%f与之匹配,千万不能用错。表达式可以作为函数调用时的参数比如:sqrt(x+2.5) ,表示以表达式x+2.5(的计算结果)作为参数,进行开平方的库函数调用。需要特别强调的是,出现在表达式中的所有变量,都应当是被初始化了的。否则,通过表达式计算出来的值,就一定是垃圾数据。点评:表达式中出现变量的多重含义要求计算机根据变量名到内存中去取该变量的值

165、(必要时,还要根据变量的类型对变量的值进行类型转换,参见下一章),然后用此值参与运算符所指定的运算。在不清楚表达式中运算符的优先级时,最直观而又最省事的办法不是去查看运算符的优先级表,而是直接在表达式中添加圆括号。适当增加“多余的”圆括号,可使得表达式中的先后运算顺序更容易看清楚。在C语言程序中,在一个表达式中最好使用同一种类型的运算量。但所使用的运算量的类型也可以不同,就涉及到了类型之间的转换问题。关于表达式中的类型转换,请参见下一章。291算术表达式(一)所谓算术表达式,是用一个或多个算术运算符将运算量(包括常量、变量、函数调用)连接起来的可计算出明确值的式子。算术表达式是用得最多的一种表

166、达式,凡是要计算一个数值的工作,大多都离不开它。算术表达式中的运算符的优先级:如果表达式中有两个以上运算符,各运算符按照什么先后顺序进行运算?这要分以下三种情况来讨论:(1)由运算符的优先级来确定。在C语言的全部算术运算符中,取负数- 这个一元运算符优先级最高;乘*、除/、取模%这些运算符的优先级其次;加 +、减 - 运算符的优先级最低: 1 -(取负数) 2. *、/、% 3. +(加法)、-(减法)也就是说,C语言中的算术表达式中的运算顺序,除了增加了一个与乘除运算优先级同样的取模运算符 % 之外,还是完全遵守我们在中小学在算术式和代数式中就用过的“先乘除、后加减”的运算顺序。比如:A 3

167、.7+4.1*12 (相当于 3.7+(4.1*12),即先做乘法运算得到49.2,然后才做加法运算。结果为52.9 。B 3.7+4.1*-12 (相当于 3.7+(4.1*(-12))), 即先做取负数运算-12,再做乘法运算得到-49.2,最后再做加法运算,得到-45.5。C 6-24%5 相当于 6-(24%5), 即先做取模运算,得到24除以5的余数为4,然后做减法6-4。结果为2 。(2)用圆括号改变运算符固有的运算顺序:在C语言中,可以用圆括号括住表达式中的一个子表达式,用来强制改变运算符固有的运算次序,比如:(3.7+4.1)*12 那就是要先做加法运算(3.7+4.1),然后

168、再做乘法运算。但是,在C语言中,千万不能象用中小学学过的方括号“”、“”和花括号“”“”来表示要优先进行运算的(子表达式)部分。因此,以下式子在C语言程序中是完全错误的:2.3*b+y-(3.7+b) %6。应当用多重圆括号()来取代 和。正确的表达式应当是: 2.3-(b+(y-(3.7+b))%6 5 3 2 1 4 / 运算的先后顺序。也就是说,在表达式中可以使用多重嵌套的圆括号,来强制表示运算的优先次序,运算顺序由内到外。在以上表达式中,最先做的是 3.7+b这个子表达式的运算。(3)在一个算术表达式中,如果出现了多个同一优先级的二元算术运算符,则是按从左到右的顺序进行运算的。 比如:

169、3*11%2/3 因为该式中从左到右依次出现的运算符是*、%、/都是同一优先级的 ,所以它的运算次序是 :3*11%3/7 1 2 3 /运算符的运算次序从左到右依次进行相当于表达式(3*11)%3)/7的运算顺序。而不是相当于: 3*(11%(3/7))或者(3*(11%3)/7)),等等。类似的:表达式 i-j+k-m 相当于 (i-j)+k)-n点评:运算符的“结合性”这里出现了一个似乎令人害怕的新名词:“结合性”。但其意义其实并不太难。运算符(与运算量)的结合性是对出现在表达式中的,具有同一优先级的多个运算符而言的。用来规定这些运算符之间与运算量的先后结合次序。先与运算符结合的运算量(

170、构成了一个子表达式)先进行运算,按结合先后次序进行运算。 小结:C语言中算术表达式中算术运算符的优先级,与我们在中小学学过的代数式中算术运算的优先级规则完全吻合。只有两点小的区别:第一,多了一个与乘除优先级相同的运算符 % ;第二,强制改变运算(符)的优先级时,只能用多重嵌套的小(圆)括号,不能用中括号和大括号。所以,记住这些规则对你来说还是很容易,对不对? 如何将一个中小学学过的代数式转变为C语言中的算术表达式,请参见下一章算术表达式(二)。28赋值语句 通常的(即没有副作用的)表达式不会改变变量的值。那么,如何通过运算来改变变量的原来取值呢?如何通过已知的变量求出未知变量的值呢?这就要用到

171、赋值运算符 “=” (以后将其简称为赋值号)构成的赋值语句。赋值语句的形式是: = ; 赋值语句的流程是:1 先计算出赋值号“=”右边表达式的值;2 将此值存放在赋值号左边的变量中。 比如 sales=3693.89;其作用就是把3693.89存入变量到sales 中,参见图2.6。图 2.6 将数据存入变量(对应的内存单元)中注意:赋值号“=”的左边只能是单个变量,不能是常量(3693.89=sales ;是错误的),也不能是函数调用(sin(x) = a/2.0; 也是错误的)。点评:赋值号左右两边的变量:“右不变,左改变”(先右取,后左存);赋值号“=”右边表达式中的变量(如果有的话)仅

172、仅是取出它(们)的值来参与表达式规定的运算,变量的值通常不会改变。而赋值号左边变量的原来值,将会被表达式算出来的新值的存入而“覆盖”掉。赋值语句特点记忆:先右取,后左存;右不变,左改变。问题1.已知华氏温度的数据在变量f中,如何求出相应的摄氏温度并把它存放在变量c中?已知转换公式是: C=(5.0/9.0)(F-32.0) ,C表示摄氏温度,F表示华氏温度。问题2.已知变量x的当前值是36,变量y的当前值是72。请问执行赋值语句 x=y ;之后,x和y的值分别是多少?请问执行赋值语句 y=x ;之后,x和y的值分别又是多少?点评:一类极为重要的、常用的赋值语句形式如 x = 包含有变量x的表达

173、式 ;(x可以是任何基本类型的变量)的赋值语句,大量地、频繁地出现在程序中。它表示的是一种迭代关系:即指明了如何由变量x的一个老值(取出来参与表达式所规定的运算),最终得到了变量x的一个新值(最终存入到赋值号右边的变量x中)。 这一点与数学中学过的方程式有很大的区别。比如:i=i+1; 和 sum=sum+k ; (k可以是任意值),在数学中是无解的方程式,但在命令型高级语言中,我们却经常需要使用这种类型的赋值语句。问题3、已知某人的工资额保存在变量salary中,如何将其增加百分之30,并且仍然保存在变量salary中?答:可采用赋值语句 salary=salary*1.3 ; (注意:不能

174、用 salary=salary*130% ;)。 问题4、已知某一角度的弧度值在变量alfa中,你能否转化成以度数形式表示的角度并依然存放在变量alfa中?答: alfa = alfa*3.1416/180;数学中的方程式是不能直接转变为赋值语句的,只有公式(等式左边是单个的未知量,等式右边是仅仅包含已知量的代数式)才可直接转变为赋值语句。解方程必须由编程者亲自做。参见例题3.2鸡兔同笼问题 。对变量进行初始化的三种基本方式1. 在变量定义时,立即给它一个初值;比如:int sum=0;2. 采用输入库函数,将数据由外部(键盘、网络或外存中的文件)存入变量所代表的内存单元中。比如: scanf

175、(“%d”,&sum);3. 用赋值语句,将表达式计算出来的值,赋给变量;比如:sum=sum+i;任何变量在定义后,都必须采用上述三种方式之一,对其进行初始化。没有进行初始化的变量,是决对不能出现在表达式中的,也不能用来输出。换言之,未经初始化的变量,只能出现在赋值号的左边。例题2.2:安全密码生成器人们平常喜欢用纯数字序列来作为密码,比如用生日、特别的日子、电话号码等,这种密码很不安全,容易破解。请编一个程序将6位纯数字的原来码转变为由小写英文字符组成的比较安全的密码。查找ASCII表可知:数字字符的ASCII 码 0是 48 9是 57 英文字符的ASCII 码 a是 97 z 是 12

176、2 分析:数字字符0到9的ASCII码值在4856的范围,26个小写英文字符中随意任取连着的9个字符就可与之匹配。例如我们取c到k。这9个字符的ASCII码的值是99107之间。如果把0到9顺序与c到k一一对应,那么只要把每个输入的值加上51即可。编写程序如下: #includeint main() char ch1,ch2,ch3,ch4,ch5,ch6; printf(请输入6位数的数字密码n); scanf(%c%c%c%c%c%c, &ch1,&ch2,&ch3,&ch4,&ch5,&ch6);ch1=ch1+51;ch2=ch2+51;ch3=ch3+51;ch4=ch4+51;ch

177、5=ch5+51;ch6=ch6+51;printf(建议使用密码:%c%c%c%c%c%cn , ch1,ch2,ch3,ch4,ch5,ch6);return 0;程序运行后,人机对话过程如下:请输入6位数的纯数字 123456 建议使用密码:defghi为了提高密码安全性,你还可以将6个字符变量的输出次序打乱,比如将倒数第2条语句改为:printf(“建议使用密码: %c%c%c%c%c%cn” , ch1,ch3,ch5,ch6,ch4,ch2);有了这个程序,你就可以用你的生日或熟悉的电话号码等纯数字来生成比较安全的密码了。这个程序有很大的改进余地。请读者自己进一步完善它,轻松生成比

178、较好记(你只要记住你熟悉的纯数字)又比较不易破解的密码。例题2.3 已知三角形的两边及其夹角,求三角形的面积类型 必修题趣味性 *难度 * 一级算法1 输入三角形的两边x,y及其夹角alfa 2 根据已知量求三角形面积,赋给变量area /以后用符号 area 表示此事3 输出三角形的对边长度及面积其中第2步需要进一步求精:第2步的进一步求精:根据求三角形求面积的公式,可转化为以下赋值语句:area=x*y*sin(alfa*180/3.1416)*/2.0转化成C语言的程序:1 #include 2 #include 3 #define PI 3.1416 4 int main() 5 6

179、float x, y, alfa ; 7 float length ,area; 89 /* 输入三角形的两边x,y及夹角alfa*/10 printf( “请输入三角形的两边及其夹角,输入数据之间用空格隔开n”); 11 scanf(“%f%f%f” , &x , &y , &alfa ); 1213 /* 求三角形面积*/ 14 area=x*y*sin(alfa*PI/180)/2; 1516 /* 输出三角形的对边长度及面积*/17 printf(“三角形的对边长为%f,面积为%f n”,length,area ); 1819 return 0;20 问题1.使用第3行符号常量有何好处

180、?问题2.第2行是否可以省略?为什么?问题3.漏写第7行会如何?问题4.第7行是否可以下移到第12行?为什么?问题5.是否可将第10行和第11行互相颠倒位置?问题6. sin(alfa*PI/180)能否写成Sin(alfa*PI/180)?或sinalfa*PI/180?问题7.能否将第11行写为: scanf(“%d%d%d”, x , y , alfa ); 找出此句中的两类错误。习题一是非判断题1C语言中,函数体必须用一对大括号括住,大括号不能省略。2C语言中,函数首部不能以分号结束。3C语言程序中语句结束时的分号常常可以省略不写。5两个整型量(包括常量和变量)m和n相除 m/n ,所

181、得的结果是截去了小数部分的整数商。6. 调用数学库函数时,可以不包含头文件 math.h 。7就象java语言一样,在C语言中定义可以出现在函数体中的任意位置。9表达式中出现的任何变量,都应当是已经了初始化了的变量。10两个运算量之间的乘号 “*” 有时可以省略不写,有时可以用 “.”号代替“*”号。11源程序中的注释在翻译成目标程序时会被编译程序忽略掉,所以写注释的意义不大,纯属浪费时间。12在赋值语句中,赋值号的左边一定是一个单个变量(有些教科书将其称为变量的左值),不能是普通的带有运算符的表达式。13C语言字符集中的同一个字符,出现在源程序的不同位置,其含义可能不同。14C语言程序中,凡

182、是可以出现常量的地方,都可以用一个表达式来替代。15赋值语句使得我们,既可以通过已知变量求得未知变量的值,又可以通过变量的老值求得该变量的新值。16语句 x=x+1; 是错误的,因为符号“=”的两边不相等。17没有副作用的表达式,永远不会改变变量的值。18内存中变量的值可以重复取出任意多次使用,变量的值都不会变。一旦存入一个新值到变量中,变量的老值将不复存在。19.库函数名通常都是小写英文组成的标示符。20单个常量、变量、有返回值的函数调用都是表达式。*21.子程序的最后一条指令一定是一条无条件跳转指令。跳转到主调程序中尚未执行的地址号最小的那一条指令。二 选择题1. C语言程序是由 构成的。

183、A)一些可执行语句 B)main函数 C)函数 D)包含文件中的第一个函数2、以下说法中正确的是( )。 A、C语言程序总是从第一个定义的函数开始执行 B、在C语言程序中,要调用的函数必须在main( )函数中定义 C、C语言程序总是从main( )函数开始执行 D、C语言程序中的main( )函数必须放在程序的开始部分3.下列关于C语言的说法错误的是( ) 。A) C程序的工作过程是编辑、编译、连接、运行 B) C语言不区分标识符的大小写C) C程序中的注释不能嵌套 D) C程序的函数体中定义序列在前语句序列在后4. 不是C语言提供的合法关键字是()。A.switchB.printfC.ca

184、seD.default5. 以下选项中合法的用户标识符是 A) longB) _2TestC) 3DmaxD) A.dat6.下列各项字符序列中,合法的变量名是 。 A)2e3 B)you C)*y D)float7、下列可以正确表示字符型常量的是( )。 A、t B、”a” C、”n” D、2978. 已定义c为字符型变量,则下列赋值语句中正确的是 ( ) A)c=97 B)c=97 C)c=a D)c=a 9语言中字符型(char)数据在内存中的存储的通常是( ) A) 原码 B) 补码 C) 反码 D) ASCII码10C语言中运算对象必须是整型的运算符是( )A) % B) / C)

185、= D) *11.在C语言程序中,表达式8/5的结果是( ) A)1.6 B)1 C)3 D)012.在C语言程序中,表达式5%2的结果是 。 A)2.5 B)2 C)1 D)3三、输入输出库函数的调用1设float型变量radius表示圆球的半径,float型变量volumn表示圆球的体积。请指出以下printf函数调用语句中的错误。1) printf (“圆球的体积是:%f立方米 , 圆球的半径是:%f立方米n”, radius );2) printf (“圆球的体积是:%d立方米n”, volumn );3) printf (“圆球的体积是:%.2f 立方米n , volumn” );

186、4) print (“圆球的体积是:%f 立方米n” , & volumn ); 2填空题1)如果要用一次printf()函数调用,依次输出n个变量(或表达式)的值,那么在函数调用的格式串中应当有 个格式符,并且格式符的中的转换说明要与输出项的一一匹配。2)如果三个待输出的变量的类型依次分别是 int、char、float,那么格式串的形式通常应当是:“* %* %* ” 。其中的星号代表任意多个普通字符或转义字符。 3已知 age 是int型变量,ch是char型变量,heigh是float 型变量,请用适当的转换说明符 d、f、c填空:1)scanf(”%_” , &age); 2)sca

187、nf(”%_” , &ch); 3)scanf(”%_” , &heigh); 4. 四、将下列代数式转换成相应的c语言表达式16y/5x-3a223|yx+lg(x2+1)+3ex+ln(y)|+sin(25)*五、能力挑战题 改错:先阅读,然后通过上机调试,请找出以下程序中的所有错误来。 /* 作者: 何勤 编写日期: 2009-12-25 功能: 输入圆球的半径,求圆球的体积 例题 2.2 # include (stido.h) #defined PI 3.1416 ; /*符号常量*/ Int Mian ; print(“请输入圆球的半径n”) Float , r ; v ; scan

188、f(”%fn”, r) V=(4/3) . PI . r3 ; Print(“圆球的体积是:%d立方米n , ” &v ); 六、阅读、编译并运行例题2.1七编程题1从键盘输入圆锥体的半径radius和高度height,计算其体积volumn.(其中圆周率要求用符号常量表示)。2编程:输入年利息、存钱的年数和金额,计算到期本金和利息之和(不计复利)。3 编程:已知一元二次方程ax2+bx+c=0 的系数a,b,c(a,b,c由键盘数入),并且假设b2-4ac0,求方程的两个实数根。4编写一个程序,让用户输入一个字符,程序显示此字符所对应的的ASCII码。要求显示格式如下: 输入的字符是: 对应

189、的ASCII码是:5. 编写一个程序,要求用户输入一个ASCII码,程序显示此ASCII码所对应的字符。要求显示格式如下: 输入的ASCII码是: 对应的字符字符是:6参照例题2.2 ,更安全的密码。请将6位纯数字好记的明码转变为比较安全的混合密码。要求6位密码由两位大写英文字符、两位小写英文字符和两位数字字符构成。7编程:学生个人生活小管家 。输入本月总收入,输入预算支出项,显示输出本月预算情况。支出分为: 就餐费、手机费、日常生活用品费、交友娱乐费。本章参考文献1美 K.N.King著 吕秀锋译C语言程序设计:现代方法人民邮电出版社2美 Brian W.Kernighan 、 Dennis

190、 M.Ritchie 著 徐宝文等译 C 程序设计语言机械工业出版社,这是一本经典权威著作,Dennis M.Ritchie 是C语言的发明者。3美J.Gleen Brooksheer著 刘艺等译计算机科学概论人民邮电出版社4. 美 Alfred V Aho等著 李建中等译编译原理机械工业出版社第三章C语言基本概念(应用提高篇)本章除了3.1、3.2节和一些例题之外,其他内容初学者都可在学完第4章或第5章之后再来学习。把这些内容安排在此处,只是为了C语言基础语法知识讲解的系统和连贯。有利于学过C语言的读者,也有利于将来在编程时通过本书查找相关知识点。 3.1讲解了用计算机求解问题的步骤;3.2

191、用伪代码构造算法;3.3常用的变量类型long int 和double。3.4介绍了printf()与scanf()调用时的重要区别。3.5对于不同数据类型之间转换作了一个简介。3.6自增运算符+和自减运算符-。3.7讲解了赋值表达式、复合赋值运算符等 3.8单个字符的输入输出库函数 3.9提高部分介绍了:其它基本类型变量、常量的进一步讨论、逗号表达式、表达式的副作用、printf()与scanf()的进一步论述。数据类型之间转换的深入一步讨论。在例题3.3中,介绍了一种在纸面上运行算法的算法走查方法,这是本章的一个重点。3.1用计算机求解问题的步骤、3.2逐步求精的伪代码表示算法、3.5 这

192、三节也是本章的重点。本章的难点是:各种数据类型之间的转换、printf()和scanf()的比较深入的讨论、表达式的副作用。31 用计算机求解问题的步骤:到目前为止,我们学了很多有关C语言的基础语法知识。但是在进行编程工作时,其实只有变量、表达式、语句(赋值语句、选择结构语句见下一章、循环结构语句见第五章)、输入数据到变量和输出变量(或者表达式)的结果,这些要素才是我们编程时需要重点考虑的。变量成为我们解决实际编程问题时的核心和主线。遇到编程题时,要考虑的关键点是:需要定义哪些类型的几个变量;需要输入哪些变量的值;如何根据已知的变量构造出合适的表达式,从而用赋值语句来求出未知变量的值;最后将求

193、出的变量(或表达式)的值输出。 用计算机求解问题,解决问题的一般过程是:1. 用普通语言简要并尽可能精确地叙述问题;2. 问题中已知的量有几个,其中有几个量随求解的具体应用场合会发生变化?(这些量应当定义为变量)有哪几个不会发生变化(这些量可用数值常量或符号常量来表示)?其中会变化的已知量一般应当在程序中用到此数据前,用输入函数调用(有的高级语言用输入语句)进行数据输入。3. 问题中要求出的有几个量?4. 从已知的量如何得到要求出的量?有何公式可以利用?有何方程式可以利用?如果是公式,就可以直接将其转换为赋值语句,只需把公式右边的数学表达式转换为高级语言的算术表达式即可。如果是方程式,则需你自

194、己亲自将方程式求解得到最后的公式,然后将其转换为赋值语句,只有到了这一步,才可以将工作交给计算机做。5. 如果从已知的到要求的最终结果需要一些中间变量,则需要在程序中说明这些中间变量,并且得到怎么从已知的量到中间变量的值的公式,最终由已知量,中间变量得到所要求的结果的公式,将所有这些公式转换为赋值语句。6. 将结果输出。其中最为困难的往往是第4步和第5步。这两步通常又被称为寻找求解问题的算法。32逐步求精的伪代码表示问题求解的算法有多种方法,其中最为常用的是伪代码和流程图。但由于流程图只适合解决规模较小的比较简单的问题,虽然初学者易于理解和掌握,表达算法也比较清晰,但在程序员和真正会编程者中并

195、不流行因为它不利于对算法的构思、修改和调整(仅适用于表达比较简单的算法)。所以本书不作介绍(养成了用流程图来表示算法的习惯后,人们很难再转变成用其它更好的方式去构造算法,所以还不如一步到位)。本书使用的是用逐步求精的伪代码来表示算法。所谓伪代码没有严格的规范(所以也比较灵活),但其中有一些结构要素,可以用一些比较规范的方式来表述对问题的计算和处理流程。一级算法只是解决问题的一个轮廓。有些复杂问题,只根据一级算法还难以直接写出C语言的源程序。这时可对一级算法进一步求精(称为二级求精),将它其中的某些步骤,扩展成更详细的步骤,直到可以很容易写出C语言程序的语句时为止。对于某些人是很显然的算法描述,

196、对于其他人可能并不显而易见。因此,算法求精的过程和步骤是因人而异的。你的编程经验越丰富,算法求精的步骤就可能越少。不过,算法求精的步骤太少也不一定是好事。因为程序员的一个良好习惯,就是把伪代码表示的一级算法(最多到二级求精),转变为源程序中的注释。注释太过简洁,会加大自己和别人阅读程序的困难。逐步求精的伪代码,其实质精神,就是模仿人们在做一个大的任务时,习惯于首先将其粗分为几个一级任务,然后再来分别考虑,每个一级任务如何完成?不太明确的一级任务还要再细分为若干个二级任务,等等。每一个任务都确定能够由现有的部下完成时,任务的负责者才能放心地据此发出正式命令,要求部下共同协作完成此项任务。学习用伪

197、代码来表达自己的编程思路和算法,笔者的体会是没有什么捷径让你能够迅速掌握它,开始只能是去模仿,去领悟,时间长了,看的和模仿的算法多了,慢慢就会用了。类似于学游泳和学骑自行车。在大量模仿和实践的过程中,你就会不知不觉地掌握它。例题3.1 求三个数的和及平均值类型 必修题趣味性 *难度*变量安排: 3个已知的数要用3个变量a1,a2,a3;三个数的和用1个变量sum,平均值用1个变量average,所有变量都用浮点类型定义。一级算法: 1. 输入这三个数a1,a2,a3; 2. 求这三个数的的和sum及平均值average; 3. 输出sum,average; 其中第二步由于不能直接转化成语句而需

198、要进一步求精。 根据求和及求平均值的以下两个代数式:sum=a1+a2+a3 average=sum3 只需将其转化为赋值语句即可。 第2步的二级求精: 2.1 sum=a1+a2+a3; /*求这三个数的和2.2 average=sum/3; /*求这三个数的平均值转化成C语言程序:/*求三个数的和及平均值*/1 #include 2 int main()3 4 float a1,a2,a3 ; /* 三个已知量,定义为浮点型变量*/5 float sum,average; / *两个未知量,定义为浮点型变量*/ 6 /*输入这三个数a1,a2,a3*/ 7 printf(“请输入3个数,3

199、个数之间用空格隔开n”);8 scanf(“%f%f%f”,&a1,&a2,&a3); 9 10 /*求这三个数的的和sum及平均值average*/ 11 sum=a1+a2+a3; 12 average=sum/3; 13 14 /*输出sum,average*/ 15 printf(“sum=%f,ave=%f”,sum,average); 16 return 0; 17 注意: 运行此程序时,要用键盘输入三个数值给变量a1,a2,a3,三个数之间要用空格隔开。输完后,按下回车键,程序才会继续运行下去(其中的道理,参见本章提高部分以及12章的提高部分)。点评 : 将表示算法的伪代码,转化

200、为程序中的注释注意:伪代码形式的一级算法,在程序中转变为注释,使得程序的可读性比较好。这是一种良好的编程习惯。问题: 如果将第8行改为 scanf(“%f,%f,%f” , &a1 ,&a2 ,&a3); 数据应当如何输入?答:用键盘输入数据时,数据之间现在不能用空格隔开,而要用逗号隔开了。第7行的语句也要做相应调整。练习:请将程序第7行删掉,再编译运行,感受一下程序对用户是否不友好了?例题3.2 鸡兔同笼问题类型 必修题趣味性 *难度 * 题目,已知鸡和兔的总头数和总脚数,求鸡有多少只,兔有多少只? 一级算法1输入总头数heads,总脚数feet2求鸡的只数chicken和兔的只数rabbi

201、t3输出变量chicken和rabbit的值其中只有第二步需要求精 根据二元一次方程组可知四个变量heads,feet,chicken,rabbit之间的关系为 headschickenrabbit feet=2chicken+4rabbit 解此方程组可得到如下两个公式: chicken=(4heads-feet)/2 rabbit=(feet-2heads)/2 将此公式转化为赋值语句,考虑到输入的总头数和总脚数不一定能得到整数解,因此将变量chicken(表示鸡的只数), rabbit(表示兔的只数),定义为实型变量。 二级求精 2.1 chicken=(4*heads-feet)/2.

202、0 2.2 rabbit=(feet-2*heads)/2.0 转化为C语言程序1 #include2 int main()3 4 int heads, feet; 5 double chicken, rabbit; 6 /*输入总头数heads,总脚数feet*/ 78 printf(“请输入总头数和总脚数,两数间用空格隔开n”); 9 scanf(“%d%d”,&heads,&feet); 1011 /*求鸡的只数chicken和兔的只数rabbit*/ 12 13 chicken=(4.0*heads-feet)/2.0; 14 rabbit=(feet-2.0*heads)/2.0;

203、15 /*输出变量chicken和rabbit的值 1617 printf(“鸡的只数%f只,兔的只数%f只”,chicken,rabbit);18 return 0;19 问题1:如果不解方程组,而直接将其转变为赋值语句,也就是用 headschickenrabbit; feet=2*chicken+4*rabbit; 取代第5和第6句,结果会如何? 答:赋值语句右边的表达式中出现的变量,必须是已经是经过初始化的变量。赋值语句是用赋值号右边的表达式求出的值,存放到赋值号左边的变量表示的存储单元中。所以,这两句是不正确的,但大多编译程序不会报错。程序运行时,会取用内存单元中变量chicken,

204、和rabbit的不确定的值(垃圾数据)参于运算。经过运算得到两个错误数值,去改变变量heads和feet的原来值。换言之,方程式不能直接转变为赋值语句,只有公式才可以直接转变为赋值语句(公式是等号左边为一个要求值的变量,等号右边是一个数学代数式,数学代数式中出现的变量的值都是已知的)。 点评:所谓计算机的智能此题程序一旦编好,由另一个用户来使用计算机运行这个程序,他会觉得计算机很聪明,竟然会求解鸡兔同笼的方程式,得到正确答案。“深蓝”计算机为什么能够战胜人类的国际象棋世界冠军卡斯帕罗夫,其道理是类似的。人类的智能转变成了程序存放在了计算机中,与计算机的强大计算能力、“记忆能力”结合起来,使得运

205、行着程序的计算机让外行看起来非常聪明能干。例题3.3 将一个三位整数反向后输出类型 必修题趣味性 *难度* 一级算法1输入一个三位整数n2通过利用学过的算术运算符整除 / 和取余数 % 进行分解,分别求出此三位整数n的百位数n3,十位数n2和个位数n13反向后的三位整数为 num=n1*100+n2*10+n34输出此三位数其中第2步由于不能立即写出C语言的语句,需要进一步求精:2.1 求出n的百位数:n3=n/1002.2 求出n的十位数n2=(nn3*100)/102.3求出n的个位数 n1=n%10算法验证:(用我们的大脑当作计算机,一张白纸当作内存)执行算法第一步,假设将一个任意指定的

206、三位整数,比如:将315输入到变量n中,用于验证上述算法过程的正确性。变量的内存单元取值将如图一所示:nn3n2n1num315不确定不确定不确定不确定 图3.1执行算法2.1步的赋值语句:n3=n/100; 先计算表达式n/100(即315/100),可得到3,将其存入变量n3中。算法2.1步执行完后,变量的内存单元取值将如图二所示(变量n3的值发生了变化):nn3n2n1num3153不确定不确定不确定 图3.2执行2.2步的赋值语句:n2=(nn3*100)/10;先计算表达式(nn3*100)/10,将n和n3的当前值从内存中取出代入到表达式,进行表达规定的运算:(3153*100)/

207、10,得到结果1 ,存入到变量n2中。算法2.2步执行完后,变量的内存单元取值将如图三所示(变量n2的值发生了变化):nn3n2n1num31531不确定不确定 图3.3执行2.3步的赋值语句:n1=n%10;后,变量的内存单元取值将如图四所示(变量n1的值发生了变化):nn3n2n1num315315不确定 图3.4执行算法第3步规定的赋值语句的运算: num=n1*100+n2*10+n3 ;之后,将得到结果513,变量的内存单元取值将如图五所示(变量num的值发生了变化):nn3n2n1num315315513 图3.5经过以上验证,算法逻辑上的确是正确的。点评 读者可仿照此处介绍的方法

208、来读懂、验证一些难懂的算法或源程序。但完全不必像本书中介绍的一样,画出这么多的变量内存单元取值变化图。只需在纸上用铅笔画出一个变量内存单元取值变化图,然后用一些假设的、比较简单的输入数据或参数,跟踪算法的实际执行流程和数值的变化,一步一步地用铅笔修改图中的变量取值。在整个执行流程中,仔细按照算法的执行步骤,修改内存中各变量的取值。 读懂、验证算法的重要手段画变量内存单元取值变化图。学会在纸面上画变量内存单元取值变化图,然后用大脑作为CPU,用一些假设的、比较简单的、比较小的(和比较少)的输入值(如果有循环,则循环次数要设置的比较少,参见第5章),去执行用伪代码写出来的算法。用这样的方法,一般能

209、够很容易地看懂一些别人写出的、比较难懂的算法或程序;也可以用此方法来验证自己编写的算法或程序是否存在逻辑上的错误(比如表达式的输入错误、语句顺序错误)或者边界错误等等。 点评:通过画变量内存单元取值变化图,验证算法的重要意义笔者认为,上述这种画变量内存单元取值变化图的技能,是想成为程序员或编程高手的一种极为重要的、基本的技能。使用这种技能有两方面的好处。第一,在写出算法而尚未编出程序时,就可以检查算法是否存在逻辑错误或边界错误(数组边界错误,或者循环次数多一次或少一次)、程序的流程是否构造正确等,大大减少了不必要的编程工作量。这类似于在装修前画出立体效果图,让用户感受一下在其中生活是否会感到舒

210、服一样(如果用户感觉不好,可重新修改设计,大大减少了实际装修后才返工的极大损失)。第二,可使读者读懂较难程序或算法的能力得到极大的提高。希望读者把这一重要的技巧至少用到分析和阅读几十个你认为比较难懂而又比较有趣的算法或程序上,使之成为你的编程工具箱中的一件“利器”。并且养成一种良好的编程习惯,在用伪代码写出算法后,不要急于把它转换成高级语言的程序,而是用这种在纸面上运行算法的方法去验证算法,尽可能在早期找出并修改逻辑错误或边界错误。这也可使得写出伪代码算法的效益最大化。当然,即使通过这种验证,也肯定不能保证这个算法是完全正确和完善的。对于语法错误更无能为力。但这种验证的重要价值,也绝对不能否认

211、。如果不用此方法来验证,写出算法后就立即编程。仅仅依赖于编译程序来进行调试,你很可能最终会得到一个没有任何语法错误和运行时错误的程序。然而,此程序得到的结果要么可能是完全不正确的(逻辑错),要么此程序存在严重的边界错误。如果是逻辑错误,则整个编程和调试工作完全是白费力气。这种白费力气,在编写大型程序时,浪费的时间是很多的。将算法转化为C语言程序:1 #include2 int main()3 4 int n;/*输入变量*/ 5 int n3,n2,n1;/*中间变量*/ 6 int num; /* 结果变量*/7 /*输入一个三位整数n*/ 8 printf(“请输入一个三位整数n”); 9

212、 scanf(“%d”, &n); 1011 /*分别求出它的百位数n3,十位数n2,个位数n1*/ 12 n3=n/100; 13 n2=(nn3*100)/10; 14 n1=n%10; 1516 /*反向后的三位整数为*/ 17 num=n1*100+n2*10+n3; 1819 /*输出此三位数*/20 printf(“num=%d”,num); 21 return 0; 22 问题1 :是否可以将第17行和第20句合并为下面这一句并删掉第6行? printf(“num=%d”, n1*100+n2*10+n3); 答: 可以,printf()函数调用时,格式串后的输出项既可以是常量或

213、变量, 也可以是一个表达式。直接输出值时可以不定义变量,将表达式的结果直接输出。问题2: 是否可以用赋值语句n=315,取代输入变量n的第9句scanf(“%d”, &n);? 答:可以,赋值语句n=315,和输入函数调用语句scanf(“%d”, &n); 都可以使变量n能够得到初始化(即得到有用的初值)。但使用前者,程序失去了灵活性,它只能将一个固定的三位整数315反向输出。如果想要将别的三位数反向输出,则需要你重新修改程序。 输入库函数调用语句scanf(“%d”, &n),则可以使得程序用户输入任何的三位整数。只需将程序重新运行一遍即可,不需要修改程序。问题3:将第12行和第13行颠倒

214、语句次序是否可以?你可用画变量内存单元取值变化图的方法得到你的结论。 答:不可以,变量n3会出现初始化错误。因为颠倒次序以后, 赋值语句n2=(nn3*100)/10;中,的表达式: (nn3*100)/10 中的变量n和n3,是要到相应变量的内存空间中去取出数值来,参与表达式所给定的运算的。变量n的值没有问题,但变量n3因为没有得到初始化,其内存中是垃圾数据。也就是说,13行的赋值语句:n2=(nn3*100)/10; 的执行,依赖于12行的赋值语句:n3=n/100; 的执行, 变量n3的值才得以初始化。 问题4:将13行与14行互换,是否可以? 答:可以,因为两条赋值语句之间的变量并没有

215、依赖关系。问题5:是否可以将所有变量n,n3,n2,n1都用实型float来定义?答:不可以;程序12行、13行的赋值语句的表达式,都要求是整除运算。14行的n%10 ,运算符 % 也要求变量n是整型,而不是实型。问题6:是否可以将4,5,6 行,用一行来表示?答:可以,但是不好。最好是将已知变量(要初始化的)、结果变量(求出的结果)和中间(临时)变量分开写,这样有利于检查程序:已知变量是否初始化了,结果变量是否输出了。3.3变量类型的进一步讨论341第2章已经介绍过了int 、float、和char 变量类型。下面介绍两个新的基本类型:long int 类型如果int 型变量取值范围不够大,

216、而你又不想用取值范围更大但又有些缺陷的float型变量,则你通常可以使用long int 型。长整型long int 通常取值范围是在 -21474836482147483647之间。long int 型变量在调用格式化输入输出库函数时,要用%Ld 或%ld 与之匹配。演示程序3.1#includemain() long int sum; printf(“请输入一个在负21亿到正21亿之间的整数n”); scanf(“%Ld” , &sum ); /注意格式符 %Ld,也可以是%ld printf(“你输入的数值是:%Ld” , sum); /注意格式符 %Ld,也可以是%ld 深入一步:如果

217、long int的取值范围还是不够用,你通常有两种选择:1、用支持新的C99标准的C编译器。使用其中的long long int 类型,这是64位长整数类型。取值范围 在 -263到263-1 之间。2、自己亲自动手,用第6章学到的数组来做。double类型如果float 型变量取值范围不够大(或者不足够小)、或者精度达不到你的要求(要求精度超过7位),你通常可以选用double型变量来进行定义。虽然C语言标准没有规定double型的范围和精度,但IEEE754标准(大多数计算机都遵循这个规范)规定:double型变量的最大取值范围是-1.79103081.7910308之间,精度达10进制的

218、16个数字。double型变量可以表示的最小正负数是+ 2.2210-308 。 注意:double 型变量在调用scanf()库函数时,要用%Lf (或%lf)与之匹配。不要用%f。double 型变量在调用 printf()库函数时,要用%f与之匹配。不要用%lf 。演示程序3.2#includemain() double large_sum; printf(“请输入一个大的实数n”); scanf(“%Lf” , &sum ); /注意此句不可以是:scanf(“%f” , &sum ) printf(“large_sum=%f” , sum); /注意此句不可以是:printf(“la

219、rge_sum =%Lf” , sum);记住:输入double型要用 %Lf(或%lf) 输出double型要用 %f ( 原因请参见参考文献:美 K.N.King 著 吕秀锋译C语言程序设计:现代方法第一版 P94页)char类型的进一步说明(未完)表2.x 常用的字符转义序列名称 转义序列名称 转义序列 响玲符 a反斜杠 回退符 b问号 ?换页符 f单引号 换行符 n 双引号 ”回车符 r(不换行)ddd 13位八进制数所代表的字符横向制表符 t xhh 12位十六进制数所代表的字符纵向制表符 v例题3.4用九行九列的“*”字符和空格字符的适当组合,来构成并输出一个汉字:”王”。思路:用

220、九个printf()语句,每个printf()语句输出这一行中的“*”字符和空格字符。C语言源程序如下:1 #include2 int main()3 4 printf(n);5 printf(*n);6 printf( * n);7 printf( * n);8 printf(*n);9 printf( * n);10 printf( * n);11 printf(*n);12 printf(n);13 return 0;说明:此程序可在显示屏的最左边显示一个由“*”号(包括和空格和空行)组成的汉字:“王”。但这个字太靠近屏幕左边,不太好看。请你修改此程序,让这个“王”字比较居中。深入一步:

221、计算机显示屏显示各种文字(包括图像)的内在奥秘计算机在屏幕上能够显示任何的文字(包括图像),其道理和习题2.3是完全类似的。只不过在习题3中,用来组成汉字“王”的基本成份,是由 “*”字符(用作为黑点)和空格字符(用作为白点)组成的9行9列的“点阵”组成,每个长和宽均为几毫米的字符(“*”或空格“ ”)构成一个“点”。 计算机用来显示一个ASCII字符,一般用的是由8行8列(还有别的规格的点阵字体)个像素(注)这种更小的点来构成字符的点阵。每个可显示的ASCII字符的这种点阵信息,通常全部存储在称为“ASCII字符集的点阵码”的字库中,它位于称作为显示卡BIOS的只读内存中(在PC机中,这块B

222、IOS只读内存芯片位于显示卡上,其中包含多套ASCII字符集的点阵库,还包括显卡的扩展驱动程序)。ASCII码值其实就是到这个“ASCII字符库”中查找要显示的点阵信息位于字符库中何处的一个序号而已。 类似于活字印刷到活字库中取一个活字字模放到印刷版面的某个位置上。如果把屏幕比作为印刷用的“白纸”,那么计算机的显示内存(显示内存通常也是在显示卡上)就可比作为由活字组成的“印刷版面”。只要把一个字符的点阵信号(相当于一个活字字模)从内存的“字库”中取(即复制)出来,填充到(即写到)显示内存的某个位置上(覆盖掉原来8行8列的像素值这个动作相当于印刷中放置一个活字字模到印刷版面上)。这个字符就会自动

223、由计算机的硬件快速显示在屏幕的某个位置上。 显示屏是一张经久耐用的唯一“张纸”,显示内存中的整屏点阵信息就是“印刷版面”,每隔十几毫秒,它就会被计算机的硬件自动“印刷”(即映射)到显示屏上,刷新整个屏幕(大约每秒刷新6080次)。 注:像素到底有多大?这与显示屏的当前分辨率有关。如果显示器的当前分辨率是1024768。那么每一行有1024个像素,每一列有768个像素。整个显示屏由1024768个像素构成。深入一步 :显示器的两种工作模式图形方式和文本方式的技术内幕在VC+6.0环境下编程,集成开发环境是运行在图形方式下的。然而,运行C语言的程序,显示器默认是工作在文本方式下的。在文本工作方式下

224、,屏幕被分割成用来显示输出80列、25行的文本(即字符)。显示器只能以单个字符作为最小输出单位。在这种文本方式下工作,不能用鼠标器,只能使用键盘。要在C语言程序运行时将显示器改变到图形方式下(比如屏幕被设置成1024768个像素)工作,需要调用相应的库函数(参见TC2.0环境下的例题 :贪吃蛇游戏)。在图形方式下,显示器以单个像素作为最小输出单位。游戏程序和窗体应用程序,通常要在图形方式下运行,这样才能由程序控制画出点(即单个像素)、线和各种颜色的图形和画面来。在图形方式下,在鼠标的移动过程中,屏幕也在频繁地刷新(即被显示内存“印刷”新的图像),有必要的话,系统程序(操作系统、鼠标驱动程序等)

225、可以将代表鼠标的点阵图像,频繁地填充到显示内存中的与鼠标当前位置对应的适当位置上。这样你就能在屏幕上看到由你“移动着”的鼠标图象。 3.4 printf()与scanf()调用(二)3.4.1字符型量和整型量输入输出时格式符匹配问题的进一步探讨不像整型量和实型量,天生就是“冤家”(注),在C语言中,整型量和字符型量天生就是“亲家”。也就是说任何一个字符型量既可以用格式符%c与之匹配,又可以用%d与之匹配。字符型量用%d格式匹配时,输出时输出的是相应字符的ASCII码;而输入时要求你输入该字符变量的ASCII码。参见以下演示程序2.6。int型量用%c格式符匹配时,输出时输出的是相应ASCII码

226、的字符;(注):根本原因在于,实型量和整型量这两种类型的量在计算机存储器中就是两种存储模式完全不同的量(参见下一章),但是输入输出库函数并不知道这一点,因为库函数不是编译程序的一部分。演示程序2.6 用字符变量在显示屏上显示字符a、6、n及其相对应的ASCII码。#includeint main() char ch1=a, ch2=6, ch3= , ch4=n; printf(ch1 is %c,ch1 ASCII is %dn,ch1,ch1 );printf(ch2 is %c,ch2 ASCII is %dn,ch2,ch2 );printf(ch3 is %c,ch3 ASCII i

227、s %dn,ch3,ch3 );printf(ch4 is %c,ch4 ASCII is %dn,ch4,ch4 );return 0; 运行此程序,得到的结果是:ch1 is a,ch1 ASCII is 97ch2 is 6,ch2 ASCII is 54ch3 is ,ch3 ASCII is 32ch4 is,ch4 ASCII is 10由以上程序运行结果可知:字符变量ch1的值是字符a, 它的ASCII码是97;字符变量ch2的值是数字字符6, 它的ASCII码是54;字符变量ch3的值是空格字符 它的ASCII码是32 ;字符变量ch4的值是字符n(它的作用就是换行),它的AS

228、CII码是10。scanf()函数调用极易出现各种错误,我们在本章提高部分将作进一步论述。3.4.2 printf()函数与scanf()函数调用时的一些重要区别:虽然格式化输入库函数scanf()在用法上与printf()有不少类似之处,但两者之间在使用上也有以下所列的重要不同之处: 第一、在printf()的格式串中可以使用转义字符 n来换行,但在scanf()的格式串中,却决对不要使用转义字符 n ;(还包括其他一些转义字符也不要使用,原因参见本章提高部分)第二、scanf()的格式串后面的参数,是输入数据的存放地址,所以在通常情况下变量名左边的地址符号“&”是决不能漏写的。例如:如果a

229、ge是int型变量,那么scanf(”%d” , &age); 决不能写成scanf(”%d” , age);然而,对于printf()函数来说,以下语句却常常是一种误写:printf(”%d” , &age); 在VC+6.0环境下,这只是输出了变量age的内存地址。正确的应当写成:printf(”%d” , age);这样才是输出变量age的值。第三、在调用scanf()之前,通常需要书写一条printf()函数调用语句。用来提示程序用户,要输入数据。并告诉用户对于输入数据的一些格式要求。这样的编程方式才是对用户友好的方式(否则,程序用户将要面对一个一无所有的黑屏进行数据输入),程序也不容

230、易因输入数据错误而崩溃。一句话:scanf()函数调用通常有求于printf();但是printf()的函数调用通常却不依赖于scanf()。第四、对于printf()函数来说:printf(“%d%d%d”, n1,n2,n3) ;很不好,三个变量的值在输出时根本区分不开;而printf(“n1=%d,n2=%d,n3=%d”, n1 , n2 , n3 ) ;却是比较好的形式。但是对于scanf()来说:scanf(“%d%d%d”, &n1 , &n2 , &n3 ) ; 通常却比较好,在输入三个数据之间只需输入一个空格将其分开(注);而scanf(“n1=%d,n2=%d,n3=%d”

231、,& n1 , &n2 , &n3 ) ;却非常不好。你必须先从键盘键入“n1=”这些字符之后,才能输入变量n1的值整个输入过程非常麻烦,完全是自讨苦吃。不信你可以亲自试一试。(注)但是对于三个字符变量ch1,ch2,ch3用一个 scanf(“%c%c%c”, &ch1 , &ch2 , &ch3 ) ; 输入的三个字符数据之间不能用输入空格将其分开,因为空格本身也是字符。第五、printf()的输出项既可以是表达式和变量,也可以是常量和函数调用(这些都是表达式);而scanf ()的输入项通常只能是(变量的)内存地址。第六、在输出时,对于float型和double型是可以这样使用格式符的:

232、%6.2f,但在输入时却不能这样写,只能是%f。3.5自加运算符 + 和自减运算符 我们经常会要用到 i=i+1; 、j=j-1;这样的赋值语句。但这样书写不太简洁。我们可以用更为简洁的方式:i+(或+i); 、j(或-j); 。 i +;或+ i; 都使变量i的值增加了1 ,相当于赋值语句i=i+1; j -;或 j; 都使变量j的值减少了1 , 相当于赋值语句j=j-1;i +和i - 称为后缀形式;而+ i和- i称为前缀形式。两者都称为自加或自减运算符。例如:int i = 7; int i = 7;i +; + i;j = i; j= i;左右两段程序执行的结果i值都为8,j的值也都

233、为8。但是把它们放在表达式或语句中就会有区别。例如,放在表达式中:int i = 7;x = i +;/*这一句相当于: x = i; i = i + 1; 这两句 */y = i;的执行结果为:x为7,y为8。即后缀方式是“先引用后增值”。而int i = 7;x = +i; /* 这一句相当于:i = i + 1; x=i; 这两句 */y = i;的执行结果为:x为8,y为8。即前缀方式是“先增值后引用”。如果i或i出现在别的语句中,读者要注意:printf(“a=%d”,a+); printf(“a=%d”,+a);等价于以下两条语句: 等价于以下两条语句:printf(“a=%d”,

234、a); aa+1;aa1; printf(“a=%d”,a);也就是说:+ 或 出现在某变量的左边,那么就要先改变该变量的值,然后再用变量的新值去做语句指定的其他事(比如参与表达式的计算或输出);如果:+ 或出现在某变量的右边,那就是先要用该变量的老值去做语句(或表达式)指定的其他事,然后再改变该变量的值(加1或减1)。 自加和自减运算符的结合方向是“自右至左”。比如 +i-,相当于+(i-)。自加和自减运算符的运算对象只能是整型变量而不能是表达式或常数。例如:7- 或 +(x - y)都是错误的。注意:自增自减运算符使用在表达式中会带来很多意想不到的问题,初学者要慎用最好作为语句单独使用(即

235、i+;或 -i;),而不要用它与其他运算符来构成(具有副作用的)表达式。点评:具有副作用的表达式与赋值运算符类似,+和-这两个运算符也会改变变量的取值,因此也是属于具有副作用的运算符。具有副作用的运算符出现在表达式中就可能形成具有副作用的表达式,这种类型的表达式一定要慎用。很容易出现意想不到的问题。参见本章提高部分。3.6赋值表达式、多重赋值、复合赋值运算符 赋值表达式的概念: 赋值语句去掉分号,就成了赋值表达式。它也完成了与赋值语句一样的运算操作。不同点在于,赋值表达式是本身具有一个计算值的,这个值就等于存放在赋值号左边变量中的值。例如 :赋值表达式 n3.6 如果变量n为整型,则根据自动类

236、型转换规则,n的值为3,赋值表达式本身的值也是3。我们自己也可以验证一下这个说法是否正确:演示程序3.3#includeint main() int n ; printf(“%d”, n=3.6); /注意:此句是输出赋值表达式 n3.6 的值运行此程序,打印出的结果确实为3。多重赋值:比如:变量1=变量2=变量3=表达式; (1)上式相当于:变量1=(变量2=(变量3=表达式); (2)所起的作用是为多个变量赋予同一个数值(最右边表达式计算出来的值)。注意:类似以下的用法都是错误的:int a=b=c=39; 复合赋值运算符:在C语言中,有一种特殊形式的赋值表达式,称为复合赋值表达式。一般形

237、式如下:变量 二元运算符 = 表达式 (1)这种表达式其实是一种普通赋值表达式的简写形式。即与(1)式等价的普通赋值表达式是:变量 = 变量 二元运算符 (表达式) (2)可以这样使用的二元运算符有两大类:算术运算符、位运算符。 即 += -= *= /= %= 等习题:求以下复合赋值语句的值:设x=14.6,y=3.71. x /= y-3 ; 提示:此赋值语句等价于: x=x /(y-3); 而不是 x= x /y -3;问题: y*=2*x+5.3; 等价于什么赋值语句?答:等价于y=y*(2*x+5.3);37 算术表达式(二)代数式如何转变为算术表达式 我们在中学数学中学到过的各种代

238、数式,一般都可以经过转换,变成C语言程序中的算术表达式。但有以下几点需要注意:1. 构成表达式的所有运算符和运算量,都要书写在同一行上。除法运算不能以上分子、下分母的两行形式出现在程序中。 2. 在计算机高级语言中一定要用”*”号代替” 号表示乘法,用”/”号(决不能用字符“”)代替”号表示除法,运算量之间的乘号“*”决不能省略,也不能用句点 . 代替乘号 。 3. 开平方要用库函数调用sqrt取代, 比如对( a2+b2 ) 开平方,要写成: sqrt(a*a+b*b);三角函数求值也要用库函数调用取代,比如sin3 ,现在要改成 sin(3* beta) ,x2y 要改写成幂函数的调用 p

239、ow(x, 2*y ) ,求绝对值现在也不能用符号 |x-y| 来表示,也要调用库函数abs(x-y)。也就是说,在C语言中,除了加、减、乘、除、取模、取负号这些运算符外,其他所有的表示代数运算的符号,都是不能用的,而要通过调用库函数来解决(请参见附录C)。这一点务必请读者要特别注意。 4. 表示优先运算进行的括号,在高级语言中只能用圆括号 ( );方括号 和花括号 现在都不能用来表示算术运算的优先顺序,而要用多重嵌套的圆括号来取代。最内层的圆括号中的子表达式,最优先进行运算,由内而外。5.在程序中进行库函数调用时,要使用圆括号把调用所需的参数括住,比如 pow (x,2*y)。调用数学库函数

240、时,还要记住必须包含数学头文件:#include 。6程序中的数字不允许用上标数字表示指数,变量名不允许出现下标。7. 各种代数式出现的百分比,现在一律要改用小数,这是因为符号 %在C语言表达式中表示取模运算(而不是除以100)。所以,必须将代数式中的68% 改写成0.68 。问题12 :请问以下算术表达式错在哪里? (s- 23.4)*y5.6/(3*Sin2a)+3.6z+1/6 答:正确的应当是:(s- 23.4)*pow(y,5.6))/(3*sin(2*a)+3.6*z+1.0/6.0 注意:在C语言中 1/6必须写成1.0/6或1.0/6.0 ,1/6的值是0。深入一步:表达式的技

241、术内幕在命令型高级语言源程序中,只能通过表达式来间接地规定:对变量、常量按什么次序进行取数(取到CPU的寄存器中)、进行何种类型的运算(算术运算、逻辑运算、位运算等)、选用何种指令(是整数运算指令还是实数运算指令等等)进行运算。例如:整型表达式 m+n ,被翻译成机器指令后,可能就是由以下3条指令序列构成的:从内存中取变量m的值到CPU的某个寄存器;(取数指令)从内存中取变量n的值到CPU的另一个寄存器;(取数指令) 命令算术逻辑单元将两个整数相加,将和存到某个寄存器中;(整型算术加法运算指令)在命令型高级语言中,没有与到内存中取数的机器语言指令所对应的取数语句。对内存单元中的数值的取用,都是

242、以出现在表达式中的要参与运算的变量(或常量)来间接表示:要对某个数据执行取数操作指令的(就象上例中的变量m和n 。只有将数据从内存取到CPU中,才能由运算器对该数据进行指定的运算)。 换句话说,在源程序语句被翻译成机器指令时,一个表达式中常常蕴含了许多条机器语言的取数指令和运算指令。表达式是使用命令型高级语言的编程效率,远远高于用汇编语言(或机器语言)编程效率的重要原因。38不同类型数据之间的类型转换简介不象有些高级语言,不同类型数据之间的转换受到严格限制。在C语言中所有类型数据之间都是可以转换的。381自动类型转换算术(关系、逻辑)表达式中的类型转换在算术(关系、逻辑)表达式中,出现在二元运

243、算符两侧的运算量,可以是属于不同类型的;这时就涉及到了一个类型之间的转换问题;如果没有出现强制类型转换,那么简单的自动类型转换规则如下:double 优先于 float 优先于long int 优先于int 优先于char 也就是说:如果表达式中出现了双精度浮点型量,则此表达式计算出的最终值是双精度浮点型的。如果表达式中出现了单精度浮点型量(但没有出现双精度浮点型量),则此表达式计算出的最终值是单精度浮点型的。如果表达式中没有出现浮点量,表达式计算出的最终值是整型(long int 或int )的。不过,数据类型的转换是分为一步一步进行的。例如对于如下表达式:56.91+A*32是先将char

244、型的量A转变为int型值做乘法运算(由于int型优先级比char型高),得到一个整数值后,再转变成单精度浮点型,然后才与56.91进行浮点加法运算。算术(关系、逻辑)表达式中类型转换的比较全面、准确的阐述,请参见本章提高部分。赋值语句(或赋值表达式)中的类型转换在赋值语句(或赋值表达式)中,如果赋值运算符右边表达式计算结果的类型与左边变量的类型不一致,则会自动进行类型转换。转换规则很简单:将表达式计算出的值转变成赋值号左边变量的类型。如果赋值号右边的数据类型“宽于”左边变量的类型(这里的“宽”和“窄”指的是数据在内存中所占据的字节数),通常会发生精度的额外损失或者错误。例如:char ch ;

245、 int in ; float fl ; double du ; 那么以下赋值语句是不会有任何问题的。 in=ch; fl=in; du=fl;但是,如果将以上赋值语句中的变量颠倒位置: ch=in; in=fl; fl=du;通常都会出现问题。第一条语句,在in的值超过255时必然出错(溢出)。即使in的取值在128到255之间,也不一定正确。这要看你正在使用的C编译器上,char类型本质上到底是有符号的整数(取值在-128127之间)还是无符号的整数(取值在0255之间)。如果是前者,就会出现把一个正整数变成了一个负整数并且存到变量ch中的错误。第二条语句,in=fl;必然使f的小数部分丢

246、失了(不会4舍5入)。更为严重的是,如果fl的值超出了in的取值范围,那么变量in中的值必然出错(溢出)。第三条语句读者可自行分析。还有以下两种情况也会发生自动类型转换:(1)函数调用时,调用时的参数类型与函数头部要求的参数类型不一致;(2)函数体中返回语句:return表达式 中的表达式类型与函数头部要求的函数返回类型值不一致时。对于这两种情况的讨论,请参见第7章函数:函数的调用与返回。372强制类型转换如果自动类型转换满足不了你的需要,则你可以要求编译程序进行强制类型转换。强制类型转换是通过使用类型转换运算符来实现的。其一般形式为: (类型名) (表达式) 其功能是把表达式的运算结果强制转

247、换成类型转换运算符所规定的类型。例如: (float) m 把整型变量m强制转换为单精度实型。 (int)(3*x-y) 把表达式3*x-y的结果强制转换为整型。在使用强制类型转换时应注意:类型说明符必须用圆括号括住。表达式也要加圆括号(单个变量可以不加括号)。例如:如果错把(int)( x-y)写成(in)x-y 则仅仅把x转换成int型之后再与y相加。以下几种情况下要使用强制类型转换:(1) 担心两个float型量x,y的乘积超出float型的表示范围,那就要这样写语句: z=(double )x*y; 其中变量z是double类型 。y的类型会自动提升。注意,另一个很类似的语句:z=(d

248、ouble )(x*y);是不对的,在进行强制类型转换前,x*y的乘积值已经超出了float型的表示范围,“羊已经跑出羊圈了”。(2) 担心两个int型量i,j的乘积超出int型的表示范围,那就要这样写语句: k=(long int )i*j; 其中k是long int类型 。(3) 想求得一个浮点型量x的小数部分,可以用这样的语句: x=x-(int)x;强制类型转换使用不当,出现的问题与赋值语句中自动类型的转换类似。注意:无论是强制类型转换或是自动类型转换,都只是为了本次运算的需要而对变量的取值进行临时性的转换,而不改变变量说明时对该变量定义的类型,也不会改变变量相对应内存中所放的数值。3

249、.9单个字符的输入输出库函数对于字符的输入输出,除了可以用格式化输入输出库函数外,还可以使用以下几个库函数:1、输出单个字符的库函数 putchar()用法举例1:putchar(d); /*这会导致将字符d在屏幕的当前光标位置显示出来*/用法举例2:putchar(ch); /*这会导致将字符变量ch所对应的字符在屏幕上显示*/用法举例3:putchar(n); /*此句运行相当于按下了“enter”键,起的是“回车换行”*/的作用(“回车换行”这个术语是从电传打字机的运行借用过来的,早期的计算机不使用屏幕显示输出信息,而是用电传打字机)。 2、输入单个字符到某个字符变量的库函数getcha

250、r() 用法举例: ch=getchar();程序运行到此句时,程序会等待程序用户从键盘输入一个字符。如果你在程序运行到此句时敲击了字符键h,并随后按下回车键“enter”(或Return),则小写字母h的ASCII码值就被getchar()函数的执行后,存放到了字符变量ch所对应的内存中。注意:输入字符函数getchar()调用时不需要参数。3、输入字符到某个字符变量getche() 这不是C89标准规定的一个函数,但此函数不少编译器都有。 用法举例: ch=getche();此库函数与getchar()的最大区别是:getche()只需要按下一个键,不必在输入一个字符后再按下回车键,get

251、char()调用后面的语句就可立即继续往下运行。310一个小型C程序的开发过程一个小型C程序的整个开发过程包括如下几个步骤:1. 分析问题并进行构思,安排所需变量和符号常量;2. 用逐步求精的伪代码或流程图构造算法(并验证算法,参见第 讲);3. (用编辑软件)编辑输入源程序,并保存在扩展名为 .c的文件中;(参见附录A)4. 进行编译预处理;(参见8.2节)5. 用编译器进行编译;(在集成开发环境下,第4和第5步是一次性完成的)6. 根据编译错误和警告回到第3步修改语法错误;7. 编译无错后,生程目标文件(扩展名为 .obj);8. 将用户程序与库函数的程序进行连接,并生成可执行的程序(扩展

252、名为.exe);9. 运行程序,发现链接或运行时错误则回到第3步修改;10. 结束编程过程,保存并保管好无错误的源程序文件(日后进行软件维护这是必须的);11.将可执行程序交给用户使用。 图3.1 C程序的开发过程*311提高部分:(尚未定稿)计算机中的数和码计算机中的二进制的数的表示,大部分与手写表示的二进制数有所不同。无符号整数(与手写表示法相同)在计算机中,无符号整数可用1个、2个、4个或8个字节来存储和传输,这是用字节中的所有位来表示数值的方法。所以实际上只能表示大于等于0的整数。一个字节的位串,能够表示的数值范围是0到255(即11111111=28-1)。两个字节的位串,能够表示的

253、数值范围是0到65535(即216-1)。4个字节的位串,能够表示的数值范围是0到4294967295(即232-1)。有符号整数(因为有符号,而在计算机中无法直接表示符号,所以与手写表示法不同) (1)二进制有符号整数的原码表示法 : 所有在某个范围中的有符号整数构成了一个(有限元素的)集合。因此,当然可以用具有长度为n的二进制位串(共有2n个码)来对其进行编码 。用二进制位串通过编码来表示有符号整数,有多种编码方法,“原码表示法”规定:用位串最左边的一位表示该数的符号位(0表示正数,1表示负数),其余各位表示该数的真正数值。表1.4中是一些典型数值的原码。01111111000000001

254、000000011111111最大正数(127)正0负0最小负数(-127)表1.4 8位原码表示法的最大正数,正0、负0和最小负数所以,将原码表示的二进制有符号整数转换成十进制整数很简单,将最高位转变成正、负号,将剩下的其余各位用前面 1.4.2节学过的方法转变成十进制数。比如:10011011就是十进制数 -27。(2)二进制有符号整数的补码表示法 :但在计算机中,通常却不是用原码来表示有符号整数,而经常是采用补码来表示。一个数的补码如何求得?可以通过原码来求得。正数的补码与原码一样;负数的补码是将原码的最高符号位(1)除开,将剩下的每一位取反(即1变成0、0变成1,这其实就是求反码)后,

255、再加上1。最后再放上符号位。例如:原码是:10110110, 将最高的符号位1除开,得到:0110110,每一位取反得到:1001001,再加上1得到:1001010,再放上符号位最终得到补码:11001010。表1.5中是一些典型数值的补码。01111111000000001000000011111111最大正数(127)0最小负数(-128) 最大负数(-1)表1.5 8位补码表示法的最大正数、0、最小负数(-128)和最大负数(-1)令人感到有些奇怪的是:对补码再一次求补后,又可以得到原码(注)。(注)原因在于:一个负数的原码和补码,除掉符号位后,其实是一个互为补码的关系。因为 :除掉符

256、号位后,原码+反码等于每一位都是1的最大数,而补码是在反码的基础上再加上1。所以如果忽略向更高位的进位,原码+补码=0。这也是补码这个术语的由来)。深入一步:计算机硬件为何要用补码进行算术运算?为何计算机硬件要如此麻烦的用补码来进行算术运算,而不用人们更容易懂得的原码进行运算?简单地说,这是由于用原码运算会出现麻烦(符号位在某些情况下不能直接参与运算,比如:(10000001)原+(00000001)原=(10000010)原=(-2)10)。这明显不对。而且还出现了两个0(正0:00000000和负0:10000000)的问题。而用补码来进行运算,以上这些问题都迎刃而解!符号位与数字位可以一

257、并参与算术运算,如果有向更高位的进位(即在最高符号位产生了进位),只要直接把它丢弃即可。采用补码,所有减法都可用加法代替,节省了制造运算器的成本(不必制造实现减法的电路,执行加、减运算,运算器中只要有“全加器”电路即可)根本原因在于:补码原本就是针对原码的这些缺陷而设计出来的。深入的细节问题,请参见附录中的一篇参考文献。*(3)二进制实数(浮点数)在计算机中的表示(即存储和传输的)方法:不通过某种编码,计算机中无法直接存储手写的小数形式的实数-110110.101或规范化的指数形式的实数-1.101101012101(小数点前面只有一位非0整数,就是规范化的指数形式)。在计算机中,对实数的编码

258、是以对二进制的规范化的指数形式为基础来进行的。其编码方式是:省略掉规范化的指数形式中的一位整数部分“1”、小数点“.”、乘号“”、基数2,只保留数的正负号(用0表示正数、1表示负数)、尾数(即实数的小数部分)和指数部分。但是指数部分还要变换。因为在计算机中,IEEE(电气和电子工程师协会)745标准规定:用8位的余127码,而不是用补码,来表示有符号的指数(注)。 在计算机中通常规定用“符号位(1位)+指数位(8位的“移码”或称为余127码)+尾数位(23位)”的方式(一共占用4个字节的内存)来表示和存储二进制实数(即单精度浮点数)的。这是由IEEE745标准规定的一种二进制实型数表示法,精度

259、是十进制的7位(细节请参见美Behrouz Forouzan等著,刘艺等译计算机科学导论机械工业出版社 36页)。大多数计算机制造商都支持这种IEEE745标准。(注)在余码系统中,正的和负的整数都可以作为无符号的码来存储和表示。为了表示正的或负的整数,将一个正整数127(称为偏移量)加到每一个数值中,将它们统一移到非负的一边,这就构成了一个余127码。从一个余127码得到它表示的有符号整数,只需减去127即可。深入一步:例题:求一个十进制数-161.875的余127码实数表示法。解1) 符号为负,所以最左边位S=1。2) 十进制转换为二进制 41.75=(110011.11)2 。3) 规范

260、化:(110011.11)2=(1.100111125)。4) 指数位的余127码为E=5+127=132=(10000100)2。5) 尾数位为 M=(1001111)2 。6) 最终得到: 1 10000100 10011110000000000000000 符号位(S) 指数位(E 8位) 尾数位(M 23位)浮点数的取值范围由上可知,能够表示的最大正数用余127码表示是:0 11111111 11111111111111111111111=2(255-127)1.11111111111111111111111=1111111111111111111111112105=3.41038 能

261、够表示的最小正数用余127码表示是:0 00000000 00000000000000000000001=2-1271.00000000000000000000001=1.1710-38 对于负数情况完全是类似的:绝对值最大的负数是-3.41038,绝对值最小的负数是-1.1710-38 。 浮点数的精度浮点数的精度取决于尾数部分。尾数部分的位数越多,能够表示的有效数字就越多。 单精度数的尾数用23位存储,加上默认的小数点前的1位1,2(23+1) = 16777216。因为 107 16777216 108,所以说单精度浮点数的有效位数是7位。 实数值0.0规定用余127码的全0来表示:用以

262、上转换方法,规范化指数表示法的0,无法用余127码来表示(因为它没有一位非零的整数部分)。因此,特别规定用余127码的全0来表示0。0 00000000 000000000000000000000001123 ASCII字符集(ASCII编码解码表):在计算机中,用来表示非数值的文字型数据的编码,在历史上有很多种,但流行的却不多。最为常用的是7位二进制的ASCII码(美国国家信息交换用标准码)。标准的7位ASCII码可以表示128(即27个)个字符,参见附录B。构成任何一个英文文本的所有常用字符。包括:大写和小写的共52个英文字符、阿拉伯数字0到9、各种标点符号、空格字符、在打印或通信时要使用

263、的其他一些(ASCII码值在031之间的)不可打印的控制符号等等,都可以在这个ASCII字符编码解码表中找到(参见附录B)。每个ASCII码可以存放在内存的一个字节中(8位中的最高位通常填一个0,只用字节的低7位来存放ASCII码)。ASCII码是用计算机处理非数值的文字型数据的重要基础。在计算机内部可以用ASCII码的序列来表示任何形式的英文文本;也可以将人们用高级程序设计语言或汇编语言编写出来的源程序,转换成二进制的ASCII码的形式,输入到计算机的内存中。在计算机制造商们就关于使用哪种字符集达成一致后,利用计算机和网络进行文本数据的处理和通信,就变得容易多了。例如:小写英文字母a的ASC

264、II码为(97)10 。为了在计算机中存放键盘上敲击的字符 a ,其实(计算机的硬件和基础软件配合起来工作)最终只是在内存的某个字节中存放了与之相对应的二进制的ACSII码的值:(01100001)2 ,它等于(97)10 。深入一步:Unicode字符集由于ASCII字符集没有对非英语国家文字中使用的字符进行编码,所以非英语国家用计算机(和网络)对本国语言文本进行处理和通信(尤其是国与国之间的通信)就比较麻烦。近年来,一种称为Unicode字符集的编码规则在迅速普及之中。一个Unicode码要用4个字节的位串来存放和传输。因此Unicode码一共可表示232个符号。代码的不同部分被分配用于表

265、示来自世界上不同语言中的符号。任何两个使用Unicode编码的计算机之间进行通信和电子邮件收发就方便多了。Unicode字符集中的前128个字符与ASCII字符集中的字符是完全一致的。可选练习1:上网查找有关Unicode码的一些更详细的知识。深入一步:数和码之间的复杂关系对数值数据本身是可进行编码的。比如对于一个二进制的有符号整数,由于在计算机中,也只能用二进制的一个位来表示此数的正负号,因此也有多种编码方式。常用的有原码、反码、补码这三种编码方式来表示有符号的二进制整数。其中补码被当今大多数计算机采用。与表示非数值的事物(或状态)的编码不一样,对于数值数据的编码(比如补码),位串中的每一个

266、“位”的重要性是不一样的(因为与权重有关系)。再比如,对一批数值数据进行压缩和解压,其实通常也是一种多对少的编码和少对多的解码。如果用一个二进制的位串来书写计算机的一条机器指令。则该位串中是既有码,又有数:位串的左边部分一般是指令的操作码,位串的右边部分是操作数(参见1.9)。虽然“码”本身也是一个“值”(序数值),但对于非数值编码的码值进行数学运算,通常是毫无意义的(除非你想对这些码值进行加密或对密码进行解密)。非数值编码的码值的通常意义,仅仅是某张编码解码表中的一个序号而已。通过这个序号,可以在该编码表中查找到此码所代表的真正含义。 然而,对于数值编码的码值,是可以进行数学运算的。比如CP

267、U中的运算器用补码做有符号的整数算术运算,而基础软件则在补码和十进制整数值之间进行相互转换。一 C语言中可以使用的所有基本变量类型。数值型变量分为完全不同的两大类:整型变量和浮点型变量。整型量和浮点型量本质上是不同的两类量,根本原因在于这两类量在内存中的存储方式完全不同,进行算术运算的机器指令也不相同(整型数有一套,浮点型数另有一套)。其中整型变量又分为两类:有符号整型变量和无符号整型变量。整型变量中无符号整型是比较简单的,内存中的2个或4个字节全部用来存放该变量的值。所以,无符号整型变量的最大取值要么是65535(即二进制的16个1连着写:1111111111111111=216-1),要么

268、是4294967295(即232-1)。前者对应于整型变量分配两个字节内存,后者对应于整型变量分配4个字节内存的情况。整型变量中的有符号整型稍稍麻烦一些。因为这种量要用(内存中变量所对应的连续)几个字节中的最左边的一个位来表示该数的符号。该位值是0,表示是变量的值是正数;该位值是1,表示变量的值是负数。所以,用两个字节存储的整型变量取值范围是-3276832767之间(在计算机中,有符号数用的是补码方式存储的,参见附录)。下面,我们首先简要讨论一下各种整型变量。整型变量整型变量分为两类:有符号的整型和无符号的整型(无符号整型要使用修饰符unsigned)。C语言中整型变量一共细分为6种类型:s

269、hort int 有符号短整型unsigned short int 无符号短整型int 有符号整型unsigned int 无符号整型long int 有符号长整型unsigned long int 无符号长整型关键字int前的所有修饰符short、long、unsigned 之间都可以颠倒顺序而不会改变定义的类型,比如: short unsigned int 等价于unsigned short int只要有了修饰符,关键字int都可以省略不写,比如:long 就等价于 long int、short就等价于short int 。无符号整型量通常用于系统编程、嵌入式编程以及驱动程序编程等方面。所

270、以常用的只有int和long int 。如果数据不大,你又想节省内存空间,可以考虑用 short类型。6种整型的每一种所表示的取值范围,都会由于环境(编译程序、机器硬件)的不同而不同。但是,有两条所有C编译器都必须遵守的原则:首先,C标准要求short int、int和long int 中的每一种都要覆盖一个确定的最小取值范围。其次,C标准要求:int类型不能比 short int 类型短、long int类型不能比 int 短。各种整型类型的最小取值范围参见下表:类型最小值最大值printf()和scanf()调用时要使用的格式符short int-3276832767%hdunsigned

271、 short int 065535%huint-3276832767%dunsigned int065535%ulong int-21474836482147483647%ldunsigned long int04294967295%lu表3.3各种整型类型的最小取值范围和与之匹配的格式符。 浮点型变量:浮点型量在计算机中的存储方式,通常遵循IEEE标准规定,是以二进制的:符号位+指数位+尾数位 形式存放的。 float型变量占4个字节一共32位的存储空间:符号位1位(第31位)+指数位8位(第23位到第30位)+尾数位23位(第0位到第22位)。二常量类型本书的基本语法要素中,常量只使用了整

272、型常量 567,425实型常量 567.17,字符型常量 a, 0, 8 等字符串常量 ”567”,”hello!”等. 这些类型。这是最基本的最常用的。在使用变量和常量的类型时也有一个原则,那就是:只要基本类型够用,就不用其它的类型。除了上一章介绍的基本整型外,整型常量还包括:长整型,要以字母L或l结尾如:123456789L;无符号整型常量,以u或U结尾 如12345U,无符号长整型123456789UL 或者123456789LU 要求编译程序按照我们的希望去存放数值常量,才需这样用(往往用在系统编程或嵌入式开发上)。此外,除了十进制常量(不能用0开始)外,在C语言中还可以使用八进制、十

273、六进制形式的整型常量。八进制数一定要以0开始, 比如054,表示的是8进制数(54)8 而不是(54)10 ;十六进制数一定要以0x(或0X)开始,比如0x54A7,表示的是十六进制数(54A7)16 (请参见第 讲,将十进制数转化为任意进制数)。八进制和十六进制常量量主要也用在系统编程和嵌入式编程中。一个浮点数,其指数表现形式可以有多种。比如543e2和5.43e4 ,但表示的其实都是同一个实数;但我们把小数点前只有一位数(5.43e4)的这种表示方式,称为“规范化的指数形式”。两种机外表现形式的浮点数,在编译(或输入)时都被编译程序(或标准输入库函数)转变为浮点数的机内表现形式()。输出时

274、则正好相反。三不同数据类型之间的转换:机器语言的算术运算指令比C语言算术表达式的限制更多。为了让计算机执行机器指令中的算术运算,通常不仅要求两个操作数有相同的长度(字节数),而且还要求数据的存储方式也相同(同是整型或浮点型)。机器指令可以直接将两个16位的整数相加,但是不能直接将16位浮点数和32位浮点数相加,也不能将16位的整数和16位的浮点数相加。然而在C语言程序中,却允许在表达式中混合使用各种基本类型数据。在一个表达式中,可以同时出现整数、浮点数和字符量。在这种情况下C编译程序可能需要生成一些指令,在一条运算指令执行之前,将其中一个操作数转换成不同的类型(与另一个操作数类型一致)。 例如

275、,如果是int 型数据和float型数据进行加法操作,那么编译器将安排把int型值转换为float型值的格式。共有以下几种情况会产生类型转换问题:(1) 当算术表达式(或逻辑表达式)中操作数的类型不匹配时时(2) 赋值运算符右侧表达式的类型与左边变量类型不匹配时(3) 函数调用时的参数传递时的类型不匹配时。此处我们仅讨论前两种情况,第三种情况在函数一章讨论。表达式中的类型转换:表达式中的类型转换,包括自动转换(又称为隐式转换)和强制类型转换(这在3.x已经讲过了)两种:1) 自动类型转换: 自动转换发生在两个不同数据类型的量混合运算时,由编译器自动完成。自动类型转换遵循以下规则:常用算术转换多

276、用于二元运算符的操作数上,包括算术运算符、关系运算符。例如,假设变量x的类型float型,而变量i为int型。常用算术转换将会应用在表达式x+i的操作数上,因为两者的类型不同。显然把变量i转换为float型(匹配变量x的类型)比把变量x转换成int型(匹配变量i的类型)更安全。整数始终可以转换成float类型;可能会发生的最糟糕的事是精度会有少量损失。相反,把浮点数转换成为int类型,将有小数部分的损失;更糟糕的是,如果原始数大于最大可能的整数或者小于最小的整数,那么将会得到一个完全没有意义的结果。常用算术转换的策略,是把操作数转换成可以安全的适用于两个数值的“最狭小的”数据类型。(粗略的说,

277、如果某种类型要求的存储字节比另一种类型少,那么这种类型就比另一种类型更狭小。)为了统一操作数的类型,通常可以将相对较狭小类型的操作数转换成另一个操作数的类型来实现,这就是所谓的提升。它把字符或短整数转换成int类型(或者某些情况下是unsigned int类型)。执行常用算术转换的规则时可以划分成两种情况。任一操作数的类型是浮点型的情况。问题4:以下赋值语句分别执行后,赋值号左边变量的值是多少?假设ch是字符型,初值是字符*、x是实型,初值是23.5、n是整型,初值是34:1ch=a+6;2n=2*x+n-12.45-ch;3x=n*ch;四表达式的副作用:在一个表达式(赋值表达式除外)中,如

278、果不出现+、-运算符,以及不出现具有副作用的函数调用(参见第6章函数),则称此表达式为没有副作用的表达式。 没有副作用的表达式,在计算出结果后,并不会改变表达式中变量的原来取值,提倡使用没有副作用的表达式。问题:j的初值为,有以下表达式(j-)+(j-)请问该表达式的值是多少?答:这个式子是具有副作用的表达式,在不同的编译环境下,得到的结果可能不一样。一些系统把作为表达式中所有j的值,表达式的结果是,j的最终值是;而另一些系统按从左到右计算,即第一个(j-)表示参与加法运算的是,然后j立即自减,这样第二个参与加法运算的(j-)的值就是了。表达式的计算结果为。j的最终值仍然是。五.逗号运算符:逗

279、号运算符在形式上就是一个或多个逗号,它是C语言中优先级最低的运算符,其形式是:表达式1,表达式2,表达式3 在对逗号表达式求值时,首先求出表达式1的值,然后再去求表达式2的值,再求表达式3的值。以最右边的表达式(这里是表达式3)的值作为整个逗号表达式的值。 子表达式的个数并不限于2个或3个,可以是任意多个。逗号表达式主要用在循环语句for语句的头部的表达式1(循环前的变量初始化)和表达式3(循环变量的更新)中。在其他地方尽量不要用,因为容易出现误用。 例如 for ( sum=0,i=1;i=100; i+)sum=sum+i; 是在for循环的表达式一中使用逗号表达式的例子(参见第5章)。六

280、.运算符的优先级与结合性:各类运算符都有明确的优先级。但表达式中同一优先级的运算,到底哪一个运算先做呢?记得在学算术和代数时,老师所教的吗?是从左到右,例如6-2+13,相当于(6-2)+13,即运算是从左到右来进行的,在C语言中,大多数运算符基本上也是类似的从左到右进行的。 不过,在C语言中,还有少量一元(或称单目)运算符是遵循从右到左的优先顺序的:例如 x=y=z=3.6;这是由一个多重赋值的表达式构成的语句,这个语句并不等同于:(x=y)=z)=3.6; /*此式不正确*/而是等同于: x=(y=(z=3.6);也就是说,对出现在同一表达式中的多个赋值运算符,其运顺序是从右向左的。3.6

281、先被赋给了z,整个赋值表达式z=3.6的值也是3.6,此值又被赋给了y,。因此,运算符的结合性实际上也是一种对运算符的优先顺序的规定,只不过是针对属于同一优先级的运算符而言的。 关于全部运算符的优先级与结合性,请参见附录D七对字符变量的进一步说明:(A) 由于对字符变量机内取值的二进制最高位的解释不一样,一些编译器把字符变量作为取值范围为0255的无符号整型变量对待,而另一些编译器将其作为取值范围为-128127的有符号整型变量对待。八printf()和scanf()的进一步论述:(未完)在C程序中,表示一些C语言字符集()中没有的ASCII字符时(比如:跳格、退格、换行等打印控制操作),都只

282、能使用字符转义序列(即转变了原来意义的一个字符序列)。字符转义序列要以字符开始。常用字符转义序列如下 :表2.x 常用的字符转义序列名称 转义序列名称 转义序列 响玲符 a反斜杠 回退符 b问号 ?换页符 f单引号 换行符 n 双引号 ”回车符 r (不换行)ddd 13位八进制数所代表的字符横向制表符 t xhh 12位十六进制数所代表的字符纵向制表符 v311编程错误分类:语法错误:可以在编译期间由编译程序找出的错误;运行时错误:程序在运行时才能发现的错误;算法错误:在编译和运行时都不能发现的,只有通过事先(编译前)或事后(程序试运行后)分析、检查结果才能发现的错误;点评:编程时,首先要想

283、方设法避免的是算法错误,因为一个规模较大的程序,如果在算法上有根本性的错误,有可能造成前功尽弃的严重后果。这就要求程序员在编写算法时仔细慎重,并掌握一些验证算法是否正确的方法(参见洗牌程序中,画变量内存取值变化图来验证算法是否正确的方法)。312最常见的编程错误列表(此表包括了同学们极易犯的几乎全部初级错误,在上机前要认真看一看;上机出现错误时,也可借助此表来排除错误):1使用非法的或错误的标识符;比如 main()写成mian();printf()写成了print(); 或写了sin(2)、Cos()、*r*r 等;2变量未经定义就使用;3. 变量类型使用不当(取值范围不够大、该用整型而用了

284、实型、精度不够等等);4变量未经初始化就使用在表达式中;5语句或定义结束缺少分号;(或误用分号:复合语句结束后面不需要分号)6表达式中漏写了必要的乘号*。例如:将3*x*y5 错写成 3xy5 ;7表达式中缺少必要的圆括号,或圆括号不匹配,或者用花括号、方括号取代了圆括号;8. 注意将数学中的代数式转变为C语言的算术表达式中的问题。参见P页。9忘记了注释的结束符*/;正确的应当是以/*开始,以*/结束,所用的两个符号“*”和“/”之间不能用空格隔开;10在该用小写字母的地方,却用了大写字母(例如,把main写成了Main、scanf写成了Scanf;定义变量名是小写,但在程序中却用了大写的变量

285、名,其中s,c,x,k,z最易用错,比如s1写成了S1、ch写成了Ch);11在语句之间对变量进行了定义。正确方法是在函数体中将所有定义放在所有语句之前(但是在C99标准中,允许在块语句中的最前部定义变量);12编写代码(程序)时,就特别要注意:避免程序在运行时,用了0作为除数;13.在字符串或输入输出格式控制串外的其他地方,用了非法的标点符号(除了英文半角输入法,其他输入方式下的标点符号都是不对的);14.漏写函数体结束时的花括号,或者花括号不配对; 15.分隔符使用不正确;比如 inta,b c. d; 应为int a,b,c,d; ;16.程序中使用了库函数,但忘记包含相应的头文件(比如

286、要包含头文件:math.h);17.标准输入输出头文件包含时出错;正确的是:#include , 或者#include “stdio.h”。 但有不少同学会出现拼写错误。第2、3两章小结:(尚未修改)1函数定义由函数首部和函数体组成。2一个C语言的程序,可以顺序包含多个不同函数定义(详见第6章),但必须有一个main函数。程序的运行都是从系统调用main函数开始的。3C语言中任何函数体的主要构成成分是:定义序列和语句序列。两种序列都要以分号结束(复合语句除外)。定义序列必须在语句系列之前。4C语言程序的次要构成成分是:编译预处理命令、注释和声明(即函数声明,参见函数一章)。这些成分是不以分号结

287、束的。5编译预处理程序将根据源程序中的编译预处理命令对源程序进行一些辅助性的插入、替换和编辑工作(详见第 章)。编译预处理命令以“#”开始,不以分号结束。每条编译预处理命令必须书写在同一行上。6注释以“/*”开始,以“*/”结束。注释会被编译程序忽略。但好的注释使程序更易被人们读懂。不要忘记书写注释的结束符号“*/” 。7C语言的字符集,请参见P 页;书写C语言的源程序的正文部分,只能使用C语言字符集中的字符,其它任何字符不得使用。字符集中的任何字符,只能用英文半角方式来输入。8标识符是以26个大小写英文字母、阿拉伯数字0到9和下划线构成的字符序列;其中左边开始的第一个字符不能是数字;C语言中

288、,给变量、符号常量、函数、语句标号命名,只能使用符合标识符规定的名字(下标变量和结构变量的成员是两个例外)。C语言的标识符对字母大小写敏感。 9关键字是被编译程序保留下来的特殊标识符;主要分为两大类:用于定义(或声明)的关键字和用于构成语句的关键字。参见p页;C89标准中的关键字都是由小写英文字母组成的。10C语言中的分隔符有三个:空格、回车/换行、逗号。同类项之间要用逗号分隔,关键字和标识符之间要用空格或回车/换行分隔,不能用逗号。在C语言中,合理地使用多余的空格及空行,可以使得程序更清晰易懂。 11C语言中常量分为数值常量(又称为字面常量)和符号常量两种;应当多使用符号常量,除了0和1以外

289、,尽量少用数值常量。符号常量一般由大写英文字母的标识符构成。12C语言中要进行运算加工的(可以变化的)数据一般都要用变量进行定义。注意定义变量时使用合适的类型。类型不同的变量,编译程序不仅安排存储空间的大小可能不一样,运算时选用的机器指令可能都不一样。注意变量的实际取值是否会超出变量规定的取值范围。C语言中各种常用变量的基本类型是int、float、char、double(双精度实型)。 13变量一定要先进行定义,定义后要初始化,然后才能在表达式中使用或进行输出。14变量可采用三种方式初始化:定义变量时就给定一个初值(不常用);输入函数调用时通过键盘输入一个值到变量中;通过赋值语句赋给变量初值

290、。没有初始化的变量不能出现在表达式中。 15通过sizeof()运算符,可得到变量占用的内存字节数。为了使你的程序可移植性好,不要对任何类型变量占用的字节数作出假定。通过&运算符,可得到变量对应存储单元的内存地址。16最常用的运算符分为三大类:算术运算符、关系运算符、逻辑运算符;在算术运算符中,除法运算符和取余数运算符最容易出错,不要漏掉或误用乘法运算符。见P页17所谓表达式,是用运算符将运算量(常量、变量、函数调用)连接起来的有意义的式子。其中还可使用圆括号来改变运算符固有的先后运算顺序圆括号内的部分优先进行运算。无副作用的表达式不会改变变量的取值;在初学编程时,表达式极易出现书写多种错误。

291、见P 页。在命令型高级语言中,实际上只能用表达式和语句这种抽象的方式来命令计算机为你工作。18赋值语句是先计算赋值号右边的表达式的值,然后将此值存放在赋值号左边的变量代表的内存中;在程序中,常常采用赋值语句来改变一个变量的原来取值。决不能把方程式直接转变成赋值语句,解方程要编程者自己做;只有公式可才直接转化为赋值语句。19形式如 x = 包含有变量x的表达式 ; (x可以是任何简单类型的变量)的赋值语句,大量的出现在程序中。它表示的是一种迭代关系:即指明了如何由变量x的一个老值(取出来参与表达式所规定的运算),最终得到了变量x的一个新值。20语句之间的变量有先后依赖关系的,不可随意颠倒先后语句次序。21C语言本身不负责数据的输入输出工作。而由输入输出库函数的调用来进行。关于输入函数调用请参见P页,关于输出函数调用请参见P页22调用库函数时不要忘记包含相应的头文件。调用的参数之间要用逗号隔开,所有调用参数要用圆括号括住。比如pow(x,3.7); 是求x3.7的值。23注意编程的步骤、注意认真学习和模仿构思逐步求精算法(伪代码表示的算法)的过程。参考文献1 何勤轻松学习C程序设计中国电力出版社 2008.82 何勤轻松学习C程序设计(修订版)百度文库(电子版)92

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 中学教育 > 其它中学文档

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号