5、C++函数


一个复杂的问题往往可以分为若干子问题,然后对每个子问题分别解决。C++和C语言就是用函数来解决子问题的。函数写好以后,可以被重复调用,我们调用时只需要关注它的功能和使用方法,至于它时怎样实现的我们不需要关心。这样有利于代码重用,提高开发效率,便于分工开发和维护。
一个C++程序可以由一个主函数和若干子函数组成,主函数是程序执行的开始点,一般就是main函数,主函数调用子函数,子函数还可以调用其他子函数。调用其他子函数的函数称为主调函数,被其他函数调用的子函数称为被调函数。

函数的定义和调用

函数定义

函数定义的语法形式是:

类型标识符  函数名(形式参数表)
{
   语句序列
}
  • 类型标识符是函数的类型,就是常说的函数的返回值类型。函数的返回值可以返回给主调函数使用,由return语句给出,比如:return 0。没有返回值的函数的类型标识符为void,不需要写return语句。
  • 形式参数简称为形参,形参表的形式如下:type1 name1,type2 name2,…,typen namen。其中,type1,type2,…,typen是类型标识符,表示形参的类型,name1,name2,…,namen是形参名。形参的作用就是实现主调函数和被调函数之间的联系,形参一般是函数需要处理的数据、影响函数功能的因素,没有形参的函数在形参表的位置写void或者不写形参表。
  • 函数在没有被调用的时候形参只是一个符号,它只表示形参的位置应该有一个什么样的数据。函数被调用时才由主调函数将实际参数赋予形参,实际参数通常简称实参。简单说,函数声明时的参数叫形参,被调用时传入的参数叫做实参。

函数调用

函数的调用形式

函数声明:可以在主调函数中声明,也可以在所有函数之前声明,只要在调用它之前声明就可以。声明形式:

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

调用形式:

函数名(实参表)
#include <iostream>
using namespace std;
double power(double x, int n);

int _tmain(int argc, _TCHAR* argv[])
{
   cout<<"5 to the power 2 is "<<power(5,2)<<endl;
   return 0;
}

double power(double x, int n)
{
   double val=1.0;
   while (n--)
       val *= x;
   return(val);
}  
  • 若是在主调函数内部声明了被调函数原型,那么该原型只能在这个函数内部有效,也就是只能在这个函数中调用。若是在所有函数之前调用,则该函数原型在本程序文件中任何地方都有效,也就是在本程序文件中任何地方都可以按照该原型调用相应的函数。
  • 实参表中应该给出与函数原型中形参个数相同、类型相符的实参。函数调用可以作为一条语句,这时函数可以没有返回值;函数调用也可以出现在表达式中用于计算,这时就必须有返回值了。

函数调用的执行过程

C++程序经过编译以后生成可执行程序,存在硬盘中,程序启动时首先从硬盘把代码装载到内存的代码区,然后从入口地址也就是main函数的起始处开始执行。程序执行时如果遇到对其他函数的调用,则暂停当前函数的执行,保存下一条指令的地址,也就是返回地址,作为从子函数返回后继续执行的切入点,并保存变量状态等现场,然后转到子函数的入口地址执行子函数。遇到return语句或者子函数结束时则恢复先前保存的现场,并从先前保存的返回地址开始继续执行。

函数的嵌套调用

函数允许嵌套调用,如函数1调用函数2,函数2又调用了函数3就形成了函数的嵌套调用。

#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
 {
     int a,b;
     int fun1(int x,int y);  //函数声明
     cin>>a>>b;
     cout<<"a、b的平方和:"<<fun1(a,b)<<endl;
     return 0;
}

int fun1(int x,int y)
{
    int fun2(int m);  //函数声明
    return (fun2(x)+fun2(y));
}
int fun2(int m)
{
   return (m*m);
}

递归调用

递归调用就是函数直接或间接的调用自身。调用自身就是指在一个函数的函数体中出现了对自身的调用语句。直接调用自身如:void fun(void) { …; fun(); …}。间接调用自身如:void fun1(void) { …; fun2(); …} void fun2(void) { …; fun1(); …},这里fun1调用了fun2,fun2又调用了fun1,这样就构成了递归。

递归调用就是将原有的问题分解成新的问题,而解决新问题时又用到了原有问题的解法。就这样循环分解下去,直到最终分解出来的问题时一个已知解的问题,这就是有限的递归调用,也才是有意义的,无限的递归调用永远也得不到解,没有实际意义。

递归调用结合函数调用执行的过程进行理解就比较好理解。

long fac(int n)
{
  long f;
  if (n<0)
     cout<<"n<0,data error!"<<endl;
  else if (n==0)
     f=1;
  else
     f=fac(n-1)*n;
  return(f);
       }

函数的参数传递与内联函数

函数的参数传递

