在链表中添加节点时使用双指针的原因是什么?

时间:2022-11-03 07:17:59

The two code examples below both add a node at the top of a linked list. But whereas the first code example uses a double pointer the second code example uses a single pointer

下面的两个代码示例都在链表的顶部添加了一个节点。但是第一个代码示例使用双指针,第二个代码示例使用单指针

code example 1:

代码示例1:

struct node* push(struct node **head, int data)
{
        struct node* newnode = malloc(sizeof(struct node));
        newnode->data = data;
        newnode->next = *head;
        return newnode;
}

push(&head,1);

code example 2:

代码示例2:

struct node* push(struct node *head, int data)
{
        struct node* newnode = malloc(sizeof(struct node));
        newnode->data = data;
        newnode->next = head;
        return newnode;
}

push(head,1)

Both strategies work. However, a lot of programs that use a linked list use a double pointer to add a new node. I know what a double pointer is. But if a single pointer would be sufficient to add a new node why do a lot of implementations rely on double pointers?

这两种策略的工作。然而,许多使用链表的程序使用双指针来添加新节点。我知道双指针是什么。但是如果一个指针就足以添加一个新的节点,为什么很多实现都依赖双指针呢?

Is there any case in which a single pointer does not work so we need to go for a double pointer?

有没有一种情况下单指针不工作,所以我们需要双指针?

13 个解决方案

#1


54  

Some implementations pass a pointer to pointer parameter to allow changing the head pointer directly instead of returning the new one. Thus you could write:

一些实现将指针传递到指针参数,以允许直接更改头指针,而不是返回新的指针。因此你可以写:

// note that there's no return value: it's not needed
void push(struct node** head, int data)
{
    struct node* newnode = malloc(sizeof(struct node));
    newnode->data=data;
    newnode->next=*head;
    *head = newnode; // *head stores the newnode in the head
}

// and call like this:
push(&head,1);

The implementation that doesn't take a pointer to the head pointer must return the new head, and the caller is responsible for updating it itself:

不接受指向头部指针的指针的实现必须返回新的头部,调用者负责更新它本身:

struct node* push(struct node* head, int data)
{
    struct node* newnode = malloc(sizeof(struct node));
    newnode->data=data;
    newnode->next=head;
    return newnode;
}

// note the assignment of the result to the head pointer
head = push(head,1);

If you don't do this assignment when calling this function, you will be leaking the nodes you allocate with malloc, and the head pointer will always point to the same node.

如果在调用这个函数时不执行这个赋值,就会泄漏使用malloc分配的节点,并且头指针总是指向同一个节点。

The advantage should be clear now: with the second, if the caller forgets to assign the returned node to the head pointer, bad things will happen.

优点现在应该很清楚了:对于第二个,如果调用者忘记将返回的节点分配给头部指针,就会发生糟糕的事情。

#2


4  

In your particular example there is no need for the double pointer. However it can be needed, if, for example, you were to do something like this:

在您的特定示例中,不需要双指针。然而,如果你要做这样的事情,你就需要这样做:

struct node* push(struct node** head, int data)
{
struct node* newnode = malloc(sizeof(struct node));
newnode->data=data;
newnode->next=*head;
//vvvvvvvvvvvvvvvv
*head = newnode; //you say that now the new node is the head.
//^^^^^^^^^^^^^^^^
return newnode;
}

#3


1  

Let's take this simple eg:

就这么简单吧。

void my_func(int *p) {
        // allocate space for an int
        int *z = (int *) malloc(sizeof(int));
        // assign a value
        *z = 99;

        printf("my_func - value of z: %d\n", *z);

        printf("my_func - value of p: %p\n", p);
        // change the value of the pointer p. Now it is not pointing to h anymore
        p = z;
        printf("my_func - make p point to z\n");
        printf("my_func - addr of z %p\n", &*z);
        printf("my_func - value of p %p\n", p);
        printf("my_func - value of what p points to: %d\n", *p);
        free(z);
}

int main(int argc, char *argv[])
{
        // our var
        int z = 10;

        int *h = &z;

        // print value of z
        printf("main - value of z: %d\n", z);
        // print address of val
        printf("main - addr of z: %p\n", &z);

        // print value of h.
        printf("main - value of h: %p\n", h);

        // print value of what h points to
        printf("main - value of what h points to: %d\n", *h);
        // change the value of var z by dereferencing h
        *h = 22;
        // print value of val
        printf("main - value of z: %d\n", z);
        // print value of what h points to
        printf("main - value of what h points to: %d\n", *h);


        my_func(h);

        // print value of what h points to
        printf("main - value of what h points to: %d\n", *h);

        // print value of h
        printf("main - value of h: %p\n", h);


        return 0;
}

Output:

输出:

main - value of z: 10
main - addr of z: 0x7ffccf75ca64
main - value of h: 0x7ffccf75ca64
main - value of what h points to: 10
main - value of z: 22
main - value of what h points to: 22
my_func - value of z: 99
my_func - value of p: 0x7ffccf75ca64
my_func - make p point to z
my_func - addr of z 0x1906420
my_func - value of p 0x1906420
my_func - value of what p points to: 99
main - value of what h points to: 22
main - value of h: 0x7ffccf75ca64

we have this signature for my_func:

我们有my_func的签名:

void my_func(int *p);

If you look at the output, in th end, the value that h points to is still 22 and the value of h is the same, altough in my_func it was changed. How come ?

如果你看输出,在最后,h点的值仍然是22 h的值是相同的,在my_func中,它被改变了。如何来吗?

Well, in my_func we are manipulating the value of p, which is just a local pointer. after calling:

在my_func中,我们在操作p的值,它只是一个本地指针。后调用:

my_func(ht);

in main(), p will hold the value that h holds, which represents the address of z variable, declared in main function.

在main()中,p将持有h持有的值,h表示在main函数中声明的z变量的地址。

In my_func(), when we are changing the value of p to hold the value of z, which is a pointer to a location in memory, for which we have allocated space, we are not changing the value of h, that we've passed in, but just the value of local pointer p. Basically, p does not hold the value of h anymore, it will hold the address of a memory location, that z points to.

my_func(),当我们改变p的价值持有z的值,这是一个指针指向内存中的一个位置,我们有分配空间,我们不改变h的价值,我们已经通过,但是当地的价值指针p。基本上,p不持有h的价值了,它将一个内存位置的地址,z点。

Now, if we change our example a little bit:

现在,如果我们稍微改变一下例子:

#include <stdio.h>
#include <stdlib.h>

void my_func(int **p) {
    // allocate space for an int
    int *z = (int *) malloc(sizeof(int));
    // assign a value
    *z = 99;

    printf("my_func - value of z: %d\n", *z);

    printf("my_func - value of p: %p\n", p);
    printf("my_func - value of h: %p\n", *p);
    // change the value of the pointer p. Now it is not pointing to h anymore
    *p = z;
    printf("my_func - make p point to z\n");
    printf("my_func - addr of z %p\n", &*z);
    printf("my_func - value of p %p\n", p);
    printf("my_func - value of h %p\n", *p);
    printf("my_func - value of what p points to: %d\n", **p);
    // we are not deallocating, because we want to keep the value in that
    // memory location, in order for h to access it.
    /* free(z); */
}

