文章目录
【C++实战三】模板入门
1. 函数模板
**函数模板定义:**建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。
函数模板语法:
template <类型形式参数表>
类型 函数名(形式参数表)
{
语句序列
}
例:
template <typename T>
void swap(T &a, T &b)
{
T t;
t = a;
a = b;
b = t ;
}
函数模板要点:
- 函数模板定义由模板说明和函数定义组成。
- 模板说明的类属参数必须在函数定义中至少出现一次。
- 函数参数表可以使用类属类型的参数,也可以用一般类型的参数。
1.1 一般模板函数
举一个简单的例子,当我们在进行数值大小的判断时,如果输入值的类型不同时,我们的比较函数需要编写多次。而使用模板函数,可以通过输入值的类型进行虚拟类型的代表,最后实现一个函数适用于各类输入数据。
// typename是对函数模板的定义,T指根据情况指定的虚拟类型
// 一般模板函数只能在.h头文件进行定义
template <typename T>
int compare(const T& v1, const T& v2){
if(v1<v2) return -1;
if(v1>v2) return 1;
return 0;
}
int main()
{
// 定义int, double, char三种类型变量的比较
int n1 = 5, n2 = 3;
double d1 = 1.1, d2 = 5.9;
const char * v1 = "bbb";
const char * v2 = "aaa";
// 输出字符串地址
cout << (int *)v1 << endl;
cout << (int *)v2 << endl;
// 输出比较结果
cout << compare(n1,n2) << endl;
cout << compare(d1,d2) << endl;
cout << compare(v1,v2) << endl;
return 0;
}
运行结果:
0x405069
0x40506d
1
-1
-1
1.2 特化模板函数
虽然一般模板函数可以实现功能的通用,但难免有时候我们会对一些类型提供特殊待遇。比如上述的例子中,字符串不能单纯采用 ”<“ 和 “>” 来比较,因为这将比较的是字符串的地址,而不是字符串的内容。这时候就可以使用特化模板函数另写一个实现方法:
//模板特化函数的声明
//特化函数的实现只能写在CPP内(不同于一般模板函数)
template <>
int compare<const char *>(const char* const& v1, const char * const& v2)
{
//调用字符串比较函数
return strcmp(v1,v2);
}
0x405069
0x40506d
1
-1
1
2. 模板类
模板函数可以减少对于多个功能相似函数的编写;在工程项目中,同样要白那些多个形式和功能都相似的类,于是c++引入了模板类的概念。
函数类语法:
template <类型参数表>
class 类模板名{
成员函数和成员变量
};
2.1 使用模板类构建Queue队列数据结构
管理队列元素类QueueItem的构造:
// 模板类
// 提前声明Queue,在管理队列元素类时会用到
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;
}
};
对列Queue类的构造:
template<class Type>
class Queue
{
public:
// 参数列表构造器;初始化head指针;初始化tail指针
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();
// 将队列orig的所有元素插入其他队列,原队列元素保留
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();
}
}
主函数:
#include "queue.h"
#include<cstring>
#include<iostream>
void TestQueue()
{
// 创建字符队列
Queue <char>q;
// 将数据插入队列
q.push('A');
q.push('B');
q.push('C');
// 将数据移出队列
q.pop();
// 输出队列首元素和队列
cout << "队列首元素:" << q.front() << endl;
cout << q ;
}
int main() {
TestQueue();
system("pause");
return 0;
}
运行结果:
队列首元素:B
< B C >
2.2 成员模板函数
模板类的成员函数通常需要用到模板类型参数(成员模板函数)。
//拷贝指定范围的队列元素插入原队列
template<class Type> template<class It> void Queue<Type>::copy_items(It beg, It end)
{
while (beg != end)
{
push(*beg);
++beg;
}
}
2.3 模板成员函数的特化
// 模板类Queue中的成员函数push()和pop()
template < >
void Queue<const char*>::push(const char* const& val);
template < >
void Queue<const char*>::pop();
当我们的模板函数无法对所有数据类型生效的时候,我们就需要对部分函数进行特化,这里我们对模板类Queue中的成员函数push()和pop()进行特化,使其实现字符串的入队和出队操作。
template <>
void Queue<const char*>::push(const char* const& val)
{
// 根据字符串长度创建字符数组
char* new_item;
// 拷贝字符内容
new_item = new char[strlen(val) + 1];
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 = pt;
}
}
template <>
void Queue<const char*>::pop()
{
// 特化模板类QueueItem
QueueItem<const char*>* p = head;
delete head->item;
head = head->next;
// 释放指针空间
delete p;
}
3. 智能指针
JAVA可以自动释放申请内存,然而C++并不具备这个特点。因此在C++工程的编写过程中,经常会产生类似内存泄露的问题。(最近在用C++写界面,当写到通过键盘来浏览图片文件夹时,经常由于忘记释放内存导致浏览过程中程序崩溃)
为了避免繁琐的内存管理问题,同时更加安全的使用系统内存,c++引入了智能指针。智能指针与常规指针的区别在于,它能够自动的判断所指向的内存是否还指针指向它,如果这片内存已没有任何常规指针指向它,则认为这片内存的生命周期已经结束,自动的释放这片内存占用的区域,留给他人使用。
接下来我们通过代码分析智能指针的优势:
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>
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();
}
// 拷贝构造函数
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)++;
}
主函数调用智能指针:
void TestAutoPtr()
{
// 创建一个类的智能指针
AutoPtr<CMatrix> h1;
// 定义数据
double data[6] = {1,2,3,4,5,6};
// 生成2行3列的数组(下标从0开始)
h1 -> Create(2,3,data);
cout << *h1 << endl;
// h2是由拷贝函数创建的,和h1指向同一个地方
AutoPtr<CMatrix> h2(h1);
// 通过set()函数改变同一地址的值
(*h2).Set(0,2,41);
cout << *h1 << endl << *h2;
}
int main()
{
TestAutoPtr();
return 0;
}
运行结果:
、
2 31 2 3
4 5 6
2 3
1 2 41
4 5 6
2 3
1 2 41
4 5 6
4. 总结
- 模板为工程代码的编写提供了极大的便利,需要注意的是一般模板函数的声明放在头文件中,而特化模板函数声明放在CPP文件中。
- 函数模板、类模板均可进行特化。在工程搭建的过程中,不是所有的相似功能都是通过通用函数实现的,当我们需要对某一类型数据进行特殊处理时,特化就显得特别重要了。
- 在做一个C++项目工程时,如果每次new出来的内存都要手动delete,这将显得特别的麻烦。而智能指针可以通过析构函数自动释放它管理的内存(在对象生命期即将结束时)。