跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

C++ 模板初阶:泛型编程基础

C++ 模板通过参数化类型实现泛型编程,避免代码重复。函数模板允许编译器根据实参推导类型,支持隐式和显式实例化。类模板则用于定义通用数据结构。涵盖模板语法、实例化机制及匹配原则,帮助理解 C++ 泛型核心。

kaikai发布于 2026/3/20更新于 2026/4/252 浏览
C++ 模板初阶:泛型编程基础

C++ 模板初阶:泛型编程基础

前言

C++ 模板是实现泛型编程的核心工具,允许编写与类型无关的代码,从而提升复用性和灵活性。模板在编译时实例化,根据实际使用的类型生成具体代码,因此不会产生额外的运行时开销。

一、模板基础

1.1 为什么需要模板?

在编写函数或类时,如果希望它们能处理多种数据类型(如 int、double、string),传统方法是使用函数重载,但这会产生大量重复代码或丢失类型信息。

模板允许将类型作为参数,编译器根据调用时传入的具体类型生成对应的代码。

场景: 需要编写一个求两个数最大值的函数,支持 int、double 和 string(按字典序)。

① 传统方法:函数重载
#include <iostream>
#include <string>
using namespace std;

// 为 int 重载
int max(int a, int b) {
    cout << "int version\n";
    return a > b ? a : b;
}

// 为 double 重载
double max(double a, double b) {
    cout << "double version\n";
    return a > b ? a : b;
}

// 为 string 重载
string max(const string& a, const string& b) {
    cout << "string version\n";
    return a > b ? a : b;
}

int main() {
    cout << max(3, 5) << "\n";       // int version
    cout << max(3.14, 2.7) << "\n"; // double version
    cout << max("hello", "world") << "\n"; // string version
    return 0;
}

缺点:

  • 代码重复:每个类型都要写一遍几乎相同的函数体。
  • 扩展性差:若要支持新类型(如 char、float),必须添加新重载。
  • 维护困难:修改算法(如改为返回较小值)需要改动所有重载函数。
② 模板方法:函数模板
#include <iostream>
#include <string>
using namespace std;

template <typename T>
T max(T a, T b) {
    cout << "template version for " << typeid(T).name() << "\n";
    return a > b ? a : b; // 要求 T 支持 operator>
}

int main() {
    cout << max(3, 5) << "\n";       // T = int
    cout << max(3.14, 2.7) << "\n"; // T = double
    cout << max("hello", "world") << "\n"; // T = string
    // 甚至支持新类型,只要它实现了 operator>
    cout << max('a', 'b') << "\n";   // T = char
    return 0;
}

优点:

  • 一份代码适用于所有类型,只要该类型支持 operator>。
  • 无需为每种类型单独编写,维护成本低。
  • 编译器根据实际调用生成对应版本的函数,效率与手写重载相同。

1.2 泛型编程思想

模板是 C++ 支持泛型编程的基础,其核心思想是'参数化类型'——将类型也视为一种参数,让算法和数据结构独立于具体类,从而编写与类型无关的通用代码,使之能够进行代码复用。

1.3 模板的分类

一般而言,模板分为两类:函数模板和类模板。

注意: 一个模板只能服务于一个函数或者一个类。

二、函数模板

2.1 函数模板的概念

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

2.2 函数模板的定义

函数模板定义以 template 关键字开头,后跟模板参数列表,然后是函数声明。

模板函数的语法:

  • 关键字 template <class 模板名称> 或 template<typename 模板名称>
  • 例如:template<class T> 或 template<typename T>
  • 备注:这里的模板名称为 T(可自定义为任意名称)
  • 提示:typename 是用来定义模板参数关键字,也可以使用 class(切记:不能使用 struct 代替 class)

通过函数模板定义函数:

template<class T>
void Swap(T& a, T& b) {
    // 函数实现
}

这里的返回类型也可以是模板参数:

template<class T>
T max(T a, T b) {
    // 函数实现
}

通过模板定义多个模板,实现接受不同类型的参数:

template<class T1, class T2>
// ...

提示: A. 当只有一个模板参数时,一般须传入相同类型的实参。 B. 当有两个模板参数时,可以传不同类型的实参。

代码示例 1:通过函数模板编写一个交换两个数的函数。

#include <iostream>
using namespace std;

template<class T>
void Swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

int main() {
    int m = 10, n = 20;
    double a = 1.2, b = 2.5;
    Swap(m, n);
    Swap(a, b);
    return 0;
}

代码示例 2:通过函数模板编写一个求两个数最大值的函数。

#include <iostream>
using namespace std;

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

int main() {
    int i = max(3, 5);      // T 被推导为 int
    double d = max(3.14, 2.7); // T 被推导为 double
    return 0;
}

代码示例 3:通过模板定义多个模板,以实现接受不同类型的参数。

#include <iostream>
using namespace std;

template<class T1, class T2>
void func2(const T1& x, const T2& y) {
    cout << x << " " << y << endl;
}

