我通常在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而不是&amp; p)传递给append()函数是否无效?
我用Google搜索了这个问题。答案要么太难理解(因为我只是一个C初学者),要么太简单了。我很感谢任何提示,评论或链接,我可以在这里阅读。
答案 0 :(得分: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()
中,我们传递了一个指向指针的副本。这个指针包含我们原始指针的地址和瞧,我们现在可以引用原始指针并改变原始指针应指向的位置。
希望它能解释得更多:)
答案 1 :(得分: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
来始终填充要添加到列表中的下一个链接节点,从头节点开始。
答案 2 :(得分: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在主函数p中已经void* q
就不会有funcB
被分配了。为什么?想想*j = 1
,j保存i的地址,如果你想修改i,那么你会j = 1
,因为如果你做<type of p>* q
那么你会把j指向另一个内存区域而不是i。对于funcA,它与q相同。把它想象为*q
它是指向p的类型的指针,它是void *,但是在funcB的情况下,它是一个int。现在你要修改p指向的地址,这意味着你不想修改q所指向的地址,你想要修改指向q的指针地址,又名p
或{ {1}}。
如果尚不清楚。试着想一下箱子。我已经用所涉及框的funcA绘制了一个快速示例。每个框都有一个名称(在框内),此框位于任意地址的进程的虚拟内存中,每个框都包含一个值。在此视图中,我们处于调用funcA(&p)
并且将完成malloc的状态。
答案 3 :(得分:2)
嘿,为什么你以这种方式思考它,想想当有人传递结构来追加函数时,在这种情况下,你的案例中的整个结构struct node{int data; struct node *link; };
将被复制到append function
的堆栈框架上,所以最好传递结构指针的地址,以便只将4个字节复制到堆栈上。
答案 4 :(得分:2)
你不需要if / else;在这两种情况下,您都会将新节点链接到操作前为NULL的指针。这可以是根节点,也可以是链中最后一个节点的 - &gt;下一个节点。两者都是struct node的指针,你需要一个指针指向这些指针才能分配给它们。
void append( struct node **q, int num){
while (*q){ q = &(*q)->link; }
*q = malloc(sizeof **q);
(*q)->data = num;
(*q)->link = NULL;
}
为什么有人会这样做?基本上因为它更短,它只使用一个循环而没有附加条件,不使用其他变量,并且可以证明它是正确的。 当然应该为malloc的结果添加一个测试,这需要一个额外的条件。
答案 5 :(得分:1)
本质上,正如Jite&amp;其他人说的是对的。
每当您想要对C中的数据结构应用更改(由另一个函数执行更改)时,您需要将“引用”传递给此数据结构,以便更改在change()函数完成之后保持不变。这也是在Python中发生的事情,除非您明确地复制,否则将对对象的引用传递给对象。在C中,您必须精确指定要执行的操作。为了简化它,它可以是:
输入data_struct
change(data_struct)=&gt;这是我的data_struct的副本,进行更改,但我不关心调用函数有关您应用的更改
或
更改(&amp; data_struct)=&gt;这是我的data_struct的地址(“引用”),应用您的更改,调用函数将在应用后看到此更改。
现在,根据原始“类型”的不同,您可能有*或**。然而,请记住,你可以拥有多少“间接”,但不确定天气是系统或编译器确定的,如果有人对我是一个接受者有答案。我从来没有超过3个间接。
答案 6 :(得分:0)
我认为原因如下:
struct node * p; //指向节点结构的指针 p = NULL;
在主块中写入上面的代码片段,表示指针p的值为NULL,因此它不指向内存中的任何内容。所以我们传递指针p的地址,以便创建一个新节点,并将新节点的地址分配给指针p的值。
*(&amp; p)== * q == temp;
通过做* q == temp;我们实现了为指针p分配一些值的目标,这个指针最初指向了无处。