29.参考c/c++堆栈实现自己的堆栈。要求:不能用stl容器。
🔬 自定义堆栈实现(不依赖STL容器)
📖 内容概览
本文将详细介绍如何在C++中实现自定义堆栈数据结构,包括基于数组的实现方案、核心功能实现和使用方法,且不依赖STL容器。通过手动实现堆栈,可以深入理解数据结构的设计原理和C++的内存管理机制。
🎯 核心概念
🧩 堆栈的定义与特性
堆栈(Stack)是一种**后进先出(LIFO, Last In First Out)**的线性数据结构,其操作受限:
- 只能在表的一端(称为栈顶)进行插入和删除操作
- 插入操作称为入栈(Push)
- 删除操作称为出栈(Pop)
🔧 堆栈的设计思路
使用数组实现堆栈的核心设计要点:
- 动态内存管理:使用指针和动态数组存储元素
- 栈顶指针:使用整数索引标记栈顶位置
- 边界检查:入栈前检查是否已满,出栈前检查是否为空
- 异常处理:对非法操作进行适当的错误提示
- 资源管理:确保析构函数正确释放动态分配的内存
📋 堆栈应实现的核心功能
| 功能 | 描述 |
|---|---|
| 入栈(Push) | 将元素添加到栈顶 |
| 出栈(Pop) | 移除并返回栈顶元素 |
| 取栈顶(Top) | 获取栈顶元素但不移除 |
| 判空(IsEmpty) | 检查堆栈是否为空 |
| 判满(IsFull) | 检查堆栈是否已满 |
| 栈长度(Size) | 获取堆栈中元素个数 |
| 清空(Clear) | 清空堆栈中的所有元素 |
| 销毁(Destroy) | 释放堆栈占用的资源 |
🛠️ 代码实现
📝 基于数组的堆栈实现
#include <iostream>using namespace std;// 基于数组实现的堆栈类template <typename T> // 使用模板支持任意类型class Stack {public: // 构造函数:创建指定容量的堆栈 explicit Stack(size_t capacity = 10) { this->capacity_ = capacity; data_ = new T[capacity_]; // 动态分配数组 top_ = -1; // 栈顶指针初始化为-1,表示空栈 }
// 析构函数:释放动态分配的内存 ~Stack() { delete[] data_; data_ = nullptr; capacity_ = 0; top_ = -1; } // 入栈操作 bool Push(const T& value) { if (IsFull()) { cout << "栈已满,无法入栈!" << endl; return false; } data_[++top_] = value; // 先递增栈顶指针,再存储元素 return true; }
// 出栈操作 bool Pop() { if (IsEmpty()) { cout << "栈为空,无法出栈!" << endl; return false; } --top_; // 仅需递减栈顶指针 return true; }
// 获取栈顶元素(不修改栈) T& Top() { if (IsEmpty()) { throw runtime_error("栈为空,无法获取栈顶元素!"); } return data_[top_]; }
// 获取栈顶元素(const版本) const T& Top() const { if (IsEmpty()) { throw runtime_error("栈为空,无法获取栈顶元素!"); } return data_[top_]; }
// 检查栈是否为空 bool IsEmpty() const { return top_ == -1; }
// 检查栈是否已满 bool IsFull() const { return top_ == static_cast<int>(capacity_) - 1; }
// 获取栈的当前大小 size_t Size() const { return top_ + 1; }
// 获取栈的容量 size_t Capacity() const { return capacity_; }
// 清空栈 void Clear() { top_ = -1; // 直接将栈顶指针重置为-1 }
// 打印栈中元素 void Print() const { if (IsEmpty()) { cout << "栈为空!" << endl; return; }
cout << "栈内元素(从栈顶到栈底):"; for (int i = top_; i >= 0; --i) { cout << data_[i] << " "; } cout << endl; }private: T* data_; // 存储元素的动态数组 size_t capacity_; // 栈的容量 int top_; // 栈顶指针,-1表示空栈 // 禁止拷贝构造和赋值操作(可选) Stack(const Stack&) = delete; Stack& operator=(const Stack&) = delete;};// 测试函数void TestStack() { Stack<int> stack(5); // 创建容量为5的整型堆栈 // 测试入栈 cout << "=== 测试入栈操作 ===" << endl; stack.Push(10); stack.Push(20); stack.Push(30); stack.Print(); // 测试栈顶元素 cout << "\n=== 测试栈顶元素 ===" << endl; cout << "当前栈顶元素:" << stack.Top() << endl; // 测试栈大小 cout << "\n=== 测试栈大小 ===" << endl; cout << "当前栈大小:" << stack.Size() << endl; cout << "栈容量:" << stack.Capacity() << endl; // 测试出栈 cout << "\n=== 测试出栈操作 ===" << endl; stack.Pop(); cout << "出栈后栈大小:" << stack.Size() << endl; // 测试判空和判满 cout << "\n=== 测试判空和判满 ===" << endl; cout << "栈是否为空:" << (stack.IsEmpty() ? "是" : "否") << endl; // 测试栈满情况 cout << "\n=== 测试栈满情况 ===" << endl; stack.Push(40); stack.Push(50); stack.Push(60); // 超出容量,应该失败 cout << "栈是否已满:" << (stack.IsFull() ? "是" : "否") << endl; // 测试清空栈 cout << "\n=== 测试清空栈 ===" << endl; stack.Clear(); cout << "清空后栈大小:" << stack.Size() << endl;}int main() { TestStack(); return 0;}代码优化说明
- 使用模板:支持任意数据类型,提高复用性
- 动态内存管理:构造函数分配内存,析构函数释放内存
- 栈顶指针设计:使用-1表示空栈,更直观
- 异常处理:对非法操作(如访问空栈顶)抛出异常
- 禁止拷贝:避免浅拷贝问题,可根据需要开放
- 清晰的接口:函数命名规范,职责明确
- 完整的测试:涵盖各种边界情况
🛠️ 基于链表的堆栈实现(扩展)
除了基于数组的实现,还可以使用链表实现堆栈,具有动态扩容的优势:
#include <iostream>using namespace std;// 链表节点定义template <typename T>struct Node { T data; Node* next;
Node(const T& value) : data(value), next(nullptr) {}};// 基于链表实现的堆栈类template <typename T>class LinkedListStack {public: LinkedListStack() : head_(nullptr), size_(0) {}
~LinkedListStack() { Clear(); }
// 入栈操作 void Push(const T& value) { Node<T>* new_node = new Node<T>(value); new_node->next = head_; head_ = new_node; ++size_; }
// 出栈操作 bool Pop() { if (IsEmpty()) { cout << "栈为空,无法出栈!" << endl; return false; }
Node<T>* temp = head_; head_ = head_->next; delete temp; --size_; return true; }
// 获取栈顶元素 T& Top() { if (IsEmpty()) { throw runtime_error("栈为空,无法获取栈顶元素!"); } return head_->data; }
bool IsEmpty() const { return head_ == nullptr; }
size_t Size() const { return size_; }
void Clear() { while (head_ != nullptr) { Node<T>* temp = head_; head_ = head_->next; delete temp; } size_ = 0; }
void Print() const { if (IsEmpty()) { cout << "栈为空!" << endl; return; } cout << "栈内元素(从栈顶到栈底):"; Node<T>* current = head_; while (current != nullptr) { cout << current->data << " "; current = current->next; } cout << endl; }private: Node<T>* head_; // 链表头指针,指向栈顶元素 size_t size_; // 栈的大小};🛠️ 两种实现方式的对比
| 实现方式 | 优点 | 缺点 |
|---|---|---|
| 基于数组 | 内存连续,访问效率高 | 固定容量,扩容需要重新分配内存 |
| 基于链表 | 动态扩容,不需要预先指定容量 | 内存不连续,访问效率较低,每个节点有额外开销 |
📋 总结与最佳实践
- 选择合适的实现方式:
- 对于固定大小或可预测大小的场景,选择基于数组的实现
- 对于大小变化较大的场景,选择基于链表的实现
- 内存管理注意事项:
- 确保动态分配的内存被正确释放
- 考虑是否需要支持拷贝构造和赋值操作
- 对于模板类,注意编译时的模板实例化
- 接口设计原则:
- 保持接口简洁明了
- 提供必要的错误检查和异常处理
- 考虑接口的易用性和安全性
- 测试建议:
- 测试各种边界情况(空栈、满栈)
- 测试正常的入栈出栈操作
- 测试异常情况的处理
- 考虑多线程环境下的安全性(如果需要)
- 性能优化:
- 对于数组实现,可以考虑实现自动扩容机制
- 对于链表实现,可以考虑使用内存池优化节点分配 通过手动实现堆栈,可以深入理解数据结构的设计原理和C++的内存管理机制,这对于掌握高级C++编程非常有帮助。无论是基于数组还是基于链表的实现,都需要关注资源管理和异常处理,确保代码的健壮性和安全性。