C#中泛型容器Stack<T>的用法并实现”撤销/重做”功能
作者:Darren 发布时间:2021-06-27 04:12:00
.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; }
}
//长度=索引+1
public 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;//有关撤销的泛型stack
private Stack<ICommand<T>> _redo;//有关重做的泛型stack
public 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();
}
来源:https://www.cnblogs.com/darrenji/p/4523483.html


猜你喜欢
- 一. String对象的比较1. ==比较是否引用同一个对象注意:对于内置类型,==比较的是变量中的值;对于引用类型 , == 比较的是引用
- 在java的开发中,java开发人员建议,尽量少用内部类,要把内部类提出他所处的那个类,单独生成一个类。直接来代码:package com.
- 1. 下载Tomcat首先,下载Apache Tomcat并解压到本地计算机,可存放于任何位置。另外,需要在系统中环境JRE_H
- 概览这部分内容来自于这个类的注释,简单翻译了下。LockSupport 类是用于创建锁和其他同步类的基本线程阻塞原语。它的实现思想
- 熟悉Android的朋友们都知道,不管是微博客户端还是新闻客户端,都离不开列表组件,可以说列表组件是Android数据展现方面最重要的组件,
- 1、SpringMVC验证@Validated的使用第一步:编写国际化消息资源文件编写国际化消息资源ValidatedMessage.pro
- RabbitMQ作为AMQP的代表性产品,在项目中大量使用。结合现在主流的spring boot,极大简化了开发过程中所涉及到的消息通信问题
- 一般,我们的web应用都是只有在用户登录之后才允许操作的,也就是说我们不允许非登录认证的用户直接访问某些页面或功能菜单项。我还记得很久以前我
- 为什么使用logback记得前几年工作的时候,公司使用的日志框架还是log4j,大约从16年中到现在,不管是我参与的别人已经搭建好的项目还是
- 如下所示:class Program {
- 1、项目引用System.Management库文件2、创建HardwareHandler.cs类文件namespace HardInfoT
- Android对sdcard扩展卡文件的操作其实就是普通的文件操作,但是仍然有些地方需要注意。比如:1.加入sdcard操作权限;2.确认s
- 前言:在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程
- 本文实例讲述了Android游戏开发学习②焰火绽放效果实现方法。分享给大家供大家参考。具体如下:本节介绍在游戏开发中常用到的数学物理应用——
- 本文为大家分享了NancyFx框架检测任务管理器的具体方法,供大家参考,具体内容如下先建一个空的项目和之前的NancyFx系列一样的步骤然后
- 位操作符是对数据按二进制位进行运算的操作符。位操作是其他很多语言都支持的操作,如C、C++和Java等,C#也不例外支持位操作。注意位操作支
- 前言 短时间提升自己最快的手段就是背面试题,最近总结了Java常用的面试题,分享给大家,希望大家都能圆梦大厂,加油,我命由我不由天
- 前面几篇文章学习了AndroidStudio插件的基础后,这篇文章打算开发一个酷炫一点的插件。因为会用到前面的基础,所以如果没有看前面系列文
- 公司在使用定时任务的时候,使用的是spring scheduled。代码如下:@EnableSchedulingpublic class T
- 参数为对象1、提交表单2、表单序列化,使用ajax提交var data = $("#addForm").serializ