C++函数的定义与使用

函数的定义和使用

main就是一个函数,它是C++程序的主函数。一个C++程序可以由一个主函数和若干子函数组成。主函数是程序执行的开始点。由主函数调用子函数,子函数还可以再调用其它子函数。

调用其它函数的函数称为主调函数。被其他函数调用的函数称为被调函数。一个函数很可能既调用别的函数又被其它函数调用。

1.函数的定义

1.1函数定义的语法形式

类型说明符   函数名(含类型说明的形式参数表)

{

   语句序列

}

1.2形式参数

类型标识符1   形参名1,类型标识符2   形参名2,···,类型标识符n   形参名n

形参的作用是实现主调函数与被调函数之间的联系。通常将函数所处理的数据、影响函数功能的因素或者函数的处理结果作为形参。

如果一个函数的形参表为空,则表示它没有任何形参。main函数可以没有形参,也可以有形参,其形参也称命令行参数,由操作系统在启动程序时初始化。

函数在没有被调用时是静止的,此时的形参只是一个符号,它标志着在形参出现的位置应该有一个什么类型的数据。

函数在被调用时才执行,也就是在被调用时才由主调函数将实际参数赋予形参。

1.3函数的返回值和返回值类型

函数可以有一个返回值,函数的返回值是需要返回给主调函数的处理结果。类型说明符规定了函数返回值的类型,函数的返回值由return语句给出,格式如下:

return 表达式;

除了指定函数的返回值外,return语句还有一个作用,就是结束当前函数的执行

一个函数也可以不讲任何值返回给主调函数,这时它的类型标识符为void,可以不写return语句,但也可以写一个不带表达式的return语句,用于结束当前函数的调用,格式如下:

return;

2.函数的调用

2.1函数的调用形式

变量在使用之前需要首先声明,类似的,函数在调用之前也需要声明。函数的定义就属于函数的声明,因此,在定义了一个函数之后,可以直接调用这个函数。但如果希望在定义一个函数之前调用它,则需要在调用函数之前添加该函数的函数原型声明。函数原型声明的形式如下:

类型说明符   函数名(含类型说明的形参表);

与变量的声明和定义类似,声明一个函数只是将函数的有关信息告诉编译器,此时并不产生任何代码;定义一个函数是除了同样要给出函数的有关信息外,主要是要写出函数的代码。

声明了函数原型之后,便可以按如下形式调用子函数:

函数名(实参列表);

实参列表应该给出与函数原型形参个数相同、类型相符的实参,每个实参都是一个表达式。函数调用可以作为一条语句,这时函数可以没有返回值。函数调用也可以出现在表达式中,这时就必须有一个明确的返回值。

 

例1   编写一个求x的n次方的函数