int main(int argc, char *argv[])
{
    // our var
    int z = 10;

    int *h = &z;

    // print value of z
    printf("main - value of z: %d\n", z);
    // print address of val
    printf("main - addr of z: %p\n", &z);

    // print value of h.
    printf("main - value of h: %p\n", h);

    // print value of what h points to
    printf("main - value of what h points to: %d\n", *h);
    // change the value of var z by dereferencing h
    *h = 22;
    // print value of val
    printf("main - value of z: %d\n", z);
    // print value of what h points to
    printf("main - value of what h points to: %d\n", *h);


    my_func(&h);

    // print value of what h points to
    printf("main - value of what h points to: %d\n", *h);

    // print value of h
    printf("main - value of h: %p\n", h);
    free(h);


    return 0;
}

we have the follwoing output:

我们有以下输出:

main - value of z: 10
main - addr of z: 0x7ffcb94fb1cc
main - value of h: 0x7ffcb94fb1cc
main - value of what h points to: 10
main - value of z: 22
main - value of what h points to: 22
my_func - value of z: 99
my_func - value of p: 0x7ffcb94fb1c0
my_func - value of h: 0x7ffcb94fb1cc
my_func - make p point to z
my_func - addr of z 0xc3b420
my_func - value of p 0x7ffcb94fb1c0
my_func - value of h 0xc3b420
my_func - value of what p points to: 99
main - value of what h points to: 99
main - value of h: 0xc3b420

Now, we actually have changed the value which h holds, from my_func, by doing this:

现在,我们已经改变了h在my_func中的值,通过这样做:

  1. changed function signature
  2. 改变函数签名
  3. calling from main(): my_func(&h); Basically we are passing the address of h pointer to double pointer p, declared as a parameter in function's signature.
  4. 调用从主():my_func(h);基本上我们将h指针的地址传递给双指针p,在函数的签名中声明为参数。
  5. in my_func() we are doing: *p = z; we are dereferencing the double pointer p, one level. Basically this got translated as you would do: h = z;
  6. 在my_func()中,我们正在做:*p = z;我们取消了双指针p,一级。基本上这个可以翻译成h = z;

The value of p, now holds the address of h pointer. h pointer holds the address of z.

p的值,现在包含h指针的地址。h指针包含z的地址。

You can take both examples and diff them. So, getting back to your question, you need double pointer in order to make modifications to the pointer that you've passed in straight from that function.

你可以同时举两个例子,然后对它们进行修改。回到你的问题,你需要双指针来修改你直接从函数中传入的指针。

#4


1  

Although the previous answers are good enough, I think it's much easier to think in terms of "copy by value".

虽然前面的答案已经足够好了,但我认为用“按价值复制”来思考要容易得多。

When you pass in a pointer to a function, the address value is being copied over to the function parameter. Due to the function's scope, that copy will vanish once it returns.

当您传入指向函数的指针时,地址值将被复制到函数参数。由于函数的作用域,当它返回时,该副本将消失。

By using a double pointer, you will be able to update the original pointer's value. The double pointer will still be copied by value, but that doesn't matter. All you really care is modifying the original pointer, thereby bypassing the function's scope or stack.

通过使用双指针,您将能够更新原始指针的值。双指针仍然会被值复制,但这并不重要。您真正关心的是修改原始指针,从而绕过函数的范围或堆栈。

Hope this answers not just your question, but other pointer related questions as well.

希望这不仅能回答你的问题,还能回答其他与指针相关的问题。

#5


0  

The answer is more obvious if you take the time to write a working node insertion function; yours isn't one.

如果您花时间编写一个工作节点插入函数,那么答案将更加明显;你不是一个。

You need to be able to write over the head to move it forward, so you need a pointer to the pointer to the head so you can dereference it to get the pointer to the head and change it.

你需要能够把它写在头部上才能将它向前移动,所以你需要一个指向头部的指针这样你就可以去引用它来获得指向头部的指针并改变它。

#6


0  

Imagine a case where you have to make certain changes and those changes should reflect back in the calling function.

假设您必须进行某些更改,这些更改应该在调用函数中反映出来。

Example:

例子:

void swap(int* a,int* b){
  int tmp=*a;
  *a=*b;
  *b=tmp;
}

int main(void){
  int a=10,b=20;

  // To ascertain that changes made in swap reflect back here we pass the memory address
  // instead of the copy of the values

  swap(&a,&b);
}

Similarly we pass the Memory Address of the Head of the List.

同样,我们传递列表头的内存地址。

This way, if any node is added and the Value of Head is Changed, then that change Reflects Back and we don't have to manually reset the Head inside of the calling function.

这样,如果添加了任何节点,并且更改了Head的值,那么这个更改就会反射回来,我们不必手动重置调用函数内部的Head。

Thus this approach reduces the chances of Memory Leaks as we would have lost the pointer to the newly allocated node, had we forgot to update the Head back in the calling function.

因此,这种方法减少了内存泄漏的可能性,因为我们丢失了指向新分配的节点的指针,我们忘记了在调用函数中更新头部。

Beside this, the second code will Work Faster since no time is wasted in copying and returning since we work directly with the memory.

此外,由于直接使用内存,所以在复制和返回时不会浪费时间,因此第二个代码将更快地工作。

#7


0  

Observation and Finding, WHY...

观察和发现,为什么……

I decided to do some experiments and make some conclusion,

我决定做一些实验并得出一些结论,

OBSERVATION 1- If the linked list is not empty then we can add the nodes in it (obviously at the end) by using a single pointer only.

观察1-如果链表不是空的,那么我们可以只使用一个指针来添加链表中的节点(显然在末尾)。

int insert(struct LinkedList *root, int item){
    struct LinkedList *temp = (struct LinkedList*)malloc(sizeof(struct LinkedList));
    temp->data=item;
    temp->next=NULL;
    struct LinkedList *p = root;
    while(p->next!=NULL){
        p=p->next;
    }
    p->next=temp;
    return 0;
}


int main(){
    int m;
    struct LinkedList *A=(struct LinkedList*)malloc(sizeof(struct LinkedList));
    //now we want to add one element to the list so that the list becomes non-empty
    A->data=5;
    A->next=NULL;
    cout<<"enter the element to be inserted\n"; cin>>m;
    insert(A,m);
    return 0;
}

Its simple to explain (Basic). We have a pointer in our main function which points to the first node (root) of the list. In the insert() function we pass the address of the root node and using this address we reach the end of the list and add a node to it. So we can conclude that if we have address of a variable in a function (not the main function) we can make permanent changes in the value of that variable from that function which would reflect in the main function.

