8

我通常用python编程。为了提高我的模拟性能,我正在学习 C。在将附加函数实现到链表时,我很难理解指针的指针的使用。这是我的书 (Understanding Pointers in C by Kanetkar) 中代码的摘录。

#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;
    }
}

在这段代码中,我将双指针 **q 传递给 append 函数。我知道这是地址的地址,即在这种情况下为 NULL 的地址。

我只是不明白为什么有人这样做。从 append() 函数中的所有内容中删除一个 * 运算符并简单地将 NULL 的地址(即 p 而不是 &p)传递给 append() 函数是否无效?

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

4

7 回答 7

17

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

快速示例:

#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;
}

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

在第二个函数调用中really_change(),我们得到了一个指向指针的副本。该指针包含指向我们原始指针的地址,瞧,我们现在可以引用原始指针并更改原始指针应指向的位置。

希望它能解释得更多:)

于 2013-03-06T10:22:23.067 回答
6

首先它不是“地址的地址”。它是指针变量的地址。例如:如果您传递包含零的int变量的地址,则您不会传递零地址;n您正在传递一个变量的地址(在这种情况下是一个int变量,在您的情况下是一个指针变量)。变量在内存中有地址。在这种情况下,参数是恰好是指针变量的变量的地址,即列表的头部。

关于为什么要这样做?简单的。C 中的所有变量(不承受指针衰减的数组)都是按传递的。如果你想通过引用(地址)来修改一些东西,那么你需要传递的“值”必须是一个地址,并且接收它的形参必须是一个指针类型。简而言之,您使传递的“值”成为一个内存地址,而不仅仅是一个基本的定标器值。然后该函数使用它(通过形式指针参数)相应地存储数据。可以把它想象成“把我想要的东西放在‘这个’内存地址”。

举个简短​​的例子,假设您想遍历一个文件,将其中的每个字符附加到节点的前向链接列表中。您不会使用您所拥有的附加方法(请参阅The Painter's Algorithm了解原因)。看看您是否可以遵循此代码,该代码使用指针到指针,但没有函数调用。

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;
}

盯着看一会儿,看看你是否能理解指针指向指针next是如何用于始终填充要添加到列表中的下一个链接节点的,从头节点开始。

于 2013-03-06T10:22:43.097 回答
3

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

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;
}

此代码以这种方式完成,因此子函数funcA可以分配p指针。首先,考虑void* p好像它在哪里int i。你所做的事情p = NULL在某种程度上与int i = 0. 现在,如果您通过&i,您不通过地址0,而是通过地址i&p传递指针的地址也会发生同样的事情。

现在在 funcA 中,您想要进行分配,所以您使用malloc但如果您这样做q = malloc(...并且 q 本来void* q在主函数中,p 将不会被分配。为什么?想一想funcB,j 持有 i 的地址,如果你想修改 i,你会这样做*j = 1,因为如果你这样做,j = 1那么你会让 j 指向另一个内存区域而不是 i。funcA 的 q 也一样。认为<type of p>* q它是指向 p 类型的指针,它是 void*,但对于 funcB,它是一个 int。现在你要修改 p 所指向的地址,也就是说你不想修改 q 所指向的地址,你要修改 q 所指向的地址,也就是*qor p

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

在此处输入图像描述

于 2013-03-06T10:43:14.903 回答
2

嘿,为什么你这样想,想想当有人将结构传递给附加函数时,在这种情况下struct node{int data; struct node *link; };,你的整个结构将被复制到堆栈帧上append function,所以最好传递结构指针的地址以便得到 4字节复制到堆栈上。

于 2013-03-06T10:23:39.560 回答
2

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

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

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

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

}

为什么会有人这样做?基本上因为比较短,所以只用了一个循环,没有附加条件,没有使用附加变量,可以证明是正确的。当然应该为 malloc 的结果添加一个测试,这需要一个附加条件。

于 2013-03-06T10:36:21.770 回答
1

本质上,正如 Jite 和其他人所说的那样是正确的。

每当您想将更改应用于 C 中的数据结构(由另一个函数执行的更改)时,您需要传递对此数据结构的“引用”,以使更改在 change() 函数完成后持续存在。这也是在 Python 中发生的事情,除非您显式地制作副本,否则您将引用传递给对象。在 C 语言中,您必须精确地指定要执行的操作。为了进一步简化它,它是:

类型 data_struct

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

或者

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

现在,根据原始“类型”是什么,您可能有 * 或 **。不过,请记住,您可以拥有多少“间接”是有限制的,不确定天气是否由系统或编译器确定,如果有人回答我是接受者的话。我从来没有超过3次间接。

于 2013-03-06T10:36:59.783 回答
0

我认为原因如下:

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

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

*(&p) == *q == 温度;

通过做 *q == temp; 我们实现了为指针 p 分配一些值的目标,该指针最初指向无处。

于 2014-12-17T17:40:38.730 回答