在链表中插入任何地方都没有什么神奇之处,唯一真正的挑战是在处理三个条件时保持指针笔直:
- 插入第一个节点(或新的第一个节点);
- 在现有第一个和最后一个节点之间的某个位置插入一个节点;和
- 在列表末尾插入。
第一种情况,插入第一个(或新的第一个)节点只需要分配一个新节点并且插入它是列表中的第一个节点,或者如果头节点已经存在,设置newnode->next = head;和head = newnode;
在最后插入没有太大区别,你只是迭代到最后一个节点并设置last->next = newnode;
在两者之间插入节点的情况需要更多考虑。保持指针笔直的最简单方法是拿出铅笔和纸,画出列表的指针图,然后在需要插入新节点的地方将列表分成两部分,并找出所需的步骤 在你拿起键盘之前(那样会容易得多)
不需要任何花哨的东西,只需一个框图,其中您的next(或您的pNext)指针连接节点。一个简单的列表将有两个节点(A 和 B)就可以了,例如
A B
+------+ +------+
| node | | node |
| next |--> | next |-->NULL
+------+ +------+
然后在新节点将被插入的地方打破列表,例如
A B
+------+ | +------+
| node | / | node |
| next |--> \ | next |-->NULL
+------+ / +------+
|
new
+------+
| node |
| next |-->
+------+
这使您可以可视化所需的步骤。定位node A,设置new->next = B;,设置A->next = new;(注意:你必须停在节点之前你想插入新节点的位置)结果然后将是:
A new B
+------+ +------+ +------+
| node | | node | | node |
| next |--> | next |--> | next |-->NULL
+------+ +------+ +------+
当您确切了解处理插入所需的操作后,现在拿起键盘并实现该逻辑。
由于您将有一个插入函数来处理所有三种情况,正如@WhozCraig 评论的那样,在处理列表操作(或通常的指针时,您可能需要在函数中分配一个新的内存块来更改指针),它有助于将指针的 地址 传递给您的函数(以便您的函数接收指针本身——而不是指针的副本)。
在您的情况下,您是否需要最初为列表包装器分配,或者在一个简单的列表中,您可以分配一个新节点作为列表中的第一个节点,传递指针的地址允许在函数内进行更改,而无需必须返回并将返回值作为新地址分配给调用函数。
为您的struct GraphicElement 和rgraphic_t 为struct RasterGraphic 类型添加几个typedef gelement_t,以减少键入并删除一些MixedCase 风格的尴尬,您可以考虑一下您的@987654339 @函数的方式如下。
首先,您的InsertGraphicElement 函数将成功或失败,因此选择一个有意义的返回类型,可以指示成功或失败。在处理列表时,返回指向新插入节点的指针很有帮助,并且即使失败也允许返回 NULL,例如
gelement_t *InsertGraphicElement (rgraphic_t **pA)
由于您传递了指向 RasterGraphic 结构的指针的指针,因此您可以将数据添加到结构中,使其作为实际列表的包装器更有用。添加节点计数器是一种方便的方法来跟踪单链表中的节点数,而不必每次都遍历列表,例如
typedef struct RasterGraphic {
size_t nelements;
gelement_t *GraphicElements;
} rgraphic_t;
在你的函数中,你应该验证你有一个分配的指针,如果没有,然后分配给RasterGraphic结构,例如
int position = 0;
if (!*pA) { /* if list NULL - allocate new list */
puts ("allocating new list.");
*pA = malloc (sizeof **pA);
if (!*pA) { /* validate every allocation */
perror ("malloc-*list");
exit (EXIT_FAILURE);
}
(*pA)->nelements = 0; /* initialize values */
(*pA)->GraphicElements = NULL;
}
接下来,您可以分配和验证新节点以添加为第一个节点或位置,例如
gelement_t *node = malloc (sizeof *node); /* allocate node */
if (!node) { /* validate */
perror ("malloc-node");
exit (EXIT_FAILURE);
}
接下来收集您的文件名和位置信息,并将node->fileName 设置为一个新分配的块,该块保存用户输入的文件名(几个帮助函数使这更容易),例如
node->fileName = get_filename_stdin(); /* request filename */
if (!node->fileName) { /* validate */
free (node);
return NULL;
}
node->pNext = NULL; /* set next pointer NULL */
position = get_int_stdin (*pA); /* request position */
您现在处于函数中处理答案开头确定的 3 种情况的位置。如果还没有(*pA)->GraphicElements 节点,您将添加第一个节点(因此您不需要询问位置)。如果它不是第一个节点,并且请求的位置是0,则作为新的第一个节点插入。 (两者都可以单独处理)
如果请求的位置大于零,则迭代到插入点之前的节点,并如上图所示插入并在那里插入节点。
一种方法是:
gelement_t *p = (*pA)->GraphicElements;
if (!p || position == 0) { /* insert as new head */
node->pNext = p;
(*pA)->GraphicElements = node;
}
else { /* insert at position (default end) */
int n = 0;
while (n < position - 1 && p->pNext) { /* locate node before */
p = p->pNext;
n++;
}
node->pNext = p->pNext; /* set node->pNext to current pNext */
p->pNext = node; /* set current pNext to node */
}
(*pA)->nelements++; /* increment number of elements in list */
它将根据用户输入处理您的插入。剩下的就是:
return node; /* meaningful return to indicate success/failure */
}
(注意:当您对列表操作逻辑感到满意时,将这个函数分解为几个单独处理操作的函数会有所帮助,例如 create_list()、create_node() 和 @987654356 @.(您在其中创建 RasterGraphic 列表、GraphicElement 节点,最后将该节点添加到列表中的给定位置 - 留给您)
把它放在一起并添加辅助函数,一个简短的例子是:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h> /* for PATH_MAX */
typedef struct GraphicElement {
char* fileName;
struct GraphicElement* pNext;
} gelement_t;
typedef struct RasterGraphic {
size_t nelements;
gelement_t *GraphicElements;
} rgraphic_t;
char *get_filename_stdin (void)
{
char filename[PATH_MAX] = "";
char *newname = NULL;
fputs ("enter filename : ", stdout);
if (fgets (filename, PATH_MAX, stdin)) {
size_t len = strlen (filename);
if (len && filename[len-1] == '\n') {
filename[--len] = 0;
if (!len) {
fputs ("error: filename empty.\n", stderr);
return NULL;
}
}
else if (len == PATH_MAX) {
fputs ("error: filename exceeds PATH_MAX\n", stderr);
return NULL;
}
newname = malloc (len + 1);
if (!newname) {
perror ("malloc-newname");
exit (EXIT_FAILURE);
}
memcpy (newname, filename, len + 1);
}
return newname;
}
int get_int_stdin (rgraphic_t *list)
{
char buf[PATH_MAX];
int pos = 0;
if (!list->nelements) {
puts ("inserting as head.");
return 0;
}
fputs ("index to insert: ", stdout);
if (fgets (buf, PATH_MAX, stdin))
if (sscanf (buf, "%d", &pos) != 1 || pos < 0 ||
pos > (long)list->nelements)
return list->nelements;
return pos;
}
gelement_t *InsertGraphicElement (rgraphic_t **pA)
{
int position = 0;
if (!*pA) { /* if list NULL - allocate new list */
puts ("allocating new list.");
*pA = malloc (sizeof **pA);
if (!*pA) { /* validate every allocation */
perror ("malloc-*list");
exit (EXIT_FAILURE);
}
(*pA)->nelements = 0; /* initialize values */
(*pA)->GraphicElements = NULL;
}
gelement_t *node = malloc (sizeof *node); /* allocate node */
if (!node) { /* validate */
perror ("malloc-node");
exit (EXIT_FAILURE);
}
node->fileName = get_filename_stdin(); /* request filename */
if (!node->fileName) { /* validate */
free (node);
return NULL;
}
node->pNext = NULL; /* set next pointer NULL */
position = get_int_stdin (*pA); /* request position */
gelement_t *p = (*pA)->GraphicElements;
if (!p || position == 0) { /* insert as new head */
node->pNext = p;
(*pA)->GraphicElements = node;
}
else { /* insert at position (default end) */
int n = 0;
while (n < position - 1 && p->pNext) { /* locate node before */
p = p->pNext;
n++;
}
node->pNext = p->pNext; /* set node->pNext to current pNext */
p->pNext = node; /* set current pNext to node */
}
(*pA)->nelements++; /* increment number of elements in list */
return node; /* meaningful return to indicate success/failure */
}
/* loop over list printing values */
void prn_list (rgraphic_t *list)
{
size_t n = 0;
gelement_t *node = list->GraphicElements;
printf ("\n\n%zu nodes in list\n", list->nelements);
for (; node; node = node->pNext)
printf ("%2zu: %s\n", 1 + n++, node->fileName);
}
/* loop over list freeing memory (pay attention to victim) */
void free_list (rgraphic_t *list)
{
gelement_t *node = list->GraphicElements;
while (node) {
gelement_t *victim = node;
node = node->pNext;
free (victim->fileName);
free (victim);
}
free (list);
}
int main (void) {
rgraphic_t *list = NULL;
puts ("\nNOTE: pressing [Enter] for index - inserts at end!\n"
" [Ctrl+d] at \"filename: \" prompt to end input.\n");
while (InsertGraphicElement(&list)) {} /* create list/insert nodes */
prn_list (list); /* print list */
free_list (list); /* free list */
}
使用/输出示例
$ ./bin/ll_single_insert_ptp
NOTE: pressing [Enter] for index - inserts at end!
[Ctrl+d] at "filename: " prompt to end input.
allocating new list.
enter filename : one
inserting as head.
enter filename : four
index to insert: 1
enter filename : two
index to insert: 1
enter filename : three
index to insert: 2
enter filename : five
index to insert:
enter filename :
5 nodes in list
1: one
2: two
3: three
4: four
5: five
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放。
您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。
对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。
$ valgrind ./bin/ll_single_insert_ptp
==9747== Memcheck, a memory error detector
==9747== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==9747== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==9747== Command: ./bin/ll_single_insert_ptp
==9747==
NOTE: pressing [Enter] for index - inserts at end!
[Ctrl+d] at "filename: " prompt to end input.
allocating new list.
enter filename : one
inserting as head.
enter filename : four
index to insert: 1
enter filename : two
index to insert: 1
enter filename : three
index to insert: 2
enter filename : five
index to insert:
enter filename :
5 nodes in list
1: one
2: two
3: three
4: four
5: five
==9747==
==9747== HEAP SUMMARY:
==9747== in use at exit: 0 bytes in 0 blocks
==9747== total heap usage: 12 allocs, 12 frees, 136 bytes allocated
==9747==
==9747== All heap blocks were freed -- no leaks are possible
==9747==
==9747== For counts of detected and suppressed errors, rerun with: -v
==9747== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始终确认您已释放已分配的所有内存并且没有内存错误。
查看一下,如果您还有其他问题,请告诉我。