c++----模板(进阶)

杨和段 2024-10-05 09:05:01 阅读 87

        也是好久没有更新了今天来将我们前面写过的模板更加升华一下。更加深一下。我们还记得我们前面讲过的模板,只是简单的运用模板而且还是参数类型模板。当然大家如果敏锐一点的话,应该就能看出这句话的问题看吧。我这里说的是参数类型模板,那么我们是不是还有无参数类型的模板啊。并且我们在前面还写过我们写的其他类型的模板。所以我们今天这篇博客就是来给大家讲讲非参数类型的模板,和模板的特化了。

非类型形参模板

       那么我们既然分了参数类型模板和非参数类型模板,那么我们第一步就是先看区别嘛。所以我们先来看看下面的区别:

类型形参即:出现在模板参数列表中,跟在

class

或者

typename

之类的参数类型名称

非类型形参,就是用一个常量作为类

(

函数

)

模板的一个参数,在类

(

函数

)

模板中可将该参数当成常

量来使用

       大家看上面的非参数类型的区别,我们得知非类型模板就是将模板中的参数改为常量。然后这个参数可以当作常量使用。这个虽然也就很明确的说出来参数类型与非参数类型的区别了,但是我个人觉得如果只是看看书面解释的话,应该不是在出现两个的时候一下子就能分出来的吧,所以我们来看看两个类型模板的代码,直观的看一下:

         我们这里的两张照片,第一张是我们的参数类型模板,第二张是非参数类型模板。我们看到的第一个区别就是我们的参数模板我们在最前面的template中写的都是class和typename。虽然我们的非参数类型模板中也写了的,但是我们这里为了对比,我们这里还写了一个正常的参数。我们主要看第二张图片中的非类型参数,我们这里直接写了一个常量的参数,不知道大家是否有联想到我们前面说过的默认参数啊。我们后面引用这个参数的话,我们都不需要传值了。我们可以直接使用这个参数的默认参数了。这下子大家应该就明确的分出了我们参数模板与非参数模板的区别了吧。当然我们不能直接了当的说是参数类型模板还是非参数类型模板好用。只能说应对不同的使用环境有不同的奇效吧。

       然后我们需要注意一下的是:

非类型模板参数只允许使用整型家族,浮点数、类对象以及字符串是不允许作为非类型模板参数的。非类型的模板参数在编译期就需要确认结果,因为编译器在编译阶段就需要根据传入的非类型模板参数生成对应的类或函数。

       我相信大家在很多我这种相关的博客中看到过这样的话,这也就证明这句话,并不是我个人认为重要的,那些大佬也是觉得重要的,所以大家一定要注意,别到时候被带进沟了。

模板特化

函数模板特化

        好了,当我们前面铺垫好了后,我们要来讲讲我们这篇博客中的重中之重了。模板的特化,我相信大家应该很少听到过这个名字。但是我们可以从这个名字上看到我们这个名字的特殊意义就是,特殊。与我们平常写的模板不一样。这里的特化更像是倾注与一个点发展。有点像术业有专攻那种。通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板。

<code>// 函数模板 -- 参数匹配

template<class T>

bool Less(T left, T right)

{

return left < right;

}

          这是我们通常会写的判断小于的函数模板,然后我们一般的话是这样使用的

cout << Less(1, 2) << endl;

         并且这样写我们也是正确的,可以得出正确的答案。但是我们真的会比较这写比较简单的嘛。像我们前面写过的,日期类。我们可能会对日期进行大小比较嘛。所以我们有可能会写成日期嘛,像下面的代码一样:

Date d1(2022, 7, 7);

Date d2(2022, 7, 8);

cout << Less(d1, d2) << endl;

        这样写是正确的,但是如果我们再换一个写法的话:

Date* p1 = &d1;

Date* p2 = &d2;

cout << Less(p1, p2) << endl;

        这样我们编译的话是没有错误的话,但是我们如果运行的话就比较结果就是错误的。Less绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中,p1指向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2指针的地址,这就无法达到预期而错误。此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方。模板特化中分为函数模板特化类模板特化

        所以这里我们就引出了我们如何特化,以及特化的分类了。有函数模板特化以及类模板特化。并且类模板中又分为全特化和偏特化(半特化)。

        然后我们这里函数模板的特化步骤:

1.

必须要先有一个基础的函数模板

2.

关键字

template

后面接一对空的尖括号

<>

3.

函数名后跟一对尖括号,尖括号中指定需要特化的类型

4.

函数形参表

必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇

怪的错误。

         和上面写的一样,我们想要实现函数模板的特化的话,我们前提是要有一个模板,就是相当于先照着这前面的一个抄,然后我们写新的东西的时候template里面不能有其他的东西。然后再函数名后面写特定函数类型。最后就是我们需要确定我们特化的函数类型模板必须与特化的函数模板参数类型相同。主要是我们需要满足前面上个条件,然后最后一个是我们需要注意的地方。

        当然我们只是光看这些的话是不能理解的,所以我们需要看看实例:

// 函数模板 -- 参数匹配

template<class T>

bool Less(T left, T right)

{

return left < right;

}

// 对Less函数模板进行特化

template<>

bool Less<Date*>(Date* left, Date* right)