这很容易解释(基本)。我们的主函数中有一个指针,指向列表的第一个节点(根)。在insert()函数中,我们传递根节点的地址,并使用这个地址到达列表的末尾并向其添加一个节点。因此,我们可以得出结论,如果我们在函数中有一个变量(而不是主函数)的地址,我们就可以从这个函数中对该变量的值进行永久性的改变,而这个函数将反映在主函数中。

OBSERVATION 2- The above method of adding node failed when the list was empty.

观察2-上述添加节点的方法在列表为空时失败。

int insert(struct LinkedList *root, int item){
    struct LinkedList *temp = (struct LinkedList*)malloc(sizeof(struct LinkedList));
    temp->data=item;
    temp->next=NULL;
    struct LinkedList *p=root;   
    if(p==NULL){
        p=temp;
    }
    else{
      while(p->next!=NULL){
          p=p->next;
      }
      p->next=temp;
    }
    return 0;
}



int main(){
    int m;
    struct LinkedList *A=NULL; //initialise the list to be empty
    cout<<"enter the element to be inserted\n";
    cin>>m;
    insert(A,m);
    return 0;
}

If you keep on adding elements and finally display the list then you would find that the list has undergone no changes and still it is empty. The question which struck my mind was in this case also we are passing the address of the root node then why modifications are not happening as permanent modifications and list in the main function undergoes no changes. WHY? WHY? WHY?

如果您继续添加元素并最终显示列表,那么您将发现列表没有进行任何更改,但仍然是空的。我想到的问题是,在这种情况下,我们也传递根节点的地址,那么为什么修改没有作为永久修改发生,而在主函数中列表没有发生更改。为什么?为什么?为什么?

Then I observed one thing, when I write A=NULL the address of A becomes 0. This means now A is not pointing to any location in memory. So I removed the line A=NULL; and made some modification in the insert function.

然后我观察到一件事,当我写A=NULL时,A的地址就变成了0。这意味着现在A没有指向内存中的任何位置。我去掉了A=NULL;并对插入函数做了一些修改。

some modifications,(below insert() function can add only one element to an empty list, just wrote this function for testing purpose)

一些修改(在insert()函数下面)只能向空列表中添加一个元素,只是为了测试目的而编写这个函数)

int insert(struct LinkedList *root, int item){
    root= (struct LinkedList *)malloc(sizeof(struct LinkedList));
    root->data=item;
    root->next=NULL;
    return 0;
}



int main(){
    int m;
    struct LinkedList *A;    
    cout<<"enter the element to be inserted\n";
    cin>>m;
    insert(A,m);
    return 0;
}

the above method also fails because in the insert() function root stores same address as A in the main() function but after the line root= (struct LinkedList *)malloc(sizeof(struct LinkedList)); the address stored in root changes. Thus now , root (in insert() function) and A (in main() function) store different addresses.

