2013-03-06 111 views
7

我通常使用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; 
    } 
} 

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

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

我已经使用了这个问题。答案要么太难理解(因为我只是一个C初学者)或太简单。我很感激任何提示,评论或链接,我可以阅读这些内容。

回答

16

当您将事物传递给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()中,我们传递了一个指针指针的副本。这个指针包含我们原始指针的地址,瞧,我们现在可以引用原始指针并且改变原始指针应该指向的地方。

希望它能多解释一下:)

+0

ahhhh!这真的很有帮助。谢谢! :-) – seb 2013-03-06 10:27:47

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总是用来填充一个连接节点添加到列表中,首先是头节点。

+0

关于价值/参考的很好的解释。 – Jite 2013-03-06 10:29:26

+0

好吧,我在这。它会花费我一分多钟。感谢您提前举办的伟大榜样! – seb 2013-03-06 10:43:19

+0

那么,曾经有一个。 32分钟前,我似乎没有刷新(或者我还记得它,这是相对不太可能;-) – wildplasser 2013-03-06 11:11:22

2

嘿嘿你为什么这么想呢,想想有人在传递结构来追加函数,那么整个结构struct node{int data; struct node *link; };在你的情况下会被拷贝到append function的栈帧上,所以最好传递结构指针的地址以便只将4个字节复制到堆栈中。

2

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

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

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

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

} 

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

+0

空指针不保证标准具有全零位表示,IIRC。 (整数和ISO浮点数) – wildplasser 2013-03-06 10:40:14

+0

这是真的,我纠正了(实际上我现在坐着)。 NULL被定义为0,并且将“正确地”比较为一个空指针,但它们不是*同义词(如果你曾经在AS/400上工作过,你知道这是真的。*惊人的*指针结构)。我将放弃我的评论。感谢您的帮助。 – WhozCraig 2013-03-06 10:42:40

+0

不,NULL不能保证全零;在源代码中,将0常量强制转换为指针类型将被解释为NULL指针,编译器将使用该平台的表示形式指向NULL指针。 (其中*在大多数情况下*全部为零) – wildplasser 2013-03-06 10:47:31

1

实质上,正如Jite &其他人所说的是正确的。您需要将一个“引用”传递给此数据结构,以使更改持久到change()函数完成后继续执行。这也是Python中发生的事情,除非您明确地创建副本,否则将对象的引用传递给对象。在C中,你必须指定你想要做什么。为了简化甚至更多,这是两种:

型data_struct

变化(data_struct)=>这里是我data_struct的副本,让你的改变,但我不会在有关修改调用函数应用

关心这里

变化(& data_struct)=>是地址我data_struct的(“参考”来),应用更改,应用它后调用函数就会看到这种变化。

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

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 = NULLint i = 0类似。现在如果您通过&i您不通过0的地址,您通过i的地址。 &p同样的事情发生在你传递指针的地址。

现在在funcA中,你想做分配,所以你用malloc但是如果你想做q = malloc(...而q应该是void* q在主函数中p就不会被分配。为什么?想想funcB,j拥有我的地址,如果你想修改我,你会做*j = 1,因为如果你想做j = 1那么你会让j指向另一个内存区域而不是i。与funcA的q相同。认为它是<type of p>* q它是一个指向void *类型的p的指针,但是在funcB的情况下它是一个int。现在你想要修改p指向的地址,这意味着你不想修改指向的地址q,你想修改q所指向的指向地址,即*qp

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

enter image description here

+0

真的很有帮助!谢谢! – seb 2013-03-06 11:51:05

+0

顺便说一下:'sizeof(void)'是零或者是一个错误。很可能你的意思是'sizeof(void *)'? – wildplasser 2013-03-10 12:07:06

+0

由于我用C语言编写的,太长了;-)谢谢,我会更新答案。 – Huygens 2013-03-10 13:18:53

0

我认为原因如下:

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

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

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

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