数据结构-链表list.h 使用操作

使用要求

        为了使用 list.h 中的链表操作宏,我们在定义结构体时需要对结构体进行一个改变,在结构体中要多添加一个 struct list_head 类型的字段,例如:
typedef struct student
{
    char first_name[MAX_STRING_LENGTH];
    char last_name[MAX_STRING_LENGTH];
    unsigned int age;
    struct list_head node_student;
}student_t;

那么在 list.h 中 list_head 是用来干嘛的呢,我们看它的定义

// import from include/linux/types.h
struct list_head {
    struct list_head *next, *prev;
};

很明显这个结构体可以用来记录前一个元素和后一个元素的位置。

初始化(INIT_LIST_HEAD)

        在对链表进行操作之前,首先我们需要为链表指定好链表头,以方便管理,后面的操作接口中的参数很多也是要和这个链表头有关联,在操作之前,要先定义一个 struct list_head 的变量用来充当链表头,然后经过 INIT_LIST_HEAD 接口去初始化好这个头节点。
struct list_head class;
INIT_LIST_HEAD(&class);

其中 INIT_LIST_HEAD 在 list.h 中定义如下

static inline void INIT_LIST_HEAD(struct list_head *list) {
    list->next = list;
    list->prev = list;
}

可以看到,他把 list_head 节点进行了初始化,使它首先指向自身。

添加元素(list_add_tail)

        当定义好一个节点元素后,就可以使用 list_add_tail 宏将节点加入链表中

例如:

student_t *stu = NULL;
/* 创建节点. */
if ((stu = make_student("Pierre", "Dupont", 16)) == NULL)
{
    fprintf(stderr, "Failed to create Pierre.\n");
    return -1;
}
/*将 stu 加入链表*/
list_add_tail(&stu->node_student, &class);
        可以看到,list_add_tail 的第一个参数是待加入节点的 struct list_head 类型字段的地址,第二个参数是头节点的地址

list.h 中的定义

static inline void list_add_tail(struct list_head *new, struct list_head *head) 
{
    __list_add(new, head->prev, head);
}

其中__list_add 定义为

static inline void __list_add(struct list_head *new, struct list_head *prev,struct list_head *next) 
{
    next->prev = new;
    new->next = next;
    new->prev = prev;
    prev->next = new;
}
        读者可自己画个读,就不难发现 list_add_tail 宏所实现的是一个类头插法,它是将节点插入在头节点的前面,但是头节点的位置不会变,也就是以我们一开始定义的头节点为头,新来的节点始终插入在这个头节点的前面,所以可以看到__list_add 中的操作就是将新节点插入在头节点和头节点前面元素之间。        

遍历操作(list_for_each_entry)

操作示例:

student_t *stu = NULL;
/* Print all students in class. */
printf("All the students in class.\n");
list_for_each_entry(stu, &class, node_student) 
{
    printf("First name: %s\n", stu->first_name);
    printf("Last name: %s\n", stu->last_name);
    printf("Age: %u\n\n", stu->age);
}
再来看看 list.h 中是如何去定义 list_for_each_entry
/**
* list_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_first_entry(head, typeof(*pos), member); \
&pos->member != (head); \
pos = list_next_entry(pos, member))
        list_for_each_entry 的第一个参数是一个节点指针,用来访问节点元素,第二个参数是头节点指针,第三个参数是节点中定义的 struct list_head 类型字段的名字, list_first_entry 是返回头节点下一个节点的节点地址,因为头节点就是一个 struct list_head 类型,没有数据元素域,只做管理用, list_next_entry 是寻找下一个节点,所以 list_for_each_entry 管理了一个 for 循环,去遍历链表元素。

合并链表(list_splice_init)

        这个操作是为了用另外一个头来管理这片链表或则将一个链表加入到另一个链表中,例如:
list_splice_init(&class, &bus);
上面代码的意思是将 class 管理的链表加入到 bus 中让 bus 管理。
其中 list.h 对 list_splice_init 的定义为:
/**
* list_splice_init - join two lists and reinitialise the emptied 
list.
* @list: the new list to add.
* @head: the place to add it in the first list.
*
* The list at @list is reinitialised
*/
static inline void list_splice_init(struct list_head *list,struct list_head *head)
{
    if (!list_empty(list)) 
    {
    __list_splice(list, head, head->next);
    INIT_LIST_HEAD(list);
    }
}
__list_splice 的定义如下
static inline void __list_splice(const struct list_head *list,struct list_head *prev,struct list_head *next) 
{
    struct list_head *first = list->next;
    struct list_head *last = list->prev;
    first->prev = prev;
    prev->next = first;
    last->next = next;
    next->prev = last;
}
        读者画图可以很清楚知道,list_splice_init 就是将 head 头节点管理的链表添加到 list 管理的链表中,INIT_LIST_HEAD 是将该头节点初始化,也就是脱离和原链表的联系。

