- 首先,在代码中定义一个结构体
LNode
用来表示链表节点,其中包含整数类型的数据成员data
和指向下一个节点的指针next
。 - 定义一个别名
LinkList
表示指向LNode
结构体的指针,即链表指针。 - 函数
List_Init
用于初始化链表,将传入的链表头指针pHead
置为 NULL,表示空链表。该函数返回一个布尔值表示是否初始化成功。 - 函数
List_Empty
用于判断链表是否为空,通过检查链表头指针pHead
是否为 NULL 来确定链表是否为空。如果链表为空,函数返回 true,否则返回 false。
#include <stdio.h>
#include<stdlib.h>
typedef struct LNode {
int data;
struct LNode * next;
} LNode, *LinkList;
// 初始化链表
bool List_Init (LinkList &pHead) {
pHead = NULL;
return true;
}
// 判断单链表是否为空
bool List_Empty (LinkList pHead) {
return pHead == NULL;
}
需要注意到以下几点:
- 在 C 语言中,没有内置的
bool
类型,可以考虑使用int
类型来替代,0 表示 false,非零值表示 true。 - 参数传递时应该使用指针引用(
LinkList &pHead
),以便在函数内部修改头指针的值。 - 如果需要在函数内部分配内存来表示新的链表节点,可以使用
malloc
函数来分配内存
另外,在 C 语言中,LinkList &pHead
和 LinkList pHead
有以下区别:
-
LinkList &pHead
:- 这种写法使用了引用(reference)的概念,但实际上 C 语言并不支持引用。
- 在 C++ 中,通过
&
符号可以创建引用类型,用于传递参数时不复制变量而直接操作原始对象。但在 C 中,这样的写法是无效的。在 C 中,通常会使用指针来模拟引用的功能。使用LinkList* pHead
-
LinkList pHead
:- 这种写法表示将
pHead
声明为一个指向LinkList
结构体的指针变量。如果使用LNode表示时可以写作LNode* newhead
- 当传递该变量作为参数时,会传递指针的副本,即函数内部对指针值的修改不会影响外部的指针变量。
- 这种写法表示将
在 C 语言中,要在函数中修改指针所指向的内容(比如链表头指针),需要传递指针的指针(LinkList* pHead
) 或者返回新的头指针,以便在函数外部更新指针的值。
修改指针指向分内容,传递指针的指针代码如下
#include <stdio.h>
#include <stdlib.h>
typedef struct LNode {
int data;
struct LNode *next;
} LNode, *LinkList;
// 初始化链表
void List_Init(LinkList *pHead) {
*pHead = NULL;
}
// 在链表头部插入新节点
// 不带虚拟头结点的头插法
void List_InsertFront(LinkList *pHead, int val) {
LNode* newNode = (LNode*)malloc(sizeof(LNode));
if (newNode == NULL) {
printf("Error: Memory allocation failed.\n");
return;
}
newNode->data = val;
newNode->next = *pHead;
*pHead = newNode;
}
// 打印链表
void List_Print(LinkList pHead) {
LNode* current = pHead;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
int main() {
LinkList head;
List_Init(&head);
// 插入一些数据
List_InsertFront(&head, 3);
List_InsertFront(&head, 5);
List_InsertFront(&head, 7);
List_Print(head);
return 0;
}
上述代码示例中:
List_Init
函数接受一个指向链表头指针的指针,并将头指针置为 NULL。List_InsertFront
函数接受一个指向链表头指针的指针以及要插入的值,在链表头部插入新节点。- 在
main
函数中,通过向函数传递头指针的指针来更新链表。
在不传递指针的指针的情况下,可以使用返回值来实现链表操作。
#include <stdio.h>
#include <stdlib.h>
typedef struct LNode {
int data;
struct LNode *next;
} LNode;
// 初始化链表并返回头节点
LNode* List_Init() {
return NULL;
}
// 在链表头部插入新节点,并返回新的头节点
LNode* List_InsertFront(LNode *pHead, int val) {
LNode* newNode = (LNode*)malloc(sizeof(LNode));
if (newNode == NULL) {
printf("Error: Memory allocation failed.\n");
return pHead;
}
newNode->data = val;
newNode->next = pHead;
return newNode; // 返回新的头节点
}
// 打印链表
void List_Print(LNode *pHead) {
LNode* current = pHead;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
int main() {
LNode* head = List_Init();
// 插入一些数据,并更新头节点
head = List_InsertFront(head, 3);
head = List_InsertFront(head, 5);
head = List_InsertFront(head, 7);
List_Print(head);
return 0;
}
/*********************************************************/
#include <stdio.h>
#include <stdlib.h>
typedef struct LNode {
int data;
struct LNode *next;
} LNode, *LinkList;
// 初始化链表并返回头节点
LinkList List_Init() {
return NULL;
}
// 在链表头部插入新节点,并返回新的头节点
LinkList List_InsertFront(LinkList pHead, int val) {
LinkList newNode = (LinkList)malloc(sizeof(LNode));
if (newNode == NULL) {
printf("Error: Memory allocation failed.\n");
return pHead;
}
newNode->data = val;
newNode->next = pHead;
return newNode; // 返回新的头节点
}
// 打印链表
void List_Print(LinkList pHead) {
LinkList current = pHead;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
int main() {
LinkList head = List_Init();
// 插入一些数据,并更新头节点
head = List_InsertFront(head, 3);
head = List_InsertFront(head, 5);
head = List_InsertFront(head, 7);
List_Print(head);
return 0;
}
在上面的示例中,LNode
结构体表示链表节点。List_Init
函数用于初始化链表并返回头节点。List_InsertFront
函数用于在链表头部插入新节点,并返回新的头节点。List_Print
函数负责打印链表中每个节点的数据。通过这种方式,而不需要传递指针的指针。
上述两种例子是链表不具有虚拟头结点的情况,当链表带有虚拟头结点时,只需虚将拟头节点的指针作为参数传递,可以方便地对链表进行插入、删除和遍历等操作,而不再需要传递指针的指针。
链表的头指针直接指向链表的第一个节点,而不是指向一个额外的虚拟头节点。如果需要添加一个虚拟头节点来简化链表的操作或者避免一些特殊情况的处理,可以像下面这样修改程序:
#include <stdio.h>
#include <stdlib.h>
typedef struct LNode {
int data;
struct LNode *next;
} LNode, *LinkList;
// 初始化链表(带虚拟头节点)
void List_Init(LinkList *pHead) {
*pHead = (LNode*)malloc(sizeof(LNode));
(*pHead)->next = NULL; // 虚拟头节点的 next 指针为 NULL
}
// 在链表头部插入新节点
void List_InsertFront(LinkList pHead, int val) {
LNode* newNode = (LNode*)malloc(sizeof(LNode));
if (newNode == NULL) {
printf("Error: Memory allocation failed.\n");
return;
}
newNode->data = val;
newNode->next = pHead->next;
pHead->next = newNode;
}
// 打印链表
void List_Print(LinkList pHead) {
LNode* current = pHead->next; // 跳过虚拟头节点
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
int main() {
LinkList head;
List_Init(&head);
// 插入一些数据
List_InsertFront(head, 3);
List_InsertFront(head, 5);
List_InsertFront(head, 7);
List_Print(head);
return 0;
}
当使用带有虚拟头节点的链表结构时,修改链表时不再需要传递指针的指针,因为在这种情况下,只需传递指向虚拟头节点的指针即可完成对链表的修改操作。在代码示例中,通过将指向虚拟头节点的指针作为参数传递给操作函数,可以直接操作链表的头部和其他节点,而无需传递指针的指针,也无需函数返回。
所以,在上述的修改后的程序中,通过将虚拟头节点的指针作为参数传递,可以方便地对链表进行插入、删除和遍历等操作,而不再需要传递指针的指针。