【C++】模板(简单详细)

本文详细介绍了C++中的模板,包括函数模板和类模板的定义、使用及目的。函数模板提供了一种通用的函数实现方式,允许处理不同类型的数据。类模板则允许创建通用的类,以适应不同数据类型的成员。文章通过实例展示了如何使用模板函数和类模板,以及特化模板的用法,并给出了Queue模板类的实现和应用。此外,还讨论了智能指针AutoPtr的构造、析构、拷贝构造函数以及运算符重载的实现。
摘要由CSDN通过智能技术生成

一、模板

1.为什么要使用模板?

  C++最重要的特性之一就是代码重用,为了实现代码重用,代码必须具有通用性。通用代码需要不受数据类型的影响,并且可以自动适应数据类型的变化。这种程序设计类型称为参数化程序设计。
  因此C++就有了“模板”这一名词,模板是C++支持参数化程序设计的工具,通过它可以实现参数化多态性。所谓参数化多态性,就是将程序所处理的对象的类型参数化,使得一段程序可以用于处理多种不同类型的对象。

2.模板定义:

  模板是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数,从而实现了真正的代码可重用性。

3.模板分类:

  模板大致分为两类函数模板和类模板
  函数模板针对参数类型不同的函数;
  类模板仅针对数据成员和成员函数类型不同的类。

4.使用模板目的:

  让程序员编写与类型无关的代码。
  注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,如不能在main函数中声明或定义一个模板。

二、函数模板

1.什么是函数模板:

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

2.函数模板的定义形式:

template<class 类型参数1, class类型参数2, ...>
返回值类型名 函数名(参数表)
{
	//函数体的定义
}

其中class也能用typename替代

template <typename 类型参数1, typename 类型参数2, ...>

3.一般模板函数用法(compare)

  举一个简单的例子,为了比较两个数a,b的值,并得出最大值需要用到以下的compare函数。
若比较的数值是整形,则:

int compare(int& a, int& b) {
	if (a < b) {
		return b;
	}
	else if (b < a) {
		return a;
	}
	else
		return a;
}

若比较的数值是double浮点型,则:

double compare(double& a, double& b) {
	if (a < b) {
		return b;
	}
	else if (b < a) {
		return a;
	}
	else
		return a;
}

  可以看到针对不同类型的变量我们都得重新再编写一次compare函数,非常的麻烦。
  那么,有什么办法可以让我们只需要编写一次compare函数就能够比较不同变量类型的数值呢?这时候就要用到我们的模板函数了。

根据模板函数的定义形式我们重新对compare函数进行定义:

T compare(T& a, T& b) {
	if (a < b) {
		return b;
	}
	else if (b < a) {
		return a;
	}
	else
		return a;
}

接下来通过main函数运行不同的变量参数:

int main() {
	int a = 1;//int整形
	int b = 2;
	int max_int = compare(a, b);
	cout <<"int整型最大数为:"<<max_int<< endl;
	double c = 1.11;//double浮点型
	double d = 1.12;
	double max_double = compare(c, d);
	cout << "double浮点型最大数为:" << max_double << endl;
}

运行截图:
在这里插入图片描述
可以看到通过模板函数我们只需要在main函数定义好类型便能直接调用compare函数。

4.特化模板函数用法

  使用模板函数时并不是所有的变量类型都适用,有时候会遇到一些特殊的类型需要特殊处理,不能直接使用当前的模板函数,所以此时我们就需要对该类型特化出一个模板函数(就是写出一个模板函数专门给该类型使用)
  模板还分为全特化和偏特化两类,但函数模板只有全特化,因为偏特化的功能可以通过函数的重载完成。
模板函数的全特化:

template<>//全特化,此处为空
char compare<char>(char& a1, char& b1) {
	if (a1 < b1) {
		return b1;
	}
	else if (b1 < a1) {
		return a1;
	}
	else
		return a1;
}

三、类模板(Queue)

1.什么是类模板:

  使用template关键字不但可以定义函数模板,也可以定义类模板,类模板代表一族类,是用来描述通用数据或处理方法的机制,它使类中的一些数据成员和成员函数的参数或返回值可以取任意的数据类型。类模板可以说是用类生成类,减少了类的定义数量。

2.类模板的定义形式:

template <类型形式及参数> 
class 类模板名{
		//类成员声明
}

若要在类模板以外定义其成员函数,则要采用以下的形式:

