Limesn
Limesn
发布于 2026-09-29 / 0 阅读
0

第九章 模板

1. 模板的本质是什么?发生在什么时期?

  • 本质:模板是 C++ 中编译期多态(静态多态)的实现方式,也是泛型编程(Generic Programming)的基础。它本身不是可执行代码,而是编译器用来生成代码的蓝图(模具)。它将“数据类型”抽象为参数,从而避免为每种类型重复编写逻辑。
  • 发生时期:发生在编译期(Compile-time)。编译器在遇到具体调用时,进行模板实例化(Instantiation),用具体类型替换模板参数,生成真正的函数或类代码。
  • 重要特性(延迟实例化):对于类模板,只有被使用的成员函数才会被实例化。未被调用的成员函数即使存在语法错误,只要不涉及模板参数的具体语义,编译依然可能通过。

2. 模板的基本语法是什么?使用哪些关键字?

  • 基本语法:
    // 函数模板
    template <typename T, int N> // T为类型参数,N为非类型参数
    T func(T a) { return a + N; }
    
    // 类模板
    template <class T>
    class MyClass { /* ... */ };
    
  • 关键字:
    • template:声明模板的必需关键字。
    • typename 与 class:在模板参数列表中,两者完全等价。推荐使用 typename,语义更清晰。
    • 依赖类型:当涉及依赖于模板参数的类型(如 T::iterator)时,必须使用 typename 显式声明它是一个类型,例如 typename T::value_type val;。
    • <>:尖括号,用于包裹模板参数列表。

3. 隐式实例化和显式实例化有什么区别?

维度隐式实例化 (Implicit)显式实例化 (Explicit)
触发者编译器(根据代码调用自动推导)程序员(手动编写代码强制生成)
语法max(1, 2); 或 MyClass<int> obj;template int max<int>(int, int);
template class MyClass<int>;
特点只有被调用的类型才会实例化;同一类型多次调用只实例化一次。无论是否被调用,都会生成代码。
工程用途常规使用。1. 显式实例化定义:将模板实现隐藏在 .cpp 中,减少头文件暴露。
2. 显式实例化声明(extern template class MyClass<int>;):C++11 引入,告诉编译器其他编译单元已实例化,本单元不要重复生成,极大缩短编译时间。

4. T, K...叫做什么?

  • 统称:模板参数(Template Parameters)。
  • 细分类:
    • T、K、V:类型参数(Type Parameters)。T 代表 Type,K 代表 Key,V 代表 Value。
    • ...Args:模板参数包(Template Parameter Pack)。Args 是名字,... 表示零个或多个。
    • int N:非类型参数(Non-type Parameter)。必须是编译期常量(整型、枚举、指针、引用,C++20 后支持浮点数等)。
    • template <typename T> class Container:模板模板参数(Template Template Parameter)。

5. 普通函数和函数模板重载时,谁的优先级更高?

  • 重载决议(Overload Resolution)规则:
    1. 精确匹配优先:如果普通函数和模板都能提供精确匹配,优先选择普通函数(普通函数不需要推导,更省编译时间)。
    2. 转换优先:如果普通函数需要隐式类型转换(如 int 转 double),而模板能推导出精确类型(如 T 推导为 int),则优先选择模板。
    3. 强制调用模板:使用空模板实参列表 max<>(a, b); 可以强制编译器只考虑模板。
    4. 特化优先:如果存在针对该类型的模板特化(Specialization),则特化版本优先于普通模板和普通函数。
  • 总结记忆:在匹配程度相同的前提下,优先级为:普通函数 > 模板特化 > 普通模板。

6. ...Args叫做什么?干嘛用的?

  • 名称:可变参数模板(Variadic Templates)(C++11 引入)。
  • 组成部分:
    • template <typename... Args>:Args 是模板参数包。
    • void func(Args... args):args 是函数参数包。
  • 作用:允许模板接受任意数量、任意类型的参数。它是实现完美转发(std::forward)、std::thread、std::make_unique 等标准库设施的基石。
  • 如何展开(C++17 前后差异):
    • C++11/14 方式:递归展开。需要写一个递归终止函数(处理最后一个参数),然后不断剥离第一个参数递归调用。
    • C++17 方式:折叠表达式(Fold Expressions)。极大简化了代码,例如:
      template <typename... Args>
      auto sum(Args... args) {
          return (args + ...); // 折叠表达式,自动展开为 args1 + args2 + ...
      }
      
    • 使用 sizeof...(Args) 可以获取参数个数。

7. 类模板怎么使用?

  • 基本使用:
    template <typename T>
    class Box {
        T content;
    public:
        Box(T c) : content(c) {}
        T get() { return content; }
    };
    
  • 实例化:
    • C++17 之前:必须显式指定类型。Box<int> b(10);
    • C++17 及以后(CTAD):支持类模板参数推导(Class Template Argument Deduction)。Box b(10);(编译器自动推导为 Box<int>)。
  • 成员函数类外定义:必须重新加上 template 声明。
    template <typename T>
    T Box<T>::get() { return content; }
    
  • 类模板的特化:
    • 全特化(Full Specialization):template <> class Box<bool> { ... };(针对特定类型完全重写)。
    • 偏特化(Partial Specialization):template <typename T> class Box<T*> { ... };(针对指针类型等特定模式重写)。

💡 进阶补充:
在现代 C++ 工程与面试中,除了上述基础,还需掌握 SFINAE(替换失败并非错误)、Concepts(C++20 概念约束) 以及 模板元编程(TMP),这些是区分 C++ 初级与高级开发者的重要分水岭。