乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

函数模板

定义与作用

函数模板是参数化的函数蓝图——用类型或值作为参数,编译器在调用时自动或显式地生成具体函数实例:

template <typename T>
T max(T a, T b) {
    return a > b ? a : b;
}

int    i = max(3, 5);        // 实例化 max<int>
double d = max(3.14, 2.71);  // 实例化 max<double>

它的核心价值:一次编写,多类型复用——避免为每种类型手写一个重载版本。

核心原理

模板实例化流程

两阶段查找

完整示例

示例一:泛型数据统计工具

场景说明:一个数据统计类,包含求最大值、求和、平均值等泛型函数。

#include <iostream>
#include <vector>
#include <string>
#include <numeric>
#include <type_traits>

// 1. 基本函数模板:最大值
template <typename T>
T maximum(T a, T b) {
    return a > b ? a : b;
}

// 2. 三个参数的重载
template <typename T>
T maximum(T a, T b, T c) {
    return maximum(maximum(a, b), c);
}

// 3. 模板 + 非模板参数:数组求和
template <typename T>
T sum(const std::vector<T>& data) {
    T total = T{};  // 值初始化(int→0, double→0.0)
    for (const auto& v : data) {
        total += v;
    }
    return total;
}

// 4. 返回类型声明:平均值(用后置返回类型自动推导)
template <typename T>
auto average(const std::vector<T>& data) -> decltype(T{} / data.size()) {
    if (data.empty()) return T{};
    return sum(data) / static_cast<T>(data.size());
}

// 5. 多个模板参数:两容器相等比较
template <typename T1, typename T2>
bool container_equal(const std::vector<T1>& a, const std::vector<T2>& b) {
    if (a.size() != b.size()) return false;
    for (size_t i = 0; i < a.size(); ++i) {
        if (a[i] != static_cast<T1>(b[i])) return false;
    }
    return true;
}

// 6. 非类型模板参数:固定大小数组的泛型打印
template <typename T, size_t N>
void print_array(const T (&arr)[N]) {
    std::cout << "[";
    for (size_t i = 0; i < N; ++i) {
        if (i > 0) std::cout << ", ";
        std::cout << arr[i];
    }
    std::cout << "]" << std::endl;
}

int main() {
    std::cout << "===== 基本函数模板 =====" << std::endl;
    std::cout << "max(3, 7) = " << maximum(3, 7) << std::endl;
    std::cout << "max(3.14, 2.71) = " << maximum(3.14, 2.71) << std::endl;
    std::cout << "max('a', 'z') = " << maximum('a', 'z') << std::endl;
    std::cout << "max(1, 5, 3) = " << maximum(1, 5, 3) << std::endl;

    // 显式指定模板参数(避免歧义)
    std::cout << "max<double>(3, 4.5) = " << maximum<double>(3, 4.5) << std::endl;

    std::cout << "\n===== 容器统计 =====" << std::endl;
    std::vector<int> scores = {85, 92, 78, 95, 88};
    std::cout << "总分: " << sum(scores) << std::endl;
    std::cout << "平均分: " << average(scores) << std::endl;

    std::vector<double> prices = {19.99, 29.50, 15.75};
    std::cout << "总价: " << sum(prices) << std::endl;
    std::cout << "均价: " << average(prices) << std::endl;

    std::cout << "\n===== 多类型模板参数 =====" << std::endl;
    std::vector<int> a = {1, 2, 3};
    std::vector<double> b = {1.0, 2.0, 3.0};
    std::cout << "containers equal? " << std::boolalpha
              << container_equal(a, b) << std::endl;

    std::cout << "\n===== 非类型模板参数 =====" << std::endl;
    int arr[] = {10, 20, 30, 40, 50};
    print_array(arr);

    return 0;
}

预期输出:

===== 基本函数模板 =====
max(3, 7) = 7
max(3.14, 2.71) = 3.14
max('a', 'z') = z
max(1, 5, 3) = 5
max<double>(3, 4.5) = 4.5

===== 容器统计 =====
总分: 438
平均分: 87.6
总价: 65.24
均价: 21.7467

===== 多类型模板参数 =====
containers equal? true

===== 非类型模板参数 =====
[10, 20, 30, 40, 50]

