对于一个职业的C语言程序员,熟练的使用链表是基础。链表在编程实践中应用广泛,也是实现各种数据结构的基础。所以这篇博文以后的几篇博文都会讲述链表以及应用链表实现各种数据结构。如果读者能够认真阅读和实践我的文章一定会对链表和C语言指针有新的认识。
1.单向链表
类似于下图的数据存储方式就是数据结构中的单向链表:
参照图片,我们定义一个数据节点的数据结构:
typedef struct node
{
void *data;
struct node *next;
}Node;
看完我定义的链表节点,读者可能发现我们的数据项居然是一个空指针。在我以后的程序代码中读者会经常看到空指针的出现。
如果有读者已经了解了空指针的用法可以跳过这一段文字。我们都知道C语言是系统语言,它为程序员提供了全部的自由。空指针就是C语言中自由的小鸟,它可以指向任意的数据类型。灵活的应用空指针我们可以实现泛型编程的效果。当然,C语言不支持泛型编程。
在接下来的代码中读者可以看到空指针的神奇之处,下面让我实现在单向链表。
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{
int data;
struct node *next;
}Node;
typedef Node* Head;
Head list_append(Node* head, int data)
{
Node *p;
Node *node = (Node *)malloc(sizeof (Node *));
if(head != NULL)
{
if(node != NULL)
{
node->data = data;
node->next = NULL;
p = head;
while (p->next !=NULL) {
p = p->next;
}
p->next = node;
}
}
else {
head = (Head)malloc(sizeof (Head));
if(head != NULL)
{
head->data = data;
head->next = NULL;
}
}
return head;
}
int main()
{
int i;
Head head = NULL;
Node *node = NULL;
for (i= 0; i < 10; i++) {
head = list_append(head, i);
}
node = head;
while (node != NULL) {
printf("%d\n", node->data);
node = node->next;
}
}
就这样一个简单的链表就实现了,今天没有实现空指针的链表,调试出了点问题,我会继续努力的,就先实现一个int的链表作为示例。当然上面的程序不是最终的版本,我会进一步的将他改进,感兴趣的读者可以持续关注我。
前面我已经通过例子给出了如何将数据存储在链表中了,并且在main函数中给出了遍历链表的方法。对于一个数据结构,我们需要学习和了解的无非就是建立、添加、插入、删除和遍历等操作。建立和添加在上面的例子已经给出,下面将给出插入和删除的例子。
void list_insert(Node *node, int data)
{
Node *p;
if(node != NULL )
{
p = (Node *)malloc(sizeof (Node *));
if(p != NULL)
{
p->data = data;
p->next = node->next->next;
node->next = p;
}
}
}
void list_del(Node *node)
{
Node *p;
if(node != NULL && node->next != NULL)
{
p = node->next;
node->next = p->next;
free(p);
}
}
int main()
{
int i;
Head head = NULL;
Node *node = NULL;
for (i= 0; i < 10; i++) {
head = list_append(head, i);
}
node = head;
while (node!= NULL) {
printf("%d\n", node->data);
if(node->data == 6)
{
list_insert(node, 15);
}
node = node->next;
}
node = head;
while (node!= NULL) {
printf("%d\n", node->data);
node = node->next;
}
node = head;
while (node->next != NULL) {
list_del(node);
}
}
为了方便对链表进行遍历,我们可以实现一个遍历的函数,比如这样。
void list_ergodic(Head head,
void *function(Node *p, void *),
void* prg)
{
Node *node = head;
while (node != NULL) {
function(node, prg);
node = node->next;
}
}
void func(Node *p, void *prg)
{
printf("%d\n", p->data);
}
int main()
{
int i;
Head head = NULL;
Node *node = NULL;
for (i= 0; i < 10; i++) {
head = list_append(head, i);
}
list_ergodic(head, func, NULL);
}
遍历函数list_ergodic的参数有点复杂,第一个参数是链表头,第二个参数是一个函数指针,第三个是一个空指针。第三个参数用一个空指针用来给回调函数传入参数,可以方便回调函数实现各种对链表的操作功能。函数指针的使用如果读者不是很了解的话我后面会有一篇博文专门讲解函数指针。