目录
1. 基础概念
2. 模板函数
2.1 一般模板函数
2.2 特化模板函数
3. 类模板
3.2 成员模板函数
3.3 类模板特化
4. 模板类AutoPtr
4.1 构造函数
4.2 析构函数
4.3 运算符重载
5. 小结
1. 基础概念
模板定义:模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。 模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。
模板分类:函数模板和类模板。函数模板针对参数类型不同的函数;类模板仅针对数据成员和成员函数类型不同的类。
使用模板目的:让程序员编写与类型无关的代码。
注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,如不能在main函数中声明或定义一个模板。
2. 模板函数
2.1 一般模板函数
函数模板是通用的函数描述,它们使用泛型来定义函数,其中的泛型可用具体的类型替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。由于模板允许以泛型(而不是具体类型)的方式编写程序,因此有时候也被称为通用编程。下面是一段一般模板函数:
templateint compare(const T &v1, const T &v2) { if(v1 < v2) return -1; if(v2 > v1) return 1; return 0; }
2.2 特化模板函数
所谓特化,就是将泛型的变得具体化。从字面上来解释,就是为已有的模板参数进行一些使其特殊化的指定,使得以前不受任何约束的模板参数,或受到特定的修饰(例如const变为了指针,甚至是经过别的模板类包装之后的模板类型)或完全被指定了下来。
对于上面函数模板,当实参为两个char指针时,比较的是指针的大小,而不是指针指向内容的大小,此时就需要为该函数模板定义一个特化版本,即特殊处理的版本:
//为实参类型 const char * 提供特化版本 template <> int compare(const char * const &v1, const char * const &v2) { return strcmp(v1, v2); }
compare
3. 类模板
3.1 类模板(queue)
所谓类模板,实际上是建立一个通用类,其数据成员、成员函数的返回值类型和形参类型不具体指定,用一个虚拟的类型来代表。使用类模板定义对象时,系统会实参的类型来取代类模板中虚拟类型从而实现了不同类的功能。定义一个类模板与定义函数模板的格式类似,必须以关键字template开始,后面是尖括号括起来的模板参数,然后是类名。
下面给出一个类模板:
queue.h
#ifndef QUEUE_H #define QUEUE_H #includeusing namespace std; template class Queue; template class QueueItem{ QueueItem(const Type &t):item(t), next(0){} Type item; QueueItem * next; friend class Queue ; friend ostream& operator<<(ostream& os, const Queue &q); public: QueueItem * operator++(){ return next; } Type & operator*(){ return item; } }; template class Queue{ public: Queue():head(0),tail(0){} Queue(const Queue& q):head(0),tail(0){ copy_items(q); } template Queue(It beg, It end):head(0),tail(0){copy_items(beg,end);} template void assign(It beg, It end); Queue& operator=(const Queue&); ~Queue(){destroy();} Type& front() {return head‐>item;} const Type& front() const{return head‐>item;} void push(const Type &); void pop(); bool empty() const{return head==0;} friend ostream& operator<<(ostream& os, const Queue &q){ os<<"< "; QueueItem * p; for(p=q.head;p;p=p‐>next){ os< item<<" "; } os<<">"; return os; } const QueueItem
* Head() const{return head;} const QueueItem * End() const{return (tail==NULL)?NULL:tail‐>next;} private: QueueItem * head; QueueItem * tail; void destroy(); void copy_items(const Queue &); template void copy_items(It beg,It end); }; template void Queue ::destroy() { while (!empty()) { pop(); } } template void Queue ::pop(){ QueueItem * p = head; head = head‐>next; delete p; } template void Queue ::push(const Type& val){ QueueItem * pt = new QueueItem (val); if(empty()){ head = tail = pt; }else{ tail‐>next = pt; tail = pt; } } template<> void Queue ::push(const char * const &val); template<> void Queue ::pop(); template void Queue ::copy_items(const Queue &orig){ for(QueueItem *pt = orig.head;pt;pt=pt‐>next){ push(pt‐>item); } } template Queue & Queue ::operator=(const Queue& q) { destroy(); copy_items(q); } template template void Queue ::assign(It beg, It end) { destroy(); copy_items(beg,end); } template template void Queue ::copy_items(It beg,It end { while(beg!=end){ push(*beg); ++beg; } } //template<> class Queue { //public: // void push(const char* str){real_queue.push(str);} // void pop(){real_queue.pop();} // bool empty() const {return real_queue.empty();} // string front() {return real_queue.front();} // const string &front() const{return real_queue.front();} //private: // Queue real_queue; //}; template int compare(const Type& v1, const Type& v2) { if(v1 v2) return 1 return 0; } template<> int compare (const char * const &v1, const char * const &v2); #endif // QUEUE_H
queue.cpp
#include "queue.h" #include#include #include using namespace std; //template<> //class Queue { //public: // void Push(const char* str){real_queue.Push(str);} // void Pop(){real_queue.Pop();} // bool isEmpty() {return real_queue.isEmpty();} // string front() const {return real_queue.front();} // friend ostream & operator<<(ostream& os, Queue &que){ // os< real_queue; //}; template <> void Queue ::Push( const char * const & str) { char * pVal = new char[strlen(str)+1]; strcpy(pVal,str); QueItem * p = new QueItem (pVal); if(isEmpty()) { head = tail = p; } else { tail‐>next = p; tail = p; } } template<> void Queue ::Pop() { if(isEmpty()) { return; } QueItem * p = head; head = head‐>next; delete []p‐>item; delete p; } template <> int compare(const char* const a, const char* const b) { return strcmp(a,b); } void testTemplate() { double a = 1.2; double b = 1.5; cout< qt; double d = 3.3; qt.Push(1); qt.Push(d); qt.Push(10); cout< qi(data,data+5); cout< vi(data,data+5); qi.assign(vi.begin(),vi.end()); cout< qst; char str[10]; strcpy(str,"I am"); qst.Push(str); strcpy(str,"Zongyue"); qst.Push(str); strcpy(str,"Wang"); qst.Push(str); cout< (str,str1)< (str1,str)< 3.2 成员模板函数
成员模板函数特化:
#includetemplate class Sample { public: void print() {printf("nprint template");} }; void Sample ::print() {printf("nprint int");}; 3.3 类模板特化
你可以用模板实参来特化类模板,和函数模板的重载类似,通过特化类模板,你可以优化基于某种特定类型的实现,或者克服某种特定类型在实例化类模板时所出现的不足,另外,如果要特化一个类模板,你还要特化该类模板的所有成员函数,虽然也可以只特化某个成员函数,但这个做法并没有特化整个类,也就没有特化整个模板。
为了特化一个类模板,你必须在起始处声明一个template<>,接下来声明用来特化类模板的类型,这个类型被用作模板实参,且必须在类名的后面直接指定。
类模板特化:
#includeclass Sample2 { public: template void print() {printf("nSample2 print template");} }; template <> void Sample2::print () {printf("nSample2 print int");} // 调用 int _tmain(int argc, _TCHAR* argv[]) { Sample2 sam2; sam2.print (); sam2.print (); return 0; } 4. 模板类AutoPtr
auto_ptr是C++标准库中(
)为了解决资源泄漏的问题提供的一个智能指针类模板(注意:这只是一种简单的智能指针)auto_ptr的实现原理其实就是RAII(Resource Application Immediately Initialize),在构造的时候获取资源,在析构的时候释放资源,并进行相关指针操作的重载,使用起来就像普通的指针。使用auto_ptr作为成员变量,以避免资源泄漏。为了防止资源泄漏,我们通常在构造函数中申请,析构函数中释放,但是只有构造函数调用成功,析构函数才会被调用,换句话说,如果在构造函数中产生了异常,那么析构函数将不会调用,这样就会造成资源泄漏的隐患。比如,如果该类有2个成员变量,指向两个资源,在构造函数中申请资源A成功,但申请资源B失败,则构造函数失败,那么析构函数不会被调用,那么资源A则泄漏。为了解决这个问题,我们可以利用auto_ptr取代普通指针作为成员变量,这样首先调用成功的成员变量的构造函数肯定会调用其析构函数,那么就可以避免资源泄漏问题。 4.1 构造函数
构造函数1:explicit auto_ptr(Type* _Ptr = 0) throw( );
auto_ptrpt;//包含一个int*的指针,并初始化为NULL auto_ptr pt(new int(123)); //包含一个int*的指针,并初始化为123的地址 auto_ptr pt = new int(123); //error!构造函数声明为explicit 构造函数2:auto_ptr(auto_ptr
& _Right) throw( ); int* ptr = new int(); auto_ptrpt1(ptr); //构造函数1 auto_ptr pt2(pt1); //将pt1的使用权转给pt2,注意pt1指向NULL了 //pt1调用了本身的release()函数,将内部指针地址传给pt2 构造函数3:template
auto_ptr(auto_ptr& _Right) throw( ); 声明这样一个拷贝构造函数的目的,就是为了派生类指针能转换成基类的指针。例:
class base { }; class Derived : public base { }; auto_ptrpDerived(new Derived); auto_ptr pbase(pDerived); //让这样的代码能通过编译器其本质是为了让,auto_ptr类内部的Derived*转换为base* 4.2 析构函数
析构函数:
~autoptr()//析构函数 { if(_ptr!=NULL) { delete _ptr; } }4.3 运算符重载
等号运算符重载:
AutoPtr& AutoPtr ::operator=(const AutoPtr & h) { decrUser(); m_pData = h.m_pData; m_nUser = h.m_nUser; (*m_nUser)++; } ->运算符重载:
T* operator->()const { return _ptr; }*运算符重载:
//*的重载 T& operator*()const { return *_ptr; }5. 小结
在我们使用类模板时,只有当代码中使用了类模板的一个实例的名字,而且上下文环境要求必须存在类的定义时,这个类模板才被实例化。声明一个类模板的指针和引用,不会引起类模板的实例化,因为没有必要知道该类的定义。也了解到了函数模板针对仅参数类型不同的函数;类模板针对仅数据成员和成员函数类型不同的类,可以显著减小源代码的大小并提高代码的灵活性,而不会降低类型安全。由于模板的可重用性和可扩展性,我们可以实现效率很高的代码。



