第五章C程序结构,裘宗燕从问题到程序(2003

c怎么计算 7

年修订),第五章 第五章C程序结构 本章讨论一些与C程序整体结构有关的问题,它们对于正确理解C语言,正确书写C程序都非常重要。
有些人使用C语言许多年,但仍常犯一些错误,自己也常弄不清楚错在哪里,其原因往往就是一些基本问题不够清楚。
本章的讨论方式是希望读者在学习C语言编写程序的过程中,也能了解一些更深的原由。
在安排这章的材料时,希望能把问题讲得比较透,也能多举出一些例子,帮助读者理解。
还有些更有意思的例子出现在后面章节里。
本章有些内容比较深入,读者第一次阅读时可能遇到一点困难,未必能完全理解。
这也没有关系,建议读者在学了后面章节后,重新回来读读这章的内容。
5.1数值类型 第二章里已介绍了几种常用数值类型。
本节对C的所有数值类型做一个全面介绍。
实数类型和整数类型 实数类型共有三个,类型名分别是:float,double,longdouble 这些类型都已在第二章节介绍了。
请注意各种实数文字量的书写形式,其中必须包含小数点或指数部分。
不加后缀的是double类型文字量,float类型的文字量在数值表示后面加后缀f或F,longdouble类型文字量加后缀l或L(建议用大写
L,小写l容易与数字1混淆)。
实数类型内部编码一般采用有关的国际标准(IEEE标准)。
例如: floatf1,f2;doublex1,x2;longdoublel1,l2; 除了实数类型之外的数值类型都是整数类型。
C语言将字符类型也看作整数类型,可以作为整数参加运算。
各种整数类型都分为带符号与无符号的两种,带符号类型表示一定范围内的正数和负数,无符号类型的值都不小于
0。
在类型名前加signed或unsigned,说明一个整数类型是带符号的或是无符号的。
其中signed都可以省略,也就是说,不加特别说明的都是带符号类型。
字符类型在这方面的情况比较特殊。
字符类型 写简单程序时通常只用字符类型char。
在一般C语言系统里,一个字符占一个字节,其中存着字符的编码。
字符类型主要用于存储文字信息和输入输出。
如果将字符的使用限制在这一范围里,我们将不会遇到任何复杂情况。
如果把字符当作整数参加运算,所用的就是字符的编码(这是一个整数)。
由此可见,一个字符与其他整数运算时起什么作用,要看所用的计算机系统里字符的编码方式。
在目前使用最广泛的编码系统(如ASCII或EBCDIC编码系统)里,数字字符和英文字母字符的编码都是顺序地连续排列的。
常常可以看到一些C程序利用了这一特征。
例如,我们可能看到程序里有下面片段: if(c>='a'&&c<='z')....../*判断c中存储的字符是否小写字母*/n=d-'0';/*将变量d里保存的数字字符的“数值”赋给整型变量n*/ 这里假定c、d保存着字符的编码,而d中存的是某个数字字符。
因为数字字符的编码连续排列,假设变量d的值为'3',d-'0'(字符0的编码)的结果正好是
3。
实际上,C语言还有signedchar和unsignedchar两个字符类型,普通char类型等价于这两者之
一,等价于哪一个要看具体的C系统。
如果程序里只用普通的可打印
1 裘宗燕 从问题到程序(2003年修订),第五章 字符(字母、数字、各种标点符号、空白字符等),这方面的情况不会产生任何影响。
把字符区分为“有符号字符”和“无符号字符”,这一点不太好理解,似乎也没有什么 道理。
问题只出现在用字符类型的数据与其他整数运算时:如何看待字符所表示的数?是把字符数据看成有符号的整数呢,还是看成无符号的整数?这方面的问题在写初级程序的时候完全没有必要去考虑。
现在只需要知道这种情况。
整数类型 基本的整数类型有三个: int,shortint,longint其中shortint可以简写为short,而longint可以简写为long。
这三个类型都有对应的无符号类型,因此整数类型实际上有六个: int short long unsignedintunsignedshortunsignedlong这里的unsignedint还可以简写为unsigned。
上表里许多类型名已经是简写形式,例如,unsignedlong的完整形式是unsignedlongint。
C语言标准没有规定int、shortint、longint的具体实现方式(二进制编码长度),只规定了一些原则,主要有:short类型的表示范围不大于int类型的表示范围,long类型的表示范围不小于int的表示范围。
并规定short至少为16位,long至少为32位;各unsigned类型总采用与对应signed类型同样长度的表示。
每个类型的具体表示(用多少位表示,用什么编码方式等)由具体C语言系统规定。
这里最基本的类型是int。
C系统里的int类型一般采用相应计算机的字长。
例如,16位计算机的C语言系统的int类型通常采用16位表示方式;而在32位计算机的C语言系统中int类型通常用32位表示。
PC机上C系统的情况比较复杂。
一些老的C系统(DOS,Windows3.1上的C系统)通常采用16位的int类型,因为16位是8086/8088CPU的字长。
这时int类型的表示范 围是-32768~32767,即−215~215−
1。
unsignedint用16位,表示范围是0~65535,即0~216−
1。
这些系统里的long类型通常用32位表示,short类型通常也用16位表 示。
一些新的C系统(如运行在WindowsNT、Windows95/98/2000等系统上C系统)则采用32位的int类型,long类型用与int一样的表示方式,short用16位表示。
有关具体C系统里各种类型的情况,使用前应该查阅系统的有关材料。
如语言手册、联机帮助信息或有关书籍。
C标准库里有两个名字分别为“limit.h”和“float.h”的文件,其中列出了与本系统所有数据类型表示有关的信息,读者可以查阅所用C系统里这的这两个文件。
这方面的进一步细节请参看第11章里对标准库情况的介绍。
整数类型的文字量用连续数字序列表示。
前面已讲过整型字面量的十进制、八进制和
六进制表示问题。
如果需要特别表示写的是长整数,就应加上后缀l或L,short类型没有字面量写法。
无符号整数的后缀是u和
U,无符号长整型加后缀UL或者LU均可。
下面是一些无符号整数的字面量的例子: 123U,2987654LU,327LU,32014U 无符号整数类型的另一个特点是算术运算以对应类型的表示范围为模进行。
当计算结果超出 类型的表示范围时,以取模后的余数作为计算结果。
假定unsigned用16位表示,表示范围是0~65535。
如果计算结果超出这个范围,就以得到的结果除以65536的余数作为结果。
例如234+65500的结果将是198。
其他无符号类型的情况也一样。
由于类型问题,计算中可能出现隐含的类型转换动作。
C语言规定,当各种小整数类型(short、unsignedshort类型,各种char类型)的数据出现在表达式之中,计算之
2 裘宗燕 从问题到程序(2003年修订),第五章 前先将它们转换为int类型的值后再参与运算,这一过程称为整数提升。
如果某类型的一个值超出了int的表示范围(例如unsignedshort类型的提升时就可能出现这种情况),那么在整数提升中将其提升为无符号整数类型。
前面讲过,两个不同类型的数值对象进行运算前,要把小数据类型的值转换到大数据类型。
现在还要补充一点:在基本类型相同时,C语言认为无符号类型是比同样有符号类型更大的类型。
举例说,如果要做下面计算: 2365U+18764 首先要从整型值18764转换生成一个无符号整数的对应值,然后用这个新值参与计算。
如果需要转换的有符号整数的值为负,转换结果依赖于具体的系统。
如果要求将无符号数转换到有符号数(通过强制转换或者赋值、参数传递等),那么也按前面所说的规则处理:如果原类型的值能在转换的目标类型中表示,那么转换后的值不变,否则转换结果依赖于具体的系统。
基本数据类型的选择 C语言提供了多个浮点数类型和多个整数类型,目的是使编程者有较多选择机会,满足复杂的系统程序设计中的各种需要。
C语言应用广泛,不同程序或软件中对数值的表示范围和精度的要求会有很大差异。
对于某些应用问题而言,选择合适的数值类型可能很重要,在写那些程序时,人们就需要更仔细地考虑,确定每一个变量应该采用哪个数值类型。
这是专业C程序员的写重要程序时的一项工作。
然而,对于一般程序,特别是对于学习C程序设计而言,这种选择就不那么重要了。
现在提出如下的类型选择原则,这也是在大多数C程序里的最合理选择:
1.如果没有特殊需要,浮点数总采用double类型,因为它的精度和表示范围能满足
般要求(float的精度常常不够,longdouble可能降低效率)。

