目录
- 绪言
- 实例化
- 显式实例化
- 隐式实例化
- 特化
- 全特化
- 类模板全特化
- 函数模板全特化
- 偏特化
- 多个模板参数
- 缺省的模板参数
- 带参数包的类型参数
- 非类型模板参数
绪言
模板是泛型编程实现的重要手段。关于模板的基本用法,可以看我的这篇文章,这里不再过多地赘述。本篇文章主要是面向掌握了C++面向对象编程基本语法,希望深入学习的读者。建议大家根据自身情况自行选择阅读与否。
实例化
模板实例化是模板的重要概念。什么是实例化呢?在编写一个模板时,我们并没有具体指定模板类型参数,而是使用了一个占位符(例如T)。而当我们在创建一个类模板对象,或者调用一个函数模板时,无论是显式指定模板类型参数,或者隐式推导,系统最终都会得到我们定义的占位符在调用时的具体指代。这个过程就叫做模板实例化。
模板实例化分为显式实例化和隐式实例化,下面将分别讲述。
显式实例化
显式实例化,即手动指定模板中的类型参数,一般放在尖括号<>内,置于函数名之后调用函数模板,或者置于类名之后创建类模板对象。如果您还不能清晰地理解显式实例化,请看下面的实例:
#include<iostream>usingnamespacestd;template<typenameT>classmyclass{public:myclass(){};myclass(intp):id(p){};~myclass(){};public:intid;myclass<T>operator+(constmyclass<T>&b){myclass<T>ret(this->id+b.id);returnret;}};template<typenameT>Tadd(T a,T b){returna+b;}intmain(){myclass<int>a(5);myclass<int>b(6);cout<<add<myclass<int>>(a,b).id<<endl;return;}运行上面的代码,将会输出以下结果:
11
观察main()函数,函数的一、二两行各定义了一个类模板对象,我们显式地用int类型代替了占位符T;函数的输出行调用了模板函数add,我们显式地用类型myclass <int>代替了占位符T。在这个实例中我们都显式地指定了类型参数。这就是显式实例化。
隐式实例化
与显式实例化相对的是隐式实例化。隐式实例化即不明确指定类型参数,而是系统通过函数的参数来推导类型参数。所以请注意:由于定义一个类模板对象时不一定都有参数可供推导,所以在C++标准中,隐式实例化是模板函数的专利,不能用于类模板。这也就告诉我们,一个类模板的名称并不是类型名,一个指定了类型参数的类模板名称才是类型名,例如在上面的实例中,myclass是非法的类型名,而myclass <int>是合法的。
隐式实例化不必指定类型参数,请看下面的实例,它与上面的实例是等价的:
#include<iostream>usingnamespacestd;template<typenameT>classmyclass{public:myclass(){};myclass(intp):id(p){};~myclass(){};public:intid;myclass<T>operator+(constmyclass<T>&b){myclass<T>ret(this->id+b.id);returnret;}};template<typenameT>Tadd(T a,T b){returna+b;}intmain(){myclass<int>a(5);myclass<int>b(6);cout<<add(a,b).id<<endl;}相比于之前的实例,这里只是把函数add()的调用更改了一下,去除了函数名之后指定类型参数的内容。这就是隐式实例化。
特化
与模板实例化相似的概念还有模板特化。模板特化,就是对于特殊的模板类型参数,我们想实现不同的功能时补充的专门性的函数。特化分为全特化和偏特化。
全特化
全特化的一般形式为:
类模板全特化
template<typenameT>classa{public:T ori;//class body};template<>classa<constchar*>{public:constchar*ori;//class body};函数模板全特化
template<typenameT>Tadd(T a,T b){returna+b;}template<>charadd<char>(chara,charb){returna+b;}偏特化
由于函数模板的偏特化可以通过重载实现,所以函数模板没有偏特化,只有类模板有偏特化。
template<typenameT1,typenameT2>classa{public:T1 ori1;T2 ori2;//class body};template<typenameT>classa<constchar*,T>{public:constchar*ori1;T ori2;//class body};多个模板参数
模板参数可以不止一个。例如在上面的实例中,类a中就有两个类型参数T1和T2。在含有多个模板参数的模板参数列表中,与函数参数类似地,每个参数之间用,隔开。
缺省的模板参数
模板参数也可以缺省,但所有缺省参数只能放在普通参数之后。模板参数可以只含有缺省参数,但即使如此,类模板对象创建时也要加上尖括号<>。请看下面的实例:
template<typenameT1=int,typenameT2=int>classa{public:T1 A;T2 B;public:a(){};a(T1 first,T2 second):A(first),B(second){};~a(){};};intmain(){a<>Classa;a<char>Classb;a<double,double>Classc;return0;}带参数包的类型参数
在类型参数列表中的typename或class关键字后加上运算符...可以声明一个类型参数是参数包类型,即该类型带有参数包。这样写的好处是可以与可变参数相结合编程。以下是一个实例:
#include<vector>#include<type_traits>#include<iostream>usingnamespacestd;template<typenameT>classmyclass{private:vector<T>ori;public:myclass(){};template<typename..._Args>myclass(_Args...args){vector<T>tmp{forward<T>(args)...};this->ori=tmp;}~myclass(){};public:voidprint(){for(inti=0;i<this->ori.size();i++){cout<<ori[i]<<' ';}cout<<endl;return;}};intmain(){myclass<int>a{1,2,3,4,5};a.print();return0;}运行上面的代码,将会输出以下结果:
1 2 3 4 5
非类型模板参数
模板的参数不一定必须是类型参数,也可以像函数参数一样是一个具体的对象。以下是一个实例,展现了模板非类型参数实现变长数组的方法:
#include<iostream>usingnamespacestd;template<typenameT,size_t N>classarr{private:T ori[N];public:arr(){memset(ori,0,sizeof(ori));}~arr(){};};intmain(){arr<int,5>a;return0;}这里模板参数N就不是类型参数,而是一个size_t类型的变量。调用时也与一般的类模板相同,将模板参数置于尖括号<>内。
希望大家多多支持!多评论、多点赞、多收藏、多关注!