教学材料《C语言》-第七章_第1页
教学材料《C语言》-第七章_第2页
教学材料《C语言》-第七章_第3页
教学材料《C语言》-第七章_第4页
教学材料《C语言》-第七章_第5页
已阅读5页,还剩135页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

7.1函数概述

C语言是一种模块化程序设计语言。模块化就是将一个复杂问题划分为若干个小问题,再把小问题划分为简单问题,使问题能通过一个程序模块解决。因此,用C编写应用程序时,通常把程序任务分解成若干个小任务模块,这些模块程序由一组外部对象(变量或函数)组成。函数是一个可以反复使用的程序段,是一段完成相关功能的执行代码。这些函数通过主函数的调用来构成一体,实现整个软件的功能。使用函数编制程序,可以使程序容易被读、写和理解,可以更加方便地排除程序的错误,对程序进行修改和维护。我们可以把函数看成一个“黑盒子”,只要将数据送进去就能得到结果,而函数内部究竟是如何工作的,对于外部程序而言并不重要。外部程序所要知道的是,应该输入函数怎样的参数,以及函数输出怎样的结果。本章将详细讲述不同类型函数的定义、声明和调用方法,以及在函数调用过程中,函数的数据传递过程,还将介绍局部变量和全局变量的定义、特点和作用范围,变量的作用域和存储类别等。熟练掌握不同类型变量的特点,就可以在编写程序过程中灵活使用各种变量,从而提高程序的效率。下一页返回7.1函数概述

C程序设计的基础工作是函数的设计和编制。C程序完全由函数组成,而且往往由多个函数组成。函数是C程序的基本模块,通过对函数模块的调用来实现特定的功能。C语言中的函数相当于其他高级语言的子程序。除C语言本身提供的库函数外,用户还可根据需要来自定义任意多个函数。一个C程序可由一个主函数main()和任意个其他函数组成。每个程序有且只有一个主函数,其他函数都是“子函数”,子函数可以互相调用,但主函数不能被子函数调用。因此,C程序的执行总是从main函数开始,完成对其他函数的调用后再返回main函数,最后由main函数结束整个程序;其他函数只有在执行的过程中被调用才执行。上一页下一页返回7.1函数概述

【例7-1】函数调用的简单例子。上一页下一页返回7.1函数概述

上一页下一页返回7.1函数概述

【说明】(1)除了main函数外,其他函数可相互调用。执行程序时,从main()开始,调用其他函数后,返回main()结束。(2)所有的函数都是独立的。在一个函数的函数体内,不能定义另一个函数,即函数不能嵌套定义,但允许嵌套调用、相互调用和递归调用(自身调用)。习惯上把调用者称为主调函数。函数的书写顺序任意。上一页下一页返回7.1函数概述

(3)从用户使用的角度划分,函数有以下两种:①标准函数:即库函数,它是由系统提供的,放在不同的头文件中,用户可直接使用。例如,printf、scanf、getchar、putchar、gets、puts、strcat等函数均属此类。②自定义函数:由用户按语法规则编写,用以解决用户专门需要的函数。不仅要在程序中定义函数本身,而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。printstart和printmessage都是用户定义的函数名,在本例中分别用来输出一排“∗”号和一行信息。(4)从函数的形式划分,函数分为无参数函数、有参数函数。详见7.2节。上一页返回7.2函数的定义与调用

函数定义的一般格式为类型标识符函数名([形式参数列表]){变量声明部分语句部分}下一页返回7.2函数的定义与调用

【说明】(1)类型标识符表示函数返回值的数据类型,可以是以前介绍的整型(int)、长整型(long)、字符型(char)、单精度浮点型(float)、双精度浮点型(double)以及无值型(void),也可以是指针(包括结构指针)。(2)函数名是对函数的标识,命名规则和变量的命名规则形同,在程序中必须是唯一的,函数名后一定要有一对圆括号,它是函数的标志。另外,定义函数时函数名后不能有“;”。(3)用[]包围的部分可以缺省。形式参数在函数名的一对圆括号内,可以没有也可以有多个,参数的类型直接在括号内标明,在函数调用时,实际参数将被复制到这些变量中。上一页下一页返回7.2函数的定义与调用

(4)一个函数包括函数首部和函数体两部分组成。函数首部由三部分组成:类型标识符、函数名、参数表。大括号{}包围的部分为函数体,函数体包含变量说明和语句两部分,在函数中说明的变量和参数均在执行该函数时存在,该函数执行完后,其定义的内部变量也随之释放而不存在。(5)当函数的函数体为空时(即大括号{}内的内容为空),此函数为空函数。例如:voiddummy(){

}调用此函数无实际作用,待扩充函数功能时使用。上一页下一页返回7.2函数的定义与调用

7.2.1无参数函数1.不带参数,没有返回值的函数此类函数既没有输入,也没有输出,只是在函数体内用于完成某项特定的处理任务,执行完成后不向调用者返回函数值。函数的定义格式:void函数名(){说明部分语句部分}上一页下一页返回7.2函数的定义与调用

