Stack by pointer
前言:因为栈的很多操作是基于表的,所以这篇文章里的例程就不再大面积地写注释了,有不理解的地方可以翻看之前的链表笔记,或者直接写在评论区。
咳咳,说到这个栈,很多人乍听之下感觉很陌生、卧槽这是什么玩意。其实生活中随处可见,在一些小餐馆,客人不多的时候,椅子都是放成一摞的,一个叠一个。有客人来了就搬下来一把——肯定是搬最上面那一把,没人会从下面搬凳子吧2333 用完之后从上面再叠放上去,这是一个例子。 刷知乎或者看网页的时候需要返回,我们按一下,就跳转到上一个页面了,那这是怎么做的呢?我们用直觉考虑一下,应该是浏览器把每一次操作的结果都保存下来,要返回的时候,就把当前层移除——移除的是最新的那一层。如果有新的跳转或者其他操作,就依次叠放到之前最新的上面。类似的,主流的文本编辑器也都支持撤销操作,我们的编辑操作被记录在一个栈中,一旦出现误操作,只需要按下撤销(一般是control+z)按钮,就可以取消最近的一次操作,并回到之前的状态。
而我们在写程序的时候会涉及到不同函数之间的相互调用,被调函数(callee)执行完后,把权限返还给主调函数(caller)这也用到了“栈”这种结构。许多程序语言本身就是建立于栈结构上的,比如Postscript和Java运行环境都是基于栈结构的虚拟机。
我们再联系上一节提到的那个“free list”,可以很明显的感到一个性质:这些行为的次序,都是增加的时候从最新的那一端增加,要移除的时候,往往是把“最后移动的元素”首先给拿出去。这就叫后进先出(Last In First Out)。而且相对于一般的序列结构,它的数据操作范围都仅限于整个表的末端。
对栈的基本操作有Push(进栈or压栈)和Pop(出栈),前者相当于插入,后者则是删除最后的元素。

这是一个进行若干操作后的抽象栈,一般的模型是存在某个元素位于栈顶,而这是唯一的可见元素。不过这样说可能有点不好理解,那比如说一摞椅子。

这就可以视作一个栈,为了维持这一放置形式,对这个栈的操作只能在顶部实施:新的椅子只能叠放到最顶端;反过来只有最顶端的椅子才能被取走。因此和这个实例相比照,栈中可操作的一端被叫做栈顶,而另外一个无法操作的盲端被称为栈底。

