类模板
类模板是C++泛型编程的另一个核心工具。它允许程序员定义与类型无关的通用类蓝图,在实例化时指定具体的数据类型。
类模板广泛应用于标准库容器(如 vector、list、stack 等),是构建可复用数据结构的基础。
问题场景:重复的数据结构定义
在程序设计中,经常遇到数据结构相同、元素类型不同的情况。
不同数据类型的数组类
// 需要为每种数据类型编写一个数组类?
class IntArray {
int* data;
int size;
// ... 相同的逻辑
};
class DoubleArray {
double* data;
int size;
// ... 相同的逻辑
};
class StudentArray {
Student* data;
int size;
// ... 相同的逻辑
};
这种为每种类型重复编写相同逻辑的做法,与函数重载面临的冗余问题如出一辙:
- 代码冗余:大量重复的类定义。
- 维护困难:修改数据结构或算法时需要同步修改所有版本。
- 可扩展性差:每增加一种新类型就需要定义一个新的类。
解决方案
类模板(Class Template) 允许我们定义一个通用的类蓝图,在实例化时指定元素类型,编译器自动生成对应类型的类代码。
类模板的定义
基本语法
类模板定义了一个类族,其中的类型参数在实例化时被具体类型替换。
类模板的语法格式
template <typename 类型参数1, typename 类型参数2, ...>
class 类名 {
// 类成员声明
// 可以使用类型参数作为:
// - 数据成员的类型
// - 成员函数的参数类型
// - 成员函数的返回类型
};
简单的类模板示例:Point类
| #include <iostream>
using namespace std;
// 类模板:Point
template <typename T>
class Point {
public:
// 构造函数参数使用类型参数
Point(T x = 0, T y = 0) : x(x), y(y) {}
// 成员函数参数和返回值使用类型参数
void setX(T value) { x = value; }
void setY(T value) { y = value; }
T getX() const { return x; }
T getY() const { return y; }
void display() const {
cout << "(" << x << ", " << y << ")" << endl;
}
private:
T x, y; // 类型参数作为数据成员类型
};
int main() {
// 实例化 int 类型的 Point
Point<int> p1(3, 4);
p1.display(); // (3, 4)
// 实例化 double 类型的 Point
Point<double> p2(3.14, 2.71);
p2.display(); // (3.14, 2.71)
// 实例化 string 类型的 Point
Point<string> p3("Hello", "World");
p3.display(); // (Hello, World)
return 0;
}
|
成员函数的定义
类模板的成员函数可以在类内定义(隐式内联),也可以在类外定义。类外定义时需要特殊的模板语法。
方式一:类内定义
- 类内定义的成员函数默认是内联函数。
- 适合函数体较短、逻辑简单的情况。
成员函数在类内定义
| template <typename T>
class Box {
public:
// 构造函数在类内定义
Box(const T& v) : value(v) {}
// 成员函数在类内定义
void setValue(const T& v) {
value = v;
}
T getValue() const {
return value;
}
void display() const {
cout << "Box(" << value << ")" << endl;
}
private:
T value;
};
|
方式二:类外定义
成员函数在类外定义
| #include <iostream>
#include <cassert>
using namespace std;
// 类模板声明
template <typename T>
class Array {
public:
// 构造函数
Array(int sz = 50);
// 复制构造函数(深拷贝)
Array(const Array<T>& a);
// 析构函数
~Array();
// 下标运算符
T& operator[](int n);
const T& operator[](int n) const;
// 获取数组大小
int getSize() const;
// 改变数组大小
void resize(int sz);
private:
T* list; // 动态数组
int size; // 数组大小
};
// ===== 类外定义成员函数 =====
// 1. 构造函数
template <typename T>
Array<T>::Array(int sz) : size(sz) {
assert(sz >= 0);
list = new T[size];
}
// 2. 复制构造函数(深拷贝)
template <typename T>
Array<T>::Array(const Array<T>& a) {
size = a.size;
list = new T[size];
for (int i = 0; i < size; i++) {
list[i] = a.list[i];
}
}
// 3. 析构函数
template <typename T>
Array<T>::~Array() {
delete[] list;
}
// 4. 下标运算符(非 const 版本)
template <typename T>
T& Array<T>::operator[](int n) {
assert(n >= 0 && n < size);
return list[n];
}
// 5. 下标运算符(const 版本)
template <typename T>
const T& Array<T>::operator[](int n) const {
assert(n >= 0 && n < size);
return list[n];
}
// 6. 获取数组大小
template <typename T>
int Array<T>::getSize() const {
return size;
}
// 7. 改变数组大小
template <typename T>
void Array<T>::resize(int sz) {
assert(sz >= 0);
if (sz == size) return;
T* newList = new T[sz];
int n = (sz < size) ? sz : size;
for (int i = 0; i < n; i++) {
newList[i] = list[i];
}
delete[] list;
list = newList;
size = sz;
}
int main() {
Array<int> a(5);
for (int i = 0; i < a.getSize(); i++) {
a[i] = i * 10;
}
for (int i = 0; i < a.getSize(); i++) {
cout << a[i] << " ";
}
cout << endl; // 0 10 20 30 40
return 0;
}
|
类外定义的关键语法要点
| 要点 |
说明 |
示例 |
| 重复模板声明 |
每个成员函数定义前都需要 template <typename T> |
template <typename T> |
| 类名带模板参数 |
类名要写成 Array<T> 而不是 Array |
Array<T>::Array |
| 作用域运算符 |
:: 前的类型是完整的 Array<T> |
Array<T>::operator[] |
类模板的实例化
与函数模板不同,类模板的实例化必须显式指定模板参数(编译器无法推导)。
类模板的实例化方式
| #include <iostream>
#include <string>
using namespace std;
template <typename T>
class Store {
private:
T item;
bool hasValue;
public:
Store() : hasValue(false) {}
void put(const T& value) {
item = value;
hasValue = true;
}
T get() const {
if (!hasValue) {
throw runtime_error("No value stored");
}
return item;
}
bool isEmpty() const { return !hasValue; }
};
int main() {
// 方式一:使用 <类型> 显式指定
Store<int> intStore;
intStore.put(100);
cout << intStore.get() << endl; // 100
Store<string> stringStore;
stringStore.put("Hello");
cout << stringStore.get() << endl; // Hello
// 方式二:使用类型别名简化(推荐)
using IntStore = Store<int>;
IntStore store2;
store2.put(200);
cout << store2.get() << endl; // 200
// 方式三:typedef(C++98/03 风格)
typedef Store<double> DoubleStore;
DoubleStore store3;
store3.put(3.14);
cout << store3.get() << endl; // 3.14
return 0;
}
|
类模板的默认模板参数
类模板可以为类型参数指定默认值,方便使用。
默认模板参数
| #include <iostream>
using namespace std;
// 默认模板参数:T 默认为 int
template <typename T = int>
class Counter {
public:
Counter(T initial = 0) : count(initial) {}
void increment() { count++; }
void decrement() { count--; }
T getCount() const { return count; }
void display() const {
cout << "Count: " << count << endl;
}
private:
T count;
};
int main() {
// 使用默认的 int 类型
Counter<> c1; // 注意:需要写 <>
c1.increment();
c1.display(); // Count: 1
// 指定为 double 类型
Counter<double> c2(3.14);
c2.increment();
c2.display(); // Count: 4.14
// C++17 起可以省略 <>
Counter c3(100); // 自动推导为 int
c3.display(); // Count: 100
return 0;
}
|
注意事项
- 使用默认模板参数时,空模板参数列表需要写成
<>(除非使用 C++17 的类模板参数推导)。
- 如果有多个模板参数,默认值可以从右向左指定(即可以为部分参数指定默认值)。
类模板的友元
类模板中的友元声明需要特别注意,因为友元函数或类本身也可能是模板。
友元函数在类内定义
友元函数在类内定义
| #include <iostream>
using namespace std;
template <typename T>
class Box {
public:
Box(const T& v) : value(v) {}
// 友元函数:在类内定义(隐式内联)
// 注意:这里的 Box<T> 与类模板参数一致
friend ostream& operator<<(ostream& os, const Box<T>& b) {
os << "Box(" << b.value << ")";
return os;
}
// 友元函数:也可以与类模板参数无关
friend void showBox() {
cout << "This is a Box" << endl;
}
private:
T value;
};
int main() {
Box<int> b1(10);
Box<double> b2(3.14);
cout << b1 << endl; // Box(10)
cout << b2 << endl; // Box(3.14)
showBox(); // This is a Box
return 0;
}
|
友元函数在类外定义
友元函数在类外定义
| #include <iostream>
using namespace std;
// 前向声明
template <typename T>
class Box;
// 友元函数声明(需要先声明模板)
template <typename T>
bool operator==(const Box<T>& a, const Box<T>& b);
template <typename T>
class Box {
public:
Box(const T& v) : value(v) {}
// 友元声明:指向类外定义的函数模板
friend bool operator== <T>(const Box<T>& a, const Box<T>& b);
T getValue() const { return value; }
private:
T value;
};
// 友元函数在类外定义
template <typename T>
bool operator==(const Box<T>& a, const Box<T>& b) {
return a.value == b.value;
}
int main() {
Box<int> b1(10);
Box<int> b2(20);
Box<int> b3(10);
cout << (b1 == b2 ? "相等" : "不相等") << endl; // 不相等
cout << (b1 == b3 ? "相等" : "不相等") << endl; // 相等
return 0;
}
|
友元类
友元类
| #include <iostream>
using namespace std;
template <typename T>
class Container {
public:
Container(const T& d) : data(d) {}
// 将整个 Manager 类声明为友元
template <typename U>
friend class Manager;
private:
T data;
};
template <typename T>
class Manager {
public:
void show(const Container<T>& c) {
cout << "Container data: " << c.data << endl;
}
void setData(Container<T>& c, const T& value) {
c.data = value;
}
};
int main() {
Container<int> c(100);
Manager<int> mgr;
mgr.show(c); // Container data: 100
mgr.setData(c, 200);
mgr.show(c); // Container data: 200
return 0;
}
|
类模板的静态成员
类模板也可以有静态成员。每个模板实例化版本拥有自己独立的静态成员副本。
类模板的静态成员
| #include <iostream>
using namespace std;
template <typename T>
class Counter {
public:
Counter() {
count++;
}
~Counter() {
count--;
}
static int getCount() {
return count;
}
private:
static int count; // 静态成员声明
};
// 静态成员定义(每个实例化版本各有一份)
template <typename T>
int Counter<T>::count = 0;
int main() {
cout << "Counter<int> count: " << Counter<int>::getCount() << endl; // 0
Counter<int> c1, c2, c3;
cout << "Counter<int> count: " << Counter<int>::getCount() << endl; // 3
{
Counter<double> d1, d2;
cout << "Counter<double> count: " << Counter<double>::getCount() << endl; // 2
cout << "Counter<int> count: " << Counter<int>::getCount() << endl; // 3(独立)
}
cout << "Counter<double> count: " << Counter<double>::getCount() << endl; // 0
return 0;
}
|
重要概念
每个模板实例化版本(如 Counter<int> 和 Counter<double>)拥有独立的静态成员副本。Counter<int>::count 和 Counter<double>::count 是两个不同的变量。
类模板的特化
当类模板的通用实现不适用于某些特定类型时,可以对这些类型提供特化(Specialization)版本。
全特化
类模板的全特化
| #include <iostream>
#include <cstring>
using namespace std;
// 通用模板
template <typename T>
class Comparator {
public:
bool isEqual(const T& a, const T& b) const {
return a == b;
}
};
// 全特化:针对 const char* 类型
template <>
class Comparator<const char*> {
public:
bool isEqual(const char* a, const char* b) const {
return strcmp(a, b) == 0;
}
};
// 全特化:针对 char* 类型
template <>
class Comparator<char*> {
public:
bool isEqual(char* a, char* b) const {
return strcmp(a, b) == 0;
}
};
int main() {
Comparator<int> intComp;
cout << intComp.isEqual(5, 5) << endl; // 1
cout << intComp.isEqual(5, 3) << endl; // 0
Comparator<const char*> strComp;
cout << strComp.isEqual("hello", "hello") << endl; // 1
cout << strComp.isEqual("hello", "world") << endl; // 0
return 0;
}
|
偏特化(部分特化)
类模板支持偏特化,当模板有多个参数时,可以为部分参数指定特化。
类模板的偏特化
| #include <iostream>
using namespace std;
// 通用模板:两个类型参数
template <typename T1, typename T2>
class Pair {
public:
void print() const {
cout << "General Pair" << endl;
}
};
// 偏特化:两个类型相同
template <typename T>
class Pair<T, T> {
public:
void print() const {
cout << "Same-type Pair" << endl;
}
};
// 偏特化:第二个类型是 int
template <typename T>
class Pair<T, int> {
public:
void print() const {
cout << "Second type is int" << endl;
}
};
// 偏特化:两个都是指针类型
template <typename T1, typename T2>
class Pair<T1*, T2*> {
public:
void print() const {
cout << "Pointer Pair" << endl;
}
};
int main() {
Pair<int, double> p1; // 通用模板
p1.print(); // General Pair
Pair<int, int> p2; // 偏特化:相同类型
p2.print(); // Same-type Pair
Pair<double, int> p3; // 偏特化:第二个是 int
p3.print(); // Second type is int
Pair<int*, double*> p4; // 偏特化:指针类型
p4.print(); // Pointer Pair
return 0;
}
|
类模板与继承
从普通类继承
类模板可以从普通类(非模板)继承。
从普通类继承
| class Base {
public:
Base(int i = 0) : id(i) {}
void showId() const { cout << "ID: " << id << endl; }
protected:
int id;
};
template <typename T>
class Derived : public Base {
public:
Derived(int id, const T& v) : Base(id), value(v) {}
void display() const {
showId();
cout << "Value: " << value << endl;
}
private:
T value;
};
int main() {
Derived<int> d(100, 200);
d.display(); // ID: 100, Value: 200
Derived<string> d2(101, "Hello");
d2.display(); // ID: 101, Value: Hello
return 0;
}
|
从类模板继承
基类本身也可以是类模板,或指定特定的实例化版本。
从类模板继承
| // 基类模板
template <typename T>
class Base {
public:
Base(const T& d) : data(d) {}
T getData() const { return data; }
protected:
T data;
};
// 派生类也是模板
template <typename T>
class Derived : public Base<T> {
public:
Derived(const T& d, const T& e) : Base<T>(d), extra(e) {}
void display() const {
cout << "Data: " << this->getData() << ", Extra: " << extra << endl;
}
private:
T extra;
};
// 派生类指定基类的实例化版本
class IntDerived : public Base<int> {
public:
IntDerived(int d) : Base<int>(d) {}
void show() const { cout << "Value: " << data << endl; }
};
int main() {
Derived<int> d1(10, 20);
d1.display(); // Data: 10, Extra: 20
Derived<string> d2("Hello", "World");
d2.display(); // Data: Hello, Extra: World
IntDerived d3(100);
d3.show(); // Value: 100
return 0;
}
|
依赖名称查找
在派生类模板中访问基类模板的成员时,需要使用 this-> 或 Base<T>:: 来限定,否则编译器可能找不到:
template <typename T>
class Derived : public Base<T> {
public:
void func() {
// data = 10; // 错误!编译器找不到 data
this->data = 10; // 正确
Base<T>::data = 10; // 也正确
}
};
综合示例
动态数组类模板
| #include <iostream>
#include <cassert>
using namespace std;
// 动态数组类模板
template <typename T>
class DynamicArray {
private:
T* data;
int size;
int capacity;
// 内部扩容函数
void grow() {
int newCapacity = capacity * 2;
T* newData = new T[newCapacity];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
delete[] data;
data = newData;
capacity = newCapacity;
}
public:
// 构造函数
DynamicArray(int initialCapacity = 10)
: size(0), capacity(initialCapacity) {
assert(initialCapacity > 0);
data = new T[capacity];
cout << "DynamicArray created, capacity: " << capacity << endl;
}
// 复制构造函数(深拷贝)
DynamicArray(const DynamicArray<T>& other)
: size(other.size), capacity(other.capacity) {
data = new T[capacity];
for (int i = 0; i < size; i++) {
data[i] = other.data[i];
}
cout << "DynamicArray copied" << endl;
}
// 赋值运算符
DynamicArray<T>& operator=(const DynamicArray<T>& other) {
if (this == &other) return *this;
delete[] data;
size = other.size;
capacity = other.capacity;
data = new T[capacity];
for (int i = 0; i < size; i++) {
data[i] = other.data[i];
}
cout << "DynamicArray assigned" << endl;
return *this;
}
// 析构函数
~DynamicArray() {
delete[] data;
cout << "DynamicArray destroyed" << endl;
}
// 在末尾添加元素
void pushBack(const T& value) {
if (size >= capacity) {
grow();
}
data[size++] = value;
}
// 删除末尾元素
void popBack() {
assert(size > 0);
size--;
}
// 下标运算符(非 const)
T& operator[](int index) {
assert(index >= 0 && index < size);
return data[index];
}
// 下标运算符(const)
const T& operator[](int index) const {
assert(index >= 0 && index < size);
return data[index];
}
// 获取大小
int getSize() const { return size; }
// 获取容量
int getCapacity() const { return capacity; }
// 判断是否为空
bool isEmpty() const { return size == 0; }
// 清空
void clear() {
size = 0;
}
// 友元:输出运算符
friend ostream& operator<<(ostream& os, const DynamicArray<T>& arr) {
os << "[";
for (int i = 0; i < arr.size; i++) {
os << arr.data[i];
if (i < arr.size - 1) os << ", ";
}
os << "]";
return os;
}
};
int main() {
cout << "=== 创建 DynamicArray<int> ===" << endl;
DynamicArray<int> arr;
cout << "\n=== 添加元素 ===" << endl;
for (int i = 1; i <= 15; i++) {
arr.pushBack(i * 10);
}
cout << "Array: " << arr << endl;
cout << "Size: " << arr.getSize() << ", Capacity: " << arr.getCapacity() << endl;
cout << "\n=== 访问元素 ===" << endl;
cout << "arr[0] = " << arr[0] << endl;
cout << "arr[5] = " << arr[5] << endl;
cout << "arr[10] = " << arr[10] << endl;
cout << "\n=== 修改元素 ===" << endl;
arr[0] = 999;
arr[5] = 888;
cout << "After modification: " << arr << endl;
cout << "\n=== 删除元素 ===" << endl;
arr.popBack();
arr.popBack();
arr.popBack();
cout << "After popBack: " << arr << endl;
cout << "Size: " << arr.getSize() << ", Capacity: " << arr.getCapacity() << endl;
cout << "\n=== 使用不同数据类型 ===" << endl;
DynamicArray<string> strArr;
strArr.pushBack("Hello");
strArr.pushBack("World");
strArr.pushBack("C++");
cout << "String array: " << strArr << endl;
DynamicArray<double> dblArr;
dblArr.pushBack(3.14);
dblArr.pushBack(2.71);
dblArr.pushBack(1.41);
cout << "Double array: " << dblArr << endl;
cout << "\n=== 复制构造 ===" << endl;
DynamicArray<int> arrCopy = arr;
cout << "Copy: " << arrCopy << endl;
return 0;
}
|
类模板与函数模板的对比
| 对比项 |
函数模板 |
类模板 |
| 声明关键字 |
template <typename T> |
template <typename T> |
| 类型参数推导 |
✓ 支持(隐式/显式) |
✗ 不支持(必须显式指定) |
| 实例化时机 |
调用函数时 |
定义对象时 |
| 默认模板参数 |
✓ 支持 |
✓ 支持 |
| 特化 |
全特化(不常用) |
全特化 + 偏特化 |
| 成员函数定义 |
无需特殊语法 |
类外定义需 template <typename T> |
| 静态成员 |
无(函数没有静态成员) |
每个实例化版本各有一份 |
| 使用场景 |
通用算法、工具函数 |
通用数据结构 |
小结
-
类模板的定义:
template <typename T> class ClassName { ... }
- 类型参数可用于数据成员、成员函数参数、返回类型等。
-
成员函数的类外定义:
- 每个成员函数前都要写
template <typename T>
- 类名要写成
ClassName<T> 而非 ClassName
-
类模板的实例化:
- 必须显式指定模板参数:
ClassName<int> obj
- 可以使用
using 或 typedef 简化类型名
-
类模板的特化:
- 全特化:为特定类型提供完整实现
template <> class ClassName<SpecialType>
- 偏特化:为满足特定条件的类型提供部分特化
-
类模板的静态成员:
- 每个实例化版本拥有独立的静态成员副本
-
类模板与继承:
- 可以从普通类继承。
- 可以从类模板继承(需要指定实例化版本或保持模板特性)。