【C++ 进阶】模板核心:非类型参数、特化、分离编译

文章目录
一、模板进阶开篇
前面我们已经学习了模板的基础用法,函数模板、类模板我们都已经接触过了,也能写出简单的通用代码,但是模板真正强大、真正难的地方,是它的进阶用法。
如果没有学过模板初阶的小伙伴,推荐看以下文章:
【C++】模板编程入门指南:零基础掌握泛型编程核心(初阶)
模板进阶主要包含这几块内容:非类型模板参数、模板特化、模板的分离编译,这些都是面试常考、写STL必须掌握的知识点,接下来我们一个一个来学习。
二、非类型模板参数
模板参数分为两种,一种是类型模板参数,一种是非类型模板参数。
类型模板参数就是我们之前写的:
template<class T>
它一般是用来传类型的,而非类型模板参数,是用常量作为模板参数,在编译期间确定值,我们来看一个例子:
template<class T, size_t N>
class array
{
private:
T _array[N];
};
这里的size_t N就是非类型模板参数,它是一个常量,不是类型,使用起来就是这样:
array<int, 10> a1;
array<int, 100> a2;
和我们平时用模板没什么区别,只是多传了一个常量,这就叫非模板参数,是不是很简单呢,但是它的使用还是有需要注意的地方,如下:
注意事项
- 浮点数、类对象、字符串不能作为非类型模板参数
- 非类型模板参数必须在编译期就能确定结果
这一块非常简单,我们就简单过一下,接下来进入重点:模板的特化。
三、模板的特化
1. 什么是特化
模板是给通用类型用的,但是某些特定类型,按照默认模板逻辑会出错,这时候我们就需要特殊处理,这就叫模板特化,我们举个例子看看:
template<class T>
bool Less(T left, T right)
{
return left < right;
}
这个模板对于int、double、自定义类型都没问题,但是如果传入指针呢?
Date* d1 = new Date(2025, 1, 1);
Date* d2 = new Date(2024, 1, 1);
cout << Less(d1, d2) << endl;
可能我们的原意是比较指针指向的内容,但是这里比较的是指针地址,不是指针指向的内容!结果完全错了,这时候,我们就需要对Date*类型特化
2. 函数模板特化
函数模板特化步骤:
- 必须先有一个基础的函数模板
- 写
template<> - 函数名后面加
<需要特化的类型>
代码如下:
template<class T>
bool Less(T left, T right)
{
return left < right;
}
// 特化 Date* 版本
template<>
bool Less<Date*>(Date* left, Date* right)
{
return *left < *right;
}
这样再调用Less(d1, d2),就会走特化版本,比较的是内容,而不是地址
小提示
函数模板特化写法有点麻烦,实际中我们直接写一个同名普通函数更简单,编译器会优先调用
3. 类模板特化(重点)
类模板特化比函数模板特化更常用,也更重要,分为两种:
- 全特化
- 偏特化
(1)全特化
全特化就是把所有模板参数都确定下来,函数模板的特化就类似于全特化,必须把所有模板参数都确定下来,不允许部分特化,接下来我们举一个全特化例子:
//特化类模板前,必须保证对应类模板存在
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
};
// 全特化:T1=int,T2=double
template<>
class Data<int, double>
{
public:
Data() { cout << "Data<int, double>" << endl; }
};
使用:
Data<int, int> d1; // 通用模板
Data<int, double> d2;// 全特化
(2)偏特化
偏特化有两种表现形式:
- 只特化部分参数
- 对参数类型加限制(指针、引用)
① 部分特化
template<class T1>
class Data<T1, int>
{
public:
Data() { cout << "Data<T1, int>" << endl; }
};
② 对类型进一步限制
// 特化为指针类型
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
Data() { cout << "Data<T1*, T2*>" << endl; }
};
使用:
Data<int*, int*> d1; // 走指针特化
Data<char, int> d2; // 走部分特化
类模板特化在STL里非常常见,比如vector<bool>、priority_queue的比较规则,都是靠特化实现的。
四、模板的分离编译(超级大坑)
这一块是模板最容易出错、面试最爱问的地方!
1. 什么是分离编译
我们平时写代码:
.h文件放声明.cpp文件放实现- 最后链接到一起
普通函数这样写完全没问题,但模板不行!
2. 为什么模板不能分离编译
模板的特点:只有被调用时,才会实例化出具体代码。
- 你在
.h声明模板 - 在
.cpp写实现,但没人调用它 - 编译器不会生成任何函数代码
- 最后main.cpp调用时,链接找不到函数 → 报错:无法解析外部符号
相当于就是头文件有声明,知道模板需要生成什么类型,但是没有实现,生成不出来,而cpp文件有实现,但是不知道需要生成什么类型,就不会实例化,最后就找不到,所以会链接失败
3. 解决方案(最实用)
把声明和实现写在同一个文件里!
一般命名为:
xxx.hpp- 或者直接用
.h
这是最推荐的写法。
// template.hpp
template<class T>
T Add(T a, T b)
{
return a + b;
}
直接包含.hpp文件,完美解决问题。
五、模板总结
优点
- 复用性强,可以快速写通用代码,实现泛型编程
- 模板是STL的基础,没有模板就没有STL
- 模板的灵活度极高,可适配各种类型,方便使用
缺点
- 有可能引起代码膨胀,使得编译变慢
- 报错信息极其混乱,很难定位错误,特别是复杂的代码,只能通过经验定位错误
- 分离编译会报错,新手容易踩坑
模板有优有缺,但总体来说,模板是C++最核心、最强大的特性之一,也是进阶必备,希望大家都能好好掌握
那么今天关于模板进阶就讲到这里,有什么不懂欢迎私信问我,我会及时做出解答!
下一篇文章我们开始学习继承,把C++面向对象的特性之一彻底吃透,敬请期待吧!
bye~
更多推荐


所有评论(0)