【说明】(1)类型标识符为void,说明此函数为无返回值函数。(2)圆括号()内没有形式参数列表,说明此函数为无参函数。圆括号()不能省略。函数的调用格式:函数名();上一页下一页返回7.2函数的定义与调用

【例7-2】调用不带参数、没有返回值函数的例子。上一页下一页返回7.2函数的定义与调用

运行结果:程序说明:在main()的前面声明了一个函数,函数类型是void型,函数名为a,无参数。然后在main()函数里调用这个函数。该函数的作用很简单,就是输入一个整数然后显示它。【注意】若函数的定义在调用之前,则可以例外。例7-1就是这种函数调用的例子。因为编译器在编译时,已经发现printstart和printmessage是两个函数名,是无返回值类型无参数的函数。上一页下一页返回7.2函数的定义与调用

2.不带参数,有返回值的函数此类函数没有输入,但被调用执行完函数体内某种功能后,将向调用者返回一个执行结果,称为函数返回值。1)函数的定义格式类型标识符函数名(){说明部分语句部分}上一页下一页返回7.2函数的定义与调用

【说明】(1)类型标识符是函数返回值的数据类型,表示此函数为有返回值函数。当返回类型是int时,可以省略。(2)圆括号()内没有形式参数列表,说明此函数为无参函数,圆括号()不能省略。(3)函数返回值由return语句返回。return语句的一般形式为return表达式;或return(表达式);该语句的功能是计算表达式的值,并返给主调函数,也可理解为函数的值。当执行该语句后,不管后续函数体内是否还有其他语句,程序都结束函数并返回。上一页下一页返回7.2函数的定义与调用

【注意】如果一个函数有返回值,就必须使用return语句,否则函数返回的是一个不确定的数值。在函数中允许有多个return语句,但每次调用只能有一个return语句被执行,因此只能返回一个函数值。2)函数的调用格式函数名();或变量=函数名();上一页下一页返回7.2函数的定义与调用

【例7-3】编写函数统计输入字符的个数,用@字符结束输入。在主函数中调用此函数,输出统计结果。上一页下一页返回7.2函数的定义与调用

运行结果:程序说明:程序中用长整型变量cn统计输入字符的个数,在printf中采用%ld作为输出格式,用以输出long型数据。输出结果cn的值为14个字母及2个换行符。另外,如果函数定义的类型与return语句中表达式值的类型不一致,程序将对表达式进行强制类型转换,强制转换为函数定义的类型,然后返回该值。上一页下一页返回7.2函数的定义与调用

【例7-4】返回值类型与函数类型不同。上一页下一页返回7.2函数的定义与调用

7.2.2有参数函数1.带参数,没有返回值的函数此类函数有若干个函数参数,但没有返回值。1)函数的定义格式void函数名(形式参数列表){说明部分语句部分}上一页下一页返回7.2函数的定义与调用

【说明】(1)类型标识符为void,说明此函数为无返回值函数。(2)圆括号()内有形式参数列表,说明此函数为有参函数。形式参数列表的格式为数据类型符1形式参数1,数据类型符2形式参数2,…,数据类型符n形式参数n(3)在形式参数列表中给出的参数称为形式参数(简称“形参”),它们可以是各种类型的变量,各参数之间用逗号间隔。(4)每个形式参数都必须指定数据类型。在进行函数调用时,主调函数将赋予这些形式参数实际的值。既然形参是变量,就必须在形式参数列表中给出形参的类型说明。上一页下一页返回7.2函数的定义与调用

2)函数的调用格式函数名称(表达式列表);表达式列表的格式为表达式1,表达式2,…,表达式n表达式列表的多个表达式之间用逗号分隔。在调用函数中给出的这些表达式称为函数的实际参数(简称“实参”),表达式列表称为实际参数列表。上一页下一页返回7.2函数的定义与调用

函数的形参和实参具有以下特点:(1)形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用,其作用是接收调用函数传递过来的实参值。实参出现在主调函数中,其作用是将调用函数中的数据传递给被调用函数。数据进入被调函数后,实参变量也不能对其使用。形参和实参的功能是传送数据。(2)形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调函数后,不能再使用该形参变量。(3)实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值、输入等办法使实参获得确定值。上一页下一页返回7.2函数的定义与调用

(4)实参和形参在数量、类型、顺序方面应严格一致,否则会发生类型不匹配的错误。(5)函数调用中发生的数据传送是单向的,即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。因此,在函数调用过程中,形参的值发生改变,而实参中的值不会变化。上一页下一页返回7.2函数的定义与调用

【例7-5】以下程序试图通过调用swap函数,把主函数中变量x和y中的数据进行交换。请观察程序的输出结果。上一页下一页返回7.2函数的定义与调用

运行结果:上一页下一页返回7.2函数的定义与调用

