本文整理自学习笔记,涵盖模板基础、特化、注意事项以及常见面试题,适合复习
一、模板基础
1.1 函数模板
cpp
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
-
编译器根据实参推导模板参数,生成具体函数(实例化)
-
可显式指定模板参数:
max<int>(1, 2)
1.2 类模板
cpp
template <typename T>
class Stack {
std::vector<T> data;
public:
void push(const T& val) { data.push_back(val); }
void pop() { data.pop_back(); }
T& top() { return data.back(); }
};
-
类模板的成员函数只有在使用时才会被实例化
-
类模板不能隐式推导模板参数(C++17 起可通过构造函数推导,需显式指定)
1.3 模板参数的类型
-
类型参数 :
typename T或class T -
非类型参数 :
int N、size_t N、指针、引用等(编译期常量) -
模板模板参数 :
template <typename> class Container
二、模板特化
2.1 全特化
为特定类型提供完全不同的实现
cpp
template <>
class Stack<bool> {
// 特殊实现
};
2.2 偏特化
只限定部分模板参数(仅适用于类模板,函数模板无偏特化,可重载)
cpp
template <typename T>
class Stack<T*> { // 针对指针类型的偏特化
// ...
};
2.3 函数模板重载 vs 特化
-
函数模板不能偏特化,但可以用重载实现类似效果
-
函数模板的全特化语法:
cpp
template <>
const char* max<const char*>(const char* a, const char* b) { ... }
但通常推荐使用重载而非特化函数模板,因为重载解析规则更直观
三、注意事项与常见陷阱
3.1 模板的编译与分离编译
-
模板的定义通常必须放在头文件中 ,因为编译器在实例化时需要看到完整定义
-
若想分离编译,可以显式实例化(
.cpp中template class Stack<int>;),但不够灵活
3.2 typename 与 class 的区别
-
在声明模板参数时,两者等价
-
但在依赖类型中,必须用
typename告诉编译器这是一个类型:
cpp
typename T::iterator it; // 若没有 typename,编译器会认为 iterator 是静态成员
3.3 依赖名称
-
模板中依赖模板参数的名称称为依赖名称。对于非依赖名称,编译器会在第一阶段进行查找
-
使用
this->或Base<T>::来限定依赖基类中的名称
- 函数模板通常被隐式视为
inline,但这不是强制的,编译器自行决定。
四、高频面试题
Q1:函数模板与普通函数重载时,哪个优先?
A:普通函数优先。如果存在完全匹配的普通函数,则不会实例化函数模板
cpp
void max(int, int);
template <typename T> void max(T, T);
max(1, 2); // 调用普通函数
Q2:类模板的成员函数何时实例化?
A:只有被调用时才会实例化。这允许类模板只实现所需函数,未被使用的函数即使有错误也不会被编译 --按需实例化
Q3:typename 和 class 在模板参数中有什么区别?
A :在声明模板参数时没有区别,但在依赖类型中必须用 typename,如 typename T::value_type
Q4:什么是模板的偏特化?举例说明
A :针对部分模板参数进行特化。例如 template <typename T> class Stack<T*> 是针对指针类型的偏特化----其他参数保持原来不变
Q5:如何避免模板代码膨胀?
A:将不依赖模板参数的代码抽取到非模板基类中,或将公共部分封装到普通函数中
Q6:std::enable_if 的原理是什么?
A :它基于 SFINAE。当条件为 true 时,enable_if 有一个公共成员 type,否则没有,导致模板实例化失败,从而移除候选
Q7:什么是"两阶段名称查找"?
A:模板在定义时对非依赖名称进行第一轮查找(不依赖模板参数),在实例化时对依赖名称进行第二轮查找
Q8:模板元编程是编译时还是运行时?
A:编译时。利用模板特化和递归在编译期完成计算,如计算斐波那契数列
Q9:template <template <typename> class Container> 的作用?
A :模板模板参数允许将一个模板作为参数传递,例如 Stack<int, std::vector>,但 std::vector 有两个模板参数(第二参数是分配器),需注意匹配