链表附加项中的指针

时间:2021-09-11 07:19:51

I normally program in python. To increase performance of my simulations, I am learning C. I have a problem to understand the use of a pointer of a pointer when implementing the append function to a linked list. This is an excerpt of the code from my book (Understanding Pointers in C by Kanetkar).

我通常用python编程。为了提高模拟的性能,我正在学习c。在实现链接列表的附加函数时,我需要理解指针的使用。这是我书中的代码摘录(Kanetkar在C中理解指针)。

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

struct node{
    int data;
    struct node *link;
};

int main(){
    struct node *p; //pointer to node structure
    p = NULL;   //linked list is empty

    append( &p,1);
    return 0;
}

append( struct node **q, int num){
    struct node *temp, *r;  //two pointers to struct node
    temp = *q;

    if(*q == NULL){
        temp = malloc(sizeof(struct node));
        temp -> data = num;
        temp -> link = NULL;
        *q = temp;
    }
    else{
        temp = *q;
        while( temp -> link != NULL)
            temp = temp -> link;
        r = malloc(sizeof(struct node));
        r -> data = num;
        r -> link = NULL;
        temp -> link = r;
    }
}

In this code, I pass the double pointer **q to the append function. I get that this is the adress of the address, i.e. the adress of NULL in this case.

在这段代码中,我将双指针**q传递给append函数。我得到这是地址的地址,也就是零的地址。

I just don't get why one does it like this. Would it not be valid to remove one * operator from everything in the append() function and simply pass the adress of NULL (i.e. p instead of &p) to the append() function?

我就是不明白为什么会这样。从append()函数的所有内容中删除一个*操作符,并将NULL(即p而不是&p)的加值传递给append()函数,这是否有效?

I have googled this question. The answers are either too hard to understand (since I'm just a C beginner) or too plain. I'm thankful for any hints, comments or links where I can read up about this.

我用谷歌搜索过这个问题。答案要么太难理解(因为我只是个C初学者),要么太简单。我感谢任何提示、评论或链接,我可以在那里了解到这一点。

7 个解决方案

#1


16  

When you pass things to functions in C, whether its variables or pointers, it's a copy of the original.

当你把东西传递给C中的函数时,无论是它的变量还是指针,它都是原始函数的拷贝。

Quick example:

简单的例子:

#include <stdio.h>
void change(char *in)
{
    // in here is just a copy of the original pointer.
    // In other words: It's a pointer pointing to "A" in our main case 
    in = "B";
    // We made our local copy point to something else, but did _not_ change what the original pointer points to.
}
void really_change(char **in)
{
    // We get a pointer-to-a-pointer copy. This one can give us the address to the original pointer.
    // We now know where the original pointer is, we can make _that one_ point to something else.
    *in = "B";
}
int main(int argc, char *argv[])
{
    char *a = "A";
    change(a);
    printf("%s\n", a); /* Will print A */
    really_change(&a);
    printf("%s\n", a); /* Will print B */
    return 0;
}

So the first function call to change() gets passed a copy of a pointer to an address. When we do in = "B" we only change the copy of the pointer we got passed.

因此,第一个函数调用change()会传递一个指向地址的指针的副本。当我们使用= "B"时,我们只更改传递的指针的副本。

In the second function call, the really_change(), we get passed a copy of a pointer-to-a-pointer. This pointer contains the address to our original pointer and voila, we can now reference the original pointer and change what the original pointer should point to.

在第二个函数调用really_change()中,传递给我们一个指针对指针的副本。这个指针包含了指向原始指针的地址,我们现在可以引用原始指针并改变原始指针指向的内容。

Hopefully it explains it somewhat more :)

希望它能解释更多:

#2


6  

First Its not "the address of the address." Its the address of a pointer variable. Ex: if you pass the address of an int variable n that contains zero, you're not passing the address of zero; you're passing the address of a variable (in this case an int variable, in your case a pointer-variable). Variables have addresses in memory. The parameter in this case is the address of a variable that happens to be a pointer-variable, the head of your list.