程序说明:实参x和y的值已传送给函数swap中对应的形参a和b;在函数swap中,a和b也确实进行了交换,但由于在C语言中,数据只能从实参单向传送给形参,而形参数据的变化并不影响对应的实参。因此在本程序中,不能通过调用swap函数把主函数中x和y的值进行交换。实参变量和形参变量之间的数据传递如图7.1所示。上一页下一页返回7.2函数的定义与调用

2.带参数,有返回值的函数1)函数的定义格式类型标识符函数名(形式参数列表){说明部分语句部分}上一页下一页返回7.2函数的定义与调用

【说明】(1)类型标识符是函数返回值的数据类型,表示此函数为有返回值函数。当返回类型是int时,可以省略。(2)圆括号()内有形式参数列表,说明此函数为有参函数。(3)函数返回值由return语句返回。return语句的格式和用法参见7.2.1节。(4)形式参数列表的格式和形式参数的有关说明参见7.2.2节。上一页下一页返回7.2函数的定义与调用

2)函数的调用格式函数名称(实际参数列表);或变量=函数名称(实际参数列表);上一页下一页返回7.2函数的定义与调用

【例7-6】比较两个数的大小,并输出大数。上一页下一页返回7.2函数的定义与调用

运行结果:程序说明:实参a和b的值已传送给函数max中对应的形参x和y;在函数max中,x和y进行比较,max函数执行的结果(x或y)将返回给变量z。最后由主函数输出z的值。实参传递值给形参的过程如图7.2所示。上一页下一页返回7.2函数的定义与调用

【例7-7】编写函数myupper(ch),把ch中的小写字母转换成大写字母作为函数值返回,其他字符不变。用字符@结束输入。上一页下一页返回7.2函数的定义与调用

运行结果:上一页返回7.3函数的声明

与变量一样,函数在使用之前要对其进行声明。所谓声明,是指在函数被调用前,说明该被调用的函数是什么类型的函数以及参数的数据类型等。声明的作用是把函数名、函数参数的个数和参数类型等信息通知编译系统,以便在遇到函数调用时,编译系统能正确识别函数并检查调用是否合法。当一个函数调用另一个函数时,要掌握以下两点:(1)如果被调用函数是一个库函数,通常在文件开头用#include将把有关库函数信息包含到程序中。(2)如果使用用户自定义的函数,通常在主函数中对被调用函数进行原型声明。如果被调用函数的定义出现在主调函数之前,则不必加以声明。下一页返回7.3函数的声明

函数声明的一般形式:类型说明符被调函数名(类型形参,类型形参,…);或类型说明符被调函数名(类型,类型,…);在例7-6中,main函数中对max函数的说明:intmax(intx,inty);也可写为intmax(int,int);上一页下一页返回7.3函数的声明

C语言中规定,在以下几种情况下,可以省略主调函数中对被调函数的函数说明。(1)当被调函数的函数定义出现在主调函数之前时,在主调函数中可以不对被调函数再作说明而直接调用。例如,在例7-6中,函数max的定义放在main函数之前,因此可在main函数中省略对max函数的函数说明“intmax(intx,inty)”。上一页下一页返回7.3函数的声明

(2)如果在所有函数定义之前,在函数外已预先说明了各个函数的类型,则在以后的各主调函数中,可不再对被调函数作说明。例如:#include<stdio.h>charstr(inta);

/∗在所有函数之前,对函数str做原型声明∗/floatf(floatb);

/∗在所有函数之前,对函数f做原型声明∗/voidmain(){…}charstr(inta){…}上一页下一页返回7.3函数的声明

floatf(floatb){…}其中,第2、3行语句对str函数和f函数预先作了说明。因此,在以后各函数中,无须对str和f函数作说明,可直接调用。上一页下一页返回7.3函数的声明

(3)对库函数的调用不需要再作说明,但必须把该函数的头文件用include命令包含在源文件前部。函数的定义和函数的声明是完全不同的。函数的定义包括函数首部和函数体,完整地定义函数的输入、输出和具体实现,是对函数的功能的确立。而函数的声明是为了编译的需要,把函数名、函数类型、形参的类型、形参的个数和顺序通知编译系统。另外,在格式上,函数的声明(如“floatadd(floatx,floaty);”)比函数的定义在结尾多一个分号。上一页返回7.4函数的嵌套调用

在C语言中,不允许作嵌套的函数定义。因此,各函数之间是平行的,不存在上一级函数和下一级函数的关系。但是C语言允许在一个函数的定义中出现对另一个函数的调用。这样就出现了函数的嵌套调用,即在被调函数中又调用其他函数。这与其他语言的子程序嵌套的情形类似。例7-1程序的调用示意如图7.3

所示。图7.3表示了三层嵌套的情形。其执行过程是:(1)执行main函数的开头部分。(2)遇到调用printstar函数的语句时,即转去执行printstar函数。(3)printstar函数没有其他嵌套的函数,执行完printstar函数的全部操作。(4)返回main函数的断点(调用printstar函数的位置)。(5)继续执行main函数的剩余部分。下一页返回7.4函数的嵌套调用

