文章目录
- 前言
- [1. 非类型模板参数](#1. 非类型模板参数)
-
- [1.1 类型形参](#1.1 类型形参)
- [1.2 非类型形参](#1.2 非类型形参)
- [2. 模板的特化](#2. 模板的特化)
-
- [2.1 概念](#2.1 概念)
- [2.2 函数模板特化](#2.2 函数模板特化)
- [2.3 类模板特化](#2.3 类模板特化)
-
- [2.3.1 全特化](#2.3.1 全特化)
- [2.3.2 偏特化](#2.3.2 偏特化)
- [2.3.3 类模板特化应用示例](#2.3.3 类模板特化应用示例)
前言
本文深入探讨了C++模板的高级特性,主要包括非类型模板参数和模板特化。
想看模板初阶的道友可以点这里:【C++】模板初阶
1. 非类型模板参数
模板参数分为类型形参与非类型形参两种,我们之前定义的那种都是类型模板参数。
1.1 类型形参
类型形参即出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
比如:
cpp
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
在该函数模板中,T 就是类型模板参数,是在使用模板时具体指定的类型
1.2 非类型形参
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
先来看一个场景:假设我们现在要定义一个静态的栈
cpp
#define N 100
template<class T>
class Stack
{
public:
// ...
private:
T _arr[N];
size_t _top;
};
我们如果要修改栈的大小修改宏就可以了,但是这么写会有一些问题,比如我们要求定义的两个栈一个大小是10,另一个是100
cpp
Stack<int> st1; // 10
Stack<int> st2; // 100
很明显这里我们做不到让两个栈一个大小是10,另一个是100,在同一份代码里面只能保持 N 是一个大小,那这就很难受了,但是非类型模板参数就能很好的解决这个问题
cpp
template<class T, size_t n = 10>
class Stack
{
public:
// ...
private:
T _arr[n];
size_t _top;
};
int main()
{
Stack<int, 10> st1;
Stack<int, 100> st2;
return 0;
}
非类型模板参数也能给缺省值
注意:
- 非类型模板参数一般要求只能是整型
- 其它类型如浮点数、类对象以及字符串是不允许作为非类型模板参数的。
- 非类型的模板参数必须在编译期间就能确认结果。
2. 模板的特化
2.1 概念
特化就是模板的特殊化,通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如下面这种场景
cpp
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);
}
private:
int _year;
int _month;
int _day;
};
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
return left < right;
}
int main()
{
cout << Less(1, 2) << endl; // 可以比较,结果正确
Date d1(2025, 12, 6);
Date d2(2025, 12, 1);
cout << Less(d1, d2) << endl; // 可以比较,结果正确
Date* p1 = &d1;
Date* p2 = &d2;
cout << Less(p1, p2) << endl; // 可以比较,结果错误
return 0;
}
运行截图:

一般情况下,Less 都是没问题的,但是当我们传 d1 和 d2 的地址时,less 比较的是 d1 和 d2 的地址而不是空间里面的值,这个时候就出错了。
那这个时候,我们就可以对模板进行特化。特化不能单独存在,即要在原模板类的基础上,针对特殊类型进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。
2.2 函数模板特化
函数模板的特化步骤:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇
怪的错误。
还是上面的那个例子,如果我们要进行特化
cpp
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
return left < right;
}
// 特化
template<>
bool Less<Date*>(Date* left, Date* right)
{
return *left < *right;
}
int main()
{
cout << Less(1, 2) << endl; // 可以比较,结果正确
Date d1(2025, 12, 6);
Date d2(2025, 12, 1);
cout << Less(d1, d2) << endl; // 可以比较,结果正确
Date* p1 = &d1;
Date* p2 = &d2;
cout << Less(p1, p2) << endl; // 可以比较,结果正确
return 0;
}
此时,比较 p1 和 p2 的大小就对了,运行截图:

虽然函数模板的特化语法讲完了,但一般用的很少,所以也不建议用,因为有的时候会很坑,比如还是之前那个函数模板
cpp
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
return left < right;
}
其实按理来说这段代码我们一般也不会直接这么写,因为考虑到自定义类型,尤其是深拷贝的自定义类型,为了减少拷贝应该要加上引用,而且最好再加上 const,这样 const 对象和普通对象都可以调用:
cpp
// 函数模板 -- 参数匹配
template<class T>
bool Less(const T& left, const T& right)
{
return left < right;
}
我们要对这个函数模板进行特化,如果对语法不是特别清楚的话,就会非常的坑
先来看错误写法:
cpp
// 函数模板 -- 参数匹配
template<class T>
bool Less(const T& left, const T& right)
{
return left < right;
}
template<>
bool Less<Date*>(const Date*& left, const Date*& right)
{
return *left < *right;
}
看一下它的报错:

报错的原因是函数模板中 const 修饰的是 left 和 right 本身,但我们特化的版本中 const 修饰的确是指向的内容,所以匹配不上,下面来看正确的写法:
cpp
// 函数模板 -- 参数匹配
template<class T>
bool Less(const T& left, const T& right)
{
return left < right;
}
template<>
bool Less<Date*>(Date* const& left, Date* const& right)
{
return *left < *right;
}
回到正题,一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。就比如上面的函数模板特化我们其实可以直接写成一个普通函数。
cpp
bool Less(Date* left, Date* right)
{
return *left < *right;
}
这种写法相较于函数模板的特化来说就要更简单明了,代码的可读性也更高,更容易书写。个人感觉函数模板的特化也不太好用,一般函数模板的特化也用的很少,因此函数模板建议还是不要用特化,简单了解一下语法即可。
2.3 类模板特化
2.3.1 全特化
全特化即是将模板参数列表中所有的参数都确定化。
cpp
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);
}
private:
int _year;
int _month;
int _day;
};
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;
}
2.3.2 偏特化
偏特化是任何针对模版参数进一步进行条件限制设计的特化版本。比如还是以这个模板为例:
cpp
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
首先偏特化有部分特化和参数更进一步的限制两种表现方式:
- 部分特化
部分特化顾名思义就是将模板参数类表中的一部分参数特化。
cpp
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, char>
{
public:
Data() { cout << "Data<T1, char>" << endl; }
//private:
// 一样的道理,这里变量写不写都可以
// T1 _d1;
// char _d2;
};
- 参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
cpp
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
//两个参数偏特化为指针类型
template <class T1, class T2>
class Data <T1*, T2*>
{
public:
Data()
{
cout << "Data<T1*, T2*>" << endl;
}
private:
T1 _d1;
T2 _d2;
// 注意这里即可以用 T1,T2 来定义值,也可以用 T1*,T2* 来定义值
// 下面两个特化和这里是一样的道理
};
//两个参数偏特化为引用类型
template <class T1, class T2>
class Data <T1&, T2&>
{
public:
Data()
{
cout << "Data<T1&, T2&>" << endl;
}
private:
T1 _d1;
T2 _d2;
};
//还可以指针和引用混在一起
template <class T1, class T2>
class Data <T1*, T2&>
{
public:
Data()
{
cout << "Data<T1*, T2&>" << endl;
}
private:
T1 _d1;
T2 _d2;
};
int main()
{
Data<float*, char*> d1;
Data<float&, char&> d2;
Data<float*, char&> d3;
return 0;
}
运行截图:

可以直接排序,结果错误日期还不是升序,而v2中放的地址是升序
// 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象
// 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期
2.3.3 类模板特化应用示例
以小于的类模板来举例:
cpp
#include <iostream>
#include<vector>
#include<algorithm>
using namespace std;
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);
}
private:
int _year;
int _month;
int _day;
};
template<class T>
struct Less
{
bool operator()(const T& x, const T& y) const
{
return x < y;
}
};
int main()
{
Date d1(2025, 12, 6);
Date d2(2025, 12, 5);
Date d3(2025, 12, 7);
vector<Date> v1;
v1.push_back(d1);
v1.push_back(d2);
v1.push_back(d3);
// 可以直接排序,结果是日期升序
sort(v1.begin(), v1.end(), Less<Date>());
vector<Date*> v2;
v2.push_back(&d1);
v2.push_back(&d2);
v2.push_back(&d3);
// 可以排序,但结果错误
sort(v2.begin(), v2.end(), Less<Date*>());
return 0;
}
在这段代码中,Less 类模板是一个仿函数,它可以调整我们 sort 函数排的是升序还是降序,这里比较小于是降序的逻辑,简单了解一下,后面会细讲
cpp
template<class T>
struct Less
{
bool operator()(const T& x, const T& y) const
{
return x < y;
}
};
回到正题,这段代码中,对于自定义类型,我们直接可以比较大小,但是如果传的是指针,就不行了,这里我们就可以对 Less 类模板按照指针方式特化
cpp
template<>
struct Less<Date*>
{
bool operator()(Date* x, Date* y) const
{
return *x < *y;
}
};
这样一来,我们传指针类型也能比较出正确结果
完!