410 likes | 504 Views
第六章 函数. 教学目标 教学内容 小结. 教学目标. 掌握函数的定义与应用。 掌握函数的传值调用与传址调用方式。 掌握主被调函数间的参数传递。 掌握命令行参数及其应用。 了解函数的递归调用。. 教学内容. 函数的定义和调用及返回值 数据在函数间的传递方式 数组名作实参 通过指针调用函数 带参数的主函数 ( 命令行参数 ) 两类特殊函数. 什么是函数?. ( 1 )函数是程序的基本组成单元,是实现特定功能的模块。 ( 2 )一个 C 语言程序是由一个 main() 函数和若干个其它函数组成的。
E N D
第六章 函数 教学目标 教学内容 小结
教学目标 • 掌握函数的定义与应用。 • 掌握函数的传值调用与传址调用方式。 • 掌握主被调函数间的参数传递。 • 掌握命令行参数及其应用。 • 了解函数的递归调用。
教学内容 • 函数的定义和调用及返回值 • 数据在函数间的传递方式 • 数组名作实参 • 通过指针调用函数 • 带参数的主函数(命令行参数) • 两类特殊函数
什么是函数? • (1)函数是程序的基本组成单元,是实现特定功能的模块。 • (2)一个C语言程序是由一个main()函数和若干个其它函数组成的。 • (3) C语言的函数包括标准库函数与用户自定义函数。
函数示例1 • float circle_area (int r) • { float s; • s=.1416*r*r; • return(s); • } • main() • { • float area; • int r; • printf("请输入圆的半径:"); • scanf("%d",&r); • printf("\n area=%f\n",circle_area(r)); • }
函数的定义与声明 • 类型标识符 函数名(类型标识符 形参1,类型标识符 形参2, …) • { • 函数体变量说明语句; • 函数体可执行语句 • } 前面示例的方式 类型标识符 函数名(形式参数表) 形式参数说明; { 函数体变量说明语句; 函数体可执行语句 } 该方式已过时
函数的定义与声明 • (1)函数与变量一样,必须先定义后使用。 • (2)如果没有声明函数类型,则编译程序自动将整型作为函数的缺省类型。 • (3)函数声明中,末尾的分号不可缺少。
函数示例2 • #include <stdio.h> • float rectangle_area(float length,float width); • int main() • { • float length,width; • printf("请输入矩形的长与宽:"); • scanf("%f,%f",&length,&width); • printf("rectangle area=%10.3f\n",rectangle_area(length,width)); • return 0; • } • float rectangle_area(float length,float width) • { • float s; • s=length*width; • return s; • }
函数的调用方式 • 假定我们定义了一个函数int max(int a,int b);,那么函数的调用方式可以采用 • (1)作为表达式的一部分,例如: • a=max(x,y)*z+s/t; • (2)作为一条独立的语句,例如: • max(x,y); • (3)作为另一个函数的实参,例如: • printf(“Maximum =%d\n”,max(x,y));
函数的调用-1 • (1)函数调用遵循“先定义,后调用”的原则。即: 一般被调用函数应放在调用函数之前定义。 • 例如:/*被调函数*/ • int fun1(int a2,int b2,int c2,….. ) {… … } • main( ) • {… • fun1(a1,b1,c2,….. ); /*调用函数*/ • }
函数的调用与函数声明-1 • 若被调用函数和调用函数不在同一编译单位(即不在同一文件)中,调用前应先对函数进行声明。 • 方式1:在调用函数前声明。例如: • int fun1(int a2,int b2,int c2,….. ) ;/*函数声明*/ • main( ) • {… • fun1(a1,b1,c2,….. ); /*函数调用*/ • } • int fun1(int a2,int b2,int c2,….. ) /*函数定义*/ • {… …}
函数的调用与函数声明-2 • 方式2:在调用函数内声明。例如: • main( ) • { … … • int fun1(int a2,int b2,int c2,….. ) ;/*函数声明*/ • fun1(a1,b1,c2,….. ); /*函数调用*/ • } • int fun1(int a2,int b2,int c2,….. ) /*函数定义*/ • { … … • }
函数的调用与函数声明-3 • 若被调用函数的函数类型是整型或字符型,则均可省略上述函数声明。 • 方式3:省略函数声明。例如: • main( ) • { … … • /*可省略函数声明*/ • fun1(a1,b1,c2,….. ); /*函数调用*/ • } • int max(int a,int b) • { • if(a>b) • return a; • else • return b; • }
函数的返回语句 • (1)主调函数通过实参向形参传递数据。 • (2)被调函数计算结果通过return语句传递回主调函数。函数return语句的一般形式为: • return (变量或表达式); • 或: return 变量或表达式; • 例如: • return x>y?x:y ; • return (x>y?x:y); • 一个函数可以有多个return语句,最终是哪个return语句被执行将由控制流程决定。
函数的返回语句 • (3)若被调函数体内没有return语句,就一 • 直执行到函数体的末尾,然后返回调用函数。 • 这时有一个不确定的值被带回(无用)。 • (4)若确实不要求带回函数值,则应将被调函 • 数定义为void类型。函数的定义形式如下: • void 函数名(参数表) • (5)return语句中表达式的类型应与函数值 • 的类型一致,若不一致时,则以函数值的类型 • 为准。 • (6) 用return语句只能返回一个值。
库函数调用 • (1)当源程序包含标准头文件,将可以使用标准库中提供的库函数了。 • (2)标准库包括: • 标准输入输出库(stdio.h) • 数学库(math.h) • 字符串处理库(string.h) • 动态内存分配库(malloc.h)
库函数调用-示例 • #include <stdio.h> • #include <math.h> • double cal(double r) • { • double d=cos(r*3.1415926535/180.0); • d = 2.0*d*d-1; • return d; • } • void main() • { • printf("cal(30.0) = %10.3f\n",cal(30.0)); • }
函数的参数 • double cal(double a,int m,int n) • { • … … • } • void test(void) • { • printf("Test void\n"); • } • void main() • { • printf("cal(30.0) = %10.3f\n",cal(30.0,2,3)); • test(); • }
函数的参数 • (1)函数的参数声明是以逗号分割的参数列表 • 例如:double cal(double a,int m,int n) • (2)如果函数没有参数,则函数可定义为: • int show(); 或int show(void); • (3)函数的return语句只能返回一个结果,而函数的参数,却容许主调函数与被调函数之间的数据,采用多种方式进行交互。
数据在函数间传递数的方式 • 数据在函数间传递数的方式有三种: • (1)值传递---通过数据传递(单向) • (2)地址传递---通过地址传递(双向) • (3)全局传递---通过全局变量传递
传值调用 • (1)传值调用时,系统先计算主调函数的实参值,然后再将该值复制给对应的被调函数形参。 • (2)如果主调函数中传入的实参是常量、普通变量或数组元素,那么,主调函数实参与被调函数的形参之间的参数传递是“值”传递,函数的调用类型为传值调用。
传址调用 • (1)传址调用时,系统先计算主调函数实参变量的地址,复制到被调函数形参指针变量的内存单元中,即地址复制。 • (2)如果主调函数中传入的实参是:变量的地址或指针变量、数组名、字符串名等,那么,主调函数实参与被调函数形参之间的参数传递是“地址”传递,函数的调用类型为传址调用。
传址调用 • (1)传址调用中,实参与形参的类型必须一致。 • (2)由于此时实参和形参使用同一内存单元,利用这种地址值的传送,能达到改变实参变量内容的目的。 • (3)传址传递是一种双向传递方式,即通过形参将被调用函数中形参值的变化传递回原调用处,以改变实参的值,从而实现返回多值的目的。
传址调用-示例1 • 例:交换两个整型参数。 • void swap(int *p,int *q); • void main( ) • { int a=10,b=100; • swap(&a,&b); • printf(“%d%d\n”,a,b); • } void swap(int *p,int *q) { int t; t=*p; *p=*q; *q=t; } 思考:1、返回了多少个值? 2、如果实参用指针,如何编写程序?
传址调用-示例2 • main( ) • { • int b1[5]={1,2,3,4,5}; • int b2[5]={3,1,2,4,6}; • int b3[5]={3,5,2,4,1}; • testArray(b1,b2,b3,2); • } void testArray(int a[5],int b[ ], int *c, int n) { int k; for(k=0;k<5;k++){ *(a+k)*=n; *(b+k)*=n; *(c+k)*=n; } } 3种类型的形参,C编译程序都将形参处理成指针。
传址调用-示例3 • #include <stdio.h> • int f(char s[ ],char t[ ]); • void main() • {//比较两个字符串(即字符数组)是否相等 • char a[20],b[20]; • int i; • scanf("%s%s",a,b); • i=f(a,b); • printf("%d\n",i); • } • int f(char s[ ],char t[ ]) • { • int i=0; • while(s[i]==t[i] && s[i]!='\0') i++; • return ((s[i]=='\0' && t[i]=='\0') ? 1 :0); • }
二维数组名作实参 • 例如:int x[3][4];fun (x); 被调函数的形参形式可为: • (1)void fun(int x[3][4]); //(数组大小可以不一致) • (2)void fun(int x[ ][4]); //(只有行下标可以省略) • (3) void fun(int (*num) [4]);
二维数组名作实参-示例1 • void main() • { int x[3][3]={1,2,3,4,5,6,7,8,9},i,*p; • fun1(x); • fun2(x); • fun3(x); • p=x; • for(i=0;i<9;i++) • printf(" %d ",*(p+i)); • } • void fun1( int x[3][3] ) • { x[1][0]=14; } • void fun2( int x[ ][3] ) • { x[1][1]=15; } • void fun3( int (*x)[3] ) • { *(*(x+1)+2)=16; } • #include <stdio.h> • void fun1(int x[3][3]); • void fun2(int x[][3]); • void fun3(int (*x)[3]); 三个函数,分别改变了数组中的哪几个函数?
二维数组名作实参-示例2 例:现有30个学生的四科考试成绩,求每个学生的平均成绩。 • #include <stdio.h> • #define N 30 • void average(int (*ss)[4],float *p, int n) • main() • {int i,s[N][4]; • float ave[N]; • for(i=0;i<N;i++) • for(j=0;j<4;j++) • scanf(“%d”,&s[i][j]); • average(s,ave,N); • printf("平均分 = \n"); • for(i=0;i<N;i++) • printf("ave[%d]=%5.2f\n",i,ave[i]); • } • void average(int (*ss)[4],float *p, • int n) • { • int i,j; • float sum; • for(i=0;i<n;i++) • { • sum=0.0; • for(j=0;j<4;j++) • sum=sum+*(*(ss+i)+j); • p[i]=sum/4; • } • }
字符串作为函数的参数 • 字符串作为实参,相应的形参应是字符型一维数组或字符指针变量。 • 由于字符串常量被隐含处理成无名的字符型一维数组,传送的将是该字符串的首地址; • 字符串变量实际就是字符型一维数组,所以,字符串作实参的情况与一维数组名作实参时相同
函数指针 • (1)C程序是由函数组成的。每个函数的原代码在内 • 存中占一连续的存储单元。其首地址称为函数的入口 • 地址。 • (2)指针变量既然可以指向整型变量、字符串、数 • 组、也可以指向函数,即可以存放函数的入口地址, • 这种指针变量称为函数指针。 • (3)定义函数指针的一般形式是: • 类型定义符 (*指针变量)( ); • 如:int (*fp)( ); /* fp为函数指针 */ • 这样函数的调用就有两种方式: • (1) 函数名调用 (2) 函数指针调用
函数指针-示例 • #include <stdio.h> • int max(int x,int y); //声明函数 • int (*fp)(int,int); //声明函数指针(习惯上不采用(int x,int y)) • void main() • { • fp=&max; //将max函数的地址赋给函数指针变量fp • printf(“Test max(2,3)=%d\n”,max(2,3)); //使用函数名调用函数 • printf(“Test (*fp)(2,3)=%d\n”,(*fp)(2,3)); //使用函数指针调用函数 • } • int max(int x,int y) • { • int z; • z=x>y?x:y; • return (z); • }
函数指针 • 函数指针和其他指针的基本性质相同,其不同点是: • (1)函数调用使用函数指针时,是将程序的控制流程转移到函数指针所指向的地址执行该函数体。 • (2)函数指针指向的是函数代码存储区,而不是通常的数据存储区。 • (3)函数指针变量的圆括号不能省略,否则编译系统将把函数指针编译成指针函数。
指针函数-示例 • #include <stdio.h> • int *max1(int *x,int *y); //声明指针函数 • void main() • { • int x=2,y=3; • printf("Test **max1(&x,&y)=%d\n",*max1(&x,&y));//使用指针函数 • } • int *max1(int *x,int *y) • { • int *z; • z=*x>*y?x:y; • return(z); • }
可执行文件名 参数 命令行参数-1 • 在DOS系统下,要把file1复制为file2,必须执 • 行以下命令: • copy file1 file2 • 这种执行命令的方式称为命令行方式,以上的输 • 入称为命令行。显然,命令行中必须要有表示命令的 • 可执行文件名以及命令所需的参数(如果需要参数), • 并以回车符作为输入命令的结束。其中: • copy file1 file2 • 然而前面介绍的由C程序形成的可执行文件一 • 般无命令行参数,如何通过命令行给C程序传递参数 • 呢?
命令行参数-2 • C编译程序为main( )函数设置了两个特殊 的内部形参:argc和argv。其形式如下: • void main(int argc char *argv[]) • 程序运行时系统首先检查命令行各参数,并对其 • 进行初始化。 • (1)argc的值是命令行中可执行文件名和所有实参个数之和。 • (2)argv[ ]指针数组的各元素分别指向命令行中可执行文件 • 名和各个实参的字符串。
命令行参数-3 • 对应于以下DOS命令: • copy file1 file2 • argc=3:总共有3个参数(copy、file1、file2) • *argv[]:为一指针数组,其中每一个元素均为指针。各个指针元素分别指向: • argv[0]-----> copy • argv[1]-----> file1 • argv[2]-----> file2 • 在C语言程序中,可以引用这些变量。
命令行参数-示例 • 已知下列程序: • # include “stdio.h” • main(int argc,char *argv[ ]) • { • int i; • for(i=0;i<argc;i++) printf(“%s\n”,argv[i]); • } • 该程序的可执行文件名为program.exe,运行时键入的命 • 令行为:program.exe apple orange banana 则执行结果为: • A: PROGRAM B:PROGRAM.EXE • apple apple • orange orange • banana banana • C: apple D:p • orange a • banana o • b
递归调用 • 函数的嵌套调用 • 即在调用一个函数的过程中可以再调用另一个函数。 • 函数的递归调用 • 递归函数是指在函数体中有调用它自己的语句。这种方法多用在求阶乘的运算中, 由于存在自调用过程,程序控制将反复进入它的函数体,为了防止产生自调用过程无休止地继续下去,在函数内必须设置某种结束自调用过程的条件(如:If语句)。
递归调用-示例 • int factorial1(int n) //计算n的阶乘 • { • int m=1; • while(n>1) • m*=n--; • return m; • } • int factorial2(int n) { //递归方式计算n的阶乘 • if(n==0) • return 1; • else • return n*factorial2(n-1); • } • void main(int argc,char *argv[]) • { printf("计算n的阶乘:factorial1(100) = %d\n",factorial1(10)); • printf("递归方式计算n的阶乘: factorial2(100) = %d\n",factorial2(10)); • }
函数部分小结 • 1、函数定义及参数说明 • 2、函数调用及数据传递 • 3、命令行程序