.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();
        }

参考资料:

http://brendan.enrick.com/post/Simple-C-Stack-Implementation

http://www.cambiaresearch.com/articles/82/generic-undoredo-stack-in-csharp

C#中泛型容器Stack<T>的用法,以及借此实现”撤销/重做”功能的更多相关文章

  1. C#中泛型容器Stack<T>

    我以前都是学出c,c++,这个学期开始学c#有点不适应,在编程中遇到些问题,所以自己在网上查了些资料,翻了一下书,写一些总结. 关于c#中Stack<T>泛型容器: <1>st ...

  2. java中Set,Map,Stack一些简单用法

    import java.util.Iterator; import java.util.Stack; import java.io.*; import java.util.Set; import ja ...

  3. 刷题upupup【Java中Queue、Stack、Heap用法总结】

    [Queue] 先进先出(First-In-First-Out),LinkedList实现了Queue接口.它只允许在表的前端进行删除操作,而在表的后端进行插入操作. add()       增加一个 ...

  4. C/C++解题常用STL大礼包 含vector,map,set,queue(含优先队列) ,stack的常用用法

    每次忘记都去查,真难啊 /* C/C++解题常用STL大礼包 含vector,map,set,queue(含优先队列) ,stack的常用用法 */ /* vector常用用法 */ //头文件 #i ...

  5. (转)Java中的容器详细总结

    Java中的容器详细总结(编辑中) 原文链接:http://anxpp.com/index.php/archives/656/ 注:本文基于 Jdk1.8 编写 通常程序总是根据运行时才知道的某些条件 ...

  6. 理解Docker(6):若干企业生产环境中的容器网络方案

    本系列文章将介绍 Docker的相关知识: (1)Docker 安装及基本用法 (2)Docker 镜像 (3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境 ...

  7. 泛型容器单元(Generics.Collections)[3]: TStack<T> 堆栈列表

    TQueue 和 TStack, 一个是队列列表, 一个是堆栈列表; 一个是先进先出, 一个是先进后出. TStack 主要有三个方法.一个属性:Push(压栈).Pop(出栈).Peek(查看下一个 ...

  8. java中的容器问题

    小小的总结一下java中的容器问题. 一.三个知识点 1.迭代器 1).java.util.Interator + hasnext(); next(); remove(); 2).java.lang. ...

  9. C#中泛型和单链表

      泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能.泛型将类型参数的概念引入 .NET Framework,类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类 ...

随机推荐

  1. 『Numpy学习指南』Matplotlib绘图

    数据生成: import numpy as np import matplotlib.pyplot as plt func = np.poly1d(np.array([,,,])) func1 = f ...

  2. 初始ASP.NET数据控件GridView

    使用GridView控件绑定数据源 GridView控件个人认为就是数据表格控件,它以表格的形式显示数据源中的数据.每列表示一个字段,每行表示一条记录.     GridView控件支持在页面有一下功 ...

  3. InnoDB master thread工作原理

    我们简单交流下InnoDB master thread学习,有兴趣的朋友可以参考<<MySQL技术内蒙--InnoDB存储引擎第二版>> void master_thread( ...

  4. 【redis】3.Spring 集成注解 redis 项目配置使用

    spring-data-redis  项目,配合 spring 特性并集成 Jedis 的一些命令和方法. 配置redis继承到spring管理项目,使用注解实现redis缓存功能. 参考:http: ...

  5. pageHelper 排序 +- 字符串处理

    自己记录一下. 前端要把sort参数传过来, 1. 如果约定是下面这种形式: sort=id-name+age+ 直接在java后台进行替换就行,连正则都不用. sort = sort.replace ...

  6. 【noip模拟赛1】古韵之同心锁

    据说在一座OI桥上,同心锁上显示的文字有着它奇异的呈现方式,需要你把它稍做改变才可解密.每个同心锁上都有3个数据.第一个数据是一个字符串s.第二个数据m表示把s串从m处分为两段,s[1]至s[m-1] ...

  7. SET操作符

    一:MySQL交集INTERSECT运算符 1.介绍 INTERSECT运算符是一个集合运算符,它只返回两个查询或更多查询的交集. 语法: INTERSECT运算符比较两个查询的结果,并返回由左和右查 ...

  8. EF框架搭建小总结--CodeFirst代码优先

    前言:之前在下总结编写了一篇 EF框架搭建小总结--ModelFirst模型优先 博文,看到一段时间内该博文的访问量蹭.蹭蹭.蹭蹭蹭...往上涨(实际也不是很多,嘿嘿),但是还是按捺不住内心的喜悦(蛮 ...

  9. Javascript中DOM详解与学习

    DOM(文档对象模型)是针对html和XML文档的一个API(应用程序编程接口).DOM描绘了一个层次化的节点树,允许开发人员添加,移除和修改页面的某一部分.下面将从这几个层次来学习. 一.节点层次 ...

  10. 使用 jquery 开发用户通讯录

    由于开发需求,需要做一个通讯录界面,点击右侧首字母菜单,列表会将对应字母列表成员滑动至顶部,效果如下图(包括点击事件+长按事件): 1.需求分析 (1)首先,我们需要把数据里用户名转换为首拼,然后归类 ...