目录
[四、实例化:隐式 vs 显式](#四、实例化:隐式 vs 显式)
[隐式 vs 显式对比](#隐式 vs 显式对比)
[1. 模板定义与实现分离](#1. 模板定义与实现分离)
[2. 忘记 template 关键字](#2. 忘记 template 关键字)
[3. 模板参数名冲突](#3. 模板参数名冲突)
[4. 依赖名称需要 typename](#4. 依赖名称需要 typename)
一、为什么需要模板?
写一个交换两个整数的函数:
cpp
void swapInt(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
如果要交换 double、string、char......你得写 N 个几乎一样的函数:
cpp
void swapDouble(double& a, double& b) { double temp = a; a = b; b = temp; }
void swapString(string& a, string& b) { string temp = a; a = b; b = temp; }
// 无穷无尽...
模板的解决方案:写一次,适用于所有类型。
cpp
template <typename T>
void swapValues(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
// 使用
int x = 5, y = 10;
swapValues(x, y); // T 被推导为 int
double d1 = 1.2, d2 = 3.4;
swapValues(d1, d2); // T 被推导为 double
二、函数模板
基本语法
cpp
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
// 使用
int m1 = max(3, 5); // T 推导为 int
double m2 = max(3.14, 2.71); // T 推导为 double
// max(3, 4.5); // ❌ 错误:T 推导冲突(int vs double)
多个类型参数
cpp
template <typename T1, typename T2>
T1 convert(const T2& val) {
return static_cast<T1>(val);
}
int i = convert<int>(3.14); // 显式指定 T1,T2 推导为 double
函数模板的重载
cpp
template <typename T>
void print(T value) {
cout << "通用版本: " << value << endl;
}
void print(int value) {
cout << "int 特化: " << value << endl;
}
int main() {
print(42); // 调用普通函数(优先)
print(3.14); // 调用模板(无普通函数匹配)
print<int>(42); // 显式调用模板版本
}
三、类模板
基本语法
cpp
template <typename T>
class Stack {
private:
vector<T> data;
public:
void push(const T& val) { data.push_back(val); }
T pop() {
T top = data.back();
data.pop_back();
return top;
}
bool empty() const { return data.empty(); }
};
// 使用
Stack<int> intStack;
intStack.push(10);
intStack.push(20);
cout << intStack.pop(); // 20
Stack<string> strStack;
strStack.push("hello");
成员函数类外定义
cpp
template <typename T>
class Box {
private:
T content;
public:
void set(const T& val);
T get() const;
};
// 类外定义需要重复 template 声明
template <typename T>
void Box<T>::set(const T& val) {
content = val;
}
template <typename T>
T Box<T>::get() const {
return content;
}
多个模板参数
cpp
template <typename Key, typename Value>
class Pair {
private:
Key first;
Value second;
public:
Pair(const Key& k, const Value& v) : first(k), second(v) {}
Key getFirst() const { return first; }
Value getSecond() const { return second; }
};
Pair<string, int> p("age", 25);
四、实例化:隐式 vs 显式
隐式实例化(最常见)
编译器根据使用方式自动生成代码:
cpp
Stack<int> intStack; // 隐式实例化 Stack<int>
Stack<double> dblStack; // 隐式实例化 Stack<double>
每次用新类型实例化,编译器都会生成一份完整代码。
显式实例化
提前告诉编译器生成特定类型的代码(通常放在 .cpp 文件中):
cpp
// 在 .cpp 文件中
template class Stack<int>; // 显式实例化 Stack<int>
template class Stack<double>; // 显式实例化 Stack<double>
// 函数模板显式实例化
template void swapValues<int>(int&, int&);
用途:
-
减少编译时间(避免在多处隐式实例化)
-
将模板实现放在
.cpp文件中(而不是头文件) -
发布库时只提供实例化后的类型
隐式 vs 显式对比
| 特性 | 隐式实例化 | 显式实例化 |
|---|---|---|
| 代码位置 | 使用处 | 显式声明处 |
| 编译时机 | 使用时 | 编译时确定 |
| 头文件 | 需要完整定义 | 只需声明 |
| 适用场景 | 通用模板 | 固定类型集合 |
五、模板与继承
模板类作为基类
cpp
template <typename T>
class Base {
protected:
T value;
public:
Base(const T& v) : value(v) {}
virtual void print() const { cout << "Base: " << value << endl; }
};
// 派生类可以是普通类或模板类
class DerivedInt : public Base<int> {
public:
DerivedInt(int v) : Base<int>(v) {}
void print() const override { cout << "DerivedInt: " << value << endl; }
};
template <typename T>
class DerivedTemplate : public Base<T> {
public:
DerivedTemplate(const T& v) : Base<T>(v) {}
void print() const override {
cout << "DerivedTemplate: " << Base<T>::value << endl;
}
};
继承模板参数
cpp
template <typename T>
class Container : public T { // 继承自模板参数
// 这是 Mixin 模式
};
六、模板与友元
友元函数/类也可以与模板配合。
模板类的友元函数
cpp
template <typename T>
class MyClass {
private:
T secret;
public:
MyClass(const T& s) : secret(s) {}
// 所有 MyClass<T> 实例的友元(同类型)
friend void showSecret(const MyClass<T>& obj) {
cout << obj.secret << endl;
}
};
int main() {
MyClass<int> obj1(42);
showSecret(obj1); // 42
MyClass<string> obj2("hello");
showSecret(obj2); // hello
// showSecret(obj1) 和 showSecret(obj2) 是不同函数
}
所有实例共享的友元
cpp
template <typename T>
class MyClass {
private:
T secret;
public:
MyClass(const T& s) : secret(s) {}
// 所有 MyClass<任意类型> 的友元
template <typename U>
friend void showAnySecret(const MyClass<U>& obj);
};
template <typename U>
void showAnySecret(const MyClass<U>& obj) {
cout << obj.secret << endl;
}
七、完整例子:泛型动态数组
cpp
#include <iostream>
#include <stdexcept>
using namespace std;
template <typename T>
class DynamicArray {
private:
T* data;
size_t size;
size_t capacity;
void resize(size_t newCapacity) {
T* newData = new T[newCapacity];
for (size_t i = 0; i < size; i++) {
newData[i] = data[i];
}
delete[] data;
data = newData;
capacity = newCapacity;
}
public:
DynamicArray() : data(nullptr), size(0), capacity(0) {}
~DynamicArray() {
delete[] data;
}
// 拷贝构造(深拷贝)
DynamicArray(const DynamicArray& other)
: size(other.size), capacity(other.capacity) {
data = new T[capacity];
for (size_t i = 0; i < size; i++) {
data[i] = other.data[i];
}
}
// 移动构造
DynamicArray(DynamicArray&& other) noexcept
: data(other.data), size(other.size), capacity(other.capacity) {
other.data = nullptr;
other.size = 0;
other.capacity = 0;
}
void push_back(const T& val) {
if (size >= capacity) {
resize(capacity == 0 ? 1 : capacity * 2);
}
data[size++] = val;
}
T pop_back() {
if (size == 0) {
throw out_of_range("数组为空");
}
return data[--size];
}
T& operator[](size_t index) {
if (index >= size) {
throw out_of_range("索引越界");
}
return data[index];
}
const T& operator[](size_t index) const {
if (index >= size) {
throw out_of_range("索引越界");
}
return data[index];
}
size_t getSize() const { return size; }
bool empty() const { return size == 0; }
// 友元输出运算符(模板类友元)
template <typename U>
friend ostream& operator<<(ostream& os, const DynamicArray<U>& arr);
};
template <typename U>
ostream& operator<<(ostream& os, const DynamicArray<U>& arr) {
os << "[";
for (size_t i = 0; i < arr.size; i++) {
os << arr.data[i];
if (i < arr.size - 1) os << ", ";
}
os << "]";
return os;
}
int main() {
DynamicArray<int> intArr;
intArr.push_back(10);
intArr.push_back(20);
intArr.push_back(30);
cout << intArr << endl;
cout << "pop: " << intArr.pop_back() << endl;
cout << intArr << endl;
DynamicArray<string> strArr;
strArr.push_back("C++");
strArr.push_back("模板");
cout << strArr << endl;
// 拷贝构造
DynamicArray<string> strArr2 = strArr;
cout << "拷贝: " << strArr2 << endl;
return 0;
}
输出:
text
[10, 20, 30]
pop: 30
[10, 20]
[C++, 模板]
拷贝: [C++, 模板]
八、常见错误
1. 模板定义与实现分离
cpp
// ❌ 错误:将模板实现放在 .cpp 文件
// 编译器在隐式实例化时看不到定义,导致链接错误
// 模板实现通常放在头文件中
2. 忘记 template 关键字
cpp
template <typename T>
void MyClass<T>::func() { ... } // ✅ 需要 template<typename T>
3. 模板参数名冲突
cpp
template <typename T>
class MyClass {
T data; // 这里的 T 是模板参数
typedef int T; // ❌ 错误:T 已被使用
};
4. 依赖名称需要 typename
cpp
template <typename T>
void func() {
T::iterator it; // ❌ 编译器不知道 iterator 是类型
typename T::iterator it2; // ✅ 告诉编译器这是一个类型
}
九、这一篇的收获
你现在应该理解:
-
函数模板 :
template <typename T>定义,类型自动推导或显式指定 -
类模板 :
Stack<int>实例化,成员函数类外定义需重复模板声明 -
实例化:隐式(使用时)和显式(提前声明)
-
模板与继承:模板类可作为基类,派生类可以是普通类或模板类
-
模板与友元:可以声明特定类型的友元,或所有类型的友元
💡 小作业:实现一个
Optional<T>类模板,类似std::optional的子集。支持:hasValue()、value()、valueOr(default)、reset()。测试int、string、自定义类型。
下一篇预告:第42篇《模板特化与偏特化:为特定类型定制实现》------泛型版本不能满足所有类型怎么办?模板特化为特定类型提供专属实现,偏特化为部分约束类型提供定制。下篇详解。