第五章 函 数 要点:掌握函数的定义,函数的原形,函数的返回值,函数的调用,函数的形式参数和实际参数之间的关系;掌握函数重载的使用方法,关键字inline的含义与使用,掌握变量的作用域与生存期,了解函数的作用域。
第五章 函 数 第五章 C++函数 重点考核的内容(知识点): 1.函数的声明格式与定义格式。 2.函数调用表达式的语法格式及执行过程。 第五章 函 数 第五章 C++函数 重点考核的内容(知识点): 1.函数的声明格式与定义格式。 2.函数调用表达式的语法格式及执行过程。 3.函数定义中形参变量的作用及实虚参数结合的过程。 5.函数声明(原型)语句的定义格式及作用。 6.形参为一维数组或二维数组时的说明格式、对应实参的语法规则、调用时数组参数传递的过程。 7.变量的作用域的概念和含义;系统对全局变量、static变量、局部变量的建立和初始化过程。
第五章 函 数 一般考核的内容(知识点): 1.函数模板(又称模板函数)的定义格式及其作用。函数模板的实例化,函数模板与一般函数定义之间的优先关系。 2.函数的递归调用的概念和递归函数的具体执行过程 3.头文件在程序文件中的位置和作用。 4.函数声明或定义中可选参数的使用,对应的函数调用的格式及执行过程。 5.内联函数的定义与作用。 6.函数重载的概念。
考核要求: 掌握以上重点考核的内容,了解其一般考核的内容。
第五章 函 数 5.1 函数概述 1.函数:将大的程序分成功能相对独立的小模块,每一个模块称之为函数。 第五章 函 数 5.1 函数概述 1.函数:将大的程序分成功能相对独立的小模块,每一个模块称之为函数。 函数分为用户定义的函数和标准库函数两种。 一个c++程序由一个main()函数和若干用户定义的函数组成。
第五章 函 数 2. 函数定义 格式:类型 函数名(参数表) { 函数体 } 例如:double sqare(double n) 第五章 函 数 2. 函数定义 格式:类型 函数名(参数表) { 函数体 } 例如:double sqare(double n) {return n*n);}
第五章 函 数 3.函数类型 (1)有参数、有返回值 例 int bigger(int a,int b) { 第五章 函 数 3.函数类型 (1)有参数、有返回值 例 int bigger(int a,int b) { return (a>b)?a:b; }
第五章 函 数 (2)有参数、无返回值 例 void delay(long a) { for(int i=1;i<=a;i++) }
第五章 函 数 (3)无参数、有返回值 例 int getx() { int x; 第五章 函 数 (3)无参数、有返回值 例 int getx() { int x; cout<<"please input a integer:\n"; cin>>x; return x; }
第五章 函 数 (4)无参数、无返回值 例 void message() { 第五章 函 数 (4)无参数、无返回值 例 void message() { cout<<"this is a message.\n"; }
第五章 函 数 3. 函数定义中,若类型省略,默认为int,但一般不要省略。 例 f(int s) { return s+1; } 第五章 函 数 3. 函数定义中,若类型省略,默认为int,但一般不要省略。 例 f(int s) { return s+1; } 例 int f(int s) { return s+1; }
第五章 函 数 5.3 函数调用 1. 函数调用格式与方式。 (1)函数调用格式:函数名(实参表) (2)函数调用方式 作为表达式的函数调用 第五章 函 数 5.3 函数调用 1. 函数调用格式与方式。 (1)函数调用格式:函数名(实参表) (2)函数调用方式 作为表达式的函数调用 作为语句的函数调用
第五章 函 数 例:#include<iostream.h> void hi() 第五章 函 数 例:#include<iostream.h> void hi() { cout<<“hi!”<<endl; } double square(double n) { return n*n; } void main() { double s=0,i; for(i=1;i<=100;i++) s=s+square(i);//作为表达式的函数调用 hi();//作为语句的函数调用 cout<<“1至100的平方和:”<<s<<endl; }
第五章 函 数 (3)函数的副作用:通过函数调用,使得实在参数的值得以改变,或产生特定的操作,称为函数的副作用。 第五章 函 数 (3)函数的副作用:通过函数调用,使得实在参数的值得以改变,或产生特定的操作,称为函数的副作用。 (4)作为语句的函数调用的功能是通过函数的副作用来体现的,因而把一个无副作用的函数作为语句来调用是毫无意义的。
第五章 函 数 2. 递归函数(recursive function) (1)递归函数:即自调用函数。在函数体内部直接或间接地自己调用自己。 第五章 函 数 2. 递归函数(recursive function) (1)递归函数:即自调用函数。在函数体内部直接或间接地自己调用自己。 例:求n! long fact(int n) { if(n==1) return 1; return fact(n-1)*n; }
第五章 函 数 (2)递归调用过程:讲解fact(4)调用过程。
第五章 函 数 (3)递归调用形式 直接递归调用:一个函数调用该函数本身。 例:Fibonacci数列1,1,2,3,5,…… 第五章 函 数 (3)递归调用形式 直接递归调用:一个函数调用该函数本身。 例:Fibonacci数列1,1,2,3,5,…… long fib(int x) { if(x>2) return (fib(x-1)+fib(x-2)); else return 1; }
第五章 函 数 间接递归调用:函数A调用函数B,而函数B又调用了函数A。 例5.2 见书。
第五章 函 数 (4)递归的条件 须有完成函数任务的语句。 例:#include<iostream.h> void main() 第五章 函 数 (4)递归的条件 须有完成函数任务的语句。 例:#include<iostream.h> void main() { if(val>1) count(val-1); cout<<”ok:”<<val<<endl;//此语句完成函数任务 }
第五章 函 数 一个递归出口:如上例if(val>1)。 一个递归调用语句:如上例count(val-1)。 先测试,后递归调用。
第五章 函 数 例:#include<iostream.h> void main() { 第五章 函 数 例:#include<iostream.h> void main() { count(val-1);//无穷递归,最终栈空间枯竭而出错 if(val>1) cout<<”ok:”<<val<<endl; }
第五章 函 数 (5)消除递归:用非递归函数代替递归函数。 例:long fact(int n) { if(n==1) return 1; 第五章 函 数 (5)消除递归:用非递归函数代替递归函数。 例:long fact(int n) { if(n==1) return 1; return fact(n-1)*n; }
第五章 函 数 例:long fact(int n) { long temp=1; for(int i=1; i<=n;i++) 第五章 函 数 例:long fact(int n) { long temp=1; for(int i=1; i<=n;i++) temp*=i; return temp; }
第五章 函 数 5.3 函数原型与头文件 1. 函数原型又称函数声明,由函数返回类型、函数名和参数表组成。 第五章 函 数 5.3 函数原型与头文件 1. 函数原型又称函数声明,由函数返回类型、函数名和参数表组成。 即:返回类型 函数名(参数表); //以;号结尾 函数原型中的参数表可以只包含参数的类型。
第五章 函 数 例:int area(int length,int width); 例:int area(int,int);
第五章 函 数 2. 函数定义 格式:返回类型 函数名(参数表) { 函数体 } 函数原型和函数定义在返回类型、函数名和参数表上必须一致。
第五章 函 数 例:int area(int length,int width); …… 第五章 函 数 例:int area(int length,int width); …… int area(int length,float width) { return length*width; }
第五章 函 数 3. 函数返回在声明时约定的数据类型。 例:int max(int a,int b) { if(a>b) 第五章 函 数 3. 函数返回在声明时约定的数据类型。 例:int max(int a,int b) { if(a>b) return a; else return b; }
第五章 函 数 4. return语句 功能:将程序的执行返回到函数调用处,并将其后表达式的值作为函数值返回。 第五章 函 数 4. return语句 功能:将程序的执行返回到函数调用处,并将其后表达式的值作为函数值返回。 (1)return语句后面的括号是可选的。 例:return (3); 等价与 return 3;
第五章 函 数 (2)若函数的返回类型是void,在函数体中也可使用return语句,但不能有返回值。 第五章 函 数 (2)若函数的返回类型是void,在函数体中也可使用return语句,但不能有返回值。 例:void message(int a) { if(a>10) return; …… }
第五章 函 数 5. 函数原型、函数定义和函数调用的关系 (1)函数必须先有原型(或定义)后调用。 (2)函数定义包含函数原型。 第五章 函 数 5. 函数原型、函数定义和函数调用的关系 (1)函数必须先有原型(或定义)后调用。 (2)函数定义包含函数原型。 (3)若函数定义于函数原型之前,则不必给出函数原型。 (4)若函数定义于函数原型之后,则必须给出函数原型。
第五章 函 数 例: #include<iostream.h> 例: 第五章 函 数 例: #include<iostream.h> double max(double,double);//函数原型 void main() { … c=max(a,b);//函数调用 } double max(double x,double y)//函数定义 { if(x>y) return x; else return y; 例: #include<iostream.h> double max(double x,double y)//函数定义 { if(x>y) return x; else return y; } void main() { … c=max(a,b);//函数调用
第五章 函 数 6. 一般,我们不将函数原型、函数调用和函数定义放在同一程序文件,而是分别保存。函数原型归入头文件,函数定义归入程序文件,函数调用归入主程序文件。从而形成项目的多文件结构。 例://头文件area.h double area(double); //程序文件area.cpp #include”area.h” #define PI 3.14159 double area(double r) { return r*r*PI; } //主程序文件areat.cpp #include”area.h” #include<iostream.h> void main() { … area=area(radius); }
第五章 函 数 7. 一个c++项目(控制台应用程序)的结构。 主程序文件(.cpp)+头文件(.h) 第五章 函 数 7. 一个c++项目(控制台应用程序)的结构。 主程序文件(.cpp)+头文件(.h) 程序文件1(.cpp)+头文件(.h) 程序文件2(.cpp)+头文件(.h) 项目(.prj) …… …… 程序文件n(.cpp)+头文件(.h)
第五章 函 数 5.4 函数调用中的参数传递。 5.4.1 传值方式:形参=实参的值 例:(形参为简单变量) 第五章 函 数 5.4 函数调用中的参数传递。 5.4.1 传值方式:形参=实参的值 例:(形参为简单变量) #include<iostream.h> double time2(double n) { n=n*2; return n; }
第五章 函 数 #include<iostream.h> double time2(double n) { n=n*2; 第五章 函 数 #include<iostream.h> double time2(double n) { n=n*2; return n; } void main() { double m=7.0; cout<<endl<<m; cout<<endl<<time2(m); cout<<endl<<m; } 结论:当形参为普通变量时, 形参和实参按传值方式结合。 n=m的值 n*=2; return n;
第五章 函 数 5.4.2 传地址方式:形参=实参的值(地址值) 例:(形参为数组变量) 第五章 函 数 5.4.2 传地址方式:形参=实参的值(地址值) 例:(形参为数组变量) 设计函数sum,它计算并返回参数数组中所有元素的合计值。 //头文件 int sum(int array[],int size);
第五章 函 数 //程序文件sum.cpp #include”sum.h” int sum(int array[],int size) { 第五章 函 数 //程序文件sum.cpp #include”sum.h” int sum(int array[],int size) { int s=0; for(int i=0;i<size;i++) s+=array[i]; return s; }
第五章 函 数 //主程序文件sumt.cpp #include<iostream.h> #include”sum.h” 第五章 函 数 //主程序文件sumt.cpp #include<iostream.h> #include”sum.h” void main() { int v1[]={1,2,3,4,5}; cout<<sum(v1,5)<<endl; int v2[]={1,2,3,4,5,6,7,8}; cout<<sum(v2,8)<<endl; }
第五章 函 数 size=5的值 //传值方式 int s=0; sum(v1,5)= for(int i=0;i<size;i++) 第五章 函 数 sum(v1,5)= 结论:当参数为数组时,形参和实参按传地址方式结合。 例:见书。 size=5的值 //传值方式 int s=0; for(int i=0;i<size;i++) s+=v1[i];//传地址方式 return s;
第五章 函 数 5.4.3 默认参数的函数 1.默认参数:函数的形参表中,若给形参定义了默认值,称此参数为默认参数。又称可选参数。 第五章 函 数 5.4.3 默认参数的函数 1.默认参数:函数的形参表中,若给形参定义了默认值,称此参数为默认参数。又称可选参数。 2.函数调用时,若不给出可选参数所对应的实参,则实参取默认值。
第五章 函 数 例: int f(int a,char b,char c=’z’,int d=100); //c、d为默认参数 第五章 函 数 例: int f(int a,char b,char c=’z’,int d=100); //c、d为默认参数 f(3,’a’,’b’)等价于f(3,’a’,’b’,100) f(3,’a’)等价于f(3,’a’,’z’,100)
第五章 函 数 3.可选参数的声明 (1)既有函数原型又有函数定义时,只能在函数原型中声明可选参数。函数定义中不允许声明可选参数。 第五章 函 数 3.可选参数的声明 (1)既有函数原型又有函数定义时,只能在函数原型中声明可选参数。函数定义中不允许声明可选参数。 (2)只有函数定义时,可选参数方可出现在函数定义中。
第五章 函 数 4.可选参数的顺序规定 (1)可选参数必须为形参表中最后且连续的若干个参数。 第五章 函 数 4.可选参数的顺序规定 (1)可选参数必须为形参表中最后且连续的若干个参数。 例:void func(int a=1,int b, int c=3,int d=4);//错误 void func(int a,int b=2, int c=3,int d=4);//正确
第五章 函 数 (2)调用具有可选参数的函数时,被省略的实参只能是最后且连续的若干个可选参数。 第五章 函 数 (2)调用具有可选参数的函数时,被省略的实参只能是最后且连续的若干个可选参数。 例:void func(int a,int b=2,int c=3,int d=4); func(10,15,20,30);//正确 func();//错误 func(12,12);//正确 func(2,15, ,20);//错误 例:见书。
第五章 函 数 5.5 内联函数(inline function) 第五章 函 数 5.5 内联函数(inline function) 1.为提高程序的执行效率和可读性,一般将只有1至5行代码的简单函数声明为内联函数。内联函数必须在被调用之前声明或调用。 例:inline int add2(int n) { return n+2; }
第五章 函 数 2.内联函数的函数体限制。 (1)内联函数只适合于只有1至5行的小函数。 (2)递归函数不能用作为内联函数。 第五章 函 数 2.内联函数的函数体限制。 (1)内联函数只适合于只有1至5行的小函数。 (2)递归函数不能用作为内联函数。 (3)内联函数体中,不能含有复杂的结构控制语句。如:switch和while语句等。
第五章 函 数 5.6 重载函数(function overloading) 第五章 函 数 5.6 重载函数(function overloading) 1.函数重载:即允许定义同名的函数,但重载的函数必须在形参的数量或类型上或顺序上与其他同名函数有所不同。 例:long add(long a,long b){return a+b;} long add(double a,double b){return a+b;}
第五章 函 数 2.形式参数:函数原型或函数定义中的参数。简称形参。 实在参数:函数调用中的参数。简称实参。 第五章 函 数 2.形式参数:函数原型或函数定义中的参数。简称形参。 实在参数:函数调用中的参数。简称实参。 3.函数重载的内部实现方法---名字粉碎(name mangling) 名字粉碎:c++根据函数名和参数信息来生成内部函数名的方法。
第五章 函 数 例: 重载函数 内部函数名 int f(char a); f_c 第五章 函 数 例: 重载函数 内部函数名 int f(char a); f_c int f(char a,int b,double c); f_cid int f_cid(); f_cidv
第五章 函 数 5.可选参数与函数重载 例:int f(int k,int m=0,double d=0.0); 则函数调用 第五章 函 数 5.可选参数与函数重载 例:int f(int k,int m=0,double d=0.0); 则函数调用 f(3,5,6.7);//正确 f(3,5);//正确 f(3);//正确
第五章 函 数 但以下函数均不能重载: int f(int k,int m=0,double d=0.0); 第五章 函 数 但以下函数均不能重载: int f(int k,int m=0,double d=0.0); int f(int);//f(2)调用谁? int f(int,int);//f(2,3)调用谁? int f(int,int,double);//f(2,3,5.0)调用谁? int f(int,double=0.0);//f(2)调用谁? 结论:对有可选参数的函数,在逐个去掉可选参数后形成的函数不能重载。
第五章 函 数 例:若有int fp(char c,int k=0,double d=100.0); 第五章 函 数 例:若有int fp(char c,int k=0,double d=100.0); 则下列函数中可以重载的是( A, D ) A. int fp(); B.void fp(char c); C. int fp(char,int); D.void fp(char,int,int);
第五章 函 数 5.7 函数和变量的作用域 1. 一个c++项目(控制台应用程序)的结构。 主程序文件(.cpp)+头文件(.h) 第五章 函 数 5.7 函数和变量的作用域 1. 一个c++项目(控制台应用程序)的结构。 主程序文件(.cpp)+头文件(.h) 程序文件1(.cpp)+头文件(.h) 程序文件2(.cpp)+头文件(.h) 项目(.prj) …… …… 程序文件n(.cpp)+头文件(.h)
第五章 函 数 2. 几个概念 跨文件作用域:不仅在定义它的程序文件中有作用,在同一项目的其它程序文件中也有作用。又称全局作用域。 第五章 函 数 2. 几个概念 跨文件作用域:不仅在定义它的程序文件中有作用,在同一项目的其它程序文件中也有作用。又称全局作用域。 文件作用域:仅在定义它的程序文件中有作用。 局部作用域:仅在定义它的函数或块中有作用。
第五章 函 数 静态生命期:整个应用程序运行期间不会消失(死亡)。 局部生命期:在定义它的函数或块运行结束立即消失(死亡)。又称自动生命期。
第五章 函 数 3. 结论
第五章 函 数 5.7.2 变量的作用域和生存期 1. 全局变量 void fn1() 例1(无修饰的全局变量) { //ch61.prj 第五章 函 数 5.7.2 变量的作用域和生存期 1. 全局变量 void fn1() { fn2(); } //ch611.cpp extern int n; //n是另一程序文件中定义的全局变量 void fn2() n=8; 结果:8 例1(无修饰的全局变量) //ch61.prj ch61.cpp ch611.cpp //ch61.cpp #include<iostream.h> void fn1(); void fn2(); int n; //全局变量 void main() { n=3; fn1(); cout<<n<<endl; }
第五章 函 数 例2(static修饰的全局变量) //ch62.prj ch62.cpp ch621.cpp //ch62.cpp 第五章 函 数 例2(static修饰的全局变量) //ch62.prj ch62.cpp ch621.cpp //ch62.cpp #include<iostream.h> void fn(); int n; //全局变量 void main() { n=20; cout<<n<<endl; fn(); } //ch621.cpp static int n; //默认初始化为0 void fn() { n++; cout<<n<<endl; } 结果:20 1 若将static改成extern,则 21
第五章 函 数 6.局部变量 例1(无修饰的局部变量) #include<iostream.h> void main() { 第五章 函 数 6.局部变量 例1(无修饰的局部变量) #include<iostream.h> void main() { int x=5,y=1; //局部变量 cout<<"x1="<<x<<",y="<<y<<endl; { int x=3; //局部变量 cout<<"x2="<<x<<",y="<<y<<endl; } cout<<"x3="<<x<<",y="<<y<<endl; } 结果:x1=5,y=1 x2=3,y=1 x3=5,y=1
第五章 函 数 例2(无修饰的局部变量) #include<iostream.h> void main() { 第五章 函 数 例2(无修饰的局部变量) #include<iostream.h> void main() { void increament(); increament(); } void increament() { int i=0; //局部变量 i++; cout<<i<<","; } 结果:1,1,1
第五章 函 数 例3(static修饰的局部变量) #include<iostream.h> void main() { 第五章 函 数 例3(static修饰的局部变量) #include<iostream.h> void main() { void increment(); increament(); } void increment() { static int i; //static修饰的局部变量 i++; cout<<i<<x<<","; 结果:1,2,3
第五章 函 数 结果: a:0 b:-10 n:1 a:4 b:10 n:13 a:0 b:-6 n:13 a:6 b:10 n:35 第五章 函 数 例4(综合举例) #include<iostream.h> void func(); int n=1;//全局变量 void main() { static int a;//静态局部变量 int b=-10; func(); cout<<"a:"<<a<<"b:"<<b<<"n:"<<n<<endl; b+=4; cout<<"a:"<<a<<"b:"<<b<<"n:"<<n<<endl; n+=10; func(); } void func() { static int a=2; //静态局部变量 int b=5;//局部变量 a+=2; n+=12; b+=5; } 结果: a:0 b:-10 n:1 a:4 b:10 n:13 a:0 b:-6 n:13 a:6 b:10 n:35
第五章 函 数 5.7.1 函数的作用域 1.函数原型或函数定义时,若加上extern修饰或无任何修饰时,具有跨文件作用域。 第五章 函 数 5.7.1 函数的作用域 1.函数原型或函数定义时,若加上extern修饰或无任何修饰时,具有跨文件作用域。 2.函数原型或函数定义时,若用static修饰,称为静态函数,具有文件作用域。 3.函数均具有静态生存期。
第五章 函 数 例://file1.cpp void fn(); void fn1(); void main() { fn(); 第五章 函 数 例://file1.cpp void fn(); void fn1(); void main() { fn(); fn1(); } //file2.cpp #include<iostream.h> void fn(); static void fn1();//文件作用域 void fn() {……} void fn1() 说明:fn1()具有文件作用域,即只在file2.cpp中有作用,file1.cpp中调用fn1(),会产生错误。
第五章 函 数 5.8 函数模板(function template) 第五章 函 数 5.8 函数模板(function template) 1.函数模板:一系列相关函数定义的模型或样板。这些函数的代码除了因数据类型不同而有所差异外,其结构基本相同。函数模板可归入头文件。
第五章 函 数 例:对函数max(x,y) int max(int a,int b){return (a>b)?a:b;} 第五章 函 数 例:对函数max(x,y) int max(int a,int b){return (a>b)?a:b;} long max(long a,long b){return (a>b)?a:b;} float max(float a,float b){return (a>b)?a:b;} double max(double a,double b){return (a>b)?a:b;} 可定义一个样板。
第五章 函 数 2. 格式:template<模板形参表>函数定义 例:template<class T> 第五章 函 数 2. 格式:template<模板形参表>函数定义 例:template<class T> T max(T x,T y) { return (x>y)?x:y;} 3. 模板的实例化 (1) 函数调用时,编译根据实参的类型(如int)替代虚拟类型(如T),并生成相应的函数定义代码的过程。
第五章 函 数 例:cout<<max(3,5); 生成如下实例: 第五章 函 数 例:cout<<max(3,5); 生成如下实例: int max(int x,int y){return(x>y)?x:y;} 例:cout<<max(3.0,5.0); double max(double x,double y){return(x>y)?x:y;} 例:cout<<max(3,5.0);//无法实例化
第五章 函 数 (2) 解决方法 A.强制实例化:函数调用时插入模板实参表。 例: 第五章 函 数 (2) 解决方法 A.强制实例化:函数调用时插入模板实参表。 例: cout<<max<int>(3,5.0); 实例化: int max(int x,int y){return(x>y)?x:y;} cout<<max<double>(3,5.0); 实例化: double max(double x, double y){return(x>y)?x:y;}
第五章 函 数 B.模板形参表中设置多个实参。 例: template<class T1,class T2> 第五章 函 数 B.模板形参表中设置多个实参。 例: template<class T1,class T2> T1 max(T1 x,T2 y) { return x>(T1)y?x:(T1)y; }
第五章 函 数 4. 函数模板形参表中,class说明的为虚拟类型参数,亦可有一般类型修饰符(如:int,double等)说明的常规参数。调用具有常规参数的模板函数必须强制实例化。 例:使用函数模板。 //ch520.cpp #include<iostream.h> #include<string.h>
第五章 函 数 template<class Type> Type max(Type d1,Type d2) 第五章 函 数 template<class Type> Type max(Type d1,Type d2) { if(d1>d2) return d1; else return d2; } int max(int,int); char max(char,char); char* max(char*str1,char* str2);//重载模板函数 void main() { int ival1=10,ival2=4; float fval1=12.3,fval2=45.67; cout<<max(ival1,ival2)<<endl; cout<<max(fval1,fval2)<<endl; cout<<max(‘a’,’A’)<<endl; cout<<max(“hello”,”hello,world”)<<endl; char* max(char* s1,char* s2) { if(strcmp(s1,s2)>0) return s1; else return s2;