函数的调用完成两项工作:
- (隐式地)定义并用实参初始化函数对应的形参
- 将控制权转移给被调用函数(主调函数的执行被暂时中断,被调函数开始执行)
- 当遇到一条return语句时函数结束执行过程。
return语句也完成两项工作:
一是返回return语句中的值(如果有的话),
二是将控制权从被调函数转移回主调函数。函数的返回值用于初始化调用表达式的结果,之后继续完成调用所在的表达式的剩余部分。
局部静态对象
某些时候,有必要令局部变量的生命周期贯穿函数调用及之后的时间。可以将局部变量定义成static类型从而获得这样的对象。
局部静态对象(local static object)在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使
对象所在的函数结束执行也不会对它有影响。
举个例子,下面的函数统计它自己被调用了多少次,这样的函数也许没什么实际意义,但是足够说明问题:
size_t count_calls()
{
static size_t ctr =0; //结束调用后,值仍然有效
return ++ctr;
}
int main()
{
for(size_t i=0; i!=10 ; ++i)
{
cout<<count_calls()<<endl;
}
return 0;
}
在控制流第一次经过ctr的定义之前,ctr被创建并初始化为0。每次调用将ctr加1并返回新值。每次执行count calls函数时,变量ctr的值都已经存在并且等于函数上一次退出时ctr的值。
如果局部静态变量没有显式的初始值,它将执行值初始化(参见3.3.1节,第88页),内置类型的局部静态变量初始化为0。
函数声明
函数的名字也必须在使用之前声明。函数只能定义一次,但可以声明多次。函数的声明中可以省略形参的名字。
如果一个函数永远也不会被我们用到,那么它可以只有声明没有定义。虚函数除外,所有的虚函数都必须有定义。
函数的三要素(返回类型、函数名、形参类型)描述了函数的接口,说明了调用该函数所需的全部信息。
函数声明也称作函数原型(function prototype)。
参数传递
值传参数
当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。我们说这样的实参被值传递(passed by value)或者函数被传值调用(called by value)。
当初始化一个非引用类型的变量时,初始值被拷贝给变量。此时,对变量的改动不会初始值。也就是说,形参的改变不会影响实参。
传递指针:拷贝的是指针,会形成两个不同的指针。但是两个指针指向同一个地方,可以间接访问它所指的对象。
传引用参数
当形参是引用类型时,我们说它对应的实参被引用传递(passed by reference)或者函数被传引用调用(called by reference)。和其他引用一样,引用形参也是它绑定的对象的别名:也就是说,引用形参是它对应的实参的别名。
对引用的操作实际上是作用在引用所指的对象上
使用引用避免拷贝
拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括IO类型在内)根本就不支持拷贝操作。当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。
使用引用形参返回额外信息
一个函数只能返回一个值,然而有时函数需要同时返回多个值,引用形参为我们一次返回多个结果提供了有效的途径。
const形参和实参
补充说明:
顶层const:表示对象本身是一个常量,比如常量指针
底层const:表示指针所指向的对象是一个常量
int i=0;
int *const pl =&i;// 顶层const 不能改变pl的值
const int ci =42; // 顶层const 不能改变ci的值
const int *p2 = &ci ; //允许改变p2的值,底层const
*const 常量指针
const * 指向常量
数组形参
数组的两个特殊性质对我们定义和使用作用在数组上的函数有影响,这两个性质分别是:不允许拷贝数组(参见3.5.1节,第102页)以及使用数组时(通常)会将其转换成指针(参见3.5.3节,第105页)。因为不能拷贝数组,所以我们无法以值传递的方式使用数组参数。因为数组会被转换成指针,所以当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。尽管不能以值传递的方式传递数组,但是我们可以把形参写成类似数组的形式:以下三个等价
void print(const int*);
void print(const int[]);
void print(const int[10]);//10只代表期望有10个数,但实际不一定
函数并不知道数组实际的大小,所以要注意数组越界。有以下三种改进方法
使用标记指定数组长度
要求数组本身含有结束标记,比如C字符串
void print(const char*cp)
{
if(cp) //如果cp不是空指针
{
while(*cp) //不是空字符
cout<<*cp++;
}
}
使用标准库规范
显示传递数组大小
void print(const char*cp ,size_t size)
特殊数组
可变形参
initializer_list
如果函数的实参数量未知但是全部实参的类型都相同,我们可以使用 initializer_list 类型的形参。initializer_list是一种标准库类型,用于表示某种特定类型的值的数组(参见3.5节,第101页)。initializer_list类型定义在同名的头文件中,它提供的操作如表所示。
template<class T> class initializer_list;
initializer_list<T> lst;
//默认初始化;T类型元素的空列表
initializer_list<T> lst{a,b,c...};
//lst的元素数量和初始值一样多;lst的元素是对应初始值的副本
lst2(lst)
lst2=lst
//拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本元素共享
lst.size() //列表中的元素数量
lst.begin() //返回指向lst中首元素的指针
lst.end() //返回指向lst中尾元素下一位置的指针
使用方法
//函数定义
void error_msg(initializer_list<string> il)
{
for(auto beg=il.begin();beg!=il.end();++beg)
cout<<*beg<<" ";
cout<<endl;
}
//函数调用
//expected和actual是string对象
if(expected != actual)
error_msg({"functionX",expectde,actual});
else
error_msg({"functionX","okay"});