2.如果没有特殊需要,整数总采用int类型,因为它是每个C系统里的最基本类型,必 定能得到硬件的基本支持,其效率不会低于任何其他整数类型。

3.如果没有特殊需要,字符总采用char类型。

4.尽量少用各种unsigned类型,除非服务于某些特殊目的。
5.2函数和标准库函数 随着要处理的问题越来越复杂,程序也会变得越来越长。
程序长带来许多问题:长的程序开发困难,牵涉的情况更复杂,写程序的人更难把握。
长程序的阅读和理解也更困难,这又影响到程序的开发和维护。
如果要修改程序,就必须先理解一项改动对整个程序的影响,防止其破坏了程序的内在一致性。
另外,随着程序变大,程序中也常出现一些相同或类似的代码片段,这使程序变得更长,也增加了程序里不同部分间的互相联系。
处理复杂问题的基本方式就是设法把它分解为一些相对简单的部分,分别处理这些部分,然后用各个部分的解去构造整个问题的解。
为支持复杂计算过程的描述和程序设计,就需要程序语言提供分解复杂描述的手段,需要有把代码段抽象出来作为整体使用和处理的手段。
随着人们对程序设计实践的总结,许多抽象机制被引进了程序语言。
这些机制极为重要,人只有借助于它们才可能把握复杂的计算过程,完成复杂的程序或软件系统。
C是70年代初研制开发的语言,那时人们在这方面的认识还比较粗浅,所以这里只提供了对计算过程片段的抽象机制,这就是前面已初步介绍过的函数机制。
函数的作用是使人可以把一段计算抽象出来,封装(包装)起来,使之成为程序中的一个独立实体。
还有为这样封装起的代码取一个名字,做成一个函数定义。
当程序中需要做这段计算时,可以通过一种简洁的形式要求执行这段计算,这种片段称为函数调用。

