我最近正在读一本书,这是在链接列表中实现堆栈的解释。以下是解释:
typedef struct Element {
struct Element *next;
void *data;
} Element;
推送和弹出跟随的相应原型:
void push( Element *stack, void *data );
void *pop( Element *stack );
现在考虑在这些例程中正确的功能和错误处理方面会发生什么。 这两个操作都会更改列表的第一个元素。必须修改调用例程的堆栈指针以反映此更改,但是对传递给这些函数的指针所做的任何更改都不会传播回调用例程。您可以通过让两个例程都指向指向堆栈的指针来解决此问题。这样,您可以更改调用例程的指针,使其继续指向列表的第一个元素。实施此更改会产生以下结果:
void push( Element **stack, void *data );
void *pop( Element **stack );
然而,我想知道的是,需要为堆栈添加双指针是什么?我理解双指针的概念,但是,当使用创建新节点时
Element *node1 = (Element *) malloc (sizeof(Element));
,我们已经有了指向节点的指针。为什么不直接发送这个指针而不是使用双指针?
答案 0 :(得分:0)
因为您没有独立的Stack对象,所以每次更改堆栈中的顶级节点时,对该堆栈的主要引用都需要针对使用它的所有内容进行更改。这就是需要双指针的原因 - 因为堆栈的旧顶部不再有效。
如果你创建了一个顶级堆栈对象,它有一个指向Element的指针,也许还有一些其他数据,比如元素数,那么你就可以将其作为单个指针发送到push / pop中,因为该对象/ struct将保持不变,只有其中的数据会发生变化。
// pseudocode
struct Stack {
Element * top = NULL;
int size = 0;
}
struct Element {
Element * next;
void * data;
}
function push(Stack * stack, void * data) {
Element * element = new Element(data);
element->next = stack->top;
stack->top = element;
size++;
}
// pop left as an exercise to the reader.. :)
int main(void*) {
Stack * stack = new Stack;
push(stack, "foo"); <== no need for double pointer
pop(stack);
printf("%d\n", stack->size);
}
答案 1 :(得分:0)
由于pointers
和passing by reference
概念的混淆,这有点令人困惑。
让我们看一下原来的push
函数。 stack
参数表示指向堆栈开头的指针。该指针由调用者提供。如果需要修改堆栈的开头,push
函数将无法执行此操作,因为它是通过值传递的,即在函数体内部它是一个副本原版的。同样适用于pop
。
用一个例子来理解它是最简单的。假设堆栈的开头位于地址0x1234
。调用push
后,调用方会将值 0x1234
传递给它。 push
可以使用此值执行任何操作,但它不会更改原始指针的数据,并且更改不会在调用者上下文中反映出来。
为什么不发送您在node1
push
内创建的Element *node1 = (Element *) malloc (sizeof(Element));
指针?这正是你应该做的,但你还需要双指针:
void push( Element **stack, void *data )
{
Element *node1 = (Element *) malloc (sizeof(Element));
node1->data = data;
node1->next = *stack;
*stack = node1;
}
如果您尝试在没有双指针的情况下实现此目的,则调用者将看不到更改并将保留旧堆栈指针:
void push( Element *stack, void *data )
{
Element *node1 = (Element *) malloc (sizeof(Element));
node1->data = data;
node1->next = stack;
stack = node1; //The data structure was updated but caller won't see it!
}