我正在尝试自学带有节点结构的链接列表,并希望有人可以帮助我解决这个问题。我会从命令行获取输入,它会让我成为一个嵌套列表,我可以输出它。
例:
输入:"1 2 3 4 5"
输出:"1 2 3 4 5"
我遇到了两件事:1)当我运行程序时,我不断收到警告:"typedef"在此声明中被忽略[默认启用]我怎样才能摆脱这个?
编辑:我已将其更改为typedef struct Node* NodePtr;
2)我的代码无法正常工作。我该如何解决这个问题?我正在尝试C++自学链表。
typedef struct Node;
typedef Node* NodePtr;
struct Node{
int x;
NodePtr next;
};
int main ()
{
int n;
NodePtr head, ptr = NULL;
head = ptr;
while (cin >> n){
ptr = new Node;
ptr->x = n;
ptr->next = NULL;
ptr = ptr->next;
}
NodePtr bling = head;
while(bling != NULL){
cout << bling->x << endl;
bling = bling->next;
}
return 0;
}
理想情况下,我想做的是制作如下所示的链接列表。
1 -> 2 -> 3 -> NULL.
首先,关于结构的声明和你似乎想要的指针类型def,有很多方法可以做到这一点。以下内容将在 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 --------------------------^
如果我们在三次迭代时停止,则最终终止分配 ( *ptr = NULL;
) 给出:
head ===> node(1)
next ===> node(2)
next ===> node(3)
next ===> NULL;
ptr --------------------------^
请注意,第一次迭代完成后,head
永远不会更改(它始终指向第一个节点)。另请注意,ptr
始终保存要填充的下一个指针的地址,在初始迭代之后(它作为头指针的地址开始),它将始终是最后一个添加的节点中next
指针的地址。
我希望这能给你一些想法。值得注意的是,将这两个指针(head
指针和ptr
指针)配对到它们自己的结构中并具有适当的管理功能定义了教科书队列;其中一端仅用于插入(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
您实际上并没有将"head"变量设置为超出NULL(head = ptr)。你实际上从一开始就失去了你的清单。试试这个:
int n;
NodePtr head, ptr;
ptr = new Node;
head = ptr;
while (cin >> n){
ptr->x = n;
ptr->next = new Node;
ptr = ptr->next;
}
然后,您可能希望删除最后一个 ptr->next 并将其设置为 0 以节省内存并避免打印出额外的值。
首先,你的 typedef : typedef struct Node;
不正确。你用 sintaxe 声明一个结构:
struct st_Name{
int field1;
double field2;
};
Typedef 就像一个名称归属函数。您应该(为了便于阅读/工作)更改结构的名称,在声明它之后,可以使用:
typedef struct Node_st Node;
或者,在一个语句中:
typedef struct node_st{
int x;
struct node_st *next;
}Node;
在上面的部分中,请注意,我还将NodePtr
更改为 struct node_st *Next
是有原因的:如果你在一个段中执行所有操作,因为 c++ 代码是线性读取的自上而下>向下(有点),如果你尝试在结构声明之前NodePtr
指向Node
的指针,你会得到一个错误,如果你稍后这样做并在结构中使用NodePtr
,你也会有一个错误。编译器还不知道结构体是否存在,因此它会说您正在尝试命名不存在的内容。
然后,您的指针操作是错误的。
...
while (cin >> n){
ptr = new Node;
ptr->x = n;
ptr->next = NULL; // You're making the next slot inexistent/NULL
ptr = ptr->next; // You're just setting ptr to NULL
}
...
我认为你想要的是继续在末尾添加将头部保持在第一个数字中。对于第一种情况,您可以简单地使用 if/else 语句来执行此操作:
while (cin >> n){
if(head == NULL){
ptr = new Node;
ptr->x = n;
ptr->next = NULL;
head = ptr;
}else{
ptr->next = new Node;
ptr = ptr->next;
ptr->x = n;
ptr->next = NULL;
}
}
这样,您的插入最终会发生,因此您的打印循环应该可以工作。
希望这有帮助。
您没有连接列表。 每个新项目都将具有->next
NULL。
您说ptr = ptr->next
(为 NULL),但下一次迭代会用新分配的节点覆盖 ptr。 您需要重新排列代码以将节点串在一起...
一种方法是ptr->next = head
使您的新节点指向旧头。然后head = ptr
将磁头移动到新节点。例如:
要在开头插入(例如,对于后进先出,后进先出):
while (cin >> n){
ptr = new Node;
ptr->x = n;
ptr->next = head;
head = ptr;
}
要在末尾插入(对于先进先出):
while (cin >> n){
if(!head) {
head = new Node;
ptr = head;
}
else
{
ptr->next = new Node;
ptr = ptr->next;
}
ptr->next = NULL;
ptr->x = n;
}