文章目录
前言:模板的定义
我们已经学过重载(Overloading),对重载函数而言,C++的检查机制能通过函数参数的不同及所属类的不同。正确的调用重载函数。例如,为求两个数的最大值,我们定义MAX()函数需要对不同的数据类型分别定义不同重载(Overload)版本。
//函数一
int max1(int a, int b)
{
if(a > b)
return 1;
else if(a < b)
return -1;
else
return 0;
}
//函数二
int max2(double a, double b)
{
if(a > b)
return 1;
else if(a < b)
return -1;
else
return 0;
}
当我们比较两个整型数值的大小时,我们可以调用max1函数来比较;当我们比较两个浮点类型的数值大小时,我们可以调用max2函数来比较。通过分析我们发现,max1函数与max2函数只是在类型上存在区别,而在功能上完全相同。如果需要比较char或者double类型的数值时,我们则又需要定义对应类型的函数,这会占据过多的内存。不仅如此,我们还可能会出现函数定义不全面而出现错误。
所以为了避免以上两种情况的发生,C++引入了模板的概念。模板的概念如下:
模板定义:模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数, 从而实现了真正的代码可重用性。模版可以分为两类,一个是函数模版,另外一个是类模版。
一、函数模板
1.一般函数模板
我们了解了什么叫模板,那么函数模板的写法如下:
template<模板参数表>
类型名 函数名(参数表)
{
函数体
}
说明: template是一个声明模板的关键字,表示声明一个模板关键字class不能省略,如果类型形参多余一个 ,每个形参前都要加class <类型 形参表>可以包含基本数据类型可以包含类类型.
现在我们将上文的两个函数改写为函数模板的形式:
#include<iostream>
using namespace std;
template<class T>
int compare(T a,T b)
{
if(a>b)
return 1;
else if(a<b)
return -1;
else
return 0;
}
int main()
{
int a=1,b=3;
double a1=2.1,b1=2;
cout<<compare(a,b)<<endl;
cout<<compare(a1,b1)<<endl;
return 0;
}
运行结果:
-1
1
在我们规定的函数模板中使用T来代替需要比较的数值类型,即原函数中的实例类型int和double。
当参数的类型确定好之后,编译器根据给定的函数模板生成一个函数,这个过程叫做函数模板的实例化,而实例化得到的函数则被称为函数模版的一个实例。
当主函数第一次调用函数模板时,生成如下的函数:
int max1(int a,int b);
当主函数第二次调用函数模板时,生成如下的函数:
int max1(float a1,float b1);
2.特化函数模板
现在我们使用函数模板来比较字符串数值的大小:
#include<iostream>
using namespace std;
template<class T>
int compare(const T *a, const T *b)
{
cout << a << endl;
cout << b << endl;
if (a > b)
return 1;
else if (a < b)
return -1;
else
return 0;
}
int main()
{
const char* a = "b";
const char* b = "a";
cout << compare(a, b) << endl;
return 0;
}
运行结果:
-1
而正确的输出结果应该是1。这是因为此时比较的是两个指针的大小而不是指针指向的内容的大小。所以,我们就需要一个特化版本:
template < >
int compare<const char*>(const char* const& v1, const char* const& v2)
{
return strcmp(v1, v2);
}
使用该特化函数后程序运行结果为1,输出正确,结合上述案例可知模板特化是在实例化模板时,对特定类型的实参进行特殊处理的一个实例版本,当以特化定义的形参使用模板时,会优先调用特化的函数,而不再通过函数模版来进行实例化。
模板特化的定义形式如下:
template <>
函数名<特化类型>(特化类型 参数1, 特化类型 参数2 , …)
{
函数体
}
二、类模板Queue
1.类模板的定义
类模板声明的语法形式为:
template<模板参数表>
class 类名
{
类成员变量
}
如果需要在类模板以外定义其成员函数,则需使用如下形式:
template<模板参数表>
类型名 类名<模板参数标识符列表>::函数名(参数表){}
2.类模板的声明与使用
定义一个实现队列的出队、入队、销毁队的模板类Queue,然后通过具体参数类型对类模板进行实例化并实现相关数据类型的数据处理,具体代码如下。
queue.h
#ifndef QUEUE_H
#define QUEUE_H
#include <iostream>
using namespace std;
//定义类模板
template<class Type> class Queue;
template<class Type> class QueueItem {
QueueItem(const Type& t) : item(t), next(0) {}
Type item;
QueueItem* next;
friend class Queue<Type>;
//输出运算符的重载
friend ostream& operator<<(ostream& os, const Queue<Type>& q);
QueueItem<Type>* operator++() {
return next;
}
Type& operator*() {
return item;
}
};
template<class Type> class Queue {
public:
Queue() : head(0), tail(0) {}
Queue(const Queue& q) : head(0), tail(0) {
copy_items(q);
}
//成员函数模板
template <class It>
Queue(It beg, It end) : head(0), tail(0) { copy_items(beg, end); }
template<class It> 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<Type>& q) {
os << "< ";
QueueItem<Type>* p;
for (p = q.head; p; p = p -> next) {
os << p->item << " ";
}
os << ">";
return os;
}
const QueueItem<Type>* Head() const { return head; }
const QueueItem<Type>* End() const { return (tail == NULL) ? NULL : tail -> next; }
private:
QueueItem<Type>* head;
QueueItem<Type> *tail;
void destroy();
void copy_items(const Queue&);
template<class It>
void copy_items(It beg, It end);
};
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()) {
head = tail = pt;
}
else {
tail -> next = pt;
//元素添加到队列后tail指针指向该元素
tail = pt;
}
}
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 beg, It end)
{
destroy();
copy_items(beg, end);
}
template<class Type> template<class It> void Queue<Type>::copy_items(It beg, It end)
{
while (beg != end) {
push(*beg);
++beg;
}
}
//销毁队列
template<class Type>
void Queue<Type>::destroy()
{
while (!empty()) {
pop();
}
}
void TestQueue();
#endif
queue.cpp
#include "queue.h"
#include<cstring>
void TestQueue()
{
Queue<int> qt;
int d = 13;
qt.push(1);
qt.push(d);
qt.push(10);
cout << qt;
}
main.cpp
#include<iostream>
#include "queue.h"
using namespace std;
int main() {
TestQueue();
return 0;
}
运行结果:
<1,13,10>
在queue.cpp中Queue qt; 将类模板实例化,根据类模板和参数类型生成对应类型的类,并往队列中添加整型数值的元素1、3、10,然后通过重载后的输出符遍历输出队列中的所有元素。
3.成员函数模板
queue.h 中有如下语句:
template <class It>
Queue(It beg, It end) : head(0), tail(0) { copy_items(beg, end); }
它定义了类中的一个成员函数模板。这个函数用于将数组中某个区间的元素添加到队列中。
对应函数实现如下:
template<class Type> template<class It> void Queue<Type>::copy_items(It beg, It end)
{
while (beg != end)
{
push(*beg);
++beg;
}
}
在TestQueue() 方法中添加代码:
short a[5] = { 0,3,6,9,4 };
Queue<int> qi(a, a + 5);
cout << qi;
运行结果:
< 0 3 6 9 4 >
4. 特化成员函数模板
上边已经介绍了特化函数模板,现在来介绍特化成员函数模板。为模板类Queue的成员函数push() 和pop() 方法定义特化版本,使其能够实现字符串的入队和出队操作,该过程即为成员函数模板特化。将代码添加到queue.cpp中:
template<>
void Queue<const char*>::pop()
{
QueueItem<const char*>* p = head;
head = head -> next;
delete p;
}
template<>
void Queue<const char*>::push(const char* const& val)
{
char* new_item = new char[strlen(val) + 1];
//将val的值赋值到new_item,其中strlen(val)为需要复制的长度
strncpy_s(new_item,strlen(new_item),val, strlen(val));
QueueItem<const char*>* pt = new QueueItem<const char*>(new_item);
if (empty())
{
head = tail = pt;
}
else
{
tail -> next = pt;
//元素添加到队列后tail指针指向该元素
tail = pt;
}
}
添加代码:
Queue<const char*> q1;
q1.push("hi");
q1.push("I'm");
q1.push("wangkailai");
cout << q1 << endl;
q1.pop();
cout << q1 << endl;
运行结果:
< hi I'm wangkailai >
< I'm wangkailai >
三、智能指针
1.定义
在C++中,动态内存的管理是用一对运算符完成的:new和delete,new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针,delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。
动态内存管理经常会出现两种问题:一种是忘记释放内存,会造成内存泄漏;一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。
为了更加容易(更加安全)的使用动态内存,引入了智能指针的概念。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。标准库提供的两种智能指针的区别在于管理底层指针的方法不同,shared_ptr允许多个指针指向同一个对象,unique_ptr则“独占”所指向的对象。标准库还定义了一种名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象,这三种智能指针都定义在memory头文件中。
下表是shared_ptr与unique_ptr都支持的操作:
下表是shared_ptr独有的操作:
2.AutoPtr
新建一个模板类AutouPtr
#ifndef AUTOPTR_H
#define AUTOPTR_H
template <class T>
class AutoPtr
{
public:
//构造函数
AutoPtr(T* pData);
//拷贝构造函数
AutoPtr(const AutoPtr<T>& h);
//声明周期结束时调用析构函数
~AutoPtr();
//等号的重载
AutoPtr<T>& operator=(const AutoPtr<T>& 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<T>::AutoPtr(T* pData)
{
m_pData = pData;
//初始化用户数为1
m_nUser = new int(1);
}
template < class T>
AutoPtr<T>::AutoPtr(const AutoPtr<T>& h)
{
m_pData = h.m_pData;
m_nUser = h.m_nUser;
//用户数加1
(*m_nUser)++;
}
template < class T>
AutoPtr<T>& AutoPtr<T>::operator=(const AutoPtr<T>& h)
{
decrUser();
m_pData = h.m_pData;
m_nUser = h.m_nUser;
(*m_nUser)++;
}
template < class T>
void AutoPtr<T>::decrUser()
{
--(*m_nUser);
if ((*m_nUser) == 0)
{
//删除数据
delete m_pData;
//地址赋为空
m_pData = 0;
delete m_nUser;
m_nUser = 0;
}
}
template < class T>
AutoPtr<T>::~AutoPtr()
{
decrUser();
}
#endif // AUTOPTR_H
其中,构造函数为AutoPtr(T* pData),析构函数为 ~AutoPtr(),拷贝构造函数为AutoPtr(const AutoPtr& h),等号重载为AutoPtr& operator=(const AutoPtr& h),指向运算符重载为T* operator ->()、 const T* operator -> () const 。
在queue.cpp中添加函数:
void TestAutoPtr()
{
//创建一个CMatrix类的指针并交给智能指针类进行管理
AutoPtr<CMatrix> 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<CMatrix> h2(h1);
(*h2).Set(0, 1, 10);
cout << *h1 << *h2 << endl;
}
运行结果:
2 3
1 2 3
4 5 6
2 3
1 10 3
4 5 6
2 3
1 10 3
4 5 6
由于指针h2是通过拷贝构造函数(拷贝h1)创建的指针,因此指针h2和h1指向的是同一个地方,因此当指针h2通过Set()函数对所指向的值域进行更改时,指针h1所指向的内容也会同时变化,故h1和h2的输出结果相同。
总结
1.模板
(1)模板就是实现代码重用的一种机制,它可以实现类型参数化,即把类型定义为参数, 从而实现了真正的代码可重用性。
(2)模板编程和函数重载可以实现C++静态多态,也叫编译时多态。
(3)模版可以分为两类,一个是 函数模版 ,另一个是 类模版。
(4)模板特化的目的: 模板本来是一组通用逻辑的实现,但是可能存在特定的参数类型下,通用的逻辑实现不能满足要求,这时就需要针对这些特殊的类型,而实现一个特例模板—即模板特化。
(5)类模板和函数模板都可以被全特化;
(6)类模板能偏特化,不能被重载;
(7)函数模板可以实现重载,不能被偏特化;
(8)类模板调用优先级:全特化类>偏特化类>主版本模板类;
(9)注意模板编程不支持分离式编译,即模板类/模板函数的声明与定义应该放在头文件里,否则会在链接时报错;
(10)函数模板同时存在具体化模板、函数模板重载、和常规函数重载时候,调用优先级:
常规函数 > 具体化模板函数 > 常规模板函数;