概念:一种与类型无关的代码. 作用:复用. 下面通过一个实例来实现:
template<class T> //类比函数参数,但是模板参数传递的是类型,也可以使用typename T Add(T a,T b) { return a + b; } int main() { int a = 10; int b = 20; cout<<Add(a,b)<<endl; //Add<int>(a,b); //显示实例化,当add函数的参数类型不同时,就会在推演类型时出错,所以此时就用到了显示实例化,明确T的类型 float c = 1.1223; float d = 2.123; cout<<Add(c,d)<<endl; return 0; }那么是怎么实现的呢?只要想探索底层的实现原理,有一个简便的方法就是:查看汇编. 此时,我们查看汇编: 所以:编译器在编译时,会进行模板类型的推演,然后模板函数在执行时会生成不同的函数名,进而实现函数的复用. 注意:模板函数只有在调用的时候才会进行检查.如果只写模板函数是不会检查函数体是否有错误
函数名相同,且在同一作用域,所以上面两个函数构成了重载.
如果要看Vector的模板实现的代码,请戳这里 模板类与模板函数相似,在这儿,我们可以通过顺序表Vector来实现模板类.
template <class T> class Vector { public: Vector(); Vector(const Vector<T>& v); Vector<T>& operator=(const Vector<T>& v); void PushBack(const T& value); void PopBack(); void Insert(size_t pos,const T& value); void Erase(size_t pos); size_t Size(); size_t Capacity(); bool Empty(); ~Vector(); protected: T* _first; T* _finish; T* _endofstorage; };敲黑板:在模板中.类名就是上例中的Vector;类型而是Vector;而在普通的类中,类名和类型相同. 当在类外面进行实现时,与我们以往写的声明是不同的.
template<class T> Vector<T>::Vector() //注意作用域 :_first(NULL) ,_finish(NULL) ,_endofstorage(NULL) {} template<class T> //必须写 Vector<T>::~Vector() { delete[] _first; _first = _finish = _endofstorage = NULL; }当在创建对象使用时,它的声明是:
Vector<int> v;经常使用的数据结构还有带头双向链表. 带头双向链表代码的实现
template<class T> struct LinkNode { T data; LinkNode<T>* prev; LinkNode<T>* next; LinkNode(const T& value) :data(value) ,prev(NULL) ,next(NULL) {} }; template<class T> class LinkList { public: typedef LinkNode<T> Node; LinkList(); LinkList(const LinkList<T>& l); LinkList<T>& operator=(const LinkList<T>& l); void PushFront(const T& value); void PopFront(); void PushBack(const T& value); void PopBack(); void Insert(Node* pos,const T& value); void Erase(Node* pos); void Clear(); ~LinkNdoe(); bool Empty(); protected: Node* head; };在生活中,我们笔记本电脑的充电器,也叫适配器(实现电压的转化). 同样的,在我们的程序中,也可以通过某种代码去实现其它的功能.比如:用Vector去模拟实现栈.
template<class T,class Container> //template<class T,class Container = Vector<T> > //缺省参数 class Stack { public: void Push(const T& value) { con.PushBack(value); } void Pop() { con.PopBack() } T& Top() { con.Front(); } bool Empty() { con.Empty(); } protected: Container con; }; int main() { Stack<int,class<int> > s; return 0; }与上面的适配器的实现类比:
template<class T,template<class T> class Container > //template<class T,template<class T> class Container = Vector<T>> class Stack { public: void Push(const T& value) { con.PushBack(value); } void Pop() { con.PopBack() } T& Top() { con.Front(); } bool Empty() { con.Empty(); } protected: Container<T> con; }; int main() { Stack<int,Vector> s; return 0; } template<class T,template<class T> class Container> 其中:template<class T>表示Container是一个模板类类型的模板参数.模板类的模板参数,也可以飞类型的模板参数. 比如:
template<class T,size_t N>利用这个可以实现静态的顺序表. 注意:double是不可以做非类型的模板参数.
使用日期类来举例学习:
template<typename T1,typename T2> class Date { public: Date() { cout<<"Date()"<<endl; } private: T1 _d; T2 _d1; }; template<typename T1,typename T2> class Date<T1& ,T2&> { public: Date(T1 a,T2 b) { cout<<"Date<T1&,T2&>"<<endl; } private: T1& _d1; T2& _d2; }; template<typename T1,typename T2> class Date<T1*,T2*> { public: Date() { cout<<"Date<T1*,T2*>"<<endl; } private: T1* _d1; T2* _d2; }; //第二个参数偏特化 template<typename T1> class Date<T1,char> { public: Date() { cout<<"Date<T1,char>"<<endl; } private: T1 _d1; char _d2; }; //第一个参数偏特化 template<typename T2> class Date<char,T2> { public: Date() { cout<<"Date<char,T2>"<<endl; } private: char _d1; T2 _d2; }; template<> //全特化 class Date<int,int> { public: Date() { cout<<"Date<int,int>"<<endl; } private: int _d1; int _d2; }; int main() { Date<int,int> d; Date<char,int> d1; Date<double&,double&> d2(1.11,2.22); Date<int*,int*> d3; return 0; }总结:当该类型的模板函数实现了时,在调用时就会调用(偏特化模板函数)实现了的对应的函数.当该函数没有实现时,就会调用模板函数.注意:当类的成员变量为引用类型时,在初始化类时对于成员变量也要初始化.(引用必须初始化)
注意:模板的全特化和半特化都是在已定义的模板的基础之上,不能单独存在.
当模板的定义在.h文件中,而声明在.c文件中,而测试函数又在另外一个文件中时,在编译时可以通过吗?
总结:模板不支持分离编译,最好的做法就是,直接在模板的定义后面进行模板的实现. 如果定义和声明不在同一个文件中,那么在执行函数时就会直接编译不通过,链接失败.因为模板函数只是在执行的时候才会去推演它的类型,才会去找实现的函数,当它们不在同一个文件时,就会因为找不到而报错.
模板总结: 优点: 1. 提高了代码的复用性,节省资源. 2. 增强代码的灵活性. 缺点: 1. 不易维护,让代码变得复杂. 2. 当模板使用错误产生错误信息时,不容易找到错误信息的根源.
我们发现,在内置类型的拷贝或赋值与自定义类型是不同的,自定义类型的不能用memcpy函数直接去拷贝,而是要自己去手动实现这个过程.怎么实现在调用的时候可以及时的找到对应的类型呢? 答案是通过萃取的方法可以实现. 下面是一个小小的例子:
//模板的萃取 #include<string> struct __TrueType {}; struct __FalseType {}; template<class T> struct TypeTraits { typedef __FalseType IsPodType; }; template<> struct TypeTraits<int> { typedef __TrueType IsPodType; }; template<> struct TypeTraits<char> { typedef __TrueType IsPodType; }; template<class T> T* __TypeCopy(T * dst,const T * src,size_t n,__FalseType) { size_t i=0; for(i=0;i<n;++i) { dst[i]=src[i]; } cout<<"operator=()->"<<endl; return dst; } template<class T> T* __TypeCopy(T * dst,const T * src,size_t n,__TrueType) { cout<<"memcpy->"<<endl; return (T*)memcpy(dst,src,sizeof(T)*n); } template<class T> void TypeCopy(const T* src,T* dest,size_t n) { __TypeCopy(dest,src,n, typename TypeTraits<T>::IsPodType()); } //特化+内嵌----->特化 void Test() { int arr[5] = {1,2,3,4,5}; int arr1[5] = {0}; std::string str[4] = {"aaaa","bbbb","cccc","dddd"}; std::string str1[4] = {""}; TypeCopy(arr1,arr,5); //首先根据对象的类型去推演T的类型-->__TrueType, //含有该类型的特化版本,就会去掉用它的拷贝方法 TypeCopy(str1,str,4); //过程:由str1,str去推演T的类型--->__FalseType-->调用它的拷贝过程 }下面是一道拓展思维的题: 不使用乘除,for,while,等循环和递归来计算1+2+3+4+….+n的值; 方法1:
class Add_1_to_n { public: Add_1_to_n() { ret = ret + i; ++i; } static int sum; static int i; }; int Add_1_to_n::sum = 0; int Add_1_to_n::i = 1; void Test() { Add_1_to_n* p = new Add_1_to_n[10]; cout<<Add_1_to_n::sum<<endl; delete[] p; }方法2:在编译期间就已经解决问题. 缺点;当n太大时,就会因为递归的层数太深而程序崩溃,无法计算出来
tempalate<size_t N> class Add_1_to_n { public: enum { sum = Add_1_to_n(N - 1)::sum + N; }; }; template<> class Add_1_to_n<1> { public: enum { sum = 1; }; }; void test { cout<<Add_1_to_n<10>::sum<<endl; }方法3:鬼畜方法(反正我是想不到~~~) 缺点:因为栈比较小,所以当N太大时,就会导致栈溢出
char arr[N][N + 1] = {0}; cout<<(sizeof(arr))>>1)<<endl;若读者有更好的方法:请指教