(6)遇到调用printmessage函数的语句时,即转去执行printmessage函数。(7)执行printmessage函数的开头部分。(8)遇到调用printstar函数的语句时,即转去执行printstar函数。(9)printstar函数没有其他嵌套的函数,执行完printstar函数的全部操作。(10)返回printmessage函数的断点(调用printstar函数的位置)。(11)继续执行printmessage函数中未执行的部分,直到printmessage函数结束。(12)返回main函数的断点(调用printmessage函数的位置)。(13)继续执行main函数的剩余部分,直到结束。上一页下一页返回7.4函数的嵌套调用

【例7-8】任何一个整数n的立方都可以表示成n个相邻奇数的和,其中最大奇数为d=2m-1,m=1+2+3+4+…+n。任意输入一个整数n,求这些奇数。上一页下一页返回7.4函数的嵌套调用

程序调用示意如图7.4所示。上一页下一页返回7.4函数的嵌套调用

运行结果:上一页返回7.5函数的递归调用

一个函数在它的函数体内直接或间接地调用它自身称为递归调用,这种函数称为递归函数。C语言允许函数的递归调用。在递归调用中,主调函数又是被调函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层。图7.5表示了直接递归调用,图7.6表示了间接递归调用。从图7.5和图7.6可以看到,这两种递归调用将无休止地调用其自身,这当然是不正确的。为了防止递归调用无终止地进行,就必须在函数内有终止递归调用的操作。常用的办法是添加条件判断,满足某种条件后就不再作递归调用,然后逐层返回。下一页返回7.5函数的递归调用

【例7-9】用递归法计算n!。计算n!的数学公式如下:n!=1×2×3×…×(n-1)×n用递归法计算n!可用下述公式表示:上一页下一页返回7.5函数的递归调用

上一页下一页返回7.5函数的递归调用

递归函数fac(5)调用示意如图7.7所示。上一页下一页返回7.5函数的递归调用

设执行本程序时输入“5”,即求5!。在主函数中的调用语句即为y=fac(5),进入fac函数后,由于n=5,不等于0或1,故应执行“f=n∗fac(n-1)”,即f=5×fac(5-1)。该语句对fac作递归调用即fac(4)。进行四次递归调用后,fac函数形参取得的值变为1,故不再继续递归调用而开始逐层返回主调函数。fac(1)的函数返回值为1,fac(2)的返回值为2×1=2,fac(3)的返回值为3×2=6,fac(4)的返回值为4×6=24,最后返回值fac(5)为5×24=120。上一页下一页返回7.5函数的递归调用

【例7-10】汉诺塔(Hanoi)问题。汉诺塔问题是典型的利用递归方法解题的例子。问题是:一块板上有三根针:A、B、C。A针上套有64个大小不等的圆盘,大盘在下、小盘在上。如图7.8所示。要把这64个圆盘从A针移动到C针,每次只能移动一个圆盘,移动可以借助B针进行。但在任何时候、任何针上的圆盘都必须保持大盘在下、小盘在上。求移动的步骤。分析:设A上有n个圆盘。如果n=1,则将圆盘从A直接移动到C。如果n=2,则:(1)将A的n-1(等于1)个圆盘移到B。(2)将A的一个圆盘移到C。(3)将B的n-1(等于1)个圆盘移到C。上一页下一页返回7.5函数的递归调用

如果n=3,则:(1)将A的n-1(等于2,令其为n′)个圆盘移到B(借助于C),步骤如下:第1步,将A的n′-1(等于1)个圆盘移到C。第2步,将A的一个圆盘移到B。第3步,将C的n′-1(等于1)个圆盘移到B。(2)将A的一个圆盘移到C。(3)将B的n-1(等于2,令其为n′)个圆盘移到C上(借助A),步骤如下:第1步,将B的n′-1(等于1)个圆盘移到A。第2步,将B的一个圆盘移到C。第3步,将A的n′-1(等于1)个圆盘移到C。至此,完成了3个圆盘的移动过程。上一页下一页返回7.5函数的递归调用

从上面分析可以看出,当n≥2时,移动圆盘的过程可以分解为以下3个步骤:第1步,把A的n-1个圆盘移到B。第2步,把A的一个圆盘移到C。第3步,把B的n-1个圆盘移到C。其中,第1步和第3步是类似的。当n=3时,第1步和第3步又分解为类同的三步,即把n′-1个圆盘从一根针移到另一根针上,n′=n-1。上一页下一页返回7.5函数的递归调用

显然,这是一个递归过程。据此算法,可编写程序代码如下:上一页下一页返回7.5函数的递归调用

上一页下一页返回7.5函数的递归调用

运行结果:上一页下一页返回7.5函数的递归调用