上述方法也失败了,因为在insert()函数的根中,与main()函数中的A存储相同的地址,但是在line root= (struct LinkedList *)malloc(sizeof(struct LinkedList)之后;存储在根中的地址会发生变化。因此,现在根(in insert()函数)和主(in main()函数)存储不同的地址。

So the correct final program would be,

所以最终的程序是,

int insert(struct LinkedList *root, int item){
    root->data=item;
    root->next=NULL;
    return 0;
}



int main(){
    int m;
    struct LinkedList *A = (struct LinkedList *)malloc(sizeof(struct LinkedList));
    cout<<"enter the element to be inserted\n";
    cin>>m;
    insert(A,m);
    return 0;
}

But we dont want two different functions for insertion, one when list is empty and other when list is not empty. Now comes double pointer which makes things easy.

但是我们不希望在插入时使用两个不同的函数,一个是列表为空,另一个是列表为空。现在来了双指针,让事情变得简单。

One thing I noticed which is important is that pointers store address and when used with '*' they give value at that address but pointers themselves have their own address.

我注意到的一件重要的事情是指针存储地址,当与'*'一起使用时,它们在那个地址上给出值,但是指针本身有它们自己的地址。

Now here is the complete program and later explain the concepts.

现在这是完整的程序,稍后将解释这些概念。

int insert(struct LinkedList **root,int item){
    if(*root==NULL){
        (*root)=(struct LinkedList *)malloc(sizeof(struct LinkedList));
        (*root)->data=item;
        (*root)->next=NULL;
    }
    else{
        struct LinkedList *temp=(struct LinkedList *)malloc(sizeof(struct LinkedList));
        temp->data=item;
        temp->next=NULL;
        struct LinkedList *p;
        p=*root;
        while(p->next!=NULL){
            p=p->next;
        }
        p->next=temp;
    }
    return 0;
}


int main(){
    int n,m;
    struct LinkedList *A=NULL;
    cout<<"enter the no of elements to be inserted\n";
    cin>>n;
    while(n--){
        cin>>m;
        insert(&A,m);
    }
    display(A);
    return 0;
}

following are the observations,

以下的观察,

1. root stores the address of pointer A (&A) , *root stores the address stored by pointer A and **root stores the value at address stored by A. In simple language root=&A, *root= A and **root= *A.

1。根存储指针A (&A)的地址,*根存储指针A存储的地址,** *根存储在A存储的地址。

2. if we write *root= 1528 then it means that value at address stored in root becomes 1528 and since address stored in root is the address of pointer A (&A) thus now A=1528 (i.e. address stored in A is 1528) and this change is permanent.

2。如果我们写*root= 1528,那么它意味着存储在根中的地址的值变成了1528,因为存储在根中的地址是指针A(和)的地址,所以现在A=1528(也就是说,存储在A中的地址是1528),这个变化是永久性的。

whenever we are changing value of *root we are indeed changing value at address stored in root and since root=&A ( address of pointer A) we are indirectly changing value of A or address stored in A.

当我们改变*root的值时,我们实际上是在改变存储在根中的地址的值,并且由于root=&A(指针A的地址)我们间接地改变了存储在A中的A或地址的值。

so now if A=NULL (list is empty) *root=NULL , thus we create the first node and store its address at *root i.e. indirectly we storing the address of first node at A. If list is not empty , everything is same as done in previous functions using single pointer except we have changed root to *root since what was stored in root is now stored in *root.

现在如果一个= NULL(列表为空)*根=零,因此我们创建第一个节点和存储地址*根即间接我们存储第一个节点的地址如果列表不是空的,一切都是一样做在前面的函数使用单个指针除了我们已经改变了根*根因为现在存储在根是存储在根。

#8


0  

When we pass pointer as a parameter in a function and want update in the same pointer we use double pointer.

当我们在函数中把指针作为参数传递并希望在同一个指针中更新时,我们使用双指针。

On the other hand if we pass pointer as a parameter in a function and catch it in single pointer then will have to return the result to calling function back in order to use the result.

另一方面,如果我们将指针作为参数传递给函数,并将其捕获到单个指针中,那么为了使用结果,我们必须返回调用函数的结果。

#9


0  

Think of memory location for head like [HEAD_DATA].

像[HEAD_DATA]一样考虑head的内存位置。

Now in your second scenario, the calling function's main_head is the pointer to this location.

在第二个场景中,调用函数的main_head是指向这个位置的指针。

main_head--->[HEAD_DATA]

main_head - - - >[HEAD_DATA]

In your code, it sent the value of the pointer main_head to the function(i.e the address of the memory location of head_data) You copied that to local_head in the function. so now

在您的代码中,它将指针main_head的值发送到函数(i)。在函数中复制到local_head的内存位置的地址。所以现在

local_head---> [HEAD_DATA]

local_head - - - >[HEAD_DATA]

and

main_head---> [HEAD_DATA]

main_head - - - >[HEAD_DATA]

Both point to the same location but are essentially independent of each other. So when you write local_head = newnode; what you did is

两者都指向同一个位置,但本质上是相互独立的。写local_head = newnode;你做的是什么

local_head--/-->[HEAD_DATA]

local_head - / - >[HEAD_DATA]

local_head-----> [NEWNODE_DATA]

local_head - - - - - - >[NEWNODE_DATA]

You simply replaced the memory address of previous memory with new one in local pointer. The main_head (pointer) still points to the old [HEAD_DATA]

您只需在本地指针中使用新的内存地址替换先前内存的内存地址。main_head(指针)仍然指向旧的[HEAD_DATA]

#10


0  

I think the point is that it makes it easier to update nodes within a linked list. Where you would normally have to keep track of a pointer for previous and current you can have a double pointer take care of it all.

我认为关键在于它使得更新链表中的节点变得更加容易。通常情况下,你需要跟踪一个指针的过去和现在,你可以有一个双指针来处理它。

#include <iostream>
#include <math.h>

using namespace std;

class LL
{
    private:
        struct node 
        {
            int value;
            node* next;
            node(int v_) :value(v_), next(nullptr) {};
        };
        node* head;

    public:
        LL() 
        {
            head = nullptr;
        }
        void print() 
        {
            node* temp = head;
            while (temp) 
            {
                cout << temp->value << " ";
                temp = temp->next;
            }
        }
        void insert_sorted_order(int v_) 
        {
            if (!head)
                head = new node(v_);
            else
            {
                node* insert = new node(v_);
                node** temp = &head;
                while ((*temp) && insert->value > (*temp)->value)
                    temp = &(*temp)->next;
                insert->next = (*temp);
                (*temp) = insert;
            }
        }

        void remove(int v_)
        {
            node** temp = &head;
            while ((*temp)->value != v_)
                temp = &(*temp)->next;
            node* d = (*temp);
            (*temp) = (*temp)->next;
            delete d;
        }

        void insertRear(int v_)//single pointer
        {
            if (!head)
                head = new node(v_);
            else
            {
                node* temp = new node(v_);
                temp->next = head;
                head = temp;
            }
        }
};

#11


0  

As @R. Martinho Fernandes pointed out in his answer, using pointer to pointer as an argument in void push(struct node** head, int data) allows you to change the head pointer directly from within push function instead of returning the new pointer.

@R。Martinho Fernandes在他的回答中指出,在void push(struct node* head, int data)中使用指针作为参数,允许您直接从push函数中更改头指针,而不是返回新的指针。

There is yet another good example which shows why using pointer to pointer instead a single pointer may shorten, simplify and speed up your code. You asked about adding a new node to the list which probably typically doesn't need pointer-to-pointer in contrast to removing the node from the singly-linked list. You can implement removing node from the list without pointer-to-pointer but it is suboptimal. I described the details here. I recommend you also to watch this YouTube video which addresses the problem.

还有一个很好的例子可以说明为什么使用指针来代替单个指针可以缩短、简化和加速代码。您要求将一个新节点添加到列表中,该节点通常不需要pointerto -pointer,而不是将节点从单一链接列表中删除。您可以实现不带指针指针的从列表中删除节点,但它是次优的。我在这里描述了细节。我建议你也观看这个YouTube视频,它解决了这个问题。

BTW: If you count with Linus Torvalds opinion, you would better learn how to use pointer-to-pointer. ;-)

顺便说一句:如果你同意莱纳斯·托瓦兹的观点,你最好学会如何使用指针指向指针。:-)

Linus Torvalds: (...) At the opposite end of the spectrum, I actually wish more people understood the really core low-level kind of coding. Not big, complex stuff like the lockless name lookup, but simply good use of pointers-to-pointers etc. For example, I've seen too many people who delete a singly-linked list entry by keeping track of the "prev" entry, and then to delete the entry, doing something like

Linus Torvalds:(…)相反,我希望更多的人理解底层的核心代码。不需要太多复杂的东西,比如无锁的名称查找,但只要很好地使用指针指向指针等。例如,我看到过太多的人通过跟踪“prev”条目来删除一个链表条目,然后删除条目,做一些类似的事情

if (prev)
prev->next = entry->next;
else
list_head = entry->next;

and whenever I see code like that, I just go "This person doesn't understand pointers". And it's sadly quite common.

每当我看到这样的代码,我就会说“这个人不懂指针”。可悲的是,这很常见。

People who understand pointers just use a "pointer to the entry pointer", and initialize that with the address of the list_head. And then as they traverse the list, they can remove the entry without using any conditionals, by just doing a "*pp = entry->next". (...)

理解指针的人只使用一个“指向入口指针的指针”,并使用list_head的地址初始化该指针。然后,当它们遍历列表时,可以不使用任何条件语句删除条目,只需执行“*pp =条目->next”。(…)


Other resources that may be helpful:

其他可能有帮助的资源:

#12


0  

The standard way to handle linked lists in C is to have the push and pop functions automatically update the head pointer.

在C语言中处理链表的标准方法是让push和pop函数自动更新头指针。

C is "Call by value" meaning copies of parameters are passed into functions. If you only pass in the head pointer any local update you make to that pointer will not be seen by the caller. The two workarounds are

C是“按值调用”,意思是参数的副本被传递到函数中。如果您只传入头部指针,则调用者将看不到您对该指针进行的任何本地更新。这两个解决方法

1) Pass the address of the head pointer. (Pointer to head pointer)

1)传递头部指针的地址。(指针指向头指针)

2) Return a new head pointer, and rely on the caller to update the head pointer.

2)返回一个新的头指针,并依赖调用者更新头指针。

Option 1) is the easiest even though a little confusing at first.

选项1)是最简单的,尽管一开始有点混乱。

#13


0  

Lets say I noted down your house address on a card-1. Now if I want tell your house address to somebody else, I can either copy the address from card-1 to card-2 and give card-2 OR I can give card-1 directly. Either ways the person will know the address and can reach you. But when I give card-1 directly, the address can be changed on card-1 but if I gave card-2 only the address on card-2 can be changed but not on card-1.

