顾客不是买产品,他更买你做事认真的态度。💓💓💓
目录
✨说在前面
亲爱的读者们大家好!💖💖💖,我们又见面了,**上一篇文章我给大家介绍了一下Linux的历史和相关的一些基本指令。**如果大家没有掌握好相关的知识,上一篇篇文章讲解地很详细,可以再回去看看,复习一下,再进入今天的内容。
我们之前给大家讲解模板的内容,只不过那是初阶,**我们今天给大家接着讲解模板进阶部分的内容。**如果大家准备好了,那就接着往下看吧~
👇👇👇
💘💘💘知识连线时刻(直接点击即可)
🎉🎉🎉复习回顾🎉🎉🎉
博主主页传送门: 愿天垂怜的博客
🍋知识点一:模板的特化
•🌰1.非类型模板参数
🔥模板参数
模板参数分类类型形参与非类型形参。
类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常
量来使用。
举例:定义一个模板类型的静态数组
cpp
template<class 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;
};
注意:
-
浮点数、类对象以及字符串是不允许作为非类型模板参数的。
-
非类型的模板参数必须在编译期就能确认结果。
🔥array的使用
**array 是一种容器类模板,它提供了一种固定大小数组的封装。**array 容器在 <array> 头文件中定义,是标准库的一部分(自C++11起引入)。**与普通的C语言****数组相比,array 提供了更好的类型安全和更多的功能,**比如迭代器的支持、元素的随机访问、范围for循环的支持等。
举例:
cpp
int main()
{
int arr1[5];
int arr2[10];
array<int, 5> arr3;
array<int, 10> arr4;
//越界检查问题
//静态数组越界读不检查,越界写抽查
//arr1[6] = 12;
arr1[7] = 13;
arr1[8] = 15;
//array越界读写都可以检查
cout << arr3[10] << endl;
return 0;
}
与C语言中的静态数组不同,C++中的array是一个封装了固定大小数组的模板类。虽然array本身并没有内置的越界检查机制,但它提供了size()成员函数来返回数组的大小,这使得程序员可以在访问数组元素之前进行索引的有效性检查。
同时,array通过下标操作符[]访问元素时,并不会自动进行越界检查。要进行越界检查,程序员需要自己编写代码来比较索引值与数组大小。
•🌰2.函数模板特化
🔥特化概念
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板
cpp
template<class T>
bool Less(T left, T right)
{
return left < right;
}
int main()
{
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; // 可以比较,结果错误
return 0;
}
日期类实现如下:
cpp
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
public:
Date(int year = 1900, 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;
};
可以看到,Less绝对多数情况下都可以正常比较,但是**在特殊场景下就得到错误的结果。**上述示例中,p1指向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2指针的地址,这就无法达到预期而错误。
此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。
🔥函数模板特化实例
函数模板的特化步骤:
-
必须要先有一个基础的函数模板
-
关键字template后面接一对空的尖括号<>
-
函数名后跟一对尖括号,尖括号中指定需要特化的类型
-
函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇
怪的错误。
cpp
template<class T>
bool LessFunc(const T& left, const T& right)
{
return left < right;
}
//函数模板特化
template<>
bool LessFunc<Date*>(Date* const& left, Date* const& right)
{
return *left < *right;
}
template<>
bool LessFunc<const Date*>(const Date* const& left, const Date* const& right)
{
return *left < *right;
}
int main() {
cout << LessFunc(1, 2) << endl;//可以比较,结果正确
Date d1(2024, 7, 16);
Date d2(2024, 7, 17);
cout << LessFunc(d1, d2) << endl;//可以比较,结果正确
Date* p1 = &d1;
Date* p2 = &d2;
cout << LessFunc(p1, p2) << endl;//可以比较,结果正确
const Date* p3 = &d1;
const Date* p4 = &d2;
cout << LessFunc(p3, p4) << endl;//可以比较,结果正确
return 0;
}
注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。即我们推荐下面这种写法:
cpp
template<class T>
bool LessFunc(const T& left, const T& right)
{
return left < right;
}
//推荐
bool LessFunc(const Date* left, const Date* right)
{
return *left < *right;
}
bool LessFunc(Date* left, Date* right)
{
return *left < *right;
}
int main()
{
cout << LessFunc(1, 2) << endl;//可以比较,结果正确
Date d1(2024, 7, 16);
Date d2(2024, 7, 17);
cout << LessFunc(d1, d2) << endl;//可以比较,结果正确
Date* p1 = &d1;
Date* p2 = &d2;
cout << LessFunc(p1, p2) << endl;//可以比较,结果错误
const Date* p3 = &d1;
const Date* p4 = &d2;
cout << LessFunc(p3, p4) << endl;//可以比较,结果正确
return 0;
}
该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。
•🌰3.类模板特化
🔥全特化
全特化即是将模板参数列表中所有的参数都确定化。
cpp
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T1 _d2;
};
//全特化
template<>
class Data<int, char>
{
public:
Data() { cout << "Data<int, char>" << endl; }
};
int main()
{
Data<int, int> d1;
Data<int, char> d2;
return 0;
}
🔥偏特化/半特化
任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类:
偏特化有以下两种表现方式:
部分特化:将模板参数类表中的一部分参数特化。
cpp
template<class T1>
class Data<T1, double>
{
public:
Data() { cout << "Data<T1, double>" << endl; }
};
template<class T1>
class Data<T1, char>
{
public:
Data() { cout << "Data<T1, char>" << endl; }
};
参数更进一步的限制:偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
cpp
template<typename T1, typename T2>
class Data<T1*, T2*>
{
public:
Data() { cout << "Data<T1*, T2*>" << endl;
T1 x; cout << typeid(x).name() << endl; }
};
template<typename T1, typename T2>
class Data<T1&, T2*>
{
public:
Data() {
cout << "Data<T1&, T2*>" << endl;
int a = 0; const T1& x = a; T2* y = &a;
cout << typeid(x).name() << endl;
cout << typeid(y).name() << endl;
}
};
int main()
{
Data<int, int> d1;
Data<int, char> d2;
Data<int, double> d3;
Data<char, double> d4;
Data<int, char> d5;
Data<int*, int*> d6;
Data<int&, int*> d8;
return 0;
}
🔥类模板特化应用示例
利用new直接在push函数中创建指向对象的指针,我们需要用指针来比较所指向内容的大小关系:
cpp
//仿函数
class DateLess
{
public:
bool operator()(Date* p1, Date* p2)
{
return *p1 < *p2;
}
};
class DateGreater
{
public:
bool operator()(Date* p1, Date* p2)
{
return *p1 > *p2;
}
};
有以下代码:
cpp
#include "PriorityQueue.h"
int main()
{
priority_queue<Date*, vector<Date*>, DateLess> q1;
q1.push(new Date(2018, 10, 29));
q1.push(new Date(2018, 7, 24));
q1.push(new Date(2018, 7, 16));
q1.push(new Date(2024, 10, 29));
q1.push(new Date(2025, 6, 22));
cout << *q1.top() << endl;
q1.pop();
cout << *q1.top() << endl;
q1.pop();
cout << *q1.top() << endl;
q1.pop();
return 0;
}
上面我们利用到了DateLess来作为降序的仿函数,但是如果我们不写DateLess,使用默认Less可以吗?其实是可以的,但是此时我们就需要对Less进行特化:
cpp
template<class T>
class Less
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
//偏特化
template<class T>
class Less<T*>
{
public:
bool operator()(T* const& x, T* const& y)
{
return *x < *y;
}
};
int main()
{
priority_queue<Date*> q2;
q2.push(new Date(2018, 10, 29));
q2.push(new Date(2018, 7, 24));
q2.push(new Date(2018, 7, 16));
q2.push(new Date(2024, 10, 29));
q2.push(new Date(2025, 6, 22));
cout << *q2.top() << endl;
q2.pop();
cout << *q2.top() << endl;
q2.pop();
cout << *q2.top() << endl;
q2.pop();
cout << endl;
return 0;
}
🍋知识点二:模板分离编译
•🌰1.什么是分离编译
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
•🌰2.模板的分离编译
假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义:
cpp
// Func.h
#include <iostream>
using namespace std;
template<class T>
T Add(const T& left, const T& right);
// Func.cpp
#include"Func.h"
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
// test.cpp
#include"Func.h"
int main()
{
func(1, 2);
Add(1, 2);
return 0;
}
那么C++程序的编译链接经过一下几个阶段:
**预处理:**头文件展开、宏替换、条件编译、去掉注释......
此时生成的是以.i为后缀的文件:
Func.i
test.i
**编译:**检查语法,生成汇编代码
此时生成的是以.s为后缀的文件:
**汇编:**汇编代码转换为二进制机器码
此时生成的是以.s为后缀的文件:
链接:目标文件合并在一起生成可执行程序,并且把需要的函数地址等链接上。
然而模板是不支持分离编译的:
•🌰3.解决方案
- 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。
- 模板定义的位置显式实例化。这种方法不实用,不推荐使用
🍋知识点三:模板总结
【优点】
-
模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
-
增强了代码的灵活性
【缺陷】
-
模板会导致代码膨胀问题,也会导致编译时间变长
-
出现模板编译错误时,错误信息非常凌乱,不易定位错误
• ✨SumUp结语
到这里本篇文章的内容就结束了,**本节介绍了C++中模板进阶的相关知识。**这里的内容虽然之前学习过一部分,但是也是有一定难度的。希望大家能够认真学习,打好基础,迎接接下来的挑战,期待大家继续捧场~💖💖💖