C语言从入门到精通
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第1篇 起步篇

第1章 C语言基础

C语言是世界上非常流行、有着广泛前途的计算机高级语言。C语言从诞生之日起就深受人们的喜爱。正是因为C语言的广泛普及,才使得后来的编程语言都遵循或延续了C语言的习惯。因此,C语言自然成为几乎所有程序员的起步语言。本章主要介绍为什么要选择C语言、各种进制的转换及计算机中数的表示。

1.1 为什么要选择C语言

目前世界上流行的计算机编程语言有许多,如C,C++,Java,Pascal,Basic,Fortran。为什么要选择C语言呢?本节主要介绍选择C语言的好处、C语言的特点及如何学好C语言。

1.1.1 选择C语言的好处

C,C++,Java,Basic是目前非常流行的编程语言,几乎每一个程序员都有过学习C语言的经历。C语言对于初学者是一个非常不错的选择,因为选择C语言有如下好处。

(1)C语言是C++、Java、C#的基础。C++、Java、C#是目前最为流行的语言,这些语言都是建立在C语言基础之上的,当你学了C语言之后,再学习C++、Java或C#会变得非常容易。

(2)C语言涵盖了计算机编程语言中的几乎所有知识,对于今后学习其他编程语言和理解计算机系统的工作方式有很大的帮助。

(3)使用C语言编写的程序运行效率高。C语言是一门高级语言,使用它编写的程序仅仅比汇编语言编写的程序的运行效率低10%~20%。

(4)C语言可以直接访问内存,并可以直接对硬件进行操作。同时,C语言要比汇编语言更加容易学习和使用。

(5)过去许多系统都是使用C语言实现的,如Windows和Linux操作系统的大部分代码是用C语言编写的,它们都可以得到重新利用。

(6)有了C语言基础,便可为今后继续学习数据结构和算法提供保障,因为目前绝大部分的数据结构和算法教材都是以C语言描述的。

(7)使用C语言既可以编写系统软件,也可以编写应用软件。现在的游戏软件、嵌入式开发和手机开发都是选择C语言作为开发语言的。

(8)使用C语言编写的程序有很好的移植性。在一种计算机系统(如IBM PC机)上编写的C语言程序,可以直接在其他系统(如DEC VAX系统)上运行。

C语言拥有如此多的优点,你也一定坚定了学习C语言的决心。接下来,就让我们来了解一下C语言的特点吧!

1.1.2 C语言的特点

C语言自从1973年诞生于贝尔实验室以来,经历了将近40年的历史。它现在仍然经久不衰、拥有广大的用户,这是与C语言自身的特点分不开的。

1.C语言程序结构紧凑、简洁、规整,表达式简练、灵活──容易理解与学习

C语言程序结构紧凑、简洁、规整,容易阅读与理解;其表达式简练,去除了一些不必要的成分,书写简单,使用起来比较灵活。因此,C语言更加容易掌握。

2.C语言的数据类型丰富──可以描述各种复杂的数据结构

C语言的数据类型包括整型、实型、字符型、数组类型、指针类型、结构体类型、联合体类型等。其中,结构体类型和联合体类型是用户自定义的类型,即用户根据具体需要自己定义的类型。C语言所提供的数据类型可以描述各种复杂的数据结构。

3.C语言的运算符丰富──实现复杂的运算比较方便

C语言包含了34种运算符,丰富的运算符与丰富的数据类型结合,构成了多样的表达式。灵活的运算符可以很容易实现比较复杂的运算。

4.C语言是一种结构化的语言──拥有3种控制语句、函数作为程序的模块单元

C语言是一种结构化的程序设计语言,适用于大型的模块化程序设计。它拥有3种控制语句,其函数是C语言程序的模块单元,每个函数各自独立。C语言的源程序可以分为多个源文件,先对其分别进行编译,然后链接在一起便可构成可执行程序,这为大型的软件开发提供了方便。

5.C语言可以对位进行操作,可以实现汇编语言的大部分功能──既是高级语言,也是低级语言

C语言可以直接访问内存单元,直接对位一级进行操作,也就是说它可以实现汇编语言的功能。C语言本身又是一门高级语言,但是它具备了汇编语言的许多功能,因此C语言既是高级语言,也是一门低级语言。正是用于C语言的这种双重特性,所以我们常常称C语言为中级语言。

6.C语言的指针是它区别于其他语言的显著特性

C语言有一种非常特殊的类型──指针。指针的存在,使得它可以直接访问内存。指针使得原本灵活多样的C语言变得更加灵活,使得编写出的程序的运行效率更加高效。

1.1.3 如何学好C语言

在选择了C语言和对C语言的特点了解之后,想要学好C语言,需要把握好以下几点。

1.确立离散性思维方式,摈弃连续性思维方式

在学习计算机语言时,一定要确立离散性的思维方式,这是决定你是否能够学好C语言的一个非常重要的因素,这是因为计算机中的数据存取形式是二进制形式,是一种离散的数据表示方式。在处理类似连续性函数、积分等问题时,需要将问题转化为离散的方式进行处理。在学习C语言时,你会深刻地体会到这一点。

2.熟练掌握二进制与十进制、十六进制、八进制之间的相互转换

在计算机中,所有的数据都是以二进制形式存储的,而我们熟悉的是十进制,再加上二进制数据表示起来又太长,因此为了方便表示,需要将二进制转换为十进制、十六进制、八进制,这样看起来就比较直观。

3.理解字符与ASCII码之间的关系

通过键盘输入的数据是字符数据,而计算机是以二进制形式存储的,这需要将字符转换为对应的二进制形式并存放起来。美国的国家标准协会ANSI专门规定了字符与ASCII之间的对应关系。

4.掌握运算符及运算符的优先级

C语言提供了34种运算符,每种运算符都有优先级与结合性。如果有多个运算符出现在同一个表达式中,需要选择优先级别高的运算符进行计算。如果运算符相同,则需要根据运算符的结合性进行运算。

5.掌握3种程序控制结构

C语言是一种结构化的程序设计语言,它具有3种控制结构:顺序结构、选择结构和循环结构。使用这3种结构可以解决所有的问题。

6.掌握一些常用的算法

在学习C语言的过程中,常常需要对一些数据进行排序及查找给定的数据,这就是排序算法和查找算法。排序算法和查找算法在程序设计过程中是非常常用的算法,排序算法可以分为冒泡排序、插入排序、选择排序等,查找算法可以分为顺序查找、折半查找等。掌握一些常用的算法对今后学习数据结构和算法是大有裨益的。

7.熟练使用指针

指针是C语言区别于其他语言的一个重要标志。指针是C语言的灵魂,熟练使用指针可以使编写程序更加灵活,编写出来的程序运行效率也会更加高效。指针是一把双刃剑,使用得好则可以提高运行效率,使用得不当,则很容易造成难以意料的错误。因此,需要大家在学习的过程中熟练掌握指针。

8.熟练掌握一个开发工具

要想学好一门语言,必须熟练掌握一个开发工具。只有多上机练习,才能知道程序是否正确。C语言的开发工具有许多,目前比较流行的有Turbo C 2.0,Turbo C 3.0,Visual C++ 6.0,Win-TC,LCC-Win32等。我们建议初学者可以学习Turbo C 2.0或Turbo C 3.0,有了基础之后可以选择Visual C++ 6.0(Visual C++ 6.0是一个非常专业的开发工具)。

1.2 程序设计语言基础──进制转换

在计算机内部,信息的存储与处理都采用了二进制形式。二进制不容易阅读且需要用很长的数码来表示一个数,为了表示方便与符合人们的阅读习惯,常常需要将二进制转换为十进制、十六进制、八进制。本节主要讲解二进制数与十进制数、十进制数与十六进制数、十进制数与八进制数,以及各种进制数的相互转换。

1.2.1 二进制与位权

二进制形式主要是由计算机内部结构决定的。计算机是由电子器件构成的,而计算机中的数据是用电子器件的物理状态表示的,其中高电平表示1,低电平表示0。

1.二进制

二进制数中只有两个数字符号:0和1。例如,101、1110101、1101110都是二进制。在计算机中,为了将二进制与十进制、十六进制、八进制区别开来,会在一个数的末尾加一个下标。例如,上面的二进制常常写成如下形式:

(101)2、(1110101)2、(1101110)2