删除元素(list_del)

使用示例:
student_t *tmp = NULL;
student_t *stu = NULL;
list_for_each_entry_safe(stu, tmp, &bus, node_student) 
{
    if (strcmp(stu->first_name, "Celine") == 0) 
    {
        list_del(&stu->node_student);
        free(stu);
    }
}
        该例子是删除链表中 first_name 元素名为 Celine 的节点,同时使用 list_for_each_entry_safe 对链表进行遍历,这个与前面我们讲到的 list_for_each_entry 是不一样的,我们先来看看 list.h 中怎么去定义 list_for_each_entry_safe
/**
* list_for_each_entry_safe - iterate over list of given type 
safe against removal of list entry
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*/
#define list_for_each_entry_safe(pos, n, head, member) \
for (pos = list_first_entry(head, typeof(*pos), member), \
n = list_next_entry(pos, member); \
&pos->member != (head); \
pos = n, n = list_next_entry(n, member))
        可以看到 list_for_each_entry_safe 相对与 list_for_each_entry 多了一个参数 n, 是用来做遍历用的,因为 list_for_each_entry_safe 是配合删除操作,所以 pos 指向的节点可能会被删除,而 n 指向可能被删除节点的下一个节点。
再来看看 list_del 在 list.h 中的定义
static inline void list_del(struct list_head *entry) 
{
    __list_del(entry->prev, entry->next);
    entry->next = LIST_POISON1;
    entry->prev = LIST_POISON2;
}
其中__list_del 定义为
static inline void __list_del(struct list_head *prev, struct list_head *next) 
{
    next->prev = prev;
    prev->next = next;
}
        这个不难看出 list_del 就是实现了 entry 结点从结点中删除,其中 LIST_POISON1 和LIST_POISON2 是 用于验证是否没有人使用未初始化的列表项。
# define POISON_POINTER_DELTA (0)
#define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)
#define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在C语言中,使用malloc函数可以动态地分配内存空间。下面是一个示例,演示了如何使用malloc函数来分配空间并创建一个单向链表: ```c #include <stdio.h> #include <stdlib.h> // 定义链表节点结构体 typedef struct Node { int data; struct Node* next; } Node; // 创建链表节点 Node* createNode(int data) { Node* newNode = (Node*)malloc(sizeof(Node)); if (newNode == NULL) { printf("内存分配失败\n"); return NULL; } newNode->data = data; newNode->next = NULL; return newNode; } // 初始化链表 Node* initList() { Node* head = createNode(0); // 创建头节点 if (head == NULL) { return NULL; } return head; } // 在链表末尾插入节点 void insertNode(Node* head, int data) { Node* newNode = createNode(data); if (newNode == NULL) { return; } Node* temp = head; while (temp->next != NULL) { temp = temp->next; } temp->next = newNode; } // 打印链表 void printList(Node* head) { Node* temp = head->next; // 跳过头节点 while (temp != NULL) { printf("%d ", temp->data); temp = temp->next; } printf("\n"); } // 释放链表内存 void freeList(Node* head) { Node* temp = head; while (temp != NULL) { Node* nextNode = temp->next; free(temp); temp = nextNode; } } int main() { Node* head = initList(); // 初始化链表 if (head == NULL) { return 0; } insertNode(head, 1); // 在链表末尾插入节点 insertNode(head, 2); insertNode(head, 3); printList(head); // 打印链表 freeList(head); // 释放链表内存 return 0; } ``` 这段代码演示了如何使用malloc函数来分配空间并创建一个单向链表。首先,我们定义了一个链表节点结构体,包含数据和指向下一个节点的指针。然后,我们实现了创建节点、初始化链表、插入节点、打印链表和释放链表内存的函数。在主函数中,我们初始化链表,插入节点,打印链表,最后释放链表内存。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九月丫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值