假设我在卡片上记下了你的住址。现在,如果我想告诉别人你的住址,我可以把地址从卡片1复制到卡片2,然后给卡片2,或者直接给卡片1。不管是哪种方式,这个人都会知道你的地址并能联系到你。但是当我直接给card-1时,地址可以在card-1上更改,但是如果我给card-2上只有card-2上的地址可以更改,而不是card-1上。

Passing a pointer to pointer is similar to giving the access to card-1 directly. Passing a pointer is similar to creating a new copy of the address.

将指针传递给指针类似于直接访问card-1。传递指针类似于创建地址的新副本。

#1


54  

Some implementations pass a pointer to pointer parameter to allow changing the head pointer directly instead of returning the new one. Thus you could write:

一些实现将指针传递到指针参数,以允许直接更改头指针,而不是返回新的指针。因此你可以写:

// note that there's no return value: it's not needed
void push(struct node** head, int data)
{
    struct node* newnode = malloc(sizeof(struct node));
    newnode->data=data;
    newnode->next=*head;
    *head = newnode; // *head stores the newnode in the head
}

// and call like this:
push(&head,1);

The implementation that doesn't take a pointer to the head pointer must return the new head, and the caller is responsible for updating it itself:

不接受指向头部指针的指针的实现必须返回新的头部,调用者负责更新它本身:

struct node* push(struct node* head, int data)
{
    struct node* newnode = malloc(sizeof(struct node));
    newnode->data=data;
    newnode->next=head;
    return newnode;
}

// note the assignment of the result to the head pointer
head = push(head,1);

If you don't do this assignment when calling this function, you will be leaking the nodes you allocate with malloc, and the head pointer will always point to the same node.

如果在调用这个函数时不执行这个赋值,就会泄漏使用malloc分配的节点,并且头指针总是指向同一个节点。

The advantage should be clear now: with the second, if the caller forgets to assign the returned node to the head pointer, bad things will happen.

优点现在应该很清楚了:对于第二个,如果调用者忘记将返回的节点分配给头部指针,就会发生糟糕的事情。

#2


4  

In your particular example there is no need for the double pointer. However it can be needed, if, for example, you were to do something like this:

在您的特定示例中,不需要双指针。然而,如果你要做这样的事情,你就需要这样做:

struct node* push(struct node** head, int data)
{
struct node* newnode = malloc(sizeof(struct node));
newnode->data=data;
newnode->next=*head;
//vvvvvvvvvvvvvvvv
*head = newnode; //you say that now the new node is the head.
//^^^^^^^^^^^^^^^^
return newnode;
}

#3


1  

Let's take this simple eg:

就这么简单吧。

void my_func(int *p) {
        // allocate space for an int
        int *z = (int *) malloc(sizeof(int));
        // assign a value
        *z = 99;

        printf("my_func - value of z: %d\n", *z);

        printf("my_func - value of p: %p\n", p);
        // change the value of the pointer p. Now it is not pointing to h anymore
        p = z;
        printf("my_func - make p point to z\n");
        printf("my_func - addr of z %p\n", &*z);
        printf("my_func - value of p %p\n", p);
        printf("my_func - value of what p points to: %d\n", *p);
        free(z);
}

int main(int argc, char *argv[])
{
        // our var
        int z = 10;

        int *h = &z;

        // print value of z
        printf("main - value of z: %d\n", z);
        // print address of val
        printf("main - addr of z: %p\n", &z);

        // print value of h.
        printf("main - value of h: %p\n", h);

        // print value of what h points to
        printf("main - value of what h points to: %d\n", *h);
        // change the value of var z by dereferencing h
        *h = 22;
        // print value of val
        printf("main - value of z: %d\n", z);
        // print value of what h points to
        printf("main - value of what h points to: %d\n", *h);


        my_func(h);

        // print value of what h points to
        printf("main - value of what h points to: %d\n", *h);

        // print value of h
        printf("main - value of h: %p\n", h);


        return 0;
}

Output:

输出:

main - value of z: 10
main - addr of z: 0x7ffccf75ca64
main - value of h: 0x7ffccf75ca64
main - value of what h points to: 10
main - value of z: 22
main - value of what h points to: 22
my_func - value of z: 99
my_func - value of p: 0x7ffccf75ca64
my_func - make p point to z
my_func - addr of z 0x1906420
my_func - value of p 0x1906420
my_func - value of what p points to: 99
main - value of what h points to: 22
main - value of h: 0x7ffccf75ca64

we have this signature for my_func:

我们有my_func的签名:

void my_func(int *p);

If you look at the output, in th end, the value that h points to is still 22 and the value of h is the same, altough in my_func it was changed. How come ?

如果你看输出,在最后,h点的值仍然是22 h的值是相同的,在my_func中,它被改变了。如何来吗?

Well, in my_func we are manipulating the value of p, which is just a local pointer. after calling:

在my_func中,我们在操作p的值,它只是一个本地指针。后调用:

my_func(ht);

in main(), p will hold the value that h holds, which represents the address of z variable, declared in main function.

在main()中,p将持有h持有的值,h表示在main函数中声明的z变量的地址。

In my_func(), when we are changing the value of p to hold the value of z, which is a pointer to a location in memory, for which we have allocated space, we are not changing the value of h, that we've passed in, but just the value of local pointer p. Basically, p does not hold the value of h anymore, it will hold the address of a memory location, that z points to.

my_func(),当我们改变p的价值持有z的值,这是一个指针指向内存中的一个位置,我们有分配空间,我们不改变h的价值,我们已经通过,但是当地的价值指针p。基本上,p不持有h的价值了,它将一个内存位置的地址,z点。

Now, if we change our example a little bit:

现在,如果我们稍微改变一下例子:

#include <stdio.h>
#include <stdlib.h>

void my_func(int **p) {
    // allocate space for an int
    int *z = (int *) malloc(sizeof(int));
    // assign a value
    *z = 99;

    printf("my_func - value of z: %d\n", *z);

    printf("my_func - value of p: %p\n", p);
    printf("my_func - value of h: %p\n", *p);
    // change the value of the pointer p. Now it is not pointing to h anymore
    *p = z;
    printf("my_func - make p point to z\n");
    printf("my_func - addr of z %p\n", &*z);
    printf("my_func - value of p %p\n", p);
    printf("my_func - value of h %p\n", *p);
    printf("my_func - value of what p points to: %d\n", **p);
    // we are not deallocating, because we want to keep the value in that
    // memory location, in order for h to access it.
    /* free(z); */
}

