位置:首页 > 软件编程 > C#笔记
C# 详解泛型容器Stack<T>的用法,并实现”撤销/重做”功能
日期:2023-01-04 人气:

大家好,对C# 详解泛型容器Stack的用法,并实现”撤销/重做”功能感兴趣的小伙伴,下面一起跟随三零脚本的小编来看看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<T> 

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