C++--模版初阶

C++ 模板初阶完全指南

一篇帮你彻底搞懂 C++ 模板基础的复习笔记


一、泛型编程

1.1 为什么需要泛型编程?

问题:如何实现一个通用的交换函数?

cpp 复制代码
// 传统方式:函数重载
void Swap(int& left, int& right) {
    int temp = left;
    left = right;
    right = temp;
}

void Swap(double& left, double& right) {
    double temp = left;
    left = right;
    right = temp;
}

void Swap(char& left, char& right) {
    char temp = left;
    left = right;
    right = temp;
}
// ...... 每种类型都要写一遍

1.2 函数重载的缺点

缺点 说明
代码复用率低 仅仅是类型不同,却要重复编写
可维护性差 一处出错,所有重载都要修改
扩展困难 新类型出现需要手动添加函数

1.3 泛型编程概念

泛型编程 :编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础

复制代码
┌─────────────────────────────────────┐
│           泛型编程思想              │
├─────────────────────────────────────┤
│                                     │
│   模板 (模具)                       │
│      │                              │
│      ├──→ 填充 int    → int 版本    │
│      ├──→ 填充 double → double 版本 │
│      └──→ 填充 char   → char 版本   │
│                                     │
│   编译器自动生成,无需手写重复代码   │
│                                     │
└─────────────────────────────────────┘

二、函数模板

2.1 概念

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

2.2 定义格式

cpp 复制代码
template<typename T1, typename T2, ..., typename Tn>
返回值类型 函数名(参数列表) {
    // 函数体
}

2.3 示例

cpp 复制代码
template<typename T>
void Swap(T& left, T& right) {
    T temp = left;
    left = right;
    right = temp;
}

int main() {
    int a = 1, b = 2;
    double c = 1.0, d = 2.0;
    char e = 'a', f = 'b';

    Swap(a, b);  // 编译器生成 int 版本
    Swap(c, d);  // 编译器生成 double 版本
    Swap(e, f);  // 编译器生成 char 版本

    return 0;
}

⚠️ 注意typename 用来定义模板参数关键字,也可以使用 class,但不能使用 struct

cpp 复制代码
// 以下两种写法等价
template<typename T>
template<class T>

// 错误写法
template<struct T>  // ❌ 不允许

2.4 函数模板的原理

复制代码
┌───────────────────────────────────────────────────┐
│                 编译器工作流程                     │
├───────────────────────────────────────────────────┤
│                                                   │
│  源代码阶段:                                      │
│      template<typename T>                        │
│      void Swap(T& left, T& right) { ... }        │
│              ↓                                    │
│  这只是蓝图/模具,不是真正的函数!                  │
│                                                   │
│  编译阶段:                                        │
│      Swap(int, int)    → 推演 T = int            │
│      Swap(double, double) → 推演 T = double      │
│              ↓                                    │
│  编译器自动生成对应类型的具体函数                  │
│                                                   │
└───────────────────────────────────────────────────┘

核心思想:将本来应该我们做的重复的事情交给了编译器!


三、函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化

3.1 隐式实例化

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

cpp 复制代码
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);   // T 推演为 int
    Add(d1, d2);   // T 推演为 double

    // ❌ 编译错误!
    // Add(a1, d1);
    // a1 推演 T = int,d1 推演 T = double
    // 只有一个 T,编译器无法确定类型

    return 0;
}

3.2 解决类型冲突的两种方式

cpp 复制代码
int main() {
    int a = 10;
    double b = 20.0;

    // 方式一:用户强制转换
    Add(a, (int)b);      // b 强转为 int

    // 方式二:显式实例化
    Add<int>(a, b);      // 指定 T 为 int

    return 0;
}

3.3 显式实例化

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

cpp 复制代码
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);      // 指定 T = int,b 会隐式转换为 int

    return 0;
}

⚠️ 如果类型不匹配,编译器会尝试进行隐式类型转换,无法转换则报错。


四、模板参数的匹配原则

4.1 原则一:非模板函数与模板可共存

cpp 复制代码
// 专门处理 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);   // 调用模板实例化版本
}

4.2 原则二:优先匹配非模板函数

cpp 复制代码
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);        // 优先调用非模板函数(不需要实例化)
}

4.3 原则三:模板可产生更好匹配

cpp 复制代码
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);       // 调用非模板函数(int, int 完全匹配)
    Add(1, 2.0);     // 调用模板(生成 Add(int, double) 更匹配)
}

4.4 原则四:模板不自动类型转换

cpp 复制代码
template<class T>
T Add(T left, T right) {
    return left + right;
}

int main() {
    int a = 10;
    double b = 20.0;

    // Add(a, b);     // ❌ 编译错误!模板不会自动转换类型

    Add(a, (int)b);   // ✅ 手动转换
    Add<int>(a, b);   // ✅ 显式实例化

    return 0;
}

