2. C++函数

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 重载解析过程

  1. 创建候选函数列表。其中包括与被调用函数名称相同的函数与模板函数。
  2. 使用候选函数列表创建可行函数列表。其中包括参数数目正确且能匹配的函数。
  3. 确定是否有最佳函数。如果有,则调用它,否则函数调用出错。

2.4.2 匹配"等级"

可行函数从最佳到最差的顺序:

  1. 完全匹配(常规函数在同类中优先于模板函数)
  2. 提升转换(例如, charshort自动转换为int, float自动转换为double, double自动转换为Long double)
  3. 常规转换(例如, int转换为char, long转换为double)
  4. 用户定义的转换(例如类中定义的转换函数)

识别完全匹配时, 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):

  1. 考虑两个函数模板:T1 和 T2。
  2. 将 T1 中的参数替换为假想的唯一类型 X。
  3. 不进行任何隐式转换,查看 T2 是否为此时 T1 中参数列表的有效模板。
  4. 对 T1 和 T2 执行相反的过程。
  5. 如果一个模板参数列表是另一个模板的有效模板参数列表, 则认为该模板不是专用于其他模板, 但反之不成立。 如果通过使用上一步, 两个模板都同时构成彼此的有效自变量, 则它们被视为同等专用化, 当你尝试使用它们时, 将会产生不明确的调用结果。
  6. 使用以下规则:
    1. 针对特定类型的模板的专用化程度高于采用泛型类型参数的模板。
    2. T*采用一个模板的专用化比只T采用一个模板更为专用, 因为X*假设类型是T模板参数的有效参数, 但X它不是有效的参数T*模板参数。
    3. const TT更专业化, 因为const X是模板参数T的有效参数, 但const T不是模板参数X的有效参数。
    4. 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++中一般禁止创建临时变量。
因为使用普通引用一般是为了修改实参的值, 若允许产生临时变量, 可能会产生不易察觉的错误。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章