C#中泛型容器Stack<T>的用法,以及借此实现”撤销/重做”功能
.Net为我们提供了众多的泛型集合。比如,Stack<T>先进后出,Queue<T>先进先出,List<T>集合元素可排序,支持索引,LinkedList<T>,双向链表的泛型实现,不支持索引;ISet<T>不允许被复制,他有2个实现,一个是HashSet<T>,不维持集合元素的排序,另一个是SortedSet<T>,支持集合元素的排序;IDictionary<TKey, TValue>是一个字典集合的泛型接口,SortedList<TKey,TValue>实现了IDictionary<TKey, TValue>,但同时也是集合,维持集合元素的排序,支持按键或按值索引。
本篇体验Stack<T>的用法。
基本用法
Stack<T>是Stack的泛型实现,提供了若干方法和属性,比如入栈、出栈、查看栈顶元素,查看栈内集合元素数量,等等。栈的最大特点是先进后出,可以把栈想像成一堆叠起来的盘子,入栈就是把一个个盘子放到最上面,出栈就是从最上面把盘子拿掉。用法比较简单:
class Program{static void Main(string[] args){var customer1 = new Customer() {ID = 1, Name = "张三", Gender = "男"};var customer2 = new Customer() { ID = 2, Name = "李四", Gender = "男" };Stack<Customer> stackCustomers = new Stack<Customer>();//入栈stackCustomers.Push(customer1);stackCustomers.Push(customer2);//查看栈顶元素Customer topCustomer = stackCustomers.Peek();Console.WriteLine("栈顶元素是:" + topCustomer.Name);//遍历所有栈内元素foreach (var customer in stackCustomers){Console.WriteLine("id is {0},name is {1}", customer.ID, customer.Name);}//出栈Customer outCustomer = stackCustomers.Pop();Console.WriteLine("正在出栈的是:" + outCustomer.Name);Console.WriteLine("当前栈内元素数量为:" + stackCustomers.Count);Console.ReadKey();}}public class Customer{public int ID { get; set; }public string Name { get; set; }public string Gender { get; set; }}
临摹一个泛型Stack<T>
泛型Stack类内部维护这一个泛型数组和索引指针,且指针的初始位置是-1。
入栈就是把指针往前提一位,并把入栈元素赋值给该栈内位置。另外,入栈要考虑是否达到容量上限,如果达到就要给数组扩容。
出栈就是让当前栈位置的元素值为入栈类型的默认值,并大指针后退一位。
获取栈顶元素就是获取栈当前索引位置对应的元素。
public class MyStack<T>{//维护T类型的数组private T[] _elements;protected T[] Elements{get { return _elements; }set { _elements = value; }}public MyStack(){_capacity = 5;//初始值Elements = new T[Capacity];}public MyStack(int capacity){Capacity = capacity;Elements = new T[Capacity];}//指针private int _index = -1;public int Index{get { return _index; }set { _index = value; }}//容量private int _capacity;public int Capacity{get { return _capacity; }set { _capacity = value; }}//长度=索引+1public int Length{get { return Index + 1; }}//入栈public void Push(T element){if (this.Length == Capacity){IncreaseCapacity();}Index++;Elements[Index] = element;}//出栈public T Pop(){if (this.Length < 1){throw new InvalidOperationException("栈内已空");}T element = Elements[Index];//原先位置元素变成默认值Elements[Index] = default(T);//索引减一Index--;return element;}//获取栈顶元素public T Peek(){if (this.Length < 1){throw new InvalidOperationException("栈内已空");}return Elements[Index];}private void IncreaseCapacity(){Capacity++;Capacity *= 2;//创建新的T类型数组T[] newElements = new T[Capacity];//把原先的数组复制到新的数组中来Array.Copy(Elements, newElements, Elements.Length);Elements = newElements;}}
现在,在客户端,实施一系列的入栈和出栈操作。
static void Main(string[] args){//创建泛型Stack实例MyStack<int> myStack = new MyStack<int>();//遍历10次入栈for (int i = 0; i < 10; i++){Console.WriteLine(i + "开始入栈");myStack.Push(i);Console.WriteLine("当前栈的长度是:" + myStack.Length);}//遍历10次出栈for (int i = 0; i < 10; i++){Console.WriteLine("当前出栈的是" + myStack.Peek());myStack.Pop();Console.WriteLine("当前栈的长度是:" + myStack.Length);}//所有出栈结束,再查看栈顶元素抛异常try{myStack.Peek();}catch (InvalidOperationException ex){Console.WriteLine(ex.Message);}//所有出栈结束,再出栈抛异常try{myStack.Pop();}catch (InvalidOperationException ex){Console.WriteLine(ex.Message);}Console.ReadKey();}
其实,泛型Stack<T>的内部也是维护着一个数组,数组的容量是动态变化的,这一点很像List<T>,就像这里提到的。
使用泛型Stack<T>实现"撤销/重做"操作
首先,操作或撤销操作是针对某种类型的撤销或重做,提炼出一个接口。
public interface ICommand<T>{T Do(T input);T Undo(T input);}
假设,这里想实现对整型数的"撤销/重做"操作。
public class AddIntCommand : ICommand<int>{private int _value;public int Value{get { return _value; }set { _value = value; }}public AddIntCommand(){_value = 0;}public AddIntCommand(int value){_value = value;}//执行操作public int Do(int input){return input + _value;}//撤销操作public int Undo(int input){return input - _value;}}
接下来,需要一个泛型类来管理所有撤销或操作命令,把这些命令放在Stack<ICommand<T>>泛型集合中。
//使用泛型Stack实现撤销或重做public class UndoRedoStack<T>{private Stack<ICommand<T>> _undo;//有关撤销的泛型stackprivate Stack<ICommand<T>> _redo;//有关重做的泛型stackpublic UndoRedoStack(){Reset();}//记录撤销的数量public int UndoCount{get { return _undo.Count; }}//记录重做的数量public int RedoCount{get { return _redo.Count; }}//恢复到出厂设置public void Reset(){_undo = new Stack<ICommand<T>>();_redo = new Stack<ICommand<T>>();}//执行操作public T Do(ICommand<T> cmd, T input){T output = cmd.Do(input);//把刚才的命令放入有关撤销的stack中_undo.Push(cmd);//一旦启动一个新命令,有关重做的stack清空_redo.Clear();return output;}//撤销操作public T Undo(T input){if (_undo.Count > 0){//出栈ICommand<T> cmd = _undo.Pop();T output = cmd.Undo(input);_redo.Push(cmd);return output;}else{return input;}}//重做操作public T Redo(T input){if (_redo.Count > 0){ICommand<T> cmd = _redo.Pop();T output = cmd.Do(input);_undo.Push(cmd);return output;}else{return input;}}}
最后,在客户端按如下调用:
static void Main(string[] args){UndoRedoStack<int> intCalulator = new UndoRedoStack<int>();int count = 0;count = intCalulator.Do(new AddIntCommand(10), count);count = intCalulator.Do(new AddIntCommand(20), count);Console.WriteLine("第一次计算的值为:{0}",count);//执行撤销操作一次count = intCalulator.Undo(count);Console.WriteLine("第二次计算的值为:{0}",count);Console.ReadKey();}
参考资料:
http://brendan.enrick.com/post/Simple-C-Stack-Implementation
http://www.cambiaresearch.com/articles/82/generic-undoredo-stack-in-csharp
C#中泛型容器Stack<T>的用法,以及借此实现”撤销/重做”功能的更多相关文章
- C#中泛型容器Stack<T>
我以前都是学出c,c++,这个学期开始学c#有点不适应,在编程中遇到些问题,所以自己在网上查了些资料,翻了一下书,写一些总结. 关于c#中Stack<T>泛型容器: <1>st ...
- java中Set,Map,Stack一些简单用法
import java.util.Iterator; import java.util.Stack; import java.io.*; import java.util.Set; import ja ...
- 刷题upupup【Java中Queue、Stack、Heap用法总结】
[Queue] 先进先出(First-In-First-Out),LinkedList实现了Queue接口.它只允许在表的前端进行删除操作,而在表的后端进行插入操作. add() 增加一个 ...
- C/C++解题常用STL大礼包 含vector,map,set,queue(含优先队列) ,stack的常用用法
每次忘记都去查,真难啊 /* C/C++解题常用STL大礼包 含vector,map,set,queue(含优先队列) ,stack的常用用法 */ /* vector常用用法 */ //头文件 #i ...
- (转)Java中的容器详细总结
Java中的容器详细总结(编辑中) 原文链接:http://anxpp.com/index.php/archives/656/ 注:本文基于 Jdk1.8 编写 通常程序总是根据运行时才知道的某些条件 ...
- 理解Docker(6):若干企业生产环境中的容器网络方案
本系列文章将介绍 Docker的相关知识: (1)Docker 安装及基本用法 (2)Docker 镜像 (3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境 ...
- 泛型容器单元(Generics.Collections)[3]: TStack<T> 堆栈列表
TQueue 和 TStack, 一个是队列列表, 一个是堆栈列表; 一个是先进先出, 一个是先进后出. TStack 主要有三个方法.一个属性:Push(压栈).Pop(出栈).Peek(查看下一个 ...
- java中的容器问题
小小的总结一下java中的容器问题. 一.三个知识点 1.迭代器 1).java.util.Interator + hasnext(); next(); remove(); 2).java.lang. ...
- C#中泛型和单链表
泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能.泛型将类型参数的概念引入 .NET Framework,类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类 ...
随机推荐
- linux下brctl配置网桥
原文:http://zhumeng8337797.blog.163.com/blog/static/1007689142011643834429/ 先装好网卡,连上网线,这是废话,不用说了. 然后开始 ...
- 数据库SQL语句性能优化
选择最有效率的表名顺序 ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表 driving table)将被最先处理,在FROM子句中包含多个表的情况下 ...
- django Rest Framework---缓存通过drf-extensions扩展来实现
什么情况下使用缓存 1.不经常更新的数据 2.用户经常访问的一些页面,比如商品列表页.商品详情页等 3.用户经常修改的一些操作:购物车.订单中心等 关于DRF缓存扩展可以参考文档:http://chi ...
- mysql索引 B+tree
一.B+tree示意图 二.为什么要用索引 1.索引能极大减少存储引擎需要扫描的数据量:因为索引有序所以可以快速查找并且不用全表查找: 2.索引可以把随机IO变为顺序IO:因为B+tree在数据中保存 ...
- 【Git使用详解】Egit的常用操作详解
常用操作 操作 说明 Fetch 从远程获取最新版本到本地,不会自动merge Merge 可以把一个分支标签或某个commit的修改合并现在的分支上 Pull 从远程获取最新版本并merge到本地相 ...
- SqlServer中Sql查看存储过程
( 一)利用Sql语句查询数据库中的所有表 1.利用sysobjects系统表 select * from sysobjects where xtype='U' 2,利用sys.tables目录视图 ...
- Java编程的逻辑 (18) - 为什么说继承是把双刃剑
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...
- hdu 2545 求当前结点到根节点的距离
求当前结点到根节点的距离 Sample Input 2 1 //n m 1 2 1 2 //询问 5 2 1 2 1 3 3 4 3 5 4 2 //询问 4 5 0 0 Sample Output ...
- 【LOJ】 #2308. 「APIO2017」商旅
题解 分数题可以想到分数规划,我们预处理出从i到j卖什么货物赚的最多,然后把每条边的边权改成"利润 - 效率 × 时间" 用spfa找正环即可 代码 #include <bi ...
- P1164 小A点菜 洛谷
题目背景 uim神犇拿到了uoi的ra(镭牌)后,立刻拉着基友小A到了一家……餐馆,很低端的那种. uim指着墙上的价目表(太低级了没有菜单),说:“随便点”. 题目描述 不过uim由于买了一些辅(e ...