一、模板函数(compare)
△函数模板的相关概念
-
函数模板针对仅参数类型不同的函数;
-
template<typename T> 函数声明或定义
类型名 函数名(参数表) { 函数体的定义 }
所有函数模板的定义都是用关键字template开始的。函数参数表由用逗号分隔的模板参数构成,可以包括以下内容:
1.class(或typename)标识符,指明可以接受一个类型参数。这些类型参数代表的是类型,可以是预定义类型或者自定义类型。
2.类型说明符 标识符,指明可以接受一个由"类型说明符"所规定类型的常量作为参数。
3.template<参数表>class 标识符,指明可以接收一个类模板名作为参数。 -
使用函数模板有两种方式:自动类型推导、显示指定类型
1.一般模板函数
int Compare(const int& a, const int& b)
{
if(a > b) return 1;
if(a < b) return -1;
return 0;
}
上面的代码用来比较两个整型(int)变量的值,用到int Compare 函数。
△ 那么问题来了,如果是比较两个double值呢?如下:
int Compare(const double& a, const double& b)
{
if(a > b) return 1;
if(a < b) return -1;
return 0;
}
上面便是用来比较两个 double 型变量的值。
△以下是char型类型比较,可以发现,根据不同的比较需要,只需要更改const 后面对应的变量类型即可。
//char类型比较
int compare(const char v1,const char v2)
{
if(v1>v2)
return 1;
else if(v1<v2)
return -1;
else
return 0;
}
!!!!如果需要比较的类型很多,那么需不需要一直这样不断去编写新的代码端呢?能否只写一遍 Compare 函数,就能用来交换各种类型的变量的值呢?
▲ 答案是:不用!
因为Compare 函数除了处理的数据类型不同外,形式上都是一样的,因此,“模板”的概念就应运而生了。
▲ 函数模板的写法如下:
template <class 类型参数1, class类型参数2, ...>
返回值类型 模板名(形参表)
{
函数体
}
Type是类型参数,代表类型。编译器由模板自动生成函数时,会用具体的类型名对模板中所有的类型参数进行替换,其他部分则原封不动地保留。同一个类型参数只能替换为同一种类型。编译器在编译到调用函数模板的语句时,会根据实参的类型判断该如何替换模板中的类型参数。
▲ 用法
template <class Type>
int compare(const Type &v1,const Type &v2)
{
if(v1<v2)
return -1;
if(v1>v2)
return 1;
return 0;
}
函数模板和重载时密切相关的。从函数模板产生的相关函数都是同名的,编译器用重载的解决的方法调用相应的函数。
▲实际应用时:
template <class Type>
int compare(const Type &v1,const Type &v2)
{
if(v1<v2)
return -1;
if(v1>v2)
return 1;
return 0;
}
int main(int argc, char *argv[])
{
const char *cp1="world",*cp2="hi"; //char型
int a=1,b=2; //int型
compare(a,b);
compare(cp1,cp2);
return 0;
}
用以上的main函数搭配,即可用来比较。
但是,这样其实还是出现另一个问题:我们写的模板函数可能不是所有数据类型都能适用,比如字符串型,字符串比较不能单纯使用"<", ">"来进行比较。那么要如何继续设计呢?于是C++又提供了特化模板函数功能。
2.特化模板函数
模板特化就是在实例化模板时,对特定类型的实参进行特殊处理,即实例化一个特殊的实例版本,当以特化定义时的形参使用模板时,将调用特化版本。所以我们为了继续维护模板函数的完整性,对于字符串这个漏洞,我们就需要针对这个类制作特化模板函数(可以理解成游戏补丁)来维护模板函数的完整性:
▲用法
emplate <>//声明是模板特化函数
int compare<const char *>(const char* const& v1, const char * const& v2) //进行全特化
{
return strcmp(v1,v2); //调用字符串比较函数
}
函数模版的特化,当函数调用发现有特化后的匹配函数时,会优先调用特化的函数,而不再通过函数模版来进行实例化。
这样就实现了关于语句的比较,而特化声明要放在模板函数的下面。
函数模板注意事项:
1.函数模板本身在编译时不会生成任何目标代码,只有由模板生成的实例会生成目标代码。
2.被多个源文件引用的函数模板,应当连同函数体一同放在头文件中,而不能像普通函数那样只将声明放在头文件中。
3.函数指针也只能指向模板的实例,而不能指向模板本身。
二、模板类Queue或Stack
1.模板类(Queue,Stack)
C++除了支持函数模板,还支持类模板(Class Template)。函数模板中定义的类型参数可以用在函数声明和函数定义中,类模板中定义的类型参数可以用在类声明和类实现中。类模板的目的同样是将数据的类型参数化。
△ 对于类模板
- 类模板针对仅数据成员和成员函数类型不同的类。
- 建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。
- template<typename T> 类
template<typename 类型参数1 , typename 类型参数2 , …> class 类名{ 类成员声明 };
▲和函数模板的区别
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板。
▲用法
#include <string>
#include<iostream>
using namespace std;
//类模板
template<class T1, class T2 = int>
class Person
{
public:
Person(T1 name, T2 age)
{
this->mName = name;
this->mAge = age;
}
void showPerson()
{
cout << "name: " << this->mName << " age: " << this->mAge << endl;
}
public:
T1 mName;
T2 mAge;
};
//1、类模板没有自动类型推导的使用方式
void test1()
{
Person <string ,int>p("xxd", 1000); //必须使用显示指定类型的方式,使用类模板
p.showPerson();
}
//2、类模板在模板参数列表中可以有默认参数
void test2()
{
Person <string> p("jmu", 999); //类模板中的模板参数列表 可以指定默认参数
p.showPerson();
}
int main() {
test1();
test2();
return 0;
}
△ 概念 queue
- push():会将一个元素置入queue中;
- front():会返回queue内的第一个元素
- back():会返回queue中的最后一个元素
- pop():会移除queue内的第一个元素
▲用法
queue.h
#ifndef QUEUE_H
#define QUEUE_H
#include <iostream>
using namespace std;
template<class Type> class Queue; //先声明 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(),tail(){
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(){destory();}
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 destory();
void copy_items(const Queue &);
template<class It> void copy_items(It beg,It end);
};
//成员模板函数
template<class Type>
void Queue<Type>::destory(){
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()){
head=tail=pt;
}
else{
tail->next=pt;
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)
{
destory();
copy_items(q);
}
template<class Type> template<class It> void Queue<Type>::assign(It beg, It end)
{
destory();
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>
int compare(const Type& v1, const Type& v2)
{
if(v1<v2) return -1;
if(v1>v2) return 1;
return 0;
}
template<>
int compare<const char*>(const char * const &v1, const char * const &v2);
#endif // QUEUE_H
在上面的Queue类中,我们定义了许多模板函数,许多简单的函数使用隐式内联函数(直接写在类里面)的方法来书写。
但是为了美观,我们将一些复杂的函数放在类外部来写,但是有一定要注意模板类和模板函数只能写在定义类的头文件中。这样编译器才能找到,而相反特化模板类特化模板函数只能在同名CPP文件里写。
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 Type>
class Compare{
public :
bool IsEqual(const Type& a, const Type& 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 Type>
class Compare <Type*>{
public:
bool IsEqual(const Type *a, const Type *b)
{
return Compare<Type>::IsEqual(*a, *b);
}
};
// 特化为另外一个类模板
template <class Type>
class Compare <vector<Type>>{
public:
bool IsEqual(const vector<Type>& a, const vector<Type>& 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;
}
};
三、模板类AutoPtr
对于C和C++来说,指针的使用即是特色,也是弱点,不合适的指针使用可能会导致一系列问题,比如内存溢出,数据泄露等。因此如何更方便的使用指针呢,这就是AutoPtr(智能指针)的目标。
1.构造函数
函数实例化
template<class Type>
AutoPtr<Type>::AutoPtr(Type* pData)
{
m_pData = pData;
m_nUser = new int(1);
}
其中函数声明:AutoPtr(T* pData);
2.析构函数
~AutoPtr()
{
decrUser();
}
void decrUser();
template<class Type>
void AutoPtr<Type>::decrUser()
{
--(*m_nUser);
if ((*m_nUser) == 0)
{
delete m_pData;
m_pData = 0;
delete m_nUser;
m_nUser = 0;
}
}
3.拷贝构造函数
函数实例化
template<class Type>
AutoPtr<Type>::AutoPtr(const AutoPtr<Type>& h)
{
m_pData = h.m_pData;
m_nUser = h.m_nUser;
(*m_nUser)++;
}
其中函数声明:AutoPtr(const AutoPtr<T>& h);
4.等号、->、*等运算符重载
①=重载
AutoPtr<Type>& AutoPtr<Type>::operator=(const AutoPtr<Type>& h)
{
decrUser();
m_pData = h.m_pData;
m_nUser = h.m_nUser;
(*m_nUser)++;
}
②->重载
Type* operator->()
{
return m_pData;
}
const Type* operator ->() const{
return m_pData;
}
③*重载
Type& operator*()
{
return *m_pData;
}
const Type& operator *() const{
return *m_pData;
}
四、数调用AutoPtr
#include<iostream>
#include"queue.h"
#include <vector>
#include "autoptr.h"
#include "CMatrix.h"
using namespace std;
int main()
{
AutoPtr<CMatrix> h1;
double data[6] = {1,2,3,4,5,6};
h1->Create(2,3,data);
cout << *h1 << endl;
AutoPtr<CMatrix> h2(h1);
(*h2).Set(0,1,10);
cout << *h1 << endl << *h2;
}
h2通过拷贝构造函数创建(拷贝h1),则h2调用Set方法后改变的是同一个地址的值,所以最后h1改变了,h1和h2输出结果相同。结果如下: