【问题标题】:pointer of a pointer in linked list append链表中指针的指针追加
【发布时间】:2013-03-06 10:12:49
【问题描述】:

我通常在 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 初学者),要么太简单。感谢您提供的任何提示、cmets 或链接,我可以从中阅读。

【问题讨论】:

    标签: c list pointers


    【解决方案1】:

    当你将东西传递给 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(),我们得到了一个指向指针的副本。这个指针包含了我们原始指针的地址,瞧,我们现在可以引用原始指针并更改原始指针应该指向的位置。

    希望它能解释得更多:)

    【讨论】:

      【解决方案2】:

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

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

      作为一个简短的示例,假设您想要遍历一个文件,将其中的每个字符附加到一个 forward 节点链接列表中。您将使用您所拥有的附加方法(请参阅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 是如何用于始终填充要添加到列表中的下一个链接节点,从头节点开始。

      【讨论】:

      • 关于价值/参考的很好的解释。
      • 好的,我会的。我要花一分钟多的时间。不过提前感谢您提供的好例子!
      • 嗯,曾经有一个。 32 分钟前,我似乎没有刷新(或者我还记得它,这是相对不可能的 ;-)
      • @wildplasser 是的,我在使用 IE 时使用的 winbox 只是自动刷新这些页面。我的 macbook air 上的 chrome 和 safari 都更加流畅。在工作中更新 SO 有时会让我发疯。
      【解决方案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 相似。现在,如果您传递&amp;i,您不会传递0 的地址,而是传递i 的地址。 &amp;p 也会发生同样的情况,您传递了指针的地址。

      现在在 funcA 中,您要进行分配,因此您使用 malloc,但如果您要使用 q = malloc(... 并且 q 在主函数中将是 void* q,则不会分配 p。为什么?想想funcB,j 保存着 i 的地址,如果你想修改 i,你会做*j = 1,因为如果你会做j = 1,那么你会让 j 指向另一个内存区域而不是 i。 funcA 的 q 也一样。把它想象成&lt;type of p&gt;* q 它是一个指向p 类型的指针,它是void*,但在funcB 的情况下它是一个int。现在你要修改p指向的地址,表示你不想修改q指向的地址,你想修改q指向的地址,也就是*q或者p .

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

      【讨论】:

      • 顺便说一句:sizeof(void) 是零或错误。很可能您的意思是sizeof (void*)
      • 太久没有用 C 编码了 ;-) 谢谢,我会更新答案。
      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        你不需要 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的结果当然要加一个测试,这需要一个额外的条件。

        【讨论】:

        • 标准不保证空指针具有全零表示,IIRC。 (整数和 ISO 浮点数)
        • 这是真的,我的立场是正确的(实际上我现在正坐着)。 NULL 被定义为 0,并且会正确地与空指针“比较”,但它们不是同义词(如果你曾经在 AS/400 上工作过,你就会知道这是真的。惊人的指针架构)。我将放弃我的评论。谢谢你把我说对了。
        • 不,NULL 不保证是全零;在源代码中,转换为指针类型的 0 常量被解释为 NULL 指针,编译器将使用平台对 NULL 指针的表示。 (在大多数情况下都是零)
        【解决方案6】:

        本质上,正如吉特和其他人所说的那样。

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

        输入数据结构

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

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

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

        【讨论】:

          【解决方案7】:

          我认为原因如下:

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

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

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

          通过 *q == temp;我们实现了为指针 p 分配一些值的目标,该指针 p 最初没有指向任何地方。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-08-11
            • 2018-07-28
            • 2013-08-07
            • 1970-01-01
            • 2020-01-22
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多