提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
本次实验将实现模板类,模板函数,模板函数特化,智能指针的设计,并对其进行相应测试。
一、实验内容
一、模板函数(compare) ? ? ? ? 1.一般模板函数 ? ? ? ? 2.特化模板函数 二、类模板Queue ? ? ? ? 1.类模板(Queue) ? ? ? ? 2.成员模板函数 ? ? ? ? 3.模板特化:模板函数特化、模板成员函数特化、模板类特化 三、模板类AutoPtr ? ? ? ? 1.构造函数 ? ? ? ? 2.析构函数 ? ? ? ? 3.拷贝构造函数 ? ? ? ? 4.等号、->、*等运算符重载 ? ? ? ? 5.6主函数调用AutoPtr
二、实验过程
1.模板函数
int compare(int a,int b)
{
return a>b? a:b;
}
double compare(double a,double b)
{
return a>b? a:b;
}
这两个函数都是用来判断a和b的大小,只有参数类型不同,功能完全一样,类似这种的情况,如果能写一段通用代码适用于多种不同数据类型,便会使代码的可重用性大大提高,从而提高软件的开发效率。使用模板函数就是为了这一目的。只需对模板函数编写一次,然后基于调用函数时提供的参数类型,C++编译器将自动产生相应的函数来正确的处理该类型的数据。
函数模板的定义形式:
template<模板参数表>
类型名? 函数名(参数表)
{
? ? ? ? 函数体的定义
}
1.1 一般模板函数
template<class T> //class关键字表明T是一个类型,在模板定义中class和typename作用相同
int compare(const T &a,const T &b)
{ //使用模板函数,函数的传入参数类型必须相同,即不能一个int,一个double
if(a>b)
return 1;
else if(a<b)
return -1;
else
{
return 0;
}
}
//使用模板函数,如果函数的返回值要与输入参数类型一致,则返回类型也要用模板类型,如果用int则返回结果为整型
void testCompare()
{
double a;
double b;
cout<<"enter a and b:"<<endl;
cin>>a>>b;
cout<<"result is:";
cout<<compare(a,b)<<endl;
}
注:1.函数模板本身在编译时不会生成任何目标代码,只有由模板生成的实例会生成目标代码;
? ? ? ? 2.被多个源文件引用的函数模板,应当连同函数体一同放在头文件中,而不能像普通函数一样只将声明放在头文件中;
? ? ? ? 3.函数指针也只能指向模板的实例,而不能指向模板本身。
运行结果:
?1.1 特化模板函数
template<>
int compare<const char*>(const char * const &v1, const char * const &v2)
{
return strcmp(v1,v2); //比较两个字符串
//根据比较结果返回一个int类型的值。如果返回值小于0,则表示在ASCII码表上v1字符串小于v2字符串
}
void testCompare()
{
const char *v1 = "hello"; //或const char * const v1 = "hello";
//与指针指向的数据类型是const有关还是非const有关,而与指针是const还是非const无关
const char *v2 = "world";
cout<<v1<<endl;
cout<<v2<<endl;
cout<<"compare result is"<<compare(v1,v2)<<endl;//调用特化版本
return 0;
}
注:1.compare后指定特化时的模板形参即const char *类型,就是说在以实参类型const char * 调用函数时,将产生该模板的特化版本。
? ? ? ? 2.形参“const char * const &v1”去掉修饰符const,实际类型是char * &v1,也就是v1是一个引用,一个指向char型指针的引用,即指针的引用,加上const,v1 就是一个指向const char 型指针的const引用;所以不能用一个指向非const 数据的指针调用特化版本,因为数据类型不匹配。
? ? ? ? 3.由于compare<const char*>声明的形参都是const char *,即char *型指针存储的是const 数据,所以不能传递一个存储了非const数据的char *型指针。同时由于编译器对特化版本不进行实参形参的常规转换,所以调用的实参必须与特化版本的声明完全一致,否则将从泛型(一般型)版本进行实例化,或者函数匹配错误。
? ? ? ? 4.被特化的函数模板,无论是否被调用,相关的目标代码都会生成,因此他们的定义放在源文件(.cpp)中,而非头文件中。
运行结果:
2.类模板(Queue)
2.1 类模板
使用模板类可以使用户为类定义一种模式,使得类中的某些数据成员、某些成员函数的参数、返回值或局部变量能取不同类型(包括系统预定义和用户自定义的)。
类模板声明的语法形式:
template <模板参数表>
class 类名
{
? ? ? ? 类成员声明
{
定义一个类模板Queue和管理类QueueItem,实现向队列中增加元素,删除元素等操作。
template<class Type> //Type表示通用数据类型,即可以取任意返回值
class Queue;
template<class Type> //类模板的定义,类模板的使用实际上是将类模板实例化成一个具体的类
class QueueItem
{
Type item;
QueueItem * next;
QueueItem(const Type& val):item(val),next(NULL){} //参数列表构造器方法
friend Queue<Type>; //将队列定义为友元类,方便访问私有变量
friend ostream& operator<<(ostream &os,const Queue<Type> &que);
public:
QueueItem<Type>* operator++()
{
return next; //返回队列下一个元素的指针
}
Type & operator*() //取出存储的元素
{
return item;
}
};
template<class Type>
class Queue
{
public:
Queue();
Queue(const Queue &q);
~Queue();
bool isEmpty(); //判断队列是否为空
void Push(const Type& val); //在队列里面增加元素
void Pop(); //在队列里面删除元素
void destroy(); //清空队列
Type& front(); //输出队列中的第一个元素
void copy_item(const Queue &orig); //拷贝全部队列
template<class It> //成员模板函数
Queue(It beg,It end); //新的有参构造函数
template<class It>
void assign(It beg,It end); //向队列中添加指定数据
template<class It>
void copy_items(It beg,It end); //拷贝部分队列
//访问头部和尾部的函数
const QueueItem<Type>* Head() const{return head;}
const QueueItem<Type>* End() const{return(tail==NULL)? NULL:tail;}
private:
QueueItem<Type> * head; //队列头指针
QueueItem<Type> * tail; //队列尾指针,使用含有模板类组成的QueueItem类需要使用模板声明
friend ostream& operator<<(ostream &os,const Queue<Type> &que)
{
os<<"< ";
for(QueueItem<Type> * p = que.head;p!=NULL;p=p->next)
{
os<<p->item<<" ";
}
os<<">";
return os;
}
};
一个类模板声明自身并不是一个类,只有当被其他代码引用时,模板才根据引用的需要生成具体的类,即类模板的使用-----实例化成一个具体的类
2.2 成员模板函数的实现
定义的语法格式:
template <模板参数表>
类型名 类名<模板参数标识符列表>::函数名(参数表)
成员模板函数同样要写在.h文件里
template <class Type>
Queue<Type>::Queue():head(NULL),tail(NULL) //指针必须初始化
{
}
template <class Type>
Queue<Type>::Queue(const Queue &q):head(NULL),tail(NULL)
{
copy_items(q); //拷贝构造器
}
template <class Type>
Queue<Type>::~Queue()
{
destroy();
cout<<"delete Queue"<<endl;
}
template<class Type>
void Queue<Type>::copy_item(const Queue &orig) //拷贝全部队列
{
for(QueueItem<Type> *pt = orig.head;pt!=NULL;pt=pt->next)
{
push(pt->item);
}
//将队列orig的所有元素插入其他队列,原队列仍然保留
}
template <class Type>
bool Queue<Type>::isEmpty()
{
if(head==NULL)
return true;
else
return false;
}
template <class Type>
void Queue<Type>::Push(const Type& val)
{
QueueItem<Type> * pt = new QueueItem<Type>(val);
if(isEmpty())
{
head = tail = pt;
}
else
{
tail->next = pt;
tail = pt;
}
}
template<class Type>
void Queue<Type>::Pop()
{
QueueItem<Type> * p = head;
head = head->next;
delete p; //释放空间
}
template<class Type>
Type& Queue<Type>::front()
{
Type ans = 0;
if(!isEmpty())
{
return head->item;
}
else
{
cout<<" Queue is NULL!"<<endl;
return ans;
}
}
template<class Type>
void Queue<Type>::destroy()
{
while (!isEmpty())
{
Pop();
}
head = tail =NULL;
}
template<class Type>
template<class It>
Queue<Type>::Queue(It beg,It end):head(NULL),tail(NULL)
{
copy_items(beg,end);
}
template<class Type>
template<class It>
void Queue<Type>::assign(It beg,It end)
{
destroy();
copy_items(beg,end);
}
template<class Type>
template<class It>
void Queue<Type>::copy_items(It beg,It end)
{
for(It it=beg;it!=end;it++)
{
Push(*it);
}
}
对编写的类模板和其成员函数进行测试:
void testQueue()
{
//Queue<int> qt; //为什么用int输出是10,4,20;double输出是10,4.4,20
//因为定义的对象的真实数据类型是int,所以会进行强制类型转换,输出的是4
//类模板定义对象:类模板名<真实数据类型> 对象名;
Queue<double> qt;
double c=4.4;
qt.Push(10);
qt.Push(c);
qt.Push(20);
cout<<qt<<endl;
qt.Pop();
qt.Push(40);
double dd=qt.front();
cout<<dd<<endl;
cout<<qt<<endl;
short data[5] = {0,3,6,9};
Queue<int> qt1(data,data+5);
cout<<qt1<<endl;
while(!qt1.isEmpty())
{
cout<<qt1.front()<<" ";
qt1.Pop();
}
vector<int> vi(data,data+5); //数组
qt1.assign(vi.begin()+1,vi.end()-1); //列表中添加3,6,9
cout<<endl<<qt1<<endl;
return 0;
}
运行结果:
?2.3 模板函数特化
当我们所写的模板无法适应所有数据类型时,就需要对部分函数进行特化,为了能输出想要的字符串结果,对字符串数据进行特化
//争对const char*数据类型对Push()函数进行特化
template<>
void Queue<const char*>::Push(const char * const &val)
{
char* new_item = new char[strlen(val)+1]; //根据字符串长度进行创建字符数组
strncpy(new_item,val,strlen(val)+1); //拷贝字符串内容
QueueItem<const char*> * pt = new QueueItem<const char*>(new_item);
if(isEmpty())
{
head=tail=pt;
}
else{
tail->next = pt;
tail = pt;
}
}
对Pop()函数进行特化
template<>
void Queue<const char*>::Pop()
{
QueueItem<const char*> * p = head; //特化模板类QueueItem
delete head->item; //释放char *数据
head = head->next;
delete p; //释放指针空间
}
?2.4?模板类的特化
针对const char*数据类型对类模板Queue实现特化
template<>
class Queue<const char*> //针对const char*数据类型对Queue实现特化
{
public:
void Push(const char* const &val)
{
//real_que.Push(val); //会先进行类型转换,将const char*转成string类型 //第一种特化
char* new_item = new char[strlen(val)+1]; //根据字符串长度进行创建字符数组
strncpy(new_item,val,strlen(val)+1); //拷贝字符串内容
QueueItem<const char*> * pt = new QueueItem<const char*>(new_item);
if(isEmpty())
{
head=tail=pt;
}
else{
tail->next = pt;
tail = pt;
}
}
void Pop()
{
//real_que.Pop();
QueueItem<const char*> * p = head; //特化模板类QueueItem
delete head->item; //释放char *数据
head = head->next;
delete p; //释放指针空间
}
void destroy()
{
while (!isEmpty())
{
Pop();
}
head = tail =NULL;
}
bool isEmpty() const
{
if(head==NULL)
return true;
else
return false;
}
string & front()
{
return real_que.front();
}
void copy_item(const Queue &orig);
friend ostream & operator<<(ostream& os,Queue<const char*> &que)
{
os<<que.real_que;
return os;
}
Queue& operator=(const Queue& q)
{
destroy();
copy_item(q);
}
private:
Queue<string> real_que;
QueueItem<const char*> *head;
QueueItem<const char*> *tail;
};
测试特化类和特化函数:
void testQueue1()
{
Queue<const char*> qst;
qst.Push("I am");
qst.Push("Ren");
qst.Push("Ruijie");
cout<<endl<<qst<<endl;
qst.Pop();
string str =qst.front();
cout<<endl<<str<<endl;
cout<<endl<<qst<<endl;
}
运行结果:
3.智能指针
3.1 智能指针的创建
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针是一个类,当超出了类的实例对象的作用域时,会自动调用对象的析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
对一个对象进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过构造函数传入普通指针。
将智能指针设为模板类,以便适应多种数据类型,同时它需要有一个全局的计数器绑定指向的内存地址用于判断当前这个地址是否还有比的指针使用它,如果智能指针指向的地址的计数器值为0时,这块地址需要被释放(自动管理)。
template<class T>
class AutoPtr
{
public:
AutoPtr():ptr(0),user(0){};
AutoPtr(T* pData); //构造函数
AutoPtr(const AutoPtr<T>& handle);
~AutoPtr(); //析构函数
AutoPtr<T>& operator=(const AutoPtr<T>& handle); //重载赋值运算符"="
void decrUser(); //减少用户数
T* operator->() //重载赋值运算符"->",返回ptr指针
{
return ptr;
}
const T* operator->() const
{
return ptr;
}
T operator*() const //重载赋值运算符"*",取出指针所指向地址中的内容
{
return *ptr;
}
T* get() const {return ptr;} //返回保存的指针
T getUser() const {return *user;}; //返回保存的用户数
private:
T* ptr = 0; //指向数据的指针
int* user=0; //指向用户数指针,用*是因为需要统一修改
};
//构造函数,构建使用智能指针的某个实例
template<class T>
AutoPtr<T>::AutoPtr(T* pData)
{
ptr = pData;
user = new int(1); //new出一个int对象,并且初始化为1;user = new int[1];new出一个数组
}
//智能指针赋初值为另一个智能指针指向的变量
template<class T>
AutoPtr<T>::AutoPtr(const AutoPtr<T>& handle)
{
ptr = handle.ptr;
user = handle.user;
(*user)++; //变量的用户数+1
}
template<class T>
AutoPtr<T>::~AutoPtr()
{
decrUser();
}
template<class T>
AutoPtr<T>& AutoPtr<T>::operator=(const AutoPtr<T>& handle) //重载赋值运算符
{
//自己引用自己则用户数不变
if(this==&handle)
return *this;
//在自身指向别人之前,自身指向的变量用户数减1
decrUser();
ptr = handle.ptr;
user = handle.user;
(*user)++;
return *this;
}
//用户数减少时应该判断是否释放变量内存(=0时)
template<class T>
void AutoPtr<T>::decrUser()
{
(*user)--;
if((*user)==0)
{
delete ptr;
ptr = 0;
delete user;
user = 0;
cout<<"release"<<endl;
}
}
3.2 测试智能指针
void TestAutoPtr()
{
int s1 = 20;
AutoPtr<int> Ap1(new int(10));
AutoPtr<int> Ap2(new int(s1));
if(Ap1.get()==0)
{
cout<<"Ap1 is NULL"<<endl;
}
cout<<"before:Ap1 value is: "<<Ap1.get()<<endl;
if(Ap2.get()==0)
{
cout<<"Ap2 is NULL"<<endl;
}
cout<<"before:Ap2 value is: "<<Ap2.get()<<endl;
Ap1 = Ap2;
cout<<"after:Ap1 value is: "<<Ap1.get()<<endl;
cout<<"after:Ap2 value is: "<<Ap2.get()<<endl;
int s2 = s1;
AutoPtr<int> Ap3(new int(s2));
cout<<"before:Ap3 value is: "<<Ap3.get()<<endl;
cout<<"after2:Ap1 value is: "<<Ap1.get()<<endl;
Ap3=Ap1;
cout<<"after3:Ap1 value is: "<<Ap1.get()<<endl;
cout<<"after:Ap3 value is: "<<Ap3.get()<<endl;
cout<<"Ap3->data:"<<*Ap3<<endl;
cout<<"Ap3-user:"<<Ap3.getUser()<<endl;
}
运行结果:
Ap1开始指向的数据是?10,地址为0x10f1980,Ap2指向的数据是20,地址是0x10f1c80,然后执行Ap1=Ap2,让Ap1指向Ap2,地址变为0x10f1c80,创建新的指针Ap3,数据为s2 = s1=20,分配的地址为?0x10f1d00,接着执行Ap3=Ap1,Ap3地址变为0x10f1c80,与Ap1指向同一个数据s1=20,此时Ap1,Ap2,Ap3都指向s1,用户数为3,生命周期结束,执行析构函数,释放三个指针。
总结
1.当类模板碰到继承时,当子继承的父类是一个类模板时,子类在声明的时候要指定出父类中T的类型,如果不指定,编译器无法给子类分配内存,如果想灵活指定出父类中T的类型,则子类也需要变为类模板。
2.类模板及其成员函数的实现写在.h文件里,特化的模板写在.cpp文件里。
3.类模板代表了一类类,模板类表示某一具体的类;如:Queue是一个类模板,Queue<int>,Queue<double>是实例化出的两个模板类,qt和qt1是
模板类生成的对象。
4.智能指针在函数结束时自动释放内存空间,不需要手动释放内存空间,在内存泄漏方面起到很好的作用,但是AutoPtr在赋值过程中直接剥夺原对象对内存的控制权,转交给新对象,再将原对象指针置为空,当再次访问原对象时就会报错。
|