为什么我们可以简单地创建链接列表时创建堆?

时间:2014-09-20 23:45:36

标签: c pointers struct linked-list

我正在研究this lesson.

的链接列表

编写器(以及每个教程中的所有其他编码器)都会创建节点类型指针变量,然后使用类型转换和malloc为它们分配内存。对我来说似乎有点不必要(关于我知道我遗漏了什么东西),为什么我们不能用这个实现同样的呢?

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

int main()
{
  struct node head;
  struct node second;
  struct node third;

  head.data = 1;
  head.next = &second;

  second.data = 2;
  second.next = &third;

  third.data = 3;
  third.next = NULL;

  getchar();
  return 0;
}

我创建了节点,下一个指针指向下一个节点的地址......

7 个答案:

答案 0 :(得分:4)

我们假设您创建了一个名为node的{​​{1}}类型的变量:

my_node

您可以将其成员作为struct node my_node;my_node.data来访问,因为它不是指针。但是,您的代码只能创建3个节点。假设您有一个循环,要求用户输入一个数字并将该数字存储在链接列表中,仅在用户键入0时停止。您不知道用户何时输入0,所以你必须有一种在程序运行时创建变量的方法。 "创建变量"在运行时称为动态内存分配,并通过调用my_node.next来完成,它始终返回一个指针。不要忘记在不再需要之后释放动态分配的数据,为此,请使用malloc返回的指针调用free函数。您提到的教程只是解释链表的基本概念,在实际程序中,您不会将自己局限于固定数量的节点,而是根据您在运行时只有的信息使链表可调整大小(除非你需要一个固定大小的链表)。

修改

"在运行时创建变量"只是一种高度简化的解释指针需求的方式。当您调用malloc时,它会在堆上分配内存并为您提供一个地址,您必须将该地址存储在指针中。

malloc

在这种情况下,int var = 5; int * ptr = &var; 是一个变量(它的所有荣耀都被声明),它保存另一个变量的地址,因此它被称为指针。现在考虑一下你提到的教程的摘录:

ptr

在这种情况下,变量struct node* head = NULL; head = (struct node*)malloc(sizeof(struct node)); 将指向在运行时在堆上分配的数据。

如果继续在堆上分配节点并将返回的地址分配给链表中最后一个节点的head成员,则只需编写{{1}即可遍历链表。 }。例如:

next

当然,您可以将元素插入到链接列表的任何位置,而不仅仅是如上所述。请注意,您唯一拥有名称的节点是pointer_to_node = pointer_to_node->next,其他所有节点都是通过指针访问的,因为您无法命名程序将拥有的所有节点。

答案 1 :(得分:3)

当然你可以这样做。但到目前为止?你要构造多少个节点?当我们不知道我们有多少条目时,我们会使用链接列表。那么如何构建节点呢?多少 ? 这就是我们使用malloc的原因。

答案 2 :(得分:3)

当您声明' struct node xyz;'在函数中,只有存在该函数时才存在。如果将其添加到链接列表然后退出该函数,该对象将不再存在,但链接列表仍然具有对它的引用。另一方面,如果从堆中分配它并将其添加到链表中,它将一直存在,直到从链表中删除并删除它。

此机制允许在整个程序中的不同时间创建任意数量的节点并将其插入到链接列表中。您在上面显示的方法只允许固定数量的特定项目在列表中放置一小段时间。你可以做到这一点,但它没有什么用处,因为你可以直接访问列表之外的项目。

答案 3 :(得分:2)

但是,如果您的文件包含未知数量的条目,并且您需要迭代它们,将每个条目添加到链接列表,该怎么办?想想如果没有malloc,你可以如何做到这一点。

您将拥有一个循环,并且在每次迭代中,您需要创建一个与所有其他节点不同的全新节点“实例”。如果你只有一堆本地人,每次循环迭代他们仍然是相同的本地人。

答案 4 :(得分:2)

只要您事先知道所需的节点数,您的代码和方法就是正确的。但是,在许多情况下,节点数量取决于用户输入,并且事先不知道。

你必须在C和C ++之间做出决定,因为类型转换和malloc只属于C语言。您的C ++链表代码不会进行类型转换,也不会使用malloc,因为它不是C代码,而是C ++代码。

答案 5 :(得分:0)

假设您正在编写文本编辑器等应用程序。该应用程序的作者不知道用户将来可能想要编辑多大的文件。

使编辑器始终使用大量内存在多任务环境中没有用,尤其是那些拥有大量用户的环境。

使用 malloc(),编辑应用程序可以根据需要从堆中获取额外的内存量,不同的进程使用不同的内存量,而不会浪费大量内存。

答案 6 :(得分:0)

你可以,你可以利用这种技术创建这样的可爱代码,以某种方式将堆栈用作malloc:

假设没有启用尾部优化,下面的代码应该足够安全。

#include <stdio.h>

typedef struct node_t {
    struct node_t *next;
    int cur;
    int n;
} node_t;

void factorial(node_t *state, void (*then)(node_t *))
{
    node_t tmp;

    if (state->n <= 1) {
        then(state);
    } else {
        tmp.next = state;
        tmp.cur = state->n * state->cur;
        tmp.n = state->n - 1;
        printf("down: %x %d %d.\n", tmp);
        factorial(&tmp, then);
        printf("up: %x %d %d.\n", tmp);
    }
}

void andThen(node_t *result)
{    
    while (result != (node_t *)0) {
        printf("printing: %x %d %d.\n", *result);
        result = result->next;
    }
}

int main(int argc, char **argv)
{
    node_t initial_state;
    node_t *result_state;
    initial_state.next = (node_t *)0;
    initial_state.n = 6; // factorial of
    initial_state.cur = 1; // identity for factorial

    factorial(&initial_state, andThen);
}

结果:

$ ./fact
down: 28ff34 6 5.
down: 28ff04 30 4.
down: 28fed4 120 3.
down: 28fea4 360 2.
down: 28fe74 720 1.
printing: 28fe74 720 1.
printing: 28fea4 360 2.
printing: 28fed4 120 3.
printing: 28ff04 30 4.
printing: 28ff34 6 5.
printing: 0 1 6.
up: 28fe74 720 1.
up: 28fea4 360 2.
up: 28fed4 120 3.
up: 28ff04 30 4.
up: 28ff34 6 5.

factorial的工作方式与平常不同,因为我们无法将结果返回给调用者,因为调用者将使用任何单个堆栈操作使其无效。单个函数调用会破坏结果,所以相反,我们必须将它传递给另一个函数,该函数将在当前结果的顶部有自己的框架,这不会使它所在的任意数量的堆栈框架无效。节点

我想有很多方法可以打破尾部调用优化以外的其他方法,但是当它没有时它会非常优雅,因为链接可以保证相当缓存本地,因为它们彼此非常接近,并且任意大小的连续分配都不需要malloc / free,因为一旦返回就会清理所有内容。