- 一、模板函数
- 1.1一般模板函数
- 1.2特化模板函数
- 二、模板类
- 2.1 模板类
- 2.2 成员函数模板
- 2.2 模板特化
- 三、智能指针
- 3.1 构造函数
- 3.2 析构函数
- 3.3 拷贝构造函数
- 3.4 运算符重载
- 四、总结
模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。
模板是一种对类型进行参数化的工具;
通常有两种形式:函数模板和类模板;
函数模板针对仅参数类型不同的函数;
类模板针对仅数据成员和成员函数类型不同的类。
使用模板的目的就是能够让程序员编写与类型无关的代码。比如编写了一个交换两个整型int 类型的swap函数,这个函数就只能实现int 型,对double,字符这些类型无法实现,要实现这些类型的交换就要重新编写另一个swap函数。使用模板的目的就是要让这程序的实现与类型无关,比如一个swap模板函数,即可以实现int 型,又可以实现double型的交换。模板可以应用于函数和类。
注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
1、函数模板的格式:
template返回类型 函数名(参数列表) { 函数体 }
其中template和class是关见字,class可以用typename 关见字代替,在这里typename 和class没区别,<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名。模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。比如swap的模板函数形式为:
templatevoid swap(T& a, T& b){},
当调用这样的模板函数时类型T就会被被调用时的类型所代替,比如swap(a,b)其中a和b是int 型,这时模板函数swap中的形参T就会被int 所代替,模板函数就变为swap(int &a, int &b)。而当swap(c,d)其中c和d是double类型时,模板函数会被替换为swap(double &a, double &b),这样就实现了函数的实现与类型无关的代码。
2、注意:对于函数模板而言不存在 h(int,int) 这样的调用,不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,即只能进行 h(2,3) 这样的调用,或者int a, b; h(a,b)。
3、实例
int compare(int a, int b) {
if(a > b)
return 1;
else if(a < b)
return -1;
else
return 0;
}
int compare1(double a, double b) {
if(a > b)
return 1;
else if(a < b)
return -1;
else
return 0;
}
可以看到当我们需要比较整型数值a和b的大小时,调用compare()函数;需要比较浮点类型的数值a和b的大小时就调用compare1()函数;而这两个函数只有参数类型不同,其它部分都相同,当需要比较更多类型的值时就需要定义很多的函数,这使得代码冗余,也浪费内存空间。所以我们可以使用函数模板来解决这一问题。
templateint compare(T a, T b) { if(ab) { return 1; } else { return 0; } } int main() { int a = 1; int b = 2; double a1 = 5.6, b1 = 6.6; cout << compare(a, b) << endl; cout << compare(a1, b1) << endl; return 0; }
在这里,调用compare(a,b)时,a和b是int 型,这时模板函数compare中的形参T就会被int 所代替,模板函数就变为compare(int &a, int &b);同理,调用compare(a1,b1)时,模板函数就变为compare(double &a, double &b)。
1.2特化模板函数针对特化的对象不同,分为两类:函数模板的特化和类模板的特化
1.函数模板的特化:当函数模板需要对某些类型进行特化处理,称为函数模板的特化。
2.类模板的特化:当类模板内需要对某些类型进行特别处理时,使用类模板的特化。
特化整体上分为全特化和偏特化
1.全特化:就是模板中模板参数全被指定为确定的类型。全特化也就是定义了一个全新的类型,全特化的类中的函数可以与模板类不一样。
2.偏特化:就是模板中的模板参数没有被全部确定,需要编译器在编译时进行确定。
注意:
模板函数只能全特化,没有偏特化。
模板类是可以全特化和偏特化的。
函数模板的特化:当函数模板需要对某些类型进行特别处理,称为函数模板的特化。
#include#include using namespace std; //函数模板 template bool IsEqual(T t1,T t2){ return t1==t2; } template<> //函数模板特化 bool IsEqual(char *t1,char *t2){ return strcmp(t1,t2)==0; } int main(int argc, char* argv[]) { char str1[]="abc"; char str2[]="abc"; cout<<"函数模板和函数模板特化"< 运行结果:
二、模板类 2.1 模板类
当没有函数模板特化时:
当函数模板特化时:
可以知道,当函数模板没有特化时,得到的结果是错的,所以需要特化处理;另外,当函数调用发现有特化后的匹配函数时,会优先调用特化的函数,而不再通过函数模版来进行实例化。1、类模板的格式为:
templateclass 类名 { ... }; 类模板和函数模板都是以template开始后接模板形参列表组成,模板形参不能为空,一但声明了类模板就可以用类模板的形参名声明类中的成员变量和成员函数,即可以在类中使用内置类型的地方都可以使用模板形参名来声明。比如:
templateclass A{public: T a; T b; T hy(T c, T &d);}; 在类A中声明了两个类型为T的成员变量a和b,还声明了一个返回类型为T带两个参数类型为T的函数hy。
2、类模板对象的创建:比如一个模板类A,则使用类模板创建对象的方法为A m;在类A后面跟上一个<>尖括号并在里面填上相应的类型,这样的话类A中凡是用到模板形参的地方都会被int 所代替。当类模板有两个模板形参时创建对象的方法为A
m;类型之间用逗号隔开。 3、对于类模板,模板形参的类型必须在类名后的尖括号中明确指定。比如A<2> m;用这种方法把模板形参设置为int是错误的(编译错误:error C2079: ‘a’ uses undefined class ‘A’),类模板形参不存在实参推演的问题。也就是说不能把整型值2推演为int 型传递给模板形参。要把类模板形参调置为int 型必须这样指定A m。
4、在类模板外部定义成员函数的方法为:
template<模板形参列表> 函数返回类型 类名<模板形参名>::函数名(参数列表){函数体},比如有两个模板形参T1,T2的类A中含有一个void h()函数,则定义该函数的语法为:
templatevoid A ::h(){} 注意:当在类外面定义类的成员时template后面的模板形参应与要定义的类的模板形参一致。模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
5.实例
实现队列的出队、入队、销毁队的模板类Queue,然后通过具体参数类型对类模板进行实例化并实现相关数据类型的数据处理,具体代码如下。queue.h文件
templateclass Queue; //声明偏特化模板类queue template class QueueItem{ //定义偏特化模板类queueitem QueueItem(const Type &t):item(t), next(0){} //构造函数,初始化item和next Type item; QueueItem * next; friend class Queue ; //定义友元类queue friend ostream& operator<<(ostream& os, const Queue &q); public: QueueItem * operator++(){ return next; } Type & operator*(){ return item; } }; template class Queue{ //定义偏特化模板类queue public: Queue():head(0),tail(0){} Queue(const Queue& q):head(0),tail(0){ copy_items(q); } template //定义模板函数,改变变量类型为it 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; //如果对象queueitem是空的,则为其生成一个队列,头为当前的queue对象 }else{ //如果对象不为空,则在其队列的后面加上此queue对象 tail->next = pt; tail = pt; } } template<> //全特化函数,定义当为字符类型时的函数模板 inline void Queue ::push(const char * const &val); template<> inline void Queue ::pop(); template void Queue ::copy_items(const Queue &orig){ //复制一个和传入的queueitem对象一样的队列 for(QueueItem *pt = orig.head;pt;pt=pt->next){ push(pt->item); } } template Queue & Queue ::operator=(const Queue& q) //重载=,使其能有copy_item函数的功能 { 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; } } queue.cpp文件
#include "queue.h" #include// template<> // int compare (const char * const &v1, const char * const &v2) // { // return strcmp(v1,v2); // } template<> void Queue ::push(const char * const &val){ char* new_item = new char[strlen(val)+1]; strncpy(new_item,val,strlen(val)+1); QueueItem * pt = new QueueItem (new_item); if(empty()){ head=tail=pt; }else{ tail->next = pt; tail = pt; } } template<> void Queue ::pop(){ QueueItem * p = head; delete head->item; head = head->next; delete p; } main.cpp文件
int main() { TestQueue(); // TestAutoPtr(); return 0; }输出结果:
2.2 成员函数模板
queue.h文件中定义了Queue类的一个成员函数模板,它用于将数组中某个区间的元素添加到队列中。
templateQueue(It beg, It end) : head(0), tail(0) { copy_items(beg, end); } 函数实现:
templatetemplate void Queue ::copy_items(It beg, It end) { while (beg != end) { //将数组中下标对应的值添加到队列中 push(*beg); ++beg; } } test_queue.cpp中的代码:
short a[5] = {0,3,6,9}; Queueqi(a,a+5); cout< 运行得到:
2.2 模板特化
1.模板成员函数特化:给2.1中实例的pop()和push()方法进行特化,使其能够实现字符串的出栈和入栈操作,实现如下:
template<> void Queue::push(const char * const &val){ char* new_item = new char[strlen(val)+1]; strncpy(new_item,val,strlen(val)+1); QueueItem * pt = new QueueItem (new_item); if(empty()){ head=tail=pt; }else{ tail->next = pt; tail = pt; } } template<> void Queue ::pop(){ QueueItem * p = head; delete head->item; head = head->next; delete p; } 对应的测试函数test_queue.h文件中的语句:
q1.push("I am"); q1.push("Luo"); q1.push("Xiaobin"); cout<运行结果:
也可以通过下列方法实现:template<> class Queue{ public: void Push(const char* const& val) { real_que.Push(val); } void Pop() { real_que.Pop(); } string & front(){return real_que.front();} friend ostream & operator<<(ostream& os,Queue &que) { os< real_que;// 用string来存储 }; 2.模板类特化
类模板的特化:与函数模板类似,当类模板内需要对某些类型进行特别处理时,使用类模板的特化。
这里归纳了针对一个模板参数的类模板特化的几种类型:
一是特化为绝对类型;
二是特化为引用,指针类型;
三是特化为另外一个类模板。实例:
#pragma once #include#include main.cpp
#include#include "template.h" using namespace std; int main() { TC tchar; tchar.funtest(); TC tint; tint.funtest(); TC tdouble; tdouble.funtest(); } 输出结果:
三、智能指针 3.1 构造函数
模板总结:
优点:
模板复用了 代码, 节省资源, 更快的迭代开发, C++的标准模板库(STL) 因此而产生。
增强了 代码的灵活性。
缺点:
模板让代码变得凌乱复杂, 不易维护, 编译代码时间变长。
出现模板编译错误时, 错误信息非常凌乱, 不易定位错误。template3.2 析构函数class AutoPtr { public: //构造函数 AutoPtr(T* pData); }; template < class T> AutoPtr ::AutoPtr(T* pData) { m_pData = pData; //初始化用户数为1 m_nUser = new int(1); } template3.3 拷贝构造函数class AutoPtr { public: //声明周期结束时调用析构函数 ~AutoPtr(); }; template < class T> AutoPtr ::~AutoPtr() { decrUser(); } template3.4 运算符重载class AutoPtr { public: //拷贝构造函数 AutoPtr(const AutoPtr & h); }; template < class T> AutoPtr ::AutoPtr(const AutoPtr & h) { m_pData = h.m_pData; m_nUser = h.m_nUser; //用户数加1 (*m_nUser)++; } templateclass AutoPtr { public: //等号的重载 AutoPtr & operator=(const AutoPtr & h); //用户数减1 void decrUser(); //指针运算符重载(返回的指针允许被改变) T* operator ->() { return m_pData; } //返回一个对象(能使用成员运算符(".")来访问成员变量) T& operator*() { return *m_pData; } const T& operator *() const { return *m_pData; } //指针运算符重载(返回的指针不允许被改变) const T* operator -> () const { return m_pData; } private: //存储数据 T* m_pData; //存储用户数 int* m_nUser; }; template < class T> AutoPtr & AutoPtr ::operator=(const AutoPtr & h) { decrUser(); m_pData = h.m_pData; m_nUser = h.m_nUser; (*m_nUser)++; } template < class T> void AutoPtr ::decrUser() { --(*m_nUser); if ((*m_nUser) == 0) { //删除数据 delete m_pData; //地址赋为空 m_pData = 0; delete m_nUser; m_nUser = 0; } } 主函数调用:
template < class T> AutoPtr四、总结::~AutoPtr() { decrUser(); } #include"cmatrix.h" void TestAutoPtr() { //创建一个CMatrix类的指针并交给智能指针类进行管理 AutoPtr h1(new CMatrix); double data[6] = {1,2,3,4,5,6}; //生成一个2行3列的数组 h1 -> Create(2, 3, data); cout << *h1 << endl; //h2(拷贝构造函数的使用)和h1指向的是同一个地方 AutoPtr h2(h1); (*h2).Set(0, 1, 10); cout << *h1 << *h2 << endl; } int main() { TestAutoPtr(); return 0; } 1.模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。
2.模板的特化分为:全特化和部分特化或片特化。
区别:如果特化后的版本,不再有模板参数,就是全特化;仍然有模板参数,就是部分特化。C++的类没有重载,所以类只能依靠特化来实现多态。3.智能指针是用以解决大量代码在申请内存之后的释放问题,智能指针是一个类,可以通过将普通指针作为参数传入智能指针的构造函数实现绑定。只不过通过运算符重载让它“假装”是一个指针,也可以进行解引用等操作。既然智能指针是一个类,对象都存在于栈上,那么创建出来的对象在出作用域的时候(函数或者程序结束)会自己消亡,所以在这个类中的析构函数中写上delete就可以完成智能的内存回收。



