1.6 函数
一、初始函数
1.概念
- 实现某一功能的相关语句组织在一起,并给之注明相应的名称,这种形式的组合就称为函数。
2.功能:C++程序的基本模块,函数组成C++程序
- 至少含有一个main函数
- main是程序运行的入口
3.分类
-
是否为系统预定义
- 库函数用户:系统预定义函数
- 自定义函数
-
是否有参
- 有参函数
- 无参函数
4.函数的定义
-
组成部分:函数首部+函数体
-
定义的规则:
-
函数返回值类型 函数名称(函数参数1类型 参数名1,函数参数2类型 参数名2,…)
{
可执行语句
}
- 函数返回值类型:函数运行后能得到的结果的数据的类型,如果没有结果,只是一系列的操作,则其返回值类型为void。(其实就是函数体内“return xxx”的xxx的类型,如果没有return,则返回值为void。)
- 函数的参数:若无参数,可不写。
- 函数体:实现函数功能的一系列的语句。
- 返回值和参数都可省,但函数体不可省!
-
二、函数原型的声明
C++规定,如果函数调用在函数定义之前,要求在调用之前声明该函数的原型。
-
格式:函数类型 函数名(形参表);
-
形参表中的形参可以只写类型
-
例:int max(int x , int y );
或 int max(int , int );
-
-
C++编译时,只检查参数的数据类型
-
-
函数原型的声明
- 函数原型是一条程序语句,它必须以“ ;”分号结束;
- 由函数返回类型、函数名和形参表构成
- 函数原型和函数定义在返回类型、函数名和形参表上必须完全一致。(除了只写类型)
三、函数的调用
1.直接调用
- 适用于函数的返回值为空(返回值:void)或返回值不需要被保存的情况
2.表达式调用
- 将函数调用放到一个表达式中,让其参与运算
- 一般用在赋值表达式中
- 例:y = sin(x);
3.嵌套调用
- 一个函数调用的结果作为另一个函数调用的参数
- 例:m = max (a,max(b,c));
4.参数传值
- 传值
- 传引用
- 传地址
5.C++的函数调用过程就是栈空间操作的过程
-
建立被调函数的栈空间
-
保护调用函数的运行状态和返回地址
-
传递参数
-
将控制权转交给被调函数
-
函数运行完成之后,复制返回值到函数数据块的底部
-
恢复调用函数的运行状态
-
返回到调用函数
分析程序的执行过程
int funcA(int x, int y); int main( ) { int a=6, b=12; a=funcA(a, b); } int funcA(int x, int y) { int n=5; n=x+y; return n; }
- 运行程序,执行main函数
- 调用函数funcA(a,b)
- 调用函数funcA(a,b)
执行n = x + y;

