【模板函数的使用详解】

文章目录

    • 1. 函数模板
      • 1.1 基本模板使用
      • 1.2 重载的模板
      • 1.3 模板局限性
        • 1.3.1 显式具体化(explicit specialization)
        • 1.3.2 实例化和具体化
        • 1.3.3 显式具体化和显式实例化的区别
    • 2 编译器选择使用哪个函数版本
      • 2.1 最佳匹配的选择原则
      • 2.2 通过编写合适的函数调用,引导编译器选择特定的函数
    • 3. 类型声明关键字decltype和后置返回类型
      • 3.1 decltype类型声明
      • 3.2 后置返回类型

1. 函数模板

需要对多个不同类型的数据使用相同的算法时,可使用模板
函数模板是通用函数的描述,也就是说,他们使用泛型来定义函数。(通用编程)

1.1 基本模板使用

//声明语法:

template <typename T>  // or class T
void Swap(T& a, T& b);

//定义语法

template <typename T>  // or class T
void Swap(T& a, T& b)
{
    T temp;   // 定义一个任意类型的变量T
    temp = a;
    a = b;
    b = temp;
}

1.2 重载的模板

需要对不同的数据类型使用不同的算法时,可以使用重载的模板。
//声明语法:

template <typename T>     // 模板1
void Swap(T& a, T& b);

template <typename T>     // 模板2
void Swap(T* a, T* b, int n);

根据特征标(T& ,T&)和 (T* , T* ,int)来匹配不同的模板,实现模板的重载
//定义语法

template <typename T>
void Swap(T& a, T& b)
{
    T temp;
    temp = a;
    a = b;
    b = temp;
}

template <typename T>
void Swap(T a[], T b[], int n)
{
    T temp;
    for (int i = 0; i < n; i++)
    {
        temp = a[i];
        a[i] = b[i];
        b[i] = temp;
    }
}

1.3 模板局限性