int main() {
    int a = 10;
    double b = 3.14;
    func2(a, b); // T1 被推导为 int 类型,T2 被推导为 double 类型
    return 0;
}

2.3 函数模板的原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具,所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。

例如:当用 double 类型使用函数模板时,编译器通过对实参类型的推演,将 T 确定为 double 类型,然后产生一份专门处理 double 类型的代码,对于 char 类型也是如此。

2.4 函数模板的实例化

不同类型的参数使用函数模板时,称为函数模板的实例化,模板参数实例化分为:隐式实例化和显式实例化。

1. 隐式实例化

让编译器根据实参推演模板参数的实际类型。

代码示例:通过函数模板实现两数相加。

#include <iostream>
using namespace std;

template<class T>
T Add(const T& left, const T& right) {
    return left + right;
}

int main() {
    int a1 = 10, a2 = 20;
    double d1 = 10.0, d2 = 20.0;
    Add(a1, a2);
    Add(d1, d2);
    return 0;
}

注意: 如果只有一个模板参数时,传入两个不同类型,该语句不能通过编译。

#include <iostream>
using namespace std;

template<class T>
T Add(const T& left, const T& right) {
    return left + right;
}

int main() {
    int a1 = 10;
    double d1 = 3.14;
    Add(a1, d1); // 编译报错
    return 0;
}

报错原因: 因为在编译期间,当编译器看到该实例化时,需要推演其实参类型: 通过实参 a1 将 T 推演为 int,通过实参 d1 将 T 推演为 double 类型,但模板参数列表中只有一个 T,编译器无法确定此处到底该将 T 确定为 int 或者 double 类型而报错。

注意: 在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅。

解决办法一:用户自己来强制转化

#include <iostream>
using namespace std;

template<class T>
T Add(const T& left, const T& right) {
    return left + right;
}

int main() {
    int a1 = 10;
    double d1 = 3.14;
    Add(a1, (int)d1); // 成功,显式转换
    return 0;
}

解决办法二:显式实例化

#include <iostream>
using namespace std;

template<class T>
T Add(const T& left, const T& right) {
    return left + right;
}

int main() {
    int a1 = 10;
    double d1 = 3.14;
    Add<int>(a1, d1); // 显式指定 T 为 int
    return 0;
}

详情解释:显式指定模板参数 例如:Add<int>(a1, d1) 或 Add<double>(a1, d1)。 这意味着你在调用时明确告诉编译器:'模板参数 T 就是 int(或 double),不要再推导了',此时编译器会跳过类型推导过程,直接使用你指定的类型来实例化函数模板。

编译器会检查这个调用是否合法:

  1. 对于 Add<int>(a1, d1),生成的函数签名是 int Add(const int&, const int&)。
  2. 实参 a1 是 int,可以直接绑定到 const int&。
  3. 实参 d1 是 double,但 double 可以隐式转换为 int,因此编译器会生成一个临时 int 对象(值为 d1 截断后的整数)传递给函数。 这种隐式转换是允许的,因此调用成功。

同理,Add<double>(a1, d1) 会将 a1 从 int 隐式转换为 double,也可以正常调用。

2. 显式实例化

在函数名后的 <> 中指定模板参数的实际类型。

代码示例:显示实例化 Add 函数的模板参数

#include <iostream>
using namespace std;

template<class T>
T Add(const T& left, const T& right) {
    return left + right;
}

int main() {
    int a = 10;
    double b = 20.0;
    // 显式实例化
    Add<int>(a, b);
    return 0;
}

2.5 模板参数的匹配原则

匹配原则一: 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。

代码示例:验证匹配原则一

// 专门处理 int 的加法函数
int Add(int left, int right) {
    return left + right;
}

// 通用加法函数
template<class T>
T Add(T left, T right) {
    return left + right;
}

void Test() {
    Add(1, 2);       // 与非模板函数匹配,编译器不需要特化
    Add<int>(1, 2);  // 调用编译器特化的 Add 版本
}

匹配原则二:

  1. 对于非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从该模板产生出一个实例。
  2. 如果模板可以产生一个具有更好匹配的函数,那么将选择模板。

代码示例:验证匹配原则二

// 专门处理 int 的加法函数
int Add(int left, int right) {
    return left + right;
}

// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right) {
    return left + right;
}

void Test() {
    Add(1, 2);       // 与非函数模板类型完全匹配,不需要函数模板实例化
    Add(1, 2.0);     // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的 Add 函数
}

三、类模板

3.1 类模板的概念

类模板不是具体的类,而是一个'类的蓝图'。它使用模板参数(通常是类型参数)来描述类中成员的类型,这些成员包括数据成员、成员函数的参数和返回值等。

3.2 类模板的定义

类模板的定义必须以 template 关键字开头,后面紧跟模板参数列表(用尖括号 <> 包围)。这是类模板语法的核心标志,告诉编译器接下来的类是一个模板,可以参数化类型或值。

基本语法定义如下所示:

template<class T1, class T2, ..., class Tn> 或  template<typename T1, typename T2, ..., typename Tn>
class 类模板名
{
    // 类内成员定义
};