在声明及定义函数的时候跟的参数叫做形参,调用函数时传进去的参数称为实参。实参和形参的数据类型必须匹配。
函数没有被调用时,形参并不占用内存,只有在调用时才会分配内存空间,然后将实参传进去。函数参数的传递有两种方式,值调用和引用调用。

值调用

值调用就是调用函数时,给形参分配内存空间,将实参拷贝给形参,之后的函数执行中形参和实参就脱离了关系,谁都不影响谁。也就是值调用时,只是用实参的值初始化下形参,之后两者互不影响。

#include<iostream>
using namespace std;
void Swap(int a, int b);
int _tmain(int argc, _TCHAR* argv[])
{
   int x=5, y=10;
   cout<<"x="<<x<<"    y="<<y<<endl;
   Swap(x,y);
   cout<<"x="<<x<<"    y="<<y<<endl;
   return 0;
}
void Swap(int a, int b)
{
   int t;
   t=a;
   a=b;
   b=t;
}

从运行结果可以看出,本来我们想调用Swap函数把x和y的值交换过来,但是事与愿违。这就是因为,这是值调用的方式,x和y分别传值给a和b后,a跟b的值在Swap内怎样变化都影响不到x和y,所以两次输出x和y的值没有变化。

引用调用

引用是一种特殊类型的变量,我们可以认为它是另一个变量的别名,利用引用名和原来的变量名访问变量的效果是一样的。引用的形式是:

类型标识符 &引用名

int  i, j;
int &ri=i;    // 建立一个int型的引用ri,并将其初始化为变量i的一个别名
j=10;
ri=j;          // 相当于 i=j;
  • 声明一个引用时,必须同时对它进行初始化,使它指向一个已存在的对象;一旦一个引用被初始化后,就不能改为指向其它对象。简单说就是引用定义的时候就指定它指向的变量,之后就不能变了。
    Note:&符号在左侧为引用,在右侧则为取地址符。

引用可以作为形参,比如void swap(int& a, int& b) {…}。这个时候引用就不需要对其初始化了,因为形参只是类型说明,主调函数调用这个函数时才会为形参分配内存,也才会用实参来初始化形参。用引用调用后,形参就是实参的别名而已,对形参做的任何改变都会影响实参发生同样的改变。

#include<iostream>
using namespace std;
void Swap(int& a, int& b);
int _tmain(int argc, _TCHAR* argv[])
{
   int x=5, y=10;
   cout<<"x="<<x<<"    y="<<y<<endl;
   Swap(x,y);
   cout<<"x="<<x<<"    y="<<y<<endl;
   return 0;
}
void Swap(int& a, int& b)
{
   int t;
   t=a;
   a=b;
   b=t;
}
  • 引用调用后x和y的值成功的交换了。值调用和引用调用的区别只是函数的形参写法不同,主调函数调用被调函数时的调用语句是一样的。

带默认形参值的函数

函数在声明时可以预先定义默认的形参值。调用时若给出实参则用实参初始化形参,如果没有给出实参则采用预先定义的默认形参值。

int add(int x=5,int y=6)      // 定义默认形参值
{
  return x+y;
}
int main()
{   
   add(10,20);              // 用实参来初始化形参,实现10+20
   add(10);                   // 形参x采用实参值10,y采用默认值6,实现10+6
   add();                       // x和y都采用默认值,分别为5和6,实现5+6
   return 0;
}
int add(int x,int y=5,int z=6);   //正确
int add(int x=1,int y=5,int z);   //错误
int add(int x=1,int y,int z=6);   //错误
  • 默认参数值必须按照从右向左的顺序定义。在有默认值的形参右面,不能出现无默认值的形参。也就是说应该把有默认值的形参都一块放到右边,不能让有默认值的跟没默认值的形参穿插着放。因为在调用时,实参初始化形参是按从左向右的顺序。
  • 调用出现在函数体实现之前时,默认形参值必须在函数原型中也就是声明时给出;而当调用出现在函数体实现之后时,默认形参值需在函数实现时给出。
  • 在相同的作用域内,默认形参值的说明应保持惟一,但如果在不同的作用域内,允许说明不同的默认形参。这里的作用域是指直接包含函数原型说明的大括号所界定的范围.(区分申明和调用,是否有返回值类型)
int add(int x=1,int y=2);
int main()
{
  int add(int x=3,int y=4);
  add();//使用局部默认形参值(实现3+4)
  return 0;
}
void fun()
{   ...
  add();//使用全局默认形参值(实现1+2)
}

内联函数

函数虽然有很多优点,比如代码复用,便于维护等。但是调用函数时需要保存现场和返回地址,被调函数执行完后还要取出这些值继续执行,这些过程在时间和空间方面都有开销。对于一些规模小、功能简单的函数可以定义成内联函数。内联函数在调用时不需要那些转移带来的开销,它是在编译的时候把函数体代码嵌入到所有调用它的语句处,我们可以认为直接把函数体的代码放那里了,当然也不完全一样,毕竟它可能有参数。
内联函数定义时使用关键字inline,声明语法形式如下:

 inline  类型标识符  被调函数名(含类型说明的形参表)

