模板进阶
目录
一、非类型模板参数
模板参数:
- 类型形参
- 非类型形参
**类型形参:**出现在模板参数列表中,跟在class或typename后的参数类型名称
**非类型形参:**用一个常量作为类模板的一个参数,在类模板种可将该参数当成常量使用
cpp
#define _CRT_SECURE_NO_WARNINGS 1
template<size_t N = 1>
class stack
{
private:
int _a[N];
int _top;
};
int main()
{
stack<> s0;
stack<5> s1;
stack<10> s2;
return 0;
}
**本质:**模板实例化了两种类
注:
只支持整型类型的参数(char、int、short、long、bool...)
不支持浮点数(C++20),类,以及字符串
非类型的模板参数必须在编译期就能确定结果
非类型的模板参数可以给缺省值
二、具有非类型模板参数的容器
2.1.静态数组容器(array)
普通的静态数组对于越界读不检查,对于越界写检查
(原理:在数组的最后设置标志位进行检查)
而array可以解决越界检查问题,读写都能够检查
(原理:调用operator[],在重载函数中强制性检查)
与vector相比:数据存储在栈区,在栈区申请空间比堆区效率高

cpp
namespace bite
{
// 定义一个模板类型的静态数组
template<class T, size_t N = 10>
class array
{
public:
T& operator[](size_t index)
{
assert(index < N);
return _array[index];
}
const T& operator[](size_t index) const
{
assert(index < N);
return _array[index];
}
size_t size()const
{
return _size;
}
bool empty()const
{
return 0 == _size;
}
private:
T _array[N];
size_t _size;
};
}
三、模板的特化
3.1.函数模板的特化
特化规则:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,括号中指定需要特化的类型
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "date.h"
template<class T>
bool LessFunc(T left, T right)
{
return left < right;
}
//函数特化
template<>
bool LessFunc<Date*>(Date* left, Date* right)
{
return *left < *right;
}
//直接编写函数(优先使用)
bool LessFunc(Date* left, Date* right)
{
return *left < *right;
}
bool LessFunc(const Date* left,const Date* right)
{
return *left < *right;
}
int main()
{
cout << LessFunc(1, 2) << endl;
Date d1(2026, 3, 10);
Date d2(2026, 3, 10);
cout << LessFunc(&d1, &d2) << endl;
return 0;
}
如果函数参数为自定义类型,特化会比较复杂
cpp
//如果是自定义类型的模板,为了避免拷贝,需要传引用,同时使用const修饰
template<class T>
bool LessFunc(const T& left,const T& right)
{
return left < right;
}
//如果传参为Date*
template<>
bool LessFunc<Date*>(Date* const& left, Date* const& right)
{
return *left < *right;
}
//如果传参为const Date*
template<>
bool LessFunc<const Date*>(const Date* const& left, const Date* const& right)
{
return *left < *right;
}
3.2.类模板的特化
**全特化:**模板参数列表中所有的参数都确定化
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
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;
};
int main()
{
Data<int, int> d1;
Data<int, char> d2;
return 0;
}
**偏特化:**模板参数列表中部分的参数确定化
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
template<class T1, class T2>
class Data
{
public:
Data()
{
cout << "Data<T1, T2>" << endl;
}
private:
T1 _d1;
T2 _d2;
};
//偏特化(半特化)
template<class T1>
class Data<T1,double>
{
public:
Data()
{
cout << "Data<T1, double>" << endl;
}
private:
int _d1;
char _d2;
};
int main()
{
Data<int, double> d1;
Data<int, char> d2;
return 0;
}
编译器优先选择全特化
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
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;
};
//偏特化(半特化)
template<class T1>
class Data<T1,char>
{
public:
Data()
{
cout << "Data<T1, char>" << endl;
}
private:
int _d1;
char _d2;
};
int main()
{
Data<int, char> d1;
return 0;
}