提示:

  1. template <typename T> 和 template <class T> 中的 T 是一个占位符类型名,在使用时会被实际类型替换。
  2. 类定义内可以使用 T 声明成员变量、成员函数参数和返回类型。
  3. 成员函数如果在类外定义,必须再次声明模板参数,并使用完整的类名 <T> 限定。

代码示例:演示语法

template <typename T> // T 是类型参数,也可用 class 代替 typename
class Stack {
public:
    void push(const T& x);
    T pop();
private:
    T data[100];
    int top;
};

代码示例:成员函数的外部定义

成员函数如果在类外定义,必须再次声明模板参数,并使用完整的类名 <T> 限定:

template <typename T>
class Stack {
public:
    void push(const T& x);
    T pop();
private:
    T data[100];
    int top;
};

template <typename T>
void Stack<T>::push(const T& x) {
    data[++top] = x;
}

template <typename T>
T Stack<T>::pop() {
    return data[top--];
}

3.3 类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟 <>,然后将实例化的类型放在 <> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

简单来说:对于模板类而言,类名不能代表类型,类名需要绑定模板参数才能成为类型。

代码示例:演示 stack 类代码实例化

#include <iostream>
#include <cstring>
using namespace std;

template<class T>
class Stack {
public:
    Stack(int n = 4) : _array(new T[n]), _capacity(n), _size(0) {}
    // 类内声明 类外定义的写法
    void Push(const T& x);
    ~Stack() { delete[] _array; }
private:
    T* _array;
    size_t _capacity;
    size_t _size;
};

template<class T>
void Stack<T>::Push(const T& x) {
    if (_size == _capacity) {
        // 扩容处理:开新空间 -> 拷贝内容 -> 释放旧空间 -> 指向新空间 -> 更新容量
        int newcapacity = _capacity * 2;
        T* tmp = new T[newcapacity];
        memcpy(tmp, _array, _capacity * sizeof(T)); // 修复:需乘以 sizeof(T)
        delete[] _array;
        _array = tmp;
        _capacity *= 2;
    }
    _array[_size++] = x;
}

int main() {
    // 类模板都需要显示实例化,不能做到隐式实例化
    Stack<int> st1; // 整形 int 类
    st1.Push(1);
    st1.Push(2);
    st1.Push(3);

    Stack<double> st2; // 浮点数 double 类
    st2.Push(1.1);
    st2.Push(1.2);
    st2.Push(1.3);

    Stack<double>* pst = new Stack<double>();
    return 0;
}

本文涵盖了 C++ 模板的基础概念、函数模板与类模板的语法及实例化机制,以及相关的匹配原则。理解这些内容是掌握 C++ 泛型编程的关键。

目录

  1. C++ 模板初阶:泛型编程基础
  2. 前言
  3. 一、模板基础
  4. 1.1 为什么需要模板?
  5. ① 传统方法:函数重载
  6. ② 模板方法:函数模板
  7. 1.2 泛型编程思想
  8. 1.3 模板的分类
  9. 二、函数模板
  10. 2.1 函数模板的概念
  11. 2.2 函数模板的定义
  12. 2.3 函数模板的原理
  13. 2.4 函数模板的实例化
  14. 1. 隐式实例化
  15. 2. 显式实例化
  16. 2.5 模板参数的匹配原则
  17. 三、类模板
  18. 3.1 类模板的概念
  19. 3.2 类模板的定义
  20. 3.3 类模板的实例化
  • 💰 8折买阿里云服务器限时8折了解详情
  • 💰 8折买阿里云服务器限时8折购买
  • 🦞 5分钟部署阿里云小龙虾了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog

更多推荐文章

查看全部
  • 归并排序实战:计算右侧小于当前元素个数与翻转对
  • Linux 初始网络(下):局域网通信与跨网段传输原理
  • 前端核心面试题与实战知识点梳理
  • 算法基础:滑动窗口技巧与经典例题解析
  • AI Agent 新范式:FastGPT 集成 MCP 协议构建工具增强智能体
  • C++ 二叉搜索树实现详解
  • 数据结构基础:顺序表详解与动态实现
  • Qwen3+Qwen Agent 智能体开发实战:接入 MCP 工具(一)
  • C++ 二叉搜索树(BST)原理及核心操作实现
  • 数据结构详解:顺序表原理与实现
  • OpenClaw 安装部署与渠道接入指南
  • Linux 基础 IO:深入理解文件描述符机制
  • Linux 文件描述符与重定向实战:从原理到 minishell 实现
  • DeepSeek 深度使用指南:提示词技巧与本地知识库搭建
  • C++ 继承:面向对象代码复用的核心机制
  • 深入剖析 Spring 框架:架构、缺陷与演进之路
  • 数据结构与算法:链表分类详解与双向链表初始化实现
  • LeetCode Hot 100 链表经典题目实战解析
  • Python Pandas 库核心用法详解
  • C++ 继承机制详解:从基础概念到多继承实战

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online