




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、第八章 函 数,一个例子:,main( ) int a , b, c, max1; int x, y, z , max2; max1=ab? a : b; max1=cmax1? c:max1; max2=xy? x : y; max2=zmax2? z:max2; ,int max(int x, int y, int z) int m; m=xy? x:y; m=zm? z:m; return(m); ,main( ) int a , b, c, max1; int x, y, z , max2; max1=max(a,b,c); max2=max(x,y,z); ,程序有函数组成。 本章主
2、要介绍: 函数的定义、函数调用、数据传递、函数参数、函数值等。,8.1 概述,一个大 程序一般应分为若干个程序模块,一个模块用来实现一个特定的功能。在高级语言中用子程序实现模块的功能。 在C语言中,子程序的作用是由函数完成的。 一个C程序由一个或若干个源文件构成。一个源文件由一个或若干个函数组成。,在程序设计中,常将一些常用的功能模块编写成函数,放在函数库中供选用。我们应该善用这些函数,以减少重复编写程序段的工作量。,一个C 程序由一个主函数和若干个函数构成。由主函数调用其它函数,其它函数也可以互相调用。同一个函数可以被一个或多个函数调用任意多次。 下图是一个函数调用的示意图。,运行结果如下:
3、 * * * * * * * * * * How do you do ! * * * * * * * * * *,printstar( ) , print_message( ) 都是无参函数。main( )函数调用这两个子函数。,例8.1 一个函数调用的例子。 main() printstar( ); print_message( ); printstar( ); printstar( ) printf(* * * * * * * * * *n); print_message( ) printf(How do you do !n);,1. 一个源程序文件由一个或多个函数组成。一个源程序文件是一
4、个编译单位,即以源程序文件为单位进行编译,而不是以函数为单位进行编译。 2. 一个C程序由一个或多个源文件组成(便于分别编写、编译和调试)。一个源文件可以为多个C程序公用。 C程序的执行从main函数开始,调用其它函数后流程回到main函数,在main函数中结束整个程序的运行。main函数是系统定义的。 所有函数都是平行的,即在定义函数时是相互独立的,一个函数并不从属于另一个函数,即函数不能嵌套定义。函数间可以相互调用,但不能调用main 函数。,说明:,6. 从函数形式看,C语言的函数分两类: (1) 无参函数:调用时,主调函数并不将数据传送给被调函数,一般用来执行指定的一组操作。无参函数可
5、以带回或不带回函数值,一般以不带回函数值居多。 (2) 有参函数:调用时,在主调函数和被调函数之间有数据传递。,5. 从用户使用的角度看,C语言的函数有两种: (1) 标准函数(库函数) (2) 用户自定义函数,1. 无参函数的定义形式 类型标识符 函数名( ) 声明部分 语句 ,8.2 函数定义的一般形式,说明:“ 类型标识符” 指定函数值的类型,即函数带回来的值的类型。如果不需要带回函数值,可以不写类型标识符。 例如: printstar( ) printf(*n); “函数名”应该是合法的标识符。,2. 有参函数定义的一般形式 类型标识符 函数名( 形式参数表列 ) 声明部分 语句 ,说
6、明: 形式参数的说明必须写在函数体的外面,函数体内用到的变量必须在函数体内进行说明。 函数类型与函数体中用于返回值的变量类型应保持一致。 如果在定义函数时不指定函数类型,系统会隐含指定函数类型为int型。,例如: int max(int x , int y) int z; /*函数体中的声明部分*/ z=xy?x:y; return(z); ,3. 空函数 类型标识符 函数名( ) 例如: dummy( ) ,用途:调用时,空函数不进行任何操作,没有任何实际作用,只是在程序中占一个位置,以后用一个编好的函数来代替。这样做的好处是程序总的结构清晰,可读性好,便于扩充新功能。,运行结果: 输入:
7、7,8 输出: max is 8,8.3 函数参数和函数的值,8.3.1 形式参数和实际参数 当主调函数和被调用函数之间有数据传递关系时,应定义为有参函数。 在定义函数时,函数名后面括号中的变量名称为“形式参数”(简称形参). 在调用函数时,函数名后面括号中的参数(可以为常量、变量、表达式),称为“实际参数”(简称实参)。,在定义函数中指定的形式参数,在未出现函数调用时,它们并不占内存中的存储单元。只有在发生函数调用时,函数中的形参才被分配内存单元,在调用结束后,形参所占的内存单元也被释放。 实参可以是常量、变量或表达式,但必须有确定的值。 例如:c=max(3,a+b); 在调用时将实参的值
8、赋给形参(数组除外,若形参是数组名,则传递的是数组首地址,而不是数组的值)。 在函数定义中必须指定形参的类型。 实参与形参的类型应相同或赋值相容。,关于形参和实参的说明:,C规定实参变量对形参变量的数据传递是“值传递”(即单向传递),只能由实参传给形参,而不能由形参传回来给实参。在内存中,实参单元与形参单元是不同的单元。(如图1),6. 在调用函数时,给形参分配存储单元,并将实参对应的值传递给形参,调用结束后,形参单元被释放,实参单元仍保留并维持原值。因此,在执行一个被调函数时,形参的值如果发生改变,并不会改变主调函数的实参的值。 (如图2),通过函数调用使主调函数能得到一个确定的值,这就是函
9、数的返回值。例如,在例8.2中,max(3,2) 的值是3,max(5,3) 的值是5。 1. 函数的返回值 是通过函数中的 return 语句获得的。return 语句将被调用函数中的一个确定值带回到主调函数中去。 如果需要从被调用函数中带回一个函数值,则被调用函数中必须包含 return 语句; 如果不需要从被调用函数中带回一个函数值,则可以不要 return 语句。 一个函数中可以有一个以上的,执行到哪一个return 语句,哪一个就起作用。 例如 int max(int x , int y) if (xy)return(x); elsereturn(y); ,8.3.2 函数的返回值,
10、例如:return z ; 与 return (z) ; 等价。 又如int max( int x, int y) return (xy?x:y) ; ,return 语句的使用形式: return (表达式); 或 return 表达式; 或 return;,返回表达式的值,不写括号,作用同上,不返回值,2. 函数值的类型 应当在定义函数时用类型标识符指定函数值的类型。 例如: int max(int x, int y) - 函数值为整型 double min(double x, double y) - 函数值为双精度型 C语言规定:凡不加类型说明的函数一律自动按整型处理 例如: int m
11、ax(int x,int y) 可写成 max(int x,int y) 在定义函数时,对函数的类型说明一般应该和 return 中的表达式的值的类型一致。,当函数值的类型和 return 语句中的表达式的值不一致时,以函数类型为准。对数值型数据,可自动进行类型转换。,main( ) float a , b ; int c ; scanf(%f,%f, ,当返回时,先将z的值转换成 int型,然后由return带回一个整型的函数值。,运行结果: 输入:1.5 , 2.5 输出:max is 2,若c 定义为实型,按%f 格式输出,结果又如何?,2.000000,例 8.3 返回值类型与函数类型
12、不同。,如果被调用函数中没有 return 语句,并不表示函数不带回函数值,而是带回一个不确定的值,如下面的语句是合法的:,为了明确表示函数“不带回函数值”,可以用 “void”将函数定义为“无类型”,(即“空类型”),这样系统就保证不使函数带回任何值。 例如: void main( ) printstar( ); void printstar( ) printf(*n); ,main( ) int a,b,c; a=printstar( ); b=print_message( ); c=printstar( ); printf(%5d%d%d,a,b,c); ,【例 8.4 】 main(
13、) int p, i=2 ; p=f (i,+i) ; printf(“%d”, p) ; int f (int a , int b) int c ; if (ab) c=1 ; else if (a=b) c=0 ; else c= -1 ; return ( c) ; ,8.4 函数的调用,8.4.1 函数调用的一般形式 函数名(实参表列); 如果是调用无参函数,则可以没有实参表列,但是“( )”不能省略。 如果实参表列中包含多个实参,每个实参之间用“,”分隔。 实参和形参必须一一对应,即个数相等,类型一致。 实参与形参按顺序对应,一一传递数据。但需要说明的是,如果实参表列有多个实参,对参
14、数求值的顺序并不是确定的,有的系统按自左至右,有的系统(如TURBO C、MS C等)则按自右至左顺序求值。,运行结果 自右至左:0 自左至右:-1 Turbo C 系统运行结果为0.,应避免这种引起歧义的形式,按函数在程序中出现的位置分,可以有三种函数调用方式: 函数语句 把函数调用作为一个语句。这时并不要求函数带回值,只要求函数完成一定的操作。 例如: printstar( ); 2. 函数表达式 函数出现在表达式中,这种表达式称为函数表达式。这时要求函数带回一个确定的值以参与表达式的运算。 例如: c=2*max(a,b) ; 3. 函数参数 :函数调用作为一个函数的实参。 例如: m=
15、max(a, max(b, c) ); printf(“%d”, max(a , b) ); 函数调用作为函数的参数, 实质上也是函数表达式调用的一种,因为函数的参数本来就要求是表达式形式。,8.4.2 函数调用的方式,8.4.3 对被调用函数的说明 和 函数原型,在一个函数中调用另一个函数(即被调函数)应具备什么条件? 被调用的函数必须是已经存在的函数(库函数或用户自己定义的函数)。但仅有这一 条件还不够。 如果是使用库函数,一般应该在程序的开头用 #include命令将调用有关库函数时所需的信息“包含” 到本文件中来。 如前面已用过的:#include #include 如果使用用户自定义
16、的函数,并且该函数与调用它的函数(即主调函数)在同一个文件中,一般还应在主调函数中对被调函数作声明(declaration),即向编译系统声明将要调用此函数,并将有关信息通知编译系统,以便于检查。,例 8.5 对被调用的函数作声明。 main( ) float add( float x , float y) ; /* 对被调函数声明 */ float a , b , c ; scanf( %f ,%f, ,注意:函数的“定义”和“声明”不是一会事。 “定义”是指对函数功能的确立,包括指定函数名、函数值类型、形参及其类型、函数体等,它是一个完整的、独立的函数单位。 而“声明”的作用则是把函数的名
17、字、函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(例如函数名是否正确,实参与形参的类型和个数是否一致等)。,函数定义,包括函数首部及函数体。,在函数声明中也可以不写形参名,而只写形参的类型。如 float add( float , float) ; 这种函数声明称为函数原型(function prototype)。 使用函数原型是ANSI C的一个重要特点,主要是利用它在程序的编译阶段对调用函数的合法性进行全面检查。 函数原型的一般形式为 函数类型 函数名(参数类型1,参数类型2,) 函数类型 函数名(参数类型1 参数名1,参数类型2 参数名2,) 第
18、一 种是基本形式。第二种只是为了阅读方便,在函数原型中加上了参数名。但编译系统不检查参数名,因此参数名是什么都无所谓。 应当保证函数原型与函数首部写法上的一致, 即函数类型、函数名、参数个数、参数类型和参数顺序必须相同。函数调用时, 函数名、实参个数应与函数原型一致。实参类型必须与函数原型中的形参类型赋值相容,如果不赋值相容, 则按出错处理。,说明:, 以前的C版本中函数声明方式不是采用函数原型,而只是声明函数名和函数类型。 如 float add( ); 系统不检查参数类型和参数个数。新版本兼容这种用法,但不提倡这种用法,因为它不进行全面检查。 如果函数值是整型或字符型,可以不进行类型说明。
19、 实际上,如果在函数调用之前,没有对函数作声明,则编译系统会把第一次遇到的该函数形式(函数定义或函数调用)作为函数的声明,并将函数类型默认为int 型。 有些C教材说,如果函数类型为整型或字符型,可以在函数调用前不必作函数声明。但是,使用这种方法时,系统无法对参数的类型做检查,若调用函数时参数使用不当,在编译时也不会报错。 为了程序清晰和安全,建议都加以声明为好。, 如果被调用函数的定义出现在主调函数之前,可以不进行声明。因为编译系统已经先知道了该函数的类型,会根据函数首部提供的信息对函数的调用作正确性检查。,如果把 例8.5改写,把 add 函数放在main 函数之前,就不必在main函数中
20、对add 做声明了。,(4) 如果已在所有函数定义之前,在文件的开头、函数的外部 做了函数声明,则在各个主调函数中 不必对所调用的函数再作声明。,char letter( char, char); float add(float, float); int max(int, int); main() . x=add(y,z); c=letter(a,b); . char letter(char c1,char c2) . float add(float a,float b) . ,8.5 函数的嵌套调用,C语言的函数定义都是互相平行的、独立的,因此C语言中不能嵌套定义函数, 但可以嵌套调用函数,
21、即在调用一个函数的过程中,又可以调用另一个函数。如下图所示:,例 8.6 用弦截法求方程 f(x)= x3 -5x2 +16x -80 =0 的根。,方法: 取两个不同的点 x1、x2, 如果f(x1)和f(x2)符号相反,则在区间(x1,x2)内必有一个根; 如果f(x1)和f(x2)符号相同,改变x1、x2,直到f(x1)与f(x2)异号。 连接f(x1)、f(x2)两点,与x 轴相交于x, 并由x 求出f(x):,若f(x)与f(x1)同号,则根在(x, x2)内,这时将x作为新的x1; 若f(x)与f(x2)同号,则根在(x1, x)内,这时将x作为新的x2; 重复第2、第3步,直到
22、|f(x)|为止。为一个很小的数,此时认为 f(x)0,x 即为所求的根。,功能分解: 1. 定义一个函数f(x) ,用来求x 的函数值 f(x) = x3 -5x2 +16x -80。 2. 定义一个函数xpoint(x1,x2),用来求f(x1)和f(x2)的连线与x轴的交点x。 3. 定义一个函数root(x1,x2),用来求(x1,x2)内的那个实根。 这里,函数root中要调用函数xpoint,而函数xpoint中又要调用函数 f 。,#include /* 定义函数f(x) */ float f(float x) float y; y=x*x*x - 5*x*x + 16*x -
23、80; return(y); /* 定义函数xpoint */ float xpoint(float x1,float x2) float x; x=(x1*f(x2) - x2*f(x1) / (f(x2) - f(x1); return(x); ,/* 定义函数root */ float root(float x1,float x2) float x, y, y1; y1=f(x1); do x=xpoint(x1,x2); y=f(x); if (y*y10) y1=y; x1=x; else x2=x; while (fabs(y)0.0001); return(x); ,/* 主函数
24、 */ main( ) float x1,x2,f1,f2,x; do printf(input x1,x2); scanf(%f %f, ,程序执行示意图:,作 业 (1),P186 8.1 8.3,在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。 C语言的特点之一就在于允许函数的递归调用。 例如:,8.6 函数的递归调用,间接调用,从图上可以看出,这两种递归调用都是无终止的自身调用。显然,程序中不应该出现这种无终止的递归调用,而只能出现有限次数的、有终止的递归调用,这可以用if 语句来控制,只有在某一条件成立时才继续执行递归调用,否则就不再继续。 递归调用的条件
25、: 在有限次的调用之后结束递归调用,即: 递归必须有一个出口。,例 8.7 用递归调用求年龄。有5个人,第5人比第4人大2岁,第4人比第3人大2岁,第2人比第1人大2岁,第1个人是10岁,问 第5个人几岁?,age(5)=age(4)+2,即 age(n)=10(n=1) age(n)=age(n-1)+2(n1),递归的求解过程分为两个阶段: 第一阶段 回推: 一直回推到递归出口条件为止; 第二阶段 递推: 从递归的出口开始进行递推求解, 一直推算到求得所要求的值为止。,age(int n) /*求年龄的递归函数*/ int c; /*c用作存放函数的返回值*/ if (n=1) c=10;
26、 else c=age(n-1)+2; return(c); main( ) printf(%dn, age(5); ,float fac(int n) float f; if(n0) printf(data errorn); else if (n= =0 | n=1) f =1; else f =n* fac(n-1); return(f); main( ) int n; float y; scanf(%d, ,例 8.8 用递归调用求n! 用递推方法求n! :从1开始,乘2,再乘3,直到乘n。递推法的特点是从一个已知的事实出发,按一定的规律推出下一个事实,再从这个新的已知事实出发,向下推出
27、一个新的事实这和递归(回推递推)不同 。 用递归方法求n! :5!= 5*4!,4!=4*3!, 2!=2*1!,1!=1。用递推公式表示为:,例8.9 Hanoi(汉诺塔问题),一个古典的数学问题: 古代有一个梵塔,塔内有3个座A、B、C,开始时A座上有64个盘子,盘子大小不等,大的在下,小的在上。 有一个老和尚想把这64个盘子从A 座移动到C 座,每次只允许移动一个盘,且在移动过程中,在每个座上都始终保持大盘在下,小盘在上。在移动过程中可以利用B 座,写出移动步骤。 这是一个只有用递归方法才能解决的问题。,分析3个盘子的情况: 1. 将A座上2个盘子移到B座 (借助C); 2. 将A座上1
28、个盘子移到C座; 3. 将B座上2个盘子移到C座 (借助A)。 其中第2 步可以直接实现。 第1、3步还需要递归分解。,递归分解: 第1 步将A座上2个盘子移到B座(借助C),分解为: 1.1 将A上一个盘子从A移到C; 1.2 将A上一个盘子从A移到B; 1.3 将C上一个盘子从C移到B。,第3步将B座上2个盘子移到C座 (借助A),分解为: 3.1 将B上一个盘子从B移到A; 3.2 将B上一个盘子从B移到C; 3.3 将A上一个盘子从A移到C。,将以上综合起来,可得到移动3个盘子的步骤为: AC,A B,C B, A C, B A,B C,A C。 共经历7(=231)步。 可以推知,移
29、动 n 个盘子需要经历2 n 1。,1. 将A 座上n-1个盘子借助C 座移到B 座上 ; 2. 将A 座上剩下的1个盘子移到C 座上; 3. 将B 座上n-1个盘子借助A 座移到C 座上 。 将第1步和第3步表示为: 将“one” 座上n-1个盘子借助“two”座移到“three”座。只是在第1 步和第3 步中,one、two、three和A、B、C的对应关系不同。 对第1步,对用关系是:oneA,twoC,threeB。 对第3步,对用关系是:oneB,twoA,threeC。 因此,可以将上面的3个步骤分成两类操作: 将n-1个盘子从一个座移到另一个 座上(n1) ; 将1个盘子从一个座
30、移到另一个 座上; 分别用两个函数来实现这两类操作。hanoi (n, one, two, three) 表示“ 将n 个盘子从one 座借助two 座移到three 座,函数move(x, y) 表示将一个盘子从x 座移到y 座。one、two、three、x 和y 对应不同情况的 A、B、C。,将n 个盘子从A 座移到C 座,可以分解为3个步骤:,汉诺塔程序:,void move(char x, char y) printf(%c -%cn, x,y); void hanoi( int n, char one, char two, char three) if (n= =1)move(on
31、e, three); else hanoi( n-1, one, three, two); move(one, three); hanoi( n-1, two, one, three); main( ) int m; printf(Input the number of diskes:n); scanf(%d, ,打印移盘方案。,float p(int n, float x) float y; if(n=0) y=1; else if (n=1) y=x; else y=( (2*n-1)*x-p(n-1,x) -(n-1)*p(n-2, x)/n; return(y); ,void main
32、( ) int n; float x, y; float p(int n, float x); printf(Input n,x:n); scanf(%d%f, ,用变量作为函数的参数时,实参变量对形参变量的数据传递是“值传递”,在函数调用过程中,形参值的变化并不会改变主调函数中实参的值 。 在C语言中,数组元素和数组名也可以作为函数参数。数组元素作函数实参,用法与变量相同。数组名作为函数的实参和形参,传递的是整个数组。,8.7 数组作为函数参数,一、 数组元素作函数实参 由于实参允许是表达式的形式,数组元素可以是表达式的组成部分,因此数组元素可以作为函数的实参。 与变量作实参一样,作为实参的
33、数组元素向形参变量传递数据时也是单向的“值传递”。,void main( ) int m,a2=3, 5; int max(int, int); /* 函数声明*/ m=max(a0, a1); printf(max=%dn,m); printf(a0=%d, a1=%dn,a0,a1); int max(int x, int y) int t; if(xy) /* 两数交换 */ t=x; x=y; y=t; printf(x=%d, y=%dn,x,y); return(x); ,运行结果: x=5, y=3 max=5 a0=3, a1=5 a0、a1的值没有改变!,【例 8.10】 数
34、组 a 和 b各有10个元素,将它们对应元素逐个比较。如果a 中元素大于b 中元素的数目(n)大于 b 中元素大于a 中相应元素的数目(m),则认为a 数组大于b 数组。给出大于,小于,等于的次数。,分析: 读入数组 a、b中10个元素数据。 比较数组a 和 b 中各个元素值,统计大于、小于、等于的次数。 比较大于的次数,由此判断a 和b 的大小。 可以将比较两个数的大小定义成一个函数。,int large(int x, int y) int flag ; if(xy) flag=1 ; else if(xy) flag= -1 ; else flag=0 ; return(flag) ; ,
35、main( ) int large(int x, int y) ; int a10,b10, i ,n=0,m=0,k=0 , f ; printf(Enter array a:n) ; for(i=0;ibi */ else if( f =0) m+ ;/* ai = = bi */ else k+ ;/* ai b %d timen a=b %d timen ak) printf(array a is larger then array b. n) ; else if(nk) printf(array a is smaller than array b. n) ; else printf(
36、array a is equal to array b. n) ; ,int large(int x, int y) int flag ; if(xy) flag=1 ; else if(xy) flag= -1 ; else flag=0 ; return(flag) ; ,用数组名作函数参数时,实参 与形参都应该用数组名(或数组指针)。这时传递的是整个数组。 【例 8.11】有一个一维数组score,内放10个学生成绩,定义一个函数求平均成绩。,float average(float array10) int i; float aver, sum=array0; for (i=1;i10;
37、i+) sum=sum+arrayi; aver=sum/10; return(aver); main() int i; float score10,aver; printf(input 10 scores:n) ; for(i=0 ; i 10 ; i+) scanf(%f, ,二、 数组名作函数参数,函数可改写为: float average(float array10) int i; float aver, sum=0; for (i=0;i10;i+) sum=sum+arrayi; aver=sum/10; return(aver); ,用数组名作函数参数时,必须在主调函数和被调用函
38、数中分别定义数组,不能只在一方定义。 如上例score、 array分别是实参、形参数组名。 实参数组和形参数组的类型必须一致,否则将出错。 实参数组和形参数组的大小可以一致,也可以不一致,但形参数组长度必须足够大。 C 编译时对形参数组大小不做检查,只是将实参数组的首地址传给形参数组。 形参数组也可以不指定大小,在定义形参数组时数组名后面跟一对空的“ ”,另外再设一个参数用来传递数组元素的个数。 上例求平均 可改为下面的形式:,注意:,main( ) int i,n,m; float score130, score240; float aver1,aver2; printf(Enter nu
39、mber1:); scanf(%d, ,float average(float array , int n) int i; float aver,sum=array0; for (i=1;in;i+) sum=sum+arrayi; aver=sum/n; return(aver); ,【例8.12 】,用数组名作函数实参时,不是把数组的值传递给形参,而是把实参数组的起始地址(首地址)传递给形参数组,即“地址传递”,这样形参数组和实参数组共占同一段内存单元,形参数组不重新分配内存空间。,函数调用过程中,形参数组中各元素的值如果发生变化,就会使实参数组元素的值同时发生变化。这一点与变量作函数参数
40、的情况不同。在程序设计中可以有意识地利用这一 特点改变实参数组元素的值。,【例 8.13】 用(改进的)选择法 对数组中的10个数进行排序。 所谓选择法,就是先将10个数中最小的数与a0对换;再将a1到a9中最小的数与 a1对换 。每比较一轮,找出还未排序的数中最小的一个,10个数共比较9轮。,对于n个数的排序: (1) 在 n个数中找最小的元素与 a0 交换; (2) 在 n-1个数中找最小的元素与 a1 交换; 直至全部排序完毕。,下面以5个数为例说明选择法。 a0 a1 a2 a3 a4 3 6 1 9 4 a0和a1,a2,a3,a4比较, 最后a0和 a2交换 1 6 3 9 4 a
41、1和 a2,a3,a4比较,最后a1和a2交换 1 3 6 9 4 a2和 a3,a4比较,最后a2和a4交换 1 3 4 9 6 a3和 a4比较,交换 1 3 4 6 9 排序结束,/* 排序函数 */ void sort(int array ,int n) int i , j , k, t; for(i=0 ; in-1 ; i+) k=i ; for (j=i+1 ; jn ; j+) if (arrayjarrayk) k=j ; if( k != i) t=arrayk; arrayk=arrayi; arrayi=t; ,/* 主函数 */ main() int i,n,a10;
42、 printf(Enter the array:n); for (i=0 ; i10 ; i+) scanf(%d, ,说明:变量k 用于存放每一趟最小元素的下标。 若k != i , 即存在比 ai 小的元素,则交换ak 和 ai 。,1. 多维数组元素作为实参时,与实参变量相同,只进行单向的“值传递”。 2. 多维数组名作实参和形参时,实参数组和形参数组之间进行的是 “地址传递”。 在函数中对形参数组的定义可以指定每一维的大小, 如: fun( int array310 ) . 也可以省略第一维的大小,但其它维的大小说明不能省略。 如: fun( int array 10 )/*合法*/
43、. fun(int array )/*不合法*/ . 在定义时,实参数组可以大于形参数组。 例如,实参数组定义为 int score510; 而 形参数组定义为 int array310; 这是由于“地址传递” 的缘故,实参数组元素和形参数组元素共占同一段内存单元。,三、 多维数组名作函数参数,int max_value(int array 4) int i , j , k , max ; max=array00 ; for(i=0 ; imax) max=arrayij ; return(max) ; void main( ) int a34=1,3,5,7,2,4,6,8,15,17,34
44、,12; printf(“max value is %dn”, max_value(a) ; ,【例 8.14 】求3*4矩阵中所有元素的最大值。,二维数组名 a,运行结果: max value is 34,p186: 8.5 8.6 8.13 8.17,作 业 (2),在C语言中,变量除了数据类型以外,还有不同的存储类别。按变量的作用域和存在域可将变量分为局部变量 和 全局变量。 8.8.1 局部变量 在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,也就是说只有在本函数内才能使用它们,在此函数之外是不能使用这些变量的,这种变量称为“局部变量”。其作用域和存在域为函数内部。,例如:
45、 int f1(int a) int b , c ; . char f2(int x , int y) int b , c ; . main( ) int m , n ; . ,8.8 局部变量和全局变量,main函数中定义的变量只在main函数中有效;主函数也不能使用其它函数中定义的变量。 不同函数中可以使用相同名字的变量,它们代表不同对象,互不干扰. 例如函数f1中的 b、c 和 f2中的b、c互不干扰,占用不同的内存单元。 形参也是局部变量,只在该函数中起作用。 例如,函数f1中的形参 a 和 f2中的形参x、y 都只在相应的函数内有效 一个函数内部,可以在复合语句中定义变量,这些变量只
46、在这个复合语句中有效,这种复合语句也称为“分程序”或“程序块”。,说明:,在函数之外定义的变量称为外部变量。外部变量是全局变量(也称全程变量)。 有效范围:全局变量可以为本文件中其它函数所公用。它的有效范围(即作用域和存在域)为从定义变量的位置开始 到 本源文件结束。,int p=1 , q=5 ; float f1(int a) int b , c ; . char c1 , c2 ; char f2( int x , int y) int i , j ; . main( ) int m , n ; . ,8.8.2 全局变量,在一个函数中既可以使用本函数中的局部变量,又可以使用有效的全局变
47、量。,有关全局变量的说明:, 设置全局变量的作用是增加函数间数据联系的渠道。 由于一个文件中的所有函数都能引用全局变量的值,因此,如果在一个函数中改变了全局变量的值,就能影响到其它函数,相当于各个函数间有直接的传递通道。 由于函数的调用只能带回一个返回值,因此,可以利用全局变量增加函数间联系的渠道,从函数得到一个以上的值。 为了便于区别全局变量和局部变量,在C 程序设计中,有一个不成文的约定(但非规定):将全局变量名的第一个字母用大写表示。,【例 8.15】 有一个一维数组,内放10个学生成绩,写一个函数,求出平均分、最高分、最低分。 显然希望从函数得到3个值,但一次函数调用只能返回一个函数值
48、,所以另外两个可用全局变量。,float Max=0 , Min=0 ; /* 外部变量定义 */ float average( float array ,int n) int i ; float aver,sum=array0; Max=Min=array0 ; for( i=1 ;iMax) Max=arrayi ; else if(arrayiMin) Min=arrayi ; sum=sum+arrayi ; aver=sum/n ; return(aver) ; main( ) float ave , score10 ; int j ; for(j=0 ; j10 ;j+) scan
49、f(%f, ,运行结果: 输入:99 45 78 97 100 67.5 89 92 66 43 输出: Max=100.0 Min=43.0 average=77.65, 建议尽量不要使用全局变量,因为: 全局变量在程序执行的全过程占用内存单元。 虽然它增加了函数之间的联系, 但会使程序的通用性、可靠性降低。 会降低程序的清晰性。在各个函数的执行过程中都可能改变外部变量的值,程序容易出错。,int a=2, b=8 ; max(int a,int b) int c ; c=ab?a:b ; return ( c ) ; main( ) int a=15; printf( %d, max(a,
50、 b) ); ,变量b是外部变量, 如果在同一个源文件中,外部变量和局部变量同名,则在局部变量的作用范围内,外部变量被“屏蔽”,即它不起作用。 例 8.16 外部变量和局部变量同名。,运行结果:15, 由于外部变量的有效范围是 从定义点 到文件终了,因此,如果要在定义点之前的函数中 使用该外部变量,应在这个函数中 对该外部变量作 “外部变量声明”。,外部变量定义在函数之后,外部变量声明方式: extern 类型标识符 外部变量;,8.9 变量的存储类别,按变量的作用域和存在域,可将变量分为局部变量 和 全局变量。 还可以从变量值存在的时间(即生存期)角度来分,将变量分为 静态存储方式变量 和
51、动态存储变量方式。 静态存储方式 是指在程序运行期间分配固定的存储空间的方式。 动态存储方式 是在程序运行期间根据需要进行动态的分配存储空间的方式。 内存中供用户使用的存储空间分为三部分,如下图所示:,8.9.1 动态存储方式与静态存储方式,在动态存储区存放以下数据: 函数形参变量; 局部变量; (未加 static 说明的局部变量,即自动变量) 函数调用时的现场保护和返回地址。 对这些数据,在函数调用开始时分配动态存储空间,函数结束时释放这些空间。,数据分别存放在静态存储区和动态存储区: 全局变量全部存放在静态存储区中,在程序开始执行时,给全局变量分配存储区,程序执行完毕就释放。 在程序执行
52、过程中它们占据固定的存储单元,而不是动态地进行分配和释放。,C语言中每一个变量和函数都有两个属性:数据类型和数据的存储类别。 数据的存储类别指的是数据在内存中存储的方法。,存储方法分为两大类:静态存储类和动态存储类。 具体包含四种:自动的(auto)、静态的(static)、寄存器的(register)、外部的(extern)。 根据变量的存储类别,可以知道变量的作用域和生存期。,函数中的局部变量,如不专门声明为static存储类别,都是动态地分配存储空间的。 分配和释放存储空间的工作是系统自动进行的,因此称为自动变量。自动变量用关键字auto 作存储类别声明。,例如: int f (int
53、a) /* 定义函数 f( ) ,a 为形参 */ auto int b , c ; /* 定义 b,c为自动变量 */ . ,关键字auto 可以省掉,auto不写则隐含为“自动存储类别”。 例如: int f (int a) int b , c ; . ,即 auto int b , c ;,8.9.2 auto局部变量,如果希望函数中的局部变量的值在函数调用结束后仍然保留,作为下一次函数调用时的初始值,这时应该将该变量定义为“静态局部变量”(static)。 【例 8.17 】考察静态局部变量的值。,f (int a) auto int b=0 ; static int c=3 ; b=
54、b+1 ; c=c+1 ; return(a+b+c) ; main( ) int a=2 , i ; for(i=0 ; i3 ; i+) printf(%4d , f(a) ; ,8.9.3 静态局部变量(static),运行结果: 7 8 9, 虽然静态局部变量在函数调用结束后仍然存在, 但是其它函数不能引用它(只能由定义它的函数专用)。,对静态局部变量的说明:, 静态局部变量属于静态存储类别,在静态存储区分配存储单元,在程序整个运行期间都不释放。 自动变量属于动态存储类别,占动态存储区,函数调用结束后即释放。, 对静态局部变量是在编译时赋初值的,即只赋初值一次,以后每一次调用时的初值就
55、是上次函数调用结束时的值(变量值具有连续性)。 自动变量赋初值,不是在编译时进行的,而是在函数调用时进行,每调用一次重新赋初值一次。(见上例), 对于静态局部变量,如果没有赋初值,则编译时自动赋初值0 (对数值型变量)或空字符(对字符型变量)。 对于自动变量,如果没有赋初值,则值是不确定的。 如auto int x ; x值是不确定的 static int y ; y值为 0,1. 需要保留函数上一次调用结束时的值。 【 例 8.18】 打印1到5的阶乘值。,int fac(int n) static int f=1 ; f=f*n ; return(f) ; main( ) int j ;
56、for(j=1 ; j=5 ; j+) printf(%d!=%dn, j , fac(j) ; ,2. 对于初始化后其值只被引用而不改变的变量。这时用局部静态变量可以避免每次调用时重新赋值。 一般情况下尽量少用静态存储变量,因为: 占用较多的内存 降低程序的可读性,结果: 1! =1 2! =2 3! =6 4! =24 5! =120,需要定义局部静态变量的情况:,一般情况下,变量的值是存放在内存中的。为了提高执行效率,C语言中允许 将局部变量的值存放在CPU中的寄存器,这种变量称为“寄存器变量” (register)。 【例 8.19】 使用register 变量。,int fac(in
57、t n) register int i , f=1 ; for(i=1 ; i=n ; i+) f=f*i ; return(f) ; main( ) int j ; for( j=1 ; j=5 ; j+) printf(%d! =%dn, j, fac(j) ); ,8.9.4 register 变量,注意: 1. 只有局部自动变量和形参可以作为寄存器变量,临时占用CPU寄存器. 2. 一个计算机系统中的寄存器数目是有限的,不能定义任意多个寄存器变量。不同的系统对register变量的处理也是不同的。 ( Turbo C 中虽然允许register变量,但作为auto变量处理 ) 3. 局部静态变量不能定义为寄存器变量。如 不能写成 register static int a,b,c; 不能把a、b、c 既放在静态存储区中,又放在寄存器中,二者只能居其一.对一个变量只能声明为一个存储类别。,外部变量(即全局变量)是在函数外部定义的,其作用域为 从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为本程序文件中的各个函数所引用。编译时将外部变量分配在静态存储区。 可以用extern 来声明外部变量,以扩展外部变量的作用域。,8.9.5 用extern 声明外部变量,1. 在一个文件内声明外部变量 如果外部变量不在文件头上定义,其有效作用域仅限于定义处到文
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年广州货运从业资格证网上考试题库及答案
- 利用志愿服务活动推动劳动教育的实践研究
- 人力资源管理招聘与选拔实务测试题
- ××超市打印设备办法
- ××中学诉讼管理制度
- 2025年运动场馆灯具项目规划申请报告
- 2025年公路养护检测设备项目申请报告
- 2025年观光型酒店项目提案报告模板
- 医学微生物学案例分析题集
- 业务合作协议及其合规责任承诺约定
- H3CNE认证考试题库及答案详解
- 景观绿化工程监理规划范文
- 公路工程施工质量控制培训
- 中国高血压防治指南(2024年修订版)
- 2025国家公务员政治理论应知应会知识考试题库(含答案)
- 隶书-课件教学课件
- 蔬菜种植基地管理手册
- 【MOOC】微处理器与嵌入式系统设计-电子科技大学 中国大学慕课MOOC答案
- epc项目劳务分包合同
- 《扭伤后怎么办》课件
- 【MOOC】算法初步-北京大学 中国大学慕课MOOC答案
评论
0/150
提交评论