在C语言中使用指针实现链表为什么很重要呢?
比如:
typedef struct item
{
type data;
struct item *next;
} Item;
typedef struct list
Item *head;
} List;
如果我不使用指针,仅使用相同的实现会发生什么?
在C语言中使用指针实现链表为什么很重要呢?
比如:
typedef struct item
{
type data;
struct item *next;
} Item;
typedef struct list
Item *head;
} List;
如果我不使用指针,仅使用相同的实现会发生什么?
那么你最终会得到这样的东西
typedef struct item
{
type data;
struct item next;
} Item;
现在C编译器将尝试计算Item
有多大。但由于next
嵌入在Item
中,它最终会得到这样一个方程式:
size-of-Item = size-of-type + size-of-Item
这意味着它是无限的。因此我们有了一个问题。由于这个原因,C需要指针,这样你就可以
size-of-Item = size-of-type + size-of-pointer
这会关闭它。更有趣的是,即使你在Java、Python或Haskell等语言中这样做,实际上你仍然会隐式存储指向(他们称为引用)来打破循环的指针,只不过他们将这个事实隐藏了起来。
// does not compile -- an item has an item has an item has an item has...
typedef struct item
{
type data;
struct item next;
} Item;
你可以做的是创建一个项目数组,并在其上实现链表:
Item *storage[100];
typedef struct item
{
type data;
int next; // points to the index inside storage of the next item
} Item;
看起来这会奏效;你可以有一个函数将项目添加到列表中,另一个函数从列表中删除项目:
void add_after(Item *new, Item *existing);
void remove(Item *existing);
existing
(确保更新前一项的next
“指针”),从而创建一个空槽,要么找到existing
项目并将new
插入storage
中的空槽(更新next
以指向那里)。add_after
和remove
操作无法在常数时间内实现,因为现在需要搜索数组并在空间不足时重新分配它。指针用于动态分配内存。
你可以将列表实现为一个简单的数组,但这意味着你分配了一个连续的内存区域,对于一个大的列表来说并不高效。此外,数组更难以扩展(如果达到其最大大小,你必须重新分配它)。
因此,对于大型列表,首选动态分配,在每个元素中可以在任何地方连续或不连续地分配内存,并且可以轻松地扩展它。
此外,不能在struct item
中存储另一个struct item
,因为结构体尚未定义,无法确定结构体的大小:
这是不可能的:
typedef struct item
{
type data;
struct item next;
} Item;
没有指针,每个列表项都包含另一个列表项(next
),该列表项又包含另一个列表项,依此类推。
你最终会得到一个无限大的数据结构,它将使用无限量的内存,当然这是不可行的。
-1
(表示指针术语中的nil
或NULL
),或者是 0 .. N-1
,表示:下一个链接元素的数组索引。索引数组的索引表示数据数组中的项目索引。
- 一个用于连接“列表”中任何数据的数组。请阅读此处。
因此,您的示例看起来像:
type data[N]; // or: type *data = new type [N];
int links[N]; // or: int *links = new int [N];
这应该是您开始进行简单测试的所有所需内容。
指针在C语言中的重要性
优点:
执行速度会更快。
指针允许我们访问函数外部的变量。
减少代码的大小。
没有指针:
代码大小会增加。
我们无法有效地维护数据。
在列表中,指针用于指向列表中下一个项。如果您没有声明指针,则无法访问列表中的多个项。
如果您在运行时动态分配一些内存,则确实需要使用指针。
有不止一种“列表”。你展示的实现是一个“链表”。没有指针就没有“链接”,所以我们需要看其他类型的列表。
那么,首先,如果从结构定义中删除*
会发生什么:它将无法编译。你可以在另一个结构体内包含一个结构体,但如果存在递归,则结构的大小将无限增大,这是不可能的!
用数组作为列表如何:
struct item list[100];
没错,那可以运行,但是现在你的列表大小是固定的。
好的,那我们来动态分配这个列表:
struct item *list = malloc(sizeof(struct item));
然后,每次你添加到列表中时,你都需要这样做:
list = realloc(list, newcount * sizeof(struct item));
list[newitem] = blah;
好的,这样做是可行的,但现在我们经常重新分配内存,这会导致大量的内存复制和低效。
另一方面,如果列表更新得非常不频繁,那么这种方法更节省空间,这可能是一个好事情。
使用数组的另一个缺点是像push、pop、sort、reverse等操作变得更加昂贵;它们都意味着多次内存复制。
还有其他类型的“列表”,但它们都涉及指针,所以我认为我们可以在这里忽略它们。