C++泛型编程——模板,容器实现
泛型编程模板技术模板实例化模板的使用模板参数模板特化函数模板特化类模板特化容器适配器分离编译模式泛型编程泛型编程是编写和类型无关的逻辑代码,是代码复用的另一种手段。模板是实现泛型编程的一种技术。以往要编写一个通用的函数有3种方法,函数重载,使用公共基类,宏函数。然而函数重载需要对各种类型一一枚举,且一个错个个错。第二种继承公共基类并重写虚...
泛型编程
泛型编程是编写和类型无关的逻辑代码,是代码复用的另一种手段。模板是实现泛型编程的一种技术。
以往要编写一个通用的函数有3种方法,函数重载,使用公共基类,宏函数。
然而函数重载需要对各种类型一一枚举,且一个错个个错。第二种继承公共基类并重写虚函数实现多态,失去了类型检查,对每一个子类维护也很麻烦。宏函数也没有类型检查。
实现Vector和List容器:https://github.com/zzaiyuyu/Vector-List
模板技术
模板实例化
不管是类模板或是函数模板,都不是真正的类或函数,只是一个编译器用来生成代码的蓝图。
模板的实例化指函数模板(类模板)生成模板函数(模板类)的过程。对于函数模板而言,模板实例化之后,会生成一个真正的函数。而类模板经过实例化之后,只是完成了类的定义,模板类的成员函数需要到调用时才会被初始化。模板的实例化分为隐式实例化和显示实例化。
隐式实例化:在发生模板函数调用或者模板类时,才进行实例化。
//隐式实例化函数模板
template <typename T>
T Max(const T& t1,const T& t2){
return (t1>t2)?t1:t2;
}
//隐式实例化类模板
template <typename T>
class A{
T num;
}
显示实例化:不管有无调用,只要显式声明,必定实例化出模板类或函数
//用template声明一下函数模板
template void func<int>(const int&);
//类模板
template class theclass<int>;
模板的使用
对函数模板的使用而言,分为两种调用方式,一种是显示模板实参调用(显示调用),一种是隐式模板实参调用(隐式调用)。对于类模板的使用而言,没有隐式模板实参和显式模板实参使用的说法,因为类模板的使用必须显示指明模板实参
- 隐式调用,编译器根据调用的实参推演出匹配的模板函数。
- 显示调用,由用户决定调用的实参类型,不经过参数推演
template <typename T>
T Max(const T& t1,const T& t2){
return (t1>t2)?t1:t2;
}
int main(){
int i=5;
cout<<Max(i,'a')<<endl; //隐式调用,没有匹配的参数无法通过编译
cout<<Max<int>(i,'a')<<endl; //显示调用,强转int,通过编译
}
模板参数
template<typename T, size_t N> //模板参数,其中T是类型参数,N是非类型参数
void PrintArray(const T (&array)[N]) {
for(size_t i = 0; i < N; ++i)
cout<<array[i]<<" ";
cout<<endl;
}
int arr[10];
PrintArray(arr); //调用参数
<>里typename后跟类型参数,其他为非类型参数。非类型参数N会根据实参决定,作为常量在函数模板里使用。
模板参数的作用域在紧跟的函数模板里有效。
模板特化
函数模板有全特化,类模板有全特化和偏特化。
有时候在一个泛型模板里并不能正确处理所有的类型,所以需要特化。
函数模板特化
//函数模板
template<class T>
bool IsEqual(T t1,T t2){
return t1==t2;
}
template<> //函数模板特化
bool IsEqual(char *t1,char *t2){
return strcmp(t1,t2)==0;
}
另外函数重载也可以实现函数模板特化的功能
bool IsEqual(char *t1,char *t2){
return strcmp(t1,t2)==0;
}
如果使用普通重载函数,那么不管是否发生实际的函数调用,都会在目标文件中生成该函数的二进制代码。分离编译模式下,应该在各个源文件中包含重载函数的声明重载的函数。
类模板特化
全特化:模板中参数全部被固定,相当于定义一个全新的类
偏特化:模板参数没有全部固定,对部分模板参数进行指定限制
类模板全特化的应用场景——类型萃取
struct TrueType {
static bool Get()
{
return true;
}
};
struct FalseType {
static bool Get()
{
return false;
}
};
//如果没有特化,就是自定义类型
template<class T>
struct TypeTraits {
typedef FalseType PODTYPE;
};
template <>
struct TypeTraits<int> {
typedef TrueType PODTYPE;
};
template <>
struct TypeTraits<double> {
typedef TrueType PODTYPE;
};
//调用
//TypeTraits<T>::PODTYPE::Get()
偏特化不仅是对部分参数的特化,同时可以是对参数的限制
常见的有将参数特化为绝对类型,指针类型
// 局部特化两个参数为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*> {
public :
Data();
private :
T1 _d1 ;
T2 _d2 ;
T1* _d3 ;
T2* _d4 ;
};
template <typename T1, typename T2>
Data<T1 *, T2*>:: Data() {
cout<<"Data<T1*, T2*>" <<endl;
}
容器适配器
已有的类模板比如vector,list,deque对操纵数据的封装,称为容器。进一步想实现一些数据结构可以借助容器的接口快速实现。
template<class T, class Container = SeqList<T> >
class{
...
private:
Container _con;
}
分离编译模式
https://blog.csdn.net/pongba/article/details/19130
上文总结一下:
每个cpp文件都会先把包含的.h复制过来,然后编译成一个单独的obj目标文件。正常情况下一个cpp里找不到其他cpp文件里的函数,会在链接期间从其他obj文件的导出符号表里找到需要的函数,并把函数地址填到自己的调用函数处。
由于模板的特殊性,只有在同一个cpp文件里发现有调用才会实例化出真正的模板函数,当函数模板单独放在一个cpp里,编译器就不会为其实例化,在链接期间别人也不可能找得到没有实例化的函数。
解决方案:
- 在模板头文件 xxx.h 里面显示实例化
- 将声明和定义放到一个文件 “xxx.hpp” 里面
hpp实质就是将.cpp的实现代码混入.h头文件当中,定义与实现都包含在同一文件,则该类的调用者只需要include该hpp文件即可.**实现代码将直接编译到调用者的**obj文件中,不再生成单独的obj
更多推荐
所有评论(0)