【C++实验三】模板

目录

一、模板函数

1.1、一般模板函数

1.2、特化模板函数

二、模板类

2.1、模板类Queue

2.2、成员模板函数

 2.3、模板成员函数特化

三、模板类AutoPtr

四、总结


一、模板函数

1.1、一般模板函数

当我们想比较两个整形数值时,我们通常会使用如下函数:

int compare1(int a, int b) {
    if(a > b)
        return 1;
    else if(a < b)
        return -1;
    else
        return 0;
}

但如果我们想比较的数值类型由整形变为浮点型时,我们就得修改外如下函数:

int compare2(float a, float b) {
    if(a > b)
        return 1;
    else if(a < b)
        return -1;
    else
        return 0;
}

通过对比发现这两个函数只有参数类型不同,而功能完全相同。这时,我们可以使用函数模板来避免多个有相同功能的函数的重复定义。函数模板的定义如下:

函数模板不是一个实际存在的函数,编译器不能为其生成可执行代码。函数模板的定义只是对函数功能的描述,当它具体执行时,将根据传递的实际参数决定其功能。

函数模板定义形式如下:

template<模板参数表>
类型名 函数名(参数表)
{
    函数体
}

我们可以将上面的函数修改为:

template<class T>
int Compare(T& a, T& b) {
	if (a > b) {
		return 1;
	}
	else if (a < b) {
		return -1;
	}
	else {
		return 0;
	}
}

void test01() {
	int a1 = 10;
	int a2 = 30;
	float b1 = 22;
	float b2 = 11;
	cout << Compare(a1, a2) << endl;;
	cout << Compare(b1, b2) << endl;;
}

运行结果如下: 

可以发现:使用自定义类型T来代替原函数中的实例类型int和float,达到使用一个通用的函数模板来处理不同的数据类型的目的(函数模板的作用)。 

1.2、特化模板函数

  • 模板特化:就是在实例化模板时,对特定类型的实参进行特殊处理,即实例化一个特殊的实例版本。
  • 当以特化定义时的形参使用模板时,将调用特化版本
template <typename T> 
int compare(const T& v1, const T& v2)
{
	cout << "调用template <typename T>" << endl;
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}

//为实参类型 const char * 提供特化版本
template <> 
int compare<const char*>(const char* const& v1, const char* const& v2)
{
	cout << "调用template <> int compare<const char *>" << endl;
	return strcmp(v1, v2);
}

int main(){
    cout << compare(1, 2) << endl; 
    const char* pa = "abc"; 
    const char* pb = "bbd";
	cout << compare(pa, pb) << endl; 

}

 由以上实验可知:模板特化是在实例化模板时,对特定类型的实参进行特殊处理的一个实例版本,当以特化定义的形参使用模板时,会优先调用特化的函数,而不再通过函数模版来进行实例化

二、模板类

2.1、模板类Queue

queue的核心接口主要由成员函数push(),front(),back(),pop()构成;

  • push():会将一个元素置入queue中;
  • front():会返回queue内的第一个元素(也就是第一个被置入的元素)
  • back():会返回queue中的最后一个元素(也就是最后被插入的元素)
  • pop():会移除queue内的第一个元素(也就是第一个被置入的元素)

注意:

  • front()和back()仅仅只是返回元素,并不对queue中的元素移除,所以多次执行这两个成员函数,而不执行pop(),返回的结果一样;
  • pop()虽然执行移除操作,但是并不返回被移除对象的值;
  • 如果想返回queue的元素,并移除返回的元素,就要同时执行fornt()和pop();
  • 如果queue内没有元素,那么front(),back(),pop()的执行都会导致未定义的行为,所以在执行这三个操作是,可以通过size()和empty()判断容器是否为空;

首先,我们定义一个实现队列的入队、出队、销毁队的模板类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>
#include<iostream>
void TestQueue()
{
	Queue <char>q;

	q.push('A');
	q.push('B');
	q.push('C');
	cout << "front:" << q.front() << endl;
	cout << q ;

}
int main() {
    TestQueue();
    system("pause");
    return 0;
}

分析:queue.cpp中的Queue <char>q对类模板进行了实例化,然后通过push函数向queue队列中添加了A,B,C三个元素,然后通过调用front函数输出队列第一个元素,又通过cout << q打印出队列元素。

2.2、成员模板函数

  成员模板函数的实现

template<class Type> 
template<class It> 
void Queue<Type>::copy_items(It beg, It end)
{
    while (beg != end) {
        //将数组中下标对应的值添加到队列中
        push(*beg);
        ++beg;
    }
}

添加测试代码: 

void TestQueue()
{
	/*Queue <char>q;

	q.push('A');
	q.push('B');
	q.push('C');
	cout << "front:" << q.front() << endl;
	cout << q << endl;*/
	int a[3] = { 1,2,3 };
	Queue <int> q1(a, a + 3);
	cout << q1;
    cout << endl;
}

 2.3、模板成员函数特化

实现模板类Queue中的成员函数push()pop()方法的特化版本,使其能够实现字符串的入队和出队操作。

template <>
void Queue<const char*>::push(const char* const& val) {
    char* new_item;
    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 = pt;
    }
}
template <>
void Queue<const char*>::pop() {
    QueueItem<const char*>* p = head;
    delete head->item;
    head = head->next;
    delete p;
}

添加测试代码:

Queue<const char*> q2;
	q2.push("good");
	q2.push("good");
	q2.push("study");
	cout << q2;

三、模板类AutoPtr

新建一个模板类AutoPtr,用于管理对象的生命周期(对对象的用户数和数据域进行实时监管,控制对象的内存释放)

#pragma once
#pragma once
#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

其中:

添加测试代码:

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(1, 2, 66);
	cout << *h1 << *h2 << endl;
}

运行结果分析:由于指针h2是通过拷贝构造函数(拷贝h1)创建的指针,因此指针h2和h1指向的是同一个地方,因此当指针h2通过Set()函数对所指向的值域进行更改时,指针h1所指向的内容也会同时变化,故*h1*h2的输出结果相同。 

四、总结

  1. 模板类本身未指定所使用的数据类型,不能单独编译模板类的实现。 只用在使用模板类的阶段,指定了模板中的数据类型,编译器才能正常编译。因此,在实际开发中,必须把实现全部写在头文件里面,把声明和实现分开的做法不可取。
  2. 模版不支持在局部函数中声明定义或使用
  3. 使用模板时,template<typename|class T> 声明行同使用T的函数之间不允许出现其它代码。
  4. 自动类型推导,必须推导出一致的数据类型T,才可以使用模板必须要确定出T的数据类型,才可以使用。
  5. 函数模板、类模板和和成员函数模板均可进行特化,模板的特化是为了处理通用模板所不能处理的特定数据类型。

版权声明:本文为qq_54699828原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。