二进制的特点是逢二进一,即满二就向高位进一。例如,在二进制数中,有0+0=0,0+1=1,1+0=1,1+1=10。需要注意,由于1+1满2需要向高位进1,所以是10,而不是2。对于10+11=101,其运算过程如图1.1所示。

2.位权

二进制数与十进制数一样,每一个数字符号在不同的位上表示的意义不同。例如,十进制数的1在个位上表示1,在十位上表示10,在百位上表示100,在千位上表示1000。这种同一个数字符号在不同位上的数值表示就是位权或权。一个n位的十进制整数从低位到高位对应的位权分别是100,101,102,…,10n-1。对于带小数点的数,小数点后的位权分别是10-1、10-2、10-3…。例如,十进制数2813.65对应的位权如图1.2所示。

图1.1 10和11的运算过程示意图

图1.2 2813.65对应的位权示意图

同理,一个 n 位的二进制整数的位权从低位到高位依次是20、21、22、…、2n-1。对于带小数点的二进制数,小数点后的各位数对应的位权依次是2-1,2-2,2-3,…,小数点前面的各位数对应的位权是20、21、22、…。例如,二进制数(1011101.101)2 的位权如图1.3所示。

图1.3 (1011101.101)2对应的位权示意图

1.2.2 二进制数与十进制数的相互转换

二进制数与十进制数的相互转换分为二进制数转换为十进制数、十进制数转换为二进制数。

1.二进制数转换为十进制数

二进制数转换为十进制数的方法比较简单,只需要将二进制数每一位上的数码与对应的位权相乘并求和,即可得到对应的十进制数。例如,二进制数(10110)2和(1011.101)2转换为十进制数的过程如下:

(10110)2=1×24+0×23+1×22+1×21+0×20=(22)10

(1011.101)2=1×23+0×22+1×21+1×20+1×2-1+0×2-2+1×2-3=(11.625)10

这里,我们使用下标10表示该数是十进制数。从上面可以看出,要将一个二进制数转换为对应的十进制数,只需要将整数部分与小数部分分别转换即可。

2.十进制数转换二进制数

十进制数转换为二进制数分为3种情况:十进制整数转换为对应的二进制整数、十进制小数转换为对应的二进制小数、一般的十进制数转换为二进制数。

1)十进制整数转换为二进制整数

十进制整数转换为对应的二进制整数有两种方法:除2取余法和降幂法。

(1)十进制整数转换为二进制整数──除2取余法。

下面先介绍除2取余法。所谓除2取余法,就是将一个十进制整数除以2,得到一个商和余数,并记下这个余数。然后再将商作为被除数除以2,得到一个商和余数,并记下这个余数。继续不断重复以上过程,直到商为0为止。每次得到的余数(0和1)分别是对应二进制整数的低位到高位上的数字。例如,十进制整数86转换为对应二进制整数的过程如图1.4所示。

(2)十进制整数转换为二进制整数──降幂法。

所谓降幂法,就是将十进制整数不断地减去与该整数最接近的二进制整数的位权,如果够减,则对应的二进制位上的数字应为1。否则,对应的二进制位上应为0。得到的差值作为新的被减数重新进行下一次计算,直到被减数为0为止。这样就得到了对应的二进制整数。例如,十进制整数93转换为对应的二进制整数的过程如图1.5所示。

图1.4 使用除2取余法将十进制整数转换为二进制整数的过程示意图

图1.5 使用降幂法将十进制整数转换为二进制整数的过程示意图

首先将93减去与之最为接近的二进制的位权,即26(=64),得到一个差值29,因为够减,所以对应的位记为1,即a6=1。然后将29作为被减数,令29减去下一个二进制数的权(即25),因为29<25(=32),所以a5=0。接着让29作为被减数,减去24(=16),因为够减,令a4=1。依此类推,直到被减数为0为止。这样就得到了二进制整数(1011101)2

2)十进制小数转换为二进制小数──乘2取整法

将十进制小数转换为二进制小数的方法是乘2取整法。所谓乘2取整,就是用2乘以十进制小数,得到一个整数和小数。然后继续使用2乘以小数部分,得到整数部分和小数部分。如此重复下去,直到余下的小数部分为0或者满足一定的精度为止。按照整数部分先后次序就构成了相应的二进制小数。

图1.6 十进制小数0.8125转换为二进制小数的过程示意图

例如,十进制小数(0.8125)10转换为二进制小数的过程如图1.6所示。

