模板(模板函数/类、模板的特化、模板的分离编译)

本文深入讲解C++模板的概念、原理及应用,包括模板函数、模板类的定义与使用,非类型模板参数,模板特化等内容,并通过具体示例帮助读者理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、什么是模板?

模板是实现函数复用的手段,类似于模具。当实现方法基本相同但是不同类型在使用时都实现一份代码相对来说就会很繁琐,而且类型是无限的,在使用方法时类型也不确定,如果写好一个可以接受任意类型的方法,那么使用时可以套用一个模板;
模板分为模板函数和模板类。关键字template

template<typename T>
template<class T>//注意在模板的定义中模板参数列表不能为空,而且必须有typename/class,注意千万不能使用struct.

例如:Swap函数,当交换int时有一份代码,char时又是一份代码,可能还会有结构体类型的值要交换,如此一来,会降低工作效率。

#include<iostream>
using namespace std;
void Swap(int& a, int& b){
    int tmp = a;
    a = b;
    b = tmp;
}
void Swap(char* a, char* b){
    char tmp = *a;
    *a = *b;
    *b = tmp;
}
int main(){
    int a = 10; int b = 20;
    Swap(a, b);
    char c = 'A';
    char d = 'B';
    Swap(&c, &d);
    system("pause");
    return 0;
}

实现了模板后:

2、模板的原理?

模板,通俗地讲就是一个T容器,只要T能够容纳的就都可以使用T。
如果一个模板没有被特化,那么编译器就不会理会他,因为它并没有代码或者说代码被隐藏了。
当调用一个函数模板时,编译器通常要根据实参推演出模板的形参类型,然后实现调用。
看下图:第四行代码少了‘;’,但是没有具体调用时不会报错的。

template<class T,class T>
void Swap(T& a, T& b){
    T tmp = a;
    a = b
    b = tmp;
}
int main(){
    int a = 10; int b = 20;
    Swap(a, b);//隐式实例化,根据参数类型推演,没有类型转换
    char c = 'A';
    char d = 'B';
    Swap<char>(c, d);//显示实例化,可能涉及类型转换,Swap<int>('1','2');首先推演出来的应该是char类型,但是又要显示成int所以会将char转换成int。
    system("pause");
    return 0;
}

3、模板函数、模板类

前面的Swap已经介绍过模板函数,下面看一下模板类(List),如果没有模板,那么想实现一个数据类型是int、char、double等,光靠typedef char DataType;也要逐一实现他们对应的类才可以,有了模板就可以根据你传的数据类型而实例化出对应的类。

这里写图片描述
模板类的实例化
下面以List的实现为例,感受一下模板使用起来的方便:

#pragma once
#include<assert.h>

template<class T>
class ListNode{
public:
    ListNode<T>* _prev;
    ListNode<T>* _next;
    T  _data;
    ListNode(const T& x)
        :_data(x)
        , _next(NULL)
        , _prev(NULL)
    {}
};


template<class T>
class List{
    typedef ListNode<T> Node;//ListNode是类名;ListNode<T>是类型
public:
    List(){
        _head = new Node(T());//利用缺省参数类型可以解决char、string等类型的参数传参
        _head->_prev = _head;
        _head->_next = _head;
    }
    ~List(){
        delete _head;
        _head = NULL;
    }
    void Insert(Node* pos, const T& x){
        assert(pos);
        //不论什么位置、有无节点都可以涵盖
        Node* pre = pos->_prev;
        Node* new_node = new Node(x);
        pre->_next = new_node;
        new_node->_prev = pre;
        new_node->_next = pos;
        pos->_prev = new_node;
    }
    void Erase(Node* pos){
        assert(pos || _head->_next != _head);
        Node* pre = pos->_prev;
        Node* next = pos->_next;
        delete pos;
        pre->_next = next;
        next->_prev = pre;
    }
    Node* Find(const T& x){
        /*if (head->_next == head){
            return NULL;
        }*/
        Node* cur = _head->_next;
        while (cur != _head){
            if (cur->_data == x){
                return cur;
            }
            cur = cur->_next;
        }
        return NULL;
    }
    void PushBack(const T& x){
        Insert(_head, x);
    }
    void PopBack(){
        Erase(_head->_prev);
    }
    void PushFront(const T& x){
        Insert(_head->_next, x);
    }
    void PopFront(){
        Erase(_head->_next);
    }
    //拷贝构造
    List(const List<T>& l){
        _head = new Node(T());
        _head->_prev = _head;
        _head->_next = _head;

        Node* cur = (l._head)->_next;
        while (cur != l._head){
            this->PushBack(cur->_data);
            cur = cur->_next;
        }
    }
    List<T>& operator=(const List<T>& l){
        if (this != &l){
            Node* cur = (l._head)->_next;
            while (cur != _head){
                this->PushBack(cur->_data);
                cur = cur->_next;
            }
        }
        return *this;
    }
    size_t Size(){
        Node* cur = _head->_next;
        size_t size = 0;
        while (cur != _head){
            size++;
            cur = cur->_next;
        }
        return size;
    }
    void Clear(){
        //请出头结点以外的节点
        Node* cur = _head->_next;
        while (cur != _head){
            Node* next = cur->_next;
            delete cur;
            cur = next;
        }
        _head->_prev = _head;
        _head->_next = _head;
    }
    bool Empty(){
        return _head->_next == _head;
    }
    const T& Front(){
        return _head->_next->_data;
    }
    void Print(){
        Node* cur = _head->_next;
        while (cur != _head){
            cout << cur->_data << " ";
            cur = cur->_next;
        }
    }
protected:
    Node* _head;
};

