本文已参与「新人创作礼」活动,一起开启掘金创作之路。
@TOC 正文开始
1. 非类型模板参数
模板参数分为类型形参与非类型形参。类型模板参数,即出现在模板参数列表中,跟在class或typename后的参数类型名称。
有如下场景,我要实现一个静态栈,别管 ——
#define N 100 //宏推荐用const替代
template<class T>
class Mystack
{
public:
//...
private:
T _a[N];
size_t _top;
};
但是,这样无法灵活控制栈的大小 ——
Mystack<int> st1; //100
Mystack<int> st1; //200
:purple_heart: 这就要引入非类型模板参数
非类型模板参数,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
template<class T,size_t N = 10>
class Mystack
{
public:
//...
private:
T _a[N];
size_t _top;
};
int main()
{
Mystack<int,100> st1; //100
Mystack<int,200> st2; //200
Mystack<int> st3; //可以给缺省参数:从右向左确,且连续
return 0;
}
注意:
- 非类型模板参数必须是整型常量(整形家族:char, short, int, long long),不能是浮点数、字符串、自定义类型。
- 可以给缺省参数:从右向左缺,且连续
2. 模板的特化
2.1 函数模板的特化
:yellow_heart: 函数模板的特化格式 ——
-
必须要先有一个基础的函数模板
-
关键字template后面接一对空的尖括号<>
-
在函数名和参数之间,写<指定需要特化的类型>
-
函数形参列表:必须要和函数模板的参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
template<class T>
bool objLess(T left,T right)
{
return left < right;
}
//// 参数匹配:有现成的,不去匹配模板
//bool objLess(Date* left, Date* right)
//{
// //可以写年月日比较,也可以用类对象的函数重载operator<
// return *left < *right;
//}
// 函数模板特化:专用化
template<>
bool objLess<Date*>(Date* left, Date* right)
{
return *left < *right;
}
int main()
{
cout << objLess(1, 2) << endl; //一般情况下,可以直接比较
Date* p1 = new Date(2022, 4, 13);
Date* p2 = new Date(2022, 4, 19);
// 不想用Date*指针比较,想按日期比较
cout << objLess(p1, p2) << endl;
return 0;
}
在函数模板中,特化意义不大。一般情况下如果函数模板遇到不能处理或者处理有误的类型:sweat_smile:,为了实现简单通常都是将该函数直接给出。
附:依然使用了我们的老朋友日期类Date
class Date
{
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);
}
friend ostream& operator<<(ostream& _cout, const Date& d);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
2.2 类模板的特化
特化,即特殊化,比如需要针对某些类型做特殊化处理。
2.2.1 全特化
全特化,是将模板参数列表中所有参数都确定化。
在priority_queue的模拟实现中讲过,如果优先级队列中数据T类型为Date*,默认是按照地址比较的,那这样直接比较的结果不是我想要的,我想要按对象比较。
void test_priority_queue3()
{
//priority_queue<Date*> pq; //默认比较地址大小
priority_queue<Date*, vector<Date*>, lessPDate> pq;
pq.push(new Date(2022, 3, 26));
pq.push(new Date(2022, 4, 1));
pq.push(new Date(2022, 2, 19));
pq.push(new Date(2022, 4, 10));
//默认比较地址大小,若想比较日期大小,自己写仿函数
while (!pq.empty())
{
cout << *pq.top() << endl;
pq.pop();
}
}
在上一篇文章中,我们借助模板的第三个参数Compare
的口子,自己写一个仿函数lessPDate传过去。现在再提供一种方式,针对Date*特化 less ——
template<class T>
struct less
{
// 大堆
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
template<>
struct less<Date*>
{
// 大堆
bool operator()Date*& x, Date*& y)
{
// of course可以自己写按年月日比较,也可以调用Date类重载的operator<
return *x < *y;
}
};
这样,不需要显式的传仿函数,实际上是对Date*类型的比较方式进行了特化 ——
priority_queue<Date*> pq;
2.2.2 偏特化
偏特化,是对模板参数进一步进行条件限制的。
比如下面模板类 ——
using namespace std;
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
偏特化有两种表现方式 ——
:purple_heart: 部分特化
将模板参数类表中的一部分参数特化。如下代码中,第一个参数随意,第二个参数是指定值 ——
// 将第二个参数特化为int
template <class T1>
class Data<T1, char> {
public:
Data() { cout << "Data<T1, char>" << endl; }
private:
T1 _d1;
char _d2;
};
int main()
{
Data<int, int> d1; //模板
Data<int, char> d2; //上一页全特化
// 偏特化
Data<char, char> d3;
Data<double, char> d4;
return 0;
}
:purple_heart: 参数更进一步的限制
如下面一段代码,是指针类型,但不管你是什么类型的指针 ——
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
Data() { cout << "Data<T1*, T2*>" << endl; }
private:
T1 _d1;
T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
Data(const T1& d1, const T2& d2)
: _d1(d1)
, _d2(d2)
{
cout << "Data<T1&, T2&>" << endl;
}
private:
const T1& _d1;
const T2& _d2;
};
int main()
{
Data<char*, char*> d5;
Data<double*, char*> d6;
Data<int&, int&> d7(1,2);
return 0;
}
注:非类型模板参数也可以特化 ——
#include<iostream>
using namespace std;
template<size_t N>
class A
{
public:
A() { cout << "A<N>" << endl; }
};
template<>
class A<10>
{
public:
A() { cout << "A<10>" << endl; }
};
int main()
{
A<100> a1;
A<10> a2;
return 0;
}
运行结果 ——
3. 模板的分离编译
分离编译:一个程序(项目)由若干个源文件共同实现,每个源文件单独编译形成目标文件,多个目标文件和链接库进行链接处理形成可执行程序,称为分离编译模式。分离编译利于我们维护项目,看.h
了解框架设计功能,看.cpp
了解实现细节。
:purple_heart: 回顾编译 链接过程
a.h a.cpp test.cpp
:small_orange_diamond:1. 预处理:头文件展开 + 条件编译 + 去注释 →
a.i test.i
:small_orange_diamond:2. 编译:检查语法问题,如果没有问题,就生成汇编代码 →
a.s test.s
:small_orange_diamond:3. 汇编:把汇编代码转换为二进制代码
a.o test.o
:small_orange_diamond: 链接:将多个obj文件合并成一个,并处理没有解决的地址问题。
普通函数分离编译没有问题,模板函数分离编译会出现链接不上错误,分析如下 ——
:yellow_heart: 解决方法
:small_orange_diamond: 1. 显式实例化
#include"a.h"
void F1(int N)
{
// 2.编译阶段:生成汇编代码
cout << "void F1(int N)" << endl;
}
template<class T>
void F2(const T& N)
{
// 2.编译阶段:不处理,没有实例化,无法生成汇编代码
cout << "void F2(const T& N)" << endl;
}
// 显式实例化
template
void F2<int>(const int& N);
缺点:用一个就得显示实例化一个,非常麻烦,泛型失去意义。
:small_orange_diamond: 2. 将声明和定义放到一个.h
或.hpp
文件中
那么,在编译阶段,test.i中,头文件展开后,直接就有模板的定义和实例化,可以直接填上调用地址,不需要链接时去找了。
4. 模板总结
:purple_heart: 模板优点
- 模板本质是一种复用,可以更快的迭代开发,C++的标准类模板库(STL)因此而产生。
- 增强了代码的灵活性。比如仿函数控制比较方式
:purple_heart: 模板缺点
- 模板会导致代码膨胀问题,实例化不同类型的多份,使可执行程序变大,导致编译时间变长。
- 出现模板编译错误时,错误信息非常凌乱,不易定位错误。
持续更新~@边通书