需要注意的是,在将一个十进制小数转换为对应的二进制小数的过程中,不一定都精确地转换为二进制小数。也就是说,在使用乘2取整法的过程中,小数部分不一定恰好等于0。在这种情况下,只需要转换到满足一定精度即可。

3)一般的十进制数转换为二进制数

如果要将一个包括整数和小数的十进制数转换为对应的二进制数,只需要将整数部分和小数部分分别转换,然后组合在一起就可构成相应的二进制数。例如,要将十进制数(86.8125)10转换为对应的二进制数,因为有

(86)10=(1010110)2

(0.8125)10=(0.1101)2

所以将以上整数和小数部分组合在一起,可得到

(86.8125)10=(1010110.1101)2

1.2.3 十六进制数与十进制数的相互转换

十六进制数与十进制数的相互转换分为十六进制数转换为十进制数、十进制数转换为十六进制数。在转换之前,先来认识一下十六进制数。

1.十六进制数

十六进制数有16个数字符号:0~9,A,B,C,D,E和F。其中A,B,C,D,E,F分别表示十进制数的10,11,12,13,14,15。十六进制数的特点是逢16进1。例如,两个十六进制数(6A)16和(29)16的相加过程如图1.7所示。

图1.7 十六进制数6A和29的相加示意图

首先将末位数A与9相加,A对应十进制数中的10,9对应十进制数中的9,相加后等于十进制数的19,对应于十六进制数的(13)16。其中3作为两者之和的低位,1作为进位,因此高位上的和为9。这样,(6A)16和(29)16的和为(93)16

2.十六进制数转换为十进制数

与十进制数、二进制数一样,十六进制数也有位权。十六进制数中的位权是16的次幂。首先利用十六进制数中的每一位上的数字乘以对应的位权,然后求和就可得到相应的十进制数。例如,十六进制数(2AD.3C)16转换为对应的十进制数的过程如下:

(2AD.3C)16=2×162+10×161+13×160+3×16-1+12×16-2=512+160+13+0.1875+0.046875=(685.234375)10

在将十六进制数转换为十进制数的过程中,需要先将十六进制数表示成对应的十进制数。如上面的A表示为10,D表示为13,C表示为12。从上面可以看出,十六进制数转换为对应的十进制数是容易的,而将十进制数转换为对应的十六进制数时需要将其分为整数部分的转换和小数部分的转换两部分。

1)十进制整数转换为十六进制整数──除16取余法

将十进制整数转换为十六进制整数时可以采用除16取余法。具体方法为:将要转换的整数除以16,得到相应的商和余数。然后让商作为被除数,继续除以16,得到新的商和余数。依此类推,直到商为0为止。将每次得到的商依次排列,就是相应的十六进制整数。例如,十进制整数(763)10转换为十六进制整数(3FB)16的过程如图1.8所示。

图1.8 十进制整数(763)10转换为十六进制整数(3FB)16的过程示意图

与十进制整数转换为二进制整数类似,十进制整数转换为十六进制整数也可以采用降幂法。

2)十进制小数转换为十六进制小数──乘16取整法

将十进制小数转换为十六进制小数的方法为乘16取整法。具体方法为:将要转换的十进制小数乘以16,得到的整数部分作为十六进制小数的十六进制数码。将小数部分继续乘以16,得到的整数部分作为下一个十六进制数码。继续让小数部分乘以16,依此类推,直到小数部分为0或者满足一定精度为止。得到的整数部分依次排列就是相应的十六进制小数。例如,十进制小数(0.93125)10转换为相应的十六进制小数的过程如图1.9所示。

在将十进制小数(0.93125)10转换为十六进制小数时,由于不能将其精确地转换为相应的十六进制小数,所以这里将小数部分精确到了小数点4位。

3.一般的十进制数转换为十六进制数

与十进制数转换为二进制数类似,将十进制数转换为十六进制数时,需要将整数部分和小数部分分别转换,然后合并在一起即可。例如,要将十进制数(763.93125)10转换为十六进制数,需要先将整数部分(763)10和小数部分(0.93125)10分别转换,因为有

图1.9 十进制小数(0.93125)10转换为十六进制小数的过程示意图

(763)10=(3FB)16

(0.93125)10=(0.DD66)16

