常用的双向循环链表,不易出错
/*
通用性比较强的双向环链
为了函数的通用性,我们在.h .c 需要用户去定夺的关键字,内容全部做成通用的接口
利用typedef抽象出了一些接口比较统一的实现方法
*/
llist.h
#ifndef LLIST_H__
#define LLSIT_H__
#define LLIST_FORWARD 1
#definr LLIST_BACKWARD 2
typedef void llist_op(const void *);//回调函数
typedef int llist_cmp(const void *,const void *);
struct llist_node_st
{
void *data;
struct llist_node_st *prev;
struct llist_node_st *next;
};
typedef struct
{
int size;
struct llist_node_st head;
}LLSIT;
LLIST *llist_create(int initsize);
int llist_insert(LLIST *,const void *data,int mode);
void *llist_find(LLIST *, const void *key, llist_cmp *);//数据类型不统一使用void 百搭
int llist_delete(LLIST *,const void *key,llist_cmp *);
int llist_fetch(LLIST *,const void *key,llist_cmp *,void *data);
void llist_travel(LLIST *,llist_op *);
void llist_destroy(LLIST *);
#endif
llist.c
#include<stdio.h>
#include<stdlib.h>
#include"llist.h"
#include<string.h>
LLIST *llist_create(int initsize)//只包含一个头节点( 双向循环链表)
{
LLIST *new;
new = malloc(sizeof(*new));
if(new == NULL)
return NULL;
new->size= initsize;
new->head.data = NULL;
new->head.prev = new->head;
new->head.next = new->head;
return new;
}
int llist_insert(LLIST *ptr,const void *data,int mode)
{
struct llist_node_st *newnode;
newnode = malloc(sizeof(*newnode));
if(newnode == NULL)
return -1;
newdata->data = malloc(ptr->size);
if(newnode->data == NULL)
return -2;
memcpy(newnode->data,data,ptr->size);
if(mode == LLIST_FORWARD)
{
newnode->prev = &ptr->head;
newnode->next = ptr->head.next;
newnode->prev->next = newnode;//头节点的 next 指针设置为指向新节点 newnode
newnode->next->prev = newnode;//原本在头节点之后的节点的 prev 指针设置为指向新节点 newnode。
}
else if(mode == LLIST_BACKWARD)
{
newnode->prev = ptr->head.prev;
newnode->next = &ptr->head;
newnode->prev->next = newnode;
newnode->next->prev = newnode;
}
else
{
return -3;
}
return 0;
}
static struct list_node_st * find_(LLIST *ptr, const void *key, llist_cmp *cmp)
{
struct llist_node_st *cur;
for(cur = ptr->head.next;cur!=ptr.head;cur=cur->next)
{
if(cmp(key,cur->data) == 0)
break;
}
return cur;
}
void *llist_find(LLIST *ptr, const void *key, llist_cmp *cmp)
{
return find_(ptr,key,cmp)->data;
}
int llist_delete(LLIST *ptr,const void *key,llist_cmp *cmp)
{
struct llist_node_st *node;
node = nodefind_(ptr,key,cmp);
if(node == &ptr->head)
return -1;
node->prev->next = node->next;
node->next->prev = node->prev;
free(node->data);
free(node);
return 0;
}
int llist_fetch(LLIST *ptr,const void *key,llist_cmp *cmp,void *data)
{
struct llist_node_st *node;
node = nodefind_(ptr,key,cmp);
if(node == &ptr->head)
return -1;
node->prev->next = node->next;
node->next->prev = node->prev;
if(data!=NULL)
memcpy(data,node->data,ptr->size);
free(node->data);
free(node);
return 0;
}
void llist_travel(LLIST *ptr,llist_op *op)//需要一个回调函数,需要用户给我传一个函数
{
struct llist_node_st *cur;
for(cur = ptr->head.next;cur!=&ptr->head;cur=cur->next)//为了封装成更通用的函数,不知道用户的结构类型,因此需要回调函数,且需要在 .h文件中使用 void 函数声明,且使用typedef重命名 看起来更好一些
op(cur->data);//借用户之手,把他知道的数据类型打印了出来 具有通用性
}
void llist_destroy(LLIST *ptr)
{
struct llist node_st *cur,*next;
for(cur= ptr->head.next;cur != &ptr->head;cur= next)
{
next = cur->next;
free(cur->data);
free(cur);
}
free(ptr);
}
main.c
#include<stdio.h>
#include<stdlib.h>
#include"llist.h"
#define NAMESIZE 32
struct score_st
{
int id;
char name[NAMESIZE];
int math;
int chinese;
};
static void print_s(const void *record)
{
const struct score_st *r = record;
printf("%d %s %d %d\n",r->id,r->name,r->math,r->chinese);
}
static int id_cmp(const void *key,const void *record)
{
const int *k = key;
const struct score_st *r = record;
return (*k - r->id);
}
static int name_cmp(const void *key,const void *record)
{
const char *k = key;
const struct score_st *r = record;
return strcmp(k,r->name);
}
int main()
{
int ret,i;
int id =3;
LLIST *handler;
struct score_st tmp;
handler = llist_create(sizeof(struct score_st));
if(handler == NULL)
exit(1);
for(i =0;i<7;i++)
{
tm.id =i;
snprintf(tmp.name,NAMESIZE,"std%d",i);
tmp.math = rand()%100;
tmp.chinese = rand%()100;
ret = llist_insert(handler,&tmp,LLIST_FORWARD);
//ret = llist_insert(handler,&tmp,LLIST_BACKWARD);
if(ret)
exit(1);
}
char *del_name = "std6";
ret = llist_delete(handler,&id,id_cmp);
//ret = llist_delete(handler,del_name,name_cmp)//如何实现根据任何字段来删除
if(ret)
printf("llist_delete failed!\n");
llist_travel(handler,print_s);
printf("\n\n");
struct score *data;
data = llist_find(handler,&id,id_cmp);
if(data == NULL)
printf("Can not find!\n");
else
printf_s(data);
llist_destroy(handler);
exit(0);
}
Makefile
all:llist
llist:llist.o main.o
$(CC) $^ -o $@
clean:
rm llist *.o -rf
补充说明
在C语言中,typedef
关键字用于给现有的数据类型定义一个新的名字。你提供的 typedef void llist_op(const void *)
是一个非常有用的技巧,特别是在定义函数指针类型时。我们来详细解释一下这个定义。
解释 typedef void llist_op(const void *)
-
原始定义:
void llist_op(const void *);
- 这行代码声明了一个函数类型
llist_op
,它接受一个const void *
类型的参数,并且没有返回值(void
)。
- 这行代码声明了一个函数类型
-
使用
typedef
:typedef void llist_op(const void *);
- 使用
typedef
后,llist_op
成为了一个新的类型名,可以用来声明这种类型的函数指针。
- 使用
使用示例
假设你有一个函数,其签名与 llist_op
类型匹配:
void print_data(const void *data) {
printf("%s\n", (const char *)data);
}
你可以使用 llist_op
类型来声明一个函数指针,并将其指向 print_data
函数:
llist_op *op = print_data;
更深入的例子
在链表操作中,这种用法非常常见。例如,你可能会定义一个链表结构,并使用 llist_op
类型的函数指针来遍历链表并对每个节点进行操作:
typedef struct node {
void *data;
struct node *next;
} Node;
typedef struct {
Node *head;
llist_op *operation; // 函数指针,用于链表操作
} LinkedList;
void traverse_list(LinkedList *list) {
Node *current = list->head;
while (current != NULL) {
list->operation(current->data); // 使用函数指针执行操作
current = current->next;
}
}
在这个例子中,LinkedList
结构包含一个 llist_op
类型的函数指针 operation
。当你遍历链表时,可以通过这个指针对每个节点的数据执行特定操作:
int main() {
LinkedList list;
list.operation = print_data; // 设置操作函数
// 假设已经初始化并填充链表
traverse_list(&list); // 遍历链表并打印每个节点的数据
return 0;
}
小结
使用 typedef
定义函数指针类型可以使代码更加简洁和可读。通过 typedef void llist_op(const void *);
,你可以方便地声明和使用这种函数指针,特别是在需要传递和调用回调函数的场合。这样,你的代码不仅更加易于理解,也更加灵活和模块化。