樊梓慕:个人主页
个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》
每一个不曾起舞的日子,都是对生命的辜负
目录
前言
1.非类型模板参数
2.模板的特化
2.1函数模板特化
2.2类模板特化
2.2.1全特化
2.2.2偏特化
3.模板的分离编译
4.模板总结
本篇文章博主会与大家共同学习非类型模板参数、类模板的特化以及模板的分离编译相关内容。
欢迎大家收藏以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。
=========================================================================
GITEE相关代码:fanfei_c的仓库
=========================================================================
我们之前学习的模板,知道模板参数一般都是『 类型』。
可是还存在有一种模板参数是非类型的,这种参数被称为非类型模板参数,一般为常量,或者说是『 整型常量』。
比如:
template //N:非类型模板参数
class StaticArray
{
public:
size_t arraysize()
{
return N;
}
private:
T _array[N]; //利用非类型模板参数指定静态数组的大小
};
非类型模板参数基本上是用来定义数组或某种个数值。
注意:
特化可以理解为『 特殊处理』,即当模板参数为某一种指定类型时, 不走默认的模板,而走我们为该类型单独设计的模板。
比如:
下面是用于比较两个任意相同类型的数据是否相等的函数模板。
template
bool Equals(T x, T y)
{
return x == y;
}
在以下场景,可以正确的使用;
cout << Equals(1, 1) << endl;
cout << Equals(1.1, 2.2) << endl;
但当遇到下面这种情况时,结果往往不是我们所预期的:
char a1[] = "hello cpp";
char a2[] = "hello cpp";
cout << Equals(a1, a2) << endl;
假设我们设计该函数模板时的目的就是判断两个内容是否相等,遇到普通的比如整型、浮点型等数据当然可以正确比较,但当遇到字符串这种,我们想的是判断他们的内容是否相等,但是在实际传递时我们传递的是字符串的地址『 指针』,这也就导致比较的是地址是否相等,这就与我们的初衷所违背。
所以我们需要对指针这种类型作特殊处理,单独设计一个新的函数模板,这就是所谓的『 特化』。
特化:在原模板类的基础上,针对特殊类型所进行的特殊化的实现方式。
函数模板的特化步骤:
根据以上步骤,我们可以针对上面的模板特化:
//基础的函数模板
template
bool Equals(T x, T y)
{
return x == y;
}
//对于char*类型的特化
template<>
bool Equals(char* x, char* y)
{
return strcmp(x, y) == 0;
}
当然如果你觉得麻烦,可以不采用特化的方式。
注意: 一般情况下,如果函数模板遇到不能处理或者处理有误的类型,为了『 实现简单』通常都是将该函数直接给出。
bool Equals(char* x, char* y)
{
return strcmp(x, y) == 0;
}
该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化是特别给出,因此『 函数模板不建议特化』。
类模板特化非为:全特化、偏特化两种。
类模板的特化步骤:
全特化是将模板参数列表中所有的参数都确定化。
例如:
template
class Fan
{
public:
//构造函数
Fan()
{
cout << "Fan" << endl;
}
private:
T1 _D1;
T2 _D2;
};
//对于T1是double,T2是int时进行特化
template<>
class Fan
{
public:
//构造函数
Fan()
{
cout << "Fan" << endl;
}
private:
double _D1;
int _D2;
};
当参数为double、int时,编译器会匹配第二个类。
偏特化是指任何针对模板参数进一步进行条件限制设计的特化版本。
偏特化又可分为以下两种表现形式:『 部分特化』、『 参数更进一步的限制』。
(1)部分特化
我们可以仅对模板参数列表中的『 部分参数』进行确定化。
template
class Fan
{
public:
//构造函数
Fan()
{
cout << "Fan" << endl;
}
private:
T1 _D1;
T2 _D2;
};
//部分特化
template
class Fan//将模板参数类表中的一部分参数特化
{
public:
//构造函数
Fan()
{
cout << "Fan" << endl;
}
private:
double _D1;
int _D2;
};
注意:当偏特化和全特化同时满足时,编译器会优先匹配全特化,因为偏特化还需要实例化其中的某个参数。
(2)参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数进一步的『 条件限制』所设计出来的一个特化版本。
比如:
//两个参数偏特化为指针类型
template
class Fan
{
public:
//构造函数
Fan()
{
cout << "Fan" << endl;
}
private:
T1 _D1;
T2 _D2;
};
//两个参数偏特化为引用类型
template
class Fan
{
public:
//构造函数
Fan()
{
cout << "Fan" << endl;
}
private:
T1 _D1;
T2 _D2;
};
分离编译其实就是在项目中,声明与定义分离的代码方式。
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
搭建一种场景用作讲解:
// a.h
template
T Add(const T& left, const T& right);
// a.cpp
template
T Add(const T& left, const T& right)
{
return left + right;
}
// main.cpp
#include"a.h"
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}
执行后:
分析:
提供模板就是为了让编译器自行生成对应的代码,可是这些文件在链接之前都是『 单独编译』的,如果声明和定义分离,编译器也就无从推导出对应的模板参数,从而导致『 无法实例化』,那最终链接时也就没有对应的实例化的函数即找不到定义,从而报错『 无法解析的外部符号』。
解决方法:
①在模板定义的位置显式实例化,可是这样太挫了,岂不是失去了模板的意义了,所以不推荐使用。
②将声明和定义放到一个文件中,一般命名为"xxx.hpp"或"xxx.h"。
- .hpp和.h后缀是一样的,只不过是为了区分模板类和普通头文件,可以理解为一种『 命名习惯』。
- 当未来你定义的类既有声明又有定义时,就命名为.hpp即可。
优点:
缺陷:
=========================================================================
如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容
博主很需要大家的支持,你的支持是我创作的不竭动力
~ 点赞收藏+关注 ~
========================================================================