所以有(763.93125)10=(3FB.DD66)16

1.2.4 十进制数与八进制的转换

十进制数转换为八进制数、八进制数转换为十进制数的方法与前面的十进制数与二进制数、十进制与十六进制数的转换方法类似。在转换之前,先来认识一下八进制数。

1.八进制数

八进制数有8个数字符号0~7,它的特点是逢8进1。例如,两个八进制整数(32)8和(51)8相加的过程如图1.10所示。

图1.10 (32)8和(51)8相加过程示意图

首先将末位上的2和1相加,得到3,作为结果的最低位。然后将高位的3与5相加,得到十进制数8。因为十进制数的8对应八进制数的10,其中0作为结果的第2位,1作为结果的最高位,所以有(32)8+(51)8=(103)8

2.八进制数转换为十进制数

八进制数的位权是8的若干次幂。将八进制数上的数乘以位权并累加求和就得到了对应的十进制数。例如,八进制数(361.25)8转换为十进制数的过程如下:

(361.25)8=3×82+6×81+1×80+2×8-1+5×8-2=192+48+1+0.25+0.078125=(241.328125)10

3.十进制数转换为八进制数

将一个一般的十进制数转换为八进制数,也需要将整数部分和小数部分分别转换,然后合并在一起。整数部分的转换可采用除8取余法或降幂法,小数部分的转换可采用乘8取整法。例如,要将一个十进制数(792.24)10转换为八进制数,转换过程如图1.11所示。

图1.11 十进制数转换为八进制数的整数部分和小数部分的转换过程示意图

由图1.11可以得到(792.24)10=(1430.1727)8,精确到了小数点后4位小数。

1.2.5 各种计算机进制数的转换

在计算机中,二进制数与十六进制数、二进制数与八进制数的相互转换是非常容易的。下面分别介绍十六进制数与八进制数转换为二进制数、二进制数转换为十六进制数与八进制数。

1.十六进制数与八进制数转换为二进制数

下面先给出计算机中常用的各种进制数的表示,如表1.1所示。

表1.1 计算机常用进制数的表示

要将十六进制数转换为二进制数,需要使用4位二进制数表示1位十六进制数。例如,十六进制数(2B3C.D8)16转换为二进制数的过程如图1.12所示。其中,最高位的2个0和小数点后的最后3个0可以省略。这样就有(2B3C.D8)16=(10101100111100.11011)2

与此类似,将八进制数转换为二进制数时,需要使用3位二进制数代替1位八进制数。例如,八进制数(241.36)8转换为二进制数的过程如图1.13所示。省略最高位0和最后的0,就有(241.36)8=(10100001.01111)2

图1.12 十六进制数转换为二进制数的过程示意图

图1.13 八进制数转换为二进制数的过程示意图

2.二进制数转换为十六进制数与八进制数

要将二进制数转换为十六进制数,需要从二进制数的小数点开始,从右往左时,应将每4位分为一组,当最后一组不够4位时,要在左端补上0使其构成4位;从左向右时,也要将每4位分为一组,当最后一组不够4位时,要在最右端补上0使其构成4位。利用表1.1中的二进制数与十六进制数的对应关系可以得到对应的十六进制数。例如,二进制数(10110110010010.01011)2转换为十六进制数的过程如图1.14所示。

从图1.14中可以得到(10110110010010.01011)2=(2D92.58)16。基于同样的道理,将二进制数转换为八进制数时,需要从二进制数的小数点开始,从右到左时,每3位分为一组,从左到右时每3位分为一组,最后一组不够3位时,补上0使其构成3位。通过表1.1中的二进制数与八进制数的对应关系可得到相应的八进制数。例如,二进制数(10011001110.10)2转换为八进制数的过程如图1.15所示。从图1.15可以得到(10011001110.10)2=(2316.4)8

图1.14 二进制数转换为十六进制数的过程示意图

图1.15 二进制数转换为八进制数的过程示意图

1.3 计算机中数的表示

在计算机中,从数的符号角度看,可以将其划分为正数和负数。从小数点所处的位置来看,可以将其划分为定点数和浮点数。对于带符号数,一般采用原码、补码、移码等表示。本节主要讲解计算机中的正负数表示、各种码制的表示──原码、补码、移码。