int main(int argc, char *argv[])
{
    // our var
    int z = 10;

    int *h = &z;

    // print value of z
    printf("main - value of z: %d\n", z);
    // print address of val
    printf("main - addr of z: %p\n", &z);

    // print value of h.
    printf("main - value of h: %p\n", h);

    // print value of what h points to
    printf("main - value of what h points to: %d\n", *h);
    // change the value of var z by dereferencing h
    *h = 22;
    // print value of val
    printf("main - value of z: %d\n", z);
    // print value of what h points to
    printf("main - value of what h points to: %d\n", *h);


    my_func(&h);

    // print value of what h points to
    printf("main - value of what h points to: %d\n", *h);

    // print value of h
    printf("main - value of h: %p\n", h);
    free(h);


    return 0;
}

we have the follwoing output:

我们有以下输出:

main - value of z: 10
main - addr of z: 0x7ffcb94fb1cc
main - value of h: 0x7ffcb94fb1cc
main - value of what h points to: 10
main - value of z: 22
main - value of what h points to: 22
my_func - value of z: 99
my_func - value of p: 0x7ffcb94fb1c0
my_func - value of h: 0x7ffcb94fb1cc
my_func - make p point to z
my_func - addr of z 0xc3b420
my_func - value of p 0x7ffcb94fb1c0
my_func - value of h 0xc3b420
my_func - value of what p points to: 99
main - value of what h points to: 99
main - value of h: 0xc3b420

Now, we actually have changed the value which h holds, from my_func, by doing this:

现在,我们已经改变了h在my_func中的值,通过这样做:

  1. changed function signature
  2. 改变函数签名
  3. calling from main(): my_func(&h); Basically we are passing the address of h pointer to double pointer p, declared as a parameter in function's signature.
  4. 调用从主():my_func(h);基本上我们将h指针的地址传递给双指针p,在函数的签名中声明为参数。
  5. in my_func() we are doing: *p = z; we are dereferencing the double pointer p, one level. Basically this got translated as you would do: h = z;
  6. 在my_func()中,我们正在做:*p = z;我们取消了双指针p,一级。基本上这个可以翻译成h = z;

The value of p, now holds the address of h pointer. h pointer holds the address of z.

p的值,现在包含h指针的地址。h指针包含z的地址。

You can take both examples and diff them. So, getting back to your question, you need double pointer in order to make modifications to the pointer that you've passed in straight from that function.

你可以同时举两个例子,然后对它们进行修改。回到你的问题,你需要双指针来修改你直接从函数中传入的指针。

#4


1  

Although the previous answers are good enough, I think it's much easier to think in terms of "copy by value".

虽然前面的答案已经足够好了,但我认为用“按价值复制”来思考要容易得多。

When you pass in a pointer to a function, the address value is being copied over to the function parameter. Due to the function's scope, that copy will vanish once it returns.

当您传入指向函数的指针时,地址值将被复制到函数参数。由于函数的作用域,当它返回时,该副本将消失。

By using a double pointer, you will be able to update the original pointer's value. The double pointer will still be copied by value, but that doesn't matter. All you really care is modifying the original pointer, thereby bypassing the function's scope or stack.

通过使用双指针,您将能够更新原始指针的值。双指针仍然会被值复制,但这并不重要。您真正关心的是修改原始指针,从而绕过函数的范围或堆栈。

Hope this answers not just your question, but other pointer related questions as well.

希望这不仅能回答你的问题,还能回答其他与指针相关的问题。

#5


0  

The answer is more obvious if you take the time to write a working node insertion function; yours isn't one.

如果您花时间编写一个工作节点插入函数,那么答案将更加明显;你不是一个。

You need to be able to write over the head to move it forward, so you need a pointer to the pointer to the head so you can dereference it to get the pointer to the head and change it.

你需要能够把它写在头部上才能将它向前移动,所以你需要一个指向头部的指针这样你就可以去引用它来获得指向头部的指针并改变它。

#6


0  

Imagine a case where you have to make certain changes and those changes should reflect back in the calling function.

假设您必须进行某些更改,这些更改应该在调用函数中反映出来。

Example:

例子:

void swap(int* a,int* b){
  int tmp=*a;
  *a=*b;
  *b=tmp;
}

int main(void){
  int a=10,b=20;

  // To ascertain that changes made in swap reflect back here we pass the memory address
  // instead of the copy of the values

  swap(&a,&b);
}

Similarly we pass the Memory Address of the Head of the List.

同样,我们传递列表头的内存地址。

This way, if any node is added and the Value of Head is Changed, then that change Reflects Back and we don't have to manually reset the Head inside of the calling function.

这样,如果添加了任何节点,并且更改了Head的值,那么这个更改就会反射回来,我们不必手动重置调用函数内部的Head。

Thus this approach reduces the chances of Memory Leaks as we would have lost the pointer to the newly allocated node, had we forgot to update the Head back in the calling function.

因此,这种方法减少了内存泄漏的可能性,因为我们丢失了指向新分配的节点的指针,我们忘记了在调用函数中更新头部。

Beside this, the second code will Work Faster since no time is wasted in copying and returning since we work directly with the memory.

此外,由于直接使用内存,所以在复制和返回时不会浪费时间,因此第二个代码将更快地工作。

#7


0  

Observation and Finding, WHY...

观察和发现,为什么……

I decided to do some experiments and make some conclusion,

我决定做一些实验并得出一些结论,

OBSERVATION 1- If the linked list is not empty then we can add the nodes in it (obviously at the end) by using a single pointer only.

观察1-如果链表不是空的,那么我们可以只使用一个指针来添加链表中的节点(显然在末尾)。

int insert(struct LinkedList *root, int item){
    struct LinkedList *temp = (struct LinkedList*)malloc(sizeof(struct LinkedList));
    temp->data=item;
    temp->next=NULL;
    struct LinkedList *p = root;
    while(p->next!=NULL){
        p=p->next;
    }
    p->next=temp;
    return 0;
}


int main(){
    int m;
    struct LinkedList *A=(struct LinkedList*)malloc(sizeof(struct LinkedList));
    //now we want to add one element to the list so that the list becomes non-empty
    A->data=5;
    A->next=NULL;
    cout<<"enter the element to be inserted\n"; cin>>m;
    insert(A,m);
    return 0;
}

Its simple to explain (Basic). We have a pointer in our main function which points to the first node (root) of the list. In the insert() function we pass the address of the root node and using this address we reach the end of the list and add a node to it. So we can conclude that if we have address of a variable in a function (not the main function) we can make permanent changes in the value of that variable from that function which would reflect in the main function.

这很容易解释(基本)。我们的主函数中有一个指针,指向列表的第一个节点(根)。在insert()函数中,我们传递根节点的地址,并使用这个地址到达列表的末尾并向其添加一个节点。因此,我们可以得出结论,如果我们在函数中有一个变量(而不是主函数)的地址,我们就可以从这个函数中对该变量的值进行永久性的改变,而这个函数将反映在主函数中。

OBSERVATION 2- The above method of adding node failed when the list was empty.

观察2-上述添加节点的方法在列表为空时失败。

