提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

前言

一、实验内容

二、实验过程

1.模板函数

1.1 一般模板函数

1.1 特化模板函数

2.类模板(Queue)

2.1 类模板

2.2 成员模板函数的实现

2.3 模板函数特化

2.4 模板类的特化

3.智能指针

3.1 智能指针的创建

3.2 测试智能指针

总结


前言

本次实验将实现模板类,模板函数,模板函数特化,智能指针的设计,并对其进行相应测试。


一、实验内容

一、模板函数(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,一个doubleif(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;elsereturn 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,9cout<<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;  //特化模板类QueueItemdelete 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;  //特化模板类QueueItemdelete head->item;          //释放char *数据head = head->next;delete p;                   //释放指针空间   }void destroy(){while (!isEmpty()){Pop();}head = tail =NULL;}bool isEmpty() const{if(head==NULL)return true;elsereturn 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;//在自身指向别人之前,自身指向的变量用户数减1decrUser();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在赋值过程中直接剥夺原对象对内存的控制权,转交给新对象,再将原对象指针置为空,当再次访问原对象时就会报错。

C++ 模板类与智能指针相关推荐

  1. 智能指针:-和*运算符重载 + 模板技术 实现智能指针(C++)

    智能指针介绍 在C++中,我们都知道在堆区new 开辟的内存,必须通过delete 进行内存释放,不然会形成内存泄漏.有时候我们使用了new 后在 写了很多代码,忘记delete 也是很正常的.那么我 ...

  2. 163-C++学习第十三弹(纯虚函数,模板,唯一性智能指针)

    纯虚函数 被指明,但不具体实现的虚拟函数 什么是抽象类? 含有纯虚函数的类我们就叫抽象类 由纯虚函数来确定它作为抽象类 下面这种做法,编译不通过 C语言里面有没有抽象类型? void(无类型) 不能定 ...

  3. C++代理类,句柄(智能指针)_C++沉思录笔记

    代理类 首先定义三个类: class Animal{ public:virtual void getName()=0;virtual void clone()=0; };class Cat:publi ...

  4. 【c++手记】句柄类智能指针

    很多同学学习c++都会看的一本经典教材<Primer> 而在面向对象里面提及到一种概念-智能指针,而往往同学会出现以下的问题 [问题] 智能指针是不是一种指针? stl里面的智能指针是什么 ...

  5. C++ 智能指针(unique_ptr / shared_ptr)代码实现

    文章目录 unique_ptr 智能指针的实现 shared_ptr 智能指针的实现 指针类型转换 unique_ptr 智能指针的实现 一个对象只能被单个unique_ptr 所拥有. #inclu ...

  6. C++ STL 四种智能指针

    文章目录 0.前言 1.unique_ptr 2.auto_ptr 3.shared_ptr 3.1 shared_ptr 简介 3.2 通过辅助类模拟实现 shared_ptr 4.weak_ptr ...

  7. 指针 转 智能指针_智能指针-它们真的那么聪明吗?

    指针 转 智能指针 Smart pointers - Are they really that smart? 智能指针-它们真的那么聪明吗? You might have heard somethin ...

  8. 智能指针相关问题解答

    Q.智能指针都有哪些呢? C++11之前:auto_ptr C++11:unique_ptr  share_ptr   weak_ptr Boost库:scrope_ptr   share_ptr   ...

  9. 【C++ Primer 第5版 笔记】第12章 动态内存与智能指针

    转载:http://blog.csdn.net/wwh578867817/article/details/41866315 第 12 章 动态内存 与 智能指针 静态内存 用来保存:(1)局部stat ...

最新文章

  1. 如何用 Windows Live Writer 和 Word 2013 分别发表博客到Cnblog 和CSDN
  2. redis高并发抽奖
  3. Java NIO之通道
  4. 五十六、从高中碾转相除法、更相减损术算法谈起
  5. 为什么on用的时候会失效?
  6. 生物信息之ME, HMM, MEMM, CRF
  7. tcga癌症亚型获取_亚型多态性应用于元组的危险
  8. python3 读取文本文件_python3读取文件最简单的办法
  9. 利用VBA导出幻灯片为图片
  10. 购买云服务器时如何选择适合的数据库?
  11. win7 配置jdk
  12. 【2019 BAPC - D】Deck Randomisation【中国剩余定理 + 循环节】
  13. android简单小游戏开发工具,傻瓜化开发Android小游戏
  14. 一个工业相机通用类解决大部分业内流行相机的访问(基于大华相机的动态链接库开发的通用相机类)C#版
  15. mysql前缀索引 默认长度_如何确定前缀索引的长度?
  16. node.js + Electron 调用 Windows API 踩坑日记
  17. 大数据技术原理与应用(第三章 分布式文件系统HDFS)
  18. 2022年湖南省自考考试学前儿童发展练习题及答案
  19. KEPServerEX 6.9 之 Fanuc Focas 驱动-CNC Data的使用(中文版)
  20. UE4课堂笔记——《UE4C++游戏开发入门教程!》第一期开场,C++必须了解小知识

热门文章

  1. 光源、照明方法、相机与镜头配套原则、PPI
  2. Windows10中添加或删除开机自启动项目
  3. python 高斯白噪声-python白噪声
  4. misc之掀桌子,stegano
  5. CSS 不规则的标题框样式
  6. pygame 弹球游戏
  7. 雷军成为北京冬奥会火炬手:这是终生难忘的时刻! 文末
  8. JAP关联MyBatis
  9. Python---GUI
  10. Android Room数据库,不会你就Out了