首先,关于你的结构的声明和你似乎想要的指针 typedef,有很多方法可以做到这一点。以下将在 C 或 C++ 中工作。
// declare NodePtr as a pointer to Node, currently an incomplete type
// C and C++ both allow you to declare a pointer to damn-near anything
// so long as there is an understanding of what it *will* be, in this
// case, a structure called Node.
typedef struct Node *NodePtr;
// Now declare the structure type itself
struct Node
{
int x;
NodePtr next;
};
也就是说,老实说,我不建议这样做。大多数工程师想要一个清晰且语法可见的定义,让他们尖叫,“这是一个指针!” 你可能不一样。我个人更喜欢这个:
struct Node
{
int x;
struct Node *next; // omit the 'struct' for C++-only usage
};
只要您以及同样重要的其他工程师阅读您的代码,了解您NodePtr
作为指向节点的指针的用法,然后选择最适合您的情况。指针类型声明对某些人来说几乎是宗教性的,所以请记住这一点。有些人更喜欢看到那些星号(我就是其中之一),有些人可能不喜欢(听起来像你=P)。
注意:有一个地方使用typedef
ed 指针类型可以避免潜在的错误:多变量声明。考虑一下:
Node* a, b; // declares one Node* (a), and one Node (b)
有一个typedef struct Node *NodePtr;
允许这样做:
NodePtr a, b; // declares two Node*; both (a) and (b)
如果您花足够的时间用 C 编写代码,那么前者会在您学会不犯该错误的情况下反复咬您一口,但它仍然会偶尔发生。
负载循环
关于将列表拼凑在一起的加载循环,您没有正确连接列表,坦率地说,有一百万种方法可以做到这一点,其中一种是下面的方法。这不需要您清除“额外的节点”。它也不需要任何if (head){} else{}
块结构来避免上述相同的情况。考虑一下我们真正想做的事情:创建节点并将它们的地址分配给正确的指针:
NodePtr head = NULL; // always the head of the list.
NodePtr* ptr = &head; // will always point to the next pointer to assign.
int n;
while (cin >> n)
{
*ptr = new Node;
(*ptr)->x = n;
ptr = &(*ptr)->next;
}
// note this always terminates the load with a NULL tail.
(*ptr)->next = NULL;
这个怎么运作
- 将头指针初始化为NULL
- 初始化节点指针指针(是的指针指针)以指向头指针。该指针对指针将始终保存目标指针的地址,该地址将接收下一个动态分配节点的地址。最初,这将是头指针。在上面的代码中,这个指向指针的指针就是变量:
ptr
.
- 开始while循环。
ptr
对于读取的每个值,分配一个新节点,将其保存在由(因此)指向的指针中*ptr
。在第一次迭代中,它保存了head
指针的地址,因此head
变量将获得我们的新节点分配。在所有后续迭代中,它包含插入的最后一个节点next
的指针的地址。顺便说一句,保存这个新目标指针的地址是我们进入下一个分配周期之前在循环中完成的最后一件事。
- 循环完成后,插入的最后一个节点需要将其
next
指针设置为 NULL 以确保正确终止的链表。这是强制性的。我们很方便地有一个指向该指针的指针(我们一直在使用的那个指针),因此我们将它“指向”的指针设置为 NULL。我们的列表已终止,我们的加载已完成。Brain Food:如果加载循环从未加载任何节点,它将指向什么指针?答案:&head
,如果我们的列表为空,这正是我们想要的(一个NULL
头指针)。
设计
我希望这将有助于更好地解释它是如何通过循环的三个完整迭代来工作的。
初始配置
head ===> NULL;
ptr --^
一次迭代后:
head ===> node(1)
next
ptr ------^
两次迭代后
head ===> node(1)
next ===> node(2)
next
ptr ----------------^
三轮迭代后
head ===> node(1)
next ===> node(2)
next ===> node(3)
next
ptr --------------------------^
如果我们在 3 次迭代中停止,最终的终止赋值 ( *ptr = NULL;
) 给出:
head ===> node(1)
next ===> node(2)
next ===> node(3)
next ===> NULL;
ptr --------------------------^
请注意,head
一旦第一次迭代完成,就永远不会改变(它总是指向第一个节点)。另请注意,ptr
始终保存要填充的下一个指针的地址,在初始迭代之后(它从我们的头指针的地址开始),将始终是添加的最后一个节点中next
指针的地址。
我希望这能给你一些想法。值得注意的是,将这两个指针(head
指针和ptr
指针)配对成各自的结构并具有适当的管理功能定义了教科书Queue;其中一端仅用于插入 ( ptr
) 一端用于提取 ( head
) 并且容器不允许随机访问。如今,对于标准库容器适配器(如std::queue<>
.
完整的工作样本
这个示例只加载我们的队列,包含 20 个元素,打印它们,然后清理队列并退出。根据需要适应您的使用情况(提示:比如更改传入数据的来源)
#include <iostream>
using namespace std;
// declare NodePtr as a pointer to Node, currently an incomplete type
// C and C++ both allow you to declare a pointer to damn-near anything
// so long as there is an understanding of what it *will* be, in this
// case, a structure called Node.
typedef struct Node *NodePtr;
// Now declare the structure type itself
struct Node
{
int x;
NodePtr next;
};
int main()
{
// load our list with 20 elements
NodePtr head = NULL;
NodePtr* ptr = &head;
for (int n=1;n<=20;++n)
{
*ptr = new Node;
(*ptr)->x = n;
ptr = &(*ptr)->next;
}
// terminate the list.
*ptr = NULL;
// walk the list, printing each element
NodePtr p = head;
while (p)
{
cout << p->x << ' ';
p = p->next;
}
cout << endl;
// free the list
while (head)
{
NodePtr victim = head;
head = head->next;
delete victim;
}
return 0;
}
输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20