帮我看下这个c语言程序改错哪里错了,就是输入十个数字,去掉最高和最低求平均,老师说不能用数组,拜托了。

查看: 53518|回复: 230
帮助新手学习单片机数码管电子钟:原理,c程序详解
这个帖子意在帮助有兴趣的初学者理解单片机驱动最基本的LED数码管电子钟的硬件原理,以及如何用c编程实现。图和代码是实打实的东西。这些内容对于仔细学过单片机的和会搞的人就不必浪费时间了。为了保持连续性,请勿跟帖。
实物图:重点是显示效果如下:
clock.jpg (18.64 KB, 下载次数: 73)
11:46 上传
硬件主要部分电路:
FIG.JPG (44.58 KB, 下载次数: 121)
11:46 上传
从以下几个部分解析:
1,8段led数码管的原理
2,单片机如何动态驱动多个数码管
3,单片机怎样构成电子钟
4,怎样用c语言编程
5,程序代码详解
1:8段led数码管的原理:再复习一下
& & LED::发光二极管大家都用过吧?直流电源,串上限流电阻,再串上LED就成了一个小电灯,
& &8段数码管就是把8个LED按数字“8”的笔画安排,使各个LED按要求发光就形成数字或其他符号了。见下图:
txt1.JPG (31.8 KB, 下载次数: 88)
11:46 上传
这个8字数码管由7个LED构成笔画,另一个LED为小圆点。这8个笔段通常以a b c d e f g h 表示,这个位置是固定的。
用电路图表示在右边。注意,数码管都是由笔段电极和公共电极构成的,而按制造时的内部接法不同分为共阳极和共阴极两种,看图就明白。老式单片机,吸收电流能力较强而吐出电流较弱,用共阳极接法多些。 新式单片机两种都可以用。
对于共阳极接法,单片机的口线通过限流电阻和LED的笔段电极--阴极连接,正好一个8位口对应一个数码管的各段,公共阳极因电流较大,超出单片机的驱动能力所以另外用一个晶体管控制,这个晶体管的基极则另外用一个口线控制:显然只有这点为0才能使得数码管发光。设a b c d e f g 均为低,h为高,则显示不带点的8字。
共阴极数码管则相反,只有笔端电极-阳极为高,公共电极:阴极为低时才能显示。显然这个制作用的是共阴极数码管。下面,列出共阴极管显示各个数字时笔段电位高低的关系,也就是让单片机的一个8位口设为P0口,如原理图,和笔段对应时的驱动二进制数据对应的16进数和显示的字形:举例说显示2,要让P0口输出十六进数0X5B
P0.7& & P0.6& & P0.5& & P0.4& & P0.3& & P0.2& & P0.1& &P0.0
h& && && &g& && && & f& && && &e& && && &d& && && &c& && && &b& && && &a
0& && && &0& && && &1& && && &1& && && &1& && && &1& && && &1& && && &1& && && &==0X3F&&显示“0“
0& && && &0& && && &0& && && &0& && && &0& && && &1& && && &1& && && &0& && && &==0X06& && && &&&1
0& && && &1& && && &0& && && &1& && && &1& && && &0& && && &1& && && &1& && && &==0X5B& && && &&&2
0& && && &1& && && &0& && && &0& && && &1& && && &1& && && &1& && && &1& && && &==0X4F& && && &&&3
0& && && &1& && && &1& && && &0& && && &0& && && &1& && && &1& && && &0& && && &==0X66& && && &&&4
0& && && &1& && && &1& && && &0& && && &1& && && &1& && && &0& && && &1& && && &==0X6D& && && &&&5
0& && && &1& && && &1& && && &1& && && &1& && && &1& && && &0& && && &1& && && &==0X7D& && && &&&6
0& && && &0& && && &0& && && &0& && && &0& && && &1& && && &1& && && &1& && && &==0X07& && && && &7
0& && && &1& && && &1& && && &1& && && &1& && && &1& && && &1& && && &1& && && &==0X7F& && && && &8
0& && && &1& && && &1& && && &0& && && &1& && && &1& && && &1& && && &1& && && &==0X6F& && && && &9
0& && && &1& && && &0& && && &0& && && &0& && && &0& && && &0& && && &0& && && &==0X40& && && &&&—
此外还可以显示A b&&c&&d& &E& & F& & G& & H& & L& & P等,就不列举了。
另一点,上面小点h始终不显示 ,怎样显示它呢?以后用到再说,一般用它较少。好了休息一下。
<p id="rate_09" onmouseover="showTip(this)" tip="赞一个!&经验 + 10 点
" class="mtn mbn">
<p id="rate_86" onmouseover="showTip(this)" tip="&经验 + 10 点
" class="mtn mbn">
<p id="rate_98" onmouseover="showTip(this)" tip="精品文章&经验 + 10 点
" class="mtn mbn">
用C语言编程
有关C语言的书籍很多,其实也用不着我来多嘴,不过是想救自己自学C的一些体会来说道说道,对老鸟毫无意义,对新手可能有所帮助,要深入钻下去,还是看书吧,我这里是尽量用通俗理解说清楚一点,即使一下还不会用C编程,如果能看明白C的代码也好啊。事情总是一步一步,首先别怕,人家都能做出来,做出来就是给人用的,只要花力气,一定能学会。
1,为什么要用C。原因很多,既然大家都用,自然有好处,最大的好处就是它的基本语法对于不同的单片机都能用,只是具体细节不一样,而汇编对于不同的单片机完全没有通用性,其他就不多说了。
2。快速了解C语言
2。1.构成C语言的主要基本零件:
数据:数据类型,常量和变量
运算符,表达式:用他们来操作数据
函数:通过运算符、表达式和数据一起来完成所需要的功能。注意C语言只有函数,没有子程序。
2.。2 C语言的基本框架
下面逐一说明。
C语言的数据,计算机语言处理的就是数据,C语言也一样。
在程序中不变的数据是常量,在程序中可改变的数据是变量
为了限定常量和变量的取值范围,我们要用数据类型来说明。在51的C,简称51C,数据类型有:
字符型:char& &:长度1字节(8位二进数)分为
有符号字符型&&signed char&&取值范围-128~+127
和无符号字符型 unsigned char 取值范围 0~255
默认为有符号型(不用加signed)
使用最多的是无符号型,必须加以unsigned 说明。例如 unsugned sec:这里sec就是一个变量,它是无符号字符型的,取值最大只能到255,如果写出sec=500;意思是给sec赋以数目500,那就错了。对于更大的数,我们使用
整型:int& &:长度2字节(16位二进数)和字符型类似也有
有符号整型 (默认)取值范围-3
无符号整型&&unsigned int& & 取值范围 0~65535& & 例如unsigned int&&sou=7850;正确,变量sou既然是无符号整型,它可以达到7850没问题。再大的数据就要用
长整型 :long& &:长度4字节 (32位二进数,赶上电脑了)也有有符号和无符号两种
有符号长整型&&(默认) 取值-~
无符号长整型&&unsigned long&&取值 0~ 为了表示小数,还有
浮点型&&float&&:长度4字节,可表示7位有效十进数。运算中还有
指针型 *&&长度1~3,它还具有上述类型。 51C还有一些独有的:
位标量 bit 只能取值0或1 ,例如 bit&&yn 只可以 yn=0;或 yn=1,别看它简单,程序中这个很方便
可寻址位 sbit 用它来访问可寻址位,如sbit&&P11=P1^1;用P11来访问P1口的P1.1,这个也用得多。
此外还有特殊故功能寄存器的sfr 和sfr16使用较少就不说了,使用最多的还是
unsigned char 和 unsigned int
常量:分为
整型:例如 123& &&&-8& &0xf9&&0b
都是程序总就是以数字出现
浮点型& &例如& & 123.45& &75e3
字符型& &例如‘A'& & 'a'& & 有单引号,注意C的大小写有区别,不可随意
字串型&&例如&About U &&&&V& 用双引号,V加了双引号尽管只有一个字符也是字串型的
变量类型上面都说了,在程序中,我们可以自己定义一个变量,然后使用它进行运算。
例如&&先定义unsigend min ;unsigned sec
然后使用:min=sec-60等等。决不可未加定义就直接来一个sta=7..
完整地说,要定义一个变量,得给他戴上3顶帽子:
存储种类&&数据类型&&存储器类型&&变量名
例如&&static&&unsigend char&&data&&num, 变量num是静态的,无符号字节 内部数据存储器
一般不用这多,存储种类就默认为auto不用写,unsigned char必不可少,存储器也不用指定。除非用到外部存储器等等,就不多说了
虽然说,用一个或一些字母就可以表示变量名,但是也不可任意为之,因为有些字是专用的,例如不能用数据类型int做变量名,类似的还有一些程序语句里的关键字,都不能做变量名,变量名除了大家常用的例如i 、 j& &还可以用具有意义的便于记住,或者用汉语拼音也行,还要注意,变量名可以以字母打头,也可以用下划线打头,但是不可以数字打头。例如
unsigned&&& &&&unsigned _23都可以,unsigned 5d就不行。
下面说C里面的运算符,表达式和语句,用他们来操作变量、常量,得到我们所需的结果。
[ 本帖最后由 wey05 于
17:23 编辑 ]
市面上买到的数码管,除了尺寸不同(驱动电流不同)以外,有单个的,双联体的,四联体的。单个的就是8个段电极和1个公共电极了,联体的,各个数码管的对应笔段内部已经连接好,也只有8个脚引出,各位数码管的公共电极则分别引出,例如4联的,一共有12根引脚。那又有什么区别呢?
单个的数码管,可以用于静态显示,也可用于动态显示。
多联的数码管只能用于动态显示。
所谓静态显示,就是数码管段电极接到一个硬件锁存器上,单片机控制口如P0,顺序输出各个数码管对应的驱动码,驱动码被分别锁存起来,再推动数码管。只要锁存器不变,数码管显示就不变。如有8个数码管,就有8个锁存器,它们同时可以显示8位不同的数字。这样的显示,明亮而稳定。但缺点就是硬件电路复杂了。但如果采用 串入并出的锁存器,可以减少单片机所需的口线,对于口线紧张的情况(如使用2051)有时还是要采用的。下面说说
2。单片机如何动态驱动多个数码管
所谓动态显示,是让几个数码管轮流显示,每一时刻只有一个管子点亮,其余都必须熄灭。方法是首先把所有数码管的公共电极电路都断开,这时大家都不显示,然后单片机输出字段数据,(例子是P0口),然后开通需要显示的数码管公共电极,这个管子就亮了。其他管子尽管也加上了相同的字段数据,但是他们的公共电极都还关着,当然也不会亮。拿这个例子来说,如果在P0口输出数据0X4F,也就是,然后P2口输出0X01也就是也就是P2口只有P2.0为高,其余都是低,那么Q8导通,最右一个(最低位)数码管就点亮:最低位或个位就显示“3”。
接下来P2口输出0,这个管子就熄灭了。大家一片黑。P0口输出0X5B。(数字“2”的笔段码),然后P2口输出0X02,也就是,P2.1为高。.于是Q7导通,十位就显示数字“2”。
就这样一位位地显示下去,直到最高位,然后又从最低位开始。周而复始。只要每个管子点亮的时间较短,整个显示过程很快,那么由于人眼跟不上数码管亮灭的快速的变化,就可以显示出完整的数字了
一般,每个数码管只要点亮4毫秒,8个管子从头到尾依次亮灭一遍需要32毫秒多,1秒钟能刷新30多次,这样虽然也还有少许闪烁感,但显示已经很清晰了。
在动态显示的情况,数码管愈多则在完整的显示周期中一个管子分得的时间就越少,而使平均亮度降低为了显示清晰,管子的限流电阻就要减小,驱动电流就要增加。所以显示位数也不能够增加太多。一般8位是没有问题的。
上上面举例是从右往左依次显示,反过来行不行呢?当然可以。不过还要结合具体的应用条件决定,这个等到编程时再说。
<p id="rate_56" onmouseover="showTip(this)" tip="好文!&经验 + 10 点
" class="mtn mbn">
我想搞个控制13个LED管的,,,不知道什么搞?
我想搞个控制13个LED管的,,,不知道什么搞?
好了,现在如果用8个数码管显示电子钟,各个数码管承担的任务就可以按照我们的要求安排了。以本实例说,从右至左依次是:秒个位,秒十位,分个位,分十位,横线,小时个位,小时十位,空白备用。比如说要显示45秒,那并不是说在P0口输出数据45然后依次开通秒个位和十位,而是:先把数字45分解为两个单独的数字4和数字5,然后找到他们所对应的共阴极数码管显示码:4对应的是16进数0X66(2进数),5对应的是0X6D(2进数),然后在P0口输出数字5的显示吗0X6D,P2口输出0X01,晶体管Q8开通于是最低位数码管显示“5”。持续一小会例如4毫秒,P0口输出0X00,不显示了,乘机P2口输出0X02(2进数)开通Q7,再给P0口输出数字4的显示码0X66,此位显示4了,再持续4毫秒。。。于是时分秒就动态显示出来了。
然而我们做的是电子钟,时分秒要随时间按规律变化,因此下面就说说
时间信号源
单片机电子钟一般可以使用专用实时钟电路作为时钟源例如最常用的DS1302,优点是走时精准,加上后备电源(小电池或法拉电容)在单片机停电时依然正确走时,还有日历功能。不过它牵涉到对电路编程,这次就不说了。以后如果大家有兴趣专门再讲。再一个方法就是利用单片机自身的资源,通过软硬件结合,产生秒信号。有了秒信号,分和时就好办了。正好这个52单片机内部有3个定时-计数电路:定时计数器T0,T1,T2。作为定时器使用的情况下,它按照单片机的工作节奏:机器周期计数,而机器周期是依赖于精准的石英振荡器产生的振荡周期的,传统的AT89S51单片机,用12兆晶体时,机器周期是1微秒。STC89C52RD,用12兆晶体时则可以使用1微秒或0.5微秒两种。不过由于STC单片机为了保证串口编程速率没有误差,使用的是11.0592兆赫的晶体,这样机器周期就要长一点。这个必须须记住的。
3.单片机怎样构成电子钟
任何一个钟,手表也好,大本钟也好,有三个部分:心脏=走时机构,脸面=显示装置,再加上调节装置:对表。
以上所说,是数码管的基本显示原理。下面再仔细说说具体到这个电子钟的显示安排。
由图可知,8个数码管从右到左,分别是秒个位,秒十位;分个位,分十位;横杠;小时个位,小时十位。还剩下最左一位备用。
按现在的动态显示方式,是从左到右一个个轮流点亮、熄灭数码管,周而复始。对于具体的一个时间值例如10时35分24秒,在显示前先要把时分秒分别拆开为十位数和个位数,要显示哪个数,除了开通对应数码管的公共阴极,还要给字段电极加上这个数字的显示码。对照原理图,过程可以是这样的:
首先P2口为0,当然P2.0到P2.7都是0,数码管全部关断,漆黑一片,准备显示。
P2口为0x01,这时仅仅P2.0为1,Q8开通。然后P0口输出0X66——数字4的显示码,最低位立即显示4。维持数毫秒,然后P0口输出0,管子灭了。趁机把P2口输出0X02,仅仅P2.1为1,Q8关闭,Q7开通,然后P0口输出数字2的显示码0X5B,秒十位显示2了。就这样,轮流把P2口的各位输出1,然后P0口加上显示代码并维持一会儿,最后就动态地显示出完整的时间数值了。注意,在转到下一个显示位置前,先关掉显示代码。下面说说怎样用单片机的定时器取得时间源。
继续~~~~~~~~~~~~
怎样使用定时/计数器:16位定时器
51单片机有2个16位定时计数器C/T0和C/T1,52机还多一个C/T2有控制串口的作用先不管他,C/T0 和CT1既是计数器又是定时器,怎么说?它的工作方式可以人为改变的,如果它输入的是P3.4引脚上的脉冲,那么就对这些脉冲计数计数值送到16位(二进位)寄存器TH0(高8位)TL0(低8位)中,如果它输入的是单片机的机器周期振荡脉冲tm那就成了定时器。下面用一个简化图表示它是如何工作的
timer.JPG (28.41 KB, 下载次数: 73)
00:50 上传
外部脉冲计数器这次用不上,用的是定时器,图中画的是C/T0,C/T1也是一样,但是C/T0和C/T1的工作都是受特殊功能寄存器TCON和TMOD控制,这个只有一套,然而计数器则各是各的图上画的是TH0和TL0,对于C/T1则它有TH1和TL1。
我们知道,机器周期是石英晶体振荡器分频而来,这个东西是比较精准的,频率误差一般在5-20ppm甚至更低,51是晶振12分频,STC机也可选6分频,甚至有单周期(不分频)的高速型,以12分频为例,假设晶振是12兆的,那分频后的机器周期tm就是1微秒,如果计数4000次就是4毫秒,用它来控制前面说的数码管显示倒是不错,而且计数40000次就是40毫秒,经过250个40毫秒是多少呢?1秒!干嘛是40毫秒呢,一次连续计数1000000,不就是1秒吗?不行!因为16位计数器最大才能计数65536,计数40000还在其范围内,要计数1百万那相差太远了。
要是定时计数器CT0(CT1也一样)工作起来,就要正确设定控制它的特殊寄存器TMOD 和TCON,还要给TH1、TH0正确的预置值,最后再使它工作。先看TMODE和TCON:如上图所示,TMOD的高4位控制C/T1,低4位控制C/T0.就以C/T0说事,
最低位M1M0是工作方式选择,这两位00:方式0,13位计数,01:方式1:16位计数。10,方式3,自动加载8位计数,11,方式4;两个8位计数器,现在我们使用16为计数器,所以M1为0,M0为1.
C/T为0时机器周期脉冲可以进来计数,那好,把它置0.
GATE位为0时允许计数开关闭合。所以置它为0。现在只要把TCON寄存器的TR0置位1,计数通路开关就闭合上机器周期脉冲就能够进入计数TL0-TH0。.可见设定好这两个寄存器的相关位,定时器就可以跑起来。但是定时时间长短,就要给TH0]TL0正确的预置值。
假如预置值是0,那么显然定时时间是65.536毫秒(12兆晶振)预置值是65534那么一个脉冲就把它装满再来一个脉冲就溢出中断了,两个脉冲之间的时间就是1微秒。一般来说,可以用以下公式计算预置值:
TH0=(65536-tx*f/6
TL0=(65536-tx*f/6,
上式中,tx是定时时间,微秒;f是晶振频率,赫兹,特别是STC使用的晶振为了满足串口下载0误差,通常是的。
顶,终于快到程序部份了..
使用定时器的要点
如上所说。使用定时器就是1:设置相关寄存器,2:设定预置值。例如,使用T0作为16位定时器,定时时间40毫秒,T1作为16位定时器,定时时间4毫秒。
TMOD:它的各位应该是:
D7—-T1的GATE:0
D6— T1的C/T:0
D5— T1的M1 :0
D4— T1的M0 :1(16位计数M1:0 M0:1)
D3—-T0的GATE:0
D2— T0的C/T:0
D1— T0的M1 :0
D0— T0的M0 :1(16位计数M1:0 M0:1)
综合起来,TMOD的设置值是进数就是0X11。
TCON:只有TR1控制T1的启动计时,TR0控制T0的启动计时,什么时候需要启动计时器,就把这两位分别置1即可,要停止计时,就把这两位分别置0。.
定时器的中断:当定时器使用方式1、方式2的时候,一旦TH1、TL1或TH0、TL0装满,再加一个计时脉冲就会“溢出”,TH1 TL1或TH0 TL0会被清除掉,先前放进去的预置值也没有了,这时定时器会使得TCON的TF1或TF0置位为1,通常我们都是把定时器设为中断工作方式,这时会引发定时器中断,单片机会暂停其他程序的处理转而处理中断程序,一般中断程序是很小的,编写时主要是重新充填预置值,以及发出检测信号等,随即结束中断程序还原先前运行的程序。
为了使用定时器中断,还必须在启动定时器(置位TR1或TR0)之前,设置好中断控制寄存器IE。在IE中,只需设置ET0位:T0中断、ET1位:T1中断和EA位:总中断即可。
寄存器设置就这么多,下面按要求,T0定时40毫秒,使用11.0592晶振,按以上算式:
TH0=(*02=0X70;
T1定时4毫秒,晶振相同则
TH1=(*01=0XF1
TL1=(*04=0X9A
实际上编程时这样的常数计算可交由编译器去完成,所以只要列出式子便可。
最后,再说说调校按键的原理:
从原理图上看P1口的P1.2和PI.4分别接有按键K1、K2用来调整小时和分钟,为简单计省去了调秒的按键。
它的工作是:首先PI口输出高电平,按后检查P1.2和P1.4的引脚:如果为高,没有按键,如果为低,有键按下。通常的按键处理,是延时10毫秒再检测电位,目的是避开由于按键未达到稳定状态会因弹跳发生的多次短暂通断。这个例子就是利用数码管扫描的时间来达到延时目的:亮一个数码管后检测按键,如发现按下就计数一次,下一个数码管再捡测,辱国检测5次都是按下则确认为按下,转去把小时或分钟加1。至此,数字钟的基本原理就说完了,下次就看看怎样通过C语言编程把这些功能都组织起来让它有规律地工作,首先我们需要了解程序流程。
好!一个老师!
程序流程是我们编程的依据,按照流程可以理解单片机的工作循环,否则上来就是一堆代码,要看清楚不是很容易的。单片机都是循环工作。主要理解他是怎么循环的,看下图:
bloc.jpg (46.22 KB, 下载次数: 98)
18:06 上传
左边就是主要的工作循环。开机后,首先初始化,给各个变量赋值,其中还有一个自检过程。以及设置定时器并加以启动。
然后进入程序循环。在循环中,单片机要顺序查询显示时间4ms到了没有,到了就要移入下一位数码管显示,移位后再检查40ms定时。否则直接检查40ms定时。如果定时已到,就处理时间累加以及时分秒递增。然后查询按键K1、K2就这样周而复始。而两个定时器在初始化启动后,就进行定时,时间一到就触发定时中断,设置定时标志以供主循环查询,同时还要恢复预置值并进入下一轮定时。
按键检测,先看电平高低,高:无键,低:有键。这里不另设延时,而按照显示移位循环5次后还为低,就是确有按键,转入按键处理。整个流程还是比较简单的,
插个队。顶你一下讲得如此详细。
TH0=(65536-tx*f/6
TL0=(65536-tx*f/6,
a lot simpler than that:
#define F_XTAL ul //xtal for a 12-state 8051
#define MSB(word_t) ((word_t) && 8) //msb of a word
#define LSB(word_t) ((word_t) & 0x00ff) //lsb of&&a word
period=(F_XTAL / ) * //period for the timer
TH0 = MSB(-period);&&//load the msb for a count-up timer
TL0 = LSB(-period);&&//load the lsb for a count-up timer
you will also find that 1) you can improve long-term timing accuracy by a) using auto-reloading (mode 2); or b) using the Roman Black approach - 2) you need to implement a way to correct for xtal inaccuracy.
hope it helps.
怎么不用51单片机的T2定时器?16位自动重载方式定时的误差为0.
微信:caoyin513 E-mail:
Powered byC语言程序设计2(1-4)_学霸学习网
C语言程序设计2(1-4)
C 语言程序设计主编 主审 张志强 周克兰 杨季文 0内容简介II内容简介本书全面、系统地介绍 C 语言程序设计的基本概念、语法和编程方法。全书共分为 10 章, 每个章节都从实际应用出发,蕴含了作者丰富的教学经验和编程心得。第 1 章通过一个简单 C 语言程序的编写、编译和运行介绍了程序设计的基本概念;第 2 章从计算机内数据存储的角度 介绍了 C 程序中包括指针在内的数据类型的概念及使用方法; 第 3 章介绍了包括指针运算在内 的 C 语言提供的各种运算功能; 第 4 章讲述了结构化程序设计的方法; 第 5 章结合循环与指针, 讲述了使用数组处理大量数据的方法; 第 6 章讲述了包括结构体在内的各种自定义数据类型的 使用方法;第 7 章讲述了使用函数进行模块化程序设计的方法,并重点讲述了指针在函数参数 中的作用;第 8 章讲述了使用指针操作动态内存的方法及链表基本应用方法;第 9 章讲述了在 C 语言中处理文件的基本方法;第 10 章介绍了 C 的预处理命令及简单应用。 全书内容由浅入深,例题经典、丰富,将指针的应用融合到全书的各章节之中。本书的结 构新颖、紧凑、通俗易懂,是学习 C 语言的合适教材。本书既可以作为普通本科院校、普通高 等专科学校的计算机教材,也可以作为计算机培训和计算机等级考试辅导的教学用书。 0前 言III前言几十年来,C 语言从其诞生之日起,就作为应用最为广泛的程序设计语言一直长盛不衰, 从家用电器中的单片机到企业生产设备中的工业控制系统,从汽车中的车载电脑到轮船、宇宙 飞船中的控制系统, 从家用电脑到巨型机,可以说只要有计算机的地方, C 语言都是最重要的程 序设计语言。 本教材主要作者为一直工作于教学一线的大学教师,承担“C 语言程序设计”课程的教学 任务十余年,有着丰富的教学经验,同时长期从事 C 语言编程工作,有几十万行程序代码的开 发经验,开发的软件多次获得省级、市级的奖励。在教学实践中,作者感受最深的就是,学生 普遍反映 C 语言难学难懂,尤其是指针千变万化,难以捉摸,而事实上,只要遵照一定的学习 规律,C 语言并不难于掌握。 部分 C 语言教材过细的内容组织让学生迷失了方向。看着满篇的烦琐内容,读者根本不明 白该学什么,更不明白 C 语言的重点是什么,唯一的感觉就是 C 语言难而烦。本书力求做到去 繁就简,以弄懂基本的、主要的、核心的内容为重点,教材也紧紧围绕循环、函数、指针等核 心内容进行组织。 另外,本教材也特别强调实践能力的培养。上机是学习 C 语言的最好方法,读者在学完第 一章后就可以开始上机练习。书中每章的内容都包含大量的实例,课后练习也以编程为主,课 后练习的内容由浅入深,如果能做到最后一道题目,本章的内容即可较好掌握。书中加有“*” 的内容,教师可以根据授课学生的实际情况选择是否进行讲授、考查。 本书第 1 章阐述了计算机及程序的基本工作原理,C 语言程序是怎样运行的,并通过一个 完整的 C 程序例子介绍了 C 程序的各部分组成、功能及 C 程序编辑、编译、运行的方法。通过 本章的学习,使读者能够对 C 语言程序及程序设计的过程和方法有一个基本的认识。 第 2 章用了大量的篇幅介绍计算机中数据的存储方式,从而引入了整数、浮点数、指针等 数据类型的概念, 并使读者理解这些数据类型的作用和意义。 在本章最后介绍了 C 语言中输入、 输出的基本方法,使读者马上可以通过这些输入、输出功能,对刚学到的各种不同类型数据进 行比较和分析。 第 3 章讲解了 C 语言中可以使用的各种运算符, 并通过大量的实例来展示这些运算符的功 能。本章还详细讲解了指针的各种运算方法,为以后指针的使用做好准备。 通过前面 3 章的学习,读者已经掌握了 C 语言中各种基本数据类型数据的处理方法。 第 4 章的内容通过讲解顺序结构、选择结构、循环结构这三种程序结构,使读者可以编写 出具有一定实用功能的程序。本章的难点是循环,尤其是多重循环,这也是很多学生在学习 C 语言过程中第一次开始掉队的地方,解决这个难点的唯一方法就练习,反复地编程练习。 第 5 章讲述数组,使 C 语言程序可以处理大量的数据,数组的处理离不开循环,所以本章 的内容还包括了第 4 章内容循环的强化。另外,由于数组的元素在内存中是连续存储的,这在 本书中第一次给了指针大展身手的空间,完成本章内容的学习,C 语言已经入门一半了。 第 6 章讲述了包括结构体在内的 C 语言各种自定义数据类型的使用方法。 本章以概念性内 容居多,虽然繁琐但并不难于掌握,书中通过各种示例对它们的定义方法和用法一一进行了展 示,是经过第 5 章艰苦学习过程后的一次小的休整。 第 7 章讲述了 C 语言中函数的使用方法及模块化程序设计的基本思想, 通过将一个复杂程 序划分成若干个函数了来实现,从而降低了程序的编写难度。在函数的调用过程中,指针作为 0前 言IV函数参数可以起到双向传值的作用,这些都是本章的重点和难点。 第 8 章讲述动态内存的使用方法,操作内存离不开指针,本章首先讲述了如何获取动态内 存,然后讲述了通过链表来组织、使用动态内存的方法。链表是结构体、指针的结合,由于操 作的复杂性使得使用函数成为必然,所以说本章内容是书上前七章内容的综合。完成本章内容 的学习,C 语言的掌握可以算入门了。 第 9 章讲述在 C 语言中操作文件的方法,主要是一些文件操作函数的应用。 第 10 章讲述在 C 语言中一些编译预处理命令的使用方法,在本章结尾处介绍了在组织多 文件的 C 语言源程序时条件编译的应用,为读者以后编写大型 C 程序提供了方便。 本书在编写过程中参考了许多同行的著作,作者在此一并表达感谢之情。 感 谢 丹 尼 斯 ? 里 奇 (Dennis MacAlistair Ritchie) 和 肯 尼 斯 ? 汤 姆 泼 逊 (Kenneth Lane Thompson),没有他们就没有 C 语言。 感谢杨季文教授和陈建明副教授以及为本书提供直接或间接帮助的每一位朋友, 你们的帮 助和鼓励促成了本书的顺利完成。如果您能够愉快地读完本书,并告之身边的朋友,原来 C 语 言并不难学,那么作者编写本书的目的就达到了。 尽管作者尽了最大努力,但是由于时间关系及作者学识所限,肯定存在缺点和错误,从而 影响写作目的。因此,恳请各位读者批评指正,以便再版时修订。编者 2011 年 11 月 2 日。 0目 录V目录内容简介 II 前 言 III 目 录 V 第 1 章 C 语言导论 1 1.1 C 语言概述 1 1.1.1 C 语言的功能 ........................................................................................................ 1 1.1.2 C 语言起源 ............................................................................................................ 2 1.1.3 C 语言的学习阶段与学习方法 ............................................................................ 3 1.2 第一个 c 程序 4 1.2.1 程序代码 ............................................................................................................... 4 1.2.2 空白和注释 ........................................................................................................... 5 1.2.3 预处理指令 ........................................................................................................... 5 1.2.4 main 函数 .............................................................................................................. 6 1.2.5 程序输出 ............................................................................................................... 6 1.3 C 语言程序的运行 7 1.3.1 程序的编译 ........................................................................................................... 7 1.3.2 程序的运行和调试 ............................................................................................... 8 1.4 习题 9 1.5 阅读材料-UNIX 和 C 的故事 9 第 2 章 数据 14 2.1 基本数据类型 14 2.1.1 整型数据 ............................................................................................................. 14 2.1.2 浮点型数据 ......................................................................................................... 17 2.1.3 指针型数据 ......................................................................................................... 17 2.2 常量 18 2.2.1 字面常量 ............................................................................................................. 19 2.2.2 符号常量 ............................................................................................................. 22 2.3 变量 23 2.3.1 标识符 ................................................................................................................. 24 2.3.2 变量的定义 ......................................................................................................... 25 2.3.3 变量的初始化 ..................................................................................................... 26 2.3.4 变量的引用 ......................................................................................................... 26 2.4 输出与输入 28 2.4.1 基本输出 ............................................................................................................. 28 2.4.2 基本输入 ............................................................................................................. 31 2.5 习题 33 2.6 阅读材料-二进制与计算机 35 2.6.1 二进制起源 ......................................................................................................... 35 0目 录VI2.6.2 计算机与二进制 ................................................................................................. 36 2.6.3 进制转换 ............................................................................................................. 36 2.6.4 计算机中的补码(two&#39;s complement) ............................................................. 38 第 3 章 运算与表达式 40 3.1 算术运算 40 3.1.1 基本算术运算符 ................................................................................................. 40 3.1.2 优先级与结合性 ................................................................................................. 42 3.1.3 数据类型转换 ..................................................................................................... 43 3.1.4 自增、自减运算 ................................................................................................. 45 3.2 关系运算 46 3.3 逻辑运算 47 3.4 *位运算 48 3.4.1 按位逻辑运算 ..................................................................................................... 48 3.4.2 移位运算 ............................................................................................................. 51 3.4.3 程序例子 ............................................................................................................. 52 3.5 指针运算 53 3.5.1 取地址运算 ......................................................................................................... 53 3.5.2 操作指针变量 ..................................................................................................... 55 3.5.3 移动指针 ............................................................................................................. 57 3.5.4 比较指针 ............................................................................................................. 58 3.6 其它运算 59 3.6.1 sizeof 运算 ........................................................................................................ 59 3.6.2 逗号运算 ............................................................................................................. 60 3.6.3 条件运算 ............................................................................................................. 60 3.7 赋值运算 61 3.7.1 赋值运算符和赋值表达式 ................................................................................. 61 3.7.2 复合赋值运算 ..................................................................................................... 62 3.8 习题 63 第 4 章 程序控制结构 66 4.1 程序语句 66 4.2 顺序结构 67 4.3 选择结构 69 4.3.1 if 语句 ................................................................................................................ 69 4.3.2 if 嵌套 ................................................................................................................ 73 4.3.3 switch 语句 ........................................................................................................ 79 4.4 循环结构 83 4.4.1 while 循环 .......................................................................................................... 84 4.4.2 do while 循环 .................................................................................................... 87 4.4.3 for 循环 .............................................................................................................. 89 4.4.4 其它控制语句 ..................................................................................................... 91 4.4.5 循环控制嵌套 ..................................................................................................... 93 4.5 习题 97 附录 A 常用字符与 ASCII 码对照表 99 附录 B 运算符和结合性 100 0目 录VII附录 C 常用标准库函数 参考文献102 110 第1章C 语言导论1第1章 C 语言导论1.1 C 语言概述1.1.1C 语言的功能目前,计算机的应用已经深入到社会的各个领域,成为人们日常工作、生活、学习的必 备工具。计算机是一种具有存储程序、执行程序能力的电子设备,计算机所有能力都是通过 执行程序来实现的。程序就是人们把需要做的工作写成一定形式的指令序列,并把它存储在 计算机内部存储器中,当人们给出命令之后,计算机就按照指令的执行顺序自动进行相应操 作,从而完成相应的工作。人们把这种可以连续执行的一条条指令的序列称为“程序” 。编写 程序的过程就称为“程序设计” 。人们编写的指令序列为了使计算机能够正确识别和执行,不 能随意编写,必须有一定的规则。这些规则的定义包含了一系列的文法和语法的要求,按照 这些规则编写的程序能够被计算机理解执行,所以它是人和计算机之间的交流语言。这种语 言类似于人与人之间交流的语言,虽然没有人类语言那么复杂,但逻辑上要求更加严格,符 合这些规则的“语言”也被称为“程序设计语言” 。 机器语言或称为二进制代码语言,计算机可以直接识别。每台机器的指令,其格式和代 码所代表的含义都是硬性规定的,如某种计算机的指令为 0000,它表示让计算 机进行一次加法操作; 而指令 0000 则表示进行一次减法操作。 它们的前八位表 示操作码,而后八位表示地址码。因为硬件设计不同,机器语言对不同型号的计算机来说一 般是不同的。用机器语言编程,就是从实用的 cpu 的指令系统中挑选合适的指令,组成一个 指令系列的过程。 由于“机器语言”与人们日常生活中使用的语言差距过大,而且大量的规则都和具体的 计算机硬件设计和实现相关,所以使用“机器语言”编写程序难度很大。为了降低编写程序 的难度,人们发明了一些更加接近人类日常语言的程序设计语言,但这些语言编写的程序不 能被计算机直接识别、执行,必须翻译成 “机器语言程序”才能被计算机执行。 根据不同程序设计语言与人类语言的接近程度,基本上可把这些语言分为高级语言、中 级语言、低级语言。低级语言最接近机器语言,学习和使用难度都比较大;高级语言最接近 人类语言,学习和使用难度相对于低级语言要容易的多,应用最为广泛。目前常见的高级语 言有 C、Java、C++、C#、Basic、Pascal 等。由程序设计语言编写的程序称为“源程序” ,高 级语言编写的程序不能被计算机硬件直接识别、 执行, 高级语言程序编译(Compile)成机器语 第1章C 语言导论2言程序的过程如图 1.1 所示。翻译 高级语言源程序 翻译程序 二进制机器语言程序图 1.1 程序编译、链接过程示意图 几十年来,人们发明了很多种的计算机程序设计语言,目前还在不断有新的程序设计语 言被发明出来,这些语言往往具有不同的特点。C 语言是目前世界上应用最为广泛的高级程 序设计语言,它是一种通用的高级程序设计语言,可以用来完成各种类型的应用软件设计。C 语言的通用性和无限制性使得它对于程序设计者来说都显得更加方便、更加有效。从微型计 算机(包括我们日常使用的 PC)到小型机、中型机、大型机、巨型机,都离不开 C 语言编写 的程序;从家用冰箱、电视、洗衣机、空调到手机,它们能够有效工作大部分都依赖内部运 行的 C 语言编写的程序;从现代化的智能车床、工业控制设备、汽车、火箭、宇宙飞船、其 内部运行程序大部分也都是 C 语言编写的。可以说,有计算机的地方就有 C 语言编写的程序 在运行。1.1.2C 语言起源在学习 C 语言之前,我们有必要了解一下 C 语言的发展历史。 目前,计算机技术发展速度如此之快以致让人目不暇接,作为计算机软件技术的基础, 新的程序设计语言和新的操作系统也在不断涌现,这些新技术、新产品往往都是由国际著名 企业或国家重要部门投入巨大的人力、物力所开发出来的。然而,提到目前最优秀、最有价 值的程序设计语言和操作系统,却要到四十年前去找,那就是强大的 C 语言和用 C 语言编写 的 UNIX 操作系统, 更加让人惊奇的是它们竟然只是几个人几乎是基于个人兴趣所缔造的, 花 费的人力物力代价更是少的可怜。 以历史发展的角度看,C 语言起源于 1968 年发表的 CPL 语言(Combined Programming Language),它的许多重要思想来自于 Martin Richards 在 1969 年研制的 BCPL 语言,以及以 BCPL 语言为基础的 B 语言。 Dennis M. Ritchie 在 B 语言的基础上, 于 1972 年研制了 C 语言, 并用 C 语言写成了第一个在 PDP-11 计算机上实现的 UNIX 操作系统(主要在贝尔实验室内部 使用) 。以后,C 语言又经过多次改进,直到 1975 年用 C 语言编写的 UNIX 操作系统第 6 版公 诸于世后,C 语言才举世瞩目。1977 年出现了独立于机器的 C 语言编译文本《可移植 C 语言 编译程序》 ,从而大大简化了把 C 语言编译程序移植到新环境所需做的工作,这本身也就使 UNIX 操作系统迅速地在众多的机器上实现。 随着 UNIX 的日益广泛使用, C 语言也迅速得到推 广。1978 年以后,C 语言先后移植到大、中、小、微型计算机上,它的应用领域已不再限于 第1章C 语言导论3系统软件的开发,而成为当今最流行的程序设计语言。 以 1978 年发布的 UNIX 第 7 版的 C 语言编译程序为基础,Brian W. Kernighan 和 Dennis M. Ritchie 合著了影响深远的名著 The C Programming Language,这本书中介绍的 C 语言成 为后来广泛使用的 C 语言版本的基础,它被称为标准 C。 1983 年美国国家标准化协会( ANSI)根据 C 语言问世以来的各种版本,对 C 语言的发展 和扩充制定了新的标准,称为 ANSI C。1989 年 ISO 根据 ANSIC 公布 C 标准,即 C89,1999 年 ISO 公布了最新的 C 标准,即 C99。 目前流行的 C 语言编译系统大多是以 ANSI C 为基础进行开发的, 不同版本的 C 编译系统 所实现的语言功能和语法规则基本部分是相同的,但在有关规定上又略有差异。本书的叙述 基本上以 ANSI C 为基础。1.1.3C 语言的学习阶段与学习方法C 语言是一门实用性、技巧性很强的计算机程序设计语言。学好它是需要花费一定功夫 的,仅仅靠上课的一点时间或短时间突击是远远不够的。C 语言也是一门实践性很强的语言, 要想学好 C 语言,上机实践的时间应远大于听课和看书学习的时间。C 语言的学习阶段大体 可划分如下: 1. 入门阶段 主要学习怎么写程序。在该阶段,首先要体会、理解什么是程序设计;学会怎样将一个 人的思路转换为计算机可以执行的程序;掌握使用 C 语言进行程序设计的基本方法;可以使 用 C 语言编写一些小程序解决一些简单的问题;这个阶段的可以写出 C 程序规模一般在几百 行以内。 2. 进阶阶段 主要学习从写出程序到写出好的程序。在学会写出简单程序之后,要学习怎么写出好的 程序;要学习怎么样用程序设计的方法解决一些比较复杂的问题;学会如何将一个复杂问题 分解成若干的简单问题然后使用模块化的程序设计方法进行求解,在这个阶段还要学会一些 常用的程序设计算法;学习如何提高程序的执行效率等。 3. 实用阶段 主要学习如何将程序设计用于到自己的工作中。经过前面两个阶段的学习,可以说已经 基本掌握了一般性程序设计的技术和方法, 但把我们的程序应用到自己的工作中还是不够的, 因为不同行业计算机的软件开发技术也是有很大差别的,例如:学习机械电子的同学可能还 要学习单片机的开发技术和计算机的控制技术,学习企业管理、财经类的同学还要学习数据 库技术,总而言之,第三阶段就是将程序设计技术结合到自己专业中的过程。 本书内容仅涉及到 C 语言的入门阶段,即使是入门阶段,达到这一阶段的水平一般也需 要付出 200 至 500 小时的学习、实践时间。万事开头难,入门的过程对于大多数的人是痛苦 的,但随着学习的深入,你会发现 C 语言中有许多东西很有趣的,这样学习不再是一个多么 难的事了,而是一件快乐的事。C 语言学习方法如下: 1. 根据读者自己的学习进度认真阅读本书上的相关章节内容的讲述(绝不能跳跃) ,对 第1章C 语言导论4照例题理解讲述内容、上机完成书后相关部分的练习习题,如果所有习题能顺利完 成则转 5。 2. 如果发现练习习题不会做,再回头看书上相关内容的讲述、再分析例题、上机练习, 如果顺利完成则转 5。 3. 如果练习习题还是不会做,可以和同学讨论、在讨论中扩展思路、增进理解、再上 机练习,如果顺利完成则转 5。 4. 如果练习习题还是不会做,可以请教老师,直到完成上机练习转 5。 5. 根据老师的推荐补充练习。 6. 如果有困难再返回到 2。 7. 完成相关知识点的掌握,继续本书后续章节内容的学习。 含金量越高的技术往往越难以掌握,程序设计是一门纯脑力运动,对人的逻辑思维能力 要求很高。多动脑,C 语言的第一阶段很快就会掌握的,没有付出就没有回报!1.2 第一个 c 程序1.2.1程序代码一个 C 语言程序是一段标准的文本,文本内容描述了实现一个具体功能的程序步骤,该 段文本内容可以用包括“记事本”在内的各种文字编辑软件编写。由于 C 语言程序本身不能 够被计算机硬件直接执行,所以还要有一个翻译软件把它翻译成计算机可以直接执行的机器 语言程序才行,为了使用户书写的程序文本顺利的被翻译软件翻译成计算机可以直接执行的 机器语言,必须要以一定的规则进行书写,这就是 C 语言的语法规则。C 语言翻译软件通常 也被称为 C 语言编译软件,C 语言编译软件也被称为 C 语言编译程序。 下面我们看一个简单的 C 程序例子,它的功能是在用户计算机屏幕上显示“欢迎进入 C 语言的世界! ”这样一行文字: 【例 1.1】欢迎进入 C 语言的世界!#1. /* #2. #3. #4. */ #5. #include &stdio.h& #6. #7. void main() #8. { #9. #10. } printf(&欢迎进入 C 语言的世界!\n&); 该程序显示如下信息: 欢迎进入 C 语言的世界! 第1章C 语言导论5在这段文本中, 每一行前面的’#’符号及数字不是程序文本, 是本书为了说明方便而增 加的,后面灰底色的内容才是程序文本,用户在录入编辑本书各例子程序时只要输入以灰色 为背景的部分即可。 上面这段 C 语言程序看起来并不复杂,但它的书写有严格的语法和文法要求,用户录入 时有一点点的违规,例如大小写写错、少了一个字符,C 语言的编译软件都可能无法把它翻 译成正确的机器语言程序。1.2.2空白和注释通过观察例 1.1 可以发现这段 C 语言程序中除了一些字符之外还有很多空白;空白主要 包括一些换行、空行、空格、制表符(Tab)等,这些空白在程序中的作用是用来分隔程序的 不同功能单位,以便使翻译软件进行识别处理,合理使用这些空白也可以使程序看起来更加 规整、有序。 程序的#1 行到#4 行,符号“/*”标记注释内容的开始, “*/”标记注释内容的结束,注 释的功能是用于程序功能说明,翻译软件在翻译程序时会忽略注释中的内容,不会把它翻译 成机器语言,在 C 程序中,凡是可以插入空白的地方都可以插入注释。注释主要功能如下: 1. 可以用来说明某一段程序的功能或这段程序使用上的注意事项,提示以后使用到这 段程序的人如何使用。 2. 使用注释符号包括一段程序,使这段程序暂时失去功能,在需要的时候可以通过删 除注释符号快速进行恢复这段程序。1.2.3预处理指令例 1.1 程序的#5 行是一条预处理指令, 它提示翻译软件在把这个 C 语言程序翻译成机器 程序前,要完成的一些操作。翻译软件中专门有一个称为“预处理器”的程序是用来解释执 行预处理指令的, “预处理器”处理程序中的所有预处理指令后,翻译软件中负责翻译的“编 译器”程序才开始翻译 C 程序为机器指令程序。 所有预处理指令总以 “#” 号开头, 这里的#include 使得 “预处理器” 把名为 “stdio.h” 的文件插入到#include 行出现的地方,实际上“stdio.h”文件声明了该段 C 语言程序中将 要在#8 行用到的“printf”的使用方法,如果没有这条预处理指令,#8 行的“printf”将 无法使用。实际上“stdio.h”文件定义了很多输入输出功能,我们在 C 程序中如果要使用这 些功能,就要包含“stdio.h”文件。#include 后面可以跟不同的文件名, “预处理器”把不 同的文件插入到#include 行出现的地方。 通常,为了方便用户,C 语言编译软件也提供了很多附加的常用程序功能,这些程序功 能用户可以在自己的程序中直接使用,从而提高程序的编写效率。为了使用这些功能,必须 在用户的 C 程序中使用包含指令来包含这些功能的声明。例如,math.h 包含了很多数学处理 功 能 , 如 果 用 户 要 在 程 序 中 使 用 这 些 数 学 处 理 功 能 , 就 要 使 用 预 处 理 指 令 #include “math.h”包含这个文件,然后才能使用“math.h”里面定义的数学功能。由#include 指令 第1章C 语言导论6包含到 C 代码中的文件通常被称为头文件,通常具有.h 为扩展名,而 C 包含代码的文本文件 被称为 C 源程序文件,通常具有.C 为扩展名。1.2.4main 函数例 1.1 程序的#7 行开始到#10 行定义了一个 C 语言函数,一个 C 语言函数就是一个 C 语 言程序的功能单位, 多个具有简单功能的 C 语言函数可以组成一个功能更复杂的 C 语言程序。 由于每个 C 语言函数都是一小段相对独立的 C 语言程序,所以每个 C 语言函数也可以被称为 一个 C 语言子程序。 #7 行定义了一个函数的名称“main” ,函数名前面的“void” 代表这个函数不需要返回 值,所谓返回值即将这个函数的执行结果提交给它的上一级程序,所谓上一级程序即执行本 函数的一段程序;函数名前面的“void”代表本函数不需要将结果提交给上一级程序; C 语言函数和 C 语言命令的显著区别是函数名称后面有一对小括号“ () ” 。函数名后面的 一对小括号“ () ”可以用来接收上级程序在执行本函数时传过来的一些数据, “ () ”内为空或 填上“void”代表这个函数不需要上一级程序传入的数据。 在每个 C 语言程序中必须且只能有一个命名为 main 的函数, 因为这个函数是每个 C 语言 程序执行的起点,而这个起点必须唯一。当 main 函数执行结束后,这个 C 语言程序也就执行 结束了。在 main 函数中可以通过函数名称执行其它的函数,其它函数执行完成后就会返回 main 函数继续执行,所以 main 函数就是其它函数的上级函数。 #8 行的“ {”代表“main”函数的开始,#10 行的“} ”代表“main”函数的结束,两个 大括号中间为函数的内容,用来描述函数的执行步骤,这里的 main 函数很简单,里面只有#9 一行程序,这行程序是一条程序语句,C 语言中规定每条语句都必须以“; ”作为结束标志。 C 语言中每条语句都可以用来完成一个具体的功能,#9 行的语句用来执行另外的一个函数 printf,这个函数在“stdio.h”文件中声明,该函数的功能是在屏幕上输出一串字符。1.2.5程序输出在 C 语言程序中,输出是很常用的一项功能,如果用户自己用 C 语言编写程序实现在屏 幕上输出那是一个很复杂的过程,对于初学者来说更是几乎不可能完成的任务。所以大多数 C 的编译软件都提供了一组输出函数,可以让用户在自己的 C 程序中直接使用,printf 就是 大部分 C 编译软件提供的最常用的一个输出函数,用户只要在自己的程序中加入预处理指令 #include &stdio.h&就可以在自己的程序中使用这个函数了。 printf 函数执行格式化的输出, 它的功能是把上级程序传给它的数据在输出设备上进行 显示。在本 C 程序例子中,在 main 中执行 printf 函数,main 就是 printf 的上级程序,可 以在 printf 后面的一对小括号中填入数据传给 printf 函数, 函数 printf 会把这些数据在屏 幕上显示出来。 printf(&欢迎进入 C 语言的世界!\n&)中的&欢迎进入 C 语言的世界!\n&在 C 语言程序 中被称为字符串,它的特点是用双引号( “” )括起来的一串字符,这串字符作为数据传给 第1章C 语言导论7printf 之后就会被 printf 在屏幕上显示出来,其中’\n’的含义是换一行,即输出完&欢迎 进入 C 语言的世界! ”后,下一个输出位置换到下一行起始的位置。1.3 C 语言程序的运行1.3.1程序的编译用 C 语言书写的程序文本称为 C 语言源程序,一个 C 语言源程序可以保存在若干个文本 文件中,C 语言源程序需要翻译成等价的机器语言程序才能在计算机上运行,实现翻译功能 的软件被称为翻译程序或编译程序。编译程序以 C 源程序作为输入,而以机器语言表示的目 标程序作为输出。 C 程序的编译过程一般分成五个步骤:编译预处理、编译、优化、汇编、链接并生可执 行的机器语言程序文件: 1.编译预处理 读取 c 源程序文件,对其中的编译预处理指令进行处理,根据执行结果生成一个新的输 出文件。这个文件的功能含义同没有经过预处理的源文件是相同的,但形式因为执行了编译 预处理指令而有所不同。 2.编译 经过编译预处理得到的输出文件中,已经没有了编译预处理指令,都是一条条的 C 程序 语句。编译程序所要做的工作就是通过词法分析和语法分析,在确认所有的指令都符合 C 语 法规则之后,将其翻译成功能、含义等价的近似于机器语言的汇编语言代码或中间代码。 3.优化阶段 优化处理就是为了提高程序的运行效率所进行的程序优化,它主要包括程序结构的优化 和针对目标计算机硬件所进行的优化两种: 对于前一种优化,主要的工作是运算优化、程序结构优化、删除无用语句等。后一种类 型的优化同机器的硬件结构密切相关,最主要的考虑是如何充分发挥机器的硬件特性,提高 内存访问效率等。 优化后的程序在功能、含义方面跟原来的程序相同,但将更富有效率。 4.汇编 汇编实际上是把汇编语言代码翻译成目标机器语言指令的过程。由于一个 C 源程序可能 保存在多个文档中,每一个 C 语言源程序文档都将最终经过这一处理而得到一个相应的目标 机器语言指令的文件,通常被简称为目标文件。 目标文件中所存放的也就是与 C 源程序等效的 机器语言代码程序。 5.链接程序 由汇编程序生成的机器语言代码目标程序还不能执行,其中可能还有许多没有解决的问 题。例如,某个源文件中的代码指令可能使用到另一个源文件中定义的指令代码或数据等。 第1章C 语言导论8所有的这些问题,都需要经链接程序的处理方能得以解决。 链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符 号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够由操 作系统装入执行的统一整体,一个完整的机器语言程序。 经过上述五个过程,C 源程序就最终被转换成机器语言可执行文件了。习惯上,通常把 前面的四个步骤合称为程序的编译,最后一个步骤称为程序的链接。用户在编写 C 语言源程 序的过程中如果发生错误,在编译和链接的过程中都可能发生错误,发生编译错误的程序不 能进行链接,发生链接错误的程序不能生成机器语言的可执行文件,用户可以根据编译程序 的错误提示进行修正,在程序编译链接过程中出现的错误通常被称为程序的语法错误。 C 语言的编译程序有很多种,它们有的是不同厂家推出的针对不同软硬件环境的不同产 品, 有的是同一产品的不同版本, 目前比较流行的 C 编译程序有 GNU 的 GCC、 微软的 Visual C++、 Borland 的 Turbo C 等,本书所讲述的内容和提供的程序都可以在以上编译程序中使用,用 户可以根据自己的硬件条件和使用的操作系统类型进行选择。1.3.2程序的运行和调试用户编写的 C 源程序经编译成可以执行的机器语言程序后就可以执行了,其执行方法与 执行其它的软件程序一样,可以在操作系统下直接启动运行。例如在 Windows 系统环境下, 用户可以用鼠标双击编译好的机器语言程序文档就可以运行该程序了。 操作系统执行程序的一般过程如下: 1. 操作系统将程序文档读入内存,为该程序的运行分配内存空间。 2. 操作系统为该程序创建进程。 3. 操作系统执行该进程。 4. 进程执行结束,操作系统释放该程序在运行中使用的一些内存空间等资源。 用户程序的一般执行过程如下: 1. 操作系统调用执行用户程序中的入口程序完成初始化。 2. 入口程序调用执行用户程序的 main 函数。 3. main 函数执行,中间可能调用其它函数。 4. main 函数执行结束后,程序终止。 可执行程序的默认入口程序由编译程序在链接程序时自动添加, 通常不需要用户编写, C 编译程序提供的默认入口程序调用用户编写的函数的 main 函数。 用户编写的程序可能存在错误,程序中的错误可分为语法错误、逻辑错误和运行错误三 大类。 语法错误是用户编写的程序违背了 C 语言的语法规则,这些错误通常在程序编译、链接 过程中可以发现。 逻辑错误指的是程序没能按照设计者的设计意图运行,例如用户企图在程序中以为按照 A 种方法可以得到 B 种结果,其实这个 A 方法是错误的,该错误只有在程序运行过程中才能 显现出来,这种错误比较隐蔽。 第1章C 语言导论9运行错误是指程序在执行过程中随机发生的错误,这种错误可能是由于程序运行的条件 发生变化引起的,这种错误非常隐蔽。 逻辑错误和运行错误常常需要在程序的运行过程中才能发现。逻辑错误和运行错误被称 为程序的“ Bug ” ,即使是资深的程序员也很难避免。找到并排除程序中的错误称为调试 (Debug) ,很多编译程序都提供了程序调试的方法。最常见的程序调试的方法是在编译程序 提供的开发环境中模拟程序的运行过程,利用开发环境提供的监视功能监视程序运行过程中 的数据的变化情况,可以快速发现程序的逻辑错误或运行错误。单步(逐个语句或逐条指令) 或可控制的调试程序称为程序的跟踪调试。掌握程序的调试技巧是学好程序设计必须掌握的 基本技能。1.4 习题1. 根据自己的理解,简述什么是程序和程序设计,它们有什么意义。 2. 根据自己的理解,简述什么是程序设计语言,它有什么功能。 3. 根据自己的理解,简述什么是低级语言和高级语言,它们各有什么特点。 4. 简述 C 语言的功能和发展的历史。 5. 根据自己的认识,简述 C 语言程序编译成机器语言程序的过程。 6. 通过网络找到两种以上 C 语言编译软件产品,并对它们进行简要的介绍。 7. 根据自己的理解,简述 C 语言的学习方法。 8. 简述一个 C 语言程序主要由哪些部分的组成的。 9. 参照本章例题,编写一段 C 语言程序,并通过一种编译软件编译和运行,输出以下 内容: A AAA AAAAA AAAAAAA AAAAAAAAA 10. 简述用户在编写程序工程中可能出现的各种错误种类,哪种错误最隐蔽,以及解决 方法。1.5 阅读材料-UNIX 和 C 的故事在计算机发展的历史上,大概没有哪个程序设计语言像 C 那样得到如此广泛的流行;也 没有哪个操作系统像 UNIX 那样获得计算机厂家和用户的普遍青睐和厚爱,它们对整个软件 技术和软件产业都产生了深远的影响。而 C 和 UNIX 两者都是贝尔实验室的丹尼斯?里奇 第1章C 语言导论10(Dennis MacAlistair Ritchie)和肯尼斯? 汤姆泼逊(Kenneth Lane Thompson)设计、 开发的。 因此, 他们两人共同获得 1983 年度的图灵奖是情理中的事。 我们先介绍汤姆泼逊, 因为就 C 和 UNIX 两者的关系而言,UNIX 的开发在前,C 是为了使 UNIX 具有可移植性而后来研制的;就里 奇和汤姆泼逊两人的关系而言,他们两人当然是亲密的合作者,但汤姆泼逊在 UNIX 的开发 中起了主导的作用,而里奇则在 C 的设计中起的作用更大一些。 汤姆泼逊 汤姆泼逊 1943 年 2 月 4 日生于路易斯安娜州的新奥尔良, 其父是美国海军战斗机的驾驶 员。汤姆泼逊自幼的爱好有两个:一个是下棋,一个是组装晶体管收音机。他父亲为了发展 孩子的智力和能力,在晶体管当时问世不久、价格不菲(每只晶体管约售 10 美元)的情况下, 很舍得为汤姆泼逊买晶体管让他摆弄。由于爱好无线电,汤姆泼逊上加州大学伯克利分校时 学的专业是电气工程,于 1965 年取得学士学位,第二年又取得硕士学位。求学期间,他还参 加 了 通 用 动 力 学 公 司 (General Dynamics Corporation) 在 伯 克 利 实 行 的 半 工 半 读 计 划 (Work-study program),因此既增长了知识,又积累了不少实践经验。 毕业以后,汤姆泼逊加盟贝尔实验室。虽然他学的是电子学,主要是硬件课程,但由于 他半工半读时在一个计算中心当过程序员,对软件也相当熟悉,而且更加偏爱,因此很快就 和里奇一起被贝尔派到 MIT 去参加由 ARPA 出巨资支持的 MAC 项目,开发第二代分时系统 Multics。但就在项目完成前不久,贝尔因感到开发费用太大,而成功的希望却很小而退出了 该项目,把所有成员都调回贝尔。这使汤姆泼逊和里奇深感沮丧。返回贝尔以后,面对实验 室中仍以批处理方式工作的落后计算机环境,他们决心以在 MAC 项目中已学到的多用户、 多任务技术来改造这种环境,以提高程序员的效率和设备的效率,便于人机交互和程序员之 间的交互,用他们后来描写自己当时的心情和想法的话来说,就是“要创造一个舒适、愉快 的工作环境” 。但他们意识到,贝尔领导人既然下决心退出 MAC,就不可能支持他们的想法, 不可能为之立项,提供资金和设备,他们只能悄悄干,自己去创造条件。 1969 年,万般无奈的汤姆泼逊在库房中偶然发现一台已弃置不用的 PDP-7,大喜过望, 立即开始用它来实施他们的设想。但开头是十分困难的,因为这台 PDP-7 除了有一个硬盘, 一个图形显示终端和一台电传打字机等硬设备外,什么软件也没有。他们只能在一台 GE 的 大型机上编程、调试,调通以后穿孔在纸带上,再输入 PDP-7,但它也损坏得不能再用了。 这时,他们听到一个消息,实验室的专利部需要一个字处理系统以便处理专利申请书(贝尔每 年要提出不少专利申请),汤姆泼逊立即找到上级自告奋勇承担这一开发任务,在这个冠冕堂 皇的借口下,他们申请到了一台新的、设备完善的 PDP-11,这才使开发工作顺利地真正开展 起来。 汤姆泼逊以极大的热情和极高的效率投入工作。 开发基本上以每个月就完成一个模块(内 核、 文件系统、 内存管理, I/O, ??)的速度向前推进, 到 1971 年底, UNIX 基本成形。 “UNIX” 这个名称是从“Multics”演变而来的:他们变“MULTI”为“UNI” 、变“CS”为“X” 。为 了向上级“交差” ,UNIX 首先交给实验室的专利部使用,3 个打字员利用 UNIX 输入贝尔当 年的专利申请表,交口称赞系统好用,大大提高了工作效率,这样,UNIX 迅速从专利部推 广至贝尔的其他部门,又从贝尔内部推向社会。贝尔实验室的领导人终于认识到了 UNIX 的 巨大价值,把它注册成为商标(但有趣的是,由于法律上的原因,注册商标及版权被贝尔的上 属公司 AT&T 取得), 推向市场。 贝尔的一个行政长官甚至宣称, 在贝尔的无数发明中, UNIX 第1章C 语言导论11是继晶体管之后的最重要的一项发明。著名的国际咨询公司 IDC 的高级分析员 Bruce Kin 估 计,1985 年单是美国就有 27.7 万个计算机系统使用 UNIX,1990 年这个数字增长至 210 万, 在世界上 UNIX 安装数量目前已超过 500 万,用户数达到 3000 万。 UNIX 之所以获得如此巨大的成功,主要是它采用了一系列先进的技术和措施,解决了 一系列软件工程的问题,使系统具有功能简单实用,操作使用方便,结构灵活多样的特点。 它是有史以来使用最广的操作系统之一,也是关键应用中的首选操作系统。UNIX 成为后来 的操作系统的楷模,也是大学操作系统课程的“示范标本” 。归纳起来,UNIX 的主要特性如 下: 作为多用户多任务操作系统,每个用户都可同时运行多个进程。 提供了丰富的经过精心编选的系统调用。整个系统的实现紧凑、简洁、优美。 提供功能强大的可编程外壳(shell)语言作为用户界面,具有简洁高效的特点。 采用树形文件结构,具有良好的安全性、保密性和可维护性。 提供多种通信机制,如管道通信、软中断通信、消息通信、共享存储器通信和信号类通 信。 采用进程对换内存管理机制和请求调页内存管理方式实现虚存,大大提高了内存使用效 率。 系统主要用 C 编写,不但易读、易懂、易修改,更极大提高了可移植性。 由于以上特点, 也由于看好 UNIX 的应用和前景, 各大公司纷纷推出自己的 UNIX 版本, 如 IBM 的 AIX,SUN 的 Solaris,HP 的 HP-UX,SCO 的 UNIXWare 和 Open Server,DEC(已 被 Compaq 收购)的 digtal UNIX, 以及加州大学伯克利分校的 UNIX BSD。 这些 UNIX 各有特 色,形成百花齐放的局面。当前呼声极高,一枝独秀,由芬兰的大学生托瓦茨(Linus Torvalds) 推出的 Linux 实际上也是 UNIX 的一个变种而已。 由于功能强劲,用途多样,使用方便,因此有人把 UNIX 称作软件中的“瑞士多用途折 叠刀”(或叫“瑞士军刀”)。 汤姆泼逊本人围绕 UNIX 的开发工作于 1978 年结束。之后他从事过的项目有“Plan 9” , 这是另一个操作系统。此外,鉴于他自幼爱好下棋,他还建造过一台名为“Belle”的下棋计 算机,还与康顿(Joseph Condon)合作,在 PDP-11/23, PDP-11/10 上编制了下棋程序,这个 程序从 1979 年到 1983 年在连续几届计算机世界比赛中都独占鳌头,成为“四连冠” ,同时也 成为被美国围棋联盟 VSCF 授予“大师”称号的第一个下棋程序。这个程序每秒可考察 15 万个棋步,与现今 IBM 的“深兰”当然无法相比,但在当时却是一个了不起的成就。 里 奇 里奇比汤姆泼逊年长 2 岁,1941 年 9 月 9 日生于纽约州的勃浪克斯山庄(Bronxville),但 在 9 岁时移居新泽西州的塞米特。里奇的父亲是一个电气工程师,在贝尔实验室的交换系统 工程实验室当主任,因此,里奇一家可谓“贝尔世家” 。里奇中学毕业后进哈佛大学学物理, 并于 1963 年获得学士学位。其间,哈佛大学有了一台 UNIVAC I,并给学生开设有关计算机 系统的课程,里奇听了以后产生了很大的兴趣。毕业以后他在应用数学系攻读博士学位,完 成了一个有关递归函数论方面的课题,写出了论文,但不知什么原因没有答辩,没有取得博 士学位,他就离开了哈佛,于 1967 年进入贝尔实验室,与比他早一年到贝尔的汤姆泼逊会 合,从此开始了他们长达数十年的合作。 第1章C 语言导论12我们前面说过,UNIX 的开发是以汤姆泼逊为主的,那末,为什么文献资料中一提到 UNIX,都一致地说是里奇和汤姆泼逊共同开发的,而且在 “排名”上往往是里奇在前,汤 姆泼逊在后呢?包括他们在 1973 年由 ACM 主办、IBM 承办的操作系统原理讨论会上首次向 社会推介 UNIX 的论文“The UNIX Time-Sharing System”的署名,里奇也是第一作者,汤姆 泼逊则为第二作者。里奇在 UNIX 开发中有些什么功劳呢? 这里有两个很重要的因素。 首先, UNIX 的成功应归功于它的创新。 前面曾经提到, UNIX 吸取与借鉴了 Multics 的经验, 如内核, 进程, 层次式目录, 面向流的 I/O, 把设备当作文件, ?? 等等。这是可以理解的,因为任何新事物必然是对原有事物的继承和发展。尤其是 UNIX, 毕竟没有正式立项,是汤姆泼逊、里奇等少数几个人偷偷干的,如果一切都要从头从新设计, 那几乎是不可能的。 但是 UNIX 在继承中又有创新, 比如 UNIX 采用一种无格式的文件结构, 文件由字节串加句号组成。这带来两大好处:一是在说明文件时不必加进许多无关的“填充 物”(类似于 Cobol 中的 “Filler”),二是任何程序的输出可直接用作其它任何程序的输入, 不必经过转换。后面这一点叫做“流水”(piping),就是 UNIX 首创的。此外,像把设备当做 文件,从而简化了设备管理这一操作系统设计中的难题,虽然不是 UNIX 的发明,但是实现 上它采用了一些新方法,比 Multics 更高明一些。正是在这些方面,里奇发挥了很重要的作 用,使 UNIX 独具特色。 其次,UNIX 成功的一个重要因素是它的可移植性。正是里奇竭尽全力开发了 C 语言, 并把 UNIX 用 C 重写了一遍,这才使它具有了这一特性。汤姆泼逊是用汇编语言开发 UNIX 的,这种语言高度依赖于硬件,由它开发的软件只能在相同的硬件平台上运行。里奇在由剑 桥大学的里查德 (M.Richards) 于 1969 年开发的 BCPL 语言 (Basic Combined Programming Language)的基础上,巧妙地对它进行改进、改造,形成了既具有机器语言能直接操作二进制 位和字符的能力,又具有高级语言许多复杂处理功能如循环、选择、分支等的一种简单易学 而又灵活、高效的高级程序设计语言。他们把这种语言称为“C” ,一方面指明了继承关系(因 为 BCPL 的首字母是“B” 。有些资料说是汤姆泼逊先根据 BCPL 开发了一种称为“B”的语 言,再由里奇根据 B 开发了 C。这种说法并不太确切,因为我们在汤姆泼逊与里奇本人的叙 述中,都没有见到有关“B” 语言这一中间过程的说法),另一方面也反映了他们对软件追求 简洁明了的一贯风格。C 开发成功以后,里奇用 C 把 UNIX 重写了一遍。我们这里用了“重 写”这个词,因为文献资料在提到这件事时都是用的这一说法,显得很轻巧 ;实际上,里奇 做的这件事本身就是“移植” ,即把汤姆泼逊用汇编语言实现的 UNIX 改用 C 来实现,这决 不是什么轻巧的工作,尤其是对 UNIX 这样的大型软件。这需要付出艰苦的劳动,也是一件 需要创造性的工作。单是里奇的此举就是可以大书特书的,而 C 作为可以不依附于 UNIX 的 一个独立的软件产品,也自有其本身的巨大价值,在计算机发展史上可以写下浓重的一笔。 前 述 里 奇 和 汤 姆 泼 逊 的 论 文 “ The UNIX Time-Sharing Symtem ” 后 来 发 表 于 《Communications of ACM》 ,1974 年 7 月。ACM1983 年在纪念该刊创刊 25 周年时曾经评选 出刊登于其上的 25 篇文章称之为具有里程碑式意义的研究论文,该文就是其中之一。 除了论文以外, 里奇还和凯尼汉(B.W.Kernighan)合著了一本介绍 C 的专著: 《C 程序设计 语言》(《The C Programming Language》,Prentice-Hall, )。我们现在见到的大量 论述 C 语言程序设计的教材和专著都是以本书为蓝本的。 汤姆泼逊和里奇在成名以后,都没有走办公司、挣大钱的路,他们仍在贝尔做他们喜爱 第1章C 语言导论13做的事,而且还一直保持着他们历来的生活习惯和作风,常常工作到深夜,在贝尔是出名的 “夜猫子” 。里奇在接受记者采访时,就自称自己是 “definitely a night person” 。里奇 1983 年接受图灵奖时已经 42 岁,但仍然单身。 获双项大奖 ACM 于 1983 年 10 月举行的年会上向汤姆泼逊和里奇颁奖。有趣的是,ACM 当年决定 新设立一个奖项叫 “软件系统奖” (Software System Award), 奖励优秀的软件系统及其开发者。 而首届软件系统奖评选结果中奖的还是 UNIX。这样,这届年会上汤姆泼逊和里奇成了最受 关注的大红人,他们同时接受了“图灵”和“软件系统”两个大奖,这在 ACM 历年的颁奖 仪式上也是从来没有过的。 第2章数据14第2章 数据2.1 基本数据类型所有计算机程序都是以处理数据为目的而存在的,数据是计算机程序能够处理的所有信 息在计算机内的表现形式。在计算机内部,数据是以某种特定形式存在的,例如,人类首次 登上月球是 1969 年,1969 是个整数;嫦娥二号飞船的最快速度是 10.848 公里/秒,10.848 是一个实数。 在计算机中,虽然所有数据都是以二进制方式保存的,但不同类型数据的存储格式和处 理方法却可能是不同的,例如整数和实数在计算机内部的存储格式和处理方法都是不同的。 然而因为计算机内部存储的所有数据都是二进制形式, 例如 101010 这样一串数 据它是整型还是实型呢?如果只凭内存中存储的二进制数据内容是无法区分它是属于哪一种 数据类型的。 为了对计算机内部存储的不同数据进行区别,C 语言要求必须在程序中对存储的数据指 定数据类型,这样在程序执行的时候才能知道如何存储、读取和处理这些数据。C 语言提供 了多种数据类型,用户在使用数据时必须要指定这个数据的类型,这样,C 语言编译程序才 能知道用户想如何存储和处理这些数据。在 C 语言中,基本数据类型主要有整型、浮点型、 指针类型三大类。2.1.1整型数据在计算机中,数据可分为有符号数和无符号数两种,例如:如果保存一个人年龄,是不 存在负数的,可以不使用正负符号;如果保存的是一个人的账户收支,那么就会有收入和支 出,收入和支出对一个人账户数值的影响是相反的,如果收入为正数,那么支出就应该是负 数。在计算机中保存的个人账户的数据应该包括正负符号的。 在 C 语言当中,把整数分成了两大类,即无符号整数和有符号的整数,这两种整数在计 算机中的存储方式是不同的。无符号整数在内存中以二进制原码的形式存放,有符号整数要 用一个二进制位来存放正负符号, 这一位通常是保存这个数据的所有二进制位中的最高位, 0 代表这个数是个正数,1 代表这个数是个负数。除了有符号位的区别,有符号数和无符号数 保存数的形式也有所区别,有符号数的正数以二进制原码的形式存放,负数以二进制补码的 形式存放。 第2章数据15例如:整数 50 的二进制原码为 110010,假设用一个字节 8 位来存放这个整数,且 50 以 无符号整数的形式存放, 因为 110010 不足 8 位, 在高位补 0, 在内存中的存放形式为
; 如果以有符号整数的形式保存,则在内存中存放的最高位为 0,后面只剩下 7 位用于保存数 据,因为 110010 不足 7 位,则高位补 0,即
在内存中的保存形式为
。 -50 以有符号整数存放,则在内存中存放的最高位为 1,后面剩下 7 位用于保存数据,-50 的 二进制补码为 001110, 因为 001110 不足 7 位, 则负数补码高位不足的 高位补 , 即 1001110, ......... ...1 . -50 在内存中的保存形式为
。 整型数据除了可分为有符号和无符号之外,数值的大小也可能相差很大,大的如地球到 月亮的平均距离为 384401 公里, 小的如一个人年龄最多不过 100 多岁, 如果这两种数据都采 用一种方式存储,即占用同样多的内存显然是不合理的,所以在 C 语言中把整型数据根据数 值的范围的大小分成四个档次,即字符型、短整型、标准整型、长整型。字符型是给一个整 数一个字节的内存,短整型是给一个整数二个字节的内存,长整型是给一个整数四个字节内 存, 标准整型对于不同的编译程序有所差别。 例如 TC 中一个标准整型的整数分配两个字节内 存、VC++、GCC 中一个标准整型的整数分配 4 个字节内存。 由于表达不同范围的整数需要使用不同数量的二进制位,用户可以根据程序应用的实际 情况为程序中使用的整型数据指定合适的整数类型。如果一个整数在转换成二进制后所占用 的位数超过了分配给它的内存位数,超出的部分将被计算机直接抛弃。例如,如果为一个整 数分配了一个字节的内存,并指定为无符号整数类型,那么它在内存中能够使用的位数只有 8 位,如果一个整数转换为二进制后实际需要 10 位,那么它在保存到分配给它的 8 位内存的 时候将发生溢出,高 2 位将被抛弃。 【例 2.1】500 按有符号字符类型的数据保存,值会变成多少? 500 转换为二进制数据后为 ,字符类型只有 8 位内存,500 的最高位被抛弃, 内存中保存的是
,程序在读这个数据时,因为
的最高位是 1,会把它当成 一个负数,然后就会认为后七位 1110100 是一个补码,然后根据补码求得到值是 12,在加上 前面的负号,
对应的十进制是数据-12,计算机就会把这个数当成-12 进行处理。 在 C 语言中,一个数是否有符号可以用 signed、unsigned 说明,signed 代表有符号数 据,unsigned 代表无符号数据。占内存多少用 char、short int、int、long int 说明,char 型也被称为字符型,占 1 个字节内存;short int 也被称为短整型,占 2 个字节、int 也被称 为整型、根据编译器不同占用字节也不同,通常 2 或 4 个字节。long int 也被称为长整型, 通常占 4 个字节内存。 一个字节的整型被称为字符型或 char 型, 跟它的主要用途有关。 因为计算机内存中不能 直接保存字符,但又需要在计算机程序中处理字符信息,所以人们就对常有的字符进行了编 码,这个编码就是一个整数值。计算机的内存中虽然不能直接存储一个字符,但可以存储这 个字符的编码,这样就可以把字符信息保存在计算机内存当中了。因为计算机是西方人发明 的,西方语言中使用的字符数量比较少,所以这个整数编码数值也不大,通常只要用一个字 节的内存就可以保存下来了,所以大量的计算机程序中都使用一个字节的内存存储一个字符 的编码,而实际上一个字节的整型数据也主要用于保存字符的的编码,所以 C 语言中就把一 个字节的整数型直接命名为字符型或 char 型。 下表以 VC++为例说明不同类型的整型数据在内存中占用内存的大小和能够存储数值的 第2章数据16具体范围。表 2.1 在 VC++中整型数据能够存储数值的范围 类型 unsigned char signed char unsigned short int signed short int unsigned int signed int unsigned long int signed long int 字节数 1 1 2 2 4 4 4 4 数值范围 0 ~ 255 -128 ~ 127 0 ~ 6 ~ 32767 0 ~
- ~ 完整说明一个整数的类型需要说明该整数是否有符号、 占内存多少, 例如 unsigned char 说明是无符号字符型数,signed short int 说明是有符号短整型数。为了提高 C 程序的书写 效率,在 C 语言中规定,基于不能引起冲突的原则,对于有符号整数,前面的 signed 说明 可以省略,即 signed short int 可简写为 short int。同样为了提高程序书写效率,在 C 语 言中规定,对于短整型,short int 说明可以简写为 short,对于长整型,long int 说明可 以简写为 long。 下面通过几个例子来说明不同类型整型数据的存储形式: 【例 2.2】 将 50 以 unsigned char 形式存储,在内存中的存储形式为: unsigned char 有一个字节即八个二进制位的内存空间, 50 的原码为 110010, 只有六位, 则多余的两位不能空着,全部补 0,即
。 【例 2.3】 将 50 以 signed char 形式存储,在内存中的存储形式为: signed char 有一个字节即八个二进制位的内存空间,50 的原码为 110010,符号位为 0, 即 0110010,只有七位,则多余的一位不能空着。规则是有符号整数,高位不足的按符号位 补足,即
。 【例 2.4】 将-50 以 signed char 形式存储,在内存中的存储形式为: signed char 有一个字节即八个二进制位的内存空间,50 的补码为 001110,符号位为 1, 即 1001110,只有七位,则多余的一位不能空着。规则是有符号整数,高位不足的按符号位 补足,即
。 【例 2.5】已知内存中某字节的存储形式为
,且知该字节存储一个有符号字符 型数,求该数值是多少? 有符号字符型,说明最高位是符号位, 的最高位为 1,则说明该数为一负数, 负数存放的是补码, 需要求原码, 去掉符号位的后得 1001110, 求原码得到 110010, 由 110010 得十进制值 50,加上前面的符号,说明该字节存储一个有符号字符型数数值是-50。 第2章数据172.1.2浮点型数据在 C 语言中,实型数据被称为浮点型数据。一个浮点型数据在内存中的存储形式比整型 数据要复杂的多。首先要将实型数转换为一个纯小数 x 乘以 2 的 n 次方的形式(n 可以取负 值) ,x 被称为该实型数据的尾数,n 被称为该实型数据的指数,然后把尾数和指数在内存中 分别存储。 浮点型数据也分为有符号浮点数和无符号浮点数,分别用 signed 和 unsigned 来说明。 signed 浮点数在内存中保存的内容分为符号、指数符号、指数、尾数四部分存储,unsigned 浮点数在内存中保存为指数符号、指数、尾数三部分。 浮点型数据占据的字节数越多,能够保存的尾数和指数的内存位数就越多,描述的数值 精度和范围也就越大。但有些实数的精度和范围要求并不高,基于减少内存浪费的原则,在 C 语言中的浮点型数据也被分为单精度浮点型、 双精度浮点型、 高精度型, 分别用 float、 double、 long double 表示。对于大多数编译程序,float、double 分别占用 4 个字节和 8 个字节内存, long double 占用内存多少由编译器决定, 但 long double 占用内存要大于或等于 double 所占用 的内存。表 2.2 在 VC++中浮点型数据能够存储数值的精度和范围 类型 float double long double 字节数 4 8 8 有效数字 6~7 15~16 15~16 数值范围 10 ~10 10 10-307 -37 38 308~10 ~10-307308在程序中使用浮点数时需要注意的是,除了浮点数受描述数值的范围影响,还要受描述 数值精度的影响,有时候还受十进制实数转换为二进制实数的规则限制,可能不能准确地将 一个十进制的实数转化为相等的二进制浮点数。由于在计算机中浮点数的存储和处理都比整 数复杂,所以在程序中能用整数类型处理数据的尽量不要用浮点数类型处理,这样可以显著 提高程序的执行效率。2.1.3指针型数据通过第 1 章讲述的程序运行过程可以知道,程序在被操作系统加载到内存后才能运行, 不论是程序数据还是程序指令,在程序运行状态下都是保存在计算机内存中的,如果一条指 令要访问程序的其它部分的指令或数据,都要到内存中去寻找,程序为了在内存中找到它想 要的指令或数据,必须要在内存中对它想要找的对象进行定位。… 101 102 103 104 105 106 107 108 …图 2-1 内存地址及单元在计算机内部,如图 2-1,计算机的内存就像一条长街上的一排房子,每间房子都可以 保存一个字节共 8 位的二进制数据,且每间房子都有一个门牌号码,这个门牌号码就是内存 第2章数据18的地址,内存地址是一组从小到大连续增长的整数,在程序中只要知道它要访问的对象的内 存地址就可以顺利找到它要访问的内容。C 语言中专门定义了一个数据类型用来保存内存地 址,这种数据类型就叫做指针。 指针类型数据存储的就是专门代表内存地址的整数。由于在计算机中通常规定内存地址 是从 0 开始顺序增长的,所以指针类型数据存储的实际上是无符号的整数数据,每个地址对 应的内存空间都可以容纳 1 个字节的 8 位二进制数据。 在 C 语言中不直接使用无符号整数类型来保存内存地址是因为内存地址即指针型数据和 无符号整型数据的处理方式有很大差别的。例如把两个无符号的整数相乘是有意义的,但把 两个内存地址相乘(等价于把两个门牌号地址相乘)显然是没有任何意义的,也是不允许的, 所以指针类型的数据和一般的整型数据能够参加的运算是不同的,因为指针型数据和无符号 整型虽然存储方式相同,但在处理方式存在很大的差别,所以 C 语言专门定义了指针类型来 保存内存地址数据。 不同的编译程序对于指针类型数据占用的内存大小的定义是不同的,TC 编译器是 2 个 字节,VC++编译器是 4 个字节、GCC 大部分也是 4 个字节。 需要注意的是虽然每个字节内存都有一个地址,但每个地址不一定只对应一个字节的内 存,例如一个整型数据占用 4 个字节的内存,但我们不希望一个整型数据有 4 个地址,所以 我们只把这 4 个字节中开始字节的地址作为这个整型数据的地址。 因

我要回帖

更多关于 谭浩强c语言程序设计 的文章

 

随机推荐