当前课程知识点:C++语言程序设计进阶 > 第九章 模板与群体数据 > 栈 > 栈类模板
栈是只能从一端访问的线性群体,可以访问的这一端称栈顶,另一端称栈底。栈是一种后进先出的数据结构。
栈空
栈满
一般状态
栈中没有元素(以数组容纳的栈为例)
栈中元素个数达到上限(以数组容纳的栈为例)
栈中有元素,但未达到栈满状态(以数组容纳的栈为例)
初始化
入栈
出栈
清空栈
访问栈顶元素
检测栈的状态(满、空)
//Stack.h #ifndef STACK_H #define STACK_H #include <cassert> template <class T, int SIZE = 50> class Stack { private: T list[SIZE]; int top; public: Stack(); void push(const T &item); T pop(); void clear(); const T &peek() const; bool isEmpty() const; bool isFull() const; }; //模板的实现 template <class T, int SIZE> Stack<T, SIZE>::Stack() : top(-1) { } template <class T, int SIZE> void Stack<T, SIZE>::push(const T &item) { assert(!isFull()); list[++top] = item; } template <class T, int SIZE> T Stack<T, SIZE>::pop() { assert(!isEmpty()); return list[top--]; } template <class T, int SIZE> const T &Stack<T, SIZE>::peek() const { assert(!isEmpty()); return list[top]; //返回栈顶元素 } template <class T, int SIZE> bool Stack<T, SIZE>::isEmpty() const { return top == -1; } template <class T, int SIZE> bool Stack<T, SIZE>::isFull() const { return top == SIZE - 1; } template <class T, int SIZE> void Stack<T, SIZE>::clear() { top = -1; } #endif //STACK_H
大家好
欢迎回来继续学习
C++语言程序设计
这一节我们来学习栈类
栈是一种线性的数据结构
它是存取操作受限制的线性表
也就是说一个栈结构
它只能从一端插入数据
从同一端删除数据
那么从这一端
把数据放进去
我们叫做入栈
也叫做压入栈
同样从这个地方把数据取出来
我们叫做出栈 也叫弹出栈
也就是栈它是一个
后进先出的这么一种数据结构
接下来呢
我们来看一下
这个栈的示意图
我们看这个图里面呢
有一个箭头指向最底下
叫做栈底
有一个箭头指向最上面一个元素
叫做栈顶
那入栈和出栈操作呢
只能在栈顶来进行
所以呢
最后放进去的元素
那肯定是最先取出来
所以它就是这样一种
后进先出的结构
那在不同的程序中呢
实现栈呢
其实有的时候小策略
会有一点不同
比如栈底到底是指向
最底下的一个元素
还是说指向它再下面的位置
栈顶是指向最上面的一个元素
还是指向
下一个空的可用的空间
这个不同的程序里面呢
处理方法可能略微有不同
但是大家只要掌握着
你去实现一个栈的时候
有这个后进先出的这个原则
就可以了
大家一定会有这样的疑问
栈有什么用呢
实际上呢
我们函数调用
和返回的过程呢
它的底层的处理
都是用栈来实现的
不然的话我们调用子函数的时候
控制流程进到子函数中
执行完了一个子函数
它怎么能知道回到哪儿去呢
怎么就能够那么准确地
回到调用的位置去呢
实际上它是把要返回的这个位置
也叫返回地址
压入到栈里面
等它需要返回的时候呢
从里面弹出来
还有呢
包括我们参数传递的时候
包括传递返回值的时候
都用到了栈
这一点呢
大家如果以后去学习数据结构
学习编译原理
都会有更多的了解
那在这里呢
我用一个特别简单的示意图
给大家演示另外一种
栈的应用
就是用栈来作表达式处理
那么这个例子呢
我把它简化了很多
而且只是用图示来表示一下
栈可以这样用
如果你真的要写程序
去实现表达式处理
那么要做的事情还是挺多的
作为这门课呢
我们不要求大家掌握这些
只要求大家对栈
有个初步的了解就行了
这是一个非常简单的表达式
处理的示意图
这是简化了的问题
比如说
我们有这样一个表达式需要处理
a除以b 加上c乘以d
那就可以有一个操作数栈
和一个操作符栈
怎么用这两个栈呢
规则是
凡是遇到了操作数
一定直接压入操作数栈里面
遇到了操作符怎么办呢
我们来看
避暑说遇到的a
a压到栈里面了
再遇到了操作符除号
现在看一下栈是空的
所以这个除号可以直接压入栈里
又遇到了操作数b
那么这个b压入栈里
接下来呢
遇到了加号
那是否压入操作符栈呢
不能马上往里压
因为这栈里面已经有元素了
要跟栈顶的元素进行比较
比较的结果呢
加号的优先级比较低
而栈顶元素的优先级比较高
所以加号暂停处理
先把栈里面这个元素
栈里面处理完再说
这个时候就弹出
操作符栈的一个元素
要进行运算
那么它是二元运算符除号
所以要弹出操作数栈的两个数据
进行这样的除法
除法结果再压回操作数栈
继续作为操作数
现在接下来轮到这个加号了
现在操作符栈为空
所以加号直接入栈
好 继续下去呢
又遇到了操作数c
那么c入栈
又遇到了乘号
乘号跟栈顶的这个加号比较呢
乘号比加号的优先级高
所以称号压入栈
为什么优先级高就可以压入栈
优先级不能压入栈呢
栈是后进先出的
后压进的东西
要现拿出来运算
那当然优先级高
你才可以往上压
好 接下来呢
遇到了操作数d
d直接压入操作数栈
再往后呢
发现表达式结束了
那这个时候
接下来就要依次从操作符栈里面
弹出操作符进行运算
第一次弹出一个乘号来
二元运算符
所以需要把两个操作数
c和d都弹出来 进行乘法
乘法的结果又压回栈里面
然后再一次呢
弹出一个加号来
同时弹出两个操作数t1 t2
进行加法
加法运算的结果
再压回操作数栈里面
再往外弹呢发现
操作符栈空了
那就说明所有的运算
都已经解决完了
这个时候操作数栈
栈顶的元素
就是这个表达式的结果
这个过程看起来
有了操作数栈
有了操作符栈
表达式处理它是非常简单的了
当然这是一个简化的问题
实际问题要比这个复杂
大家要去写程序去实现
这个表达式处理
还是有些难度的
需要去学一下数据结构课
里面会讲这样的算法的
栈呢 有三种基本状态
一个是栈空
就是栈里面没有元素的时候
一个是栈满
整个栈的存储空间都装满了
再装不下了
这种状态
还有一种状态呢
当然就是最普通的一般状态
既不空也不满的状态
现在我们看一下
这几种状态的示意图
这就表示栈空的情况
栈里面一个元素都没有
这个表示栈满的情况
全部分配给这个栈用的存储空间
都装满了
那就再放不进去数据了
只能往外弹数据出栈了
那么这个是一般的状态
栈既没有空 也没有满
我们也可以继续往里面放数据
需要的时候呢
往外弹数据
接下来呢我们还是
通过一个例题来看
怎么样自己来写一个栈类的模板
那么为此呢
我们要了解
栈有哪些基本的操作呢
首先
我们要能够对栈初始化
要能够将数据压入栈里面去
叫入栈
要能够从栈里面弹出数据
要能够清空栈
还有呢
能够看一下这个栈顶的元素
也就是读取栈顶的元素
但是不删除它
还有呢
需要能够检测栈的状态
栈到底是不是空
是不是满
这些是栈的基本操作
好 接下来我们来看看
我们怎么样来写一个程序
去实现这个栈类的模板
这是一个栈类模板
这个模板的类型参数表里面
有一个类型参数
这里含有一个常量
我们讲这个类模板里面
可以有常量作为它的参数的
规定了这个size是50
再看这个类模板的设计
它的数据成员
一个是一个内部的数组
这个数组大小就是size
然后这个是
top是指向栈顶的这个指针
那它的成员函数有构造函数
有压入栈的push函数
有弹出栈的pop函数
清空栈cear函数
peek是读取栈顶的元素
但是不删除它
判断栈是否空
判断栈是否满
这就是栈的最基本的这几个操作都包含在里面了
然后我们看这个函数的实现
构造函数就是将top指针
置成-1 初始状态
然后这个push函数呢
就是将参数
这个数据item
T类型的数据 item压入栈里
所以我们来看到
当首先栈不满的情况下
我们就可以压栈
先将top指针加1
是指向下一个可用的空间
然后将item放进去
弹出栈呢也是
要判断一下栈首先不空才能弹
就是return
返回这个栈顶元素
同时栈顶指针要减1
先返回元素后减1
这个peek是读取栈顶的元素
先判断栈不空的情况下
返回栈顶的元素
但是它与这个弹出栈不一样
与弹出栈不一样
弹出栈要将栈顶指针减1
而这个返回栈顶元素呢
它只是访问栈顶元素
栈顶指针并不减1
这个判断栈是否空
就比较一下top这个指针
它是否等于-1
判断栈是否满
判断这个top它是否等于SIZE-1
然后这个清空栈呢
将是将栈顶指针置为-1
这就是一个简单的
只具有基本功能的
这个栈类模板
-导学
--导学
-继承的基本概念和语法
-第七章 继承与派生--继承的基本概念和语法习题
-继承方式
-第七章 继承与派生--继承方式
-基类与派生类类型转换
-第七章 继承与派生--基类与派生类类型转换
-派生类的构造和析构
--派生类的构造函数
--派生类的析构函数
--第七章 继承与派生--派生类的构造和析构
-派生类成员的标识与访问
--虚基类
-第七章 继承与派生--派生类成员的标识与访问
-小结
--小结
-综合实例
--第七章综合实例
-实验七
--实验七
-导学
--导学
-第八章 多态性--导学
-运算符重载
--运算符重载的规则
-第八章 多态性--运算符重载
-虚函数
--虚函数
--虚析构函数
--虚表与动态绑定
-第八章 多态性--虚函数
-抽象类
--抽象类
--第八章 多态性--抽象类
-override与final
-第八章 多态性--override与final
-小结
--第八章小结
-综合实例
--第八章综合实例
-实验八
--实验八
- 第八章讲义
-导学
--导学
-模板
--函数模板
--类模板
-第九章 模板与群体数据--模板
-线性群体
--线性群体的概念
-第九章 模板与群体数据--线性群体
-数组
--数组类模板
-链表
--链表类模板
-第九章 模板与群体数据--链表
-栈
--栈类模板
--栈类模板课后习题
--例9-9 栈的应用课后习题
-队列
--队列类模板
-第九章 模板与群体数据--队列
-排序
--排序概述
--插入排序
--选择排序
--交换排序
-第九章 模板与群体数据--排序
-查找
--查找
--查找课后习题
-小结
--小结
-综合实例
--综合实例
-实验九
--实验九
- 第九章讲义
-导学
--导学
-泛型程序设计及STL的结构
--STL简介
-第十章 泛型程序设计与C++标准模板库--泛型程序设计及STL的结构
-迭代器
--迭代器
-第十章 泛型程序设计与C++标准模板库--迭代器
-容器的基本功能与分类
-第十章 泛型程序设计与C++标准模板库--容器的基本功能与分类
-顺序容器
--顺序容器的特征
--第十章 泛型程序设计与C++标准模板库--顺序容器
-关联容器
--集合
--映射
-第十章 泛型程序设计与C++标准模板库--关联容器
-函数对象
--函数对象
--函数适配器
-算法
--算法
-小结
--第十章小结
-综合实例
--综合实例
-实验十
--实验十
- 第十章讲义
-导学
--导学
-I/O流的概念及流类库结构
-第十一章 流类库与输入/输出--I/O流的概念及流类库结构
-输出流
--输出流概述
--向文本文件输出
--向二进制文件输出
--向字符串输出
-第十一章 流类库与输入/输出--输出流
-输入流
--输入流概述
--输入流应用举例
--从字符串输入
-第十一章 流类库与输入/输出--输入流
-输入/输出流
--输入/输出流
-第十一章 流类库与输入/输出--输入/输出流
-小结
--小结
-综合实例
--综合实例
-实验十一
--实验十一
- 第十一章讲义
-导学
--第12章导学
-异常处理的思想与程序实现
-第十二章 异常处理--异常处理的思想与程序实现
-异常处理中的构造与析构
-第十二章 异常处理--异常处理中的构造与析构
-标准程序库异常处理
-第十二章 异常处理--标准程序库异常处理
-小结
--第12章小结
-综合实例
--综合实例
-实验十二
--实验十二
- 第十二章讲义