c++中模板的知识梳理

模板作用

在C++中模板是泛型编程的一种体现,是采用无类型的逻辑代码编写。其作用就是达到代码的复用从而减少开发人员写重复的代码。

模板的分类

模板分类,大的方面分为模板函数和模板类。

模板函数

在时间开发过程中很有可能同一块代码,但是用了不同的类型,那么我们就必须每个类型都要写一份代码,为了解决这个问题,引入了模板函数。
具体是什么样子呢?

在模板泛型编程中引入一个新的关键字,template关键字。用法如下:

template<class T>
void function(){}

这就是一个模板函数。当然这只是一个简单的例子,我们先来解释一下,template我们已经说过是一个关键字,就是为了声明成一个模板函数,<>中的class是什么,T又是什么?class是声明T类型的关键字,class也可以换成typename,而T就是下面function函数中的类型,这里的 T 不是固定的,也可以定义成自己喜欢的,但是大多数情况下,都是定义成T带type类型这个单词。
模板函数的具体的格式:

template<class 形参名1, class 形参名2, ...> //也就是对应的类型
void 函数名(参数列表)
{...}

在使用的过程中,我们就可以像使用普通函数一样,进行传参数,但是必须与声明的参数名相匹配。

函数模板的显示实例化

怎么理解呢?

template<class T>
void fun(T& a, T& b){}

这个时候就不能传参是时候传 fun(2 , 2.2); 这样做的会导致与模板的参数不匹配,因为我们在模板中只声明了一个参数类型T,而我们传参数的时候一个是int一个是float类型,从而编译器不能推演出T的类型,所以我们这时就有一个显示实例话。
就是在传参的时候显示的加上类型,如:

fun<int>(2, 2.2);

这里就说明我们要传的参数就是int型参数。在以后的我们最好使用显示的实例化传参数。

函数模板的重载

重载是在函数调用中,在同一作用域中两个具有相同的函数名字,但是函数参数或者返回值不同的函数,
那么在模板函数中也有重载,主要是模板函数的模板参数不同,怎么理解?

template<class T1, class T2>
void fun(T1& a, T2& b){}

这个函数就和上面的fun在同一作用域中就形成了模板函数的重载。
这个时候显示实例化就是

fun<int, double>(1,2.2);

非类型的模板函数参数

什么是非类型的模板参数,非类型的模板参数就是模板参数的其中一个为给定的参数,并且知道类型可以给一个默认的参数(即缺省参数),而且在这个值初始化后,就不然能修改。这个就有一个好处,当我们在用静态顺序表的时候,不知到要开多大的空间,所以我们把这个可以让调用者来给定来定,所以非类型的模板函数参数,当给定初始化值后就不能被改变。
例如:

template<class T, int VALUE>
T fun(T a)
{
    return a+value;
}

这就是当我们调用的时候自己给定value的值。

fun<int, 10>(20); //调用

模板类

前面有了模板函数的铺垫那么我们看模板类就很相对来说比较容易。
模板类就是在实现一个类的时候实现泛型的类型,意思就是实现的的类与类型无关,是一个与类型无关的逻辑代码。
作用也是为了进行代码的复用,可以在不同的类型下用到。
我们先举个例子来看模板类

template<class T>
class Date
{
public:
    Date();
    ~Date();
private:
    T year;
    T month;
    T day;
};

这是就是一个很简单的例子,我们实例化的时候就可以

// 这里Date<int>是一个类型,所以必须要加上<int>编译器才会推演出类型
Date<int> d; 

在C++中类中的成员函数可以在类外实现,所以类模板也不例外,但是在类外是实现类中的成员,有一下要注意的几点:
1)要在实现的成员函数的前面加上template<…>
2)在类外写成员函数时,需要加域,注意这里的域是类加类型。
例如:我们要实现上面Date的构造函数,就要写成

template<class T>
Data<int>::Date(){}

模板参数

模板参数是什么?模板参数就是把一个模板类的参数定义成了另一个模板类。
例如:我们在实现顺序表的时候,用一个模板类,实现好了以后,又需要实现一个栈的数据结构,那么我们可以利用顺序表的数据结构,实现一个容器适配器,也就是把顺序表当一个类型,然后为栈模板类的参数。
如:

template<class T, class Container = SeqList<T> >
class Stack
{
public:
    void Push_back();
    void Pop_back();
    const T& Top();
private:
    Container con; // 将其定义为这个类的成员
};

这样我们要怎么实例化呢?

Stack<int, SeqList<int> > s; //在类中T 现在给定的类型就是int

在上面的T 和SeqList< T >是一种类型。
但是在用的时候如果不注意把传参数的两个写成不一样的结果就显然不正确了。

模板的模板参数

模板的模板参数就是为了解决上面的问题,但是也带来问题,导致模板类的可读性降低。
我们就在上面的代码上做出简单的改进

// 与上面相比后面的没有了T
template<class T, template<class>class Container = SeqList > 
class Stack
{
public:
    void Push_back();
    void Pop_back();
    const T& Top();
private:
    Container<T> con; // 在这里加上了T
};

这个实例化就是

Stack<int, SeqList > s; // 只写出容器适配的类名字,不写T

非类型的类模板参数

在函数模板中有非类型的函数模板参数,现在是在类中,但是用法和原理大同小异,没有什么特别大的区别。

类模板的特化

模板的特化就是在类的模板的基础上加上,给不同的类型进行一些特殊的处理代码,怎么理解?

就是我们产生模板类就是为了能达到代码的复用,但是在不同的类型中,我们为了提高效率,有些类的类型用一种更高效的方法实现,这样就可以提高效率,所以我们就用模板类的特化来实现。
那么模板的特化怎么实现的呢?就是在模板类为原生模板的基础上实现特化。

模板类中又分为全特化和偏特化(局部特化)

全特化

模板类中全特化举例子:

template<class T>
class Date
{
public:
    Date();
    ~Date();
private:
    T year;
    T month;
    T day;
};

全特化

template<>
class Date<int> // 在这里加上特化后的类型
public:
    Date();
    ~Date();
private:
    int year; // 这里也就直接用特化的类型
    int month;
    int day;
};

特化后在类外写成员函数就不需要写模板参数。

局部特化

我们来类比一下全特化,局部特化从表面意思上就可以知道,在多个模板参数的时候,我们可以特化其中一个参数,这就是局部特化。
我们就拿上面的例子来讲

template<class T1, class T2>
class Date
{
public:
    Date();
    ~Date();
private:
    T2 year;
    T1 month;
    T1 day;
};

偏特化

template<class T1>
class Date<T1,int> // 在这里加上特化后的类型
public:
    Date();
    ~Date();
private:
    int year; // 这里也就直接用特化的类型
    T1 month;
    T1 day;
};

最后还有一点要注意就是在模板中,如果没有进行调用,编译器不会对其进行语法检查,如果有调用,那么编译器才会根据类型进行生成一份代码。

还有就是要在编写模板的时候,不能把定义与声明放在两个不同的文件中,这样编译器会在编译完成后,在链接过程中会产生链接错误。

在模板类的特化中,如果在调用的类型是模板类特化的,那么就会直接去特化的类,编译器不会再生成一份代码。顺序是有全特化找全特化,没有看局部特化,没有才去生成一份模板类的代码。

如有错误,还望指正,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值