C++ 模板:写一次代码,搞定所有类型——“万能模具“的魔力
·
🧰 如果说函数重载是"同名不同命",那模板就是"同名同命,但能适配所有类型"。你只需要写一套代码,编译器会自动帮你生成
int版、double版、string版……这就是 C++ 模板的"万能模具"魔法。本文从模板基础到实战编程,带你彻底掌握这个泛型编程的核心武器。
📖 文章导航
- 8.1 模板简介:模板是什么?为什么需要它?
- 8.2 函数模板:让函数适配任意类型
- 8.3 类模板:让类适配任意类型
- 8.4 模板编程:实战——栈类模板 & 链表类模板
8.1 模板简介——从"重复劳动"到"一劳永逸"
1.1 问题:没有模板的世界有多痛苦?
假设你要写一个求最大值的函数:
// int 版本
int maxInt(int a, int b) {
return (a > b) ? a : b;
}
// double 版本
double maxDouble(double a, double b) {
return (a > b) ? a : b;
}
// char 版本
char maxChar(char a, char b) {
return (a > b) ? a : b;
}
// string 版本
string maxString(string a, string b) {
return (a > b) ? a : b;
}
// 😩 每种类型都要写一遍,逻辑完全相同,只有类型不同!
💡 这就像你要做圆形的饼干、方形的饼干、心形的饼干,每种形状都买一个模具——太浪费了!如果有一个万能模具,换个形状就能用,那该多好?
1.2 解决方案:模板
// 一个模板搞定所有类型!
template <typename T>
T maxValue(T a, T b) {
return (a > b) ? a : b;
}
int main() {
cout << maxValue(3, 5) << endl; // int 版,输出 5
cout << maxValue(3.14, 2.71) << endl; // double 版,输出 3.14
cout << maxValue('a', 'z') << endl; // char 版,输出 z
cout << maxValue(string("apple"), string("banana")) << endl; // string 版,输出 banana
}
🎯 一句话理解模板:模板就是"类型的参数化"——把类型当作参数传进去,编译器帮你生成对应类型的代码。
8.2 函数模板——让函数变成"万能钥匙"
2.1 函数模板的定义
// 语法:
// template <typename T> 或 template <class T>
// 返回值类型 函数名(参数列表) { 函数体 }
// typename 和 class 在模板参数中完全等价,用哪个都行
template <typename T>
T maxValue(T a, T b) {
return (a > b) ? a : b;
}
2.2 函数模板的使用
#include <iostream>
#include <string>
using namespace std;
template <typename T>
T maxValue(T a, T b) {
return (a > b) ? a : b;
}
int main() {
// 方式一:自动类型推导(编译器根据参数推导 T 的类型)
cout << maxValue(3, 5) << endl; // T 被推导为 int
cout << maxValue(3.14, 2.71) << endl; // T 被推导为 double
// 方式二:显式指定类型
cout << maxValue<double>(3, 4.5) << endl; // 显式指定 T 为 double
// 3 被转为 3.0,结果是 4.5
// string 也能用!
cout << maxValue<string>("hello", "world") << endl; // world
return 0;
}
2.3 多类型参数的函数模板
// 两个不同的类型参数
template <typename T1, typename T2>
void showPair(T1 a, T2 b) {
cout << "(" << a << ", " << b << ")" << endl;
}
int main() {
showPair(1, 3.14); // (1, 3.14)
showPair("hello", 42); // (hello, 42)
showPair('A', true); // (A, 1)
return 0;
}
2.4 函数模板与普通函数的"较量"
// 普通函数
int maxValue(int a, int b) {
cout << "调用了普通函数" << endl;
return (a > b) ? a : b;
}
// 函数模板
template <typename T>
T maxValue(T a, T b) {
cout << "调用了函数模板" << endl;
return (a > b) ? a : b;
}
int main() {
maxValue(3, 5); // 调用了普通函数(普通函数优先匹配)
maxValue(3.14, 2.71); // 调用了函数模板(类型不匹配普通函数)
maxValue<>(3, 5); // 调用了函数模板(<> 强制调用模板版本)
maxValue<int>(3, 5); // 调用了函数模板(显式指定类型)
}
💡 匹配优先级:普通函数 > 模板生成的函数。但加了
<>就强制走模板。
2.5 函数模板的限制与特化
// 通用模板
template <typename T>
T maxValue(T a, T b) {
return (a > b) ? a : b;
}
// 针对 const char* 的特化版本(因为默认比较的是指针地址,不是字符串内容!)
template <>
const char* maxValue<const char*>(const char* a, const char* b) {
return (strcmp(a, b) > 0) ? a : b;
}
int main() {
// 通用模板:比较的是指针地址(不靠谱)
const char* s1 = "apple";
const char* s2 = "banana";
cout << maxValue(s1, s2) << endl; // 调用特化版本,正确比较字符串内容
// 用 string 就没这个问题
cout << maxValue(string("apple"), string("banana")) << endl; // banana
}
8.3 类模板——给类也装上"万能适配器"
3.1 类模板的定义
#include <iostream>
#include <string>
using namespace std;
// 定义一个类模板
template <typename T>
class Box {
T content; // T 类型的成员
public:
Box(T val) : content(val) {}
T getContent() const { return content; }
void setContent(T val) { content = val; }
void show() const { cout << "盒子里装的是: " << content << endl; }
};
3.2 类模板的实例化
int main() {
// 实例化:必须显式指定类型(类模板不能自动推导!)
Box<int> intBox(42);
intBox.show(); // 盒子里装的是: 42
Box<double> doubleBox(3.14);
doubleBox.show(); // 盒子里装的是: 3.14
Box<string> strBox("Hello Template!");
strBox.show(); // 盒子里装的是: Hello Template!
// 修改内容
intBox.setContent(100);
cout << intBox.getContent() << endl; // 100
// Box<> autoBox(42); // ❌ 编译错误!类模板必须显式指定类型
return 0;
}
💡 函数模板 vs 类模板:
- 函数模板:可以根据参数自动推导类型
- 类模板:必须显式指定类型(C++17 起部分情况可以推导)
3.3 多类型参数的类模板
template <typename K, typename V>
class Pair {
K key;
V value;
public:
Pair(K k, V v) : key(k), value(v) {}
K getKey() const { return key; }
V getValue() const { return value; }
void show() const { cout << key << " => " << value << endl; }
};
int main() {
Pair<string, int> student("张三", 90);
student.show(); // 张三 => 90
Pair<int, double> data(1, 3.14);
data.show(); // 1 => 3.14
return 0;
}
3.4 默认模板参数
// 给模板参数设置默认值
template <typename T, typename Container = vector<T>>
class Stack {
Container data;
public:
void push(const T& val) { data.push_back(val); }
void pop() { data.pop_back(); }
T top() const { return data.back(); }
bool empty() const { return data.empty(); }
size_t size() const { return data.size(); }
};
int main() {
// 使用默认的 vector<int> 作为底层容器
Stack<int> s1;
s1.push(10);
s1.push(20);
cout << s1.top() << endl; // 20
// 自定义底层容器为 deque<int>
Stack<int, deque<int>> s2;
s2.push(30);
cout << s2.top() << endl; // 30
return 0;
}
💡 默认模板参数就像函数的默认参数——不传就用默认值,传了就用你传的。
3.5 类模板的成员函数在类外定义
template <typename T>
class Calculator {
T value;
public:
Calculator(T v);
T getValue() const;
Calculator<T> add(const Calculator<T>& other) const;
};
// 类外定义时,每个函数前都要加 template <typename T>
template <typename T>
Calculator<T>::Calculator(T v) : value(v) {}
template <typename T>
T Calculator<T>::getValue() const {
return value;
}
template <typename T>
Calculator<T> Calculator<T>::add(const Calculator<T>& other) const {
return Calculator<T>(value + other.value);
}
int main() {
Calculator<int> a(10), b(20);
Calculator<int> c = a.add(b);
cout << c.getValue() << endl; // 30
}
8.4 模板编程实战——造两个经典数据结构
4.1 栈类模板(Stack)
#include <iostream>
#include <stdexcept>
using namespace std;
template <typename T>
class Stack {
static const int MAX_SIZE = 100;
T data[MAX_SIZE];
int topIndex;
public:
Stack() : topIndex(-1) {}
// 入栈
void push(const T& val) {
if (topIndex >= MAX_SIZE - 1) {
throw overflow_error("栈满了!溢出了!");
}
data[++topIndex] = val;
}
// 出栈
void pop() {
if (isEmpty()) {
throw underflow_error("栈空了!没东西可弹!");
}
topIndex--;
}
// 查看栈顶
T top() const {
if (isEmpty()) {
throw underflow_error("栈空了!没东西可看!");
}
return data[topIndex];
}
// 判空
bool isEmpty() const { return topIndex == -1; }
// 大小
int size() const { return topIndex + 1; }
};
int main() {
// int 栈
Stack<int> intStack;
intStack.push(10);
intStack.push(20);
intStack.push(30);
cout << "栈顶: " << intStack.top() << endl; // 30
cout << "大小: " << intStack.size() << endl; // 3
intStack.pop();
cout << "弹出后栈顶: " << intStack.top() << endl; // 20
// string 栈
Stack<string> strStack;
strStack.push("Hello");
strStack.push("World");
cout << "栈顶: " << strStack.top() << endl; // World
// double 栈
Stack<double> dblStack;
dblStack.push(3.14);
dblStack.push(2.71);
cout << "栈顶: " << dblStack.top() << endl; // 2.71
return 0;
}
💡 一个
Stack模板,三种类型(int/string/double)都能用——这就是模板的威力!
4.2 链表类模板(Linked List)
#include <iostream>
using namespace std;
template <typename T>
class LinkedList {
// 节点结构
struct Node {
T data;
Node* next;
Node(const T& val) : data(val), next(nullptr) {}
};
Node* head;
int count;
public:
LinkedList() : head(nullptr), count(0) {}
// 析构函数:释放所有节点
~LinkedList() {
Node* current = head;
while (current) {
Node* temp = current;
current = current->next;
delete temp;
}
}
// 头部插入
void pushFront(const T& val) {
Node* newNode = new Node(val);
newNode->next = head;
head = newNode;
count++;
}
// 尾部插入
void pushBack(const T& val) {
Node* newNode = new Node(val);
if (!head) {
head = newNode;
} else {
Node* current = head;
while (current->next) {
current = current->next;
}
current->next = newNode;
}
count++;
}
// 删除指定值(第一个匹配项)
bool remove(const T& val) {
if (!head) return false;
// 如果要删的是头节点
if (head->data == val) {
Node* temp = head;
head = head->next;
delete temp;
count--;
return true;
}
// 查找并删除
Node* current = head;
while (current->next && current->next->data != val) {
current = current->next;
}
if (current->next) {
Node* temp = current->next;
current->next = temp->next;
delete temp;
count--;
return true;
}
return false;
}
// 查找
bool contains(const T& val) const {
Node* current = head;
while (current) {
if (current->data == val) return true;
current = current->next;
}
return false;
}
// 大小
int size() const { return count; }
// 判空
bool empty() const { return count == 0; }
// 遍历打印
void print() const {
Node* current = head;
while (current) {
cout << current->data;
if (current->next) cout << " -> ";
current = current->next;
}
cout << " -> NULL" << endl;
}
// 用 [] 访问(重载运算符)
T operator[](int index) const {
if (index < 0 || index >= count) {
throw out_of_range("下标越界了!");
}
Node* current = head;
for (int i = 0; i < index; i++) {
current = current->next;
}
return current->data;
}
};
int main() {
// ===== int 链表 =====
cout << "=== int 链表 ===" << endl;
LinkedList<int> intList;
intList.pushBack(10);
intList.pushBack(20);
intList.pushBack(30);
intList.pushFront(5);
intList.print(); // 5 -> 10 -> 20 -> 30 -> NULL
cout << "第2个元素: " << intList[1] << endl; // 10
cout << "包含20? " << (intList.contains(20) ? "是" : "否") << endl; // 是
intList.remove(20);
intList.print(); // 5 -> 10 -> 30 -> NULL
// ===== string 链表 =====
cout << "\n=== string 链表 ===" << endl;
LinkedList<string> strList;
strList.pushBack("Hello");
strList.pushBack("C++");
strList.pushBack("Template");
strList.pushFront("Hi");
strList.print(); // Hi -> Hello -> C++ -> Template -> NULL
strList.remove("Hello");
strList.print(); // Hi -> C++ -> Template -> NULL
cout << "大小: " << strList.size() << endl; // 3
// ===== double 链表 =====
cout << "\n=== double 链表 ===" << endl;
LinkedList<double> dblList;
dblList.pushBack(3.14);
dblList.pushBack(2.71);
dblList.pushBack(1.41);
dblList.print(); // 3.14 -> 2.71 -> 1.41 -> NULL
return 0;
}
输出:
=== int 链表 ===
5 -> 10 -> 20 -> 30 -> NULL
第2个元素: 10
包含20? 是
5 -> 10 -> 30 -> NULL
=== string 链表 ===
Hi -> Hello -> C++ -> Template -> NULL
Hi -> C++ -> Template -> NULL
大小: 3
=== double 链表 ===
3.14 -> 2.71 -> 1.41 -> NULL
模板进阶技巧(选读)
类型约束:用 static_assert 限制模板类型
template <typename T>
T safeDivide(T a, T b) {
// 限制 T 只能是算术类型(int、double 等)
static_assert(is_arithmetic<T>::value, "T 必须是算术类型!");
if (b == 0) throw runtime_error("除数不能为零!");
return a / b;
}
int main() {
cout << safeDivide(10, 3) << endl; // 3
cout << safeDivide(10.0, 3.0) << endl; // 3.33333
// safeDivide<string>("a", "b"); // ❌ 编译错误:T 必须是算术类型!
}
可变参数模板(C++11)
// 递归终止
void print() {}
// 可变参数模板:支持任意数量的参数
template <typename T, typename... Args>
void print(T first, Args... rest) {
cout << first;
if (sizeof...(rest) > 0) cout << ", ";
print(rest...); // 递归调用
}
int main() {
print(1, 3.14, "hello", 'A', true);
// 输出:1, 3.14, hello, A, 1
}
总结:模板知识全景图
C++ 模板
│
├── 📖 模板简介(8.1)
│ → "类型的参数化",写一次代码适配所有类型
│
├── 🔧 函数模板(8.2)
│ ├── 定义:template <typename T> 返回类型 函数名(参数)
│ ├── 使用:自动推导 / 显式指定
│ ├── 重载:普通函数优先,<> 强制走模板
│ └── 特化:针对特定类型定制版本
│
├── 📦 类模板(8.3)
│ ├── 定义:template <typename T> class 类名
│ ├── 实例化:必须显式指定类型 Box<int>
│ ├── 多参数:template <typename K, typename V>
│ ├── 默认参数:template <typename T = int>
│ └── 类外定义:每个成员函数前都要加 template
│
└── 🏗️ 模板编程实战(8.4)
├── 栈类模板:push / pop / top / isEmpty
└── 链表类模板:pushFront / pushBack / remove / contains / []
🎯 一句话总结:模板就是 C++ 的"万能模具"——你只定义一次"形状",编译器就能帮你做出
int版、double版、string版的任何"饼干"。代码写一遍,类型千变万化,这就是泛型编程的魅力!
如果觉得有帮助,欢迎点赞收藏 👍 有问题可以在评论区讨论!
更多推荐
所有评论(0)