栈是我们常用的数据结构之一,在很多项目中都有所应用。也是练习c++指针、类与泛型编程的好demo。
首先看,什么是栈?(摘自c++官网)
Stacks are a type of container adaptor, specifically designed to operate in a LIFO context (last-in first-out), where elements are inserted and extracted only from one end of the container.
翻译
栈是一种容器适配器,专门设计用于在LIFO上下文中操作(后进先出),其中元素仅从容器的一端插入和提取。
呃......看起来好深奥......简单来说,栈就像一个弹夹:
你是从弹夹顶端塞入塞入子弹 (后来者居上)
当你取出元素时,是打出顶端的子弹 (后进先出)
那这个数据结构有哪些成员函数呢?
去除构造函数,总共有一下几个函数:
push(T v) 从栈顶塞入一个值
pop() 从栈顶取出一个值
top() 获取栈顶的值 (注意: 这个和上面的不一样,上面是删除栈顶元素,这个是获取栈顶元素的值)
size() 获取栈中元素的数量
以上便是栈的所有操作,那我们先编写一下main.cpp,理清一下思路吧:
empty() 查看栈是否为空
#include <iostream> #include "stack/stack.h" #include "stack/stack.cpp" int main() { std::cout << "Stack: " << std::endl; test::stack<int> a(12); // 为了不和stl库的栈命名冲突,我们把stack写在test命令空间中 std::cout << a.top() << "," << a.size() << "," << a.empty() << std::endl; a.pop(); std::cout << a.size() << "," << a.empty() << std::endl; a.push(123); std::cout << a.top() << "," << a.size() << "," << a.empty() << std::endl; a.push(1); std::cout << a.top() << "," << a.size() << "," << a.empty() << std::endl; return 0; }
那现在我们就很清楚了,可以先从栈的构造函数入手,首先创建文件stack/stack.h stack/stack.cpp。然后编写以下代码:
#ifndef MYDS_STACK_H #define MYDS_STACK_H namespace test { template <class T> class stack { public: stack(); stack(T v); }; } #endif //MYDS_STACK_H
然后,我们该如何存储每一个节点呢?我们可以专门写一个节点类来存,并在栈中存放栈顶元素节点的指针。
那这个节点元素就应该有两个属性: 值和指向下一个的指针。
为了方便起见,我们直接将该类的所有属性设为public,代码如下:
template <class T> class stack_node { public: stack_node<T>* next = nullptr; T value; };
接下来,我们先为stack类添加一个私有属性root来存储栈顶元素的指针:
private: stack_node<T>* root;
那么,我们可以将两种构造函数就可以出来了:
stack() : root(nullptr) {} stack(T v) : root(new test::stack_node<T>()) { root->value = v; }
现在,我们就可以编写其他成员函数了。
让我们回顾下,栈有哪些成员函数?
有push();pop();top();size()以及empty();
那我们就可以重新编写这个类了:
template <class T> class stack { public: stack() : root(nullptr) {} stack(T v) : root(new test::stack_node<T>()) { root->value = v; } bool empty(); int size(); T top(); void pop(); void push(T v); private: stack_node<T>* root; };
这下,我们就可以专心致志地编写stack的逻辑代码了。
首先挑软柿子捏,把empty函数编写完成。
我们来想一下,这个栈怎样才算空呢?就是其中没有一个元素?那么如何判断没有元素呢?先编写一个size函数,查看这个栈元素个数是否零?不行,后面我们会提到,这个size函数实现会十分麻烦,而且有一个方法可以快速得判断。
那我们想一下,代码底层可以怎么想呢?,我们先看一下两个构造函数: 如果没有默认参数,就那就是一个空栈,root为空指针,而如果是第二个构造函数,那就会创建非空栈,root为指向其第一个元素的指针!
那我们就知道了,当root为空指针时,该栈就是空的!
既然如此,我们转到stack/stack.cpp中,编写如下代码:
#include "stack.h" template <class T> bool test::stack<T>::empty() { return root == nullptr; }
然后我们可以再挑一个简单的top函数来先实现。因为我们的root指针就是指向该栈的顶元素的,所以直接返回root的value属性即可。
template <class T> T test::stack<T>::top() { return root->value; }
现在简单的地方编写完成了,接下来几个都是硬茬,不好惹,那我们可以先编写pop函数。
我们想一下,root就是存储栈顶元素的,而栈是后进先出,所以pop时就是删除root,然后将root设为root的next元素,并且如果root的next是空指针的话,就将root赋值为nullptr。
虽然有点绕,但仍然可以很快实现。
template <class T> void test::stack<T>::pop() { if (!root->next) { delete root; root = nullptr; return; } stack_node<T>* test = root->next; delete root; root = test; }
那我们再编写push函数。
在编前,我们思考一下,该如何push呢?push一个元素时,就是栈顶多了一个元素,而root就是存储栈顶元素的,我们只要把root设置为这个新元素,并让这个新元素的next设为我们的旧root好了。
template <class T> void test::stack<T>::push(T v) { if (root == nullptr) { root = new test::stack_node<T>; root->value = v; return; } stack_node<T>* tmp1 = root; stack_node<T>* tmp2 = new test::stack_node<T>; tmp2->value = v; tmp2->next = tmp1; root = tmp2; }
接着,我们就剩下最后一个硬骨头了: size函数。我们该如何计算其的个数呢?其实也不难。我们想一想,其实我们只要遍历一遍stack,然后就看循环了几次,就有几个元素了。
那如何遍历元素呢?我们可以取root的next,然后取root的next的next,再取root的next的next的next等等等。
那我们就可以编写了:
template <class T> int test::stack<T>::size() { stack_node<T>* i = root; int size = 0; while (i != nullptr) {// 如果i是空指针就结束循环 i = i->next; size++; } return size; }
现在,我们就编写完成了栈的全部代码,最后,全部代码如下:
main.cpp
#include <iostream> #include "stack/stack.h" #include "stack/stack.cpp" int main() { std::cout << "Stack: " << std::endl; test::stack<int> a(12); std::cout << a.top() << "," << a.size() << "," << a.empty() << std::endl; a.pop(); std::cout << a.size() << "," << a.empty() << std::endl; a.push(123); std::cout << a.top() << "," << a.size() << "," << a.empty() << std::endl; a.push(1); std::cout << a.top() << "," << a.size() << "," << a.empty() << std::endl; return 0; }
stack/stack.h
#ifndef MYDS_STACK_H #define MYDS_STACK_H namespace test { template <class T> class stack_node { public: stack_node<T>* next = nullptr; T value; }; template <class T> class stack { public: stack() : root(nullptr) {} stack(T v) : root(new test::stack_node<T>()) { root->value = v; } bool empty(); int size(); T top(); void pop(); void push(T v); private: stack_node<T>* root; }; } #endif //MYDS_STACK_H
stack/stack.cpp
#include "stack.h" template <class T> void test::stack<T>::push(T v) { if (root == nullptr) { root = new test::stack_node<T>; root->value = v; return; } stack_node<T>* tmp1 = root; stack_node<T>* tmp2 = new test::stack_node<T>; tmp2->value = v; tmp2->next = tmp1; root = tmp2; } template <class T> int test::stack<T>::size() { stack_node<T>* i = root; int size = 0; while (i != nullptr) { i = i->next; size++; } return size; } template <class T> void test::stack<T>::pop() { if (!root->next) { delete root; root = nullptr; return; } stack_node<T>* test = root->next; delete root; root = test; } template <class T> T test::stack<T>::top() { return root->value; } template <class T> bool test::stack<T>::empty() { return root == nullptr; }
(现在我们实现了栈,一次类推,你也可以试着自己实现一个队列。)
[完]