{

return *left < *right;

}

          我们这里看到我们的模板特化这个示例完全的体现出了我们上面写的三个特点先要有一个模板,然后写template<>,并且<>里面不能写其他。最后就是在函数名后面的<>里面写出函数特化的类型。注意得就是我们特化函数模板类型必须与我们的初始化模板类型保持一致。

类模板特化

         但是又有一个问题了,我们讲了函数模板特化,我们的类模板特化咧,但是我们这个特化有什么用啊。如果我们用官方一点的话讲就是函数模板特化指函数模板在模板参数为特定类型下的特定实现 模板的特化和函数类模板的重载类似,你可以依次重写这个函数或者类, 也可以只特化某个成员 (片特化),也可以特化整个类 (全特化)。我们白话就是讲我们特化相当于将一个大项目中有很多小组,但是这个小组只专注于处理一件事,这就是我们特化的意义,然后上面我们白话讲的是我们的偏特化,偏特化就是专注搞一个,那么我们的全特化就是全部的函数模板参数给特殊化了。

        虽然说我们的函数模板可以特化,但是我们下面该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。

bool Less(Date* left, Date* right)

{

return *left < *right;

}

         然后我们其实讲特化的话,更加倾向于类模板的特化。我们全特化就是全特化即是将模板参数列表中所有的参数都确定化。我们来看看下面的例子:

template<class T1, class T2>

class Data

{

public:

Data() {cout<<"Data<T1, T2>" <<endl;}

private:

T1 _d1;

T2 _d2;

};

template<>

class Data<int, char>

{

public:

Data() {cout<<"Data<int, char>" <<endl;}

private:

int _d1;

char _d2;

};

void TestVector()

{

Data<int, int> d1;

Data<int, char> d2;

}

        我们看到我们类模板的全特化与函数模板的特化区别就是我们类名后没有<>直接是跟着我们的类成员的。并且我们的特化类的<>中写出来我们初始化的模板中写了两种参数类型,所以我们在特化的成员中可以写出两种参数类型。这就是全特化,将初始模板的参数成员全部都改掉。就是我们前面说的将模板参数中的参数全部确定。

        那么我们的全特化是将模板参数中的参数全部确定,那么我们的偏特化就是参数不全部确定。

//半特化 / 偏特化()半特化不是特化一半

//1、将部分模板参数列表中的一部分参数特化

template<class T1>

class Data<T1, char>

{

public:

Data() { cout << "Data<T1, char>" << endl; }

};

//2、偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本

//只要T1 和 T2是指针就走这个 -- 针对指针特殊化处理

template<class T1, class T2>

class Data<T1*, T2*>

{

public:

Data() { cout << "Data<T1*, T2*>" << endl; }

};

template<class T1, class T2>

class Data<T1&, T2&>

{

public:

Data() { cout << "Data<T1&, T2&>" << endl; }

};

int main()

{

Data<int, int> d1;

Data<int, double> d2;

//只要第二个是char都会匹配:半特化/偏特化

Data<int, char> d3;

Data<char, char> d4;

//只要是两个指针

Data<int*, int*> d5;

Data<int*, char*> d6;

Data<int*, string*> d7;

Data<int*, void*> d8;

//void不是类型,但是void*是一个类型,void*是不能解引用不能++

Data<int*, int> d9;//匹配原生的指针

Data<int&, char&> d10;

return 0;

}

        像我上面的这个例子一样我们我们偏特化就是将将部分模板参数列表中的一部分参数特化。如果全部特化的话,就是我们前面的全特化了,但是我们不用值特化一半,这个没有明确规定,只要是有特化和未特化的那么就是偏特化了。

         我相信很多人都在想我们特化有什么用啊,那么大家看看下面的这个例子后在思考一下:

#include<vector>

#include<algorithm>

template<class T>

struct Less

{

bool operator()(const T& x, const T& y) const

{

return x < y;

}

};

int main()

{

Date d1(2022, 7, 7);

Date d2(2022, 7, 6);

Date d3(2022, 7, 8);

vector<Date> v1;

v1.push_back(d1);

v1.push_back(d2);

v1.push_back(d3);

// 可以直接排序,结果是日期升序

sort(v1.begin(), v1.end(), Less<Date>());

vector<Date*> v2;

v2.push_back(&d1);

v2.push_back(&d2);

v2.push_back(&d3);

// 可以直接排序,结果错误日期还不是升序,而v2中放的地址是升序

// 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象

// 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期

sort(v2.begin(), v2.end(), Less<Date*>());

return 0;

}

          通过观察上述程序的结果发现,对于日期对象可以直接排序,并且结果是正确的。但是如果待排序元素是指针,结果就不一定正确。因为:sort最终按照Less模板中方式比较,所以只会比较指针,而不是比较指针指向空间中内容,此时可以使用类版本特化来处理上述问题,就像下面的代码一样

// 对Less类模板按照指针方式特化

template<>

struct Less<Date*>

{

bool operator()(Date* x, Date* y) const

{

return *x < *y;

}

};

        所以我们特化就是在面对一些情况的时候是很有作用的哦。

总结

      好了,上面就是我们这篇博客想与大家分享的模板进阶了,我们这篇博客其实主要讲模板参数中不一定是   参数类型模板,然后就是我们的特化,以及特化的使用。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。