程序说明:从程序中可以看出,hanoi函数是一个递归函数,它有4个形参:n、x、y、z。n表示圆盘数,x、y、z表示三根针。hanoi函数的功能是把x的n个圆盘移动到z。当n==1时,直接把x的圆盘移到z,输出x→z。如n!=1,则分为三步:递归调用hanoi函数,把n-1个圆盘从x移到y;输出x→z;递归调用hanoi函数,把n-1个圆盘从y移到z。在递归调用过程中,n=n-1,故n的值逐次递减,当n=1时,终止递归,逐层返回。上一页下一页返回7.5函数的递归调用

从上面的例子可以看出,能采用递归描述的问题必须符合以下两个条件:(1)可以把一个问题转化为一个新的问题,而这个新问题的解决方法仍与原问题的解决方法相同,只是所处理的对象有所不同,但它们也只是有规律地递增或递减。(2)必须有一个明确的结束条件。否则,递归将无休止地进行。采用递归算法的优点是可将复杂问题变得很简单;缺点是系统要自动安排一系列内部操作,每调用函数一次,就需在内存堆栈区分配空间,用于存放函数变量、返回值等信息,如果递归次数过多,就可能引起堆栈溢出。因此,该算法通常使效率降低,对硬件要求高。此外,并不是所有的问题都可以用递归来实现。上一页返回7.6数组作为函数参数

数组可以作为函数参数使用,进行数据传送。数组用作函数参数时,有两种形式:一种是把数组元素(下标变量)作为实参使用;另一种是把数组名作为函数的形参或实参使用。1.数组元素作函数实参数组元素就是下标变量,它与普通变量并无区别。因此它作为函数实参使用与普通变量是完全相同的,在函数调用时,把作为实参的数组元素的值传送给形参,实现单向的值传送.下一页返回7.6数组作为函数参数

【例7-11】从键盘输入10个任意整数,求其中最大的数。上一页下一页返回7.6数组作为函数参数

运行结果:程序说明:从程序中可以看出,整型函数max有两个形参,即int型变量x和y。在函数体中,根据x和y的值输出相应的结果,返回给主函数。在主函数main中,首先完成数组a的输入,然后用一条for语句以数组a的元素值作为实参来调用max函数,将函数返回值传送给m,最后输出m的值。上一页下一页返回7.6数组作为函数参数

2.数组名作函数参数数组名代表了数据元素所占内存单元的首地址,用数组名作函数参数时,所进行的传送是地址的传送,即把实参数组的首地址赋予形参数组名。在函数调用时,编译系统不为形参数组分配内存,而将实参的地址直接传给形参。所以,实参数组和形参数组共享相同的内存单元,即形参数组和实参数组为同一数组,共同拥有一段内存空间。上一页下一页返回7.6数组作为函数参数

【例7-12】在数组a中存放了一个学生5门课程的成绩,求平均成绩。上一页下一页返回7.6数组作为函数参数

上一页下一页返回7.6数组作为函数参数

程序说明:从程序中可以看出,实型函数aver,有一个形参为实型数组a,长度为5。在函数aver中,把各元素值相加求出平均值,返回给主函数。在主函数main中,首先完成数组sco的输入,然后以数组名sco作实参来调用aver函数,函数返回值传送给av,最后输出av的值。上一页下一页返回7.6数组作为函数参数

【说明】(1)用数组名作函数参数时,应该在主调函数和被调函数分别定义数组。例如,在例7-12中,a是形参数组名,sco是实参数组名,分别在其所在函数中定义,不能只在一方定义。(2)形参数组和实参数组的类型必须一致,否则将引起错误。(3)将普通变量(或下标变量)作为函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时,发生的值传送是把实参变量的值赋予形参变量。在将数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。因为形参数组实际上并不存在,编译系统不为形参数组分配内存。那么,数据的传送是如何实现的呢?在6.3.4节介绍过,数组名就是数组的首地址。因此在数组名作函数参数时,所进行的传送只是地址的传送,即把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就相当于有了实在的数组。上一页下一页返回7.6数组作为函数参数

图7.9所示说明了这种情形。设a为实参数组,类型为整型,a占有以2000为首地址的内存区;b为形参数组名。当发生函数调用时,进行地址传送,把实参数组a的首地址传送给形参数组名b,于是b也取得该地址2000。因此,数组a、b共同占有以2000为首地址的一段连续的内存单元。从图中还可以看出,a和b下标相同的元素实际上也占相同的两个内存单元(整型数组每个元素占2字节)。例如,a[0]和b[0]都占用2000和2001单元,即a[0]=b[0]。依次类推,则a[i]=b[i]。上一页下一页返回7.6数组作为函数参数

(4)形参数组可以不指定大小,即在定义数组时,在数组名后面接一个空的方括号。也可以用另一参数作大小,以确定使用实际数组的元素个数。例如,可以写为floataver(floata[5])或floataver(floata[],intn)其中,形参数组a没有给出长度,而由n值动态地表示数组的长度。n的值由主调函数的实参进行传送。由此,例7-12可改为例7-13的形式。上一页下一页返回7.6数组作为函数参数

【例7-13】在数组a中存放了一个学生5门课程的成绩,求平均成绩。形参数组不定义长度。上一页下一页返回7.6数组作为函数参数