就像这样。
因为栈是一个表,所以任何实现表的方法都能实现栈,这次就说一下好理解的的指针实现吧,比数组貌似好理解一些。用单链表实现的话,我们要通过在表顶端插入来实现Push,通过删除顶端元素实现Pop。而Top操作仅仅是返回顶端元素的值。不过在很多时候都是把Pop和Top合二为一的。本来可以用前一节的代码段,不过为了清楚起见,还是从头开始写吧
和之前一样,先给出一些前提性声明,实现栈同样要用到表头。
struct Node;
typedef struct Node *PtrToNode;
typedef PtrToNode Stack;
struct Node{
int Element;
PtrToNode Next;
};
测试空栈与测试空表的方式一样。
int IsEmpty(Stack i){
return i->Next==NULL;
}
创建一个栈的话也很简单,只需要建立一个头结点就好。
Stack Creat(){
Stack S;
S=(Stack)malloc(sizeof(struct Node));
if(S==NULL)
printf("out of space!!!");
else
S->Next=NULL;
MakeEmpty(S);
return S;
}
现在是中场问答时间,我们创建一个栈之后,里面会有什么?就是仅仅申请一块内存,然后什么也不做。里面会有——
垃圾数据,对吧。这是上学期的知识,声明一个变量后,系统会随机填充一段数据,我们不知道里面是什么,但是,我们能确定一点——这东西十有八九不是我们所期望的,因此我们需要把它扔掉。这就是MakeEmpty的意义。
void MakeEmpty(Stack S){
if(S==NULL)
printf("Must creat a stack first");
else
while(!IsEmpty(S))
Pop(S);
}
关于这个Pop函数是什么,emmm接着往后看吧,你看这涉及到了函数间的相互调用,就是运用了栈的特性。还有一个好玩的事实,就是——我们在写一个栈的时候已经用到了栈的环境,用栈来写栈,这就陷入递归了233 从这个角度再次理解一下递归吧,毕竟理解递归是筛选合格程序员的一道门槛。
创建之后就该讨论对栈的各项操作了,主要就三个:出栈,入栈和取栈顶元素。先说入,有入才有出嘛,Push是作为向链表前端进行插入而实现的,其中表的前端作为栈顶。所以实现起来也很顺畅
void Push(int X,Stack S){
Stack TemCell;
TemCell=(Stack)malloc(sizeof(S));
if(S==NULL) printf("Out of space!!!");
else{
TemCell->Element=X;
TemCell->Next=S->Next;
S->Next=TemCell;
}
}
这里提一句,S是表头,里面什么都不存,而第一个有效元素是S->Next,原因是S仅作为一个地址说明,告诉我们第一个有效元素“在哪”,我们不可能指望S存数据,不然的话,谁来告诉我们这个栈的顶在哪呢?这很重要,理解这个观点是看懂下面所有函数的基础,是重中之重。
接着说取栈顶元素,Top的实施是通过考察整个表在第一个位置上的元素而完成的,也就是把Head的元素返回
int Top(Stack S){
if(!IsEmpty(S))
return S->Next->Element;
printf("Empty stack");
return ;
}
最后,Pop是通过删除表的前端元素而实现的。
void Pop(Stack S) {
PtrToNode FirstNode;
if(IsEmpty(S))
printf("Empty stack");
else{
FirstNode=S->Next;
S->Next=S->Next->Next;
free(FirstNode);
}
}
到这里,已经很清楚了,所有的操作均花费常数时间,因为这些函数没有任何地方涉及到栈的size,更不用说依赖于size的循环了。但是这种实现方法的缺点在于对malloc和free的调用开销是昂贵的。避免这个缺点的方法就是用数组实现,具体的实现方法以后会说到,在后面几篇文章里会详细讨论栈的应用和数组实现。
下面写了一个测试程序
#include <stdio.h>
#include <stdlib.h>
struct Node;
typedef struct Node *PtrToNode;
typedef PtrToNode Stack;
struct Node{
int Element;
PtrToNode Next;
};
//函数签名
int IsEmpty(Stack i);
void Push(int X,Stack S);
int Top(Stack S);
void Pop(Stack S);
void MakeEmpty(Stack S);
Stack Creat();
void Traverse(Stack S); //入口
int main(){
Stack S;
S=Creat();
int n;
printf("Please input all elements to complete a stack,finished by 0\n");
while (scanf("%d",&n)&&n)
Push(n, S);
Traverse(S);
printf("Input imperative(1:top\t2:remove\t3:add),0 to quit\n");
while (scanf("%d",&n)&&n) {
if (n==)
printf("Top element:%d\n",Top(S));
else if(n==){
Pop(S);
Traverse(S);
}
else if(n==){
printf("number:");
scanf("%d",&n);
Push(n, S);
Traverse(S);
}
else
printf("Input again,it is invalid");
}
} //接口内部一览
int IsEmpty(Stack i){
return i->Next==NULL;
}
void Push(int X,Stack S){
Stack TemCell;
TemCell=(Stack)malloc(sizeof(S));
if(S==NULL) printf("Out of space!!!");
else{
TemCell->Element=X;
TemCell->Next=S->Next;
S->Next=TemCell;
}
} int Top(Stack S){
if(!IsEmpty(S))
return S->Next->Element;
printf("Empty stack");
return ;
} void Pop(Stack S) {
PtrToNode FirstNode;
if(IsEmpty(S))
printf("Empty stack");
else{
FirstNode=S->Next;
S->Next=S->Next->Next;
free(FirstNode);
}
}
void MakeEmpty(Stack S){
if(S==NULL)
printf("Must creat a stack first");
else
while(!IsEmpty(S))
Pop(S);
}
Stack Creat(){
Stack S;
S=(Stack)malloc(sizeof(struct Node));
if(S==NULL)
printf("out of space!!!");
else
S->Next=NULL;
MakeEmpty(S);
return S;
}
void Traverse(Stack S){
for (; S->Next; S=S->Next) {
printf("%d->",S->Next->Element);
}
printf("NULL\n");
}
完整代码(已测试)
然后自己调试一下吧,这会加深你对栈的理解的,祝食用愉快~
Stack by pointer的更多相关文章
- What are the differences between a pointer variable and a reference variable in C++?
Question: I know references are syntactic sugar, so code is easier to read and write. But what are t ...
- JVM学习(2)——技术文章里常说的堆,栈,堆栈到底是什么,从os的角度总结
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 堆栈是栈 JVM栈和本地方法栈划分 Java中的堆,栈和c/c++中的堆,栈 数据结构层面的堆,栈 os层面 ...
- 软件调试——IA-32 保护模式下寄存器一览
最近在看张银奎先生的<调试软件>一书,想将关键的技术记录下来,以便日后查阅,也分享给想看之人吧. 1 通用寄存器 EAX,EBX,ECX,EDX:用于运算的通用寄存器,可以使用AX,BX等 ...
- Assembly - Registers
Processor operations mostly involve processing data. This data can be stored in memory and accessed ...
- 内核函数KiFastCallEntry
KiFastCallEntry() 机制分析 概述 Win32 子系统 API 调用 ntdll!ZwWriteFile() 函数 ntdll!KiFastSystemCall() 函数 _KUSER ...
- __cdecl、__stdcall、__fastcall、thiscall 进栈、出栈区别
https://en.wikipedia.org/wiki/X86_calling_conventions https://msdn.microsoft.com/en-us/library/984x0 ...
- 递归转手工栈处理的一般式[C语言]
是任意形式的递归,是化解的一般式. 主题所谓的“递归调用化解为栈处理”,意思是,将递归函数调用化解为“一个由stack_push stack_pop stack_top等函数调用组成的循环式子”.这里 ...
- How a C++ compiler implements exception handling
Introduction One of the revolutionary features of C++ over traditional languages is its support for ...
- return机制
C/C++中,函数内部的一切变量(函数内部局部变量,形参 )都是在其被调用时才被分配内存单元.子函数运行结束时,所有局部变量的内存单元会被系统释放.形参和函数内部的局部变量的生命期和作用域都是在函数内 ...
随机推荐
- 创建maven项目pom.xml第一行报错
之前也创建过几次maven项目,也是第一行报错,之前直接是右键项目强制更新maven好像就解决了,这次遇见这个问题使用这个方法好像不起作用了,给的一堆英文报错又看不懂,幸好在网上看见路人甲大神提示,根 ...
- Mybatis第二篇【CRUD、分页】
完成CRUD操作 我们在上一篇中已经简单知道了Mybatis是怎么使用的以及工作流程了,这次我们使用Mybatis来完成CRUD的操作,再次巩固Mybatis的开发步骤以及一些细节 包与类之间的结构 ...
- 【漏洞公告】CVE-2017-12615/CVE-2017-12616:Tomcat信息泄漏和远程代码执行漏洞
2017年9月19日,Apache Tomcat官方确认并修复了两个高危漏洞,漏洞CVE编号:CVE-2017-12615和CVE-2017-12616,该漏洞受影响版本为7.0-7.80之间,在一定 ...
- JVM 运行时数据区总结 栈 堆 堆大小配置总结
1. 程序计数器 线程私有 当前线程所执行的字节码的行号指示器 2. 虚拟机栈 线程私有 存:Java方法(局部变量表(基本数据类型).操作数栈.动态链栈.方法出口) StackOverflowErr ...
- 利用angularJs自定义指令(directive)实现在页面某一部分内滑块随着滚动条上下滑动
最近老大让我一个效果实现在页面某一部分内滑块随着滚动条上下滑动,说明一下我们项目使用技术angularJs.大家都知道,使用jquery很好实现. 那么angular如何实现呢,我用的是自定义指令(d ...
- 开天辟地-用visualstudio2010编写helloworld
安装好visual之后,创建新项目 向源文件添加helloworld.cpp 编写helloworld代码,编译运行即可 在运行时候出现一个错误,错误和解决方法如下:
- angular之$watch方法详解
在$apply方法中提到过脏检查,首先apply方法会触发evel方法,当evel方法解析成功后,会去触发digest方法,digest方法会触发watch方法. (1)$watch简介 在diges ...
- Java为什么把String设计成不可变的(immutable)
在java中,String是字符串常量,可以从内存,同步机制,数据结构等方面分析 1:字符串中常量池的需要 String不同于普通基础变量类型的地方在于对象.java中的字符串对象都保存在字符串常量池 ...
- Apache Spark 2.2.0 中文文档 - SparkR (R on Spark) | ApacheCN
SparkR (R on Spark) 概述 SparkDataFrame 启动: SparkSession 从 RStudio 来启动 创建 SparkDataFrames 从本地的 data fr ...
- 使用 TUN 设备实现一个简单的 UDP 代理隧道
若要实现在 Linux 下的代理程序,方法有很多,比如看着 RFC 1928 来实现一个 socks5 代理并自行设置程序经过 socks5 代理等方式,下文是使用 Linux 提供的 tun/tap ...