4、非类型模板参数

非类型模板参数表示的是一个值而不是一个类型。
静态顺序表:不仅要有数据类型,而且要确定好空间,如果第一个顺序表需要的长度为100,而第二个顺序表需要的长度为1000,如果没有使用模板我们就肯定会#define MaxSize 1000,此 时如果只使用100个长度,那么就会有990会被浪费。
非类型的类模板参数

template<class T,size_t MaxSize>
class seqlist{
    public:
        seqlist();
private:
    T array[MaxSize];
    int _size;
};

非类型函数模板参数

template <class T,double value>//浮点数和类对象是不允许作为模板参数的
class test{
private:
    double _value;
};

5、特化–类型萃取

1、全特化:(将所有参数都特化成具体类型)
特化后,在定义成员函数就不会需要模板形参,不需要编译器的推演

#include<iostream>
using namespace std;
template<class T>
class SeqList{
public:
    SeqList();
    ~SeqList();
private:
    int _size;
    int _capacity;
    T* _data;
};
template <class T>
SeqList<T>::SeqList()
:_size(0)
, _capacity(10)
, _data(new T[_capacity])
{
    cout << "SeqList<T>" << endl;
}
template<class T>
SeqList<T>:: ~SeqList(){
    delete[] _data;
}

//****************************************************
template<>
class SeqList<int>{
public:
    SeqList(int capacity);
    ~SeqList();
private:
    int _size;
    int _capacity;
    int* _data;
};
SeqList<int>::SeqList(int capacity)
:_size(0)
, _capacity(10)
, _data(new int[_capacity])
{
    cout << "SeqList<int>" << endl;
}
SeqList<int>:: ~SeqList(){
    delete[]_data;
}

下图是运行结果,我们可以看出,特化了int类型,所以在使用int时就不走模板实例化那一步。而double没有特化,仍要根据参数推演出具体的类型。
这里写图片描述
2、偏特化(局部特化):只特化部分参数

#include<iostream>
using namespace std;


template<class T, class D>
class  Data{
public:
    Data();
private:
    T _a1;
    D _a2;
};
template<class T, class D>
Data<T, D>::Data(){
    cout << "Data<T,D>" << endl;
}
template <typename T>
class Data<T, int>
{
public:
    Data();
private:
    T _a1;
    int _a2;
};
template<class T>
Data<T, int>::Data(){
    cout << "Data<T,int>" << endl;
}

这里写图片描述

6、模板的分离编译

模板不支持分离编译

7、模板函数重载

此处借例子需要说一下模板的屏蔽规则,定义一个模板参数列表只能做用于从当前到函数定义结束,这里写图片描述此时会出错。

template<class T>
void Swap(T& a, T& b){
    T tmp = a;
    a = b
    b = tmp;
}
template<class T>
void Swap(const  T& a, const T& b){
    T tmp = a;
    a = b
    b = tmp;
}
template<class T>
void Swap(const  T& a=3, const T& b=7){//缺省参数
    T tmp = a;
    a = b
    b = tmp;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值