目录
一、模板函数
举一个最简单的例子,为了比较两个整型变量的值,需要写下面的 Compare 函数:
int Compare(const int& a, const int& b)
{
if(a > b) return 1;
if(a < b) return -1;
return 0;
}
为了比较两个 double 型变量的值,还需要编写下面的 Compare 函数:
int Compare(const double& a, const double& b)
{
if(a > b) return 1;
if(a < b) return -1;
return 0;
}
如果还要比较两个 char 型变量的值,比较两个 CStudent 类对象的值等等,这些都需要我们再编写 Compare 函数。而这些 Compare 函数除了处理的数据类型不同外,形式上都是一样的。能否只写一遍 Compare 函数,就能用来交换各种类型的变量的值呢?继承和多态显然无法解决这个问题。因此,“模板”的概念就应运而生了。
函数模板的写法如下:
template <class 类型参数1, class类型参数2, ...>
返回值类型 模板名(形参表)
{
函数体
}
其中的 class 关键字也可以用 typename 关键字替换,例如:
template <typename 类型参数1, typename 类型参数2, ...>
1、一般模板函数
// 一般模板函数
template <class Type>
int Compare(const Type& a, const Type& b){
if(a > b) return 1;
if(a < b) return -1;
return 0;
}
Type是类型参数,代表类型。编译器由模板自动生成函数时,会用具体的类型名对模板中所有的类型参数进行替换,其他部分则原封不动地保留。同一个类型参数只能替换为同一种类型。编译器在编译到调用函数模板的语句时,会根据实参的类型判断该如何替换模板中的类型参数。
#include <iostream>
using namespace std;
template <class Type>
int Compare(const Type& a, const Type& b){
if(a > b) return 1;
if(a < b) return -1;
return 0;
}
int main()
{
int a = 1, b = 1;
cout << Compare(a, b) << endl; //编译器自动生成 void Compare(const int&, const int&)函数
double c = 1.1, d = 2.2;
cout << Compare(c, d) << endl; //编译器自动生成 void Compare(const double&, const double&)函数
return 0;
}
对于该函数模板,当实参为两个char指针时,比较的是指针的大小,而不是指针指向内容的大小,此时就需要为该函数模板定义一个特化版本,即特殊处理的版本。
2、特化模板函数
特化模板函数是在一个统一的函数模板不能在所有类型实例下正常工作时,需要定义类型参数在实例化为特定类型时函数模板的特定实现版本。
// 特化模板函数
template <>
int Compare<const char *>(const char * const &v1, const char * const &v2)
{
return strcmp(v1, v2);
}
二、模板类
1、模板类
C++除了支持函数模板,还支持类模板(Class Template)。函数模板中定义的类型参数可以用在函数声明和函数定义中,类模板中定义的类型参数可以用在类声明和类实现中。类模板的目的同样是将数据的类型参数化。
声明类模板的语法为:
template<typename 类型参数1 , typename 类型参数2 , …>
class 类名{
类成员声明
};
类模板和函数模板都是以 template 开头,后跟类型参数;类型参数不能为空,多个类型参数用逗号隔开。一但声明了类模板,就可以将类型参数用于类的成员函数和成员变量了。换句话说,原来使用 int、float、char 等内置类型的地方,都可以用类型参数来代替。
template <class Type> class Queue; //模板类Queue
template <class Type> class QueueItem{ //模板类QueueItem
private:
QueueItem(const Type& t):item(t),next(0){}
Type item;
QueueItem *next;
friend class Queue<Type>; //友元类Queue
friend ostream& operator<<(ostream& os, const Queue<Type> &q); //友元函数,对操作符<<进行重载
public:
QueueItem<Type>* operator++(){ //对++操作符重载,获取下一个
return next;
}
Type& operator*(){ //对*操作符重载,返回当前item
return item;
}
};
2、成员模板函数
①普通类的成员模板函数
不管是普通类,还是类模板,它的成员函数可以是一个函数模板,称为成员函数模板,不可以是虚函数,否则编译器报错。
class A
{
public:
template<typename Type>
void print(const Type& a)
{
cout << a << endl;
}
};
int main()
{
A a;
a.print(5); //编译器自动推断类型来实例化这个函数模板
return 0;
}
②类模板的成员模板函数
- 类模板的模板参数必须用<>指定,成员函数模板(函数模板)的参数可以由编译器自动推断。
- 类模板的成员函数(包括普通成员函数/成员函数模板)只有为程序所用才进行实例化。
- 如果某函数从未被使用,则不会实例化该成员函数。
// Queue.h
// 模板类Queue —— 队列
template <class Type> class Queue{
private:
QueueItem<Type> *head; //头
QueueItem<Type> *tail; //尾
void destroy(); //销毁队列
void copy_items(const Queue& orig);
template<class It>
void copy_items(It begin, It end);
public:
Queue():head(0),tail(0){} //缺少构造函数
Queue(const Queue& q):head(0),tail(0){//拷贝构造函数
copy_items(q);
}
template<class It> //成员模板函数
Queue(It begin, It end):head(0),tail(0){
copy_items(begin, end);
}
template<class It> //成员模板函数
void assign(It begin, It end);
Queue& operator= (const Queue& q); //运算符重载
~Queue(){ destroy(); } //析构函数
Type& front(){ return head->item; }//获取队列头元素
const Type& front() const;
void push(const Type& val); //入队
void pop(); //出队
bool empty() const{ return head == 0; } //判断队列是否为空
friend ostream& operator<<(ostream& os, const Queue<Type>& q); //友元函数,对操作符<<进行重载
const QueueItem<Type>* Head() const{ return head; } //返回队列头
const QueueItem<Type>* End() const{ return (tail==NULL)?NULL:tail->next; }//返回队列尾
};
3、模板特化
①模板函数特化
特化模板函数是在一个统一的函数模板不能在所有类型实例下正常工作时,需要定义类型参数在实例化为特定类型时函数模板的特定实现版本。
// 一般模板函数
template <class Type>
int Compare(const Type& a, const Type& b){
if(a > b) return 1;
if(a < b) return -1;
return 0;
}
// 特化模板函数
template <>
int Compare<const char *>(const char * const &v1, const char * const &v2)
{
return strcmp(v1, v2);
}
②模板成员函数特化
申明(放在.h文件中) :
template<>
int Compare<const char*>(const char * const &v1, const char * const &v2);
template<>
void Queue<const char*>::push(const char * const &val);
template<>
void Queue<const char*>::pop();
实现(只能放在.cpp文件中):
template <>
int Compare<const char *>(const char * const &v1, const char * const &v2)
{
return strcmp(v1, v2);
}
template<>
void Queue<const char*>::push(const char * const &val){
char* newitem = new char[strlen(val)+1]; //+1是因为最后有一个结束符
strncpy(newitem, val, strlen(val)+1); //复制字符串
QueueItem<const char*> * pt = new QueueItem<const char*>(newitem);
if(empty()){
head = tail = pt;
}
else {
tail->next = pt;
tail = pt;
}
}
template<>
void Queue<const char*>::pop(){
QueueItem<const char*> *p = head;
delete head->item; //new分配的空间,要用delete
head = head->next;
delete p;
}
③模板类特化
模板类特化,与函数模板类似,当类模板内需要对某些类型进行特别处理时,使用类模板的特化。
类模板特化的三种类型:
- 一是特化为绝对类型;
- 二是特化为引用,指针类型;
- 三是特化为另外一个类模板。
// 用于比较的类模板
template <class T>
class Compare{
public :
bool IsEqual(const T& a, const T& b){
return a == b;
}
};
// 特化为绝对类型
template <>
class Compare <double>{
public:
bool IsEqual(const double& a, const double& b)
{
return abs(a - b) < 10e - 6 ;
}
};
// 特化为引用,指针类型
template <class T>
class Compare <T*>{
public:
bool IsEqual(const T *a, const T *b)
{
return Compare<T>::IsEqual(*a, *b);
}
};
// 特化为另外一个类模板
template <class T>
class Compare <vector<T>>{
public:
bool IsEqual(const vector<T>& a, const vector<T>& b){
if(a.size() != b.size()){
return false;
}
else{
for(int i = 0; i < a.size(); i++){
if(a[i] != b[i]) return false;
}
}
return true;
}
};
4、模板类Queue代码实现
// queue.h
#include <iostream>
using namespace std;
template <class Type> class Queue; //模板类Queue
template <class Type> class QueueItem{ //模板类QueueItem
private:
QueueItem(const Type& t):item(t),next(0){}
Type item;
QueueItem *next;
friend class Queue<Type>; //友元类Queue
friend ostream& operator<<(ostream& os, const Queue<Type> &q); //友元函数,对操作符<<进行重载
public:
QueueItem<Type>* operator++(){ //对++操作符重载,获取下一个
return next;
}
Type& operator*(){ //对*操作符重载,返回当前item
return item;
}
};
//模板类Queue —— 队列
template <class Type> class Queue{
private:
QueueItem<Type> *head; //头
QueueItem<Type> *tail; //尾
void destroy(); //销毁队列
void copy_items(const Queue& orig);
template<class It>
void copy_items(It begin, It end);
public:
Queue():head(0),tail(0){} //缺少构造函数
Queue(const Queue& q):head(0),tail(0){//拷贝构造函数
copy_items(q);
}
template<class It> //成员模板函数
Queue(It begin, It end):head(0),tail(0){
copy_items(begin, end);
}
template<class It> //成员模板函数
void assign(It begin, It end);
Queue& operator= (const Queue& q); //运算符重载
~Queue(){ destroy(); } //析构函数
Type& front(){ return head->item; }//获取队列头元素
const Type& front() const;
void push(const Type& val); //入队
void pop(); //出队
bool empty() const{ return head == 0; } //判断队列是否为空
friend ostream& operator<<(ostream& os, const Queue<Type>& q){ //友元函数,对操作符<<进行重载
QueueItem<Type>* p;
for(p = q.head; p; p = p->next){
os<<p->item<<" ";
}
return os;
}
const QueueItem<Type>* Head() const{ return head; } //返回队列头
const QueueItem<Type>* End() const{ return (tail==NULL)?NULL:tail->next; }//返回队列尾
};
// 销毁队列
template <class Type>
void Queue<Type>::destroy(){
while(!empty()){
pop();
}
}
// 出队,删除队列第一个元素
template <class Type>
void Queue<Type>::pop(){
QueueItem<Type> *p = head;
head = head->next; //保留头指针后的元素
delete p;
}
// 入队
template<class Type>
void Queue<Type>::push(const Type& val){
QueueItem<Type> *pt = new QueueItem<Type>(val);
if(empty()){ //若队列为空,让头指针和尾指针等于pt
head = tail = pt;
}
else { //若队列非空,未尾指针后移,尾指针等于pt
tail -> next = pt;
tail = pt;
}
}
// 当类型为char,模板成员函数特化,在头文件李声明
template<>void Queue<const char*>::push(const char * const &val);
template<>void Queue<const char*>::pop();
// 复制队列元素
template<class Type>
void Queue<Type>::copy_items(const Queue &orig){
for(QueueItem<Type> *pt = orig.head; pt; pt=pt->next){
push(pt->item);
}
}
// 运算法重载
template<class Type>
Queue<Type>& Queue<Type>::operator=(const Queue& q){
destroy();
copy_items(q);
}
// 区间拷贝复制
template<class Type> template<class It>
void Queue<Type>::assign(It begin, It end)
{
destroy();
copy_items(begin, end);
}
// 区间拷贝复制
template<class Type> template<class It>
void Queue<Type>::copy_items(It begin,It end){
while(begin!=end){
push(*begin);
++begin;
}
}
// queue.cpp
#include "queue.h"
#include<string.h>
template<>
void Queue<const char*>::push(const char * const &val){
char* newitem = new char[strlen(val)+1]; //+1是因为最后有一个结束符
strncpy(newitem, val, strlen(val)+1); //复制字符串
QueueItem<const char*> * pt = new QueueItem<const char*>(newitem);
if(empty()){
head = tail = pt;
}
else {
tail->next = pt;
tail = pt;
}
}
template<>
void Queue<const char*>::pop(){
QueueItem<const char*> *p = head;
delete head->item; //new分配的空间,要用delete
head = head->next;
delete p;
}
// main.cpp
#include <iostream>
#include "queue.h"
#include <vector>
using namespace std;
int main()
{
Queue<int> qt;
double c = 5.3;
qt.push(6);
qt.push(c); //输出为整型
qt.push(10);
cout<<qt;
cout<<endl;
short a[5] = {1,2,3,4};
Queue<int> qi(a,a+5);
cout<<qi;
cout<<endl;
vector<int> vi(a,a+5);
qi.assign(vi.begin(),vi.end());
cout<<qi;
cout<<endl;
Queue<const char*> q1;
q1.push("hello");
q1.push("I am");
q1.push("wendy!");
cout<<q1;
cout<<endl;
Queue<const char*> q2(q1);
cout<<q2;
return 0;
}
运行结果:
三、模板类AutoPtr
C++中我们都会用到指针。我们经常使用malloc或者new申请一块内存,然后用一个指针来保存起来。有内存的申请就必须要对它进行释放的处理,否则会造成最严重的后果——内存泄漏。少量申请的内存我们能够记得去释放,但如果成千上万行代码,全部记住就比较困难,所以智能指针就是为了解决这个问题而产生了。
1、构造函数
template<class T>
class AutoPtr{
public:
AutoPtr(T* pData); //构造函数声明
AutoPtr(const AutoPtr<T>& h); //拷贝构造函数声明
~AutoPtr(); //析构函数
private:
void decrUser(); //减少引用数
T *m_pData; //数据
int *m_nUser; //引用数
};
template <class T>
AutoPtr<T>::AutoPtr(T* pData){ //构造函数实例化
m_pData = pData;
m_nUser = new int(1); //初始时的引用计数为1
}
2、析构函数
~AutoPtr(){ //析构函数
decrUser();
}
template<class T>
void AutoPtr<T>::decrUser() //减少引用数
{
--(*m_nUser);
if((*m_nUser)==0){ //当一个对象的引用为 0 时,这个对象资源被销毁
delete m_pData;
m_pData = 0;
delete m_nUser;
m_nUser = 0;
}
}
3、拷贝构造函数
template<class T>
AutoPtr<T>::AutoPtr(const AutoPtr<T>& h){
m_pData = h.m_pData;
m_nUser = h.m_nUser;
(*m_nUser)++; //被引用或者被拷贝,引用的对象的引用计数要增加1
}
4、等号、->、*等运算符重载
template<class T>
class AutoPtr
{
public:
AutoPtr(T* pData); //构造函数声明
AutoPtr(const AutoPtr<T>& h); //拷贝构造函数声明
~AutoPtr(){ //析构函数
decrUser();
}
AutoPtr<T>& operator=(const AutoPtr<T>& h); //等号运算符重载声明
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:
void decrUser(); //减少引用数
T *m_pData; //数据
int *m_nUser; //引用数
};
AutoPtr<T>& AutoPtr<T>::operator=(const AutoPtr<T>& h) //实例化等号运算符重载
{
if(this == &h) return *this;
decrUser();
m_pData = h.m_pData;
m_nUser = h.m_nUser;
(*m_nUser)++; //引用数加1
return *this;
}
5、主函数调用AutoPtr
#include <iostream>
#include "queue.h"
#include "AutoPtr.h"
#include "CMatrix.h"
#include <vector>
using namespace std;
int main()
{
AutoPtr<CMatrix> h1(new CMatrix); //创建类型为CMatrix的对象h1
double data[9] = {1,2,3,4,5,6,7,8,9};
h1->Create(3,3,data); //创建数据
cout << *h1 << endl;
AutoPtr<CMatrix> h2(h1); //调用拷贝构造函数,创建对象h2
(*h2).Set(0,1,10); //把h2数据的第0行,第1列数值改为10
cout << *h1 << endl << *h2;
return 0;
}
运行结果:
五、实验总结
我们常常需要编写多个形式和功能都相似的函数,因此用函数模板可以减少重复,提高复用性;有时候我们也需要编写多个形式和功能都相似的类,使用类模板更加方便,编译器从类模板可以自动生成多个类,避免了重复劳动。
C++的内存管理是让人头疼的事,当我们写一个new语句时,一般会立即把delete语句也写上,但我们不能避免程序还未执行到delete时就跳转或者在函数中未执行到最后的delete语句就返回的情况,若我们不在每一个可能跳转或者返回的语句前释放资源,就会造成内存泄露。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。
本次实验实践了模板类,用模板类实现队列以及智能指针。模板类可用来创建动态增加或减少的数据结构,灵活性, 可重用性和可扩展性较好,可以大大减少开发时间,但是易读性比较不好,调试比较困难,模板的数据类型只能在编译时才能被确定。