现在我知道为什么在定义链表时使用指针了。仅仅是因为结构不能有递归定义,如果没有指针,编译器将无法计算节点结构的大小。
struct list{
int data;
struct list* next; // this is fine
};
但是当我将链表的第一个节点声明为:
时,混乱就开始了struct list* head;
为什么必须是指针?不能简单地声明为
吗?struct list head;
的地址用于进一步使用?
这个问题没有明确的答案。两种方法都可以。这个问题的答案取决于您希望如何组织链表以及如何表示空列表。
你有两个选择:
-
没有"dummy"头元素的列表。在这种情况下,空列表在
中由null表示。head
指针struct list* head = NULL;
所以这是你的问题的答案:我们声明它作为一个指针能够表示一个空列表设置
head
指针为null。 -
带有"dummy"头元素的列表。在这种情况下,列表的第一个元素不用于存储实际的用户数据:它只是作为列表的起始"虚拟"元素。它被声明为
struct list head = { 0 };
上面表示一个空列表,因为
head.next
是空的,head
对象本身"不算数"。。如果你愿意,你可以这样声明。请记住,
head
实际上不是一个列表元素。实际元素从head
之后开始
和往常一样,请记住,当您使用非动态分配的对象时,这些对象的生命周期是由作用域规则控制的。如果您想要覆盖这些规则并手动控制对象的生存期,那么您别无选择,只能动态分配它们,因此,请使用指针。
你可以这样声明一个列表
struct list head = {};
但是在实现访问列表的函数时会遇到一些困难。他们必须考虑到第一个节点没有被用作列表的其他节点,并且第一个节点的数据成员data
也没有被使用。
通常以如下方式声明列表
struct List
{
// some other stuff as for example constructors and member functions
struct node
{
int data;
struct node* next; // this is fine
} head;
};
和
List list = {};
或者在c++中你可以直接写
struct List
{
// some other stuff as for example constructors and member functions
struct node
{
int data;
struct node* next; // this is fine
} head = nullptr;
};
List list;
当然你可以自己定义List的默认构造函数。
在本例中,例如要检查列表是否为空,只需定义以下成员函数
struct List
{
bool empty() const { return head == nullptr; }
// some other stuff as for example constructors and member functions
struct node
{
int data;
struct node* next; // this is fine
} head;
};
简单来说,如果您的head是链表的开始节点,那么它将只包含链表开始的第一个节点的地址。这样做是为了避免普通程序员的困惑。由于头只包含地址,因此它被声明为指针。但是您想要声明的方式也很好,只需相应地编写代码。提示:如果您稍后想要对链表进行一些更改,例如在链表的开头进行删除或插入操作,您将面临需要另一个指针变量的问题。因此最好将第一个节点声明为指针。