如果T为数组、指针或结构时,编写的模板函数很可能无法处理某些类型。而且这里有可能重载也会失效,重载并不是万能的,(重载要求参数特征标不同,但是当两个不同的算法特征标相同时是没办法进行重载的。

这里有两种解决方案。

  • 运算符重载
  • 为特定类型提供具体化模板(除通用模板外另外写个特殊的)

1.3.1 显式具体化(explicit specialization)

  • 对于给定的函数,可以有:非模板函数、模板函数、显示具体化函数以及他们的重载版本
  • 显示具体化的原型和定义都应以template<>打头,并通过名称来指出类型
  • 非模板函数 》 具体化模板 》 常规模板
//常规模板
template <typename T>
void Swap(T& a, T& b);

struct job
{
    char name[40];
    double salary;
    int floor;
};

// explicit specialization 显示具体化模板
template <> void Swap<job>(job& j1, job& j2);


//Swap中的是可选的,因为函数的参数类型表明,这是job的一个具体化
template <> void Swap(job& j1, job& j2);
//这样写也可以

1.3.2 实例化和具体化

实例化:常规模板只是一个模板而已,并没有在内存中实际生成的函数定义指令。
只有在发生函数调用时,再根据具体的参数类型来生成特定的函数定义,这叫隐式具体化
也可以使用命令直接生成也定数据类型的函数定义,叫显式实例化这样直接链接即可使用,省去了隐式具体化所浪费的时间和空间。
显示实例化的语法:

template void Swap<int>(int ,int); //显式实例化一个int型函数

具体化(specialization):

  • 显式实例化(explicit instantiation )
  • 隐式实例化(implicit instantiation)
  • 显式具体化(explicit specializtion)
    相同点在于都是使用具体数据类型的函数定义,而不是通用描述。

区别:

//通用模板
template<class T>
void Swap(T &, T &);  

//显式具体化
template<>void Swap<job>(job &, job &);

int main()
{
    //显式实例化
    template void Swap<char>(char &, char &);
    short a,b;
    ...
    //隐式实例化
    Swap(a,b);
    
    job n,m;
    ...

    Swap(n,m);  //使用显式具体化函数

    char g,h;
    ...
    Swap(g,h); //使用显式实例化

}

1.3.3 显式具体化和显式实例化的区别

语法层面的区别 ,具体化比实例化多一个<>

//显式具体化声明 :template<>
//显式实例化声明: template

//通用模板 template /

用途层面的区别:
显式实例化是提前生成某个数据类型的函数定义,节省调用时间
显示具体化是指某个特殊类型单独定义,不使用通用模板。
这两个其实没什么关系,就是长得比较像而已,注意区分即可。

2 编译器选择使用哪个函数版本

2.1 最佳匹配的选择原则

非模板函数 》 具体化模板 》 常规模板
指向非const数据指针和引用优先于分非const的指针和引用参数匹配。
(const和非const的区别只适用于指针和引用数据)
参数类型最匹配的,需要的转换最小的。
如果有多个最佳匹配,或没有匹配的函数,会报错。

2.2 通过编写合适的函数调用,引导编译器选择特定的函数

// choices.cpp -- choosing a template
#include 

template<class T>      
T lesser(T a, T b)         // #1 模板函数,返回a,b中较小值
{
    return a < b ? a : b;
}

int lesser(int a, int b)  // #2 非模板函数,返回a,b的绝对值的较小值
{
    a = a < 0 ? -a : a;
    b = b < 0 ? -b : b;
    return a < b ? a : b;
}

int main()
{
    using namespace std;
    int m = 20;
    int n = -30;
    double x = 15.5;
    double y = 25.9;

    cout << lesser(m, n) << endl;       // use #2  与模板函数和非模板函数都匹配,因此选择非模板函数
    cout << lesser(x, y) << endl;       // use #1 with double  T = double ,与模板函数相匹配
    cout << lesser<>(m, n) << endl;     // use #1 with int  lesser<>(m, n) 中的 <> 指出,使用模板函数,编译器注意到实参类型为int,因此选择T = int
    cout << lesser<int>(x, y) << endl; // use #1 with int 这条语句要求进行显式实例化(使用int代替T),将使用显式实例化的得到的函数,
    //x,y将被强制转换为int类型(15和25),所以这里会有一个警告,精度会丢失,结果是15而不是15.5

    // cin.get();
    return 0;
}

3. 类型声明关键字decltype和后置返回类型

3.1 decltype类型声明

在编写模板函数时,有时候不能提前知道某个变量是那种类型。

template<class T1,class T2>
void ft(T1 X, T2 y)
{
    ?type? xplusy = x + y;
}

可以看到,这里我们没办法知道xplusy这个变量该定义什么样的数据类型,因为x和y的类型都是未知的,这时候c++11新增了decltype类型声明关键字 ,decltype(x+y)就代表一个和括号里(x+y)这个数据类型一样的一个类型。

使用语法如下

int x;
decltype(x) y; // y和x的数据类型相同

T1 x;
T2 y;
decltype(x+y) xplusy = x + y; //使xplusy的类型与x+y的类型相同

如需多次声明,可结合使用typedefdecltype
template<typename T1,typename T2>
void ft(T1 x,T2 y)
{
    ...
    typedef decltype(x + y) xpytype;
    xpytype xpy = x + y;
    xpytype arr[10];
    xpytype & rxy = arr[2];
    ...
}

decltype在实际使用时,编译器为了确定数据类型,必须要遍历一个和核对表,简化版的核对表如下

假设有如下声明

decltype(expression) var

第一步:如果expression是个没有用括号括起的限定符,则var与该标识符的类型相同,包括const等限定符。

double x = 5.5;
double y =7.69;
double & rx = x;
const double *pd;

decltype(x) w; //w is type double
decltype(rx) u = y;// u is type double & 
decltype(pd) v; // pd is type const double *

第二步:如果expression是一个函数调用,则var的类型与函数的返回类型相同。(并不用真的调用一遍函数,编译器只需要查看一下函数原型声明的返回值类型即可)

long indeed(int , const int ); //function prototype 
decltype(indeed(3,4)) m; // m is type long

第三步:expression是一个用括号括起来的标识符,(是一个左值),则var为指向其类型的引用。
(括号并不会改变表达式的值和左值性)

double xx = 4.4;
decltype((xx)) r2 = xx; // xx is type double &
decltype(xx) w = xx; // w is type double (第一步就匹配上了)

**第四部:**如果以上三步都不满足,则var类型与expression的类型相同

int j = 3;
int &k = j;
int &n = j;

decltype(j+6) i1; // i1 is type int
decltype(100l) i2; // i2 is type long
decltype(k+n) i3; // i3 is type int 
//虽然 k和n都是引用类型,但是k+n不是引用,他是两个int的和,所以是int类型

3.2 后置返回类型

template<typename T1,typename T2>
?type? gt(T1 x,T2 y)
{
    ...
    return x+y;
}

这里无法预先知道x+y的类型,而使用decltype(x+y)也不行,因为此时x和y的参数还未具体声明,他们不在作用域内,编译器看不到他们,也无法使用他们。必须在声明参数之后才能使用decltype,不能提前使用。
C++11新增了后置返回类型,将返回类型移动到了参数声明后面。auto在这里是一个占位符,表示使用后置返回类型。

语法如下:

template<class T1,class T2> -> decltype(x + y)
{
    ...
    return x + y;
}

现在,decltype在参数声明后面,因此x和y位于作用域内,可以使用它们。

你可能感兴趣的:(c++,primer,plus学习笔记,c++)