1.3.1 计算机中的正数与负数表示

在计算机中,要处理的数往往是带符号数,即包括正数和负数的数。例如,+101、-110、-0.1011都是带符号数。但计算机中的数的正负号不是使用+和-来表示,而是使用1和0来表示的。通常,一个数的最高位是符号位,表示数的正和负,其中1表示负数,0表示正数。例如,(01011)2表示二进制数+1011,(111.011)2表示二进制数-11.011。

如果用1个字节(即8个二进制位)表示一个带符号整数,因为最高位是符号位,数值位为7位,所以其表示范围是-127~127。对于无符号数来说,数值位为8位,表示范围为0~255。例如,十进制整数+87和-87的二进制表示为

(+87)10=(01010111)2

(-87)10=(11010111)2

其中,二进制数的最高位为符号位。如果用两个字节表示一个二进制数,则对于带符号整数来说,它的表示范围是-32767~32767;对于无符号整数来说,它的取值范围是0~65535 (216-1)。例如,十进制整数+65和-65的二进制表示为

(+65)10=(01000001)2

(-65)10=(11000001)2

1.3.2 原码、补码

在计算机中,通常使用原码、补码来表示二进制数。其中,原码比较直观,补码主要用于进行加减运算。

1.原码

在使用原码表示二进制数时,最高位是符号位,其中0表示正数,最高位的1表示负数。其他位是数值位。在原码表示法中,8个二进制位所能表示的最大整数是01111111,即(-127)10,最小整数是11111111,即(127)10。而0有两种形式00000000和10000000,分别表示+0和-0。下面来看两个二进制整数(00110110)2和(00001101)2的相加运算过程,如图1.16所示。

从图1.16可以看出两个正数相加,结果是正确的。下面来看一个正数和一个负数相加的运算过程,例如,(10010010)2和(00111011)2相加的过程如图1.17所示。

图1.16 两个二进制数相加的过程示意图

图1.17 一个正数和一个负数的原码相加过程示意图

(10010010)2对应十进制数-18,(00111011)2对应十进制数59,相加后结果应该为41,显然图1.17两个原码相加的结果是错误的。在计算机中,两个数相减实际上也是两个数相加的过程,即先将减数符号取反,然后将两个数相加。例如,将(01001010)2和(00110011)2相减,即是将(01001010)2和(10110011)2相加,它们的运算过程如图1.18所示。其中,(01001010)2对应十进制数74,(00110011)2对应十进制数51,两个数相减应该为23,图1.18的运算结果显然是错误的。

由此可以看出,在计算机中使用原码表示时,两个异号数相加和两个同号数相减的结果是不正确的。为了能够正确地进行加减运算,需要使用补码。

2.补码

在计算机内部,在进行加减等算术运算时,通常采用补码形式进行。正数的补码与原码相同,负数的补码是在原码的基础上,除符号位外,首先对各位按位取反,然后在最低位上加1。例如,十进制数(21)10的原码为00010101,补码也是00010101。十进制数(-21)10的原码是10010101,为了得到它的补码,需要先让数值位按位取反,得到11101010,然后在最低位加1,得到11101011。如图1.19所示。

图1.18 两个正数的原码相减过程示意图

图1.19 原码转变为补码的过程示意图

下面来看两个同符号数的补码的相加运算过程。例如,如果要将十进制数-56和-78相加,必须先将-56和-78转换为补码。它们的相加过程如图1.20所示。

图1.20 -56和-78的补码相加过程示意图

两个数相加后得到补码(10101100)2,向最高位的进位1被舍弃。那该补码又对应十进制哪个数呢?这时需要将补码转换为原码,转换完后就很容易看出来了。将补码转换为原码的方法与将原码转换为补码的方法是相同的,即除了符号位外,首先将补码按位取反得到(11010011)2,然后最低位加1,就得到了原码(11010100)2,很容易看出,它对应的十进制数是-84。结果显然是正确的。

下面来看两个异号数的补码相加是否正确。例如,十进制数-18和59相应的补码相加的过程如图1.21所示。从图1.21可以看出,补码(00101001)2的原码是(00101001)2,对应十进制数41,显然结果是正确的。

下面再来看两个同号数的相减运算结果是否正确。例如,十进制数74减去51的补码运算过程如图1.22所示。从图1.22中可以看出,两个同号数相减的补码运算也是正确的。

