C++的类模板允许我们定义不依赖具体数据类型的通用类,只需要在使用时指定具体类型即可生成对应的类实例,这在实现通用容器、工具类等场景中非常实用。

类模板的基础定义
类模板的定义以template关键字开头,后面跟着模板参数列表,模板参数通常用typename或者class声明,二者在定义模板参数时没有区别。基础语法格式如下:
// 定义一个简单的动态数组类模板
template <typename T>
class DynamicArray {
private:
T* data; // 存储数据的指针
int size; // 数组当前元素个数
int capacity; // 数组容量
public:
// 构造函数,默认容量为10
DynamicArray(int cap = 10) : size(0), capacity(cap) {
data = new T[capacity];
}
// 析构函数,释放动态分配的内存
~DynamicArray() {
delete[] data;
}
// 向数组末尾添加元素
void push_back(const T& val) {
if (size >= capacity) {
// 容量不足时扩容,这里简单处理为翻倍
capacity *= 2;
T* newData = new T[capacity];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
delete[] data;
data = newData;
}
data[size++] = val;
}
// 获取指定位置的元素
T& operator[](int index) {
return data[index];
}
// 获取当前元素个数
int getSize() const {
return size;
}
};
上面的代码定义了一个支持任意类型的动态数组类模板,我们可以通过指定不同的类型来实例化不同的类:
#include <iostream>
using namespace std;
int main() {
// 实例化存储int类型的动态数组
DynamicArray<int> intArray;
intArray.push_back(1);
intArray.push_back(2);
cout << "int数组第一个元素:" << intArray[0] << endl;
// 实例化存储double类型的动态数组
DynamicArray<double> doubleArray;
doubleArray.push_back(3.14);
cout << "double数组第一个元素:" << doubleArray[0] << endl;
return 0;
}
类模板的成员函数类外实现
如果类模板的成员函数需要在类外实现,需要加上模板参数声明,并且要注明所属的类模板:
template <typename T>
class SimpleStack {
private:
T* elements;
int topIndex;
int maxSize;
public:
SimpleStack(int size = 10);
~SimpleStack();
void push(const T& val);
T pop();
};
// 构造函数类外实现
template <typename T>
SimpleStack<T>::SimpleStack(int size) : topIndex(-1), maxSize(size) {
elements = new T[maxSize];
}
// 析构函数类外实现
template <typename T>
SimpleStack<T>::~SimpleStack() {
delete[] elements;
}
// push方法类外实现
template <typename T>
void SimpleStack<T>::push(const T& val) {
if (topIndex < maxSize - 1) {
elements[++topIndex] = val;
}
}
// pop方法类外实现
template <typename T>
T SimpleStack<T>::pop() {
if (topIndex >= 0) {
return elements[topIndex--];
}
return T();
}
模板特化的概念与全特化实现
模板特化是指针对某些特定的类型,提供专门的模板实现,当模板参数匹配特化版本时,编译器会优先使用特化版本而不是通用模板。全特化是指所有模板参数都指定为具体类型。
比如我们之前定义的DynamicArray类模板,对于bool类型,我们不需要用每个元素占一个字节的方式存储,而是可以用位来存储,节省内存,这时候就可以为bool类型做全特化:
// 通用DynamicArray类模板声明
template <typename T>
class DynamicArray;
// bool类型的全特化版本
template <>
class DynamicArray<bool> {
private:
// 用unsigned char数组存储位,每个字节存8个bool值
unsigned char* data;
int bitSize; // 存储的bool个数
int byteCapacity; // 字节容量
public:
DynamicArray(int cap = 10) : bitSize(0), byteCapacity((cap + 7) / 8) {
data = new unsigned char[byteCapacity]();
}
~DynamicArray() {
delete[] data;
}
// 设置指定位置的bool值
void set(int index, bool val) {
if (index >= bitSize) {
bitSize = index + 1;
// 如果位数量超过当前字节容量,扩容
if ((bitSize + 7) / 8 > byteCapacity) {
byteCapacity *= 2;
unsigned char* newData = new unsigned char[byteCapacity]();
for (int i = 0; i < (bitSize - 1 + 7) / 8; i++) {
newData[i] = data[i];
}
delete[] data;
data = newData;
}
}
int byteIdx = index / 8;
int bitIdx = index % 8;
if (val) {
data[byteIdx] |= (1 << bitIdx);
} else {
data[byteIdx] &= ~(1 << bitIdx);
}
}
// 获取指定位置的bool值
bool get(int index) const {
if (index >= bitSize) return false;
int byteIdx = index / 8;
int bitIdx = index % 8;
return (data[byteIdx] >> bitIdx) & 1;
}
int getSize() const {
return bitSize;
}
};
使用全特化版本的DynamicArray<bool>时,编译器会自动匹配这个特化实现:
int main() {
DynamicArray<bool> boolArray;
boolArray.set(0, true);
boolArray.set(1, false);
boolArray.set(2, true);
cout << "bool数组第0位:" << boolArray.get(0) << endl;
cout << "bool数组第1位:" << boolArray.get(1) << endl;
return 0;
}
偏特化版本的实现
偏特化是指只指定部分模板参数,或者对一些模板参数做约束,比如我们可以为指针类型的模板参数做偏特化,针对指针类型做一些特殊的处理,比如默认初始化为空指针:
// 通用类模板
template <typename T>
class DataWrapper {
private:
T value;
public:
DataWrapper() : value(T()) {}
void setValue(const T& val) {
value = val;
}
T getValue() const {
return value;
}
};
// 指针类型的偏特化版本
template <typename T>
class DataWrapper<T*> {
private:
T* value;
public:
// 指针类型默认初始化为空
DataWrapper() : value(nullptr) {}
void setValue(T* val) {
value = val;
}
T* getValue() const {
return value;
}
// 指针类型特有的解引用方法
T& dereference() const {
return *value;
}
};
偏特化版本在使用指针类型实例化时会被优先匹配:
int main() {
DataWrapper<int> intWrapper;
intWrapper.setValue(100);
cout << "int包装值:" << intWrapper.getValue() << endl;
int a = 200;
DataWrapper<int*> ptrWrapper;
ptrWrapper.setValue(&a);
cout << "指针包装解引用值:" << ptrWrapper.dereference() << endl;
return 0;
}
类模板的注意事项
- 类模板的定义通常放在头文件中,因为编译器需要在实例化时看到完整的模板定义,否则可能出现链接错误。
- 模板参数可以有默认值,比如
template <typename T, int Size = 10>,实例化时可以省略有默认值的参数。 - 特化版本必须定义在通用模板之后,并且特化的类型必须和通用模板的参数列表匹配。
- 类模板不支持分离编译,也就是不能把模板定义放在cpp文件中,否则其他文件包含头文件时无法实例化模板。