-
调用函数funcA(a,b)
执行return n ;
-
返回到main函数中
a = funA (a,b);
四、内置函数
C++提供了一种机制,在编译时,将所调用的函数的代码嵌入到调用函数代码中,再执行函数时省去了调用环节,提高了函数的执行速度。这种机制称为内置(内联)函数。
-
格式:
inline 函数类型 函数名(形参表)
{
函数体
}
inline是C++的关键字,在编译时,编译程序会把这个函数嵌入到调用函数的函数体中。
-
调用格式
函数名(实参表)
实例1 计算三个整数中的大数
#include <iostream>
using namespace std;
inline int max(int a,int b,int c) // 这是一个内置函数,求3个整数中的最大者
{
if (b>a) a=b;
if (c>a) a=c;
return a;
}
int main( )
{
int i=7,j=10,k=25,m;
m=max(i,j,k);
cout<<"max="<<m<<endl;
return 0;
}
实例1说明:
由于在定义函数时指定它是内置函数,因此编译系统在遇到函数调用max( i,j,k ) 时就用max函数体的代码代替max( i,j,k ) ,同时用实参代替形参。调用语句m= max( i,j,k ) 就被置换成:
{
a=i;
b = j;
c= k;
if ( b>a) a=b;
if ( c>a) a=c;
m=a;
}
实例2 用内置函数计算平方根
#include <iostream>
using namespace std;
inline int power(int x) //定义内置函数
{
return x*x;
}
int main()
{
cout<<power(2)<<endl;
cout<<power(1+1)<<endl;
return 0;
}
实例2说明:
编译程序遇见内置函数power时,先求出函数的实参值(1+1=2),然后用power函数体代替函数调用,调用语句变成:
{ cout<<2*2<<endl;
cout<<2*2<<endl;
}
运行结果是
4
4
提出思考:
若把代码改为
#define power(a) a*a
cout<<power(1+1)<<endl;
运行结果是????
- 总结:使用内置函数可以节省程序的运行时间,一个函数定义为内置函数后,在程序编译阶段,编译器就会把每次调用该函数的地方都直接替换为该函数体中的代码,由此省去函数调用的空间分配、保存现场、参数传递和返回操作等,从而加快整个程序的执行速度。但增加了目标程序的长度。所以在使用时要衡量时间和空间的得失。
五、函数的重载
C++允许在同一个域中用同一个函数名定义多个函数,这些函数的参数个数、参数类型、参数顺序不相同。功能相似而参数不同的函数可以使用相同的函数名,这就是函数重载。
1.对于“参数不同”的说明:
- 参数类型不同
- void sort(int *arr , int n );
- void sort(float *arr , int n );
- 参数个数不同
- int max (int a , int b );
- int max (int a, int b ,int c );
- 参数顺序不同
- void find(float *arr,int n , float x);
- void find(float *arr,float x , int n );
(参数不同是指满足以上三个条件至少一个即可!)
2.实例说明
-
实例1:设计程序计算三个数中的大数
#include <iostream> using namespace std; int max(int a,int b,int c) //求3个整数中的最大者 { if (b>a) a=b; if (c>a) a=c; return a; } float max(float a,float b, float c) //求3个实数中的最大者 { if (b>a) a=b; if (c>a) a=c; return a; } long max(long a,long b,long c) //求3个长整数中的最大者 { if (b>a) a=b; if (c>a) a=c; return a; } int main( ) { int a,b,c; float d,e,f; long g,h,i; cin>>a>>b>>c; cin>>d>>e>>f; cin>>g>>h>>i; int m; m= max(a,b,c); //函数值为整型 cout <<"max_i="<<m<<endl; float n; n=max(d,e,f); //函数值为实型 cout<<"max_f="<<n<<endl; long int p; p=max(g,h,i); //函数值为长整型 cout<<"max_l="<<p<<endl; return 0; }
-
实例2:用一个函数名求两个整数或三个整数中的最大数。
#include <iostream> using namespace std; int max(int a,int b,int c) //求3个整数中的最大者 { if (b>a) a=b; if (c>a) a=c; return a; } int max(int a, int b) //求两个整数中的最大者 { if (a>b) return a; else return b; } int main( ) { int a=7,b=-4,c=9; cout<<max(a,b,c)<<endl; //输出3个整数中的最大者 cout<<max(a,b)<<endl; //输出两个整数中的最大者 return 0; }
注意:不允许函数参数个数、参数类型、参数顺序都相同,只是函数返回值不同。因为系统无法从调用形式上判断调用与哪个函数相匹配。
-
实例3:返回一个数的绝对值
#include<iostream> using namespace std; int ABS(int); double ABS(double); float ABS(float); int main() { int x1=-12; double x2=-12.0; float x3=-12.0; cout<<ABS(x1)<<endl; cout<<ABS(x2)<<endl; cout<<ABS(x3)<<endl; } int ABS(int a) { return (a>0?a:-a); } double ABS(double a) { return (a>0?a:-a); } float ABS(float a) { return (a>0?a:-a); }
从以上实例中我们可以总结出以下结论:
1.只要参数个数不同,参数类型不同,参数顺序不同,函数就可以重载。然而,只是返回类型不同则不允许重载。
-
void func(int a); √
-
void func(char a); √
-
void func(char a, int b); √
-
void func(int a, char b); √
-
char func(int a); ×
2.C++按下列三个步骤的先后顺序找到匹配的函数并调用之。
①寻找一个严格匹配,如果找到了,就调用那个函数。
②通过相容类型的隐式转换寻求一个匹配。
③通过用户定义的转换寻求一个匹配,若能查出唯一的一组 转换,就用那个函数。
-
六、有默认参数的函数
C++允许为函数的参数设置默认值,这时调用函数时,如果没有实参,就以默认值作为实参值。
-
格式:
形参类型 形参变量名 = 常数
-
功能:
调用函数时,如果没有实参,就以常数作为该形参的值;如果有实参,仍以实参的值作为该形参的值。
-
注意:
1.有默认值的形参必须放在形参表的右边,不允许无默认参数值和有默认参数值的形参交错排列。
2.如果用函数原型声明,只要在函数原型声明中定义形参的默认值即可。
3.一个函数名不能同时用于重载函数和带默认形参值的函数。当调用函数时,如少写一个参数,系统无法判断是利用重载函数还是利用带默认参数值的函数,出现二义性。
int max (int a, int b);
int max (int a, int b, int c = 100);
此时 max是重载函数,又带默认参数值,如果出现max( 5, 23)形式的调用,编译系统无法断定调用哪个函数,于是发出编译出错的信息。
4.参数使用默认值时,对应的实参可以省略。
例:
#include<iostream> using namespace std; int func(int a=2); int main() { cout<<func(5)<<endl; cout<<func()<<endl; } int func(int a) { return a; }
5.默认参数规则:
①一般来说,默认参数总是在函数声明时描述。
②函数参数默认值只能从后往前设置,而实参只能从前往后给出。
例:
-
void func(int a=1, int b, int c=3); ×
-
void func(int a, int b=2, int c=3); √
-
func(10,15,20); √
-
func( ); ×
-
func(12,13); √
-
func(2, , 20); ×
-
-
实例:编写计算圆柱体体积函数
float volume ( float h, float r = 12.5)
调用可以采用以下任何一种形式:
volume( 45.6);
volume( 32.5, 10.5);
函数参数结合从左到右,用第一种方式调用时,只有一个实参,圆半径的值取默认值12.5
用第二种方式调用时,有两个实参,圆半径的值取实参的值10.5。
七、递归函数
1.定义:在函数体中出现调用自身语句的函数称为递归函数。
2.举例:阶乘n!的数学函数描述为
其等价的C++函数为:
unsigned int f(unsigned int n)
{
if(n==0||n==1)
return n;
return f(n-1)+f(n-2);
}
n的取值范围1≤n≤46。
3.说明:
递归函数在运行中,其调用与被调函数的指令代码是同一个函数副本,只不过各个不同运行中的调用点,作为状态的一部分,在栈中被分别保护起来。因此,是C++的函数机制决定了递归操作的可能性与形式。
4.实例:
n!的函数,当调用f(3)时,其运行栈描述如图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aZ0Gwrqf-1589434710746)(…/images/image-20200514133450990.png)]
unsigned f(unsigned n)
{
if(n==1)
{
return 1;
}else{
return n*f(n-1);
}
}
5.递归条件:
①递归不能无限制地调用下去,因为栈空间是有限的,所以递归函数是有条件地调用自身。
例如:阶乘函数中的 “if(n==1) return 1;”
当n为1时,函数就不再递归了。
②递归函数就有递归调用语句,且递归调用应有参数,参数值应该是逐渐逼近停止条件。
例如:f(n-1)相对f(n)来说,是逐渐逼近了停止条件。
③递归条件应先测试,后递归调用无条件递归的逻辑错误,编译器是检查不出来的。