3 裘宗燕 从问题到程序(2003年修订),第五章 函数抽象机制带来了许多益处:
1.重复出现的程序片段被一个唯一的函数定义和一些形式简单的函数调用所取代,这样有 可能使程序变得更简短而清晰。

2.由于整个程序里同样的计算片段仅描述一次,需要改造这部分计算时,就只要修改一个 地方:改变函数的定义。
程序的其他地方可能完全不需要修改。

3.函数定义和使用形成对程序复杂性的一种分解,使人在程序设计中可以孤立地考虑函数 本身的定义与函数的使用问题,有可能提高程序开发的效率。

4.把具有独立逻辑意义的适当计算片段定义为函数后,函数可以看成是在更高层次上的程 序基本操作。
一层一层的函数定义可以使人可以站在一个个抽象层次上去看待和把握程序的意义,这对于开发大的软件系统是非常重要的。
在前面章节里,已经讨论了许多有关的实例。
5.2.1C语言的库函数 C语言是一种比较简洁的语言,其基本部分较小,例如,语言本身甚至没有提供输入输出功能的结构。
C程序所需要的许多东西都是通过函数方式提供的。
每个C系统都带有一个相当大的函数库,其中以函数方式提供了许多程序中常用的功能。
ANSIC标准对函数库做了规范化,总结出一批最常用的功能,定义了标准库。
今天的每个C系统都提供了标准库函数,供人们开发C程序时使用。
标准库的功能通过一批头文件描述,如果要使用标准库的功能,就需要用#include命令引进相应头文件。
此外,具体C系统通常还根据其运行环境的情况提供了扩充库,使采用这个C系统开发的程序可利用特定硬件或操作系统的功能等。
例如,运行在微机DOS系统上的C系统将提供一批利用DOS系统特定功能的函数;运行在Windows上的C语言系统都提供了一批与Windows环境有关的函数;运行在UNIX上C的系统必定提供一批与UNIX系统接口的函数。
扩充库的功能也是通过一批头文件描述的,使用它们使也需引入相应头文件。
无论是标准库函数还是扩充库函数,都可看作常用计算过程的抽象。
如果写程序时需要,就可以按规定方式直接调用这些函数,不必自己写程序实现这些功能,也不必关心这些函数是如何实现的。
这样,开发C系统的人只做了一次工作,就使所有使用该系统编程序的人都节省了大量时间和精力。
由此可以明显看到函数的意义和作用。
C标准库函数完成一些最常用的基本功能,包括基本输入和输出、文件操作、存储管理,以及其他一些常用功能函数,如数学函数、数据值的类型转换函数等。
对这些函数的介绍散布在本中各个章节里。
第11章包含对标准库其他重要函数的介绍。
至于具体C系统的扩充函数库,就需要查阅系统联机帮助材料、系统手册或其他参考书籍。
学习本课程时也应学会使用手册和联机帮助材料,学会如何阅读它们。
下面介绍两组简单函数。
5.2.2字符分类函数 首先介绍标准库文件ctype.h描述的各种字符分类函数。
这些函数很简单,它们对满 足条件的字符返回非0值,否则返回0值。
下面是有关函数: isalpha(c) c是字母字符 isdigit(c) c是数字字符 isalnum(c) c是字母或数字字符 isspace(c) c是空格、制表符、换行符 isupper(c) c是大写字母 islower(c) c是小写字母 trl(c) c是控制字符 isprint(c) c是可打印字符,包括空格
4 裘宗燕 从问题到程序(2003年修订),第五章 isgraph(c)isxdigit(c)ispunct(c) c是可打印字符,不包括空格c是十六进制数字字符c是标点符号 要使用这些函数,应当在程序前部用#include命令包含系统头文件ctype.h。
在这个头 文件里还说明了两个字母大小写转换函数: inttolower(intc)inttoupper(intc) 当c是大写字母时返回对应小写字母,否则返回c本身当c是小写字母时返回对应大写字母,否则返回c本身 例如,下面程序统计文件中数字、小写字母和大写字母的个数,其中使用了标准库的几个字 符分类函数。
采用标准库函数的做法比自己写条件判断更合适,值得提倡。
#include#include intmain(){ intc,cd=0,cu=0,cl=0;while((c=getchar())!
=EOF){ if(isdigit(c))++cd;if(isupper(c))++cu;if(islower(c))++cl;}printf("digits:%d\n",cd);printf("uppers:%d\n",cu);printf("lowers:%d\n",cl);return0;} 5.2.3随机数生成函数 计算机程序实现的都是确定性的计算:给一个或者一组初始数据,它总计算出一批确定 的结果。
然而计算机应用中有时也需要带有随机性的计算。
一个例子是程序调试。
在程序调试时,人们需要用各种数据进行程序运行试验,看能否 得到预期结果,有时用随机性数据作为试验数据是很合适的。
另一个应用领域是计算机模拟, 也就是用计算机模拟某种实际情况或者过程,以帮助人认识其中的规律性。
客观事物的变化 中总有一些随机因素,如果用确定性数据进行模拟,多次模拟得到的结果完全一样,将无法 很好地反映客观过程的实际情况。
由于这些情况,人们希望能用计算机生成随机数。
实际上,计算机无法生成真正的随机 数,通过计算只能生成所谓的伪随机数。
如何用计算机生成随机性比较好的随机数仍是人们 研究的一个问题。
最常用的随机数生成方法是定义一种递推关系,通过这个递推关系生成
个数的序列,还要设法使这个序列中的数看起来比较具有随机性。
最常用的简单递推关系是通过除余法定义的关系: α0=
A,αn=(B×αn−1+C)modD对n>0这里
A,B,
C,D都是整常数,0≤A<
D。
通过很好地选择常数
B,C,可以产生出值位于0到D−1范围中的比较好的随机数列。
自己定义随机数的生成函数的问题留到后面讨论,这里介绍C标准库提供的随机数功能。
要使用标准库的随机数生成功能,程序前部应包含系统文件stdlib.h,这个文件里描述了与随机数生成有关的函数: intrand(void) 这是一个无参函数,每次调用将得到一个新随机整数,其值在0和系统定义的符号常量RAND_MAX之间。
不同系统里的RAND_MAX可能不同,一般系统中用32767。
voidsrand(unsignedseed) 这个函数用参数seed的值重新设置种子值,即为生成下一个随机数而保存的一个整数值
5 裘宗燕 从问题到程序(2003年修订),第五章 (由它出发递推,取得下一个随机数)。
默认的初始种子值是
1。
举个例子。
下面程序先打印出由默认种子值出发的10个随机数,而后打印设定了新的 种子值11后生成的10个随机数: #include#include intmain(){ inti;for(i=0;i<10;++i)printf("%d",rand());putchar('\n');srand(11);for(i=0;i<10;++i)printf("%d",rand());putchar('\n');return0;} 程序里的putchar('\n');使输出换一行。
在某个系统里这个程序输出: 4118467633426500191691572411478293582696224464742764821136498924011222239834852823828519 5.3函数定义和程序的函数分解 无论系统提供多少库函数,其数量终归有限,编程时总要考虑定义自己的函数。
一个C程序主要由一系列函数定义组成。
每个函数定义包含一段程序代码,执行时将完成一定工作。
定义函数时给定了一个名字,供调用这个函数时使用。
函数定义的基本形式是: 返回值类型函数名参数表函数体返回值类型描述函数执行结束时返回的值类型;函数名用标识符表示,主要用于调用这个函数;参数表描述函数的参数个数和各参数的类型;函数体是一个复合语句,描述被这个函数所封装的计算过程。
函数体之前的部分称为函数头部,它描述了函数外部与函数内部的联系,下面要着重讨论这部分的问题。
例如,下面是一个我们熟悉的函数: longfact(intn){intfac,i;for(fac=1,i=1;i定义函数时可以不写返回值类型,这表示返回int类型的值。
这种做法不应提倡,因 为它容易引起错误(未来的C系统将禁止不写类型的形式)。
我们也可以定义不返回值的函数,这时用关键字void说明“返回值类型”。
这种写法很别扭,是C语言把两类东西(有返回值和无返回值的函数)用同一形式写出而带来的副作用。
一个函数可以有任意多个参数,各参数描述用逗号分隔。
每个参数描述包括一个类型名和一个参数名。
函数定义的参数表里给出的参数名称为函数的形式参数,简称形参。
定义无参函数时参数表应写成()或者(void)。
后一写法很不自然,是早期C语言的遗留问题对新ANSIC标准的影响,我们只能接受。
没参数的函数又称无参函数。
5.3.1主函数 每个C程序里总有一个名为main的特殊函数,常称为主函数。
主函数规定了整个程序执行的起点,专业术语是程序入口。
程序执行从这个函数开始,一旦它执行结束,整个程序就完成了。
程序里不能调用主函数,它将在程序开始执行时被自动调用。
C语言规定主函数的返回值必须是int类型。
有些书里的程序示例没描述main的返
6 裘宗燕 从问题到程序(2003年修订),第五章 回值类型,按上面所说,这正说明其返回值为int。
主函数的返回值不会在本程序内部使用(因为main不能在程序内调用),这个值将在程序结束时提供给操作系统。
在程序外部,例如操作系统,可以检查和使用程序的这个返回值。
写C程序时应为主函数定义返回值,一般用返回值0表示程序正常结束,用其他值表示执行中出现了非正常情况。
按语言规定,在主函数结束时如果没有提供返回值,程序将自 动产生一个表示成功完成的返回值(通常就是0)。
一些C系统会对主函数返回值的情况产生警告,这是不对的,但我们也不必戒意。
这样main函数的样子就是: intmain(){......return0; } 除了主函数外,程序里的其他函数只有在明确调用时才能进入执行状态。
所以,一个函 数要在程序执行过程中起作用,那么它或是被主函数直接调用的,或是被另外一个能被调用 执行的函数调用的。
没有被调用的函数在程序执行中不会起任何作用。
5.3.2程序的函数分解 在编写大些的程序时,应该特别注意程序的功能分解,在这里也就是函数分解。
也就是 说,应该把程序写成一组较小的函数,通过这些函数的互相调用完成所需要的工作。
初学者 往往不注意函数分解,一些教学材料或书籍中对这个问题强调得也很不够,给出的程序例子 经常是一大片,没有结构性,初学者不良编程习惯的形成往往与此有关。
实际上,在学习程 序设计的过程中强调函数分解是绝对必要的,没有合理的函数分解,完成规模较大的程序将 更困难,要花费更多时间,写出的程序通常也更难理解,出现了错误更难发现和改正。

点值得读者特别注意。
一般说,一个C程序 由一组函数构成,图5.1显示了一个C程序的结构和执行中的调用关系。
#include...intf(...){ ...f(...)...}intg(...){ ...f(...)... main 左边是程序的概貌,其中除主函数外还定义了三个函数,这些函数互相调用。
右边图形显示了调用关系,矩形表示函数,箭头表示函数调用。
递归调 }voidh(...){ ...f(...)......g(...)...}intmain(void){...h(...)......g(...)... h g f这里的箭头表示函数调用关系 用(函数f调用自己)表} 现为到自身的箭头。
图5.1源程序及其确定的函数调用关系示意图 问题是:什么样的程 序片段应当定义成函数呢?这并没有万能的准则,程序设计者需要自己分析问题,总结经验。
这里提出两条线索,供读者学习时参考:
1.

程序中可能有重复出现的相同或相似的计算片段。
可以考虑从中抽取出共同的东西,定 义为函数。
这将使一项工作只有一个定义,需要时可以多次使用。
这样做不但可能缩短 程序,也将提高程序的可读性和易修改性。

2.

标签: #content #图片 #怎么做 #网页 #电脑 #箭头 #文件 #应用程序