综上所述可以得出以下结论:在计算机中的整数的相加、相减运算过程中,使用补码进行运算可以得到正确的结果。

图1.21 -18和59的补码相加的运算过程示意图

图1.22 74减去51的补码运算过程示意图

注意

在用补码表示二进制数时,只有一个0,用补码表示为00000000。如果用一个字节表示补码,则补码的表示范围是-128~127。其中,-128的补码为10000000,127的补码为01111111,-1的补码为11111111。

1.3.3 浮点数

前面讲解的二进制数(包括小数和整数)都属于定点数,也就是小数点不变的数。其中,整数可以看成小数点位于最低位的后面。在计算机中,还有一种数(称为浮点数),也就是小数点位置不固定的数。

1.浮点数的构成

浮点数由两个部分构成:尾数和阶码。其中,尾数用补码表示,阶码用移码表示。因此,浮点数可以表示为

F=S×2N

式中,F表示浮点数,S表示尾数,N表示阶码。这类似于我们以前学过的科学计数法。尾数S一般是定点小数,而N一般是定点整数。其中,定点小数S中小数点后第一位一般是非零数字1。浮点数F的结构示意图如图1.23所示。

图1.23 浮点数F的结构示意图

为了运算方便,通常使用补码表示尾数,使用移码表示阶码。尾数决定了浮点数的精度,阶码决定了浮点数的表示范围。

2.移码

移码就是符号位取反的补码。移码通常用做浮点数的阶码。在使用移码表示的二进制数中,1表示正数,0表示负数。例如,几个二进制数的补码与移码的对应关系为

(29)10=(00011101)补码=(10011101)移码

(-87)10=(10101001)补码=(00101001)移码

(-1)10=(11111111)补码=(01111111)移码

(0)10=(00000000)补码=(10000000)移码

(-127)10=(10000001)补码=(00000001)移码

如果用一个字节表示移码,则移码的表示整数范围是-128~127。它的表示范围与补码的表示范围相同,只是两者的二进制符号位刚好相反。定点数的移码也可以进行相加和相减等运算,只是运算结果后得到的不是移码,需要将符号位取反后才能得到相应的移码。例如,十进制数(-79)10和(2)10对应的移码相加运算过程如图1.24所示。

图1.24 十进制数-79和2的移码相加运算过程示意图

将(-79)10和(2)10相加后得到的结果10110011并不是移码,而是补码。除了符号位之外,对其他位按位取反并加1,可得到原码11001101,即十进制数(-77)10。由此可见结果正确。

在计算机内部,移码可以与补码一样进行算术运算。在运算完毕之后,再将结果转换为原码,这样就很容易得到对应的十进制数了。

3.浮点数的表示

我们已经知道,一个浮点数由尾数和阶码构成。其中,尾数用补码表示,阶码用移码表示。例如,一个十进制小数(-321.25)10,用32位二进制数表示时,尾数占用24位,移码占用8位。转换时,先将(-321.25)10转换为对应的二进制数,即(-321.25)10=(-101000001.01)2,表示为科学计数法,即为(-0.10100000101)2×29。然后分别将尾数部分和移码部分分别进行转换即可。

1)尾数部分

尾数部分是(-0.10100000101)2,转换为补码形式后如图1.25所示。

图1.25 尾数部分转换为补码形式的示意图

2)阶码部分

阶码部分是(+9)10,转换为移码后如图1.26所示。

图1.26 阶码部分转换为移码的过程示意图

将两者合并在一起,就得到了浮点数(-321.25)10的表示形式,如图1.27所示。

图1.27 浮点数(-321.25)10的表示形式

1.4 小结

本章主要讲解了程序设计的基础知识。本章的开始部分主要介绍了选择C语言的好处、C语言的特点及如何学好C语言。本章然后介绍了计算机常用的二进制数、十进制数、八进制数、十六进制数的相互转换。本章最后又介绍了计算机中数的表示,在计算机中常用的原码、补码和移码。其中,原码最直观,非常容易转换为十进制数;补码常用来进行算术运算;移码常用来表示浮点数的阶码。本章是C语言程序设计的基础,通过学习本章,可以了解到计算机的内部数据的表示形式,也可为后面的学习打下坚实基础。