int insert(struct LinkedList *root, int item){
    struct LinkedList *temp = (struct LinkedList*)malloc(sizeof(struct LinkedList));
    temp->data=item;
    temp->next=NULL;
    struct LinkedList *p=root;   
    if(p==NULL){
        p=temp;
    }
    else{
      while(p->next!=NULL){
          p=p->next;
      }
      p->next=temp;
    }
    return 0;
}



int main(){
    int m;
    struct LinkedList *A=NULL; //initialise the list to be empty
    cout<<"enter the element to be inserted\n";
    cin>>m;
    insert(A,m);
    return 0;
}

If you keep on adding elements and finally display the list then you would find that the list has undergone no changes and still it is empty. The question which struck my mind was in this case also we are passing the address of the root node then why modifications are not happening as permanent modifications and list in the main function undergoes no changes. WHY? WHY? WHY?

如果您继续添加元素并最终显示列表,那么您将发现列表没有进行任何更改,但仍然是空的。我想到的问题是,在这种情况下,我们也传递根节点的地址,那么为什么修改没有作为永久修改发生,而在主函数中列表没有发生更改。为什么?为什么?为什么?

Then I observed one thing, when I write A=NULL the address of A becomes 0. This means now A is not pointing to any location in memory. So I removed the line A=NULL; and made some modification in the insert function.

然后我观察到一件事,当我写A=NULL时,A的地址就变成了0。这意味着现在A没有指向内存中的任何位置。我去掉了A=NULL;并对插入函数做了一些修改。

some modifications,(below insert() function can add only one element to an empty list, just wrote this function for testing purpose)

一些修改(在insert()函数下面)只能向空列表中添加一个元素,只是为了测试目的而编写这个函数)

int insert(struct LinkedList *root, int item){
    root= (struct LinkedList *)malloc(sizeof(struct LinkedList));
    root->data=item;
    root->next=NULL;
    return 0;
}



int main(){
    int m;
    struct LinkedList *A;    
    cout<<"enter the element to be inserted\n";
    cin>>m;
    insert(A,m);
    return 0;
}

the above method also fails because in the insert() function root stores same address as A in the main() function but after the line root= (struct LinkedList *)malloc(sizeof(struct LinkedList)); the address stored in root changes. Thus now , root (in insert() function) and A (in main() function) store different addresses.

上述方法也失败了,因为在insert()函数的根中,与main()函数中的A存储相同的地址,但是在line root= (struct LinkedList *)malloc(sizeof(struct LinkedList)之后;存储在根中的地址会发生变化。因此,现在根(in insert()函数)和主(in main()函数)存储不同的地址。

So the correct final program would be,

所以最终的程序是,

int insert(struct LinkedList *root, int item){
    root->data=item;
    root->next=NULL;
    return 0;
}



int main(){
    int m;
    struct LinkedList *A = (struct LinkedList *)malloc(sizeof(struct LinkedList));
    cout<<"enter the element to be inserted\n";
    cin>>m;
    insert(A,m);
    return 0;
}

But we dont want two different functions for insertion, one when list is empty and other when list is not empty. Now comes double pointer which makes things easy.

但是我们不希望在插入时使用两个不同的函数,一个是列表为空,另一个是列表为空。现在来了双指针,让事情变得简单。

One thing I noticed which is important is that pointers store address and when used with '*' they give value at that address but pointers themselves have their own address.

我注意到的一件重要的事情是指针存储地址,当与'*'一起使用时,它们在那个地址上给出值,但是指针本身有它们自己的地址。

Now here is the complete program and later explain the concepts.

现在这是完整的程序,稍后将解释这些概念。

int insert(struct LinkedList **root,int item){
    if(*root==NULL){
        (*root)=(struct LinkedList *)malloc(sizeof(struct LinkedList));
        (*root)->data=item;
        (*root)->next=NULL;
    }
    else{
        struct LinkedList *temp=(struct LinkedList *)malloc(sizeof(struct LinkedList));
        temp->data=item;
        temp->next=NULL;
        struct LinkedList *p;
        p=*root;
        while(p->next!=NULL){
            p=p->next;
        }
        p->next=temp;
    }
    return 0;
}


int main(){
    int n,m;
    struct LinkedList *A=NULL;
    cout<<"enter the no of elements to be inserted\n";
    cin>>n;
    while(n--){
        cin>>m;
        insert(&A,m);
    }
    display(A);
    return 0;
}

following are the observations,

以下的观察,

1. root stores the address of pointer A (&A) , *root stores the address stored by pointer A and **root stores the value at address stored by A. In simple language root=&A, *root= A and **root= *A.

1。根存储指针A (&A)的地址,*根存储指针A存储的地址,** *根存储在A存储的地址。

2. if we write *root= 1528 then it means that value at address stored in root becomes 1528 and since address stored in root is the address of pointer A (&A) thus now A=1528 (i.e. address stored in A is 1528) and this change is permanent.

2。如果我们写*root= 1528,那么它意味着存储在根中的地址的值变成了1528,因为存储在根中的地址是指针A(和)的地址,所以现在A=1528(也就是说,存储在A中的地址是1528),这个变化是永久性的。

whenever we are changing value of *root we are indeed changing value at address stored in root and since root=&A ( address of pointer A) we are indirectly changing value of A or address stored in A.

当我们改变*root的值时,我们实际上是在改变存储在根中的地址的值,并且由于root=&A(指针A的地址)我们间接地改变了存储在A中的A或地址的值。

so now if A=NULL (list is empty) *root=NULL , thus we create the first node and store its address at *root i.e. indirectly we storing the address of first node at A. If list is not empty , everything is same as done in previous functions using single pointer except we have changed root to *root since what was stored in root is now stored in *root.

现在如果一个= NULL(列表为空)*根=零,因此我们创建第一个节点和存储地址*根即间接我们存储第一个节点的地址如果列表不是空的,一切都是一样做在前面的函数使用单个指针除了我们已经改变了根*根因为现在存储在根是存储在根。

#8


0  

When we pass pointer as a parameter in a function and want update in the same pointer we use double pointer.

当我们在函数中把指针作为参数传递并希望在同一个指针中更新时,我们使用双指针。

On the other hand if we pass pointer as a parameter in a function and catch it in single pointer then will have to return the result to calling function back in order to use the result.

另一方面,如果我们将指针作为参数传递给函数,并将其捕获到单个指针中,那么为了使用结果,我们必须返回调用函数的结果。

#9


0  

Think of memory location for head like [HEAD_DATA].

像[HEAD_DATA]一样考虑head的内存位置。

Now in your second scenario, the calling function's main_head is the pointer to this location.

在第二个场景中,调用函数的main_head是指向这个位置的指针。

main_head--->[HEAD_DATA]

main_head - - - >[HEAD_DATA]

In your code, it sent the value of the pointer main_head to the function(i.e the address of the memory location of head_data) You copied that to local_head in the function. so now

在您的代码中,它将指针main_head的值发送到函数(i)。在函数中复制到local_head的内存位置的地址。所以现在

local_head---> [HEAD_DATA]

local_head - - - >[HEAD_DATA]

and

main_head---> [HEAD_DATA]

main_head - - - >[HEAD_DATA]

Both point to the same location but are essentially independent of each other. So when you write local_head = newnode; what you did is

两者都指向同一个位置,但本质上是相互独立的。写local_head = newnode;你做的是什么

local_head--/-->[HEAD_DATA]

local_head - / - >[HEAD_DATA]

local_head-----> [NEWNODE_DATA]

local_head - - - - - - >[NEWNODE_DATA]

You simply replaced the memory address of previous memory with new one in local pointer. The main_head (pointer) still points to the old [HEAD_DATA]

您只需在本地指针中使用新的内存地址替换先前内存的内存地址。main_head(指针)仍然指向旧的[HEAD_DATA]

#10


0  

I think the point is that it makes it easier to update nodes within a linked list. Where you would normally have to keep track of a pointer for previous and current you can have a double pointer take care of it all.

我认为关键在于它使得更新链表中的节点变得更加容易。通常情况下,你需要跟踪一个指针的过去和现在,你可以有一个双指针来处理它。

#include <iostream>
#include <math.h>

using namespace std;

class LL
{
    private:
        struct node 
        {
            int value;
            node* next;
            node(int v_) :value(v_), next(nullptr) {};
        };
        node* head;

    public:
        LL() 
        {
            head = nullptr;
        }
        void print() 
        {
            node* temp = head;
            while (temp) 
            {
                cout << temp->value << " ";
                temp = temp->next;
            }
        }
        void insert_sorted_order(int v_) 
        {
            if (!head)
                head = new node(v_);
            else
            {
                node* insert = new node(v_);
                node** temp = &head;
                while ((*temp) && insert->value > (*temp)->value)
                    temp = &(*temp)->next;
                insert->next = (*temp);
                (*temp) = insert;
            }
        }

        void remove(int v_)
        {
            node** temp = &head;
            while ((*temp)->value != v_)
                temp = &(*temp)->next;
            node* d = (*temp);
            (*temp) = (*temp)->next;
            delete d;
        }

        void insertRear(int v_)//single pointer
        {
            if (!head)
                head = new node(v_);
            else
            {
                node* temp = new node(v_);
                temp->next = head;
                head = temp;
            }
        }
};

#11


0  

As @R. Martinho Fernandes pointed out in his answer, using pointer to pointer as an argument in void push(struct node** head, int data) allows you to change the head pointer directly from within push function instead of returning the new pointer.

@R。Martinho Fernandes在他的回答中指出,在void push(struct node* head, int data)中使用指针作为参数,允许您直接从push函数中更改头指针,而不是返回新的指针。

There is yet another good example which shows why using pointer to pointer instead a single pointer may shorten, simplify and speed up your code. You asked about adding a new node to the list which probably typically doesn't need pointer-to-pointer in contrast to removing the node from the singly-linked list. You can implement removing node from the list without pointer-to-pointer but it is suboptimal. I described the details here. I recommend you also to watch this YouTube video which addresses the problem.

还有一个很好的例子可以说明为什么使用指针来代替单个指针可以缩短、简化和加速代码。您要求将一个新节点添加到列表中,该节点通常不需要pointerto -pointer,而不是将节点从单一链接列表中删除。您可以实现不带指针指针的从列表中删除节点,但它是次优的。我在这里描述了细节。我建议你也观看这个YouTube视频,它解决了这个问题。

BTW: If you count with Linus Torvalds opinion, you would better learn how to use pointer-to-pointer. ;-)