首先,它不是“地址的地址”。它是指针变量的地址。如果你传递一个包含0的int变量的地址,你不会传递0的地址;您正在传递一个变量的地址(在本例中是int变量,在本例中是指针间变量)。变量在内存中有地址。在这种情况下,参数是一个变量的地址,它碰巧是指针变量,列表的头。

Regarding why one does this? Simple. All variables in C (arrays via pointer-decay not withstanding) are passed by value. If you want to modify something by reference (address), then the "value" you need to pass must be an address and the formal parameter that receives it must be a pointer-type. In short, you make the "value" being passed a memory address rather than just a basic scaler value. The function then uses this (via a formal pointer parameter) to store data accordingly. Think of it as saying "put that thing I wanted at "this" memory address."

为什么会这样?简单。C中的所有变量(数组通过指针-衰减而不受限制)都通过值传递。如果您想通过引用(地址)修改某样东西,那么您需要传递的“值”必须是一个地址,接收它的形式参数必须是一个指针类型。简而言之,您使“值”被传递到一个内存地址,而不只是一个基本的标量值。然后函数使用这个(通过一个正式的指针参数)相应地存储数据。把它想成“把我想要的东西放到”这个“内存地址”里。

As a short example, suppose you wanted to run through a file, appending every character within into a forward linked list of nodes. You would not use an append-method like the one you have (see The Painter's Algorithm for why). See if you can follow this code, which uses a pointer-to-pointer, but no function call.

作为一个简短的示例,假设您希望运行一个文件,将其中的每个字符附加到一个向前链接的节点列表中。您不会使用像您拥有的那样的附加方法(请参阅Painter的算法了解原因)。看看您是否可以遵循这段代码,它使用指针指针指针,但不使用函数调用。

typedef struct node
{
    char ch;
    struct node *next;
} node;


node *loadFile(const char *fname)
{
    node *head = NULL, **next = &head;
    FILE *fp = fopen(fname, "r");
    if (fp)
    {
        int ch;
        while ((ch = fgetc(fp)) != EOF)
        {
            node *p = malloc(sizeof(*p));
            p->ch = ch;
            *next = p;
            next = &p->next;
        }
        *next = NULL;
        fclose(fp);
    }
    return head;
}

Stare at that awhile and see if you can understand how the pointer-to-pointer next is used to always populate the next linked node to be added to the list, starting with the head-node.

盯着那一段时间,看看你是否能理解,下一个被用来填充下一个链接的节点是如何被添加到列表中的,从头节点开始。

#3


3  

You need to do it that way for the function to be able to allocate the memory. Simplifying the code:

你需要这样做才能让函数能够分配内存。简化代码:

main()
{
  void *p;
  p = NULL;
  funcA(&p);

  int i;
  i = 0;
  funcB(&i);
}

funcA(void **q)
{
  *q = malloc(sizeof(void*)*10);
}

funcB(int *j)
{
  *j = 1;
}

This code is done that way so the subfunction funcA can allocate the p pointer. First of all, consider void* p as if it where int i. What you do by doing p = NULL is similar in a way as int i = 0. Now if you pass &i you do not pass the address of 0, you pass the address of i. Same thing happens with &p you pass the address of the pointer.

这个代码是这样做的,所以subfunction funcA可以分配p指针。首先,把void* p想象成int i,你做的p = NULL在某种程度上和int i = 0很相似。如果你传递&i你不传递0的地址,你传递i的地址。同样的事情发生在&p你传递指针的地址。

Now in funcA, you want to make the allocation, so you use malloc but if you would do q = malloc(... and q would have been void* q in the main function p would not have been allocated. Why? Think of funcB, j holds the address of i, if you want to modify i you would then do *j = 1, because if you would do j = 1 then you would have made j point to another memory region instead of i. It is the same with q for funcA. think of it as <type of p>* q it is a pointer to the type of p which is void*, but in case of funcB it is a int. Now you want to modify the address p is pointing at, it means that you do not want to modify the address q is pointing at, you want to modify the pointing address at what q is pointing at, aka *q or p.

在funcA中,你想要进行分配,所以你要使用malloc,但是如果你要做q = malloc(…q是无效的* q在主函数p中没有被分配。为什么?想到funcB,j持有我的地址,如果你想修改我将做* j = 1,因为如果你会做j = 1,那么你会使指向另一个内存区域,而不是我。它是相同的对funcA问。认为它是< p >类型* q是一个指针类型的p void *,但如果funcB int。现在你想修改地址p是指着,这意味着你不希望修改地址问指着,您想修改地址指向q是指向什么,又名* q和p。

If it isn't yet clear. Try to think of boxes. I have drawn a quick example with funcA of the boxes involved. Each box has a name (within the box), this box is in the virtual memory of the process at an arbitrary address, and each box contain a value. In this view we are in the state where the funcA(&p) has been called and the malloc will be done.

如果还不清楚的话。试着想想盒子。我已经用所涉及的盒子的功能画了一个简单的例子。每个框都有一个名称(在框中),这个框位于进程的虚拟内存中的任意地址,每个框包含一个值。在这个视图中,我们处于调用funcA(&p)并完成malloc的状态。

链表附加项中的指针

#4


2  

Hey why you are thinking it in this way,think when someone is passing structure to append function,in that case whole structure struct node{int data; struct node *link; }; in your case will get copied on stack frame of append function,so better to pass address of structure pointer so as to get just 4 bytes copied onto stack.

嘿,你为什么这么想,想想当有人把结构传递给附加函数时,在这种情况下整个结构结构结构节点{int data;结构节点*链接;};在你的情况下,会被复制到附加函数的堆栈帧上,所以最好将结构指针的地址传递给堆栈,这样就可以只复制4个字节到堆栈上。

#5


2  

You don't need the if/else; in both cases you link the new node to a pointer that was NULL before the operation. This could be the root node, or the ->next node of the last node in the chain. Both are pointers to struct node, and you need a pointer to these pointers in order to assign to them.

你不需要if/else;在这两种情况下,都将新节点链接到操作之前为NULL的指针。这可以是根节点,或者是链中最后一个节点的下一个节点的->。这两个都是指向struct节点的指针,您需要一个指向这些指针的指针,以便分配给它们。

void append( struct node **q, int num){

    while (*q){ q = &(*q)->link; }

    *q = malloc(sizeof **q);
    (*q)->data = num;
    (*q)->link = NULL;

}

Why would anyone do this? Basically because it is shorter, it uses only one loop and no additional conditions, uses no additional variables, and it can be proven to be correct. There should of course a test be added for the result of malloc, which would need an addtional condition.

为什么会有人这么做?因为它更短,它只使用一个循环,没有附加条件,不使用其他变量,并且可以证明它是正确的。当然应该对malloc的结果添加一个测试,这需要一个附加条件。

#6


1  

In essence, as Jite & the others said is correct.

本质上,正如Jite & others所说的那样是正确的。

Whenever you want to have a change applied to a data structure in C (change performed by another function), you need to pass a "reference" to this data structure for the change to persist beyond the change() function completes. This is what happens in Python too, you pass references to Objects unless you explicitly make a copy. In C you have to specify what you want to do precisely. To simplify it even more, it's either:

每当您希望对C中的数据结构应用更改(由另一个函数执行的更改)时,您都需要向该数据结构传递一个“引用”,以便更改在change()函数完成之后继续存在。这也是在Python中发生的情况,您将引用传递给对象,除非您显式地进行复制。在C语言中,你必须精确地指定你想要做什么。为了进一步简化,它是:

type data_struct

类型data_struct

change(data_struct) => here's a copy of my data_struct, make your change but I do not care in the caller function about the change you apply

change(data_struct) =>这是我的data_struct的副本,进行您的更改,但是我不关心调用者函数中您所应用的更改

or

change(&data_struct) => here's the address (a "reference" to) of my data_struct, apply your change, the caller function will see this change after it is applied.

change(&data_struct) =>,这里是data_struct的地址(“引用”),应用您的更改,调用函数在应用后将看到这个更改。

Now, depending of what the original "type" is, you might have * or **. Nevertheless, keep in mind that there is a limit to how many "indirections" you can have, not sure weather it is system or compiler determined, if anyone has an answer to that I'm a taker. I never had more than 3 indirections.

现在,根据原始的“类型”是什么,您可能有*或**。尽管如此,请记住,您可以有多少“间接”是有限制的,不确定它是由系统还是编译器决定的,如果有人知道我是一个接受者的话。我从来没有超过3个方向。

#7


0  

I think reason is as follows :

我认为原因如下:

struct node *p; //pointer to node structure p = NULL;

结构节点* p;//节点结构指针p = NULL;

Above snippet when written in main block , means value of pointer p is NULL so it does not point to anything in memory . So we pass address of pointer p in order to create a new node and assign the address of new node to the value of pointer p .

上面的代码片段在主块中写入时,意味着指针的值为NULL,所以它不会指向内存中的任何东西。因此,我们通过指针p的地址来创建一个新节点,并将新节点的地址分配给指针p的值。

*(&p) == *q == temp;

*(&p) = *q = temp;

By doing *q == temp; we achieve our goal of assigning some value to pointer p which was originally pointing nowhere .

通过做*q == temp;我们的目标是为指针p赋值,而指针p最初指向的是空的。

#1


16  

When you pass things to functions in C, whether its variables or pointers, it's a copy of the original.

当你把东西传递给C中的函数时,无论是它的变量还是指针,它都是原始函数的拷贝。

Quick example:

简单的例子:

#include <stdio.h>
void change(char *in)
{
    // in here is just a copy of the original pointer.
    // In other words: It's a pointer pointing to "A" in our main case 
    in = "B";
    // We made our local copy point to something else, but did _not_ change what the original pointer points to.
}
void really_change(char **in)
{
    // We get a pointer-to-a-pointer copy. This one can give us the address to the original pointer.
    // We now know where the original pointer is, we can make _that one_ point to something else.
    *in = "B";
}
int main(int argc, char *argv[])
{
    char *a = "A";
    change(a);
    printf("%s\n", a); /* Will print A */
    really_change(&a);
    printf("%s\n", a); /* Will print B */
    return 0;
}

So the first function call to change() gets passed a copy of a pointer to an address. When we do in = "B" we only change the copy of the pointer we got passed.

因此,第一个函数调用change()会传递一个指向地址的指针的副本。当我们使用= "B"时,我们只更改传递的指针的副本。

In the second function call, the really_change(), we get passed a copy of a pointer-to-a-pointer. This pointer contains the address to our original pointer and voila, we can now reference the original pointer and change what the original pointer should point to.

在第二个函数调用really_change()中,传递给我们一个指针对指针的副本。这个指针包含了指向原始指针的地址,我们现在可以引用原始指针并改变原始指针指向的内容。

Hopefully it explains it somewhat more :)

希望它能解释更多:

#2


6  

First Its not "the address of the address." Its the address of a pointer variable. Ex: if you pass the address of an int variable n that contains zero, you're not passing the address of zero; you're passing the address of a variable (in this case an int variable, in your case a pointer-variable). Variables have addresses in memory. The parameter in this case is the address of a variable that happens to be a pointer-variable, the head of your list.

首先,它不是“地址的地址”。它是指针变量的地址。如果你传递一个包含0的int变量的地址,你不会传递0的地址;您正在传递一个变量的地址(在本例中是int变量,在本例中是指针间变量)。变量在内存中有地址。在这种情况下,参数是一个变量的地址,它碰巧是指针变量,列表的头。

Regarding why one does this? Simple. All variables in C (arrays via pointer-decay not withstanding) are passed by value. If you want to modify something by reference (address), then the "value" you need to pass must be an address and the formal parameter that receives it must be a pointer-type. In short, you make the "value" being passed a memory address rather than just a basic scaler value. The function then uses this (via a formal pointer parameter) to store data accordingly. Think of it as saying "put that thing I wanted at "this" memory address."

为什么会这样?简单。C中的所有变量(数组通过指针-衰减而不受限制)都通过值传递。如果您想通过引用(地址)修改某样东西,那么您需要传递的“值”必须是一个地址,接收它的形式参数必须是一个指针类型。简而言之,您使“值”被传递到一个内存地址,而不只是一个基本的标量值。然后函数使用这个(通过一个正式的指针参数)相应地存储数据。把它想成“把我想要的东西放到”这个“内存地址”里。

As a short example, suppose you wanted to run through a file, appending every character within into a forward linked list of nodes. You would not use an append-method like the one you have (see The Painter's Algorithm for why). See if you can follow this code, which uses a pointer-to-pointer, but no function call.

作为一个简短的示例,假设您希望运行一个文件,将其中的每个字符附加到一个向前链接的节点列表中。您不会使用像您拥有的那样的附加方法(请参阅Painter的算法了解原因)。看看您是否可以遵循这段代码,它使用指针指针指针,但不使用函数调用。

typedef struct node
{
    char ch;
    struct node *next;
} node;


node *loadFile(const char *fname)
{
    node *head = NULL, **next = &head;
    FILE *fp = fopen(fname, "r");
    if (fp)
    {
        int ch;
        while ((ch = fgetc(fp)) != EOF)
        {
            node *p = malloc(sizeof(*p));
            p->ch = ch;
            *next = p;
            next = &p->next;
        }
        *next = NULL;
        fclose(fp);
    }
    return head;
}

Stare at that awhile and see if you can understand how the pointer-to-pointer next is used to always populate the next linked node to be added to the list, starting with the head-node.

盯着那一段时间,看看你是否能理解,下一个被用来填充下一个链接的节点是如何被添加到列表中的,从头节点开始。

#3


3  

You need to do it that way for the function to be able to allocate the memory. Simplifying the code:

你需要这样做才能让函数能够分配内存。简化代码:

main()
{
  void *p;
  p = NULL;
  funcA(&p);

  int i;
  i = 0;
  funcB(&i);
}

funcA(void **q)
{
  *q = malloc(sizeof(void*)*10);
}

funcB(int *j)
{
  *j = 1;
}

This code is done that way so the subfunction funcA can allocate the p pointer. First of all, consider void* p as if it where int i. What you do by doing p = NULL is similar in a way as int i = 0. Now if you pass &i you do not pass the address of 0, you pass the address of i. Same thing happens with &p you pass the address of the pointer.

这个代码是这样做的,所以subfunction funcA可以分配p指针。首先,把void* p想象成int i,你做的p = NULL在某种程度上和int i = 0很相似。如果你传递&i你不传递0的地址,你传递i的地址。同样的事情发生在&p你传递指针的地址。

Now in funcA, you want to make the allocation, so you use malloc but if you would do q = malloc(... and q would have been void* q in the main function p would not have been allocated. Why? Think of funcB, j holds the address of i, if you want to modify i you would then do *j = 1, because if you would do j = 1 then you would have made j point to another memory region instead of i. It is the same with q for funcA. think of it as <type of p>* q it is a pointer to the type of p which is void*, but in case of funcB it is a int. Now you want to modify the address p is pointing at, it means that you do not want to modify the address q is pointing at, you want to modify the pointing address at what q is pointing at, aka *q or p.

在funcA中,你想要进行分配,所以你要使用malloc,但是如果你要做q = malloc(…q是无效的* q在主函数p中没有被分配。为什么?想到funcB,j持有我的地址,如果你想修改我将做* j = 1,因为如果你会做j = 1,那么你会使指向另一个内存区域,而不是我。它是相同的对funcA问。认为它是< p >类型* q是一个指针类型的p void *,但如果funcB int。现在你想修改地址p是指着,这意味着你不希望修改地址问指着,您想修改地址指向q是指向什么,又名* q和p。

If it isn't yet clear. Try to think of boxes. I have drawn a quick example with funcA of the boxes involved. Each box has a name (within the box), this box is in the virtual memory of the process at an arbitrary address, and each box contain a value. In this view we are in the state where the funcA(&p) has been called and the malloc will be done.

如果还不清楚的话。试着想想盒子。我已经用所涉及的盒子的功能画了一个简单的例子。每个框都有一个名称(在框中),这个框位于进程的虚拟内存中的任意地址,每个框包含一个值。在这个视图中,我们处于调用funcA(&p)并完成malloc的状态。

链表附加项中的指针

#4


2  

Hey why you are thinking it in this way,think when someone is passing structure to append function,in that case whole structure struct node{int data; struct node *link; }; in your case will get copied on stack frame of append function,so better to pass address of structure pointer so as to get just 4 bytes copied onto stack.

嘿,你为什么这么想,想想当有人把结构传递给附加函数时,在这种情况下整个结构结构结构节点{int data;结构节点*链接;};在你的情况下,会被复制到附加函数的堆栈帧上,所以最好将结构指针的地址传递给堆栈,这样就可以只复制4个字节到堆栈上。

#5


2  

You don't need the if/else; in both cases you link the new node to a pointer that was NULL before the operation. This could be the root node, or the ->next node of the last node in the chain. Both are pointers to struct node, and you need a pointer to these pointers in order to assign to them.

你不需要if/else;在这两种情况下,都将新节点链接到操作之前为NULL的指针。这可以是根节点,或者是链中最后一个节点的下一个节点的->。这两个都是指向struct节点的指针,您需要一个指向这些指针的指针,以便分配给它们。

void append( struct node **q, int num){

    while (*q){ q = &(*q)->link; }

    *q = malloc(sizeof **q);
    (*q)->data = num;
    (*q)->link = NULL;

}

Why would anyone do this? Basically because it is shorter, it uses only one loop and no additional conditions, uses no additional variables, and it can be proven to be correct. There should of course a test be added for the result of malloc, which would need an addtional condition.

为什么会有人这么做?因为它更短,它只使用一个循环,没有附加条件,不使用其他变量,并且可以证明它是正确的。当然应该对malloc的结果添加一个测试,这需要一个附加条件。

#6


1  

In essence, as Jite & the others said is correct.

本质上,正如Jite & others所说的那样是正确的。

Whenever you want to have a change applied to a data structure in C (change performed by another function), you need to pass a "reference" to this data structure for the change to persist beyond the change() function completes. This is what happens in Python too, you pass references to Objects unless you explicitly make a copy. In C you have to specify what you want to do precisely. To simplify it even more, it's either:

每当您希望对C中的数据结构应用更改(由另一个函数执行的更改)时,您都需要向该数据结构传递一个“引用”,以便更改在change()函数完成之后继续存在。这也是在Python中发生的情况,您将引用传递给对象,除非您显式地进行复制。在C语言中,你必须精确地指定你想要做什么。为了进一步简化,它是:

type data_struct

类型data_struct

change(data_struct) => here's a copy of my data_struct, make your change but I do not care in the caller function about the change you apply

change(data_struct) =>这是我的data_struct的副本,进行您的更改,但是我不关心调用者函数中您所应用的更改

or

change(&data_struct) => here's the address (a "reference" to) of my data_struct, apply your change, the caller function will see this change after it is applied.

change(&data_struct) =>,这里是data_struct的地址(“引用”),应用您的更改,调用函数在应用后将看到这个更改。

Now, depending of what the original "type" is, you might have * or **. Nevertheless, keep in mind that there is a limit to how many "indirections" you can have, not sure weather it is system or compiler determined, if anyone has an answer to that I'm a taker. I never had more than 3 indirections.

现在,根据原始的“类型”是什么,您可能有*或**。尽管如此,请记住,您可以有多少“间接”是有限制的,不确定它是由系统还是编译器决定的,如果有人知道我是一个接受者的话。我从来没有超过3个方向。

#7


0  

I think reason is as follows :

我认为原因如下:

struct node *p; //pointer to node structure p = NULL;

结构节点* p;//节点结构指针p = NULL;

Above snippet when written in main block , means value of pointer p is NULL so it does not point to anything in memory . So we pass address of pointer p in order to create a new node and assign the address of new node to the value of pointer p .

上面的代码片段在主块中写入时,意味着指针的值为NULL,所以它不会指向内存中的任何东西。因此,我们通过指针p的地址来创建一个新节点,并将新节点的地址分配给指针p的值。

*(&p) == *q == temp;

*(&p) = *q = temp;

By doing *q == temp; we achieve our goal of assigning some value to pointer p which was originally pointing nowhere .

通过做*q == temp;我们的目标是为指针p赋值,而指针p最初指向的是空的。