1、第7章 函数,7.1 模块化程序设计概述 7.2 函数的定义 7.3 函数的调用 7.4 数组作为函数的参数 7.5 变量的作用域和存储类型 7.6 内部函数和外部函数,7.1 模块化程序设计概述,一个被开发的软件往往由许多功能组成,包含的程序语句很多。从组成来看,各个功能模块之间彼此有一定的联系,但是在功能上是各自独立的。从开发过程来看,可能不同的模块是由不同的程序员开发的,怎样才能将不同的功能模块连接在一起形成一个程序,怎样才能保证不同开发者的工作既不互相重复又能彼此衔接,这就需要进行模块化程序设计。而支持这种设计方法的语言被称为模块化程序设计语言,我们的C语言就具备这种模块化程序设计的功
2、能,实现这种功能的工具就是函数。 所谓模块化程序设计,指的是将一个大的程序自上向下进行功能分解,将其分成若干个子模块,每个模块对应了一个功能,有自己的界面,有相关的操作,可以完成独立的功能。各个模块可以分别由不同的人员编写和调试,最后再将不同的模块组装成一个完整的程序。在C语言中,就是用函数来实现功能模块的定义,C程序的功能可以通过函数之间的调用来实现。一个完整的C程序可以由多个源程序文件组成,而每一个文件中都可以包含多个函数。,返回,7.2 函数的定义,7.2.1 概述 函数是一个可以反复使用的、具有特定功能的且相对独立的程序段。 建立函数称为“函数的定义”,使用函数称为“函数的调用”。由于
3、函数可以被其他函数调用,通常把调用其他函数的函数称为“主调函数”,而把被调用的函数称为“被调函数”。在C语言中,除主函数外,任何函数都可以是被调函数,也可以是主调函数,而主函数main( )只能是主调函数,不允许是被调函数,因为任何函数都不能调用主函数。 每个函数都有特定的加工处理要求,称为“函数的功能”;每次调用函数时把要处理的对象和最终处理的结果统称为“函数的参数”;把主调函数中的加工处理对象传入被调函数,称为“函数参数的输入”;而把被调函数的加工处理最终结果带回到主调函数,称为“函数参数的输出”;函数参数的输入和输出统称为“函数间的数据传递”。,下一页,返回,7.2 函数的定义,下面我们
4、先来看一个简单的函数调用的例子。 例7-1 有下列程序。 main( ) printstar( ); /*调用printstar函数*/ print_message( );/*调用print message*/ printstar( );/*调用printstar函数*/ printstar( )/*printstar函数*/ printf(*n); ,上一页,下一页,返回,7.2 函数的定义,print_message( )/*print_message函数*/ printf(How do you do!n); 运行情况如下:printstar和print_message都是用户定义的函数名
5、,分别用来输出一排“*”号和一行信息。,上一页,下一页,返回,7.2 函数的定义,说明: 一个源程序文件由一个或多个函数组成。一个源程序文件是一个编译单位,即以源程序为单位进行编译,而不是以函数为单位进行编译。 一个C程序由一个或多个源程序文件组成。对较大的程序,一般不希望全放在一个文件中,而将函数和其他内容(如预定义)分别放在若干个源文件中,再由若干个源文件组成一个C程序。这样可以分别编写、分别编译,提高调度效率。一个源文件可以为多个C程序公用。 C程序的执行从main函数开始,调用其他函数后流程回到main函数,在main函数中结束整个程序的运行。main函数是系统定义的。,上一页,下一页
6、,返回,7.2 函数的定义,所有函数都是平行的,即在定义函数时是互相独立的,一个函数并不从属于另一函数,即函数不能嵌套定义(这是和PASCAL不同的),函数间可以互相调用,但不能调用main函数。 从用户使用的角度看,函数有两种: 标准函数,即库函数。这是由系统提供的,用户不必自己定义这些函数,可以直接使用它们。应该说明,不同的C系统提供的库函数的数量和功能不同,当然有一些基本的函数是共同的。 用户自己定义的函数。用以解决用户的专门需要。 从函数的形式看,函数分两类:,上一页,下一页,返回,7.2 函数的定义,无参函数。如例7-1中的printstar和print_message就是无参函数。
7、在调用无参函数时,主调函数并不将数据传送给被调用函数,一般用来执行指定的一组操作。printstar函数的作用是输出18个星号。无参函数可以带回或不带回函数值,但一般以不带回函数值的居多。 有参函数。在调用函数时,在主调函数和被调用函数之间有数据传递。也就是说,主调函数可以将数据传给被调用函数使用,被调用函数中的数据也可以带回来供主调函数使用。 7.2.2 函数的定义 任何函数(包括主函数main())都是由函数说明和函数体两部分组成。上面已介绍过,根据函数是否需要参数,可将函数分为无参函数和有参函数两种。,上一页,下一页,返回,7.2 函数的定义,1.无参函数定义 无参函数定义的一般形式为:
8、 类型标识符或void 函数名(void) 说明语句部分; 可执行语句部分; 注意:在旧标准中,函数可以缺省参数表。但在新标准中,函数不可缺省参数表,若不需要参数,则用“void”表示,主函数main()例外。,上一页,下一页,返回,7.2 函数的定义,2.有参函数定义 有参函数定义的一般形式为: 类型标识符 函数名(数据类型 参数1,数据类型 参数2 ) 说明语句部分; 可执行语句部分; 有参函数比无参函数多了一个参数表列。调用有参函数时,调用函数将赋予这些参数实际的值。,上一页,下一页,返回,7.2 函数的定义,例7-2 定义一个函数,用于求两个数中的较大数。 int max(int n1
9、, int n2) /*定义函数max()*/ int m; m= n1n2?n1:n2; return m; main() int num1,num2; printf(input two numbers:n); scanf(%d,%d, &num1, &num2); printf(max=%dn, max(num1,num2); ,上一页,下一页,返回,7.2 函数的定义,3.空函数 空函数是既无参数、又无函数体的函数。其定义的一般形式为: 函数类型或void 函数名(void) 例如: dummy( ) ,上一页,下一页,返回,7.2 函数的定义,调用此函数时,什么工作也不做,没有任何实际
10、作用。在主调函数中写上“dummy( );”表明“这里要调用一个函数”,而现在这个函数没有起作用,等以后扩充函数功能时补充上。在程序设计中往往根据需要确定若干模块,分别由一些函数来实现。而在第1阶段只设计最基本的模块,其他一些次要功能则在以后需要时陆续补上。在编写程序的开始阶段,可以在将来准备扩充功能的地方写上一个空函数(函数名取将来采用的实际函数名,如用merge( )、matproduct( )、concatenate( )和shell( )等,分别代表合并、矩阵相乘、字符串连接和希尔法排序等),只是这些函数未编好,先占一个位置,以后用一个编好的函数代替它。这样做,程序的结构清楚,可读性好
11、,以后扩充新功能方便,对程序结构影响不大。空函数在程序设计中常常是有用的。,上一页,下一页,返回,7.2 函数的定义,7.2.3 函数的参数和函数的返回值 1.函数参数 在调用函数时,主调函数和被调函数之间会涉及数据的传递。通常,主要出现在对有参函数的调用中。函数的参数有两种形式:形式参数和实际参数。在定义函数时,函数名后面括号中说明的变量称为形式参数,简称形参;在主调函数中,被调用函数名后面括号中的变量或表达式列表称为实际参数,简称实参。,上一页,下一页,返回,7.2 函数的定义,例7-3 求两个数的平均值。 #include main( ) int a=10,b=20; int aver;
12、 aver=average(a,b); /*a、b为实参*/ printf(a=%d, b=%d, aver=%dn, a, b, aver); ,上一页,下一页,返回,7.2 函数的定义,int average(int x, int y)/*x、y为形参*/ x+=y; y=x/2; printf(x=%d, y=%dn, x, y); return y; 运行结果: x=30, y=15 a=10, b=20, aver=15,上一页,下一页,返回,7.2 函数的定义,说明: 由于实参与形参存在着一一对应的关系,数据类型最好保持一致。数据类型不一致时,对于数值型数据,系统自动进行类型转换,
13、在不能完成类型转换时,系统显示出错信息。 实参可以是常量、变量或表达式,必须要有确定的值。 C语言规定,实参变量对形参变量的数据传递是单向的“值传递”,也就是说,数据只由实参传递给形参,而不会逆向传递。,上一页,下一页,返回,7.2 函数的定义,在调用函数时,系统给形参分配存储单元(一般分配在堆栈中),并将实参对应的值传递给形参。调用结束后,形参单元被释放,实参单元仍保留原值。因此,例7-3在执行average()函数时,形参x, y的值虽然发生了改变,但主调函数中实参a, b的值不变。 2.函数的返回值 一般来说,通过函数调用使主调函数能得到一个希望的值,这个值就是函数的返回值,也称函数值。
14、如在例7-3中,average()函数的值是15,返回到主调函数。,上一页,下一页,返回,7.2 函数的定义,函数返回值的说明: 函数的返回值是通过函数中的return语句获得的,return语句将被调函数中希望得到的值返回到主调函数中。有的主调函数不需要从被调函数中带回返回值,则可以不用return语句,或只用return,后面不带参数。对被调函数中有多个return语句的情况,程序执行到哪一个return语句,哪一个return语句就起作用。如: int max(int n1,int n2) if (n1n2) return n1; else return n2; ,上一页,下一页,返回,
15、7.2 函数的定义,return语句后面的参数有两种形式:return n1和return (n1)。 return语句后面的参数也可以是表达式:如return (x/2)。 函数返回值的类型。函数返回值是一个确定的值,在函数定义时须指定其类型,这一类型应当与函数定义时指定的函数类型保持一致。如果二者不一致时,对于数值型数据,系统自动以函数类型为准进行类型转换,在不能完成类型转换时,系统显示出错信息。C语言规定,对没有加类型说明的函数调用时,一律按int 型处理。,上一页,下一页,返回,7.2 函数的定义,例7-4 main( ) int c; c=circ(5,8); printf(%dn,
16、c); circ(int a,int b) /*没有指定函数类型,系统默认为int型*/,上一页,下一页,返回,7.2 函数的定义, float f;/*函数的返回值f,定义为float型*/ f=3.14159*(a+b)/2; return f; 运行结果: 20,上一页,返回,7.3 函数的调用,7.3.1 函数的调用 在一个函数中调用另一个函数时,程序控制就从主调函数中转移到被调函数,并且从被调函数的函数体起始位置开始执行该函数的语句。在执行完函数体中的所有语句,或者遇到return语句时,程序控制就从被调函数返回到主调函数中原来的断点位置继续执行。 1. 对被调用函数的说明 主调函数
17、在调用被调用函数之前要先说明后调用。函数的说明在程序的数据说明部分,它可以在函数内部说明,也可以在函数外部说明。函数的说明是以函数原型的方式出现的,所谓函数原型是指被调函数的首部后加一个分号“;”。,下一页,返回,7.3 函数的调用,函数说明的一般形式为: (1)存储类型 数据类型 函数名(类型名1,类型名2,); (2)存储类型 数据类型 函数名(类型名1 形参名1,类型名2 形参名2,); 第(1)种形式是基本的形式。为了便于阅读程序,也允许在函数原型中加上参数名,就成了第(2)种形式。其中,形参名可以省略,因为C编译系统只检查类型名,而不检查形参名。若省略存储类型,则默认为外部型。对于无
18、参函数则函数名后的圆括号内是空的。,上一页,下一页,返回,7.3 函数的调用,对被调函数说明,其目的在于通知编译系统被调函数的存储类型、返回值的类型以及形参的个数、类型和顺序,以便主调函数在调用它时逐一进行对照检查,保证调用的合法性。 C语言规定,在下列3种情况下可以省略对被调函数的说明: 若被调函数在主调函数之后定义,且被调函数的返回值是int型或char型,则可省略对被调函数的说明。 若被调函数的定义出现在主调函数之前,则主调函数可以省略对被调函数的说明。 若已在所有函数定义之前,在函数外部已做了函数声明,则在各个主调函数中不必对所调用的函数再作声明。例如:,上一页,下一页,返回,7.3
19、函数的调用,char letter(char,char); /*以下3行在所有函数之前,且在函数外部*/ float f(float,float); int i(float,float); main( ) /*不必声明它所调用的函数*/ char letter(char c1,char c2)/*定义letter函数*/ ,上一页,下一页,返回,7.3 函数的调用,float f(float x,float y)/*定义f函数*/ int i(float j,float k)/*定义i函数*/ 2. 函数调用的一般形式 函数调用的一般形式为: 函数名(实际参数表列); 若是调用无参函数,则“实
20、际参数表列”可以没有,但括弧不能省略。若实际参数表列包含多个实参,则各参数间用逗号隔开。实参与形参的个数应相等,类型应一致。实参与形参按顺序对应,一一传递数据。,上一页,下一页,返回,7.3 函数的调用,下面我们通过示例来说明函数调用中的相关规则。 例7-5 编一程序,给定实型变量x和整型变量n的值,试计算 这里设计3个函数,除主函数main( )外,还有两个函数:fun( )函数用于计算每一项的值,fac( )函数用于计算阶乘。 程序如下: main( ) long fac(int); float fun (float, long); int i, n; float x, t=1.0, df
21、=0.0; printf (Enter x, n:); scanf (%f, %d, ,上一页,下一页,返回,7.3 函数的调用,for (i=1; i=n; i+) t*=x; df+=fun (t, fac (i); /*函数调用fac (i)作为函数fun( )的实参*/ printf (df=%fn, df); float fun (float y, long m) float d; d=y/m; return (d); ,上一页,下一页,返回,7.3 函数的调用,long fac (int k) long p=1; int j; for (j=1; j=k; j+) p*=j; re
22、turn (p); 运行结果: Enter x, n: 3.56,10 df=35.551704 这里,在主函数main( )中的数据说明部分对函数fac( )和fun( )进行了说明;函数fac( )的调用作为函数fun( )调用的实参出现。而fun( )和fac( )在调用时,其实际参数在类型、个数与顺序上与形式参数完全一致并相匹配。,上一页,下一页,返回,7.3 函数的调用,一个函数可以被其他函数多次调用,每次调用可以处理不同的数据对象。函数调用的过程可归纳如下: 执行主调函数,当遇到函数调用时,系统首先计算实参表达式的值,并临时为形参分配存储单元,然后把实参的值复制一份到对应形参的存储
23、单元中。若是无参函数,则上述工作不执行。 将程序的流程控制转移到被调函数,执行其函数体内的语句。 当执行到return语句或到达函数末尾时,控制返回到主调函数的调用处,若函数有返回值,则回送一个值,同时释放形式参数所占的存储单元,然后从函数调用处继续执行主调函数的后面操作。,上一页,下一页,返回,7.3 函数的调用,3. 函数调用的方式 按函数在程序中出现的位置来分,可以有以下3种函数调用方式: (1)函数语句 把函数调用作为一个语句。如例7-1中的 printstar( ); 这时不要求函数带回值,只要求函数完成一定的操作。 (2)函数表达式 函数出现在一个表达式中,这种表达式称为函数表达式
24、。这时要求函数带回一个确定的值以参加表达式的运算。例如: c=2*max(a,b); 函数max是表达式的一部分,它的值乘2再赋给c。,上一页,下一页,返回,7.3 函数的调用,(3)函数参数 函数调用作为一个函数的实参。例如: m=max(a,max(b,c); 其中max(b,c)是一次函数调用,它的值作为max另一次调用的实参。m的值是a、b和c 3者中最大的。又如: printf(%d,max(a,b); 也是把max(a,b)作为printf函数的一个参数。 函数调用作为函数的参数,实质上也是函数表达式形式调用的一种,因为函数的参数本来就要求是表达式形式。,上一页,下一页,返回,7.
25、3 函数的调用,7.3.2 函数的嵌套调用 函数的嵌套调用是指调用一个函数的过程中,该函数又调用另一个函数。如图7-1所示。 上图中,main()函数的执行步骤是: 例7-6 求组合数 分析:组合数的计算公式为:,上一页,下一页,返回,7.3 函数的调用,定义函数cmn(m,n)用来计算组合数。该函数中需要3次计算阶乘,因此又可定义一函数fac(k),用以计算阶乘。 据此可写出程序如下: #include long fac(int x); int cmn(int m,int n); main() int c; c=cmn(8,5); printf(c(m, n)=%d,c); ,上一页,下一页
26、,返回,7.3 函数的调用,long fac(int k) int i; long y=1 ; for(i=1;i=k;i+) y=y*i; return y; ,上一页,下一页,返回,7.3 函数的调用,int cmn(int m,int n) int mn; mn=fac(m)/(fac(n)*fac(m-n); return mn; 请读者自己分析程序的执行过程。,上一页,下一页,返回,7.3 函数的调用,7.3.3 函数的递归调用 在调用某一函数过程中,出现直接或间接调用该函数自身的情况,称为函数的递归调用。这是C语言的一大特点。其中直接调用函数自身的,称直接递归调用;间接调用函数自身
27、的,称间接递归调用。如图7-2和图7-3所示。 图7-2和图7-3所示的情况,都无限次调用自身而构成死循环,是无意义的。因此,递归调用中须指定递归调用的次数或给定终止调用的条件。如例7-7。,上一页,下一页,返回,7.3 函数的调用,例7-7 用递归法求阶乘n!。 分析:对于求n!的递归方法,可用下面的公式表示:因为n!1*2*3*n,而(n-1)!= 1*2*3*(n-1)。于是可知n!=n*(n-1)!,(n-1)!=(n-1)*(n-2)!,。 当n=0或1时,n!=1。 所以,如果求n!的值,必须求(n-1)!的值,而求(n-1)!的值又依赖于求(n-2)!的值,一直向下推算,直到求出
28、1!,而1!=1,此时可回推出2!=2*1!=2*1=2,3!=3*2!=3*2=6,一直回推,直到回推到求n!值的计算。,上一页,下一页,返回,7.3 函数的调用,根据上述分析,可以设计一个函数fac(n),用以计算n!。程序如下: #include long fac(int n); main() long f; int n; printf(Input number n:); scanf(%d, ,上一页,下一页,返回,7.3 函数的调用,long fac(int n) long f; if(n0) printf(Error!n); exit(1); /*终止运行*/ if(n=1) f=1
29、; else f=n*fac(n-1); return f; ,上一页,下一页,返回,7.3 函数的调用,运行结果: Input number n:5 120 例7-8 Hanoi(汉诺)塔问题。这是一个古典的数学问题,是一个只有用递归方法(而不能用其他方法)解决的问题。问题是这样的,古代有一个梵塔,塔内有3个座A、B和C,开始时A座上有64个盘子,盘子大小不等,大的在下,小的在上,如图7-4所示。有一个老和尚想把这64个盘子从A座移到C座,但每次只允许移动一个盘,且在移动过程中在3个座上都始终保持大盘在下,小盘在上。在移动过程中可以利用B座,要求编程序打印出移动的步骤。,上一页,下一页,返回
30、,7.3 函数的调用,分析:设A上有n个盘子。 若n=1,则将圆盘从A直接移动到C。 若n=2,则: (1)将A上的n-1(等于1)个圆盘移到B上。 (2)再将A上的一个圆盘移到C上。 (3)最后将B上的n-1(等于1)个圆盘移到C上。 若n=3,则: (1)将A上的n-1(等于2,令其为n)个圆盘到B(借助于C), 步骤如下: 将A上的n-1(等于1)个圆盘移到C上。,上一页,下一页,返回,7.3 函数的调用,将A上的一个圆盘移到B。 将C上的n-1(等于1)个圆盘移到B。 (2)将A上的一个圆盘移到C。 (3)将B上的n-1(等于2,令其为n)个圆盘移到C(借助A),步骤如下: 将B上的n
31、-1(等于1)个圆盘移到A。 将B上的一个盘子移到C。 将A上的n-1(等于1)个圆盘移到C。 至此,完成了3个圆盘的移动过程。,上一页,下一页,返回,7.3 函数的调用,从上面分析可以看出,当n大于等于2时, 移动的过程可分解为3个步骤: 第1步 把A上的n-1个圆盘移到B上。 第2步 把A上的一个圆盘移到C上。 第3步 把B上的n-1个圆盘移到C上。 其中第1步和第3步是类同的。 当n=3时,第1步和第3步又分解为类同的3步,即把n-1个圆盘从一个针移到另一个针上,这里的n=n-1。 显然这是一个递归过程,据此可编程如下:,上一页,下一页,返回,7.3 函数的调用,move(int n,i
32、nt x,int y,int z) if(n=1) printf(%c%cn,x,z); else move(n-1,x,z,y); printf(%c%cn,x,z); move(n-1,y,x,z); ,上一页,下一页,返回,7.3 函数的调用,main() int h; printf(ninput number:n); scanf(%d, ,上一页,下一页,返回,7.3 函数的调用,从程序中可以看出,move函数是一个递归函数,它有4个形参n,x,y,z。n表示圆盘数,x,y,z分别表示3根针。move 函数的功能是把x上的n个圆盘移动到z 上。当n=1时,直接把x上的圆盘移至z上,输出
33、xz。如n!=1则分为3步:递归调用move函数,把n-1个圆盘从x移到y;输出xz;递归调用move函数,把n-1个圆盘从y移到z。在递归调用过程中n=n-1,故n的值逐次递减,最后n=1时,终止递归,逐层返回。当n=4 时程序运行的结果为:,上一页,下一页,返回,7.3 函数的调用,input number: 4 the steps of moving 4 diskes: ab ac bc ab ca cb ab ac,上一页,下一页,返回,7.3 函数的调用,bc ba ca bc ab ac bc,上一页,返回,7.4 数组作为函数的参数,前面介绍过可以用变量作函数参数,此外,数组元素
34、也可以作函数实参,其用法与变量相同。 数组作为函数的参数有两种形式:一种是数组元素作为函数实参; 另一种是数组作为函数参数,既可以作为形参,又可以作为实参。 当数组作为函数参数时,传递的是数组首地址。 7.4.1 数组元素作为函数实参 当数组元素作为函数实参时,与用变量作为函数参数时一样,是单向的“值传递”方式。 例7-9 用数组subject10存放10位同学的某门科目成绩,然后分别统计出该科目的及格人数和不及格人数。,下一页,返回,7.4 数组作为函数的参数,#include main() int subject10; int i,pass=0,unpass=0; for(i=0;i10;
35、i+) scanf(%d,上一页,下一页,返回,7.4 数组作为函数的参数,if(judge(subjecti)=0) unpass=unpass+1; printf(pass:%d, unpass:%dn,pass,unpass); int judge(int grade) int flag; if(grade=60) flag=1; else flag=0; return flag; ,上一页,下一页,返回,7.4 数组作为函数的参数,7.4.2 数组作为函数参数 数组作为函数参数时,既可以作为实参,也可以作为形参。 例7-10 有一个10位数的数组,今输入一个数,要求判断该数是否在该数组
36、中。 #include void find(int a,int b); main() int i,n,number10=12,32,45,2,23,58,46,77,44,35; printf(Input n:);,上一页,下一页,返回,7.4 数组作为函数的参数,scanf(%d,上一页,下一页,返回,7.4 数组作为函数的参数,else printf(Not find! n); exit(1); 说明: 数组作函数参数时,主调函数与被调函数中应分别定义数组,如例7-10中,number10为主调函数中的定义数组,a为被调函数中定义的数组。调用函数语句中,实参用到数组时只写数组名。如上例中的
37、find(number,n)语句,number为数组名。 实参数组与形参数组类型应一致。不一致时,若系统能进行类型转换则自动转换,但不一定能得到期望的结果。否则会发生“类型不匹配”的错误。,上一页,下一页,返回,7.4 数组作为函数的参数,用数组作函数参数时,数据传递的是整个数组,不是单个的数组元素。实际上,是把实参数组的首地址传递给形参数组,这样两个数组就共占同一段内存单元。 所以: 在定义形参数组时可以不指定其大小,只要在数组名后加上一个空括号 。如例7-10中,void find(int a,int b)就没有指定形参数组a的大小。如果被调用函数中涉及对数组元素个数的处理,可以另设一个参
38、数,用以传递需要处理的元素个数。,上一页,下一页,返回,7.4 数组作为函数的参数,例7-11 输入任意10个数,求它们的平均数。 #include float average(float array,int n); main() int i; float aver,number10; for(i=0;i10;i+) scanf(%f, ,上一页,下一页,返回,7.4 数组作为函数的参数,aver=average(number,10); printf(The average of 10 numbers is %6.2fn,aver); float average(float array,int
39、 n) int j; float aver,sum=0;,上一页,下一页,返回,7.4 数组作为函数的参数,for(j=0;j10;j+) sum=sum+arrayj; aver=sum/n; return aver; 形参数组中元素值的变化会使实参数组元素的值同时发生变化。,上一页,下一页,返回,7.4 数组作为函数的参数,例7-12 用选择法对数组中的5个数按由小到大排序。 分析:所谓选择法就是将5个数中最小的数与a0对换,再将a1到a4中最小的数与a1对换,每比较一轮,找出一个未经排序的数中最小的一个。5个数共需比较4轮。具体步骤如下:,上一页,下一页,返回,7.4 数组作为函数的参数
40、,据此可编写程序如下: void sort(int array,int n); main() int i,a5; printf(Enter 5 numbers:n); for(i=0;i5;i+) scanf(%d,上一页,下一页,返回,7.4 数组作为函数的参数,printf(n); sort(a,5); printf(The sorted order:n); for(i=0;i5;i+) printf(%d ,ai); printf(n); ,上一页,下一页,返回,7.4 数组作为函数的参数,void sort(int array,int n) int i,j,t; for(i=0;iar
41、rayj) t=arrayi; arrayi=arrayj; arrayj=t; ,上一页,下一页,返回,7.4 数组作为函数的参数,7.4.3 用多维数组名作函数参数 多维数组元素可以作为实参,这点与前述相同。 可以用多维数组名作为实参和形参,在被调用函数中对形参数组定义时可以指定每一维的大小,也可以省略第1维的大小说明。如 int array310; 或int array 10; 二者都合法而且等价。但是不能把第2维以及其他高维的大小说明省略。如下面是不合法的: int array ;,上一页,下一页,返回,7.4 数组作为函数的参数,因为从实参传来的是数组起始地址,在内存中各元素是一行接
42、一行地顺序存放的,而并不区分行和列,若在形参中不说明列数,则系统无法决定应为多少行多少列。不能只指定第1维而省略第2维,下面写法是错误的: int array3 ; 形参数组第1维的大小可以是任意的。例如,实参数组定义为 int score510; 而形参数组定义为 int array310; 均可以,C编译不检查第1维的大小。请读者从“传递地址”这一特点出发来思考这个问题。,上一页,下一页,返回,7.4 数组作为函数的参数,例7-13 有一个34的矩阵,求所有元素中的最大值。 解此题的算法是:先使变量max的初值为矩阵中第1个元素的值,然后将矩阵中各个元素的值与max相比,每次比较后都把“大
43、者”存放在max中,全部元素比较完后,max的值就是所有元素的最大值。 程序如下: max_value(int array 4) int i,j,max; max=array00; for(i=0;i3;i+) for(j=0;j4;j+),上一页,下一页,返回,7.4 数组作为函数的参数,if(arrayijmax)max=arrayij; return(max); main( ) int a34=1,3,5,7,2,4,6,8,15,17,34,12; printf(max value is%dn,max_value(a); 运行结果如下: max value is 34,上一页,返回,7
44、.5 变量的作用域和存储类型,7.5.1 变量的作用域 通过前面的学习,已经知道变量是程序运行过程中值可以改变的量。编译系统为变量分配内存单元,用来存放程序运行过程中的输入数据、中间结果和最终结果等。变量的数据类型用来说明该变量在内存中所占的字节数以及变量的运算规则。此外,变量的属性还包含另两个方面的内容:一是变量的作用域,即变量的合法使用范围;一是变量的存储类别,用来说明变量的存储位置,变量的存储类别决定着变量的生存期。 变量只能在它的作用范围内使用,这就是变量的作用域规则。,下一页,返回,7.5 变量的作用域和存储类型,变量的作用域直接与定义变量的位置相关。在函数内部(或复合语句内部)定义
45、的变量称为局部变量;在函数外任意位置定义的变量称为全局变量。局部变量又称内部变量,全局变量又称外部变量。 局部变量的作用域是定义它的函数内或复合语句内,在它的作用域之外,局部变量是不可见的,也就是说,一个函数内定义的局部变量是不能被其他的函数所引用的。 局部变量的特性有助实现信息隐蔽,即使不同的函数定义了同名的局部变量,也不会相互影响。 全局变量的作用域是从定义它的位置开始,直至它所在源程序文件的结束。全局变量的使用增加了函数之间传递数据的途径,在全局变量的作用域内的任何函数都能引用该全局变量,一个函数对全局变量的修改,能影响到其他引用这个变量的函数;因此对全局变量的使用不当会产生意外的错误。,上一页,下一页,返回,7.5 变量的作用域和存储类型,全局变量的使用,也会使得函数的通用性降低,从结构化程序设计方法的角度看,函数应是完成单一功能的程序段,过多使用全局变量,会使函数之间的依赖性增加、耦合性高。一般情况下,除非性能的特别要求,建议避免使用全局变量。 例7-14 请写出下列程序的输出结果。 int a=1,b=2; int func1(int x) x+=a+; b+; return(x); ,