网站开发师是做什么的,网站域名在哪里备案,镇江丹阳怎么样,app定制公司哪家好#x1f466;个人主页#xff1a;Weraphael ✍#x1f3fb;作者简介#xff1a;目前学习C和算法 ✈️专栏#xff1a;C航路 #x1f40b; 希望大家多多支持#xff0c;咱一起进步#xff01;#x1f601; 如果文章对你有帮助的话 欢迎 评论#x1f4ac; 点赞#x1… 个人主页Weraphael ✍作者简介目前学习C和算法 ✈️专栏C航路 希望大家多多支持咱一起进步 如果文章对你有帮助的话 欢迎 评论 点赞 收藏 加关注✨ 前言
初阶模板地址点击跳转 目录 前言一、typename和class的区别二、非类型模板参数2.1 概念2.2 实际例子array容器 三、模板特化3.1 概念3.2 函数模板特化3.3 类模板特化3.3.1 全特化3.3.2 偏特化 四、模板分离编译问题4.1 什么是分离编译4.2 模板的分离编译4.3 解决方法 五、小结 一、typename和class的区别
在以前博客我们说过定义模板参数关键字可以用typename也可以用class它们是没有区别的可是真的没有区别吗来看看以下这个例子
假设要打印容器的数据要封装一个打印函数以迭代器的方式
#include iostream
#include vector
using namespace std;void Print(const vectorint v)
{vectorint::const_iterator it v.begin();while (it ! v.end()){cout *it ;it;}cout endl;
}int main()
{vectorint v{ 10,20,30,40,50 };Print(v);return 0;
}【输出结果】 以上代码虽然可以正常输出但它只能打印vectorint类型容器的数据若要打印vectordouble又或者是list容器的数据那么这样就写死了有人想可以用函数模板。
代码如下
#include iostream
#include vector
using namespace std;// 把class换成typename也是可以的
templateclass Container
void Print(const Container v)
{Container::const_iterator it v.begin();while (it ! v.end()){cout *it ;it;}cout endl;
}int main()
{vectorint v{ 10,20,30,40,50 };Print(v);return 0;
}【输出结果】 使用函数模板后发现以上代码报错了提示说需要在Container前加上typename 那么为什么必须要加上typename呢
这是因为编译器在编译的时候是从上往下的当编译到Container::const_iterator it时Container还没实例化那么此时编译器区分不了Container是类型还是类对象静态成员变量Container::const_iterator。vectorint是实例化出来的加上域作用限定符::去找其内嵌类型(迭代器)所以不会报错。因此编译器要求加上typename告诉Container是类型。
二、非类型模板参数
2.1 概念
模板参数分为类型形参与非类型形参 类型形参出现在模板参数列表中跟在class或者typename之类的参数类型名称。 非类型形参将常量作为类(函数)模板的一个参数在类(函数)模板中可将该参数当成常量来使用。
举个例子定义一个模板类型的静态数组
#include iostream
#include vector
using namespace std;// size_t N 10 - 非类型形参
templateclass T, size_t N 10
class Array
{
public:T operator[](size_t index) { return _array[index]; }const T operator[](size_t index) const { return _array[index]; }size_t size() const { return _size; }bool empty() const { return 0 _size; }private:T _array[N];size_t _size;
};需要注意的是非类型模板参数必须满足以下两点
必须是常量不可被修改必须是整型。整型家族有char、short、bool、int、long、long long等
因此可以得出非类型模板参数一般是用来定义一个数组的大小的
2.2 实际例子array容器 在C11标准中引入了一个容器array它的底层使用了非类型模板参数是一个真正意义上的泛型数组这个是用来对标传统数组的。
#include iostream
#include array
using namespace std;int main()
{int arr[10] { 0 }; //传统数组arrayint, 10 _array; //array容器// 读arr[12]; _array[12];// 写arr[12] 0; _array[12] 10;return 0;
}对比传统数组
array也并没有进行初始化。array新数组对于越界读、写检查更为严格。传统数组越界读写不会发生报错而array数组则会报错。
虽然对越界行为检查严格 但在实际开发中很少使用array容器因为它对标传统数组连初始化都没有而vector也是类似于数组的容器在功能和实用性上可以全面碾压并且 array使用的是栈区上的空间会存在栈溢出问题因此可以说array是一个鸡肋的容器。
三、模板特化
3.1 概念
模板特化顾名思义就是对模板(泛型思想)的特殊化处理 。模板特化中分为函数模板特化与类模板特化。
通常情况下使用模板可以实现一些与类型无关的代码但对于一些特殊类型的可能会得到一些错误的结果需要特殊处理。
比如实现一个专门用来进行小于比较的函数模板
#include iostream
using namespace std;templateclass T
bool Less(T x, T y)
{return x y;
}int main()
{int a 1;int b 2;cout Less(a, b) endl; // 可以比较结果正确int* p1 a;int* p2 b;cout Less(p1, p2) endl; // 可以比较结果错误return 0;
}【输出结果】 上述示例中p1指向的a显然小于p2指向的b但是Less内部并没有比较p1和p2指向的对象内容而比较的是p1和p2指针的地址。
因此就需要对模板进行特化。即在原模板函数的基础上针对特殊类型所进行特殊化的实现方式。
3.2 函数模板特化
函数模板的特化步骤
必须要先有一个基础的函数模板关键字template后面接一对空的尖括号函数名后跟一对尖括号尖括号中指定需要特化的类型函数形参必须要和基础的模板函数的基础参数类型完全相同
#include iostream
#include vector
using namespace std;// 必须要先有一个基础的函数模板
templateclass T
bool Less(T x, T y)
{return x y;
}// 对Less函数模板进行特化
template // 关键字template后面接一对空的尖括号
// 函数名后跟一对尖括号尖括号中指定需要特化的类型
bool Lessint*(int* x, int* y) // 函数形参必须要和基础的模板函数的基础参数类型完全相同
{return *x *y;
}int main()
{int a 1;int b 2;cout Less(a, b) endl; // 可以比较结果正确int* p1 a;int* p2 b;cout Less(p1, p2) endl; // 可以比较结果错误return 0;
}【输出结果】 不过对于函数模板特化来说存在一个更加方便的东西函数重载同样也能解决特殊需求
bool Less(int* x, int* y)
{return *x *y;
}3.3 类模板特化
模板特化主要用在类模板中它可以在泛型思想之上解决大部分特殊问题并且类模板特化还可以分为全特化和偏特化
3.3.1 全特化
全特化指将原模板参数列表中所有的参数都确定化
注意在进行全特化前
需要存在最基本的泛型模板全特化模板中的模板参数可以不用写需要在类名之后指明具体的参数类型否则无法实例化出对象
// 原模板
templateclass T1, class T2
class Test
{
public:Test(const T1 t1, const T2 t2):_t1(t1), _t2(t2){cout Test(const T1 t1, const T2 t2) endl;}private:T1 _t1;T2 _t2;
};// 全特化后的模
// 将原模板参数列表中所有的参数都确定化
template
class Testint, char
{
public:Test(const int t1, const char t2):_t1(t1), _t2(t2){cout Testint, char endl;}private:int _t1;char _t2;
};int main()
{Testint, int T1(1, 2);Testint, char T2(20, c);return 0;
}调用时会优先选择更为匹配的类模板 3.3.2 偏特化
偏特化有以下两种表现方式
部分特化
顾名思义只特化一部分模板参数
// 原模板
templateclass T1, class T2
class Data
{
public:Data() { cout DataT1, T2 endl; }private:T1 _d1;T2 _d2;
};// 偏特化// 将第二个参数特化为int
template class T1
class DataT1, int
{
public:Data() { cout DataT1, int endl; }
private:T1 _d1;int _d2;
};参数更进一步的限制
即不仅仅指特化部分参数而是针对模板参数的更进一步的条件限制所设计出来的一个特化版本
借助偏特化解决指针无法正常比较问题
class Date
{
public:Date(int year 1970, int month 1, int day 1): _year(year), _month(month), _day(day){}bool operator(const Date d)const{return (_year d._year) ||(_year d._year _month d._month) ||(_year d._year _month d._month _day d._day);}bool operator(const Date d)const{return (_year d._year) ||(_year d._year _month d._month) ||(_year d._year _month d._month _day d._day);}private:int _year;int _month;int _day;
};//原来的比较模板
templateclass T
class Less
{
public:bool operator()(T x, T y) const{return x y;}
};//偏特化后的比较模板
templateclass T
class LessT*
{
public:bool operator()(T* x, T* y) const{return *x *y;}
};int main()
{Date d1 { 2018, 4, 10 };Date d2 { 2023, 5, 10 };cout d1 d2: LessDate()(d1, d2) endl;cout d1 d2: LessDate*()(d1, d2) endl;int a 1;int b 2;cout a b: Lessint*()(a, b) endl;return 0;
}四、模板分离编译问题
4.1 什么是分离编译
一个程序项目由若干个源文件共同实现而每个源文件单独编译生成目标文件最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
4.2 模板的分离编译
假如有以下场景模板的声明与定义分离开在头文件中进行声明源文件中完成定义 【程序结果】 出现了链接错误
【分析】
代码从文本变为可执行程序所需要的步骤
预处理头文件展开、宏替换、条件编译、删除注释生成纯净的C/C代码编译语法/词法/语义分析错误检查无误后生成汇编代码。注意头文件不参与编译编译器对工程中的多个源文件是分离开并且单独编译的汇编生成符号表生成二进制指令链接将符号表进行合并并处理地址问题
当模板的声明和定义分离时在realize.cpp中由于是 泛型编译器无法确定函数原型实例化因此无法生成函数也就无法获得函数地址在进行链接时无法在符号表中找到目标地址进行跳转导致链接错误
除了模板以外还有一个很常见的连接错误有函数声明却没有定义 4.3 解决方法
将声明和定义放到一个文件推荐使用这种
templateclass T
T add(const T x, const T y)
{return x y;
}在函数定义时进行模板特化编译时生成地址以进行链接不推荐使用
template
int add(const int x, const int y)
{return x y;
}五、小结
模板的优点
模板复用了代码节省资源更快的迭代开发C的标准模板库(STL)因此而产生增强了代码的灵活性
模板的缺陷
模板会导致代码膨胀问题也会导致编译时间变长出现模板编译错误时错误信息非常凌乱不易定位错误