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

标签:C#,泛型,容器,Stack,撤销,重做,功能
0
投稿

猜你喜欢

  • logback输出日志屏蔽quartz的debug等级日志方式

    2023-08-04 23:40:04
  • idea创建SpringBoot项目时Type选maven project和maven pom有何区别

    2023-07-04 07:20:27
  • SpringCloud Feign多参数传递及需要注意的问题

    2022-05-25 11:11:58
  • mybatis中数据加密与解密的实现

    2022-04-21 22:49:16
  • Java内存模型之happens-before概念详解

    2023-11-23 03:11:50
  • Spring Security 控制授权的方法

    2023-08-06 19:21:08
  • SpringBoot如何接收Post请求Body里面的参数

    2023-07-30 13:43:35
  • 详谈java 堆区、方法区和栈区

    2023-11-23 18:35:22
  • SpringBoot实战之SSL配置详解

    2022-08-03 19:38:17
  • C#预处理器指令的用法实例分析

    2023-03-09 16:21:07
  • Spring boot整合log4j2过程解析

    2023-11-29 10:47:53
  • 如何设计一个安全的API接口详解

    2023-03-06 14:57:03
  • Sprint Boot 集成MongoDB的操作方法

    2022-11-13 07:10:37
  • springcloud初体验(真香)

    2022-12-08 06:33:44
  • C# 网络编程之UDP

    2021-10-02 16:06:40
  • C#简单实现显示中文格式星期几的方法

    2021-09-08 12:27:05
  • Spring Data JPA 映射VO/DTO对象方式

    2022-10-13 10:25:27
  • Java原生服务器接收上传文件 不使用MultipartFile类

    2023-11-09 19:31:36
  • 一篇文章带你Java Spring开发入门

    2021-06-25 10:04:15
  • Java实现短信验证码的示例代码

    2023-11-09 03:22:47
  • asp之家 软件编程 m.aspxhome.com