




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、第4章 函 数,4.1 函数的定义与声明 4.2 函数的调用 4.3 变量的存储类别 4.4 内部函数和外部函数 4.5 编译预处理 本章小结 习题四,4.1 函数的定义与声明,4.1.1 函数的概念 通过前面的学习,我们知道C语言源程序是由函数组成的。函数是构造C程序的基本模块,它相当于其他高级语言中的子程序。实际上,C语言源程序可以由一个主函数和若干个其他函数组成。其他函数可以是标准的库函数,也可以是用户自己编写的函数。每个函数具有完整的、独立的功能,从而使程序整体呈现出清晰的结构。,可以说在解决实际问题时,无论涉及的问题是复杂还是简单,规模是大还是小,用C语言编写程序,其任务都只有一个,
2、那就是编写函数,至少要编写一个main()函数。可以说C程序的功能就是由各式各样的函数实现的,即 函数模块功能模块 语言中“函数”的概念与数学上“函数”的概念有相似之处,但又不完全相同。在英语中“函数”与“功能”是同一个单词,即function。所以,从理解的角,度,与其说“函数模块”,倒不如说“功能模块”更恰当,或者说“模块”更简单。 程序通过对函数模块的调用可实现特定的功能。可以把函数看成一个“黑匣子”,只要将数据送进去就能得到结果,而函数内部究竟如何工作,外部程序是不知道的。外部程序所知道的仅限于输入给函数什么以及函数会输出什么。 从定义的角度看,函数可以分为系统库函数和用户自定义函数。
3、,(1)系统库函数。系统库函数也叫标准库函数,简称标准函数。为了用户使用方便,每一种版本的C编译系统都提供一批由商家开发编写的函数,放在一个库中,这就是函数库。函数库中的函数称为库函数,或系统库函数。使用系统库函数时,用户无须定义,也不必在程序中声明类型,只要在主程序前用编译预处理命令将有关该函数原型的头文件包括到程序中即可。 ANSI C和Turbo C 2.0均提供了300多种库函数,这些库函数从功能上可分为以下几种:,1)字符分类函数:用于对字符按ASCII码分类(分为字母、数字、控制字符、分隔符、大小写字母等)。 2)转换函数:用于字符或字符串的转换(在字符量和各类数字量(整型、实型等
4、)之间进行转换;在大、小写字母之间进行转换)。 3)目录路径函数:用于文件目录和路径操作。 4)诊断函数:用于内部错误检测。 5)图形函数:用于屏幕管理和各种图形输出。,6)输入/输出函数:用于完成输入/输出。 7)接口函数:用于与DOS,BIOS和硬件的 接口。 8)字符串函数:用于字符串操作和处理。 9)内存管理函数:用于内存管理。 10)数学函数:用于数学函数计算。 11)日期和时间函数:用于日期、时间进行转换操作。 12)进程控制函数:用于进程管理和控制。,13)其他函数:用于其他各种功能。 以上各类函数不仅数量多,而且有的还需要硬件知识才会使用,因此想要全部掌握需要一个较长的学习实践
5、过程。应首先掌握一些最基本、最常用的函数,再逐步深入。 (2)用户自定义函数。库函数完成的功能是有限的,要解决用户的专门问题,就需要自己编写所需的函数,即由用户自己定义的函数,简称自定义函数。自定义函数时不仅要在程序中定义函数模块,本身,而且还要在主调函数模块中对该被调函数进行类型声明,然后才能使用,即用户把自己的算法编成一个个相对独立的函数模块,然后通过调用的方法来使用自己的函数。 从函数的形式看,函数又可以分为无参函数和有参函数。 (1)无参函数,即函数中没有任何参数。在调用无参函数时,主调函数并不将数据传送给被调函数。无参函数可以返回或不返回函数值,但一般以不返回函数值为多。,(2)有参
6、函数,即函数中包括相应的参数。在调用有参函数时,在主调函数和被调函数之间存在着数据的传递关系。也就是说,主调函数可以将数据传给被调函数使用,被调函数也可以回送数据给主调函数使用。 一个C源程序文件一般由一个或多个函数组成。一个大的项目程序一般由一个或多个源程序文件组成,一个源程序文件是一个独立的编译单位。,4.1.2 函数的定义 一般来说,函数只定义不声明不能使用,无定义有声明则属非法。 定义函数是在程序中编写一条块分割、相对独立、功能单一、接口简单、调用容易、有返回结果的模块。 函数定义的一般形式如下: 类型标识符 函数名(形式参数表列) 函数体 ,其中:“类型标识符 函数名(形式参数表列)
7、”称为函数首部(function header),“类型标识符”必须是C语言合法的关键字,“函数名”可以是用户自定义的标识符,“形式参数表列”是用逗号隔开的若干临时变量。“函数体”(function body)是一个复合语句,一般由说明部分和语句部分组成。 【例4.1】输入3个整数,求出其中最大值。 程序:,程序运行情况: 屏幕提示:Input the datas: 输入数据:65 24 31 输出结果:Max is 65 分析: 自定义函数max()的功能是求两个数中的较大数。main()两次调用自定义函数max(),求出3个数中的最大数。main()第一次调用自定义函数max(),时,得到
8、的返回值是a和b中的较大数,并赋值给变量t,第二次调用自定义函数max()时,将变量t的值作为实际参数,也就是将t的值与c的值进行比较,最后返回的值就是a,b,c中的最大值。 【说明】 (1)函数定义的一般形式由“函数首部”(函数头)和“函数体”两部分组成。“函数首部”即函数定义的第一行,包括函数的类型、函数名和形式参数表列。 “函数体”即花括号“”中的部分,在语法上可以,是一条语句,也可以是一个复合语句。函数的定义必须独立并先于函数的声明和调用。 (2)类型标识符定义了函数返回值的类型。函数在调用结束后一般可以返回一个值,该返回值由函数的类型决定。当函数的类型缺省时,系统默认为int型。当函
9、数类型标识符为void时,函数没有返回值,一般称为“空类型函数”。 (3)函数名是用户为函数定义的名字,必须符合C语言标识符的命名规则。,(4)形式参数简称形参,形式参数表列简称形参表列。形参表列中的形参用于接收从主调函数传来的实参数据。 (5)当没有形参时,函数名后面的一对圆括号不能省略。 (6)花括号“”中的“说明部分”和“语句部分”构成了函数体。一般情况下函数体中定义的变量,仅在函数执行期间临时存在。函数体中也可以没有变量定义,只有语句,或者二者都没有。,例如: void null(void) (7)C语言的所有函数都是并列的,即函数定义是互相独立的。一个函数并不属于另一个函数。任何一个
10、函数都不能定义在另一个函数的内部,即函数不能嵌套定义。,4.1.3 函数的声明 通常在主调函数中,要对将要调用的函数事先声明。声明的目的是通知编译系统,在本函数中将要调用哪些函数以及它们的信息(函数名、函数的类型、形参的个数和类型以及次序等),以便编译系统对函数进行检查。例如,形参与实参的类型、个数、次序是否一致,调用函数返回值的类型是否正确等。 函数声明的一般形式如下:,类型标识符 函数名(形式参数表列); 例如: double power(int x,int y); 以上的函数声明也称为函数的原型。形式参数表列中形参的数目和类型是至关重要的,而形参的名字则可以省略。 例如: double
11、power(int,int); power()函数的类型是double类型,它有两个形参,其类型是int型。,函数声明可位于主调函数的函数体内或函数体外(一般位于程序开头部分)。在函数体外声明的函数可在声明之后,直到该文件结束时被任何函数调用;在函数体内声明的函数只能在该函数体内被调用。有时,函数也可以缺省声明。缺省声明有以下几种情况: (1)被调用函数写在主调函数之前。 (2)被调用函数的类型是int型。 (3)调用系统库函数时,不需对其声明,但必须用#include预处理命令包含所需的头文件。,对所有被调用函数均进行声明是好的编程习惯,这既符合现代程序设计的风格,又方便程序的阅读和检查。
12、4.1.4 函数参数和函数的返回值 1形式参数和实际参数 多数情况下,主调函数和被调函数之间都有数据传递关系,而参数就是函数调用时传递数据的载体。我们把在定义函数时,函数名后面圆括号中的变量,称为“形式参数”(formal parameter);在调,用函数时,函数名后面圆括号中的变量,称为“实际参数”(acrual parameter)。 【例4.2】输入k和r值,计算k!/(r!(kr)!)的值。 程序: main() int r,k; float a,b,c,d;,printf(n0,data error); t=-1; else for(i=2; i=n; i+) t=t*i; ret
13、urn(t); 程序运行情况:,屏幕提示:Please input r,k: 输入数据:2 6 输出结果:k!/(r!(k-r)!) = 15.00 【说明】 (1)形参变量只有当函数被调用时才分配内存单元,调用后自动释放。它有两个作用:一是表示从主调函数接收哪些类型的信息;二是表示在函数体中有哪些参与运算或被输入和输出。,(2)实参可以是常量、变量或表达式,但必须有一个确定的值。 (3)实参和形参必须在个数上相等,类型上兼容。 (4)实参和形参之间的值传递,实际上是赋值操作,即把实参之值赋给形参。 2函数的返回值 函数的返回是指流程从被调函数返回到主调函数。有的函数有返回值,有的函数没有返回
14、值。通常该返回值是通过return语句来实现的。,return语句的一般形式如下: return表达式;或return(表达式);或return; 其中,return是关键字,“表达式”是需要返回给主调函数的值。 return语句有两个作用:一是宣告一次函数调用的结束;二是带回函数的返回值,送到调用表达式中去。 【说明】 (1)return语句中表达式值的类型应与函数的,类型一致,当不一致时,返回值的类型取决于函数的类型。函数默认的返回值类型为int型。 (2)在一个函数中可以有一个或多个return语句,但最终只能执行一个。 (3)函数调用不需要返回值时,return后可不跟表达式,也可以没
15、有return语句。没有该语句时,函数体的最后一个花括号“”的作用是将流程返回主调函数。,(4)有时为了明确表示函数无返回值,可以将函数定义为“void”类型。但应注意:一旦函数定义为“void”类型,就不能再使用被调用函数的返回值。例如:如果已将fun(x,y)函数定义为void类型,则下面的语句是错误的。 c=fun(a,b); 编译时,系统会给出错误的信息。,4.2 函数的调用,C语言源程序由一个主函数和若干个自定义函数组成,因此,C程序是一系列被定义函数的集合。一个函数被另一个函数调用的过程称为函数的调用。一个函数可以被主函数main()调用,也可以被其他函数调用,各函数之间也可以相互
16、调用,但不能调用主函数main()。习惯上人们常常把调用函数称为主调函数(main()函数一定是主调函数),把被调用函数称为被调函数(系统库函数一定是被调用函数)。,4.2.1 函数调用的一般形式 所谓函数调用(function call),是指使程序流程转向所定义的函数。函数一旦定义,以其名为首地址的一段内存单元将被该函数占有。因此当在程序中出现该函数名时,就意味着程序将转到这一段内存地址,调用该函数,执行相关的一系列操作。对于有参函数,通常情况下,函数的调用是靠形参和实参的“虚实结合”来完成“值传递”的。,1函数调用的一般形式 函数调用的一般形式如下: 函数名(实际参数表列) 其中,“函数
17、名”必须与函数定义、函数声明时的函数名同名;“实际参数表列”可以是若干个有确定值的变量或表达式等,若有多个实参,则各实参之间必须用逗号隔开。 【说明】,(1)实际参数简称实参,实际参数表列简称实参表列。在实参表列中,实参可以是变量名、常量、表达式或者是我们后面将要学到的数组名、数组元素、结构体变量名或指针等。 (2)无参函数调用的一般形式为:函数名()。函数名后面的括号不能省略。 (3)被调函数总有“何处调用,返回何处”的 特点。,2函数调用的条件 在一个函数中调用另一函数(即被调用函数)必须具备以下条件: (1)被调用(声明)函数必须是已经存在的函数(包括库函数或自定义函数)。 (2)如果使
18、用库函数,则一般应该在本文件开头用#include命令将调用有关库函数时所需的信息“包含”到本文件中来。例如: #include或#includestdio.h,(3)如果使用自定义函数,而且该函数与主调函数在同一个文件中,则一般还应该在主调函数中对被调函数作声明。 (4)函数只有一个入口,一个出口,具有相对独立性,函数最多只能带回一个返回值。 3函数调用的方式 在C语言中,按照函数在程序中出现的位置,可以分为以下几种调用方式:,(1)语句调用。语句调用就是直接调用系统库函数或用户自定义函数。其调用形式是在函数末尾加一个分号“;”,即把一个函数调用作为一个独立语句使用。语句调用也称为直接调用。
19、 例如: printf(Max is %d,c); swap(m,n);,(2)表达式调用。表达式调用就是使函数作为表达式的一部分参与运算,出现在允许表达式出现的任何地方。 例如: m=2*max(a,b); z=sqrt(x)+sqrt(y); /*计算x与y的平方根之和*/ (3)参数调用。参数调用就是把函数的值作为一个函数的实参。例如: m=max(a,max(b,c); printf(%d,max(a,b);,4.2.2 函数的传值调用 通过前面的学习,我们已经知道,C语言中函数是通过定义中的形式参数与调用中的实际参数传递与外部环境发生联系的。归纳起来主要有“值传递”和“地址传递”两种
20、。本章介绍值传递,也称传值调用。函数传值调用的基本过程如图4.2.1所示。,图4.2.1 传值调用的基本过程,从图中可以看出: (1)当函数被调用时,形参变量被创建,即它被临时分配了与实参不同的存储单元。 (2)创建形参变量后,实参变量的值相应地传递给形参变量。 (3)执行被调函数体,返回主调函数并带回函数值。 (4)形式参数变量所占的内存单元被释放。,传值调用的特点是:函数中对形参变量的操作不会影响到调用函数的实参变量,因为形参的值不能回传给实参,只能由实参传给形参,即函数的“值传递”是单向的。 有关地址传递的内容将在后面章节中介绍。 【例4.3】调用swap()函数,交换两个变量值。 程序
21、: main() ,int m=6,n=8; void swap(int x,int y);/*声明swap()函数*/ swap(m,n);/*语句调用*/ printf(m=%d,n=%dn,m,n); void swap(int x,int y)/*自定义swap()函数*/ int temp; temp=x;x=y;y=temp;,printf(x=%d,y=%dn,x,y); 程序运行结果: 分析: 从该程序运行结果可以看出,调用swap()函数只交换了两个形参变量x和y的值,而没有改变实参变量m和n的值。,为了能正确传值,在函数传值调用过程中,应注意实参与形参相匹配。如果二者不匹配
22、,会出现什么后果呢?请看下面的例子。 【例4.4】形参变量与实参变量类型不一致时的函数调用。 程序: main() float x=-9.86,y=6.54;,printf(“%f+%f=%fn”,x,y,add(x,y);/*参数 调用*/ add(int m,int n) /*自定义add ()函数*/ printf(m=%d,n=%dn,m,n); return(m+n); 程序运行结果:,分析: 程序中实参变量为float类型,而形参变量为int类型,因此程序运行结果一定是错误的。但如果将上面的程序作以下修改,运行结果就正确了。,【例4.5】修改后的程序。 程序: main () fl
23、oat add(int m, int n);/*由于函数的返回值是float类型,因此要进行函数声明*/ float x=-9.86,y=6.54; printf(%f+%f=%fn,x,y,add(x,y); ,float add(int m,int n) printf(m=%d,n=%dn,m,n); return(m+n); 程序运行结果:,4.2.3 函数的嵌套调用 在C语言中,函数的定义是独立的、平行的。在定义函数时,一个函数内不能包含另一个函数,即函数不能嵌套定义,但函数可以嵌套调用。一个函数在被调用的过程中,又调用了另一个函数,这就是函数的嵌套调用,如图4.2.2所示。 图中的标
24、号表明了该程序的执行过程,箭头表明了流程的方向。 其执行过程是:,图4.2.2 函数的嵌套调用,(1)执行main()函数。 (2)main()函数调用a()函数。 (3)a()函数调用b()函数。 (4)执行b()函数。 (5)b()函数返回a()函数。 (6)a()函数返回main()函数。 (7)继续执行main()函数剩余部分直到结束。 【例4.6】求1k+2k+3k+nk,假设k的值为3,n的值为7。,程序运行结果: 分析: 该程序中有3个函数main(), sub(), powers()。其中主函数main()调用sub()函数,其功能是进行累加求和;在sub()函数中嵌套调用po
25、wers()函数,其功能是计算乘方数。程序的流程如图4.2.3所示。,图4.2.3 例4.6的嵌套调用过程,4.2.4 函数的递归调用 在数学上,递归是一种十分有用的工具。同样的在程序设计中,使用递归也是十分重要的。当一个问题蕴含着递归关系时,采用递归算法往往是比较简洁的。但要牺牲内存空间,作为处理时的堆栈。递归函数又称为自调用函数,其特点是在函数内直接或间接的自己调用自己。 一个函数直接或间接地调用自身,称为函数的递归调用,它是嵌套调用的一种特例。递归调用可,分为直接递归调用和间接递归调用两种,如图4.2.4和图4.2.5所示。,图4.2.4 直接递归调用 图4.2.5 间接递归调用,直接递
26、归调用是在函数f()执行过程调用f()自身。 间接递归调用是在函数f1()执行过程中调用函数f2(),而在函数f2()执行过程中又调用函数f1(),实质上是函数f1()间接调用了自身。 【例4.7】用递归方法求一个数的阶乘。 算法: 如求5!,有5!=5*4!,而4!=4*3!,依此类推。 n!的递归公式可描述如下:当n为1时,n!=1;当n1时,n!=n*(n-1)!。,程序运行结果: 分析: 从形式上看,在函数体内出现调用本身的语句时,它就是递归函数。递归过程实质上就是程序控权反复进入同一函数体,当条件满足时,程序控制权才逐级从函数中返回。,【例4.8】输入若干个字符,然后将它们按照相反顺
27、序输出。 程序: #include void stur() char c; if(c=getchar()!=n) stur();,putchar(c); void main() stur(); 程序运行情况: 输入数据:hjkl 输出结果:lkjh 分析:,以上递归函数stur()是一个无参函数。当第1次调用stur()函数时,输入字符“h”;第2次调用stur()函数时,输入字符“j”;第3次调用stur()函数时,输入字符“k”;第4次调用stur()函数时,输入字符“l”(可这样一直调用下去)。当第5次调用stur()函数时,按回车键,该回车键是程序调用结束的条件,因此,终止“回归”过程
28、,接着开始“递推”过程,从回车键开始输出该字符“l”,递推上去输出字符“k”、字符“j”,最后输出字符“h”结束。其调用过程如图4.2.6所示。,图4.2.6 递归调用stur()函数的过程,【说明】 (1)递归过程不应是无限的,而应是有限的,调用终点应有一个确定的值。 (2)递归的终止条件是设计函数时应当考虑的。任何有意义的递归总是由两部分组成:一是递归的方式,二是递归终止的条件。,4.3 变量的存储类别,变量是对程序中数据连同存储空间的抽象。在C语言中一个变量有两种属性:一种是操作属性,另一种是存储属性。变量类型反映变量的操作属性,而变量的存储属性则反映了变量的以下一些特征。 (1)从变量
29、的作用域来分,变量可分为局部变量和全局变量。 (2)从变量的生存期来分,变量可分为静态存储变量和动态存储变量。,(3)从变量存放的位置来分,变量可分为内存变量和寄存器变量。 因为一个变量既有数据类型的属性,又有存储类别的属性,所以在定义变量时,既可以指定其类型,也可以指定其存储类别。 例如:register int a;/*表示a是一个存储在寄 存器中的整型变量*/ 变量的类型说明我们早已熟悉,本节着重介绍变量的另一个重要属性存储类别。,4.3.1 局部变量和全局变量 在C语言中,变量按其定义的范围可以分为局部变量和全局变量。 1局部变量 局部变量是指在一个函数内部定义的变量,又称为内部变量,
30、其作用域(作用域表明变量在空间上的有效范围)为本函数内部,函数之外不能使用这些变量。例如:,float f(int a) int b,c;/*a,b,c的作用域仅限于在f()函数 中*/ main() ,int x,y;/*x,y的作用域仅限于在main()函数 中*/ 【说明】 (1)main()函数中定义的变量也是局部变量,不会因为它们是在主函数中定义的就可以在整个文件或程序中有效。主函数也不能使用其他函数中的局部变量,因为主函数也是函数,与其他函数是平,平行关系。这一点与其他语言不同。 (2)不同函数中可以使用相同名字的变量,它们代表不同的对象,互不干扰,在内存中占据不同的存储单元。 (
31、3)形参是局部变量,仅为所在函数使用,其他函数不能使用。 (4)函数内部的复合语句,也可以定义变量,这些变量只在本复合语句中有效,离开该复合语句该变量的存储单元就被释放。例如:,main() int a,b;/*在main()内定义a,b*/ int c;/*在复合语句中定义c*/ c=a*10+b; ,2全局变量 在函数之外定义的变量称为全局变量,全局变量又称为外部变量,也称为全程变量。全局变量可以被本文件中其他函数所共用。它的有效范围是从定义变量的位置开始直到本文件结束,即它属于一个源程序文件。全局变量只要满足在使用它之前和函数之外这两个条件,就可在程序的任何位置进行说明,习惯上通常是在程
32、序的main()函数前定义。 【例4.9】写出下列程序的执行结果。,【说明】 (1)全局变量的作用增加了函数间数据联系的渠道。函数的调用最多只能带回一个返回值,但利用全局变量可以从函数中得到多个值。原因是全局变量可以被多个函数引用,当被其中一个函数改变时,其他函数也可以使用被改变的全局变量的值。,(2)建议非必要情况时,尽量少使用全局变量。理由如下: 1)全局变量在程序的整个执行期间都占据存储单元,而不是仅在需要时才临时分配,使内存开销变大。 2)全局变量的使用降低了函数的通用性。在程序设计中,模块划分的原则是“高内聚、低耦合”,即函数的功能要单一,与其他模块的相互影响要尽量少,而用全局变量是
33、不符合这个原则的。,3)全局变量的过多使用,将会降低程序的可读性。 (3)如果在同一个源文件中,全局变量与局部变量同名,则在局部变量的作用范围内,全局变量被“屏蔽”(不起作用)。 【例4.10】全局变量与局部变量同名。 程序: int a=7,b=10; /*a,b为全局变量*/ max(int a,int b) /*形参a,b为局部变量*/, return(ab?a:b); main() int a=15;/*a为在main()函数内部定义的 局部变量*/ printf(%d,max(a,b); ,程序运行结果: 15 分析: 程序中重复使用a,b作变量名,当在main()中调用函数max(
34、)时,实参变量a为main()函数内部定义的变量,其值为15,b为全局变量,其值为10,外部变量a在本程序中不起作用。请读者注意区别它们的不同含义和作用域。,4.3.2 变量的存储类别 上面从作用域的角度,我们可以把变量分为全局变量和局部变量,这里我们从生存期(生存期表明变量存在的时间)的角度,又可以把变量分为静态存储变量和动态存储变量。 生存期和作用域是从时间和空间两个不同的角度来描述变量的特征,二者有联系,也有区别。 变量的完整说明格式为 存储类别 类型标识符 变量名表列;,C语言中用来说明变量存储类别(属性)关键字有4个:auto(自动),static(静态),register(寄存器)
35、和extern(外部)。 1静态存储方式和动态存储方式 C语言把用户执行程序占用的内存空间(用户区)分为3部分:程序代码区、静态存储区和动态存储区,如图4.3.1所示。执行程序存放在程序代码区,各种变量和数据则分别放在静态存储区或动态存储区。,静态存储方式是指变量存放在静态存储区,在程序执行的整个过程中都占有存储单元。动态存储方式是指变量在函数调用开始时,才临时分配动态存储空间,函数执行结束时释放这些空间,在程序执行过程中,这种分配和释放是动态的。,图4.3.1 C程序在内存中的存储映像,2局部变量的存储方式 (1)auto变量。函数中的局部变量,如果没有特别说明为static存储类别,则均为
36、auto存储类别,这类局部变量也称为自动变量。函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量)都是自动变量。自动变量的显著特点体现在调用该函数时系统会给它们自动分配存储空间,在函数调用结束时又能自动释放这些存储空间。所以,在C语言中,大量使用的变量都被系统默认说明为 auto,即隐含为自动变量。,自动变量用关键字auto作存储类别说明。 例如: int f(int a) auto int b,c=2;/*定义b,c为自动变量*/ 变量名前的关键字“auto”可以省略,当“auto”省,略时,系统会自动默认其为自动变量。 例如,int i,j,k;等价于auto int i,j,k
37、;。 (2)static变量。其实,局部变量既可以存放在动态存储区,也可以存放在静态存储区。如果想使一个局部变量在函数调用结束后所占用的存储单元不释放,将值保留下来,在下一次该函数被调用时继续复用该值,就可以将局部变量定义为“静态局部变量”。静态局部变量用关键字static进行说明。 【例4.11】静态局部变量的使用。,程序: void fun() int a,b=8; static int c,d=10; a=34;c=9; a+ ;b+ ;c+ ; d+ ; printf (%dt%dt %dt %dn,a,b,c,d); ,main() int i; for(i=0;i2;i+) fun
38、(); 程序运行结果:,分析: 程序中main()函数两次调用fun()函数。在函数fun()中,a和b为局部自动变量,在每次调用函数时,都重新分配内存单元,重新赋值,因此两次调用得到的结果是一样的。而变量c和d为静态局部变量,它们在程序开始运行时就分配存储单元,变量d被赋初值10,而c未赋初值,初值为0。第一次调用fun()时,c被赋值为9,c和d的输出结果为10和11。调用结束后,它们的存储单元不被释放。当第二次调用fun()时,它们的值仍然保留,但由于变量c又,重新进行了一次赋值为9的操作,因此,变量c和d输出的结果为10和12。 【说明】 (1)静态局部变量属于静态存储类别,存放在内存
39、的静态存储区,在程序整个运行间都不释放。 (2)静态局部变量是在编译时赋初值的。在程序运行时它已有初值,以后每次调用函数时不再重新赋初值,而是保留上次函数调用结束时的值(如例4.11中的d)。,(3)在定义局部变量时,如果不赋初值,则对于静态局部变量来说,编译时,系统自动赋初值0或可以重新赋值(如例4.11中的c);而对于自动变量来说,它的值是一个不确定的数。 (4)虽然静态局部变量在函数调用结束后仍然存在,但它仍是局部变量,其他函数不能引用。 3全局变量的存储方式 全局变量存放在静态存储区时,它是在程序开始执行时分配存储单元的,程序执行完后释放这些,存储单元。在程序执行过程中它们一直占用固定
40、的存储单元,而不是动态地进行分配和释放。 一个较大的C程序可以由一个或多个源程序文件组成。当有多个源文件时,一个文件中的变量是否可以被其他文件引用呢?有以下两种情况: (1)只限于被本文件引用,而不能被其他文件引用。在定义外部变量时加static,限定了外部变量的作用范围,仅在本文件中的各函数中有效。,(2)允许其他文件中的函数引用。外部变量是在函数的外部定义的,它的作用域是从变量的定义点开始,到本文件结束。如果在定义点之前的函数需要引用该外部变量,则应该在引用之前用关键字extern对该变量作“外部变量说明”,以扩展外部变量的作用范围。 用extern说明的外部变量,也可以来自其他文件。需要
41、指出的是,对外部变量加static说明,并不意味着这时才是静态存储方式,不加则是动态存储,方式。外部变量都是静态存储方式,都在编译时分配内存,只是作用范围不同而已。 【例4.12】用extern说明外部变量,以扩展其在程序文件中的作用域。 程序: int add(int x,int y) return(x+y); ,main() extern int A,B; printf(%d,add(A,B); int A=9,B=16; 程序运行结果: 25 分析:,在本程序文件的main()函数中用extern对A和B进行了外部变量说明,表示A和B是已经定义的外部变量,但定义的位置在后面。这样在mai
42、n()函数中就可以合法地使用全局变量A和B了。如果不作extern说明,则编译时会出错。 4寄存器变量 通常情况下,变量的值都是存放在内存中的,变量运算时在运算器和内存之间进行数据交换。但是,有时为了提高程序的运行效率,C语言允许将,一些频繁使用的变量保存在CPU的寄存器中,需要时可直接从寄存器中取出,而不必再到内存中存取。循环变量常被作为寄存器变量。 寄存器变量用关键字register说明。 例如: register int t; 【例4.13】计算s=x1+x2+x3+xn,x和n由键盘输入。 程序:,程序运行情况: 输入数据:5 6 输出结果:S=19530 【说明】 (1)只有局部自动
43、变量和形参变量可以作为寄存器变量。在调用一个函数时,将占用一些寄存器以存放寄存器变量的值,函数调用结束后释放这些寄存器。此后,在调用另一个函数时又可以利用它来存放该函数的寄存器变量。,(2)一个计算机系统中寄存器的数目是有限的,不能定义任意多个寄存器变量。不同的系统允许使用的寄存器个数是不同的,而且对寄存器变量的处理方法也是不同的,有的系统把寄存器变量当做自动变量处理,分配内存单元,并不真正把它们存放在寄存器中,有的系统只允许将int,char和指针型变量定义为寄存器变量。 (3)局部静态变量不能定义为寄存器变量。不能把变量既放在静态存储区中,又放在寄存器中。,对一个变量只能说明为一种存储类别
44、。 4.3.3 存储类别小结 对一个变量的完整定义,需要指定两种属性:存储类别和数据类型。本节我们对变量的存储类别从不同角度进行一些归纳小结。 (1)从作用域的角度看,变量有局部变量和全局变量之分,如表4.1所示。,表4.1 局部变量和全局变量,(2)从生存期的角度来看,变量有动态存储和静态存储两种类型,如表4.2所示。 (3)从存放的位置看,变量可分为内存变量和寄存器变量。 (4)生存期和作用域是变量存储属性的两个不同概念,如表4.3所示。,表4.2 动态存储和静态存储,表4.3 存储类别、生存期、作用域,4.4 内部函数和外部函数,函数从本质上说是全局的。因为一个函数可以被另一个函数调用,
45、那么,根据函数能否被其他文件中的函数调用,可以将函数分为内部函数和外部函数。 4.4.1 内部函数 如果一个函数只能被本文件中其他函数调用,则该函数称为内部函数。在定义内部函数时,需在函数名和函数类型前面加上关键字static,即,static 类型标识符 函数名(形参表列) 例如: static int function(int x,int y) 内部函数又称静态函数。使用内部函数,可以使函数局限于所在文件,如果在不同的文件中有同名的内部函数,则互不干扰。这样在完成一个较大的程序时,不同的人可以分别编写不同的函数,而不必担心所用函数是否会与其他文件中的函数同名。通常把只由同一文件使用的函数和
46、外部变量放在一个文件中,在其前加关键字static使之局部化,其他文件不能引用。,4.4.2 外部函数 在定义函数时,如果在其前加关键字extern,则表示此函数是外部函数。 例如: extern int function(int x,int y) 函数function可以被其他文件调用,如果在定义函数时省略extern,则隐含为外部函数。 使用外部函数有两种方法:,(1)在需要调用外部函数的文件中,用extern声明的函数是外部函数。 如果一个程序由多个源文件组成的,如main.c,file1.c,file2.c,则可在main.c源文件中的main()函数中使用extern将file1.c
47、,file2.c各文件中的函数一一声明。 (2)在main.c文件中还可以用#include命令将多个文件包含到一个文件中。 例如:,#includefile1.c #includefile2.c 这样,编译系统会自动将这两个文件放到main()函数的前面,作为一个整体编译,而不是分3个文件编译。这时,这些文件中定义的函数被认为是在同一文件中,不再作为外部函数被其他文件调用。main()函数中原有的extern说明也可以没有。,4.5 编译预处理,编译预处理是在编译之前对程序进行的一些预加工。预处理命令是由ANSI C统一规定的,它不是C语言的组成部分,不能直接对它们进行编译。必须在对程序编译
48、之前,先对程序中这些特殊的命令进行“预处理”,将处理的结果和源程序一起再进行通常的编译,得到目标程序。 C语言提供的预处理主要有以下3种: (1)宏定义。 (2)文件包含。,(3)条件编译。 为了与C语言的语句相区别,这些命令以符号“#”开头,且后面不加分号。 4.5.1 宏定义 宏定义是编译预处理中的一种。在C语言源程序中允许用一个标识符表示一个字符串,称为“宏体”。被定义为“宏体”的标识符称为“宏名”。在程序中的宏名都用宏体替换,其意义是使用宏名代替了一个字符串。在预编译时将宏名替换成字符串的过,程称为“宏展开”。宏定义可分为不带参数的宏定义和带参数的宏定义两种。 1不带参数的宏定义 不带
49、参数的宏定义的一般形式如下: #define 宏名 宏体 例如: #define PI 3.14159,使用宏定义可以提高程序的可移植性和可读性;另外,当程序中多处使用字符串的地方需要进行修改时,仅仅修改宏定义即可。 【说明】 (1)宏名一般用大写,以区别变量名。 (2)宏定义是简单的字符串替换,不进行语法检查。 (3)宏定义时,可引用已定义过的宏名,即宏定义可以嵌套。,(4)程序中出现的双撇号内的字符串,即使与宏名相同,也不被置换。 (5)宏定义的作用范围是从定义点到文件结束,但可以用 #undef命令终止宏定义的作用域。 #undef命令的一般形式如下: #undef宏名 其目的是将宏名局
50、限于需要用它的程序段中。,2带参数的宏定义 在宏定义中不仅可以进行简单的字符串替换,还可以进行参数替换。 带参数的宏定义的一般形式如下: #define 宏名(参数表) 字符串 例如: #define S(a,b) a*b area=S(4,5);,带参数的宏定义展开时,先将程序中宏名后面的实参从左至右依次替换命令行中宏名后的形参(如将4, 5分别代替a, b),再将宏名置换成字符串(如将S(4,5)置换成4*5)。这样预编译后程序中S(4,5)被替换成4*5,赋值语句被展开后,相当于 area=4*5; 【例4.14】求圆面积。 程序: #define PI 3.1415926,#defin
51、e S(r) PI*r*r main() float a,area; a=4.8; area=S(a); printf(r=%fnarea=%fn,a,area); 程序运行结果:,分析: 程序中,宏名后的参数与函数中的形参相似,实参也可以是常量、变量和表达式。若宏体中出现了不是参数的字符(如a*b的*号),则会保留。 【说明】,(1)宏定义是由源程序中的宏定义命令完成的,宏展开是预处理程序自动完成的。 (2)宏展开仅是作字符串替换。如果程序中宏名后的实参是一个表达式(如S(a+b)),则会用实参a+b替换字符串中的形参(如PI*a+b*a+b);若给字符串中参数加上括号,则展开时实参也可以替
52、换成带括号的形式,如字符串为PI*(r)*(r)形式,则展开时为PI*(a+b)*(a+b)。,(3)宏名与参数表之间不能有空格,否则会将空格后的参数表作为代换表达式的一部分。如: #define S (r) PI*r*r 以上的宏定义被认为是不带参数的宏S,替换时将其后的所有字符(包括空格在内)当作宏体进行替换。假如有area=S (a),则area=(r) PI*r*r (a)。 (4)带参数的宏和函数在形式上有类似之处,如均有实参和形参,并且要求个数相同。但带参数的宏定义与函数有本质上的不同。主要区别如下:,1)函数调用时,先求出实参表达式的值,然后代入形参。而使用带参的宏只是进行简单的
53、字符串替换。如当字符串x+y是一个实际参数时,不计算x+y的值,而是将整个字符串x+y替换相应的形参。 2)对函数中的实参和形参都要定义类型,二者的类型要求兼容;而宏名无类型,它的参数也无类型,宏定义时,字符串可以是任何类型的数据。 例如: 带参的宏:#define S(r) PI*r*r,其中,r不是变量。如果在语句中有S(6.3),则展开后为PI*6.3*6.3,语句中并不出现r,也无所谓类型。 3)调用函数最多只能得到一个返回值,而使用宏可以设法得到几个结果。 4)使用宏的次数多时,每展开一次都使源程序变长,而函数调用不会使源程序变长。 5)函数调用是在程序运行时处理的,分配临时的内存单
54、元,且占用运行时间。而宏展开则是在编,译时进行的,只占编译时间;宏展开时并不分配内存单元,不进行值的传递处理,也没有“返回值”的概念;宏替换不占用运行时间。 4.5.2 文件包含 C语言提供了#include命令来实现“文件包含”的操作,它的作用是把另外一个源文件的全部内容包含进本文件中。其一般格式如下: #include 文件名或#include ,如图4.5.1(a)所示为文件file1.c,它有一个 “#includefile2.c”命令,另外还有其他内容M。如图4.5.1(b)所示为另一文件file2.c,其内容用N表示。在对“#include”命令进行编译预处理时,file2.c就被
55、包含到file1.c中,得到如图4.5.1(c)所示的结果。编译时,会将包含以后的file1.c作为一个源文件单位进行编译。,图4.5.1 文件包含的含义,通常,用文件包含命令包含头文件,即以“.h”为扩展名的文件。事实上,扩展名还可以是“.c”或者没有扩展名。一般情况下,头文件中可以包括宏定义、结构体类型定义、全局变量定义、常用函数的定义等。 被包含的文件可以分为两类:一类是用户自定义的文件,另一类是系统提供的标准库函数所在的文件。当用户需要使用库函数时,应该在文件的开头用#include命令包含所对应的头文件。,例如: #include或#includestdio.h/*输入/输出函数*/
56、 #include或#includemath.h/*数学函数*/ #include或#includestring.h/*字符串处理函数*/ #include或#includectype.h/*字符处理函数*/,#include或#includestdlib.h/*实用函数*/ #include或#includetime.h/*时间函数*/ #include或#includemalloc.h /*动态存储分配函数*/ 有些C语言编译系统不遵循标准ANSI C的规定,而用其他的头文件名,读者在使用时可查阅有关手册。,【说明】 (1)一个#include命令只能包含一个文件,要包含n个文件时,需要用
57、到n个#include命令。 (2)文件包含可以嵌套,即文件1可以包含文件2,文件2又可以包含文件3;也可以在文件1中用两个#include命令分别包含文件2和文件3。 (3)使用#include命令时,系统提供的头文件名可用引号引起来或用尖括号括起来。,二者的区别是:用引号时,先在使用#include命令的文件所在目录中寻找被包含文件,若找不到,再按系统指定的标准方式检索其他目录;用尖括号时,不检查源文件所在目录,直接按系统指定的标准方式检索文件目录。 (4)被包含文件与使用#include命令的文件在预编译处理后成为同一个文件,它们将作为一个源文件单位进行编译。还可以使用被包含文件中的全局
58、变量,而不用extern声明。,4.5.3 条件编译 一般情况下,源程序中所有的行都参加编译。但是,有时希望对其中一部分内容只在满足一定条件的情况下才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。 条件编译命令有3种形式,第一种形式如下: #ifdef 标识符,程序段1 #else 程序段2 #endif 作用是若标识符已定义过(一般用#define定义),则对程序段1进行编译,否则编译程序段2,其中#else和程序段2可以缺省。 例如,在调试程序时,需要输出一些必要的信息,等调试完成后就不需要了。
59、于是可在源程序中插入条件编译:,#ifdef DEBUG printf(x=%d,y=%d,z=%dn,x,y,z); #endif 若在它的前面有命令行: #define DEBUG 则程序运行时输出x,y,z的值,供调试分析。调试完后只需将define命令行删掉即可。 第二种形式如下: #ifndef 标识符,程序段1 #else 程序段2 #endif 作用是若标识符未定义,则编译程序段1,否则编译程序段2,与第一种形式的作用恰好相反。这两种形式用法差不多,在实际中可以根据需要任选一种。 第三种形式如下:,#if 表达式 程序段1 #else 程序段2 #endif 作用是若指定的表达式值为真(非零)则编译程序段1,否则编译程序段2(可事先给定一条件,使程序在不同的条件下执行不同的功能)。,本
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 二零二五年度06289工程项目招标与合同纠纷预防与处理合同
- 二零二五年度O2O物流仓储设施租赁合同范本
- 二零二五年度汽车无偿租赁附带车辆升级服务合同
- 二零二五版bot项目投资合作与市场拓展合同
- 二零二五年度网络安全风险评估与防护技术服务合同
- 二零二五年度停车场场地租赁安全管理及智能收费合同
- 2025版航空航天设备采购合同标准
- 2025版城市广场景观草皮铺设与生态修复施工合同
- 2025版深基坑施工安全施工合同协议书范本
- 2025版车间租赁安全环保监督协议
- 2024人教版七年级下册生物第三单元 植物的生活 单元测试卷(含答案)
- 国家开放大学行管专科《行政组织学》期末纸质考试总题库(2025春期版)
- 2024-2025学年江苏省南通市高一(上)期末物理试卷(含答案)
- 中药涂擦治疗护理技术操作规范
- 《大金智能控制系统》课件
- 北师大版四年级下册数学口算题1000道带答案
- AAMIST79-2017卫生保健设施蒸汽灭菌和无菌保证综合指南
- 高一下学期期末考试物理试题
- 施工现场重大危险源辨识及监控措施
- DB21T 2414.2-2015 公共场所双语标识英文译法 第2部分:道路交通
- 新产品开发流程
评论
0/150
提交评论