上一页下一页返回7.6数组作为函数参数

运行结果:程序说明:从程序中可以看出,实型函数aver的形参数组a没有给出长度,由n动态地确定该长度。在main函数中,函数调用语句为“aver(sco,5);”,其中实参5将赋予形参n作为形参数组的长度。上一页下一页返回7.6数组作为函数参数

(5)在变量作函数参数时,所进行的值传送是单向的,即只能从实参传向形参,不能从形参传回实参。形参的初值和实参相同,而形参的值发生改变后,实参并不变化,两者的终值是不同的。当用数组名作函数参数时,情况则不同。由于实际上形参和实参为同一数组,因此当形参数组发生变化时,实参数组也随之变化。当然,这种情况不能理解为发生了“双向”的值传递。但从实际情况来看,调用函数之后,实参数组的值将由于形参数组值的变化而变化。上一页下一页返回7.6数组作为函数参数

【例7-14】数组名作函数参数。上一页下一页返回7.6数组作为函数参数

上一页下一页返回7.6数组作为函数参数

运行结果:上一页下一页返回7.6数组作为函数参数

程序说明:从程序中可以看出,函数nzp的形参为整型数组a,长度为5。主函数中实参数组b也为整型,长度也为5。在主函数中,首先输入数组b的值,然后输出数组b的初始值,接下来,以数组名b为实参来调用nzp函数。在函数nzp中,按要求把负值单元清0,并输出形参数组a的值。返回主函数之后,再次输出数组b的值。从运行结果可以看出,数组b的初值和终值是不同的,数组b的终值和数组a是相同的。这说明实参、形参为同一数组,它们的值同时得以改变。上一页下一页返回7.6数组作为函数参数

(6)多维数组也可以作函数的参数。在函数定义时,对形参数组可以指定每一维的长度,也可省去第一维的长度。例如:intMA(inta[3][10])或intMA(inta[][10])这两种写法都是合法的,而且等价。但是,不能省略第2维或其他高维的大小说明。例如,下面的声明是不合法的:intMA(inta[3][])或intMA(inta[][])这种要求是由数组在内存中的存放特点决定的。二维数组是由若干个一维数组组成的,在内存中,数组是按行存放,因此在声明二维数组时,必须指定列数(即一行中包含几个元素)。上一页下一页返回7.6数组作为函数参数

【例7-15】有一个3×4矩阵,求矩阵中所有元素的和。上一页下一页返回7.6数组作为函数参数

运行结果:其实,C编译系统并不检查多维数组中第一维的大小。当二维数组作实参时,在第2维大小相同的情况下,实参数组的第1维可以与实参数组不相同。例如:形参数组声明为inta[5][4];而实参数组可以声明为inta[2][4];或inta[7][4];上一页返回7.7局部变量和全局变量

在讨论函数的形参变量时曾经提到,形参变量只在被调用期间才分配内存单元,调用结束立即释放。这表明,形参变量只有在函数内才有效,离开该函数就不能再使用了。这种变量有效性的范围称为变量的作用域。不仅对于形参变量,C语言中所有的量都有自己的作用域。变量说明的方式不同,其作用域也不同。C语言中的变量,按作用域范围可分为两种,即局部变量和全局变量。下一页返回7.7局部变量和全局变量

1.局部变量局部变量也称为内部变量。局部变量是在函数内作定义说明的,其作用域仅限于函数内,离开该函数后再使用这种变量是非法的。例如:上一页下一页返回7.7局部变量和全局变量

上一页下一页返回7.7局部变量和全局变量

【说明】(1)函数f1是一个无参函数,a、b是在f1的范围内有效的一般变量。或者说,变量a、b的作用域限于f1内。(2)函数f2带两个形式参数x和y,i和j为一般变量。在f2的范围内,x、y、i、j有效,或者说,变量x、y、i、j的作用域限于f2内。(3)主函数中定义的变量也只能在主函数中使用,因此变量m、n的作用域限于main函数内。不能在其他函数中使用。同时,主函数中也不能使用其他函数中定义的变量。因为主函数也是一个函数,它与其他函数是平行关系。这一点与其他程序语言是不同的,应予以注意。(4)函数f3的函数体内包含一个内嵌的语句块(复合语句),变量z是在复合语句中定义的变量,其作用域只在复合语句范围内有效。而在函数体内声明的变量p和q在整个函数体内有效,显然在复合语句内也有效。上一页下一页返回7.7局部变量和全局变量

(5)形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量。(6)允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆。上一页下一页返回7.7局部变量和全局变量

【例7-16】局部变量示例。上一页下一页返回7.7局部变量和全局变量

运行结果:程序说明:在main中定义了i、j、k三个变量,其中k未赋初值。而在复合语句内又定义了一个变量k,并赋初值为8(第5行)。注意:这两个k不是同一个变量。在复合语句外,由main定义的k起作用;而在复合语句内,则由在复合语句内定义的k起作用。因此,程序第4行的k为main所定义,其值应为5;第7行输出的k值为5。第6行在复合语句内,由复合语句内定义的k起作用,其初值为8,故输出值为8。上一页下一页返回7.7局部变量和全局变量