调用和普通函数调用方法相同。
使用内联函数应该注意:

  1. 内联函数体内不能有循环语句和switch语句;
  2. 内联函数的定义必须出现在内联函数第一次被调用之前;
  3. 对内联函数不能进行异常接口声明,就是不能声明可能抛出的异常;
  4. 内联函数应该是语句比较少、结构比较简单的函数,不应该是复杂的函数,因为它对编译器来说就是代码,如果很复杂会造成代码膨胀,反而增大开销,这种情况下其实多数编译器就都会自动把它作为普通函数来处理。
 #include<iostream>
 using namespace std;
inline double CalArea(double radius)
{ 
    return 3.14*radius*radius;
}
int _tmain(int argc, _TCHAR* argv[])
{
    double r(3.0);
    double area;
    area=CalArea(r);
    cout<<area<<endl;
    return 0;
}

重载函数与函数模板

重载函数

重载函数就是,两个以上的函数取相同的函数名,但是函数形参的个数或者类型不同,编译器会根据实参与形参的类型和个数进行最佳匹配,自动确定调用哪一个函数。

为什么要有重载函数呢?因为如果没有重载函数,那么对不同类型的数据进行类似的操作也要定义不同名称的函数,比如加法函数,就必须对整数加法和浮点数加法分别定义不同的函数名:
int nadd(int a, int b);
float fadd(float a, float b);
这样调用需要记住的函数名太多,而且功能类似,很不方便。

  1. 形参类型不同
int   add(int x,int y);
float add(float x,float y);
  1. 形参个数不同
int add(int x,int y);
int add(int x,int y,int z);

重载函数的使用需要注意:

  1. 重载函数的形参不管是类型还是个数必须有一样是不同的。因为编译器就是看实参和哪个函数的形参的类型及个数匹配,来判断调用哪个函数,如果函数名、形参类型和个数相同,即使函数返回值类型不同,编译器也会认为是函数重复定义的语法错误,就是说它认为是一个函数。
  2. 重载函数都是进行类似的操作,不要把不同的功能定义成重载函数,否则会让人对调用有误解.

函数模板

如果功能实现完全相同,并且形参数量也相同。即用一段通用的代码适用于多种不同的数据类型,可以用函数模板来实现。
模板是有可以使用和操作任何数据类型的通用代码构成,这种程序设计叫做参数化程序设计,因为它把数据类型当成了参数,可以用来创建一个通用功能的函数,支持多种不同类型的形参和返回值。函数模板的定义形式是:

template <typename 标识符>
函数定义

使用例子:

#include<iostream>
using namespace std;

template <typename T>
T abs(T x)
{
   return x<0 ? -x:x;
}
     

int _tmain(int argc, _TCHAR* argv[])
{
    int     n = 5;
    double  d = -2.3;
    cout << abs(n) << endl;
    cout << abs(d) << endl;
    return 0;
}

模板中可以设置多个类型,同种类型必须采用相同的标识符。

标准库函数

math.h

math.h文件

输入输出流

数据从一个对象到另一个对象的流动我们一般称之为“流”,比如程序中的数据在屏幕上显示出来,我们可以想成数据从程序流向屏幕,就是输出流。从键盘输入数据就是输入流了。从流中获取数据叫做提取操作,向流中添加数据叫做插入操作。cin是系统预定义的输入流,用来处理标准输入即键盘输入。cout是预定义的输出流,用来处理标准输出,即屏幕输出。

  • “<<”是预定义的插入符,它用在cout上可以实现屏幕输出。使用形式如下:cout<<表达式<<表达式…。这里可以连着多个表达式,输出多个数据到屏幕。这里的表达式可以是很复杂的表达式,系统会计算出这些表达式的值只把结果传给插入符<<,然后显示到屏幕上。例如,cout<<“a+b=”<<a+b;会把"a+b="这个字符串和a+b的计算结果输出到屏幕。如果a=1,b=2;则屏幕上显示a+b=3。
  • “>>”是提取符,用到cin上用来把键盘上输入的数赋值给变量。使用形式为:cin>>表达式>>表达式…。这里的提取符也可以有多个,每个后边跟一个表达式,这里的表达式一般是用来存放输入值的变量。比如,int a,b; cin>>a>>b;后面这个语句要求从键盘上输入两个整型数,两个数之间用空格分隔,如果输入 3 4,则变量a的值为3,b的值为4。
  • setw(int)用来设置域宽,就是设置数值的显示位数
  • setprecision(int)用来设置浮点数的小数位数(包括小数点)
  • endl插入换行符,并刷新流。
  • 还有Dec,Hex,Oct是要求以几进制显示。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值