C++: 模板基础
最后更新:2026-08-26
前面的课程里,我们写的函数或类都只能处理一种类型。
但如果你要写一个"能处理
int、double、string的max函数呢?模板就是解决这个问题——让函数和类能支持任意类型。
1. 什么是模板?
(1) 1.1 生活中的模板*
| 生活场景 | 程序中的对应 |
|---|---|
| 做蛋糕的模具(可以倒不同口味的面糊) | 模板 |
| Word 邮件合并(一份模板,填入不同收件人) | 模板 |
模板的本质: 让编译器根据使用时的类型,自动生成对应版本的函数或类。
(2) 1.2 为什么需要模板?
不用模板(重复代码):
#include <iostream>
// 处理 int 的 max
int max(int a, int b) {
return a > b ? a : b;
}
// 处理 double 的 max
double max(double a, double b) {
return a > b ? a : b;
}
// ... 要写 N 个版本!
用模板(代码复用):
#include <iostream>
// 函数模板
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
int main() {
std::cout << max(3, 5) << std::endl; // 编译器自动生成 int 版本
std::cout << max(3.14, 2.72) << std::endl; // 编译器自动生成 double 版本
return 0;
}
2. 函数模板*
(1) 2.1 基本语法*
template <typename TypeName>
ReturnType functionName(parameterList) {
// function body
}
💡 重点: typename 也可以写成 class(两者等价),但推荐用 typename(更直观)。
▶ 示例 1:函数模板(难度⭐)
#include <iostream>
// 函数模板
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
int main() {
std::cout << "max(3, 5) = " << max(3, 5) << std::endl;
std::cout << "max(3.14, 2.72) = " << max(3.14, 2.72) << std::endl;
// 也可以显式指定类型
std::cout << "max<int>(3, 5) = " << max<int>(3, 5) << std::endl;
return 0;
}
输出:
max(3, 5) = 5
max(3.14, 2.72) = 3.14
max<int>(3, 5) = 5
3. 类模板*
(1) 3.1 基本语法*
template <typename TypeName>
class ClassName {
// members
};
▶ 示例 2:类模板 Pair(难度⭐⭐)
#include <iostream>
#include <string>
// 类模板:存一对值
template <typename T1, typename T2>
class Pair {
private:
T1 first;
T2 second;
public:
Pair(const T1& a, const T2& b) : first(a), second(b) {}
void print() {
std::cout << "(" << first << ", " << second << ")" << std::endl;
}
};
int main() {
Pair<int, double> p1(3, 3.14);
Pair<std::string, int> p2("Alice", 20);
p1.print(); // (3, 3.14)
p2.print(); // (Alice, 20)
return 0;
}
输出:
(3, 3.14)
(Alice, 20)
💡 重点: 类模板使用时必须显式指定类型(除非构造函数能推导)。
4. 模板的声明与定义*
(1) 4.1 问题:模板应该放哪里?*
如果你把模板的声明放 .h,定义放 .cpp,会链接错误!
原因: 模板不是真正的函数/类,它是编译时生成代码的"模具"。
- 如果声明放
.h,定义放.cpp,编译器在编译.cpp时不知道要生成哪些类型版本。 - 只有使用了模板的地方,编译器才知道要生成哪些版本。
(2) 4.2 解决方法:声明和定义放一起*
推荐写法: 把模板的声明和定义都放 .h 文件里。
// pair.h(头文件)
#ifndef PAIR_H
#define PAIR_H
template <typename T1, typename T2>
class Pair {
private:
T1 first;
T2 second;
public:
Pair(const T1& a, const T2& b);
void print();
};
// 定义也放这里!
template <typename T1, typename T2>
Pair<T1, T2>::Pair(const T1& a, const T2& b) : first(a), second(b) {}
template <typename T1, typename T2>
void Pair<T1, T2>::print() {
std::cout << "(" << first << ", " << second << ")" << std::endl;
}
#endif
💡 提示: 这是模板和普能函数/类最大的区别——模板通常声明和定义放一起。
5. 实战:简单数组模板*
▶ 示例 3:实现 Array 类模板(难度⭐⭐⭐)
#include <iostream>
#include <cassert>
template <typename T>
class Array {
private:
T* data;
int size;
public:
Array(int sz) : size(sz) {
data = new T[size];
}
~Array() {
delete data;
}
// 拷贝构造函数(深拷贝)
Array(const Array& other) : size(other.size) {
data = new T[size];
for (int i = 0; i < size; i++) {
data[i] = other.data[i];
}
}
// 重载 运算符
T& operator(int index) {
assert(index >= 0 && index < size);
return data[index];
}
int getSize() const {
return size;
}
};
int main() {
Array<int> arr(5);
for (int i = 0; i < arr.getSize(); i++) {
arr[i] = (i + 1) * 10;
}
for (int i = 0; i < arr.getSize(); i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
return 0;
}
输出:
10 20 30 40 50
▶ 示例 3:函数模板(难度⭐)
#include <iostream>
template<typename T>
T getMax(T a, T b) {
return (a > b) ? a : b;
}
int main() {
std::cout << "int最大:" << getMax(3, 5) << std::endl;
std::cout << "double最大:" << getMax(3.14, 2.71) << std::endl;
std::cout << "string最大:" << getMax("apple", "banana") << std::endl;
return 0;
}
输出:
int最大:5
double最大:3.14
string最大:banana
T 的实际类型。
Q:模板和函数重载有什么区别? A:> - 函数重载:多个同名函数,参数类型不同(编译时确定调用哪个) > - 函数模板:一个"模具",编译器根据使用时的类型自动生成对应版本 > > 推荐: 如果逻辑完全一样(只是类型不同),用模板;如果逻辑不同,用重载。 Q:模板能支持所有类型吗? A:不能!模板要求类型支持某些操作。 > > template
<typename T>> T add(T a, T b) { > return a + b; // 要求 T 支持 operator+ > } > > // 如果 T 是自定义类,但没有重载 operator+,会编译报错。 > Q:什么时候该用模板? A:当你的函数/类的逻辑和类型无关时。 > > 典型场景: > - 容器类(std::vector、std::array) > - 算法(std::sort、std::find) > - 工具函数(std::max、std::min、std::swap)
Q:关于templates basics最重要的是什么? A:先理解核心概念,再通过实践例子来巩固。
❓ 常见问题
📖 小节
- 函数模板:泛型编程,编译时实例化
- 模板参数用 typename/class 声明
- 类模板:容器、智能指针等通用抽象
- 模板特化:为特定类型定制实现
- 模板的优势:代码复用、类型安全、零开销
📝 作业
-
基础题 (Difficulty ⭐): 写一个函数模板
T min(T a, T b),返回两个值中的较小值。 -
进阶题 (Difficulty ⭐⭐): 写一个类模板
BoxT,包含一个T类型的成员,以及void set(T val)和T get()成员函数。 -
挑战题 (Difficulty ⭐⭐⭐): 扩展上面的
Array类模板,添加: -
void push_back(const T& val)(动态扩容) -
void pop_back() -
用模板实现,让它能存任意类型
6. 🚀 下一步*
学会了模板基础,接下来我们学习 STL 容器(第34课)—— 用 C++ 标准库提供的现成容器,不用自己造轮子!