顺便说一句:如果你同意莱纳斯·托瓦兹的观点,你最好学会如何使用指针指向指针。:-)

Linus Torvalds: (...) At the opposite end of the spectrum, I actually wish more people understood the really core low-level kind of coding. Not big, complex stuff like the lockless name lookup, but simply good use of pointers-to-pointers etc. For example, I've seen too many people who delete a singly-linked list entry by keeping track of the "prev" entry, and then to delete the entry, doing something like

Linus Torvalds:(…)相反,我希望更多的人理解底层的核心代码。不需要太多复杂的东西,比如无锁的名称查找,但只要很好地使用指针指向指针等。例如,我看到过太多的人通过跟踪“prev”条目来删除一个链表条目,然后删除条目,做一些类似的事情

if (prev)
prev->next = entry->next;
else
list_head = entry->next;

and whenever I see code like that, I just go "This person doesn't understand pointers". And it's sadly quite common.

每当我看到这样的代码,我就会说“这个人不懂指针”。可悲的是,这很常见。

People who understand pointers just use a "pointer to the entry pointer", and initialize that with the address of the list_head. And then as they traverse the list, they can remove the entry without using any conditionals, by just doing a "*pp = entry->next". (...)

理解指针的人只使用一个“指向入口指针的指针”,并使用list_head的地址初始化该指针。然后,当它们遍历列表时,可以不使用任何条件语句删除条目,只需执行“*pp =条目->next”。(…)


Other resources that may be helpful:

其他可能有帮助的资源:

#12


0  

The standard way to handle linked lists in C is to have the push and pop functions automatically update the head pointer.

在C语言中处理链表的标准方法是让push和pop函数自动更新头指针。

C is "Call by value" meaning copies of parameters are passed into functions. If you only pass in the head pointer any local update you make to that pointer will not be seen by the caller. The two workarounds are

C是“按值调用”,意思是参数的副本被传递到函数中。如果您只传入头部指针,则调用者将看不到您对该指针进行的任何本地更新。这两个解决方法

1) Pass the address of the head pointer. (Pointer to head pointer)

1)传递头部指针的地址。(指针指向头指针)

2) Return a new head pointer, and rely on the caller to update the head pointer.

2)返回一个新的头指针,并依赖调用者更新头指针。

Option 1) is the easiest even though a little confusing at first.

选项1)是最简单的,尽管一开始有点混乱。

#13


0  

Lets say I noted down your house address on a card-1. Now if I want tell your house address to somebody else, I can either copy the address from card-1 to card-2 and give card-2 OR I can give card-1 directly. Either ways the person will know the address and can reach you. But when I give card-1 directly, the address can be changed on card-1 but if I gave card-2 only the address on card-2 can be changed but not on card-1.

假设我在卡片上记下了你的住址。现在,如果我想告诉别人你的住址,我可以把地址从卡片1复制到卡片2,然后给卡片2,或者直接给卡片1。不管是哪种方式,这个人都会知道你的地址并能联系到你。但是当我直接给card-1时,地址可以在card-1上更改,但是如果我给card-2上只有card-2上的地址可以更改,而不是card-1上。

Passing a pointer to pointer is similar to giving the access to card-1 directly. Passing a pointer is similar to creating a new copy of the address.

将指针传递给指针类似于直接访问card-1。传递指针类似于创建地址的新副本。