我最近一直在实现递归目录搜索实现,我正在使用Stack来跟踪路径元素。当我使用string.Join()加入路径元素时,我发现它们被颠倒了。当我调试方法时,我查看了堆栈,发现元素本身在Stack的内部数组中是相反的,即最近的Push()ed元素位于内部数组的开头,而最近最近的Push() ed元素位于内部数组的末尾。这似乎是落后的,非常反直觉。有人可以告诉我为什么微软会以这种方式实现堆栈吗?
答案 0 :(得分:35)
我认为你错了。
Stack<T>.Push
内部不在内部数组的开头插入一个项目(它没有)。相反,它从顶部到底部进行枚举,因为这是一种直观地通过堆栈进行枚举的方式(想想一堆煎饼:你从顶部开始向下工作)。
如果从Visual Studio的调试器中查看集合的内容,我认为它将按照它们枚举的顺序显示给你 - 而不是它们在内部存储的顺序*。
查看Reflector中的Stack<T>.Push
方法,您会发现代码基本上与您期望的完全相同:
// (code to check array size)
this._array[this._size++] = item;
// (code to update internal version number)
因此,堆栈在内部将新元素添加到其内部数组的末尾。这是让你感到困惑的Stack<T>.Enumerator
类,而不是Stack<T>
类本身。
*我不知道这一般是否属实,但Stack<T>
是正确的。请参阅Hans Passant's excellent answer了解原因。
答案 1 :(得分:17)
你让我去那里一点,确实看起来完全低音。然而,还有其他事情发生了。堆栈&lt;&gt; class有一个名为System_StackDebugView&lt;&gt;的调试器可视化工具。这是一个内部类,您必须使用Reflector查看它。
该可视化工具具有Items属性,这是您在调试器中展开节点时所看到的内容。 Items属性使用Stack&lt;&gt; .ToArray()。看起来像这样:
public T[] ToArray()
{
T[] localArray = new T[this._size];
for (int i = 0; i < this._size; i++)
{
localArray[i] = this._array[(this._size - i) - 1];
}
return localArray;
}
是的,倒退。
答案 2 :(得分:11)
您所描述的是正确的实现,因为堆栈是LIFO(后进先出)结构。想象它就像一堆盘子,最近放入堆叠的元素是第一个被移除的元素。你有没有遇到过FIFO的堆栈?
FIFO将是一个队列。
答案 3 :(得分:2)
以下是堆栈的push和pops方法的实现方式。请注意,它使用的是数组中的最后一个索引,而不是第一个索引。因此,必须有其他一些问题让你倒退。
public virtual void Push(object obj)
{
if (this._size == this._array.Length)
{
object[] destinationArray = new object[2 * this._array.Length];
Array.Copy(this._array, 0, destinationArray, 0, this._size);
this._array = destinationArray;
}
this._array[this._size++] = obj;
this._version++;
}
public virtual object Pop()
{
if (this._size == 0)
{
throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EmptyStack"));
}
this._version++;
object obj2 = this._array[--this._size];
this._array[this._size] = null;
return obj2;
}
答案 4 :(得分:1)
要添加到其他答案,如果在调试器中向下滚动到Stack&lt;&gt;元素的底部并打开Raw View-&gt;非公开成员 - &gt; _array您可以看到内容用于保存项目的实际内部数组,并验证它们是否按预期顺序排列。
答案 5 :(得分:0)
我不知道在他们认为堆栈顶端的哪个方面很重要,只要你现在知道它到底是哪一端。实际上更有意义的是,当你将某些东西推到堆叠上时,你将它推到顶部(开始)并将其他物品向下移动......