我正在研究本课的链表。
编写器(以及每个教程中的所有其他编码人员(通过创建节点类型指针变量,然后使用类型转换和 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;
}
我已经创建了节点,下一个指针指向下一个节点的地址......
假设您创建了一个名为 my_node
的 node
类型的变量:
struct node my_node;
您可以my_node.data
和my_node.next
访问其成员,因为它不是指针。但是,您的代码只能创建 3 个节点。假设您有一个循环,要求用户输入一个数字并将该数字存储在链表中,仅当用户键入 0 时才停止。您不知道用户何时会输入 0,因此您必须有一种在程序运行时创建变量的方法。在运行时"创建变量"称为动态内存分配,通过调用 malloc
来完成,它总是返回一个指针。不要忘记在不再需要动态分配的数据后释放它,为此使用 malloc
返回的指针调用 free
函数。你提到的教程只是解释了链表的基本概念,在实际程序中,你不会将自己限制在固定数量的节点上,而是根据你只在运行时拥有的信息使链表的大小可调整(除非一个固定大小的链表是你所需要的(。
编辑:
"在运行时创建变量"只是解释指针需求的一种高度简化的方式。当您调用 malloc
时,它会在堆上分配内存并为您提供一个地址,您必须将其存储在指针中。
int var = 5;
int * ptr = &var;
在这种情况下,ptr
是一个变量(它被声明了它的所有荣耀(,它保存另一个变量的地址,因此它被称为指针。现在考虑一下您提到的教程的摘录:
struct node* head = NULL;
head = (struct node*)malloc(sizeof(struct node));
在这种情况下,变量head
将指向运行时在堆上分配的数据。
如果您继续在堆上分配节点并将返回的地址分配给链表中最后一个节点的next
成员,则只需编写 pointer_to_node = pointer_to_node->next
即可遍历链表。例:
struct node * my_node = head; // my_node points to the first node in the linked list
while (true)
{
printf("%dn", my_node->data); // print the data of the node we're iterating over
my_node = my_node->next; // advance the my_node pointer to the next node
if (my_node->next == NULL) // let's assume that the 'next' member of the last node is always set to NULL
{
printf("%dn", my_node->data);
break;
}
}
当然,您可以将元素插入到链表的任何位置,而不仅仅是我上面提到的末尾。请注意,虽然您唯一有名称的节点是 head
,但所有其他节点都通过指针访问,因为您不可能命名程序将永远保留的所有节点。
当你在函数中声明"struct node xyz;"时,它只存在于该函数存在的时候。 如果将其添加到链表,然后退出函数,则该对象将不再存在,但链表仍具有对它的引用。 另一方面,如果您从堆中分配它并将其添加到链表中,它将仍然存在,直到它从链表中删除并删除。
此机制允许在整个程序的不同时间创建任意数量的节点并将其插入到链表中。 上面显示的方法仅允许在短时间内将固定数量的特定项目放置在列表中。 您可以这样做,但它几乎没有用处,因为您可能只是直接访问列表之外的项目。
当然,你可以这样做。 但是多远? 您要创建多少个节点?当我们在创建列表时不知道需要多少条目时,我们使用链接列表。那么如何创建节点呢?多少?这就是我们使用malloc()
(或new
节点(的原因。
但是,如果你有一个包含未知数量条目的文件,并且你需要迭代它们,将每个条目添加到链表中,该怎么办?想想如何在没有malloc
的情况下做到这一点。
您将有一个循环,在每次迭代中,您需要创建一个全新的节点"实例",与所有其他节点不同。如果你只有一堆局部变量,每次循环迭代它们仍然是相同的局部变量。
只要您事先知道所需的节点数,您的代码和方法就是正确的。但是,在许多情况下,节点的数量取决于用户输入,并且事先并不知道。
你肯定必须在 C 和 C++ 之间做出选择,因为类型转换和 malloc 只属于 C 语言。您的C++链表代码不会进行类型转换或使用malloc
,因为它不是 C 代码,而是C++代码。
假设您正在编写文本编辑器等应用程序。 应用程序的编写者不知道用户将来可能想要编辑多大的文件。
使编辑器始终使用大量内存在多任务环境中没有帮助,尤其是在具有大量用户的环境中。
使用malloc((,编辑应用程序可以根据需要从堆中获取额外的内存量,不同的进程使用不同的内存量,而不会浪费大量内存。
你可以,你可以利用这种技术来创建像这样的可爱代码,以某种方式将堆栈用作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.
阶乘的工作方式与平时不同,因为我们无法将结果返回给调用方,因为调用方将使用任何单个堆栈操作使其无效。 单个函数调用将破坏结果,因此,我们必须将其传递给另一个函数,该函数将在当前结果之上拥有自己的帧,这不会使它位于其上的任意数量的堆栈帧无效,这些堆栈帧位于其上,可以容纳我们的节点。
我想除了尾部调用优化之外,还有很多方法可以打破这一点,但是当它不这样做时,它真的很优雅,因为链接保证是相当本地缓存的,因为它们彼此相当接近,并且任意大小的连续分配不需要malloc/free,因为一旦返回发生,一切都会被清理。
假设您正在制作一个像 CHROME 网络浏览器这样的应用程序,那么您希望在运行时用户创建的选项卡之间创建链接,这只有在使用动态内存分配时才可能。这就是为什么我们使用 new
、 malloc()
等来应用动态内存分配。☺ :)。