C程序设计 谭浩强 著 清华大学出版社
目 录 概述 函数定义的一般形式 函数的参数和函数的值 函数的调用 函数的嵌套调用 函数的递归调用 数组作为函数参数 局部变量和全局变量 目 录 概述 函数定义的一般形式 函数的参数和函数的值 函数的调用 函数的嵌套调用 函数的递归调用 数组作为函数参数 局部变量和全局变量 变量的存储类型 内部函数和外部函数 如何运行一个多文件的程序
8.1 概述 一个C程序可由一个主函数和若干个函数构成。由主函数调用其他函数,其他函数也可以互相调用。同一个函数可以被一个或多个函数调用任意多次。 说明 例8.1 示意图 返回主菜单
说 明 (1)一个源文件由一个或多个函数组成。一个源程序文件是一个编译单位,即以源程序为单位进行编译,而不是以函数为单位进行编译。 说 明 (1)一个源文件由一个或多个函数组成。一个源程序文件是一个编译单位,即以源程序为单位进行编译,而不是以函数为单位进行编译。 (2)一个C程序由一个或多个源程序文件组成。对较大的程序 ,一般不希望全放在一个文件中,而将函数和其他内容(如预定义)分别放在若干个源文件中,再由若干源文件组成一个C程序。这样可以分别编写、分别编译,提高调试效率。一个源程序可以为多个C程序公用。 (3)C程序的执行从main函数开始,调用其他函数后流程返回到main函数,在main 函数中结束整个程序的运行。main函数是系统定义的。
如例8.1的printstar和print_message (4)所有函数都是平行的,即在定义函数时是相互独立的,一个函数并不从属于另一函数,即函数不能嵌套定义(这是和PASCAL不同的)。函数间可以互相调用,但不能调用main函数。 (5)从用户使用的角度看,函数有两种: 标准函数,即库函数。 用户自己定义的函数。 (6)从函数的形式看,函数分两类: 无参函数。在调用无参函数时,主调函数并将数据传送给被调用函数,一般用来执行指定的一组操作。无参函数可以带回或不带回函数值,但一般以不带回函数值得居多。 有参函数。在调用函数时,在主调函数和被调函数之间有数据传递。也就是说,主调函数可以将数据传给被调用函数使用,被调用函数中的数据也可以带回来供主调函数使用。 系统自己定义的 如例8.1的printstar和print_message
main a b c d e f g h i
例8.1 简单的函数调用 调用printstar函数 main() {printstar(); print_message(); C程序设计 第八章 函 数 例8.1 简单的函数调用 main() {printstar(); print_message(); printstar(); } {printf( “ * * * * \n”); printf_message(); {printf(“ How do you do! \n”); 调用printstar函数 调用print_message函数 调用printstar函数 程序运行后,结果显示: * * * * * * * * * * * * * * * * * How do you do! 调用printstar函数 调用print_message函数 运行程序
8.2 函数定义的一般形式 无参函数的定义形式 类型标识符 函数名() {声明部分 语句 } 有参函数定义的一般形式 类型标识符 函数名() {声明部分 语句 } 有参函数定义的一般形式 类型标识符 函数名(形式参数表列) 例如: int max(int x,int y) {int z; z=x>y?x:y; return(z);}
例如: dummy() {} 可以有”空函数” 类型说明符 函数名() { } 对形参的声明的传统方式 在老版本C语言中,对形参类型的声明是放在函数定义的第2行。 例如: int max(x,y) int x,y; {int z; z=x>y?x:y; return(z);} 返回主菜单
8.3 函数参数和函数的值 形式参数和实际参数 在定义函数时函数名后面括弧中的变量名称为 “形式参数”(简称“形参”),在主调函数中调用一个函数时,函数名后面括弧中的参数(可以是一个表达式)称为”实际参数”. 例8.2 说明 函数的返回值 通过函数调用使主调函数能得到一个确定的值,这就是函数的返回值。 返回主菜单 说明 例8.3
例8.2 调用函数时的数据传递 main() {int a,b,c; scanf(“%d,%d”,&a,&b); c=max(a,b); printf(“Max is %d”, c); } max(int x, int y) {int z; z=x>y?x:y; return(z); 输入: 7,8 结果: Max is 8 运行程序
关于形参和实参的说明 (1)在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。只有在发生函数调用时,函数max中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放。 (2)实参可以是常量、变量或表达式。在调用时将实参的值赋给形参。 (3)在被定义的函数中,必须指定形参的类型。 (4)实参与形参的类型应相同或赋值兼容。 (5)C语言规定,实参变量对形参变量的数据传递是“值传递”,即单向传递,只由实参传给形参,而不能由形参传回来给实参。在内存中,实参单元与形参单元是不同的单元。 如: c=max(a,b) max(int x, int y) {int z; z=x>y?x:y; return(z);} 如:max(3,a+b)
如: c=max(a,b) max(int x, int y) {int z; x=x+8;y=y+12; z=x>y?x:y; return(z);} 调用时,a b 2 3 调用结束后 a 2 b 3 15 x 2 3 y x 10 y
说明 函数的值只能通过return语句返回主调函数。 return 语句的一般形式为: return 表达式; 或者为: 函数值的类型和函数定义中函数的类型应保持一致。 如函数值的类型和return语句中表达式的值不一致,以函数类型为准。 如: int max(float x, float y)(函数值为整形) char letter(char c1,char c2)(函数值为字符型) 例8.3
为了明确表示“不带回值”,可以用“void”定义“无类型”(或称“空类型”)。 例如: Void printstar() {…} 如果被调用函数中没有return语句,并不带回一个确定的、用户所希望得到的函数值,但实际上,函数并不时不带回值,而只是不带回有用的值,带回的是一个不确定的值。 为了明确表示“不带回值”,可以用“void”定义“无类型”(或称“空类型”)。 例如: Void printstar() {…} 下面的用法是错误的 a=printstar();
例8.3返回值类型与函数类型不同 main() {float a,b; int c; scanf(“%f,%f”,&a,&b); c=max(a,b); printf(“Max is %d”, c); } max(float x, float y) {float z; /* z为实型变量*/ z=x>y?x:y; return(z); 运行情况如下: 1.5,2.5 Max is 2 运行程序
8.4 函数的调用 调用无参函数,“实参列表”可以没有; 包括多个实参,各参数间用逗号隔开; 函数调用的一般形式 函数名(实参表列); 8.4 函数的调用 调用无参函数,“实参列表”可以没有; 包括多个实参,各参数间用逗号隔开; 函数调用的一般形式 函数名(实参表列); 函数调用的方式 1)函数语句 2)函数表达式 3)函数参数 对被调用函数的声明和函数原型 1)函数调用的条件 2 )函数原型 A.函数类型 函数名(参数类型1 ,参数类型2……); B.函数类型 函数名(参数类型1 参数名1,参数类型2 参数名2 ……); 例8.4 如: c=2*max(a,b); 如: printstar(); 例8.5 如: m=max(a,max(b,c)); 如: float add(float,float) 说明 返回主菜单
函数调用应具备的条件 被调用的函数必须是已经存在的函数(是库函数或用户自己定义的函数)。 如果使用库函数,一般还应该在本文件开头用#include命令将调用有关库函数时所需用到的信息“包含”到本文件中来。 如使用用户自己定义的函数,而且该函数与调用函数在同一文件中,一般应在主调函数中对被调用的函数作声明。 注意:定义和声明不是一回事。“定义”是对函数功能的确立,包括指定函数名,函数值类型、形参及类型等,是一个完整、独立的函数单位。 “声明”是把函数的名字、函数类型和形参的类型、个数以及顺序通知编译系统,以便在调用该函数时系统按此进行对照检查。 如: #include <stdio.h> 例8.5
说 明 如: 以前的C版本的函数声明方式不是采用函数原型,而只声明函数名和函数类型。 float add(); 说 明 如: float add(); 以前的C版本的函数声明方式不是采用函数原型,而只声明函数名和函数类型。 如在函数调用之前,没有对函数作说明,则编译系统会把第一次遇到的该函数形式(函数定义或函数调用)作为函数的声明,并将函数类型默认为int型。 如果被调用函数的定义出现在主调用函数之前,可不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必对所调用的函数再作声明。 举例 举例
例 8.4 main() {int i=2,p; p=f(i,++i); /*函数调用*/ printf(“%d”,p); } C程序设计 第八章 函 数 例 8.4 main() {int i=2,p; p=f(i,++i); /*函数调用*/ printf(“%d”,p); } int f(int a,int b) /*函数定义*/ {int c; if(a>b) c=1; else if(a==b) c=0; else c=-1; return(c); 运行结果: 运行程序
例 8.5 对被调用的函数作声明 main() {float add(float x,float y); float a,b,c; scanf(“%f,%f”,&a,&b); c=add(a,b); printf(“sum is %f”,c); } float add(float x,float y) {float z; z=x+y; return(z); } 运行情况如下: 输入3.6, 6.5 结果: sum is 10.000000 运行程序
例 8.5.1 举 例 float add(float x,float y); main() /*不必对add函数作声明*/ C程序设计 第八章 函 数 例 8.5.1 举 例 float add(float x,float y); main() /*不必对add函数作声明*/ {float a,b,c; scanf(“%f,%f”,&a,&b); c=add(a,b); printf(“%f”,c); } float add(float x,float y) {float z; z=x+y; return(z);
例 8.5.2 举 例 char lettter(char,char); /*以下3行在所有函数之前, 且在函数外部*/ 例 8.5.2 举 例 char lettter(char,char); /*以下3行在所有函数之前, 且在函数外部*/ float f(float,float); int i(float,float); main() {…} char letter(char c1,char c2) /*定义letter函数*/ float f(float x,float y) /*定义f函数*/ int i(float j,float k) /*定义i函数*/
8.5 函数的嵌套调用 1)C语言的函数定义都是相互平行、独立的,也就是说在定义函数时,一个函数内部不能包含另一个函数。 8.5 函数的嵌套调用 1)C语言的函数定义都是相互平行、独立的,也就是说在定义函数时,一个函数内部不能包含另一个函数。 2)C语言不能嵌套定义, 但可以嵌套调用函数。 图形说明 例8.6 返回主菜单
a函数 b函数 main函数 调用a函数 调用b函数
例 8.6用弦截法求方程f(x)=x3 -5x2 +16x -80=0根 C程序设计 第八章 函 数 例 8.6用弦截法求方程f(x)=x3 -5x2 +16x -80=0根 方法如下: (1)取两个不同点x1、x2,如果 f(x1)和f(x2)符号相反,则(x1,x2) 区间内必有一个根。如果f(x1)与f(x2)同符号,则应改变x1、x2,直到f(x1)与f(x2)异号为止。 (2)连接(x1,f(x1))和(x2,f(x2))两点,如图。 y f(x2) x1 x x2 x f(x1) f(x) x点的坐标可用下式求出: x=[x1• f(x2) – x2• f(x1) ] / [f(x2) - f(x1) ] 再从x求出f(x)
例 8.6用弦截法求方程f(x)=x3 -5x2 +16x -80=0根。 C程序设计 第八章 函 数 例 8.6用弦截法求方程f(x)=x3 -5x2 +16x -80=0根。 (3)若f(x)与f(x1)同符号,则根必在(x,x2)区间内,此时将x作为新的x1。如果f(x)与f(x2)同符号,则根必在(x1,x)区间内,将x作为新的x2。 (4)重复步骤(2)和(3),直到︱f(x) ︱<a为止,a为一个很小的数,例如10-6。此时认为f(x) ≈0。 N-S流程图
输入x1,x2,求f(x1),f(x2) 直到f(x1)和f(x2)异号 y=f(x)与y1=f(x2) y 与y1同号 真 假 x1=x 求(x1,f(x1))与(x2,f(x2))连线与x轴的交点x y=f(x)与y1=f(x2) y 与y1同号 真 假 x1=x y1=y x2=x y2=y 直到∣y∣<a 输出x的值
例 8.6用弦截法求方程f(x)=x3 -5x2 +16x -80=0根。 C程序设计 第八章 函 数 例 8.6用弦截法求方程f(x)=x3 -5x2 +16x -80=0根。 #include<math.h> float f(float x) /*定义f函数,以实现f(x)=x3-5x2+16x-80=0*/ {float y; y=((x-5.0)*x+16.0)*x-80.0; return(y); } float xpoint(float x1,float x2) /*定义xpoint函数,求出弦与x轴交点*/ {float y; y=((x1*f(x2)-x2*(fx1))/(f(x2)-f(x1));
例 8.6用弦截法求方程f(x)=x3 -5x2 +16x -80=0根。 C程序设计 第八章 函 数 例 8.6用弦截法求方程f(x)=x3 -5x2 +16x -80=0根。 float root(float x1,float x2) /*定义root函数,求近似根*/ {float x,y,y1; y1=f(x1); do { x=xpoint(x1,x2); y=f(x); if(y * y1>0) /*f(x1)与f(x2)同符号*/ {y1=y; x1=x; } else x2=x; }while(fabs(y)>=0.0001); return(x);
例 8.6用弦截法求方程f(x)=x3 -5x2 +16x -80=0根。 C程序设计 第八章 函 数 例 8.6用弦截法求方程f(x)=x3 -5x2 +16x -80=0根。 main() {float x1,x2,f1,f2,x; do { printf(“input x1,x2:\n”); scanf(“%f,%f”,&x1,&x2); f1=f(x1); f2=f(x2); }while(f1*f2>=0); x=root(x1,x2); printf(“A root of equation is%8.4f”,x); } 运行结果: input x1,x2; 从键盘上输入2,6 A root of equation is 5.0000 运行程序
例 8.6用弦截法求方程f(x)=x3 -5x2 +16x -80=0根。 C程序设计 第八章 函 数 例 8.6用弦截法求方程f(x)=x3 -5x2 +16x -80=0根。 该题函数调用的示意图: root函数 xpoint函数 f函数 main函数 调用root 函数 调用xpoint函数 调用f函数 输出根x结束
8.6 函数的递归调用 递归调用 在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。 直接调用本函数 如: int f(int x) {int y,z; z=f(y); return(2*z);} 递归调用 在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。 直接调用本函数 间接调用本函数 f函数 f1函数 f2函数 调用f2函数 调用f1函数 调用f函数
8.6 函数的递归调用 例8.7 例8.8 例8.9 返回主菜单
C程序设计 第八章 函 数 例 8.7 有5个人,第5个人说他比第4个人大2岁,第4个人说他对第3个人大2岁,第3个人说他对第2个人大2岁,第2个人说他比第1个人大2岁,第1个人说他10岁。求第5个人多少岁。 分析 age(5)=age(4)+2 age(4)=age(3)+2 age(3)=age(2)+2 age(2)=age(1)+2 age(1)=10 用数学公式表示: 10 (n=1) age(n)= age(n-1)+2 (n>1) 归纳
例 8.7 求第五个人年龄的过程 求解过程示意图 age(int n) /*求年龄的递归函数*/ {int c; /*c用作存放函数的返回 例 8.7 求第五个人年龄的过程 求解过程示意图 age(int n) /*求年龄的递归函数*/ {int c; /*c用作存放函数的返回 值的变量*/ if(n==1) c=10; else c=age(n-1)+2; return(c); } main() {printf(“%d”,age(5)); 运行程序 运行结果: 18
age(5) =18 age(5) =age(4)+2 age(4) =16 age(4) =age(3)+2 age(3) =14 age(3) age(2) =age(2)+2 =12 age(2) =age(1)+2 age(1) 10
input an intege number:10 C程序设计 第八章 函 数 例8.8 用递归方法求n! 1 (n=0,1) n!= n·(n-1)! (n>1) float fac(int n) {float f; if(n<0){print(“n<0,dataerror!”):} else if(n==0||n==1) f=1; else if=fac(n-1)*n; return(f);} main() {int n; float y; printf(“input an integer number:”); scanf(“%d”,&n); y=fac(n); printf(“%d!=%10.0f”,n,y);} 运行程序 运行情况: input an intege number:10 10!= 3628800
例8.9 Hanoi(汉诺)塔问题 古代有一个梵塔,塔内有3个座A、B、C,开始时A座上有64个 盘子,盘子大小不等,大的在下,小的在上(如下图)。有一个 老和尚相吧这64个盘子从A座移到C座,但每次只允许移动一个 盘,且在移动过程中在3个座上都始终保持大盘在下,小盘在上。 在移动过程中可以利用B座。 B A C
例8.9 Hanoi(汉诺)塔问题 分析如下: 首先,分析如何将A座上3个盘子移到C座上的过程; 1)将A座上2个盘子移到B座上(借助C); 3)将B座上2个盘子移到C座上(借助A). 其中2)可以直接实现。 其中1)可用递归方法分解: 1.1将A上1个盘子从A移到C; 1.2将A上1个盘子从A移到B; 1.3将C上1个盘子从C移到B。 其中3)分解为 3.1将B上1盘子从B移到A上; 3.2将B上1盘子从B移到C上; 3.3将A上1盘子从A移到C上。
例8.9 Hanoi(汉诺)塔问题 综合如下: A C A B C B A C B A B C A C 其次,由上述分析知道: 将n个盘子从A座移到C座可分解为以下3 个步骤: 1)将A上n-1个盘借助C座先移到B座上; 2)把A座上剩下的一个盘移到C座上; 3)将n-1个盘从B座借助于A座移到C座上。
例8.9 Hanoi(汉诺)塔问题 运行程序 运行结果 void move(charx,char y) {printf(“%c--->%c\n”,x,y); } void hanoi(int n,char one,char two,char three) /*将n个盘从one座借助two座,移到three座*/ {if(n==1) move(one,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:”); scanf(“%d”,&m)’ printf(“The step to moving %3d diskes :\ n”,m); hanoi(m,’A’,’B’,’C’);} 运行程序 运行结果
例8.9 Hanoi(汉诺)塔问题 运行情况如下: input the number of diskes:3 C程序设计 第八章 函 数 例8.9 Hanoi(汉诺)塔问题 运行情况如下: input the number of diskes:3 The step to moving 3 diskes: A --->C A --->B C--->B A--->C B--->A B--->C
8.7 数组作为函数参数 数组元素作函数实参 数组名作函数参数 用多维数组名作函数参数 例8.10 例8.11 例8.12 例8.13 说明 例8.14 数组元素就是变量,它与普通变量并无区别。 因此它作为函数实参使用与普通变量是完全相同的,在发生函数调用时,把作为实参的数组元素的值传送给形参,实现单向的值传送。 返回主菜单
C程序设计 第八章 函 数 例8.10 数组元素作为函数实参举例说明 有两个数组a,b,各有10个元素,将它们对应地逐个相比(即a[0]与b[0]比……)。如果a数组中的元素大于b 数组中的相应元素的数目多于b数组中元素大于a数组中相应元素的数目(如a[i]〉 b[i]6次,b[i]>a[i]3次,其中i每次为不同的值),则认为a数组大于b数组,并分别统计出两个数组相应元素大于、等于、小于的 次数。
例8.10 数组元素作为函数实参举例说明 main() {int large(int x,int y); /*函数说明*/ C程序设计 第八章 函 数 例8.10 数组元素作为函数实参举例说明 main() {int large(int x,int y); /*函数说明*/ int a[10],b[10],i,n=0,m=0,k=0; printf(“enter array a:\n”); for(i=0;i<10;i++) scanf(“%d”,&a[i]); scanf(“%d”,&b[i]); printf(‘\n”); {if(large(a[i],b[i])==1) n=n+1; else if(large(a[i],b[i])==0) m=m+1; else k=k+1;} printf(“a[i]>b[i] %d times \na[i]<b[i]%d times \n”,n,m,k); if(n>k) printf(“array a is larger than array b\n”); else if(n<K) printf(“array a is smaller than array b\n”); else printf(“array a is equal to array b\n”);}
例8.10 数组元素作为函数实参举例说明 运行情况如下: large(int x,int y) enter array a: C程序设计 第八章 函 数 例8.10 数组元素作为函数实参举例说明 large(int x,int y) {int flag; if(x>y) flag=1; else if(x<y) flag= -1; else flag=0; return(flag); } 运行情况如下: enter array a: 1 3 5 7 9 8 6 4 2 0 enter array b: 5 3 8 9 -1 -3 5 6 0 4 a[i]>b[i] 4 times a[i]=b[i] 1 times a[i]<b[i] 5 times array a is smaller than array b 运行程序
例8.11 数组名作函数参数举例说明 有一个一维数组score,内放10个学生成绩,求平均成绩。 运行情况如下: float average(float array[10]) {int i; float aver,sum=array[0]; for(i=1;i<10;i++) sum=sum+array[i]; aver=sum/10; return(aver);} main() {float score[10],aver; int i; printf(“input 10 scores:\n”); for(i=0;i<10;i++) scanf(“%f”,&score[i]); printf(‘\n”); aver=average(score); printf(“average score is %5.2f”,aver);} 运行情况如下: input 10 scores: 100 56 78 98.5 76 87 99 67.5 75 97 average score is 83.4 运行程序
例8.12数组名作函数参数举例说明2 对8.11的改写,即形参数组不指定大小。 运行结果: C程序设计 第八章 函 数 例8.12数组名作函数参数举例说明2 对8.11的改写,即形参数组不指定大小。 float average(float array[ ],int n) {int i; float aver,sum=array[0]; for(i=1;i<n;i++) sum=sum+array[i]; aver=sum/n; return(aver);} main( ) {float score_1[5]={98.5,97,91.5,60,55}; float score_2[10]={67.5,89.5,99,69.5,77,89.5,76.5,54,60,99.5}; printf(“the average of class A is %6.2f\n”,average(score_1,5)); printf(“the average of class B is %6.2f\n”,average(score_2,5));} 运行结果: the average of class A is 80.40 the average of class B is 78.20 说明 运行程序
说 明 用数组与用数组元素名作函数参数不同点: 说 明 用数组与用数组元素名作函数参数不同点: 1)用数组元素作实参时,只要数组类型和函数的形参变量的类型一致,那么作为下标变量的数组元素的类型也和函数形参变量的类型是一致的。因此,并不要求函数的形参也是下标变量。换句话说,对数组元素的处理是按普通变量对待的。用数组名作函数参数时,则要求形参和相对应的实参都必须是类型相同的数组,都必须有明确的数组说明。当形参和实参二者不一致时,即会发生错误。
2)在普通变量或下标变量作函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时发生的值传送是把实参变量的值赋予形参变量。在用数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。因为实际上形参数组并不存在,编译系统不为形参数组分配内存。那么,数据的传送是如何实现的呢?在我们曾介绍过,数组名就是数组的首地址。因此在数组名作函数参数时所进行的传送只是地址的传送,也就是说把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就等于有了实在的数组。实际上是形参数组和实参数组为同一数组,共同拥有一段内存空间。
上图说明了这种情形。图中设a为实参数组,类型为整型。a占有以2000为首地址的一块内存区。b为形参数组名。当发生函数调用时,进行地址传送,把实参数组a的首地址传送给形参数组名b,于是b也取得该地址2000。于是a,b两数组共同占有以2000为首地址的一段连续内存单元。从图中还可以看出a和b下标相同的元素实际上也占相同的两个内存单元(整型数组每个元素占二字节)。例如a[0]和b[0]都占用2000和2001单元,当然a[0]等于b[0]。类推则有a[i]等于b[i]。
例8.13数组名作函数参数举例说明3 用选择法对数组中十个整数进行由小到大排序。 排序的算法如下: C程序设计 第八章 函 数 例8.13数组名作函数参数举例说明3 用选择法对数组中十个整数进行由小到大排序。 排序的算法如下: 1) 先从a[0] a[9]的十个整数中找出最小的数,与元素a[0]交换。 2) 接下来在从a[1] a[9]的九个整数中找出最小的数,与元素a[1]交换。依次类推,每一次比较,减少比较一个数,共比较九次,剩下最大的数留在a[9]。
例8.13数组名作函数参数举例说明3 以5个整数的排序为例,说明选择法的比较过程: a[0] a[1] a[2] a[3] a[4] C程序设计 第八章 函 数 例8.13数组名作函数参数举例说明3 以5个整数的排序为例,说明选择法的比较过程: a[0] a[1] a[2] a[3] a[4] 3 6 1 9 4 未排序时的情况 1 6 3 9 4 将5个数中的最小数1与a[0] 互换 1 3 6 9 4 将4个数中的最小数与a[1] 互换 1 3 4 9 6 将3个数中的最小数与a[2] 互换 1 3 4 6 9 将2个数中的最小数与a[3] 互换 剩下最大的数留在a[4]。
例8.13数组名作函数参数举例说明3 运行程序 void sort(int array[ ],int n ) main( ) C程序设计 第八章 函 数 例8.13数组名作函数参数举例说明3 void sort(int array[ ],int n ) { int i,j,k,t ; for(i=0;i<n-1;i++) { k=i; for(j=i+1;j<n;j++) if(array[j] <array[k]) k=j; t=array[k]; array[k]=array[i]; array[i]=t; } main( ) { int a[10],i; printf(“enter the array\n”); for(i=0;i<n-1;i++) scanf(“%d”,&a[i]); sort(a,10); printf(“the sorted array: \n”) for(i=0;i<10;i++) printf(“%d ”,a[i]); printf(“\n” ); } 运行程序
用多维数组名作函数参数的说明 多维数组元素与一维数组元素一样,可以看作一个变量,所以在调用函数时可以作为实参,进行值的传递。 用多维数组名作为函数参数传递的是数组首元素的地址,要求形参是相同类型的同维数组。这里,形参是二维数组时,第二维的大小(长度)必须指明,而第一维的大小(长度)可以指明,也可以不指明。 如: int array[3][10] 或 int array[ ][10] 但以下表示是错误的: int array[ ][ ] int array[3][ ]
例8.14用多维数组名作函数参数举例 求出3X4的矩阵(二维数组)中的最大元素。 算法分析: C程序设计 第八章 函 数 例8.14用多维数组名作函数参数举例 求出3X4的矩阵(二维数组)中的最大元素。 算法分析: 先使变量max的初值为矩阵中第一元素的值,然后将矩阵中 各个元素的值与max相比,每次比较后都把“大者”存放在max 中,全部元素比较完后,max的值就是所有元素的最大值。
例8.14用多维数组名作函数参数举例 运行结果为 max value is 34 运行程序 C程序设计 第八章 函 数 例8.14用多维数组名作函数参数举例 max-value(int array[ ][4] ) { int i,j,k,max ; max=a[0][0]; for(i=0;i<3;i++) for(j=0;j〈4;j++) if(array[i][j] >max) max= array[i][j] ; return max; } main( ) { int a[3][4]={ {1,2,5,7},{2,4,6,8}, {15,17,34,12} }; printf(“maxvalue is %d \n ” ,max -value(a)) ; 运行结果为 max value is 34 运行程序
8.8 局部变量和全局变量 局部变量 在一个函数内部定义的变量是内部变量,只在本函数范围内有效。 全局变量 8.8 局部变量和全局变量 float f1(int a) {int b,c; ……} char f2(int x,int y) {int i,j; main() {int m,n; a,b,c有效 x,y,i,j有效 m,n有效 局部变量 在一个函数内部定义的变量是内部变量,只在本函数范围内有效。 全局变量 全局变量可以为本文件中其他函数所共 用。其有效范围为从定义变量的位置开 始到本源文件结束。 说明 返回主菜单 图示及说明
说 明 (1)局部变量只在定义函数的内部可访问、有效。 说 明 (1)局部变量只在定义函数的内部可访问、有效。 (2)不同的函数内部定义的变量可以取相同的名字,由于1)的原因,访问它们时不会发生混淆。 (3)形式参数也是局部变量。 (4)在函数内部,可以在一复合语句中定义局部变量,这些局部变量只在该复合语句内部看见、有效。
对于前四点的图形说明 main( ) { int a,b; /*a,b是局部变量,只在整个主函数*/ … /* main内部可见、有效*/ { int c; /* c是局部变量,只在复合语句内部*/ c=a+b; /* 有效、可见*/ … }
有关全局变量的图示说明 全局变量p和q的作用范围 全局变量c1、 c2的作用范围 int p=1,q=5; /*外部变量*/ float f1(int a) /*定义函数f1*/ {int b,c; ….. } char c1,c2; /*外部变量*/ char f2(int x,int y) /*定义函数f2*/ {int i,j; ……. main( ) /*主函数*/ {int m,n; …. 全局变量c1、 c2的作用范围 全局变量p和q的作用范围
几点说明 1)设全局变量的作用是增加了函数间数据联系的渠道。文件中 各个函数都能访问全局变量,可为各个函数之间增加了数据联系 的渠道,被调函数通过全局变量将结果告知主调函数。 2)全局变量不应设置过多,以降低函数之间相互影响,符合现代 模块化设计风格。 3)若全局变量与局部变量同名,则在局部变量作用的范围内, 局部变量有效(即局部变量起作用)。 例8.15 例8.16
例8.15 全局变量使用举例 统计一个班级学生一门功课的平均成绩、找出最高分及最底分。 运行程序 C程序设计 第八章 函 数 float Max=0,Min=0,q=5; */Max、Min是全局变量*/ float average(float array[],int n) { int i; float aver,sum=array[0]; Max=Min=array[0]; for(i=1;in;i++) { if(array[i]Max) Max= array[i]; else if(array[i]Min) Min= array[i]; sum+=array[i]; } aver=sum/n; return aver; } main( ) { float ave,score[10]; int i; printf(“enter the array\n”); for(i=0;i<10;i++) scanf(“%f”,&score[i]); ave=average(score,10); printf(“max=%6.2f\nmin=%6,2f\n”,Max,Min) ; printf(“average=%6.2f\n”,ave); } 运行程序
例8.15 全局变量使用举例 函数average中与外界有联系的 变量与外界的联系图。 运行结果: 99 45 78 97 100 全局变量 C程序设计 第八章 函 数 例8.15 全局变量使用举例 函数average中与外界有联系的 变量与外界的联系图。 运行结果: 99 45 78 97 100 67.5 89 92 66 43 max=100.00 min=43.00 average=77.65 Min aver array n Max Min aver score 10 Max Min 全局变量 Max main 函数 average
例8.16 外部变量与内部变量同名举例 int a=3,b=5; /*a、b为外部变量*/ C程序设计 第八章 函 数 例8.16 外部变量与内部变量同名举例 int a=3,b=5; /*a、b为外部变量*/ max( int a, int b) /*a、b为局部变量*/ {int c; c=a>b?a:b; return(c); } main() {int a=8; /a为局部变量*/ printf(“%d”,max(a,b)); 形参 a、b 作用 范围 运行结果: 8 局部变量a作用范围 全局变量b作用范围 运行程序
8.9 变量的存储类型 动态存储和静态存储方式 auto变量 用static声明局部变量 register变量 用extern声明外部变量 关于变量的声明和定义 存储类别小结 返回主菜单
8.9.1 动态存储方式与静态存储方式 定义 1)静态存储方式是指在程序运行期间分配固定的存储空间的 方式。 2)动态存储方式是在程序运行期间根据需要进行动态的分配 存储空间的方式。 从内存中供用户使用的存储空间的情况来分析存储方式 存储空间分为三部分: 1)程序区 2)静态存储区 3)动态存储区
8.9.1 动态存储方式与静态存储方式 供用户适用的 内存空间可分 为三个区域: 1)程序区存放程序的机器码; 2)数据存放在静态存储区和动态存储区。 3)静态存储区存放全局变量和静态局部变 量; 4)动态存储区存放: 自动局部变量 形式参数 函数调用时的被保护的返回地址(断 点)和需保护的现场信息 程序区 静态存储区 动态存储区
8.9.2 auto变量 1)变量和函数有两个属性:数据类型、存储类别。 变量的存储类别指的是变量在内存中存放的方式--静态存储还是动态存储。分为4种auto、static、register、extern。 2)auto变量 函数中的形参和在函数中定义的变量,都属于此类。在调用该函数时系统会给它们分配存储空间;在调用该函数结束时自动释放这些存储空间。 例如: int f(int a) /*定义f函数,a为形参*/ {auto int b,c=3; /定义b、c为自动变量*/ ……}
8.9.2 auto变量 实际上,关键字“auto”可以省略,则隐含确定为”自动存储类别”, 属于动态存储方式。 例如: auto int b,c=3; int b,c=3 两者等价
8.9.3 用static声明局部变量 变量所在的函数在调用结束后,该变量所占用的存储单元不释放 ,在下一次该函数调用时,该变量已有值,就是上一次函数调用 结束时的值。 例题8.17 考察静态局部变量的值 说明 例题8.18 打印1到15的阶乘积
说 明 1)静态局部变量属于静态存储类别,在静态存储区内分配存储 单元。在程序整个运行期间都不释放。而自动变量属于动态存储 说 明 1)静态局部变量属于静态存储类别,在静态存储区内分配存储 单元。在程序整个运行期间都不释放。而自动变量属于动态存储 类别,占动态存储空间而不占静态存储区空间,函数调用结束即 释放。 2 )对静态局部变量是在编译时赋初值的。以后每次调用函数时 不再重新赋初值而只是保留上次函数调用结束时的值。而对自动 变量赋初值,不是在编译时进行的,而是在函数调用时进行,每 调用一次函数重新给一次初值,相当于执行一次赋值语句。 3)如在定义局部变量时不赋初值的话,而对静态局部变量来说, 编译时自动赋初值0(对数值型变量)或空字符(对字符变量)。 而对自动变量来说,如果不赋初值则它的值是一个不确定的值。 这是因每次函数调用结束后存储单元已释放,下次调用时又重新 另分配存储单元,而所分配的单元中的值是不确定的。
4)虽然静态局部变量在函数调用结束后还存在,但其他函数是 不能引用它的。 5)需要使用局部静态变量的情形“: A 需要保留函数上一次调用结束时的值。 B 如果初始化后,变量只被引用而不改变其值,则这时用静态 变量比较方便。
例8.17 考察静态局部变量的值 运行结果为: 7 8 9 运行程序 f(int a) { auto b=0; C程序设计 第八章 函 数 例8.17 考察静态局部变量的值 f(int a) { auto b=0; static c=3;*/函数f中静态局部变量c(static)*/ b=b+1; c=c+1; return a+b+c; } main( ) { int a=2; for(i=0;i<3;i++) printf(“%d ”,f(a)); 运行结果为: 7 8 9 运行程序
例8.17 考察静态局部变量的值 第几次调用 调用时初值 调用结束时的值 b c a+b+c 第一次 第二次 第三次 3 4 5 1 6 7 函数调用过程中,各变量值的变化图示 第几次调用 调用时初值 调用结束时的值 b c a+b+c 第一次 第二次 第三次 3 4 5 1 6 7 8 9
例8.18 打印1到5的阶层值 int fac(int n) {static int f=1; f=f*n; return(f); } 例8.18 打印1到5的阶层值 int fac(int n) {static int f=1; f=f*n; return(f); } main() {int i; for(i=1;i<=5;i++) printf("%d!=%d\n",i,fac(i)); 运行结果: 1!=1 2!=2 3!=6 4!=24 5!=120 运行程序
8.9.4 register 变量 如果有一些变量使用频繁,则为存取变量的值要花不少时间。为 提高执行效率,C语言允许将局部变量的值放在CPU中的寄存器 中,需要用时直接从寄存器取出参加运算,不必再到内存中去存 取。这种变量叫“寄存器变量”(register)。 例8 .19 说明
关于register变量使用说明 1)只有局部自动变量和形式参数可以作为寄存器变量; 2)一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量; 3)局部静态变量不能定义为寄存器变量。 如:下面书写是错误的 register static int a,b,c;
例8.19 使用寄存器变量 int fac(int n) {register int i,f=1; for(i=1;i<=n;i++) 例8.19 使用寄存器变量 int fac(int n) {register int i,f=1; for(i=1;i<=n;i++) f=f*i return(f); } main() {int i; for(i=0;i<=5;i++) printf("%d!=%d\n",i,fac(i)); 运行程序
8.9.5 用extern声明外部变量 外部变量(即全局变量)是在函数的外部定义的,它的作用域为 从变量的定义处开始,到本程序文件的末尾。在此作用域内,全 局变量可以为程序中各个函数所使用。编译时将外部变量分配在 静态存储区。 有时需要用extern来声明外部变量,以扩展外部变量的作用域。 1)在一个文件内声明外部变量 如果在定义点之前的函数要引用外部变量,则在引用前用关键字 extern对该变量作”外部变量声明“。 2)在多个文件的程序中声明外部变量 例8.20 例8.21
例8.20用extern声明外部变量,扩展程序文件中的作用域。 C程序设计 第八章 函 数 例8.20用extern声明外部变量,扩展程序文件中的作用域。 int max(int x,int y) /*定义max函数*/ {int z; z=x>y?x:y; return(z); } main() {extern A,B; /*外部变量声明*/ printf("%d\n",max(A,B)); int A=13,B=-8; /*定义外部变量*/ 运行结果: 13 运行程序
例8.21用extern将外部变量的作用域扩展到其他文件 C程序设计 第八章 函 数 例8.21用extern将外部变量的作用域扩展到其他文件 下面程序的作用是给定 b的值,输入a和m,求aXb和am的值。 文件file1.c中的内容为 main() {int power(int);/*对调用函数作 声明*/ int b=3,c,d,m; printf(“enter the number a and its power”) ; scanf(“%d,%d”,&A,&m); c=A*b; printf(“%d*%d=%d”,A,m,d); } int A;/*定义外部变量*/ 文件file2.c中的内容为 extern A;/*声明A为已定 义的外部变量*/ power(int n); {int i,y=1; for(i=1;i<=n;i++) y*=A; return(y);} 运行程序
8.9.6 用static声明外部变量 有时在程序设计中希望某些外部变量只限于被本文件引用,而不 例如: 注意 file2.c extern int A; fun(int n) {…… A=A*n; ……} file1.c static int A; main() {……} 对外部变量加static并不意味 着这时才是静态变量(存放在静态存储区中),而不加 static的是动态存储(存放在 动态存储区)。两种形式的外部变量都是静态存储方式,只是作用范围不同。
8.9.7 关于变量声明和定义 1)函数的声明和定义的区别 对函数的声明是放在声明部分中的,而函数的定义是一个独立的 模块。 2)对变量的声明和定义 A 在声明部分出现的变量的两种情况 第一种:需要建立存储空间(如:int a;) 第二种:不需要建立存储空间(如:extern a;) B 外部变量的定义和声明 外部变量的定义只一次,位置在所有函数之外,而同一文件的 声明可以有许多次,位置在函数内。 外部变量的初始化只能在 “定义”时进行。 定义性说明 引用性说明
8.9.7 关于变量的声明和定义 C 用static来声明一个变量的作用 第一、对局部变量用static声明,则为该变量分配的空间在整个 程序执行期间始终存在。 第二、全局变量用static声明,则该变量的作用域只限于本文件 模块(即被声明的文件中)。
8.9.8 存储类别小结 局部变量 外部变量 register 局部static auto 外部static 外部 存储类别 静态 动态 存储方式 静态存储区 动态区 存储区 寄存器 自动赋初值0或空字符 不确定 未赋初值 作用域 定义变量的函数或复合语句内 本文件 其它文件 编译时赋初值,只赋一次 每次函数调用时 赋初值 程序整个运行期间 函数调用开始至结束 生存期
8.10 内部和外部函数 根据函数能否被其他源文件调用,将函数区分为内部和外部函数。 内部函数 static 类型标识符 函数名(形参表) (1)在定义函数时,如果在函数首部的最左端冠以关键字extern, 则表示此函数是外部函数,可供其他文件调用。 (2)在需要调用此函数的文件中,用extern声明所用的函数是 外部函数。 static int fun(int a,int b) extern int fun(int a,int b 例8.22 返回主菜单
例8.22 用extern声明所用的函数 有一个字符串,内存若干字符,今输入一个字符,要求程序将该字符从字符串中删去。用外部函数实现。 C程序设计 第八章 函 数 例8.22 用extern声明所用的函数 有一个字符串,内存若干字符,今输入一个字符,要求程序将该字符从字符串中删去。用外部函数实现。 程序说明: 在文件file1.c中定义主函数,主函数调用其它三个文件中 定义的三个函数分别实现读入字符串、从一给定的字符串 删去一个给定的字符、输出字符串的功能。
例8.22 用extern声明所用的函数 /* file1.c文件 定义一个主函数 */ main( ) { extern enterstring(char *); extern deletestring(char *,char); extern print(char *); /*以上3行声明在本函数中将要调用的在其他文件中定义 的3个文件*/ char c,str[80]; enter_string(str); scanf(“%c”,c); delet_dstring(str,c); print_string(str);}
例8.22 用extern声明所用的函数 /* file2.c文件 定义函数, 实现读入字符串 */ 运行情况: 输入abcdefg 输入c 实现从字符串中删去一个*/ /* 给定的字符。 缺省externd的函数默认为 外部函数 */ void deleter_string(char *s,char ch) { int i,j; for(i=j=0;s[i];i++) if(s [i]!=ch s[j++]=s[i]; s[j]=‘\0’; } /* file2.c文件 定义函数, 实现读入字符串 */ #include“stdio.h” extern void enter_string(char *s) { gets(s); /*向字符数组输入字符串*/ } 运行情况: 输入abcdefg 输入c 输出abdefg /* file4.c文件 定义函数, 实现输出字符串 */ void print_string(char *s) { printf(“%s”, s); } 运行程序
8.11 如何运行一个多文件的程序 用Turbo C集成环境 在MS C上运行编译连接 用#include 命令 返回主菜单
用 Turbo C集成环境 对运行例8.22程序举例。 (1)先后输入并编辑4个文件,并分别以文件名file1.c、file2.c、 (2)在编译状态下,建立一个“项目文件”,它不包括任何程序 语句,而只包括组成程序的所有的文件名。即 file1.c file2.c file3.c file4.c 扩展名.c可以省略。4个文件顺序任意,可以连续写在同一行上。 如这些源文件不在当前目录下,应指出路径。 (3)将以上内容存盘,文件名自定,但扩展名必须为.prj。在 Turbo C主菜单中选择Project菜单,按回车键后出现下拉菜单, 找到其中的Project name项并按回车键,在出现的对话框中输入 项目文件名。
用Turbo C集成环境 (4)按功能键F9,进行编译连接,系统先后将4个文件翻译成 目标文件,并把它们连接成一个可执行文件a.exe。 (5)按Ctrl+F9键,即可运行可执行文件a.exe。
用#include 命令 将file1.c、file2.c、file3.c和file4.c包含到file1.c中。在file1.c中 的开头加3行: #include “file1.c” #include “file2.c” #include “file3.c” 在编译时,系统自动将3个文件放到main函数的前头,作为 一个整体编译。
The end