逐段分析:

  • maximum(T a, T b) 是最基础的函数模板——T 从实参推导,两个参数类型必须一致
  • maximum<double>(3, 4.5) 显式指定 T=double——解决 int 和 double 的推导歧义
  • auto average(...) -> decltype(...) 用后置返回类型 + decltype 推导返回类型——比直接写 double 更泛用
  • container_equal 有两个独立的类型参数 T1 和 T2——允许比较 int 向量和 double 向量
  • print_array 使用非类型模板参数 size_t N——编译期推导数组大小,无运行时开销

示例二:swap 函数模板的设计

#include <iostream>
#include <vector>
#include <string>

// 泛型 swap——适用于任何可移动类型
template <typename T>
void my_swap(T& a, T& b) {
    T temp = std::move(a);  // 移动而非拷贝
    a = std::move(b);
    b = std::move(temp);
}

// 为 std::string 提供特化版本(演示——实际无需,泛型版已足够)
template <>
void my_swap(std::string& a, std::string& b) {
    std::cout << "  [使用 std::string 特化版 swap]" << std::endl;
    a.swap(b);  // 更高效的成员 swap
}

// 泛型打印函数
template <typename T>
void print(const std::string& label, const T& value) {
    std::cout << label << " = " << value << std::endl;
}

int main() {
    std::cout << "===== 泛型 swap =====" << std::endl;

    int x = 10, y = 20;
    std::cout << "交换前: x=" << x << ", y=" << y << std::endl;
    my_swap(x, y);
    std::cout << "交换后: x=" << x << ", y=" << y << std::endl;

    std::string s1 = "Hello", s2 = "World";
    std::cout << "交换前: s1=" << s1 << ", s2=" << s2 << std::endl;
    my_swap(s1, s2);  // 调用特化版本
    std::cout << "交换后: s1=" << s1 << ", s2=" << s2 << std::endl;

    return 0;
}

预期输出:

===== 泛型 swap =====
交换前: x=10, y=20
交换后: x=20, y=10
交换前: s1=Hello, s2=World
  [使用 std::string 特化版 swap]
交换后: s1=World, s2=Hello

易错场景与面试考点

易错场景

场景错误表现正确做法
类型推导歧义:max(1, 2.5)编译错误max<double>(1, 2.5) 或 max<int>(1, 2.5)
数组传参退化:template<T> f(T arr)T 推导为指针用 T (&arr)[N] 非类型模板参数
返回类型推导:auto vs decltype(auto)auto 丢弃引用和 cv需要保留引用/const 时用 decltype(auto)
模板定义在 .cpp 文件中链接错误模板定义必须在头文件(或显式实例化)
typename 和 class 混用引起歧义历史习惯选择一种风格统一使用

常见面试问题

  1. 函数模板的实例化时机?——模板被使用时(ODR-used),编译器根据实参类型生成具体函数代码。这是隐式实例化。也可以显式实例化:template int max(int, int);。

  2. 模板参数推导失败的情况有哪些?——(1) 同一个 T 推导出不同类型(max(1, 2.5));(2) 模板参数只出现在无法推导的上下文中(如 T::value_type);(3) 非类型模板参数无法从实参推导。

  3. 什么是两阶段查找(Two-Phase Lookup)?——第一阶段在模板定义时查找非依赖名并做语法检查;第二阶段在模板实例化时查找依赖名并做语义检查。ADL 在第二阶段进行。

  4. 为什么模板函数通常定义在头文件中?——编译器需要看到完整的模板定义才能实例化。定义在 .cpp 中会导致其他翻译单元找不到定义。解决方案:头文件全定义、或使用 extern template + 显式实例化。

  5. typename 和 class 在模板参数中有区别吗?——没有。template<typename T> 和 template<class T> 完全等价。typename 更准确(T 不一定是类),但 class 更短。

小结

  • 函数模板是编译期多态——为每种使用的类型生成一份代码
  • 模板参数推导优先于隐式转换——类型不一致时显式指定参数
  • 两阶段查找:非依赖名在定义时查,依赖名在实例化时查
  • 模板定义必须在头文件——除非使用显式实例化
  • 非类型模板参数(如 size_t N)在编译期确定,零运行时开销
下一页
类模板