#include<iostream>
using namespace std;
double power(double x,int n)
{
double val=1.0;
while(n--)
val*=x;
return val;
}
int main()
{
cout<<"5 to the power 2 is"<<power(5,2)<<endl;
return 0;


例2   输入一个8位二进制数,将其转换为十进制数输出

#include<iostream>
using namespace std;
double power(double x,int n);
int main(){
int value=0;
cout<<"Enter an 8 bit binary number: ";
for(int i=7;i>=0;i--)
{
char ch;
cin>>ch;
if(ch=='1')
value+=static_cast<int>(power(2,i));
}
cout<<"Decimal value is"<<value<<endl;
return 0;
}
double power(double x,int n)
{
double val=1.0;
while(n--)
val*=x;
return val;

}

注意:ch的类型写成int是不正确的


例3 编写程序求π的值,公式如下:

π=16arctan(1/5)-4arctan(1/239)

其中arctan用如下形式的级数计算:

   arctanx=x-x^3/3+x^5/5-x^7/7+···

直到级数某项绝对值不大于10^-15为止;π和x均为double型。

#include<iostream>
using namespace std;
double arctan(double x)
{
double sqr=x*x;
double e=x;
double r=0;
int i=1;
while(e/i>1e-15)//1e-15是科学记数法的1*10的-15次方
{
double f=e/i;
r=(i%4==1)?r+f:r-f;
e=e*sqr;
i+=2;
}
return r;
}
int main()
{
double a=16.0*arctan(1/5.0);
double b=4.0*arctan(1/239.0);
cout<<"PI="<<a-b<<endl;
return 0;
}


例4  寻找并输出11~999之间的数m,它满足m,m^2,m^3均为回文数

所谓回文数是指其各位数字左右对称的整数。例如,121,676,94249等。满足上述条件的数如m=11,m^2=121,m^3=1331.

分析:判断一个数是否是回文数,可以用除以10求余的方法,从最低位开始,依次取出该数的各位数字,然后用最低位充当最高位,按反序重新构成新的数,与原数比较是否相等,若相等,则原数为回文。

#include<iostream>
using namespace std;
//判断n是否为回文数
bool symm(unsigned n)
{
unsigned i=n;
unsigned m=0;
while(i>0)
{
m=m*10+i%10;
i/=10;
}
return m==n;
}

int main()
{
for(unsigned m=11;m<=999;m++)
{
if(symm(m)&&symm(m*m)&&symm(m*m*m))
{
cout<<"m="<<m;
cout<<"   m*m="<<m*m;
cout<<"   m*m*m="<<m*m*m<<endl;
}
}
return 0;
}

 

例5 投骰子的随机游戏

 游戏规则是:每个骰子有六面,点数分别为1,2,3,4,5,6。游戏者在程序开始时输入一个无符号整数,作为产生随机数的种子。每轮投两次骰子,第一轮如果和数为7或者11则为胜,游戏结束;和数为2,3或者12则为负,游戏结束;和数为其他值则将此值作为自己的点数,就像第二轮、第三轮···直到某轮的和数等于点数则取胜,若在此前出现和数为7则为负。

有rollDice函数负责模拟投骰子,计算和数并输出和数。

提示  系统函数int rand(void)的功能是产生一个伪随机数,伪随机数并不是真正随机的。这个函数自己不能产生真正的随机数。如果在程序中连续调用rand,期望由此可以产生一个随机数序列,你会发现每次运行这个程序时产生的序列都是相同的,这称为伪随机数序列。这是因为函数rand需要一个称为”种子“的初始值,种子不同,产生的伪随机数也就不同。因此只要每次运行时给予不同的种子,然后连续调用rand便可以产生不同的随机数序列。如果不设置种子,rand总是默认种子为1。不过设置种子的方法比较特殊,不是通过函数的参数,而是在调用它之前,需要首先调用另外一个函数void srand(unsigned int seed)为其设置种子,其中的参数seed便是种子。

#include<iostream>
#include<cstdlib>
using namespace std;


int rollDice(){
int die1=1+rand()%6;
int die2=1+rand()%6;
int sum=die1+die2;
cout<<"Player rolled"<<die1<<"+"<<die2<<"="<<sum<<endl;
return sum;
}
enum GameStatus{WIN,LOSE,PLAYING};
int main()
{
int sum,myPoint;
    GameStatus status;


unsigned seed;
cout<<"Please enter an unsigned integer:";
cin>>seed;
srand(seed);


sum=rollDice();
switch(sum)
{
case 7:
case 11:
status=WIN;
break;
case 2:
case 3:
case 12:
status=LOSE;
break;
default:
status=PLAYING;
myPoint=sum;
cout<<"point is"<<myPoint<<endl;
break;
}
while(status==PLAYING)
{
sum=rollDice();
if(sum==myPoint)
status=WIN;
else if(sum==7)
status=LOSE;


}
if(status==WIN)
cout<<"player wins"<<endl;
else
cout<<"player loses"<<endl;
return 0;
}

2.2嵌套调用

函数允许嵌套调用。如果函数1调用了函数2,函数2再调用函数3,便形成了函数的嵌套调用

例 输入两个整数,求他们的平方和。

分析:设计两个函数:求平方和函数fun1和求一个整数的平方函数fun2。由主函数调用fun1,fun1又调用fun2。

#include<iostream>
using namespace std;
int fun2(int m)
{
return m*m;
}
int fun1(int x,int y)
{
return fun2(x)+fun2(y);
}
int main()
{
int a,b;
cout<<"Please enter two integers(a and b):";
cin>>a>>b;
cout<<"The sum of square of a and b:"<<fun1(a,b)<<endl;
return 0;

}

2.3递归调用

函数可以直接或间接地调用自身,称为递归调用。

所谓直接调用自身,就是指在一个函数的函数体中出现了对自身的调用表达式,例如:

void fun1()

{

···

fun1();     //调用fun1()自身

···

   

}

这就是函数直接调用自身的例子。

而下面的情况是函数间接调用自身:

void fun1()

{

···

fun2();

···

}

void fun2()

{

···

fun1();

···

}

这里fun1调用了fun2,而fun2又调用了fun1,于是构成了递归。

递归算法的实质是将原有的问题分解成新的问题,而解决新问题时又用到了原有问题的解法。按照这一原则分解下去,每次出现的新问题都是原有问题的简化的子集,而最终分解出来的问题,是一个已知解的问题。这便是有限的递归调用。只有有限的递归调用才是有意义的,无限的递归调用永远得不到解,没有实际意义。

递归的过程有如下两个阶段。

第一阶段:递推。将原问题不断分解为新的子问题,逐渐从未知向已知推进,最终达到已知的条件,即递归结束的条件,这时递推阶段结束。

例如,求5!,可以这样分解:

5!=5*4!—>4!=4*3!—>3!=3*2!—>2!=2*1!—>1!=1*0!—>0!=1

未知----------------------------------------------------------->已知

第二阶段:回归。从已知的条件出发,按照递推的逆过程,逐一求值回归,最后达到递推的开始处,结束回归阶段,完成递归调用。

例如,求5!的回归阶段如下:

5!=5*4!<—4!=4*3!<—3!=3*2!<—2!=2*1!<—1!=1*0!<—0!=1

未知<-----------------------------------------------------------已知

例1 求n!

分析:当n=0时,n!=1;

          当n>0时,n!=n*(n-1)!

递归结束的条件是n=0。

#include<iostream>
using namespace std;
unsigned fac(unsigned n)
{
unsigned f;
if(n==0)
f=1;
else
f=fac(n-1)*n;
return f;
}
int main()
{
unsigned n;
cout<<"Enter a positive integer:";
cin>>n;
unsigned y=fac(n);
cout<<n<<"!="<<y<<endl;
return 0;

}

注意:对同一个函数的多次不同调用中,编译器会为函数的形参和局部变量分配不同的空间,他们互不影响。

例2 用递归法计算从n个人中选择k个人组成一个委员会的不同组合数。

分析:由n个人里选k个人的组合数=由n-1个人里选k个人的组合数+由n-1个人里选k-1个人的组合数

由于计算公式本身是递归的,因此可以编写一个递归函数来完成这一功能,递推的结束条件是n=k或n=0,这时的组合数是1,然后开始回归。

#include<iostream>
using namespace std;
int comm(int n,int k)
{
if(n<k)
return 0;
else if(n==k||k==0)
return 1;
else
return comm(n-1,k)+comm(n-1,k-1);
}
int main()
{
int n,k;
cout<<"Please enter two integers n and k:";
cin>>n>>k;
cout<<"C(n,k)="<<comm(n,k)<<endl;
return 0;

}

例3 汉诺塔问题

有三根针A,B,C。A针上有n个盘子,盘子大小不等,大的在下,小的在上。要求把这n个盘子从A针移动到C针,在移动的过程中可以借助B针,每次只允许移动一个盘子,且在移动过程中在三针上都保持大盘在下,小盘在上。

分析:将n个盘子从A针移动到C针可以分解为下面三个步骤:

(1)将A上n-1个盘子移动到B针(借助C针);

(2)将A针上剩下的一个盘子移到C针上;

(3)将n-1个盘子从B针移到C针(借助A针)。

事实上,上面三个步骤包含下面两种操作:

(1)将多个盘子从一个针移动到另一个针上,这是一个递归的过程。

(2)将一个盘子从一个针移到另一个针上。

#include<iostream>
using namespace std;
void move(char src,char dest)
{
cout<<src<<"-->"<<dest<<endl;
}
void hanoi(int n,char src,char medium,char dest)
{
if(n==1)
move(src,dest);
else
{
hanoi(n-1,src,dest,medium);
move(src,dest);
hanoi(n-1,medium,src,dest);
}
}
int main()
{
int m;
cout<<"Enter the number of disks:";
cin>>m;
cout<<"the steps to moving"<<m<<"disks:"<<endl;
hanoi(m,'A','B','C');
return 0;

}

3.函数的参数传递

在函数未被调用时,函数的形参并不占有实际的内存空间,也没有实际的值。只有在函数被调用时才为形参分配存储单元,并将实参与形参结合。每个实参都是一个表达式,其类型必须与形参相符。函数的参数传递指的就是形参与实参结合(简称形实结合)的过程,形实结合的方式有值传递和引用传递。

3.1值传递

值传递是指当发生函数调用时,给形参分配内存空间,并用实参来初始化形参(直接将实参的值传递给形参)。这一过程是参数值的单项传递过程,一旦形参获得了值便与实参脱离关系,此后无论形参发生了怎样的改变,都不会影响到实参。

例1

将两个整数变换次序后输出

#include<iostream>
using namespace std;
void swap(int a,int b)
{
int t=a;
a=b;
b=t;
}
int main()
{
int x=5,y=10;
cout<<"x="<<x<<"   y="<<y<<endl;
swap(x,y);
cout<<"x="<<x<<"   y="<<y<<endl;
return 0;

}

分析:从上面的运行结果可以看出,并没有达到交换的目的。这是因为,采用的是值传递,函数调用时传递的是实参的值,是单向传递过程。形参值的改变对实参不起作用。

2.引用传递

值传递时参数是单向传递。

引用是一种特殊类型的变量,可以被认为是另一个变量的别名,通过引用名与通过被引用的变量名访问变量的效果是一样的。例如:

int i,j;

int &ri=i;  //建立一个int型的引用ri,并将其初始化为变量i的一个别名

j=10;

ri=j;         //相当于i=j

使用引用时必须注意以下问题:

·声明一个引用时,必须同时对它进行初始化,使它指向一个已存在的对象。44

·一旦一个引用被初始化后,就不能改为指向其它对象。

也就是说,一个引用,从它诞生时,就必须确定是哪个变量的别名,而且始终只能作为作为这一个变量的别名,不能另作他用。

引用也可以作为形参,如果将引用作为形参,情况便稍有不同。这是因为,形参的初始化不在类型说明时进行,而是在执行主调函数中的调用表达式时,才为形参分配内存空间,同时用实参初始化形参。这样引用类型的形参就通过形实结合,成为了实参的一个别名,对形参的任何操作也就会直接作用于实参。

用引用作为形参,在函数调用时发生的参数传递,称为引用传递。

例2 使用引用传递改写例1,使两整数成功的进行交换

#include<iostream>
using namespace std;
void swap(int &a,int &b)
{
int t=a;
a=b;
b=t;
}
int main()
{
int x=5,y=10;
cout<<"x="<<x<<"   y="<<y<<endl;
swap(x,y);
cout<<"x="<<x<<"   y="<<y<<endl;
return 0;

}

例3

值传递与引用传递的比较

#include<iostream>
#include<iomanip>
using namespace std;


void fiddle(int in1,int &in2)
{
in1=in1+100;
in2=in2+100;
cout<<"The values are";
cout<<setw(5)<<in1;
cout<<setw(5)<<in2<<endl;
}
int main()
{
int v1=7,v2=12;
cout<<"The values are";
cout<<setw(5)<<v1;
cout<<setw(5)<<v2<<endl;
fiddle(v1,v2);
cout<<"The values are";
cout<<setw(5)<<v1;
cout<<setw(5)<<v2<<endl;
return 0;
}










©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值