指针的特化与引用的特化
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
//两个参数偏特化为指针类型(不管什么类型的指针)
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<int*, int*> d1; // 调用特化的指针版本
Data<int&, int&> d2(1, 2);// 调用特化的指针版本
return 0;
}
**示例:**通过优先级队列和类模板特化实现Date类的降序排序
priority_queue.h
cpp
#pragma once
#include <vector>
namespace bit
{
template<class T>
class Less
{
public:
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
//类模板的特化
template<>
class Less<Date*>
{
public:
bool operator()(Date* const & x, Date* const& y)
{
return *x < *y;
}
};
template<class T>
class Greater
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
template<class T, class Container = vector<T>, class Compare = Less<T>>
class priority_queue
{
public:
void AdjustUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
Compare com;
if (com(_con[parent], _con[child]))
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void AdjustDown(int parent)
{
int child = parent * 2 + 1;
Compare com;
while (child < _con.size())
{
if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
{
++child;
}
if (com(_con[parent], _con[child]))
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void push(const T& x)
{
_con.push_back(x);
AdjustUp(_con.size() - 1);
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
const T& top()
{
return _con[0];
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
};
}
date.h
cpp
#pragma once
#include<assert.h>
#include<iostream>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1);
bool CheckDate();
void Print();
int GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
static int monthDayArray[13] = { -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
{
return 29;
}
else
{
return monthDayArray[month];
}
}
bool operator<(const Date& d) const;
bool operator<=(const Date& d) const;
bool operator>(const Date& d) const;
bool operator>=(const Date& d) const;
bool operator==(const Date& d) const;
bool operator!=(const Date& d) const;
Date& operator+=(int day);
Date operator+(int day) const;
Date& operator-=(int day);
Date operator-(int day) const;
//后置++
Date operator++(int);
//前置++
Date& operator++();
//后置--
Date operator--(int);
//前置--
Date& operator--();
int operator-(const Date& d) const;
private:
int _year;
int _month;
int _day;
};
date.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "Date.h"
void Date::Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
bool Date::CheckDate()
{
if (_month < 1 || _month > 12 || _day < 1 || _day > GetMonthDay(_year, _month))
{
return false;
}
else
{
return true;
}
}
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
if (!CheckDate())
{
cout << "非法日期" << endl;
Print();
}
}
Date& Date::operator+=(int day)
{
if (day < 0)
{
return *this -= (-day);
}
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
Date Date::operator+(int day) const
{
Date tmp = *this;
tmp += day;
return tmp;
}
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += (-day);
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
_month = 12;
--_year;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator-(int day) const
{
Date tmp = *this;
tmp -= day;
return tmp;
}
bool Date::operator<(const Date& d) const
{
if (_year < d._year)
{
return true;
}
else if (_year == d._year)
{
if (_month < d._month)
{
return true;
}
else if (_month == d._month)
{
if (_day == d._day)
{
return true;
}
}
}
return false;
}
bool Date::operator==(const Date& d) const
{
return _year == d._year && _month == d._month && _day == d._day;
}
bool Date::operator<=(const Date& d) const
{
return *this < d || *this == d;
}
bool Date::operator>(const Date& d) const
{
return !(*this <= d);
}
bool Date::operator>=(const Date& d) const
{
return !(*this < d);
}
bool Date::operator!=(const Date& d) const
{
return !(*this == d);
}
//后置++
Date Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
//前置++
Date& Date::operator++()
{
*this += 1;
return *this;
}
int Date::operator-(const Date& d) const
{
Date max = *this;
Date min = d;
int flag = 1;
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
int n = 0;
while (min != max)
{
++min;
++n;
}
return n * flag;
}
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
while (1)
{
cout << "请依次输入年月日:>";
in >> d._year >> d._month >> d._day;
if (!d.CheckDate())
{
cout << "输入日期非法:";
d.Print();
cout << "请重新输入!" << endl;
}
else
{
break;
}
}
return in;
}
test.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include "date.h"
using namespace std;
#include "priority_queue.h"
//不用传仿函数
//class DateLess
//{
//public:
// bool operator()(Date* p1, Date* p2)
// {
// return *p1 < *p2;
// }
//};
int main()
{
//bit::priority_queue < Date*, vector<Date*>, DateLess> q2;
bit::priority_queue <Date*> q2;
q2.push(new Date(2026, 3, 10));
q2.push(new Date(2026, 3, 13));
q2.push(new Date(2026, 3, 12));
cout << *q2.top() << endl;
q2.pop();
cout << *q2.top() << endl;
q2.pop();
cout << *q2.top() << endl;
q2.pop();
return 0;
}

注:
当传参的类型为指针类型时,最好使用偏特化
可以适用于任意类型的指针变量
示例:
priority_queue.h
cpp
//指针类型偏特化
template<class T>
class Less<T*>
{
public:
bool operator()(T* const & x, T* const& y)
{
return *x < *y;
}
};
test.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include "date.h"
using namespace std;
#include "priority_queue.h"
int main()
{
bit::priority_queue <int*> q2;
q2.push(new int(1));
q2.push(new int(2));
q2.push(new int(3));
cout << *q2.top() << endl;
q2.pop();
cout << *q2.top() << endl;
q2.pop();
cout << *q2.top() << endl;
q2.pop();
return 0;
}

模板参数底层类型的剥离
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include "date.h"
using namespace std;
#include "priority_queue.h"
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
template <typename T1, typename T2>
class Data <T1&, T2*>
{
public:
Data()
{
cout << "Data<T1&, T2*>" << endl;
int a = 0;
//注:T1是int,而非int&
//T2是int,而非int*
T1& x = a;
T2* y = &a;
T1 z = a;
cout << typeid(x).name() << endl;
cout << typeid(y).name() << endl;
}
private:
T1 _d1;
T2 _d2;
};
int main()
{
Data<int&, int*> d1;
return 0;
}
四、模板分离编译
分离编译模式
一个程序(项目)由若干个源文件共同实现
每个源文件单独编译成目标文件
最后将目标文件链接起来,形成单一的可执行文件的过程
a.h
cpp
template<class T>
T Add(const T& left, const T& right);
a.cpp
cpp
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
main.cpp
cpp
#include"a.h"
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}
预处理
- 头文件展开
- 宏替换
- 条件编译
- 去除注释
生成**.i**文件
编译
- 检查语法
- 生成汇编代码
生成**.s**文件
汇编
- 汇编代码转换二进制的机器码(mov 000,call 001...)
生成**.o**文件,符号表(函数名+地址)
链接
- 目标文件合并在一起
- 生成可执行程序
- 把需要的函数地址链接等链接上
生成**.exe/a.out**文件

解决方案:
在.cpp文件中将模板显示实例化(不推荐)
直接在.h文件中定义模板,用的地方直接实例化(推荐)
五、模板总结
优点:
复用代码,节省资源
增强代码的灵活性
缺点:
编译报错的信息非常凌乱,不易定位