我得开始一章一篇笔记了,因为进入以前没怎么接触过的知识盲区了233
本篇内容包括:
- 内联函数。
- 引用变量。
- 如何按引用传递函数参数。
- 默认参数。
- 函数重载。
- 函数模板。
- 函数模板具体化。
第8章 函数探幽
8.2 引用变量
8.2.1 创建引用变量
1 | int rats; |
引用接近const指针,必须在创建时进行初始化。
8.2.3 引用的属性和特别之处
如果要编写类似于上述示例的函数(即使用基本数值类型),应采用按值传递的方式,而不要采用按引用传递的方式。当数据比较大(如结构和类)时,引用参数将很有用。
如果实参与引用参数不匹配,C++ 将生成临时变量。仅当参数为
const
引用时,C++ 才允许这样做。如果引用参数是const
,则编译器将在下面两种情况下生成临时变量:- 实参的类型正确,但不是左值
- 左值参数是可被引用的数据对象,例如,变量、数组元素、结构成员、引用和解除引用的指针都是左值。
- 非左值包括字面常量(用引号括起的字符串除外,它们由其地址表示)和包含多项的表达式。
- 具体分类详见 cppreference
- 实参的类型不正确,但可以转换为正确的类型
- 实参的类型正确,但不是左值
C++11 新增了另一种引用——右值引用(rvalue reference)。这种引用可指向右值,是使用
&&
声明的。新增右值引用的主要目的是,让库设计人员能够提供有些操作的更有效实现。第18章将讨论如何使用右值引用来实现移动语义(move semantics)。以前的引用(使用&
声明的引用)现在称为左值引用。1
2double &&rref = std::sqrt(36.00);
std::cout << rref << std::endl;
8.2.4 将引用用于结构
函数的返回值也可以是引用。此时一定要注意左值的生命周期。
8.3 默认参数
对于带参数列表的函数,必须从右向左添加默认值。也就是说,要为某个参数设置默认值,则必须为它右边的所有参数提供默认值。
8.4 函数重载
函数重载的关键是函数的参数列表——也称为函数特征标(function signature)。编译器在检查函数特征标时,将把类型引用和类型本身视为同一个特征标。匹配函数时,并不区分
const
和非const
变量。名称修饰:
C++ 如何跟踪每一个重载函数呢?它给这些函数指定了秘密身份。使用 C++ 开发工具中的编辑器编写和编译程序时,C++ 编译器将执行一些神奇的操作——名称修饰(name decoration)或名称矫正(name mangling),它根据函数原型中指定的形参类型对每个函数名进行加密。请看下述未经修饰的函数原型:
long MyFunctionFoo(int, float);
。这种格式对于人类来说很适合;我们知道函数接受两个参数(一个为int类型,另一个为float
类型),并返回一个long
值。而编译器将名称转换为不太好看的内部表示,来描述该接口,如下所示?MyFunctionFoo@@YAXH
对原始名称进行的表面看来无意义的修饰(或矫正,因人而异)将对参数数目和类型进行编码。添加的一组符号随函数特征标而异,而修饰时使用的约定随编译器而异。
8.5 函数模板
在模板中,
class
关键字和typename
关键字等价。如果不考虑向后兼容(Backwards compatibility,即向下兼容,Downward Compatibility)的问题,并愿意键入较长的单词,则声明类型参数时,应使用关键字typename
而不使用class
。注意,函数模板不能缩短可执行程序。
8.5.3 显式具体化
显式具体化的原型和定义应以 template<>
打头,并通过名称来指出类型。
1 | // non template function prototype |
其中,Swap<job>
中的 <job>
可以省略。
8.5.4 实例化和具体化
在代码中包含函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例(instantiation)。模板并非函数定义,但使用
int
的模板实例是函数定义。这种实例化方式被称为隐式实例化(implicit instantiation)。现在 C++ 还允许显式实例化(explicit instantiation)。这意味着可以直接命令编译器创建特定的实例。如:
1
template void Swap<int>(int &, int &);
编译器看到上述声明后,将使用
Swap()
模板生成一个使用int
类型的实例。也就是说,该声明的意思是“使用Swap()
模板生成int
类型的函数定义。”注意显式实例化和显式具体化的区别。试图在同一个文件(或转换单元)中使用同一种类型的显式实例和显式具体化将出错。还可通过在程序中使用函数来创建显式实例化:
1
2
3
4
5template <typename T>
T Add(T a, T b); /* details omitted */
int m = 6;
double x = 10.2;
std::cout << Add<double>(x, m) << std::endl;
8.5.5 编译器选择使用哪个函数版本
- 来决定为函数调用使用哪一个函数定义的过程称为重载解析(overloading resolution)。
通常,从最佳到最差的顺序如下所述。- 完全匹配,但常规函数优先于模板。
- 提升转换(例如,
char
和short
自动转换为int
,float
自动转换为double
)。 - 标准转换(例如,
int
转换为char
,long
转换为double
)。 - 用户定义的转换,如类声明中定义的转换。
- 进行完全匹配时,C++ 允许某些“无关紧要的转换”(Type(argument-list)意味着用作实参的函数名与用作形参的函数指针只要返回类型和参数列表相同,就是匹配的,
volatile
关键字见第九章):从实参 到形参 type type & type & type type [] type * type(argument-list) type(*)(argument-list) type const type type volatile type type * const type * type * volatile type *
- 若有多个完全匹配的原型,则进行最佳匹配。
- 指向非
const
数据的指针和引用优先于const
指针和引用。 - 非模板函数将优先于模板函数(包括显式具体化)
- 如果两个完全匹配的函数都是模板函数,则较具体的模板函数优先。术语“最具体(most specialized)”并不一定意味着显式具体化,而是指编译器推断使用哪种类型时执行的转换最少。
- 指向非
- 自己选择。如
lesser<>(m, n)
指出应选择模板函数。
8.5.6 模板函数的发展
decltype
关键字:假设有声明如下
1
decltype(expression) var;
则:
如果 expression 是一个没有用括号括起的标识符,则 var 的类型与该标识符的类型相同,包括
const
等限定符。e.g.
1
decltype(x) y;
如果 expression 是一个函数调用,则 var 的类型与函数的返回类型相同。
e.g.
1
decltype(Solve(x)) y;
如果 expression 是一个左值,则 var 为指向其类型的引用。
e.g.
1
decltype((x)) y = x;
如果前面的条件都不满足,则 var 的类型与 expression 的类型相同
另一种函数声明语法(C++11 后置返回类型)
1
2
3
4
5template<typename T1, typename T2>
auto foo(T1 x, T2 y) -> decltype(x + y) {
/* details omitted */
return x + y;
}