2.全局变量全局变量也称为外部变量,它是在函数外部定义的变量。它不属于哪一个函数,它属于一个源程序文件,其作用域从它定义位置开始至源文件结束。例如:上一页下一页返回7.7局部变量和全局变量

上一页下一页返回7.7局部变量和全局变量

【说明】(1)在函数中使用全局变量,一般应作全局变量说明。只有在函数内经过说明的全局变量才能使用。(2)全局变量所作用到的函数,相当于这些函数的公共变量。于是,当一个函数对其值进行改变后,另一个函数使用该变量的值亦相应改变。这有利于函数之间进行值传递。(3)不要随意使用全局变量。一方面,全局变量始终占据内存单元;另一方面,如果函数依赖于外部定义的变量,就将降低通用性。(4)对于不在作用域内的函数,若使用全局(外)变量,需在函数体内加上extern保留字。(5)全局变量和局部变量同名时,局部变量有效。上一页下一页返回7.7局部变量和全局变量

【例7-17】输入正方体的长、宽、高。求正方体的体积及三个面的面积。上一页下一页返回7.7局部变量和全局变量

运行结果:上一页下一页返回7.7局部变量和全局变量

程序说明:使用外部变量,可以增加函数的返回数。在本题程序中,函数vs返回了4个变量v、s1、s2和s3的值。上一页下一页返回7.7局部变量和全局变量

【例7-18】外部变量与局部变量同名。上一页下一页返回7.7局部变量和全局变量

运行结果:程序说明:如果同一个源文件中,外部变量与局部变量同名,则在局部变量的作用范围内,外部变量被“屏蔽”,即它不起作用。在main函数中,变量a的值8屏蔽了外部变量a的值3。上一页返回7.8变量的存储类别

7.8.1动态存储方式与静态存储方式前面已经介绍了,从变量的作用域(即从空间)角度来分,可以把变量分为全局变量和局部变量。从另一个角度,从变量值存在的作用时间(即生存期)角度来分,可以把变量存储方式分为静态存储方式和动态存储方式。静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式,而动态存储方式则是在程序运行期间根据需要来动态地分配存储空间的方式。用户存储空间可以分为三个部分:程序区、静态存储区、动态存储区。数据分别存放在静态存储区和动态存储区。全局变量全部存放在静态存储区,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。在程序执行过程中,它们占据固定的存储单元,而不动态地进行分配和释放。下一页返回7.8变量的存储类别

动态存储区存放以下数据:(1)函数形式参数。(2)自动变量(未加static声明的局部变量)。(3)函数调用时的现场保护和返回地址。对以上数据,在函数开始调用时分配动态存储空间,在函数结束时释放这些空间。如果一个函数被多次调用,而在该函数中定义了局部变量,则这些局部变量的存储空间地址可能不相同,存储单元反复地分配、释放。在C语言中,每个变量和函数有两个属性:数据类型和数据的存储类别。存储类别是指数据在内存中的存储方式,具体包含4种:自动(auto)、静态(static)、寄存器(register)、外部(extern)。上一页下一页返回7.8变量的存储类别

7.8.2局部变量存储类别1.自动变量(auto变量)函数中的局部变量,如不专门声明为static存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属此类,在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间。这类局部变量称为自动变量。自动变量用关键字auto作存储类别的声明。上一页下一页返回7.8变量的存储类别

例如:intf(inta)

/∗定义f函数,a为参数∗/{autointb,c=3;

/∗定义b,c自动变量∗/︙}其中,a是形参,b、c是自动变量,对c赋初值3。执行完f函数后,自动释放a、b、c所占的存储单元。关键字auto可以省略。若省略auto,则隐含定义为“自动存储类别”,属于动态存储方式。上一页下一页返回7.8变量的存储类别

2.静态局部变量(static局部变量)在函数体(或复合语句)内部,用static关键字进行声明的变量称为静态局部变量。【例7-19】静态局部变量。上一页下一页返回7.8变量的存储类别

运行结果:上一页下一页返回7.8变量的存储类别

【说明】(1)静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。自动变量(即动态局部变量)属于动态存储类别,占动态存储空间,函数调用结束后即释放。(2)静态局部变量在编译时赋初值,即只赋初值一次;而对自动变量赋初值是在函数调用时进行,每调用一次函数,就重新赋一次初值,相当于执行一次赋值语句。(3)如果在定义局部变量时不赋初值,则对静态局部变量而言,编译时自动赋初值0(对数值型变量)或空字符(对字符变量)。而对自动变量而言,如果不赋初值,则它的值是一个不确定的值。上一页下一页返回7.8变量的存储类别

【例7-20】输出1~5的阶乘值。上一页下一页返回7.8变量的存储类别

