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; }
        }
        //长度=索引+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();
        }

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。如果你想了解更多相关内容请查看下面相关链接

(0)

相关推荐

  • C#中泛型类和扩展方法如何使用

    我们经常通过泛型构造函数创建泛型实例,也常调用实例的扩展方法.以下的代码在项目中随处可见: static void Main(string[] args) { var strs = new List<string> {"hello","world"}; var result = strs.Where(s => s.StartsWith("h")); foreach (var item in result) { Console.W

  • C#泛型集合类型实现添加和遍历

    在"C#中List<T>是怎么存放元素的"中,分析了List<T>的源码,了解了List<T>是如何存放元素的.这次,就自定义一个泛型集合类型,可实现添加元素,并支持遍历 该泛型集合类型一定需要一个添加元素的方法,在添加元素的时候需要考虑:当添加的元素超过当前数组的容量,就让数组扩容:为了支持循环遍历,该泛型集合类型必须提供一个迭代器(实现IEnumerator接口). public class MyList<T> { T[] items

  • C#使用struct类型作为泛型Dictionary<TKey,TValue>的键

    我们经常用简单数据类型,比如int作为泛型Dictionary<TKey,TValue>的key,但有时候我们希望自定义数据类型作为Dictionary<TKey,TValue>的key,如何做到? 如果我们想自定义一个struct类型作为key,就必须针对该struct定义一个实现IEqualityComparer<T>接口的比较类,实现该接口的2个方法:Equals()方法和GetHashCode()方法,前者用来比较两个key是否相等,后者用来获取key的哈希值.

  • C#中的队列Queue<T>与堆栈Stack<T>

    一.概述: Queue<T>队列,对象的先进先出集合(“FIFO”).Stack<T>栈,对象的后进先出集合(”LIFO”). Queue<T>.Stack<T>类似于List<T>,但 Queue<T>没有IList<T>,所以不能用索引访问队列.也没有实现ICollection<T>,无Add,Remove等方法. 二.操作 1.入队列:Enqueue() Queue<string> nums

  • C# 泛型集合类List<T>使用总结

    目录 为什么选择使用List,而不是使用Array,或者ArryList 去重.交集.并集.差集操作 重写Equals() 和 GetHashCode() 简单使用 C#中List可谓是使用最广泛的一种数据类型了,使用他来规范数据时,往往会涉及到对数据的处理操作,相关处理数据方法也非常丰富,本文将简单介绍为何使用它,以及部分处理方法的灵活使用. 为什么选择使用List,而不是使用Array,或者ArryList 首先要说下数组的局限性 (1) 数组中元素是固定的:类型和数量都必须确定!一旦定义,

  • C#中泛型举例List<T>与DataTable相互转换

    一. DataTable转换到List<T> /// <summary> /// TableToList /// </summary> public class TableListConverter<T> where T : class, new() { public static IList<T> TableToList(DataTable dt) { IList<T> ts = new List<T>();// 定义集

  • C#泛型的使用案例

    有这样一个有关汽车的类. public class Car { public int ID { get; set; } public string Make { get; set; } } 现在,在客户端创建一个Car的集合实例,再遍历. static void Main(string[] args) { var car1 = new Car() {ID = 1, Make = "Ford"}; var car2 = new Car() {ID = 2, Make = "To

  • C# 泛型字典 Dictionary的使用详解

    本文主要介绍了C# 泛型字典 Dictionary的使用详解,分享给大家,具体如下: 泛型最常见的用途是泛型集合,命名空间System.Collections.Generic 中包含了一些基于泛型的集合类,使用泛型集合类可以提供更高的类型安全性,还有更高的性能,避免了非泛型集合的重复的装箱和拆箱. 很多非泛型集合类都有对应的泛型集合类,我觉得最好还是养成用泛型集合类的好习惯,他不但性能上好而且 功能上要比非泛型类更齐全.下面是常用的非泛型集合类以及对应的泛型集合类 非泛型集合类 泛型集合类 Ar

  • C#泛型语法详解

    一.为什么要有泛型? 我们在写一些方法时可能会方法名相同,参数类型不同的方法,这种叫做重载.如果只是因为参数类型不同里面做的业务逻辑都是相同的,那可能就是复制粘贴方法,改变参数类型,例如一些排序算法,int.float.double等类型的排序,参数数组存的数据类型不一样,还有像根据索引找到List集合中的对象.可能这个对象是Person.Dog等对象,这样方法改变的只是参数类型,那就是能不能写一个方法,传递不同的参数类型呢?于是乎有了泛型. 二.什么是泛型? 泛型通过参数化类型来实现在同一份代

  • C# 泛型List排序的实现

    本文主要介绍了C# 泛型List排序的实现,分享给大家,具体如下: 代码 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace L_List_sort { public class Person:IComparable<Person> { // 属性 public string name; publ

随机推荐