6.1函数基础
一个典型的函数定义包括以下几个部分:返回类型(return type)、函数名字、由0个或多个形参组成的参数列表以及函数体。其中,形参以逗号隔开,形参的列表位于一对圆括号之内。函数执行的操作在语句块中说明,该语句块称为函数体。
我们通过调用运算符(call operator)来执行函数。调用运算符的形式是一对圆括号,它作用于一个表达式,该表达式是函数或者是指向函数的指针;圆括号之内是一个用逗号隔开的实参列表,我们用实参初始化函数的形参。调用表达式的类型就是函数的返回类型。
编写函数
举个例子,我们准备编写一个求数的阶乘的程序。n的阶乘是从1到n所有数字的乘积,例如5的阶乘是120。即:1 * 2 * 3 * 4 * 5 = 120。
程序如下所示:
// val的阶乘是val * (val - 1) * (val - 2) ... * ((val - (val - 1))*1)
int fact(int val)
{
int ret = 1; // 定义一个局部变量,用于保存计算结果
while(val > 1)
ret *= val--; // 把ret和val的乘积赋值给ret,然后val减1
return ret; // 返回结果
}
函数的名字是fact,它作用于一个整型参数,返回一个整型值。在while循环内部,在每次迭代时,用后置递减运算符将val的值减1.return语句负责结束fact函数,并返回结果ret的值。
函数调用
要调用fact函数,必须提供一个int型值,调用得到的结果也是一个整数:
int main()
{
int j = fact(5); // j = 120,即fact(5)的结果。
cout << "5! is : " << j << endl;
return 0;
}
函数的调用完成两项工作:一、用实参初始化函数对应的形参;二、将控制权转移给被调函数。此时,主调函数的执行被暂时中断,被调函数开始执行。
执行函数的第一步是(隐式地)定义并初始化它的形参。因此,当调用fact函数时,首先创建一个名为val的int型变量,然后将它初始化为调用时所用的实参5。
当遇到一条return语句时函数结束执行过程。和函数的调用一样,return语句也完成两项工作:一是返回return语句中的值(如果有的话);二是将控制权从被调函数转移回主调函数。函数的返回值用于初始化调用表达式的结果,之后继续执行主调函数中调用所在表达式的剩余部分。因此,对fact函数的调用等价如下形式:
int val = 5; // (隐式地)定义并初始化形参val = 5
int ret = 1; // fact函数体内的代码
while(val > 1)
{
ret *= val--;
int j = ret; // 用被调函数的返回值ret的副本,初始化主调函数的调用表达式的结果j
}
形参和实参
实参是形参的初始值。第一个实参初始化第一个形参,第二个实参初始化第二个形参,以此类推。尽管实参和形参存在对应关系,但是并没有规定实参的求值顺序。编译器能够以任意可行的顺序对实参求值。
实参的类型必须与对应的形参类型匹配。函数有几个形参,就必须提供相同数量的实参。因为函数的调用规定实参数量应该与形参数量一致,所以形参一定会被初始化。
函数的形参列表
函数的形参列表可以为空,但是不能省略。要想定义一个不带形参的函数,最常用的办法是书写一个空的形参列表。不过为了与C语言兼容,也可以使用关键字void表示函数没有形参。
void f1 () {/*...*/} // 隐式地定义空形参列表
void f2 (void) {/*...*/} // 显示地定义空形参列表,与C语言兼容
形参列表中的形参通常用逗号隔开,其中每个形参都是含有一个声明符的声明。即使两个形参的类型一样,也必须把两个类型都写出来:
int f3 (int v1,int v2) {/*...*/}
任意两个形参都不同同名,而且函数最外层作用域中的局部变量也不能使用与函数形参一样的名字。形参名是可选的,但是由于我们无法使用未命名的形参,所以形参都应该有个名字。偶尔,函数确实有个别形参不会被用到,则此类形参通常不命名,以表示在函数体内不会用到此类形参。不管怎样,是否设置未命名的形参并不影响调用时所提供的实参数量。即使某个形参不会被函数使用,也必须为它提供一个实参。
函数返回类型
大多数类型都能用作函数的返回类型。一种特殊的类型是void,它表示函数不返回任何值。函数的返回类型不能是数组类型或函数类型,但可以使指向数组或函数的指针。