运行结果:程序说明:每调用一次fac(i),就输出一个i!,同时保留这个i!的值,以便下次乘以(i+1)。上一页下一页返回7.8变量的存储类别

3.寄存器变量(register变量)在C语言中,用存储类别声明为register的变量称为寄存器变量。它也是自动类变量,与auto变量的区别仅在于:register变量建议编译程序将变量的值保留在CPU寄存器中,而不是像一般变量那样占内存单元。因为确定和修改寄存器变量的值不需要访问内存,所以程序运行时,访问存于寄存器内的值要比访问存于内存中的值快得多。因此,通常将使用频繁的变量定义为register变量,可以提高程序的运行速度。比较常用的是将register变量作为循环控制变量。上一页下一页返回7.8变量的存储类别

【例7-21】使用寄存器变量,求s=1+2+3+…+1000。程序代码:程序说明:本程序循环1000次,i和s都频繁使用,因此可以定义为寄存器变量。上一页下一页返回7.8变量的存储类别

【说明】(1)只有局部自动变量和形式参数可以作为寄存器变量。register变量通常仅用于int型和char型的变量。(2)一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量。(3)局部静态变量不能定义为寄存器变量。例如,以下声明是错误的:registerstaticintm;【注意】一个变量只能声明为一种存储类别。静态变量存放在内存中的静态存储区中。不能将一个变量既存放在静态存储区,又放在寄存器中,二者只能择其一。上一页下一页返回7.8变量的存储类别

7.8.3全局部变量存储类别1.extern声明外部变量外部变量(即全局变量)是在函数的外部定义的,它的作用域为从变量定义处开始,到本程序文件的末尾。如果在定义点之前的函数想引用该外部变量,则应该在引用之前用关键字extern对该变量作“外部变量声明”,表示该变量是一个已经定义的外部变量,以扩展外部变量的作用域。有了此声明,就可以从“声明”处起,合法地使用该外部变量。所有全局变量均属于静态存储变量,它们一定存放在静态存储区。全局变量既可以被本文件中各函数用,也可以被其他源文件中的函数引用。上一页下一页返回7.8变量的存储类别

1)只被本文件中的函数引用当一个外部变量的声明不在文件的开头,在它的声明之前的函数想要引用的话,则应该在引用之前用关键字extern对该变量进行“外部变量”声明,告诉编译器此变量是已经声明的外部变量。从声明之后,就可以在调用函数中合法地使用该全局变量。上一页下一页返回7.8变量的存储类别

【例7-22】用extern声明外部变量,扩展程序文件中的作用域。上一页下一页返回7.8变量的存储类别

运行结果:程序说明:在本程序文件的最后1行定义了外部变量A、B,由于该外部变量定义的位置在函数main之后,因此本来在main函数中不能引用外部变量A、B。然而,由于在main函数中用extern对A和B进行了“外部变量声明”,因此就可以从“声明”处起,合法地使用该外部变量A和B。用extern声明外部变量时,类型名可以省略不写。例如,“externintA,B;”也可以写成“externA,B;”。上一页下一页返回7.8变量的存储类别

2)可被其他文件中的函数引用在同一个应用程序中,不同的源文件不能声明同名的变量,否则在进行连接时会出现“重复声明”的错误。如果是两个或多个源程序文件需要引用同一个外部变量,那么只需要在其中一个文件中用extern关键字作外部变量声明即可。这样就可以把外部变量的作用域扩展到多个文件,甚至整个应用程序。上一页下一页返回7.8变量的存储类别

【例7-23】用extern声明外部变量,作用域扩展到其他程序文件中。上一页下一页返回7.8变量的存储类别

上一页下一页返回7.8变量的存储类别

上一页下一页返回7.8变量的存储类别

运行结果:程序说明:在本程序file2.c文件的开头,声明了外部变量x,告诉编译器本文件中出现的变量x是一个已经在其他文件中声明过的外部变量,不必再重新为其分配存储空间。本来外部变量x的作用域是文件file1.c,通过在file2.c文件中使用外部变量声明,其作用域扩展到file2.c文件夹。如果还有更多的文件需要使用变量x,只要在文件开头加上“externx;”声明即可。上一页下一页返回7.8变量的存储类别

2.用static声明外部变量用static关键字声明的外部变量称为静态外部变量(也称静态全局变量)。静态外部变量只能被本文件中的函数引用,而不能被其他文件引用。注意,外部变量本身就是静态变量,加static关键字并不会影响它的存储方式。非静态外部变量与静态外部变量的区别在于:非静态外部变量的作用域可以被扩展到整个应用程序,即在一个源文件中声明的外部变量,在其他源文件中可以通过使用extern关键字声明来引用它;而静态外部变量的作用域只限于本文件,不可以通过使用外部变量声明扩展其作用域到其他源文件。上一页下一页返回7.8变量的存储类别

例如:/∗file1.c文件∗/#include<stdio.h>staticchara;voidmain(){

︙}/∗file2.c文件∗

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论