我正在阅读“编程访谈暴露”一书中的以下段落,参考C中链表列表的实现:
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);
有人可以解释为什么我们需要在这里使用双指针?我对提供的解释有点不确定。
答案 0 :(得分:3)
它类似于C中着名的swap()函数。
案例1:
void swapFails(int x, int y) {
int temp = x;
x = y;
y = temp;
}
案例2:
void swapOk(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}
我们调用这样的交换:
int x = 10;
int y = 20;
案例1:
swapFails(x, y);
案例2:
swapOk(&x, &y);
请记住,我们想要更改x和y的值。对于CHANGING数据类型的值,我们需要指针。把它提升到指标的下一个级别。对于CHANGING指针的值,我们需要双指针。
对于使用链表的堆栈: 如果您按下值10,20和30,它们将按如下方式存储:
top --- bottom
30 -> 20 -> 10
因此,每当您从堆栈中推送或弹出值(链接列表)时,您都会看到链接列表的顶部或第一个节点发生更改。因此你需要双指针。
答案 1 :(得分:1)
第一个版本发送copy
指针,如果在函数内部发生了更改,那么当函数返回调用者<时,本地副本只会被更改/ em>调用者仍然有一个指向旧地址的指针。
Element *stack =...
push (stack)
void push( Element *stack, void *data ) {
stack = ... // this changes the local pointer allocated on the function's stack
}
//call returns
stack //still points to old memory
然而,第二个版本传递一个指向堆栈指针的指针,所以当它改变时,它会改变调用函数中的堆栈指针。
答案 2 :(得分:1)
在C中,一切都按值传递,假设我有这个函数:
void foo(void* ptr)
{
ptr=NULL;
}
如果在main中调用此方法,则传递的指针将不为NULL(除非它已经为NULL)。因为在将指针传递给函数之前会生成指针的副本。如果要修改这是值,你必须传递一个双指针:
void foo(void** ptr)
{
*ptr=NULL;
}
同样适用于要修改其值的堆栈。
答案 3 :(得分:1)
使用单指针签名,您可以使用以下代码设想
Element *myStack = NULL ;
.... bla bla bla ....
push(myStack, something);
然后在push
调用中,您告诉堆栈实现堆栈的旧头元素在哪里,但是push
的实现无法实现告诉你 new 头部在哪里。它无法更改myStack
变量,因为C中的参数传递总是按值传递 - 即,push
函数会告诉myStack
的{em>值发生了什么是,但没有机会改变调用者的变量。
为了使事情有效,你需要告诉push原语你需要改变的局部变量的地址:
....
push(&myStack, something);
由于myStack
本身的类型为Element *
,因此指向 myStack
变量的指针的类型为Element **
。