C#中泛型容器Stack<T>的用法并實(shí)現(xiàn)”撤銷/重做”功能
.Net為我們提供了眾多的泛型集合。比如,Stack<T>先進(jìn)后出,Queue<T>先進(jìn)先出,List<T>集合元素可排序,支持索引,LinkedList<T>,雙向鏈表的泛型實(shí)現(xiàn),不支持索引;ISet<T>不允許被復(fù)制,他有2個(gè)實(shí)現(xiàn),一個(gè)是HashSet<T>,不維持集合元素的排序,另一個(gè)是SortedSet<T>,支持集合元素的排序;IDictionary<TKey, TValue>是一個(gè)字典集合的泛型接口,SortedList<TKey,TValue>實(shí)現(xiàn)了IDictionary<TKey, TValue>,但同時(shí)也是集合,維持集合元素的排序,支持按鍵或按值索引。
本篇體驗(yàn)Stack<T>的用法。
基本用法
Stack<T>是Stack的泛型實(shí)現(xiàn),提供了若干方法和屬性,比如入棧、出棧、查看棧頂元素,查看棧內(nèi)集合元素?cái)?shù)量,等等。棧的最大特點(diǎn)是先進(jìn)后出,可以把棧想像成一堆疊起來的盤子,入棧就是把一個(gè)個(gè)盤子放到最上面,出棧就是從最上面把盤子拿掉。用法比較簡單:
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); //遍歷所有棧內(nèi)元素 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("當(dāng)前棧內(nèi)元素?cái)?shù)量為:" + stackCustomers.Count); Console.ReadKey(); } } public class Customer { public int ID { get; set; } public string Name { get; set; } public string Gender { get; set; } }
臨摹一個(gè)泛型Stack<T>
泛型Stack類內(nèi)部維護(hù)這一個(gè)泛型數(shù)組和索引指針,且指針的初始位置是-1。
入棧就是把指針往前提一位,并把入棧元素賦值給該棧內(nèi)位置。另外,入棧要考慮是否達(dá)到容量上限,如果達(dá)到就要給數(shù)組擴(kuò)容。
出棧就是讓當(dāng)前棧位置的元素值為入棧類型的默認(rèn)值,并大指針后退一位。
獲取棧頂元素就是獲取棧當(dāng)前索引位置對應(yīng)的元素。
public class MyStack<T> { //維護(hù)T類型的數(shù)組 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("棧內(nèi)已空"); } T element = Elements[Index]; //原先位置元素變成默認(rèn)值 Elements[Index] = default(T); //索引減一 Index--; return element; } //獲取棧頂元素 public T Peek() { if (this.Length < 1) { throw new InvalidOperationException("棧內(nèi)已空"); } return Elements[Index]; } private void IncreaseCapacity() { Capacity++; Capacity *= 2; //創(chuàng)建新的T類型數(shù)組 T[] newElements = new T[Capacity]; //把原先的數(shù)組復(fù)制到新的數(shù)組中來 Array.Copy(Elements, newElements, Elements.Length); Elements = newElements; } }
現(xiàn)在,在客戶端,實(shí)施一系列的入棧和出棧操作。
static void Main(string[] args) { //創(chuàng)建泛型Stack實(shí)例 MyStack<int> myStack = new MyStack<int>(); //遍歷10次入棧 for (int i = 0; i < 10; i++) { Console.WriteLine(i + "開始入棧"); myStack.Push(i); Console.WriteLine("當(dāng)前棧的長度是:" + myStack.Length); } //遍歷10次出棧 for (int i = 0; i < 10; i++) { Console.WriteLine("當(dāng)前出棧的是" + myStack.Peek()); myStack.Pop(); Console.WriteLine("當(dāng)前棧的長度是:" + myStack.Length); } //所有出棧結(jié)束,再查看棧頂元素拋異常 try { myStack.Peek(); } catch (InvalidOperationException ex) { Console.WriteLine(ex.Message); } //所有出棧結(jié)束,再出棧拋異常 try { myStack.Pop(); } catch (InvalidOperationException ex) { Console.WriteLine(ex.Message); } Console.ReadKey(); }
其實(shí),泛型Stack<T>的內(nèi)部也是維護(hù)著一個(gè)數(shù)組,數(shù)組的容量是動態(tài)變化的,這一點(diǎn)很像List<T>,就像這里提到的。
使用泛型Stack<T>實(shí)現(xiàn)"撤銷/重做"操作
首先,操作或撤銷操作是針對某種類型的撤銷或重做,提煉出一個(gè)接口。
public interface ICommand<T> { T Do(T input); T Undo(T input); }
假設(shè),這里想實(shí)現(xiàn)對整型數(shù)的"撤銷/重做"操作。
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; } //執(zhí)行操作 public int Do(int input) { return input + _value; } //撤銷操作 public int Undo(int input) { return input - _value; } }
接下來,需要一個(gè)泛型類來管理所有撤銷或操作命令,把這些命令放在Stack<ICommand<T>>泛型集合中。
//使用泛型Stack實(shí)現(xiàn)撤銷或重做 public class UndoRedoStack<T> { private Stack<ICommand<T>> _undo;//有關(guān)撤銷的泛型stack private Stack<ICommand<T>> _redo;//有關(guān)重做的泛型stack public UndoRedoStack() { Reset(); } //記錄撤銷的數(shù)量 public int UndoCount { get { return _undo.Count; } } //記錄重做的數(shù)量 public int RedoCount { get { return _redo.Count; } } //恢復(fù)到出廠設(shè)置 public void Reset() { _undo = new Stack<ICommand<T>>(); _redo = new Stack<ICommand<T>>(); } //執(zhí)行操作 public T Do(ICommand<T> cmd, T input) { T output = cmd.Do(input); //把剛才的命令放入有關(guān)撤銷的stack中 _undo.Push(cmd); //一旦啟動一個(gè)新命令,有關(guān)重做的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; } } }
最后,在客戶端按如下調(diào)用:
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("第一次計(jì)算的值為:{0}",count); //執(zhí)行撤銷操作一次 count = intCalulator.Undo(count); Console.WriteLine("第二次計(jì)算的值為:{0}",count); Console.ReadKey(); }
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請查看下面相關(guān)鏈接
相關(guān)文章
C#中Byte[]和String之間轉(zhuǎn)換的方法
很多朋友不清楚如何在Byte[]和String之間進(jìn)行轉(zhuǎn)換?下面小編給大家?guī)砹薭yte與string轉(zhuǎn)換的方法,感興趣的朋友參考下吧2016-08-08c#獲取季度時(shí)間實(shí)例代碼(季度的第一天)
這篇文章主要介紹了c#獲取季度時(shí)間:季度的第一天、季度的最后一天等功能,大家參考使用吧2013-12-12DataGridView凍結(jié)列或行、列順序調(diào)整、操作行頭列頭標(biāo)題的方法
這篇文章介紹了DataGridView凍結(jié)列或行、列順序調(diào)整、操作行頭列頭標(biāo)題的方法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-02-02Unity編輯器選擇器工具類Selection常用函數(shù)示例詳解
這篇文章主要為大家介紹了Unity編輯器選擇器工具類Selection常用函數(shù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08C#基于UDP實(shí)現(xiàn)的P2P語音聊天工具
這篇文章主要是一個(gè)應(yīng)用,使用udp傳送語音和文本等信息。在這個(gè)系統(tǒng)中沒有服務(wù)端和客戶端,相互通訊都是直接相互聯(lián)系的,能夠很好的實(shí)現(xiàn)效果2015-09-09C#實(shí)現(xiàn)批量Word轉(zhuǎn)換Html的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用C#批量Word轉(zhuǎn)換Html的功能,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)C#有一定的幫助,感興趣的小伙伴可以跟隨小編一起了解一下2022-12-12