template <类型形式及参数> 
类型名 类名<模板参数标识符列表>::函数名(参数表)

3.模板类的用法

(一)、Queue,QueueItem类

  如下例子所示,定义了模板类Queue和模板类QueueItem

template<class Type> 
class QueueItem{  //定义偏特化模板类queueitem
     QueueItem(const Type &t):item(t), next(0){}//构造函数,初始化item、next,&t是为了使传入变量完整
     Type item;
     QueueItem * next;
     friend class Queue<Type>;//定义友元类queue,可以访问queueitem的成员
     friend ostream& operator<<(ostream& os, const Queue<Type> &q);
 public:
     QueueItem<Type>* operator++(){
         return next;
     }
     Type & operator*(){
         return item;
     }
 };
 
template<class Type>
class Queue {      //定义偏特化模板类queue
public:
    Queue() :head(0), tail(0) {}
    Queue(const Queue& q) :head(0), tail(0) {
        copy_items(q);
    }
    template<class It>     //定义模板函数,改变变量类型为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);
};

(二)、成员模板函数

  代码如下,创建了三个成员模板函数,destroy(),pop()push(),对应的功能分别是清空所有数据,删除队列最后一个数据以及为队列添加数据

template<class Type>void Queue<Type>::destroy()//清空对象的数据
{
    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;//如果对象queueitem为空则为其生成一个队列,头为当前的queue对象
    }
    else { //如果对象不为空,则在其队列的后面加上此queue对象
        tail->next = pt;
        tail = pt;
    }
}

(三)、模板特化

  前面已经讲解了一下什么是模板特化,模板函数的特化又与模板类的特化有所不同。模板函数只能全特化,而模板类既有全特化又有偏特化。
  全特化即将所有模板类型都进行特化,例如将上述程序的Type用int来代替;
  偏特化即对模板类型做一些限制,偏特化分为两种,部分特化和类型范围限制。
模板成员函数全特化:

template<>     //全特化函数,定义当为字符类型时的函数模板,头文件声明
inline void Queue<const char*>::push(const char* const& val);
template<>
inline void Queue<const char*>::pop();

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 (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;
}

模板类特化:

//模板类特化
template<> 			//<>里为空
class 类名<特化类型> //<>里指明具体类型
{
	类成员声明
};

4.运行测试Queue类

void TestQueue()
{
    Queue<int> qt;
    double d = 3.3;
    qt.push(1);
    qt.push(d);     //问题:模板函数并没有生成double类型的push
    qt.push(10);
    cout << endl;
    cout << qt;
    short data[5] = { 0,3,6,9 };
    Queue<int> qi(data, data + 5);
    cout << endl;
    cout << qi;

    vector<int> vi(data, data + 5);
    qi.assign(vi.begin(), vi.end());
    cout << endl;
    cout << qi;
    Queue<const char*> q1;
    q1.push("I'm");
    q1.push("come");
    q1.push("from");
    q1.push("JMU");
    cout << endl;
    cout << q1;

    Queue<const char*> q2(q1);
    cout << q2;
}

运行截图:
在这里插入图片描述

四、类模板实现(AutoPtr类)

1.构造函数

template<class T>
AutoPtr<T>::AutoPtr(T* pData)
{
	m_pData = pData;
	m_nUser = new int(1);
}

2.析构函数

~AutoPtr()
	{
		decrUser();
	}
void decrUser();
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;
	}
}

3.拷贝构造函数

template<class T>
AutoPtr<T>::AutoPtr(const AutoPtr<T>& h)
{
	m_pData = h.m_pData;
	m_nUser = h.m_nUser;
	(*m_nUser)++;
}

4.等号、->、*等运算符重载

智能指针可以跟普通的指针一样,使用"*“和”->"等操作

AutoPtr<T>& operator=(const AutoPtr<T>& h);
T* operator->()
{
	return m_pData;
}
T& operator*()
{
	return *m_pData;
}
const T& operator *()const
{
	return *m_pData;
}
const T* operator ->()const
{
	return m_pData;
}
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)++;
}

5.主函数调用AutoPtr

#include<iostream>
#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;
}

6.运行实现截图

可以看到h2是通过拷贝构造函数(拷贝h1)创建的,所以h2调用Set方法后改变的是同一个地址的值,因此h1也跟着改变了。进而导致h1和h2输出结果相同。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

打代码能当饭吃?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值