C++ primer 查漏补缺九:第六章 函数

函数的调用完成两项工作:

  1. (隐式地)定义并用实参初始化函数对应的形参
  2. 将控制权转移给被调用函数(主调函数的执行被暂时中断,被调函数开始执行)
  3. 当遇到一条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"});
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值