1 / 58

第五章 函数

第五章 函数. 5.1 函数的定义、调用与说明 5.2 函数间参数传递 5.3 递归函数 5.4 函数参数缺省 5.5 函数重载 5.6 函数模板 5.7 作用域与存储类. 5.1 函数的定义、调用与说明. y. x. z. 5.1.1 引例 已知五边形的各条边的长度,计算其面积 计算多边形面积,可将多边形分解成若干个三角形. 计算三角形面积的公式如下:. 用前面所学的知识实现:. #include "math.h" #include "iostream.h" void main()

pooky
Download Presentation

第五章 函数

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第五章 函数 5.1 函数的定义、调用与说明 5.2 函数间参数传递 5.3 递归函数 5.4 函数参数缺省 5.5 函数重载 5.6 函数模板 5.7 作用域与存储类

  2. 5.1 函数的定义、调用与说明 y x z • 5.1.1 引例 • 已知五边形的各条边的长度,计算其面积 • 计算多边形面积,可将多边形分解成若干个三角形 • 计算三角形面积的公式如下:

  3. 用前面所学的知识实现: #include "math.h" #include "iostream.h" void main() {float a,b,c,d,e,f,g,p1,p2,p3,s1,s2,s3,s; cin>>a>>b>>c>>d>>e>>f>>g; p1=(a+b+c)/2; s1=sqrt(p1*(p1-a)*(p1-b)*(p1-c)); p2=(c+d+e)/2; s2=sqrt(p2*(p2-c)*(p2-d)*(p2-e)); p3=(e+f+g)/2; s3=sqrt(p3*(p3-e)*(p3-f)*(p3-g)); s=s1+s2+s3; cout<<s<<endl; }

  4. 用函数实现: #include "math.h" #include "iostream.h" float area(float x, float y, float z) //定义求三角形面积函数 { float c,s ; c = (x + y + z)/2; s = sqrt(c*(c-x) * (c-y) * (c-z)); return s; } void main() //主函数 {float a,b,c,d,e,f,g,s; cin>>a>>b>>c>>d>>e>>f>>g; s=area(a,b,c)+area(c,d,e) +area(e,f,g); cout<<s<<endl; } • 特点: • 函数具有相对独立的功能 • 函数与函数之间通过参数(输入)和返回值(输出)来联系 • 使用函数有利于代码重用,提高开发效率

  5. 5.1.2 函数的定义 函数类型 函数名(形式参数类型表) {函数体} 【例5.1】求三个整型数中最大的数。 将两数的比较独立出来编一函数: int max(int x,int y) { int z; if(x>y) z=x; else z=y; return z; //将大的数返回 } void main() { int a,b,c,m; cin>>a>>b>>c; m=max(a,b); m=max(c,m); cout<<m<<endl; }

  6. 【例5.2】编一程序,输出如右图形。 将n行构成的三角形独立出来编一函数: void pic(int n) { for(int i=0;i<n;i++) { cout.width(10-i); //控制每行输出的起始位 for(int j=0;j<2*i+1;j++)cout<<"*"; cout<<endl; } } void main() { pic(4); pic(6); }

  7. 说明: • 函数类型指函数返回值的数据类型 • 函数体由语句和其它分程序组成。 • 形式参数可以为空,但圆括号不能省略。 • 函数体中不允许再嵌套定义函数 • 对没有返回值的函数,函数类型定为void型(无类型或空类型)。 • 非void型函数 函数体中必须有return语句,形式为:      return 表达式 ; 或 return (表达式) ; 用于返回函数值。表达式值的类型与函数类型最好一致。 • void型函数 函数体中return语句可以不出现。若出现,则不能带表达式。

  8. 5.1.3 函数调用 函数名(实在参数表) 形式: 函数调用的过程: • 注意: • 实参与形参的个数、位置与类型必须一致。它可以是同类型的常量、变量或表达式。 • 调用的形式可以是表达式,也可以是语句。 • 函数定义中的形参只有当发生函数调用时,才被分配内存单元。

  9. void pic(int n) { for(int i=0;i<n;i++) { cout.width(10-i); for(int j=0;j<2*i+1;j++) cout<<"*"; cout<<endl; } } #include "iostream.h" #include "iomanip.h" void main() { pic(4); //以语句形式调用 pic(6); }

  10. 函数调用和返回的过程: 主函数 自定义函数 ① 保存:返回地址、当前现场 ④ 输入数据 处理过程 ③ ② ⑤ 调用函数 ⑥ 函数结束或 函数值返回 ⑦ 恢复:主调程序现场、返回地址 输出 ⑧ 结束 注意:数据的输入、输出一般放在主函数中

  11. 【例5.3】求正整数m,n的最大公约数和最小公倍数 。 分析:m、n的最小公倍数为: m*n/最大公约数。 int gcd(int m,int n) //求最大公约数 { while(int r=m%n) {m=n;n=r;} return(n); } #include "iostream.h“ void main() { int m,n; cin>>m>>n; cout<<gcd(m,n)<<endl; cout<<sct(m,n)<<endl; } int sct(int m,int n) //求最小公倍数 { return( m*n/gcd(m,n) ); } 注意: 函数中参数传递是单向“传值”。

  12. 函数嵌套调用的示意图:

  13. 5.1.4 函数说明(函数原型) 形式: 函数类型 函数名(形式参数类型表); 【例5.4】函数说明示例 #include "iostream.h" void main() {int a,b,c; int max(int x,int y); //函数说明,也可int max(int,int); cin>>a>>b; c=max(a,b); cout<<c<<endl; } int max(int x,int y) { return x>y?x:y ; } 省略参数名 注意:  函数调用在前,定义在后,则必须对函数进行说明,函数说明可以放在函数调用之前所在的函数内,也可以放在源文件开头、函数的外部。  函数说明和函数定义在返回类型、函数名和参数表上必须要完全一致。

  14. 5.2 函数间参数传递 5.2.1 传值参数 特点:形参的改变不会影响实参的值 【例5.5】m是一个3位的正整数,将满足m、m2、m3均为回文数的正整数输出。所谓回文数是指顺读与倒读数字相同,如4、151、34543。 分析:将正整数的每位数取出,构造一个逆序的正整数,若该数与原来的相同,即为回文数。 实参:常量、变量和表达式。 形参:变量 bool palindrome(int x) { int m=x,n=0,k; while(x!=0) //构造一逆序数 { k=x%10; n=n*10+k; x/=10; } return m==n; }

  15. 【例5.6】分析下面程序,能否交换两个变量的值?【例5.6】分析下面程序,能否交换两个变量的值? #include "iostream.h" void swap(int x,int y) {int temp; temp=x;x=y;y=temp; } void main() {int a,b; cin>>a>>b; swap(a,b); cout<<"a="<<a<<" b="<<b<<endl; }

  16. 5.2.1 指针参数 1.形参为指针变量 特点:形参的改变能影响实参值 【例5.7】交换两个变量的值 #include "iostream.h" void swap(int * ,int * ); void main() { int a,b;cin>>a>>b; swap(&a,&b); cout<<a<<b<<endl; } void swap(int *x,int *y) {int temp=*x;*x=*y;*y=temp;} 形参:指针变量 实参:变量的地址、指针变量或数组名

  17. 【例5.8】随机生成10个1~100之间的数放在一维数组中,求其平均值及最大的元素值 。 #include "iostream.h" #include "stdlib.h" const int N=10; void fun(float *p,float *p1,int *p2) { float sum,max1; sum=max1=*p++; for(int i=1;i<N;i++) { if (max1<*p) max1=*p; sum=sum+*p;p++; } *p1=sum/N; *p2=max1; } void main() { float a[10],aver,max,x; for(int i=0;i<10;i++) { x=rand()%100+1; a[i]=x; } fun(a,&aver,&max); //注意实参的写法 cout<<aver<<max<<endl; } 程序:

  18. 【例5.9】字符串的复制 程序: #include "iostream.h" void copy_string(char *from, char *to) { while(*from!='\0') *to++=*from++; *to='\0'; } void main() { char a[]="I am a teacher.", b[]="You are a student."; cout<<a<<endl<<b<<endl; copy_string(a,b); cout<<a<<endl<<b<<endl; }

  19. 注意:若对应的实参是指针变量,则该指针应有确定的指向。注意:若对应的实参是指针变量,则该指针应有确定的指向。 若该二语句删除,程序运行时将异常终止。 void main() {char *a,*b; a=new char[40]; b=new char[20]; cin>>a>>b; copy_string(a,b); cout<<a<< b<<endl; delete []a; delete []b; }

  20. 2.形参为常指针 防止被调函数对实参所指对象的修改 错误,不能修改const对象 #include "iostream.h " int f(const int * p) { int a=100; a=*p; return a; } void main() { int x=5; cout<<f(&x); } *p=a; return *p;

  21. 注意:若实参是常对象的地址,则形参必须定义为常指针。注意:若实参是常对象的地址,则形参必须定义为常指针。 #include "iostream.h" int f(const int *p) { int a=100; a=*p; return a; } void main() { const int x=5; cout<<f(&x); } 形参不能定义为int *p

  22. 3.函数返回值为指针 即函数的返回值可以是变量的地址、数组名或指针变量等。 如: float *fun(float x[],float y); char *strcat(char *strDest,const char *strSource); 在说明或定义返回值为指针的函数时,只须在函数名前加一指针类型说明符即可。

  23. 【例5.10】拼接两个字符串s1和s2,将拼接后的字符串存于s1中返回。【例5.10】拼接两个字符串s1和s2,将拼接后的字符串存于s1中返回。 char *strcat1(char *s1,const char *s2) {char *p=s1; while(*p++); --p; while(*p++=*s2++) ; return(s1); } 思考:若不定义为返回指针值的函数,则程序应如何修改?

  24. 注意:不能返回一个局部变量(在函数内定义的变量)的地址注意:不能返回一个局部变量(在函数内定义的变量)的地址 #include "iostream.h" int *fun(int x) { int y=x*x; return &y; } void main() { int a=5, *p; p=fun(a); cout<<*p; } p指向的对象已经不存在

  25. 5.2.2 引用参数 特点:形参的改变可影响实参值。 &为引用声明符。 x是a的引用, y是b的引用。 #include "iostream.h" void swap(int &x,int &y) { int temp=x;x=y;y=temp; } void main() { int a,b; cin>>a>>b; swap(a,b); cout<<"a="<<a<<"b="<<b; } 形参是引用 实参只能是变量名 引用是一种特殊类型的变量,可认为是另一个变量的别名,它不占用存储空间,对引用的操作就是对被引用者的操作,它们代表的是同一存储单元。

  26. 【例5.11】编一函数,判别一个自然数N是否是降序数,同时,求出该数各位数和。并加以调用,若是降序数输出“yes”,否则输出“no”。例如:3、441、531是降序数;而412不是降序数。【例5.11】编一函数,判别一个自然数N是否是降序数,同时,求出该数各位数和。并加以调用,若是降序数输出“yes”,否则输出“no”。例如:3、441、531是降序数;而412不是降序数。 bool drop(int x,int &sum) { bool flag=1;int x1=x; while(x1) { sum+=x1%10;x1/=10; } while(x>=10&&flag) //判断是否降序数 if(x/10%10>=x%10) x/=10; else flag=0; return flag; }

  27. 5.2.3 数组名作参数 特点:在被调函数中对形参数组的任何改变均会影响实参所指地址里的内容 。 形参:数组名 实参:数组名或指针变量 【例5.12】对含有n个元素的整型数组a,从大到小进行排序。 void sort(int x[],int n) {int i,j,k,w; for(i=0;i<n-1;i++) {k=i; for(j=i+1;j<n;j++)if(x[k]<x[j])k=j; if(i!=k){w=x[i];x[i]=x[k];x[k]=w;} } } 数值型数组的元素个数一般须传给形参 调用:sort(a,10);

  28. 【例5.13】 求字符串长度,并调用之。 int len(char s[]) { int i=0; while(s[i]!='\0')i++; return i; } 字符型数组的元素个数一般不用传给形参 调用: cout<<len(str);

  29. 正确 错误 int max(int x,int y) int max(int x,y) void sort(int a[],int n) void sort(int a[n],int n) void sort(int a[];int n) void sort(int a[],n) 注意: ① 形参和实参的类型应保持一致。 ② 函数头里的参数,要一个一个地分别说明或 列出。 下面是几种函数头正确与错误的写法: int m(int x[][4],int y) int m(int x[][],int y)

  30. 5.2.4 对象参数 实参为CString对象,则形参可以是: (1) CString 参数,对形参的改变不影响实参; (2) CString &参数, 对形参的改变将影响实参; (3) const CString &参数,不允许改变形参值。 【例5.14】 统计字符串str1中出现子串sub的个数 int count(CString str1,const CString sub) { int i,count=0,k; i=str1.Find(sub); while(i>=0) {count++; k=str1.GetLength(); str1=str1.Mid(i+1,k-i); i=str1.Find(sub); } return count; }

  31. 5.3 递归函数 用自身的结构来描述自身就称为递归。 最典型的例子是对阶乘运算: 特点: ①原始问题可转化为解决方法相同的新问题; ②新问题的规模比原始问题小; ③新问题又可转化为解决方法相同的规模更小的新问题,直至终结条件为止。

  32. 【例5.15】编fac(n)=n! 的递归函数 递推 回归 long fac(int n) { if(n==1) return(1); return (n*fac(n-1)); } fac(3)=3*fac(2) fac(2)=2*fac(1) fac(4)=4*fac(3) fac(1)=1 fac(4)=4*6 fac(3)=3*2 fac(2)=2*1 递推过程   每调用自身,当前参数压栈,直到达到递归结束条件。 回归过程   不断从栈中弹出当前的参数,直到栈空。 思考:若fac函数中没有语句 if(n==1) return(1);程序运行结果将如何?

  33. 【例5.16】用递归函数实现将一个十进制整数转换成二至十六任意进制的字符【例5.16】用递归函数实现将一个十进制整数转换成二至十六任意进制的字符 void convert(int m,int r) { char b[17]="0123456789ABCDEF"; if(m!=0) { convert(m/r,r);cout<<b[m%r]; } } • 任何有意义的递归必须具有: • 递归结束条件及结束时的值; • 能用递归形式表示,并且递归向终止条件发展。 递归算法设计简单,程序代码简洁易读,但它消耗的机时和占据的内存空间比非递归大。

  34. 参数eps取默认值1e-6 void main() { cout<<"s1="<<s(2.0); cout<<"s2="<<s(3.0); cout<<"s3="<<s(1.0,1e-5);} 参数eps的值为1e-5 5.4 函数参数缺省 【例5.17】求下面级数的部分和。精度为: #include "iostream.h" #include "math.h" double s(double x , double eps=1e-6) {int n=1; double w=0.0, t=1.0 ; while(fabs(t)>=eps) {w+=t; t=t*x/(n++); } return w; }

  35. 说明: • 默认参数的说明必须出现在函数调用之前。若一个函数说明已给出参数的默认值,则在函数定义中不允许再设置。 • 要求赋予默认值的参数必须放在形参表列中的最右端。 分析下面各例: void fun(int i, int j, int k, int m=3,int n=4); 假设函数调用语句为: fun(1,2); //错误,至少应有三个实参 fun(10,20,30); //正确,m、n取默认值 fun(10,20,30,40); //正确,m取40 、n取默认值4 fun(10,20,30, ,50); //错误,只能从左至右匹配

  36. 5.5 函数重载引例:求不同类型数据的绝对值 #include <iostream.h> #include <math.h> void main() {int x1=-1; double x2=2.5; long x3=3L; cout<<abs(x1)<<endl; cout<<fabs(x2)<<endl; cout<<labs(x3)<<endl; } 不同名函数实现同一类的操作 不足:易出错、难记忆 函数重载:一组参数(个数或类型)不同的函数共用一个函数名。

  37. 一. 参数类型不同的重载函数 【例5.18】 #include <iostream.h> int abs(int x) {return x>0?x:-x; } double abs(double x) {return x>0?x:-x; } long abs(long x) {return x>0?x:-x; } void main() {int x1=1; double x2=2.5; long x3=3L; cout<<abs(x1)<<endl; cout<<abs(x2)<<endl; cout<<abs(x3)<<endl; } 编译器根据函数参数的类型来确定应该调用哪个函数

  38. 二. 参数个数不同的重载函数 【例5.19】 #include <iostream.h> int min(int a,int b) {return a<b?a:b; } int min(int a,int b,int c) {int t=min(a,b); return min(t,c);} int min(int a,int b,int c,int d) {int t1=min(a,b); int t2=min(c,d); return min(t1,t2);} void main() { cout<<min(13,5,4,9)<<endl; cout<<min(-2,8,0)<<endl; } 编译器根据调用语句中实参的个数来确定应该调用哪个函数 可见:重载使得函数的使用更加灵活、方便。它体现了c++对多态性的支持———一个名字,多个入口 。 注意:不要同时使用重载函数和缺省参数函数。

  39. 5.6 函数模板 使用函数重载需编写多个函数,而利用函数模板只需定义一个通用函数即可。 利用函数模板定义的函数称为模板函数,形式: template <class 类型参数表 > 类型 函数名( 参数表) {……} 定义一个数据类型T 使用模板函数时,模板中的类型参数T用实参的类型替换 【例5.20】 #include <iostream.h> template <class T> T min(T a,T b) {return a<b?a:b; } void main() { cout<<min(3,5)<<endl; cout<<min(8.5,5.6)<<endl; }

  40. 分析以下几种模板函数:  template <class T> T fun1(T a,int b) //可含有模板参数表中未给出的数据类型 {…… }  template <class T1,T2,T3> //模板中可带有多个参数 void fun1(T1 a,T2 b,T3 c) {…… }  template <class T> void fun1(int a,int b) //必须至少有一个参数的类型为模板的类型参数 {…… }  template <class T> void fun1(int a,int b) {T i,j; …… }

  41. 【例5.21】定义一个通用的函数,对n个数按递增排序。【例5.21】定义一个通用的函数,对n个数按递增排序。 #include "iostream.h " template <class TEM> void sort(TEM x[],int n) {int i,j,k; TEM w; for(i=0;i<n-1;i++) { k=i; for(j=i+1;j<n;j++) if(x[k]>x[j])k=j; if(i!=k) {w=x[i];x[i]=x[k];x[k]=w;} } } void main() {int i,a[6]={6,9,2,4,1,0}; double b[4]={5.5,8.0,3.3,0}; sort(a,10); sort(b,5); for(i=0;i<10;i++) cout<<a[i]<<endl; for(i=0;i<5;i++) cout<<b[i]<<endl; } 注意w变量的定义

  42. 全局变量(外部变量):     在函数外部定义的变量 局部变量(内部变量):     在函数内部定义的变量, 包括形式参数 【例5.22】 5.7 作用域与存储类别 从下面三方面分析各变量 作用域(可见性): 在什么范围内可以访问 空间概念 生存期: 在什么时间存在 时间概念 初始化 #include "iostream.h" int digit(long n) { int k=0; while(n!=0) {n/=10; k+=1;} return k; } void main() { long x; cin>>x; cout<< digit(x)<<endl; }

  43. 作用域:从定义点开始到所在的分程序结束 生存期:开始执行分程序就生成,分程序执行结束就消亡 初始化:可以初始化,缺省值为随机值。 作用域不可以扩展 5.7.1 自动变量 【例5.23】 #include "iostream.h" int f(int x) { x++; int y=5; //auto int k=5; y++; return x+y; } void main() { int k=2; cout<<f(k)<<endl; cout<<f(k+1)<<endl; } 缺省的存储类别:auto 注意:只有内部变量存储类别才能auto

  44. 5.7.2 静态局部变量 作用域:从定义点开始到所在的分程序结束。 生存期:程序的执行周期 初始化:可以初始化,缺省值为0或'\0 ' 。 初始化一次 在开始执行程序时初始化 作用域不可以扩展 一个函数可能被调用多次, 前一次调用的结果带到下一次去。 【例5.24】 #include "iostream.h" int fac(int n) {static int f=1; f=f*n; return(f);} void main() {auto int i; for(i=1;i<=5;i++) cout<<"i!="<<fac(i); }

  45. 5.7.3 全局变量 作用域:从定义点开始到所在的文件结束 生存期:程序的整个执行周期 初始化:可以初始化,缺省值为0或'\0 '。 作用域可以扩展: 向上扩展 横向扩展,扩展到另一个文件 注意:扩展的地方不能初始化

  46. 【例5.25】外部变量示例 #include "iostream.h" int m=10; void f1(int n) { n=2*n;m=m/3; } int n; void f2() { n=5;m++;n++; } void main() { int n=2; f1(n); f2(); cout<<m<<n<<endl; } 外部变量与局部变量同名,起作用的是局部变量 注意: 外部变量的作用域只限于定义处到文件结束,定义点之前的函数或其它文件中的函数不可以引用该外部变量。 可以使用extern声明符来扩展外部变量的作用域

  47. (1)作用域向定义点之前的函数扩展 #include <iostream.h> extern int i; //不能初始化 void fun() { // extern int i; cout<<i; } int i=5; void main() { int j=20; cout<<j; fun(); } #include <iostream.h> extern int i; void fun() { //extern int i; 错误 cout<<i; } void g() {i++;} int i=5; void main() { int j=20; cout<<j; g(); fun(); }

  48. (2)作用域扩展到另一个文件 【例5.26】作用域横向扩展示例 //file1.cpp extern max,min; #include "iostream.h" void main() { void maxmin(int x[],int n); int a[10]={11,2,3,-4,5,6,7,8,0,20}; maxmin(a,10); cout<<max<<" "<<min<<endl; } //file2.cpp int max,min; void maxmin(int x[],int n) { max=x[0];min=x[0]; for(int i=0;i<n;i++) { if(x[i]>max)max=x[i]; if(x[i]<min)min=x[i]; } }

  49. 总结:

  50. 5.8 程序举例 【例5.27】编一判断质数的函数,验证歌德巴赫猜想:任何大于2的偶数均可表示为两个素数的和。例如:4=2+2(特例,仅此一个),6=3+3,8=3+5,…。程序要求输入任一偶数,输出6到该数范围内的各个满足条件的组合。 分析:对一个偶数,分解为两个质数和,既n=a+b。方法是从找最小的质数a为3开始(因2是偶数,另一个必定是偶数,不可能是质数),判断b=n-a是否是质数,若b也是质数,则n符合要求;否则,找下一个质数a,再判断b。

More Related