文章目录
2.1 内联函数
C++内联函数其实是对C中的宏的优化(或者说新的实现方法)。使用内联函数代替宏能避免某些错误的风险。
至少在函数声明与函数定义之中的一处使用关键字inline
,可以使函数成为内联函数。在类声明中定义的函数会被自动转换为内联函数。
内联函数的调用语句会被编译器自动替换为函数的代码。从而省去了调用函数所需的时间。
2.2 函数重载
通过创建拥有不同参数列表的同名函数,可以实现函数重载。重载函数的返回值可以不同,但不能作为区分不同重载函数的标准,也就是说不能用来进行函数重载。
2.3 函数模版
第三代具体化(IOS/ANSI C++标准)
- 对于给定的函数名,可以有非模版函数、模板函数、和显式具体化模板函数及它们的重载版本。
- 显式具体化的原型与定义应以
template<>
打头,并来指出类型。- 具体化模板优先于常规模板,而非模板函数优先于模板函数。
隐式实例化、显式实例化、显式具体化统称为具体化
模版函数定义(typename
可以用 class
代替):
template<typename AnyType>
bool isgreater(AnyType a, AnyType b)
{
if(a > b)
return true;
return false;
}
2.3.1 decltype 关键字(C++11)
decltype
的作用是检测表达式的类型(type),并将其用来指出类型。常常在模板函数中使用。
decltype(expression) var;
decltype
还可以搭配auto
实现的后置类型声明处理特殊的函数返回值。常常用于模板函数。
后置类型声明
auto h(int x, float y) -> double;
搭配decltype
,用于模板函数
template<typename T1, typename T1>
auto h(T1 x, T2 y) -> decltype(x + y)
{
// ...
return x + y;
}
2.3.2 显式具体化
假设定义了一个类Data
, Data
中有成员number
// 原型
template<> isgreater<Data>(Data, Data); // <Data>是可选的
// 定义
template<> isgreater<Data>(Data a, Data a) // <Data>是可选的
{
if(a.number > b.number)
return true;
return false;
}
2.3.3 实例化
- 隐式实例化:模板函数本身并不会创建函数, 在使用模板函数时会创建相应地函数, 这种实例化称为隐式具体化
- 显式实例化:具体做法是声明所需的种类, 用
<>
指出类型, 并在声明开头添加关键字template
template isgreater<int>(int, int);
!注意: 在同一文件(或转换单元)中若已有显式具体化, 编译器不会隐式创建实例, 使用同一类型的显式具体化和显式实例化将会导致错误
2.4 重载解析
2.4.1 重载解析过程
- 创建候选函数列表。其中包括与被调用函数名称相同的函数与模板函数。
- 使用候选函数列表创建可行函数列表。其中包括参数数目正确且能匹配的函数。
- 确定是否有最佳函数。如果有,则调用它,否则函数调用出错。
2.4.2 匹配"等级"
可行函数从最佳到最差的顺序:
- 完全匹配(常规函数在同类中优先于模板函数)
- 提升转换(例如,
char
和short
自动转换为int
,float
自动转换为double
,double
自动转换为Long double
) - 常规转换(例如,
int
转换为char
,long
转换为double
) - 用户定义的转换(例如类中定义的转换函数)
识别完全匹配时, C++允许进行"无关紧要的匹配", 如下(Type指其他类型而非变量名):
从实参 | 到形参 | 注 |
---|---|---|
Type | Type & | |
Type& | Type | |
Type [] | Type* | 数组到指针 |
Type(argument-list) | Type (*)(argument-list) | 函数名到函数指针 |
Type | const Type | |
Type | volatile Type | |
Type* | const Type* | |
Type* | volatile Type* |
2.4.3 完全匹配与最佳匹配
- 仅对非模板函数, 指向非
const
数据的指针或引用优先于指向const
数据的指针或引用 - 非模版函数优先于模版函数(如上面所提到的)
- 显式具体化优先于隐式具体化()
对于第一条, 需注意模板函数与非模板函数的不同, 参考以下代码:
1. 对非模板函数, 见上文
#include <iostream>
using namespace std;
void func(int &){
cout << "int &" << endl;
}
void func(const int &){
cout << "const int &" << endl;
}
int main()
{
int i;
int& ri = i;
func(ri);
return 0;
}
输出:
int &
2. 对模板函数, 见下文"部分排序规则"
#include <iostream>
using namespace std;
template<typename T>
void func(T &){
cout << "T &" << endl;
}
template<typename T>
void func(const T &){
cout << "const T &" << endl;
}
int main()
{
int i;
int& ri = i;
func(ri);
return 0;
}
输出:
const T &
2.4.3.1 部分排序规则
有多个与函数调用的自变量列表匹配的函数模板可用。 C++ 定义了函数模板的部分排序以指定应调用的函数。 由于有一些模板可能会视为专用化程度相同,因此排序只是部分的。
编译器从可能的匹配项中选择可用的专用化程度最高的模板函数。 例如, 如果一个函数模板采用类型T
, 而另一个所采用T*
的函数模板可用, 则T*
认为该版本更为专用化。 只要参数是指针类型, 就优先使用T*
泛型版本, 即使两者都是允许的匹配项。
通过以下过程可确定一个函数模板候选项是否更加专用化(more specialized):
- 考虑两个函数模板:T1 和 T2。
- 将 T1 中的参数替换为假想的唯一类型 X。
- 不进行任何隐式转换,查看 T2 是否为此时 T1 中参数列表的有效模板。
- 对 T1 和 T2 执行相反的过程。
- 如果一个模板参数列表是另一个模板的有效模板参数列表, 则认为该模板不是专用于其他模板, 但反之不成立。 如果通过使用上一步, 两个模板都同时构成彼此的有效自变量, 则它们被视为同等专用化, 当你尝试使用它们时, 将会产生不明确的调用结果。
- 使用以下规则:
- 针对特定类型的模板的专用化程度高于采用泛型类型参数的模板。
- 仅
T*
采用一个模板的专用化比只T
采用一个模板更为专用, 因为X*
假设类型是T
模板参数的有效参数, 但X
它不是有效的参数T*
模板参数。 const T
比T
更专业化, 因为const X
是模板参数T
的有效参数, 但const T
不是模板参数X
的有效参数。const T*
比T*
更专业化, 因为const X*
是模板参数T*
的有效参数, 但const T*
不是模板参数X*
的有效参数。
简单地说, 哪一个模板指出的细节更多就使用哪个
示例
// partial_ordering_of_function_templates.cpp
// compile with: /EHsc
#include <iostream>
template <class T> void f(T) {
printf_s("Less specialized function called\n");
}
template <class T> void f(T*) {
printf_s("More specialized function called\n");
}
template <class T> void f(const T*) {
printf_s("Even more specialized function for const T*\n");
}
int main() {
int i =0;
const int j = 0;
int *pi = &i;
const int *cpi = &j;
f(i); // Calls less specialized function.
f(pi); // Calls more specialized function.
f(cpi); // Calls even more specialized function.
// Without partial ordering, these calls would be ambiguous.
}
输出
Less specialized function called
More specialized function called
Even more specialized function for const T*
2.4 小细节
2.4.1 默认参数
在函数声明中可以指定参数的默认值。
第一个默认参数后只有默认参数。
2.4.2 指针与"数组"
在函数头与函数声明中, int arr[]
与int* arr
等效
2.4.3 临时变量与引用
仅当引用形参为const
时, 编译器才会在实参与形参不匹配时生成临时变量。
- 实参的类型正确, 但不是左值
- 实参的类型不正确, 但可以转换为正确的类型
C中一开始将等式左边的实体称为左值, 但在引入
const
关键字后情况发生了变化。现在的情况是将可以被引用的数据对象称为引用, 是否为const
对是否为左值并没有影响。
- 左值: 变量、数组元素、结构成员、引用、解除引用的指针(如
*p
,p
为指针)- 非左值: 字面常量(C-style 字符串除外, 其值类似指针常量)、包含多项的表达式
若形参为普通引用, C++中一般禁止创建临时变量。
因为使用普通引用一般是为了修改实参的值, 若允许产生临时变量, 可能会产生不易察觉的错误。