C++ 的类模板是泛型编程的核心特性之一, 它让我们能够编写适用于多种类型的通用代码, 从而提高代码的复用性和扩展性.
本教程通过栈的实现为例, 深入探讨类模板的实现, 使用, 以及特化, 偏特化, 默认参数和类型别名等高级特性, 帮助您更全面地掌握这一强大工具.
1. 实现一个 Stack
笔者选择用一个栈的模板来做演示, 因为栈的接口和功能相对而言比较简单. Stack
模板支持如下操作:
Push
: 入栈一个元素Pop
: 弹出一个元素Top
: 获取栈顶的元素Empty
: 栈是否为空Size
: 栈的元素数量
实现代码array.hpp
template <typename T>
class Stack {
private:
T* data_ = nullptr;
size_t capacity_ = 10; // 原始的空间为10
size_t length_ = 0;
void resize(size_t new_capacity) {
T* new_data = new T[new_capacity];
for (size_t i = 0; i < length_; ++i) {
new_data[i] = data_[i];
}
delete[] data_;
data_ = new_data;
capacity_ = new_capacity;
}
public:
Stack() { data_ = new T[capacity_]; }
~Stack() { delete[] data_; }
void Push(const T& value) {
if (length_ == capacity_) {
resize(capacity_ * 2);
}
data_[length_++] = value;
}
void Pop() {
if (length_ == 0) {
throw std::out_of_range("Index out of range");
}
--length_;
}
[[nodiscard]] size_t Size() const { return length_; }
[[nodiscard]] bool Empty() const { return length_ == 0; }
T Top() const {
if (0 == length_) {
throw std::out_of_range("Index out of range");
}
return data_[length_ - 1];
}
};
从上面的代码可以看到
- 模板类相比普通类多了一个泛型声明:
template <typename T>
- 在模板类的内部, 数据成员或者函数成员都可用使用泛型
T
- 类模板的实现通常是在一个头文件中, 不需要像普通类进行头文件/源文件分离.
2. 如何使用类模板
下面是一个简单的例子, 演示如何使用 Stack
类模板. 用法跟 STL 库中的容器类使用非常相似, 类似std::vector
.
Stack<int> arr;
arr.Push(10);
arr.Push(20);
arr.Push(30);
std::cout << "Stack size: " << arr.Size() << std::endl;
std::cout << "Stack top: " << arr.Top() << std::endl;
arr.Pop();
std::cout << "After Pop, size: " << arr.Size() << std::endl;
std::cout << "Stack top: " << arr.Top() << std::endl;
3. 类模板的特化
类模板的特化可以为特定类型提供专门的实现.
例如, 为 bool
类型特化 Stack
.
用一个 bit
表示 bool
值, 这样就可以节省很多存储空间.
template <>
class Stack<bool> {
private:
uint8_t* data_ = nullptr;
size_t capacity_ = 10;
size_t length_ = 0;
void resize(size_t new_capacity) {
auto* new_data = new uint8_t[new_capacity];
for (size_t i = 0; i < length_; ++i) {
new_data[i] = data_[i];
}
delete[] data_;
data_ = new_data;
capacity_ = new_capacity;
}
public:
Stack() { data_ = new uint8_t[(capacity_ + 7) / 8]; }
~Stack() { delete[] data_; }
[[nodiscard]] size_t Size() const { return length_; }
[[nodiscard]] bool Empty() const { return length_ == 0; }
void Push(bool value) {
if (length_ == capacity_) {
resize(capacity_ * 2);
}
size_t byte_index = length_ / 8;
size_t bit_index = length_ % 8;
if (value) {
data_[byte_index] |= (1 << bit_index);
} else {
data_[byte_index] &= ~(1 << bit_index);
}
++length_;
}
void Pop() {
if (0 == length_) {
throw std::out_of_range("Index out of range");
}
--length_;
}
[[nodiscard]] bool Top() const {
if (0 >= length_) {
throw std::out_of_range("Index out of range");
}
size_t byte_index = (length_ - 1) / 8;
size_t bit_index = (length_ - 1) % 8;
uint8_t result = (data_[byte_index] >> bit_index) & 1;
return result == 1;
}
};
4. 类模板的偏特化
偏特化允许对部分模板参数提供专门实现. 例如, Stack
类针对指针类型的偏特化.
注意: 这个偏特化的实现是假定了Stack
对与入栈的指针具有所有权, Stack
在析构的时候会释放栈内的指针.
template <typename T>
class Stack<T*> {
private:
T** data_ = nullptr;
size_t capacity_ = 10;
size_t length_ = 0;
void resize(size_t new_capacity) {
auto* new_data = new T*[new_capacity];
for (size_t i = 0; i < length_; ++i) {
new_data[i] = data_[i];
}
delete[] data_;
data_ = new_data;
capacity_ = new_capacity;
}
public:
Stack() { data_ = new T*[capacity_]; }
~Stack() {
for (size_t i = 0; i < length_; ++i) {
delete data_[i];
}
delete[] data_;
}
void Push(T* value) {
if (length_ == capacity_) {
resize(capacity_ * 2);
}
data_[length_++] = value;
}
void Pop() { length_--; }
T* Top() { return data_[length_ - 1]; }
[[nodiscard]] size_t Size() const { return length_; }
[[nodiscard]] bool Empty() const { return length_ == 0; }
};
对比总结
特性 | 特化 | 偏特化 |
---|---|---|
作用范围 | 单一具体类型(如 bool ) | 一类类型(如 T* , std::vector<T> ) |
实现独立性 | 独立于通用模板 | 通常继承通用模板的逻辑 |
使用复杂性 | 较简单 | 较复杂, 需要匹配模板参数模式 |
应用场景 | 对单一类型需求的特殊优化 | 针对一类类型需求的共性优化 |
5. 类模板默认参数
观察上面的代码, 我们会发现有个问题是默认的大小是写死的. 这个多少有些不灵活, 我们可以增加一个模板参数, 来决定栈预留空间.
// 模板类 Array 的一般实现
template <typename T, size_t N = 10>
class Stack {
private:
T* data_ = nullptr;
size_t capacity_ = N;
size_t length_ = 0;
// 其他代码不变
};
6. 类模板 type alias
通过 using
创建模板类型别名:
#include "stack.hpp"
using IntStack = Stack<int>;
int main() {
IntStack stack;
stack.Push(1);
}
总结
本教程从基础到高级应用, 详细讲解了 C++ 类模板的实现与特性. 希望这些内容能帮助您更好地理解并应用类模板.