第六章:函数

6.1    函数基础

1、一个典型的函数(function)定义包括以下部分:返回类型(return type)、函数名字、由0个或多个形参(parameter)组成的列表以及函数体。我们通过调用运算符来执行函数。

2、实参是形参的初始值,实参与形参存在对应关系,但是并没有规定实参的求值顺序,编译器能以任意可行的顺序对实参求值。

6.1.1    局部对象

1、在C++语言中,名字有作用域,对象有生命周期(lifetime)。理解这两个概念非常重要:名字的作用域是程序文本的一部分,名字在其中可见;对象的生命周期是程序执行过程中该对象存在的一段时间。

2、在所有函数体之外定义的对象存在于程序的整个执行过程中。此类对象在程序启动时被创建,直到程序结束才会销毁。(全局变量)

3、局部变量的生命周期依赖于定义的方式:我们把只存在于块执行期间的对象成为自动对象,当块的执行结束后,块中创建的自动对象的值就变成未定义的了;局部静态对象在程序的执行路径第一次经过对象定义时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。但是,局部静态对象的作用域仍然是局部的,只是生命周期与全局变量相同而已。局部静态对象的定义方式为类型名前加static,如果没有显式的初始值,它将执行值初始化,内置类型的局部静态变量初始化为0。

6.1.2--6.1.3    函数声明,分离式编译

1、函数应该在头文件中声明,而在源文件中定义,定义函数的源文件应该把含有函数声明的头文件包含进来,编译器负责验证函数的定义和声明是否匹配。

2、编译和链接多个头文件(P187,第一次标记红色,红色代表并没有看懂......以后标红也是一个意思)

6.2    参数传递

6.2.1--6.2.2    传值参数,传引用参数

1、 指针形参值传递时,拷贝之后,两个指针是不同的指针,但指针所指的对象是同一个,所以虽然不能改变指针本身,但可以改变它所指对象的值。

2、使用引用参数的好处:避免拷贝,可以使函数返回额外信息。

6.2.3    const形参与实参

1、当形参是顶层const,必须要注意之前关于顶层const的讨论。例如,当形参有顶层const时,传给它常量对象或者非常量对象都是可以的;定义两个名字相同的函数,若形参列表的区别仅仅在于一个包含顶层const,一个不包含,则会引发错误,等等。

2、指针或引用形参含有const时,要注意初始化的规则。我们可以使用非常量初始化一个底层const对象,但是反过来不行;同时一个普通的引用必须用同类型的对象初始化。

3、把函数不会改变的形参定义成普通的引用是一种比较常见的错误,这么做带给函数的调用者一种误导,即函数可以修改它的实参的值。此外,使用普通引用而非常量引用也会极大地限制函数所能接受的实参类型。因此,尽量使用常量引用。

6.2.4    数组形参

1、数组的两个特殊性质对我们定义和使用作用在数组上的函数有影响,这两个性质分别是:不允许拷贝数组以及使用数组时通常会将其转换成指针。

2、管理数组形参的长度有三种方式:使用(结束)标记、使用标准库规范以及显示传递数组大小。

3、数组引用形参,可以把形参绑到数组上,可这样做也限制了函数,它只能作用于固定大小的数组。

4、传递多维数组时,实际上传递的是指向数组的指针,因此多维数组的第二维(以及后面所有维度)的大小都是数组类型的一部分,不能省略。

6.2.5--6.2.6    main:处理命令行选项,含有可变形参的函数

6.3    返回类型和return语句

6.3.1--6.3.2    无返回值函数,有返回值函数

1、返回一个值的方式和初始化一个变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。

2、不要返回局部对象的引用或指针,因为函数终止意味着局部变量的引用或指针将指向不再有效的内存区域。

3、调用一个返回引用的函数得到左值,其他返回类型得到右值。可以像使用其他左值那样来使用返回引用的函数的调用,特别是,我们能为返回类型是非常量引用的函数的结果赋值。

4、C++11新标准规定,函数可以返回花括号包围的值的列表,该列表也用来对表示函数返回的临时量进行初始化。

5、main函数不能调用它自己。

6.3.3    返回数组指针

因为数组不能被拷贝,所以函数不能返回数组。不过,函数可以返回数组的指针或引用。方法有下面几种:

1、暴力声明:int (*func(int i))[20]; 注意外围的括号必须存在。

2、使用类型别名:typedef,using

3、使用尾置返回类型:auto func(int i) -> int(*)[20]

4、使用decltype:需要注意的是,decltype作用于数组时,结果为数组类型,因此需要显式添加*表明我们返回的是指针。

6.4    函数重载

1、main函数不能重载。

