大家好,对C# 详解泛型容器Stack
.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的泛型实现,提供了若干方法和属性,比如入栈、出栈、查看栈顶元素,查看栈内集合元素数量,等等。栈的最大特点是先进后出,可以把栈想像成一堆叠起来的盘子,入栈就是把一个个盘子放到最上面,出栈就是从最上面把盘子拿掉。用法比较简单:
/**
* 泛型容器Stack<T>的用法
* @arrange (三零.脚本) www.q3060.com
**/
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类内部维护这一个泛型数组和索引指针,且指针的初始位置是-1。
入栈就是把指针往前提一位,并把入栈元素赋值给该栈内位置。另外,入栈要考虑是否达到容量上限,如果达到就要给数组扩容。
出栈就是让当前栈位置的元素值为入栈类型的默认值,并大指针后退一位。
获取栈顶元素就是获取栈当前索引位置对应的元素。
/**
* 泛型容器Stack<T>的用法
* @arrange (三零.脚本) www.q3060.com
**/
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;
}
}
现在,在客户端,实施一系列的入栈和出栈操作。
/**
* 泛型容器Stack<T>的用法
* @arrange (三零.脚本) www.q3060.com
**/
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
//使用泛型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();
}