⚠️ 注意:编译器一般不会对模板参数进行类型转换,因为一旦转换出问题,编译器需要"背黑锅"!

4.5 匹配原则速查表

场景 调用结果
非模板函数完全匹配 ✅ 调用非模板函数
模板函数更匹配 ✅ 调用模板实例化
都不匹配但非模板可转换 ✅ 调用非模板函数
都不匹配且模板不可转换 ❌ 编译错误

五、类模板

5.1 定义格式

cpp 复制代码
template<class T1, class T2, ..., class Tn>
class 类模板名 {
    // 类内成员定义
};

5.2 示例:栈类模板

cpp 复制代码
#include<iostream>
using namespace std;

template<typename T>
class Stack {
public:
    Stack(size_t capacity = 4) {
        _array = new T[capacity];
        _capacity = capacity;
        _size = 0;
    }

    void Push(const T& data);

private:
    T* _array;
    size_t _capacity;
    size_t _size;
};

// 类外定义成员函数
template<class T>
void Stack<T>::Push(const T& data) {
    // 扩容逻辑
    _array[_size] = data;
    ++_size;
}

5.3 类模板的实例化

cpp 复制代码
int main() {
    Stack<int> st1;      // int 栈
    Stack<double> st2;   // double 栈

    return 0;
}

⚠️ 重要Stack 是类名,Stack<int> 才是类型!

5.4 类模板 vs 函数模板实例化

对比项 函数模板 类模板
实例化方式 可隐式推演 必须显式指定
语法 func(args)func<T>(args) ClassName<T>
类型确定 编译器推演 用户指定

六、模板注意事项

6.1 声明和定义分离

cpp 复制代码
// ❌ 不建议:声明和定义分离到 .h 和 .cpp
// 会出现链接错误!

// ✅ 建议:声明和定义放在同一个头文件中

原因后续章节会讲解(模板实例化需要完整定义可见)

6.2 模板参数命名

cpp 复制代码
// 单个参数
template<typename T>

// 多个参数
template<typename T1, typename T2>

// 混合使用
template<typename T, int N>  // N 是非类型模板参数

七、一图总结

复制代码
┌───────────────────────────────────────────────────────┐
│                   C++ 模板初阶总结                     │
├───────────────────────────────────────────────────────┤
│                                                       │
│   【泛型编程】                                         │
│      目的:编写与类型无关的通用代码                    │
│      核心:模板是泛型编程的基础                        │
│                                                       │
│   【函数模板】                                         │
│      格式:template<typename T>                       │
│      实例化:隐式 / 显式                              │
│      原理:编译器根据类型生成具体函数                  │
│                                                       │
│   【实例化规则】                                       │
│      隐式:编译器推演类型                              │
│      显式:用户指定 <类型>                            │
│                                                       │
│   【匹配原则】                                         │
│      1. 非模板函数与模板可共存                        │
│      2. 优先匹配非模板函数                            │
│      3. 模板可产生更好匹配时选模板                    │
│      4. 模板不自动类型转换                            │
│                                                       │
│   【类模板】                                           │
│      实例化必须显式指定类型                           │
│      Stack<int> 才是类型,Stack 只是类名              │
│                                                       │
└───────────────────────────────────────────────────────┘

八、记忆口诀

复制代码
模板模具造函数,类型填充自动生;
隐式推演显式定,优先匹配非模板;
类模板要显式写,Stack<int> 才是型;
声明定义放一起,分离编译会出错!

希望这篇笔记能帮助你快速复习 C++ 模板初阶知识!如有疑问,欢迎讨论交流。

相关推荐
算法鑫探18 小时前
闰年判断:C语言实战解析
c语言·数据结构·算法·新人首发
yaoxin52112318 小时前
384. Java IO API - Java 文件复制工具:Copy 示例完整解析
java·开发语言·python
lizhihai_9918 小时前
股市学习心得-AI算力20大硬件四金刚
学习
卖芒果的潇洒农民18 小时前
【0417】学习路线
学习
WBluuue18 小时前
数据结构与算法:康托展开、约瑟夫环、完美洗牌
c++·算法
NotFound48618 小时前
实战指南如何实现Java Web 拦截机制:Filter 与 Interceptor 深度分享
java·开发语言·前端
木子墨51618 小时前
LeetCode 热题 100 精讲 | 并查集篇:最长连续序列 · 岛屿数量 · 省份数量 · 冗余连接 · 等式方程的可满足性
数据结构·c++·算法·leetcode
浅时光_c18 小时前
14 结构体 共用体 枚举类型
c语言
Ava的硅谷新视界19 小时前
用了一天 Claude Opus 4.7,聊几点真实感受
开发语言·后端·编程
rabbit_pro19 小时前
Python调用onnx模型
开发语言·python