(9)--C语言课件第09章函数.ppt
第第9章章函数函数人们在设计一个复杂的程序时,通常人们在设计一个复杂的程序时,通常将它分解为若干个相对独立的将它分解为若干个相对独立的程序模块程序模块(模块化程序设计,(模块化程序设计,有利于程序的调试和有利于程序的调试和维护维护)。)。在在C语言中通过语言中通过将一个程序划分为若干将一个程序划分为若干个函数个函数,来实现程序的模块化。,来实现程序的模块化。C语言函数的分类:语言函数的分类:1.按函数编写者划分按函数编写者划分库函数库函数:是由是由C语言编译系统预先定义好的函数,语言编译系统预先定义好的函数,用户可直接调用。用户可直接调用。如如printf函数、函数、sqrt函数等。函数等。用户函数用户函数:是由用户自己定义的函数。是由用户自己定义的函数。如前面程序中的如前面程序中的main函数。函数。2.按函数形式划分按函数形式划分无参函数无参函数:不带参数(自变量)的函数。不带参数(自变量)的函数。如如getchar函数。函数。有参函数有参函数:带有参数(自变量)的函数。带有参数(自变量)的函数。如如printf函数、函数、sqrt函数等。函数等。库函数库函数是由是由C语言编译系统预先定义好的函语言编译系统预先定义好的函数,用户可直接调用。数,用户可直接调用。用户在程序中调用库函数时,必须用用户在程序中调用库函数时,必须用include命令命令包含相应的头文件包含相应的头文件。库函数与库函数与头文件的对应关系可查表。头文件的对应关系可查表。【例例】从键盘输入一个以度为单位的角度值,求从键盘输入一个以度为单位的角度值,求其正弦值并输出。其正弦值并输出。#include#include#definePI3.14159main()floatx,y;scanf(%f,&x);y=sin(x);/调用调用库函数只须给出函数名和参数即库函数只须给出函数名和参数即可可printf(y=%fn,y);/此程序有错,你能发现吗?此程序有错,你能发现吗?【例例】从键盘输入一个以度为单位的角度值,求从键盘输入一个以度为单位的角度值,求其正弦值并输出。其正弦值并输出。#include#include#definePI3.14159main()floatx,y;scanf(%f,&x);x=x*PI/180;/*化为弧度化为弧度*/y=sin(x);/*调用库函数只须给出函数名和参数即可调用库函数只须给出函数名和参数即可*/printf(y=%fn,y);用户函数的定义和调用用户函数的定义和调用可以将程序中可以将程序中相对独立的程序段相对独立的程序段定义为一个定义为一个函数。函数。主调函数:调用另一个函数的函数。主调函数:调用另一个函数的函数。被调函数:被另一个函数调用的函数。被调函数:被另一个函数调用的函数。无参函数的定义无参函数的定义格式:格式:类型名类型名函数名函数名()函数体函数体【例例】编程序打印如下图形。编程序打印如下图形。Thefirstone:Thesecondone:首先,编写一个只有首先,编写一个只有main函数的程序以实现上述功函数的程序以实现上述功能。能。#includeint main(void)int main(void)inti,j;printf(Thefirstone:n);for(i=1;i=4;i+)for(j=1;j=i;j+)printf(*);printf(n);printf(Thesecondone:n);for(i=1;i=4;i+)for(j=1;j=i;j+)printf(*);printf(n);return 0;return 0;在该程序中,打印一个三角形的程序段重复在该程序中,打印一个三角形的程序段重复了两次,但是并不能简单地将它们合并为了两次,但是并不能简单地将它们合并为一个循环。一个循环。因此可以将打印三角形的程序段单独拿出来,因此可以将打印三角形的程序段单独拿出来,定义为一个被调函数,然后在主函数中调定义为一个被调函数,然后在主函数中调用它。用它。为了得到打印三角形的被调函数,只需以相应的程为了得到打印三角形的被调函数,只需以相应的程序段作为序段作为函数体函数体,并添加,并添加函数头函数头即可。即可。voidstar()inti,j;for(i=1;i=4;i+)for(j=1;j=i;j+)printf(*);printf(n);return;无参函数无参函数的调用的调用用户定义了函数之后,就可以用户定义了函数之后,就可以像调用库函数像调用库函数那样那样调用它。调用它。无参函数的调用格式无参函数的调用格式:函数名函数名()例例2续续main()printf(Thefirstone:n);star();/*调用前面定义的函数调用前面定义的函数star*/printf(Thesecondone:n);star();/*调用前面定义的函数调用前面定义的函数star*/#include#include void star(void star()int i,j;int i,j;for(i=1;i=4;i+)for(i=1;i=4;i+)for(j=1;j=i;j+)for(j=1;j=i;j+)printf(*);printf(*);printf(n);printf(n);return;return;mainmain()()printf(The first printf(The first one:n);one:n);star();star();printf(The second printf(The second one:n);one:n);star();star();无参函数的无参函数的调用调用及及返回返回过程过程有参函数有参函数的定义和调用的定义和调用首先来看一个实例。首先来看一个实例。【例例9.3】已知已知m、n是正整数,编写程序是正整数,编写程序求求m中取中取n的组合数。的组合数。问题分析:问题分析:首先,我们编写一个只有首先,我们编写一个只有main函数的程序函数的程序以实现上述功能。以实现上述功能。#includemain()intm,n,i,k;longp,c,c1,c2,c3;printf(请输入请输入m与与n的值:的值:);scanf(%d%d,&m,&n);k=m;p=1;for(i=1;i=k;i+)p=p*i;c1=p;k=n;p=1;for(i=1;i=k;i+)p=p*i;c2=p;k=m-n;p=1;for(i=1;i=k;i+)p=p*i;c3=p;c=c1/(c2*c3);printf(m中取中取n的组合数的组合数=%ldn,c);本程序中可以将求阶乘的程序段单独拿出来,定义本程序中可以将求阶乘的程序段单独拿出来,定义为一个为一个被调函数被调函数,然后在主函数中调用它。,然后在主函数中调用它。为了得到求阶乘的被调函数,只需为了得到求阶乘的被调函数,只需以相应的程序段以相应的程序段作为函数体作为函数体,并添加,并添加函数头函数头即可。即可。longfac()longp;inti,k;p=1;for(i=1;i=k;i+)p=p*i;return;分析该被调函数,可以发现还存在两个问题分析该被调函数,可以发现还存在两个问题没有解决。没有解决。一是变量一是变量m、n的值显然应该在主调函数中输的值显然应该在主调函数中输入,如何将入,如何将m、n及及m-n的值的值传递给变量传递给变量k呢呢?二是如何将求得的阶乘(即变量二是如何将求得的阶乘(即变量p的值)的值)传传递给递给主调函数中的变量主调函数中的变量c1、c2与与c3呢?呢?为了实现主调函数与被调函数之间的数据传为了实现主调函数与被调函数之间的数据传递,递,C语言规定:语言规定:将被调函数中将被调函数中用于接受数据的变量用于接受数据的变量的定义,的定义,移到函数首部的括号中,称为移到函数首部的括号中,称为被调函数的被调函数的参数参数;将被调函数中将被调函数中用于向主调函数传递数据的变用于向主调函数传递数据的变量量(或表达式或表达式)置于置于return之后,称为之后,称为被被调函数的返回值调函数的返回值。修正之后的被调函数如下:修正之后的被调函数如下:longfac(intk)longp;inti;p=1;for(i=1;i=k;i+)p=p*i;returnp;一旦定义好了求阶乘的被调函数,就可以像调用库一旦定义好了求阶乘的被调函数,就可以像调用库函数那样来调用它。函数那样来调用它。相应的主函数如下:相应的主函数如下:main()intm,n;longc,c1,c2,c3;printf(请输入请输入m与与n的值:的值:);scanf(%d%d,&m,&n);c1=fac(m);c2=fac(n);c3=fac(m-n);c=c1/(c2*c3);printf(m中取中取n的组合数的组合数=%ldn,c);main()intm,n;longc;printf(请输入请输入m与与n的值的值n);scanf(%d%d,&m,&n);c=fac(m)/(fac(n)*fac(m-n);printf(组合数为组合数为%ldn,c);#include long fac(int k)long p;int i;p=1;for(i=1;i=k;i+)p=p*i;return p;完整源程序完整源程序有参函数的定义与调用格式有参函数的定义与调用格式1.有参函数有参函数的定义格式的定义格式类型名类型名函数名函数名(形式参数形式参数表表)函数体函数体2.有参函数的调用格式有参函数的调用格式函数名函数名(实际参数实际参数表表)【练习练习】定义一个求定义一个求n的阶乘的函数,并在的阶乘的函数,并在主函数中调用它求主函数中调用它求1!+3!+.+19!。函数的参数函数的参数函数的参数分为两种。函数的参数分为两种。1.形式参数:形式参数:定义定义函数时所使用的参数。函数时所使用的参数。2.实际参数:实际参数:调用调用函数时所使用的参数。函数时所使用的参数。3.形参格式:形参格式:类型名类型名形参形参1,类型名类型名形参形参2,如如intmax(intx,inty)4.实参格式:实参格式:实参实参1,实参,实参2,如如c=max(a,b);5.参数的传递过程参数的传递过程函数函数调用调用时,将时,将实参实参的值的值赋给赋给对应的对应的形参形参;但;但函数函数返回返回时,形参的值不能传回给实参。时,形参的值不能传回给实参。这种传递方式称为这种传递方式称为值传递值传递,即单向传递。,即单向传递。#includeintmax(intx,inty)intz;if(xy)z=x;elsez=y;return(z);main()inta,b,c;scanf(%d%d,&a,&b);c=max(a,b);printf(c=%dn,c);参数的传递过程参数的传递过程6.几点说明几点说明因为参数传递时,要将因为参数传递时,要将实参实参的值的值赋给赋给对应的对应的形形参参,故要求:,故要求:形参形参只能是只能是变量变量,而实参可以是常量、变量,而实参可以是常量、变量或表达式。或表达式。实参实参与对应的与对应的形参形参应该应该类型相同类型相同或或赋值兼容赋值兼容。如整型、实型、字符型之间。如整型、实型、字符型之间。函数的返回值函数的返回值被调函数用被调函数用return语句语句传递给主调函数的传递给主调函数的值,称为被调函数的值,称为被调函数的返回值返回值(函数值)。(函数值)。return语句语句格式:格式:return表达式表达式;或或return(表表达式达式);return;(可省略不写可省略不写)例如:例如:return(xy?x:y);功能:返回到主调函数,并以表达式的值功能:返回到主调函数,并以表达式的值作为被调函数的返回值。作为被调函数的返回值。#includeintmax(intx,inty)intz;if(xy)z=x;elsez=y;return(z);main()int a,b,c;scanf(%d%d,&a,&b);c=max(a,b);printf(c=%dn,c);有参函数的调用与返回有参函数的调用与返回3.几点说明几点说明一次一次函数调用函数调用至多至多有有一个一个返回值。返回值。例如:例如:intf(intx)if(x=0)return1;elsereturn-1;例如:例如:intf()return3.96;其返回值为其返回值为3。函数的类型函数的类型决定决定返回值返回值的类型。的类型。若一个被调函数若一个被调函数无须返回值无须返回值,则可以将其,则可以将其函数类型定义为函数类型定义为void(空值类型),以明确(空值类型),以明确表示该函数无返回值。表示该函数无返回值。这类函数或者使用这类函数或者使用return;返回,或者直接返回,或者直接省省略略return语句。语句。例如,例例如,例9.1中的被调函数中的被调函数star就应该定义为就应该定义为void类型。类型。若在定义函数时,未明确指明该函数的类型若在定义函数时,未明确指明该函数的类型,那么,那么C89标准标准会将该函数的类型视为会将该函数的类型视为int型型。不过,最好还是不过,最好还是旗帜鲜明旗帜鲜明地指明函数的类型,地指明函数的类型,以使得程序更加规范。以使得程序更加规范。例如,前面所有程序中的例如,前面所有程序中的main函数,其函数类函数,其函数类型均视为型均视为int型。型。函数的调用方式函数的调用方式函数有函数有两种两种调用方式。调用方式。1.函数调用函数调用作表达式作表达式是指将函数调用作为一个表达式的一部分。是指将函数调用作为一个表达式的一部分。例如:例如:c1=fac(m);printf(%fn,fac(m);很显然,此时的很显然,此时的函数调用函数调用要参与表达式中的要参与表达式中的运算运算,故要求被调函数故要求被调函数必须有返回值必须有返回值。2.函数调用作语句函数调用作语句是指将函数调用作为一条是指将函数调用作为一条单独的语句单独的语句。例如:例如:star();printf(Helloworld!n);由于此时的函数调用不需要参与表达式运算,由于此时的函数调用不需要参与表达式运算,故故不要求不要求被调函数被调函数有返回值有返回值。函数原型的声明函数原型的声明在在C程序中,程序中,被调函数被调函数的的定义点定义点可以在主调可以在主调函数函数之后之后,也可以在主调函数,也可以在主调函数之前之前。若被调函数的定义位于主调函数之后,则若被调函数的定义位于主调函数之后,则系统编译时系统编译时不便于不便于进行进行语法检查语法检查。故故C语言规定:语言规定:凡是凡是在主调函数之后在主调函数之后定义的被调函数定义的被调函数,必须声,必须声明明函数原型函数原型。2.函数原型格式函数原型格式类型类型函数名函数名(类型类型形参形参1,类型类型形参形参2,);例如:例如:intmax(intx,inty);即即复制函数头复制函数头并并添加分号添加分号构成语句。构成语句。类型类型函数名函数名(形参形参1类型类型,形参形参2类型,类型,);例如:例如:intmax(int,int);即省略形参变量名。即省略形参变量名。3.函数原型的声明通常放在函数原型的声明通常放在主调函数之前主调函数之前,也可以,也可以放在放在主调函数内部主调函数内部。4.库函数库函数的的原型原型在标准在标准头文件头文件中已作声明。中已作声明。(因此在程序中调用库函数时,要用因此在程序中调用库函数时,要用include命令包含命令包含相应的头文件。相应的头文件。)【例例】已知已知m为一正整数,编程序判断为一正整数,编程序判断m是否为是否为素数(质数)。素数(质数)。素数概念:只能素数概念:只能被被1和自身整除和自身整除的大于的大于1的自然的自然数。数。算法设计:算法设计:拿拿m被被2到到m-1之间之间的整数的整数i来除。来除。若若m不能被不能被2整除;(如整除;(如m=7)m也不能被也不能被3整除;整除;.m也不能被也不能被m-1整除;整除;则则m是素数。(要是素数。(要同时满足同时满足m-2个条件个条件。)。)若若m能被能被2到到m-1之间的之间的某一某一整数整数i整除,则整除,则m不是素数。不是素数。(如(如m=9)(只需(只需满足一个条件满足一个条件。)。)流程图流程图流程图流程图完整流程图完整流程图#includeintmain(void)intm,i;printf(printf(请输入一个大于请输入一个大于1 1的整数:的整数:n);n);scanf(%d,&m);i=2;while(im)if(m%i=0)break;elsei+;if(i=m)printf(%d是素数是素数n,m);elseprintf(%d不是素数不是素数n,m);return 0;return 0;#include/用用for循环循环实现实现main()intm,i;scanf(%d,&m);for(i=2;i=m)printf(%d是素数是素数n,m);elseprintf(%d不是素数不是素数n,m);算法改进:算法改进:试除时,除数只取试除时,除数只取2到到sqrt(m)之间的整数之间的整数即即可(数论定理)。可(数论定理)。#include#includemain()intm,i,k;scanf(%d,&m);k=(int)sqrt(m);/*k为不大于为不大于sqrt(m)的最大整数的最大整数*/for(i=2;ik)printf(%d是素数是素数n,m);elseprintf(%d不是素数不是素数n,m);【例例】编写一个判断素数的函数,然后调用该编写一个判断素数的函数,然后调用该函数求出函数求出1000以内的所有素数。以内的所有素数。问题分析:问题分析:可首先将上述判断素数的程序改写为被调函数,可首先将上述判断素数的程序改写为被调函数,然后在主函数中直接调用该被调函数。然后在主函数中直接调用该被调函数。#includeintisprime(intm)inti;for(i=2;i=m)return(1);elsereturn(0);改进版被调函数:改进版被调函数:#includeintisprime(intm)inti;for(i=2;i=m-1;i+)if(m%i=0)return(0);/*若能整除,则若能整除,则m不是素数,返回不是素数,返回0*/return(1);/*若执行至此,则肯定不能整除,若执行至此,则肯定不能整除,即即m是素数,返回是素数,返回1*/相应的主函数:相应的主函数:main()inti;for(i=2;i=1000;i+)if(isprime(i)!=0)/*等价于等价于if(isprime(i)*/printf(%d,i);printf(n);变量的作用域变量的作用域程序中定义的变量,只有在程序中定义的变量,只有在一定的范围之一定的范围之内内才是才是有效的有效的,这个范围称为该变量的,这个范围称为该变量的作用域作用域。局部变量局部变量1.凡是在凡是在函数内部函数内部定义的变量,都是局部定义的变量,都是局部变量。变量。形参形参也是局部变量。也是局部变量。2.局部变量的作用域是定义它的局部变量的作用域是定义它的函数体函数体或或复合语句复合语句。分析运行程序:分析运行程序:#includevoidfun()s=s*2;printf(fun函数中函数中s=%dn,s);return;main()ints;s=10;fun();printf(main函数中函数中s=%dn,s);分析运行程序:分析运行程序:#includevoidfun()ints;s=20;printf(fun函数中函数中s=%dn,s);return;main()ints;s=10;fun();printf(main函数中函数中s=%dn,s);可见,不同函数中的局部变量可以同名,且相互独立。可见,不同函数中的局部变量可以同名,且相互独立。有问题的程序:有问题的程序:#includemain()inta,b;scanf(%d%d,&a,&b);intt;/*C89只能在只能在函数体函数体或或复合语句的开头复合语句的开头定义变量定义变量*/t=a;a=b;b=t;printf(t=%dn,t);printf(a=%d,b=%dn,a,b);修正之后的程序:修正之后的程序:#includemain()inta,b;scanf(%d%d,&a,&b);intt;/*C89只能在只能在函数体函数体或或复合语句的开头复合语句的开头定义变量定义变量*/t=a;a=b;b=t;printf(t=%dn,t);printf(a=%d,b=%dn,a,b);全局变量全局变量1.全局变量是指在全局变量是指在函数外部函数外部定义的变量。定义的变量。2.全局变量的作用域,是全局变量的作用域,是从其定义点开始从其定义点开始直至本程序文件的末尾直至本程序文件的末尾。分析运行程序:分析运行程序:#includeints;voidfun()s=s*2;printf(fun函数中函数中s=%dn,s);return;main()s=10;fun();printf(main函数中函数中s=%dn,s);【例例】编写一个求圆的面积的被调函数以及编写一个求圆的面积的被调函数以及调用它求圆环的面积的主函数。限定调用它求圆环的面积的主函数。限定不能不能使用函数返回值使用函数返回值在函数之间传递数据。在函数之间传递数据。问题分析:问题分析:由于限定不能使用函数返回值传递数据,故由于限定不能使用函数返回值传递数据,故可可借助于全局变量借助于全局变量在函数之间传递数据。在函数之间传递数据。源程序:源程序:#includefloats;/*全局变全局变量量*/main()floatr1,r2,s1,s2,s0;scanf(%f%f,&r1,&r2);area(r1);/函数作函数作语句语句s1=s;area(r2);s2=s;s0=s1-s2;printf(s0=%fn,s0);void area(float r)s=3.14159*r*r;return;float area(float r)float s;s=3.14159*r*r;return s;【例例】同名变量的屏蔽。同名变量的屏蔽。#includeinta=100;main()inta;a=200;printf(内层的内层的a=%dn,a);printf(外层的外层的a=%dn,a);可见,局部变量能够可见,局部变量能够屏蔽屏蔽同名的同名的全局变量全局变量。变量的存储方式变量的存储方式在在C程序中定义的变量,按照其程序中定义的变量,按照其存储方式存储方式不不同,具有不同的同,具有不同的生存期生存期。静态存储方式静态存储方式的变量,在整个程序运行期间,的变量,在整个程序运行期间,始终始终占据内存空间。占据内存空间。动态存储方式动态存储方式的变量,在程序运行期间,的变量,在程序运行期间,动动态地态地分配与回收内存空间。分配与回收内存空间。变量的存储类别变量的存储类别变量的存储方式是通过定义其变量的存储方式是通过定义其存储类别存储类别来指来指定的。定的。局部变量局部变量有三种存储类别。有三种存储类别。自动变量自动变量定义形式:定义形式:auto类型名类型名变量名;变量名;例如:例如:autointa,b;说明:说明:未定义存储类别的局部变量,均视为未定义存储类别的局部变量,均视为auto变量。变量。自动变量的分配与释放自动变量的分配与释放自动变量,在所属函数自动变量,在所属函数被调用时被调用时,由系统自,由系统自动动分配分配内存空间;内存空间;而在该函数而在该函数返回时返回时,由系统自动,由系统自动释放释放内存空内存空间。间。auto变量的内存分配与释放变量的内存分配与释放#includeintmax(intx,inty)/执行到此处为执行到此处为x、y分配内存分配内存intz;/执行到执行到此处为此处为z分配内存分配内存if(xy)z=x;elsez=y;return(z);/执行到此处回收执行到此处回收x、y、z的内存的内存main()inta,b,c;scanf(%d%d,&a,&b);c=max(a,b);printf(c=%dn,c);分析运行以下程序:分析运行以下程序:#includemain()inti,s;i=1;while(i=100)s=s+i;i+;printf(s=%dn,s);可见,未经可见,未经赋值的自动变量,其值是赋值的自动变量,其值是不确定不确定的。的。寄存器变量寄存器变量定义形式定义形式:register类型类型变量名;变量名;例如例如:registerinta,b;寄存器变量存储于寄存器变量存储于CPU的的寄存器寄存器中,以提高访问速中,以提高访问速度。度。注意:注意:不能对寄存器变量进行不能对寄存器变量进行取地址运算取地址运算。例如:例如:registerinta;scanf(%d,&a);/错误错误由于新型的由于新型的C语言编译系统能够语言编译系统能够自动优化自动优化寄存寄存器的分配,故定义寄存器变量器的分配,故定义寄存器变量已无必要已无必要。静态变量静态变量定义形式:定义形式:static类型名类型名变量名;变量名;例如:例如:staticinta,b;【例例】运行分析程序。运行分析程序。#includeintfact(intn)intf=10;/自动变量自动变量f=f*n;return(f);main()ints;s=fact(2);printf(第一次调用第一次调用s=%dn,s);s=fact(3);printf(第二次调用第二次调用s=%dn,s);20f10f10f30f【例例】运行分析程序。运行分析程序。#includeintfact(intn)staticintf=10;/静态局部静态局部变量变量f=f*n;return(f);main()ints;s=fact(2);printf(第一次调用第一次调用s=%dn,s);s=fact(3);printf(第二次调用第二次调用s=%dn,s);10f20f20f60f静态局部变量静态局部变量占用的内存,在所属函数返回时占用的内存,在所属函数返回时并并不释放不释放,而要一直保持到整个程序运行结,而要一直保持到整个程序运行结束。束。静态局部变量静态局部变量只在程序被编译时进行一次初始只在程序被编译时进行一次初始化化,而在程序运行过程中不再进行初始化。,而在程序运行过程中不再进行初始化。【例例】分析运行程序。分析运行程序。#includeintfact(intn)intf=1;/自动变量自动变量f=f*n;return(f);main()inti;for(i=1;i=4;i+)printf(%dn,fact(i);例例静态局部变量应用。静态局部变量应用。#includeintfact(intn)staticintf=1;/静静态变量态变量/*f只进行一次初始化只进行一次初始化*/f=f*n;return(f);main()inti;for(i=1;i=4;i+)printf(%dn,fact(i);第一次调用第一次调用:i=1 n=1 f=11 第二次调用第二次调用:i=2 n=2 f=12第三次调用第三次调用:i=3 n=3 f=26第四次调用第四次调用:i=4 n=4 f=624#includemain()staticinti,s;i=1;while(i=100)s=s+i;i+;printf(s=%dn,s);可见,未经赋值的静态局部变量,系统可见,未经赋值的静态局部变量,系统自动自动初始化为初始化为0。全局变量的存储类别全局变量的存储类别全局变量都是静态变量。全局变量都是静态变量。1.extern变量声明变量声明用于用于扩展扩展全局变量的作用域。全局变量的作用域。2.static全局变量全局变量用于用于限制限制全局变量的作用域。全局变量的作用域。函数的存储类别函数的存储类别1.extern函数函数用于用于扩展扩展函数的作用域。函数的作用域。2.static函数函数用于用于限制限制函数的作用域。函数的作用域。