2、不允许两个函数除了返回类型外其他所有的要素都相同。

3、顶层const不影响传入函数的对象,一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。

4、如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载(底层const)。

5、const_cast在重载函数的情景中最有用。(P209)

6.4.1    重载与作用域

1、如果我们在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体。在不同的作用域中无法重载函数名。(P210)

2、在局部作用域中声明函数不是一个好的选择。

3、在C++语言中,名字查找发生在类型检查之前。

6.5    特殊用途语言特性

6.5.1    默认实参

1、可以在定义函数时给形参一个默认值,这样调用函数时,有默认实参的参数可以不提供实参,此时将用默认实参初始化形参。当然如果不想使用默认值,只需提供实参即可。

2、一旦某个形参被赋予了默认值,它后面所有形参都必须有默认值。

3、对于函数的声明来说,通常的习惯是将其放在头文件中,并且一个函数只声明一次,但是多次声明同一个函数也是合法的。不过有一点需要注意,在给定的作用域中一个形参只能被赋予一次默认实参。换句话说,函数的后续声明只能为之前那些没有默认值的形参提供默认实参,而且该形参右侧的所有形参都必须有默认值。

6.5.2--6.5.3    内联函数和constexpr函数,调试帮助

6.6    函数匹配

1、候选函数具备两个特征:一是与被调用的函数同名,二是其声明在调用点可见。

2、可行函数具备两个特征:一是其形参数量与本次调用提供的实参数量相等,二是每个实参的类型与对应的形参类型相同,或者能转换成形参的类型。如果没找到可行函数,编译器将报告无匹配函数的错误。

3、寻找最佳匹配:如果最终有多个调用是“最佳匹配”,则编译器最终将因为这个调用具有二义性而拒绝其请求。“最佳匹配”的条件:(1)该函数每个实参的匹配都不劣于其他可行函数需要的匹配;(2)至少有一个实参的匹配优于其他可行函数提供的匹配。

4、调用重载函数时应尽量避免强制类型转换。如果在实际应用中确实需要强制类型转换,则说明我们设计的形参集合不合理。

6.6.1    实参类型转换

为了确定最佳匹配,编译器将实参类型到形参类型的转换划分成几个等级:

1、精确匹配:实参类型和形参类型相同;实参从数组类型或函数类型转换成对应的指针类型;向实参添加顶层const或者从实参中删除顶层const。

2、通过const转换实现的匹配(底层const)

3、通过类型提升实现的匹配

4、通过算术类型转换或指针转换实现的匹配

5、通过类类型转换实现的匹配

6.7    函数指针

1、声明一个函数指针,只需把函数名换成指针即可。

2、当我们把函数名作为一个值使用时,该函数自动地转换为指针。因此,可以直接将函数名赋值给同类型的函数指针,而不需要取地址符。另外,我们还能直接使用指向函数的指针调用该函数,无须提前解引用指针。

3、在指向不同函数类型的指针间不存在转换规则,即它们属于不同的类型,无法相互转换。但我们可以为函数指针赋值为nullptr,表示该指针没有指向任何一个函数。

4、如果定义了指向重载函数的指针,指针类型必须与重载函数中的某一个精确匹配。

5、和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。此时,形参看起来是函数类型,实际上却是当成指针使用。实际上这也是函数指针的一个重要用途——例如标准库的sort函数中,cmp函数就是一个参数,实际上参数并不是函数本身,而是指向它的指针。

6、返回指向函数的指针:和数组类似,虽然不能返回一个函数,但是能返回指向函数类型的指针。然而,我们必须把返回类型写成指针形式,编译器不会自动地将函数返回类型当成对应的指针类型处理,这点与我们使用函数名的情况不一样。

7、声明一个返回函数指针的函数,最简单的办法是使用类型别名,也可以使用尾置返回类型。另外,如果我们明确知道返回的函数是哪一个,就能使用decltype简化书写函数指针返回类型的过程。需要注意的是,decltype作用于某个函数时。它返回函数类型而非指针类型。因此我们需要显式的加上*以表明我们需要返回指针,而非函数本身。

8、作业题6.54--6.56

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

int add(int a, int b)
{
	return a + b;
}

int subtract(int a, int b)
{
	return a - b;
}

int multiple(int a, int b)
{
	return a * b;
}

int divide(int a, int b)
{
	if (b == 0)
	{
		cout << "Inf!" << endl;
		return 0;
	}
	return a / b;
}

int main()
{
	using PF = int(*)(int, int);
	vector<PF> op;
	op.push_back(add);
	op.push_back(subtract);
	op.push_back(multiple);
	op.push_back(divide);

	cout << op[2](3, 5) << endl;	// 15
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值