内核经典数据结构list 剖析

news2024/11/18 16:30:02

前言:

linux内核中有很多经典的数据结构,list(也称list_head)为其中之一,这些数据结构都是使用C语言实,并且定义和实现都在单独的头文件list.h中。可以随时拿出来使用。

list.h的定义不同linux发行版本路径不同,我们可以在/usr/include目录下find。

虽然list_head是基于C语言实现,但其设计初衷是使用在linux内核中,所以其中用了许多GUN的方法而并非C标准,如typeof,该宏的作用是获取变量类型。所以list_head

存在一定的平台相关性,如果要做跨平台需要进行改造。现我们只对内核原始list_head进行详细说明及实例演示。

原理:

list_head属于一个双向循环链表,表头只是一个指针。当链表为空时,表头的前驱和后继分别指向自己。

list_head的特点:

不包含数据,链表中只有prev和next,这样使得我们的链表使用起来就比较灵活,使用时只需要将其包含进使用的对象即可,有一种面向对象的感觉。例如:在使用时只需要在宿主结构中嵌入list_head类型节点,并将节点插入到list_head表头中。

这样多个宿主结构体内容就通过链表关联了起来。使用时只需要使用宿主结构中的list_head节点反推宿主结构体的首地址就可以访问宿主结构体。

相对于传统链表的包含数据,解决了链表结构冗余,通用性差,不方便移植等问题。

该链表不包含互斥锁,所以在多线程使用下需要加互斥锁进行同步,如下:

struct list_item

{

list_head list;

pthread_mutex_t lmutex;

}

struct list_item g_list_item;//包含了链表头及互斥锁。

链表定义:

struct list_head {

struct list_head *next, *prev;

};

实现方法:

函数说明:

作用

接口类型

函数原型

参数说明

函数说明

备注

增加

内部接口

static inline void __list_add(struct list_head *xnew,

struct list_head *prev,struct list_head *next)

{

next->prev = xnew;

xnew->next = next;

xnew->prev = prev;

prev->next = xnew;

}

xnew:新增节点

prev:插入位置的前一个节点

next:插入位置的后一个节点

双向循环链表的插入。

对于双向循环链表,节点插入时我们一般需要考虑4个元素:插入节点前驱;插入节点后继;插入位置前的一个节点后继;插入位置后的一个点解前驱;

所以这里的prev我们可以理解为插入位置前的一个节点,next则为插入位置后的一个节点;

增加

对外接口

static inline void list_add(struct list_head *xnew, struct list_head *head)

{

__list_add(xnew, head, head->next);

}

xnew:新增节点

head:链表头

头插法的方式插入链表。

尾插与头插不同,从这里调用__list_add函数时传参进行区分。

增加

对外接口

static inline void list_add_tail(struct list_head *xnew, struct list_head *head)

{

__list_add(xnew, head->prev, head);

}

xnew:新增节点

head:链表头

尾插法的方式插入链表。

删除

内部接口

static inline void __list_del(struct list_head * prev, struct list_head * next)

{

next->prev = prev;

prev->next = next;

}

prev:要删除节点的前驱。

next:要删除节点的后继。

将要删除节点的前驱作为下一个节点的前驱;将后继作为前一个节点的后继;这样要删除的节点就与原链表断开。

同时该节点删除后,该节点原先的前驱和后继连了起来,保证了链表不产生断点。

删除

对外接口

static inline void list_del(struct list_head *entry)

{

__list_del(entry->prev, entry->next);

entry->next = LIST_POISON1;

entry->prev = LIST_POISON2;

}

entry:要删除的节点

删除节点,并将节点的next,prev分别指向LIST_POISON1,LIST_POISON2。

两个函数都调用了__list_del函数,但是list_del最后将prev,next分别指向了LIST_POSTION2和LIST_POSTION1两个特殊位置,对这两个位置的访问会引起页故障。属于不安全函数;而list_del_init最后调用INIT_LIST_HEAD将next,prev都指向了自己,属于安全函数。一般建议使用list_del_init。

注意:如果链表中的节点是通过malloc或new申请的看空间,这里调用删除接口后需要取手动释放空间

删除

对外接口

static inline void list_del_init(struct list_head *entry)

{

__list_del(entry->prev, entry->next);

INIT_LIST_HEAD(entry);

}

entry:要删除的节点

删除节点,并将节点的next,prev指向自己,即初始化。

替换

对外接口

static inline void list_replace(struct list_head *old,

struct list_head *xnew)

{

xnew->next = old->next;

xnew->next->prev = xnew;

xnew->prev = old->prev;

xnew->prev->next = xnew;

}

old:被替换的节点

new:替换节点

使用新的节点替换原有的旧节点。

通过实现可以看出list_replace函数替换后old节点与new节点指向的前驱和后继都一样,属于不安全函数;而list_replace_init则会将old的前驱和后继分开,属于安全函数;一般建议使用list_replace_init。

替换

对外接口

static inline void list_replace_init(struct list_head *old,

struct list_head *xnew)

{

list_replace(old, xnew);

INIT_LIST_HEAD(old);

}

old:被替换的节点

new:替换节点

使用新的节点替换原有的旧节点。

移动

对外接口

static inline void list_move(struct list_head *list, struct list_head *head)

{

__list_del(list->prev, list->next);

list_add(list, head);

}

list:要移动的节点

head:表头

移动节点到head之后

移动接口如果的操作的是非链表内部元素,则相当于增加接口的作用。

移动

对外接口

static inline void list_move_tail(struct list_head *list,struct list_head *head)

{

__list_del(list->prev, list->next);

list_add_tail(list, head);

}

list:要移动的节点

head:表头

移动节点到head之前

判空

对外接口

static inline int list_empty(const struct list_head *head)

{

return head->next == head;

}

head:表头

判断链表是否为空,返回1表示链表为空,0表示非空。

判空

对外接口

static inline int list_empty_careful(const struct list_head *head)

{

struct list_head *next = head->next;

return (next == head) && (next == head->prev);

}

head:表头

判断链表是否为空,返回1表示链表为空,0表示非空。

因为内核在运行时候可能有多个进程同时修改内存,判断链表是否为空需要判断他的前一个节点和后一个节点是否为空。但是注释也承认了这种判断方法的不安全性,要是保证安全操作,还需要加锁保护。

判断是否为最后一个节点

对外接口

static inline int list_is_last(const struct list_head *list,

const struct list_head *head)

{

return list->next == head;

}

list:当前节点

head:链表表头

判断当前节点是否为链表的最后一个节点。

链表合并

内部接口

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:被合并的链表

prev:合并位置的前一个节点

next:合并位置的后一个节点

ist作为被合并链表的表头,合并相当于将一个链表作为一个整体插入到另一个链表的某一个位置。

first作为表头list的后继,last作为表头list的前驱;表头list的后继的前驱(first→prev)指向合并位置的前一个节点;表头list的前驱的后继(last→next)指向合并位置的后一个节点。

注意:合并后的方要保持一致,还是一个环形。

链表合并

对外接口

static inline void list_splice(struct list_head *list, struct list_head *head)

{

if (!list_empty(list))

__list_splice(list, head, head->next);

}

list:被合并的链表

head:将list合并到head链表

将list链表合并到head链表,合并方式为头插法合入,即将list加入到head之后。

该函数内部实现调用了__list_splice,合并原理见__list_splice详细说明

list_splice于list_splice_init的区别是list_splice_init在合并后将list进行了初始化,属于安全函数。

建议使用list_splice_init

链表合并

对外接口

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);

}

}

链表合并

对外接口

static inline void list_splice_tail(struct list_head *list,struct list_head *head)

{

if (!list_empty(list))

__list_splice(list, head->prev, head);

}

list:被合并的链表

head:将list合并到head链表

将list链表合并到head链表,合并方式为尾插法合入,即将list加入到head之前。

list_splice_tail于list_splice_tail_init的区别是list_splice_tail_init在合并后将list进行了初始化,属于安全函数。

建议使用list_splice_tail_init

链表合并

对外接口

static inline void list_splice_tail_init(struct list_head *list,struct list_head *head)

{

if (!list_empty(list)) {

__list_splice(list, head->prev, head);

INIT_LIST_HEAD(list);

}

}

部分宏说明:(附demo)

宏定义

实现

说明

备注

offsetof

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

获取变量在宿主结构中的偏移量

(TYPE*)0 这个是欺骗编译器,有一个TYPE类型的指针,它的地址是0;然后对这个指针的MEMBER取地址,因为基地址是0,所以对MEMBER取地址后就是MEMBER在TYPE中的偏移量了。

欺骗编译器的用法类似如下:

int *p = (int *)0; 打印p的地址是nil

container_of

#define container_of(ptr, type, member) (\

{const typeof( ((type *)0)->member ) *__mptr = (ptr);\

(type *)( (char *)__mptr - offsetof(type,member) );})

根据结构中某个成员的地址获取宿主结构的地址

ptr:成员地址。

type:宿主结构类型。

member:宿主结构中定义的成员。与ptr同类型。

使用与list_entry相同。

由于type是类型,member是成员,所以这里还是使用(type *)0)->member则表示type类型指针的member成员,然后用typeof取member的类型,这样__mptr就与member成员类型是一样的,最终目的还是为了定义一个与ptr类型相同的中间变量,并且将ptr赋值给它。

(type *)( (char *)__mptr - offsetof(type,member) );

这一步骤是通过成员现地址减去成员偏移量计算宿主结构体首地址,这里必须对mptr强转成(char*)类型指针,原因是指针加减操作不同于我们int变量的加减,

比如指针的+1操作是指针当前地址+1*N(指针类型大小)。强转成char*后,+n就是指针当前地址+n。如果不强转,直接偏移会越界。

其实定于中间变量这一步我们可以省略,因为我们是通过成员prt的地址来计算宿主结构体的首地址,所以我们可以直接使用ptr,现对container_of改造如下:

#define container_of(ptr, type, member) (\

{(type*)((char *)ptr - offsetof(type,member));})

list_entry

#define list_entry(ptr, type, member) \

container_of(ptr, type, member)

根据成员ptr的地址获取宿主结构体的首地址。

type:宿主结构体的类型。

member:ptr在宿主结构体中的成员变量名。

struct student

{

char name[32];

int age;

struct list_head list;

};

ptr则表示一个一个list_head类型的指针,type则是struct student类型,member则是成员变量list。

list_first_entry

#define list_first_entry(ptr, type, member) \

list_entry((ptr)->next, type, member)

根据成员ptr的地址获取ptr下一个节点的宿主结构体首地址。

type:宿主结构体的类型

member:ptr在宿主结构体中的成员变量名。

同list_entry

list_for_each

#define list_for_each(pos, head) \

for (pos = (head)->next; pos != (head); \

pos = pos->next)

从head之后的一个节点开始向后循环,一般与list_entry或其它方法配合使用。循环时节点无法操作。

pos:list_head类型的指针。

head:链表头。

void Data_list_for_each()

{

struct list_head *postion = NULL;

struct student *p = NULL;

/*

参数1:struct list_head结构体指针

参数2:头指针

*/

//list_for_each_prev(postion,&g_list)

list_for_each(postion,&g_list)

{

/*

参数1:struct list_head结构体指针

参数2:链表容器结构体类型,这里即struct student

参数3:链表容器中链表的成员变量

*/

//二选一,list_entry的实现内部其实调用了container_of

p = list_entry(postion,struct student,list);

//p = container_of(postion,struct student,list);

printf("%s-%d\n",p->name,p->age);

}

return;

}

list_for_each_prev

#define list_for_each_prev(pos, head) \

for (pos = (head)->prev; pos != (head); \

pos = pos→prev)

从head之后的一个节点开始向前循环,一般与list_entry或其它方法配合使用。

pos:list_head类型的指针。

head:链表头。

list_for_each_safe

#define list_for_each_safe(pos, n, head) \

for (pos = (head)->next, n = pos->next; pos != (head); \

pos = n, n = pos->next)

list_for_each的安全版本,循环时可以操作节点。

从该宏的实现我们可以看到,相对于list_for_each多了一个临时变量n

void Data_list_del_1()

{

struct list_head *postion = NULL;

struct list_head *tmp = NULL;

struct student *p = NULL;

list_for_each_safe(postion,tmp,&g_list)

{

p = list_entry(postion,struct student,list);

list_del(&p->list);

//list_del_init(&p->list);

free(p);

}

return;

}

list_for_each_entry

#define list_for_each_entry(pos, head, member) \

for (pos = list_entry((head)->next, typeof(*pos), member); \

&pos->member != (head); \

pos = list_entry(pos->member.next, typeof(*pos), member))

与list_for_each功能相似,只是参数不同,这里的pos是宿主结构体类型,我们可以理解为这个就是list_for_each与list_entry的使用结合体。

比如我们要使用list_for_each对链表进行遍历,并且遍历宿主结构体时就需要与list_entry进行配合。list_for_each_entry刚好实现了这个功能。

pos:宿主结构体类型指针。

head:链表头。

member:宿主结构中链表的成员变量。

void Data_list_for_each_entry()

{

struct student *p = NULL;

/*

参数1:链表容器结构体指针

参数2:头指针

参数3:链表容器中链表的成员变量

*/

list_for_each_entry(p,&g_list,list)

{

printf("%s-%d\n",p->name,p->age);

}

return;

}

list_for_each_entry_reverse

#define list_for_each_entry_reverse(pos, head, member) \

for (pos = list_entry((head)->prev, typeof(*pos), member); \

&pos->member != (head); \

pos = list_entry(pos->member.prev, typeof(*pos), member))

反向遍历,list_for_each_entry为正向遍历

void Data_list_for_each_entry_reverse()

{

struct student head;

struct student *p = NULL;

//参数定义同Data_list_for_each_entry函数

list_for_each_entry_reverse(p,&g_list,list)

{

printf("%s-%d\n",p->name,p->age);

}

return;

}

list_for_each_entry_continue

#define list_for_each_entry_continue(pos, head, member) \

for (pos = list_entry(pos->member.next, typeof(*pos), member); \

&pos->member != (head); \

pos = list_entry(pos->member.next, typeof(*pos), member))

从某个节点开始遍历。

需要与list_prepare_entry(pos,head,member) 配合使用,list_prepare_entry的返回值作为list_for_each_entry_continue的pos。

list_for_each_entry_safe_continue与list_for_each_entry_continue一样,只不过list_for_each_entry_safe_continue遍历后可以直接操作节点,比如删除。因此带safe的函数多了一个中间的临时变量。

pos:宿主结构体类型指针。

head:链表头。

member:宿主结构中链表的成员变量。

void Data_list_for_each_entry_continue()

{

struct student *pos = NULL;

struct student *n = NULL;

n = list_prepare_entry(pos,&g_list,list);

/*

list_for_each_entry_safe_continue与list_for_each_entry_continue一样,只不过

list_for_each_entry_safe_continue遍历后可以直接操作节点,比如删除。因此带safe的

函数多了一个中间的临时变量。

*/

list_for_each_entry_continue(n,&g_list,list)

{

printf("%s-%d\n",n->name,n->age);

}

return;

}

list_for_each_entry_safe

#define list_for_each_entry_safe(pos, n, head, member) \

for (pos = list_entry((head)->next, typeof(*pos), member), \

n = list_entry(pos->member.next, typeof(*pos), member); \

&pos->member != (head); \

pos = n, n = list_entry(n->member.next, typeof(*n), member))

list_for_each_entry的安全版本,即在遍历的时候可以操作节点。属于正向遍历。

这里我们看到用临时节点n保存了pos的下一个节点地址,这样我们就可以进项删除操作了,删除pos之后,再将n赋值给pos,

整个链表结构是完整的。

pos:宿主结构体类型指针。

n:宿主结构体临时指针。

head:链表头。

member:宿主结构中链表的成员变量。

void Data_list_del_2()

{

//删除链表中元素

struct student *n = NULL;

struct student *pos = NULL;

list_for_each_entry_safe(pos,n,&g_list,list)

{

/*list_del与list_del_init都是删除节点,但是list_del_init属于安全删除*/

//list_del(&pos->list);

list_del_init(&pos->list);

free(pos);

}

return;

}

list_for_each_entry_safe_continue

#define list_for_each_entry_safe_continue(pos, n, head, member) \

for (pos = list_entry(pos->member.next, typeof(*pos), member), \

n = list_entry(pos->member.next, typeof(*pos), member); \

&pos->member != (head); \

pos = n, n = list_entry(n->member.next, typeof(*n), member))

同list_for_each_entry_continue,属于安全函数,遍历的时候节点看可以进行操作,例如删除。

list_for_each_entry_safe_reverse

#define list_for_each_entry_safe_reverse(pos, n, head, member) \

for (pos = list_entry((head)->prev, typeof(*pos), member), \

n = list_entry(pos->member.prev, typeof(*pos), member); \

&pos->member != (head); \

pos = n, n = list_entry(n->member.prev, typeof(*n), member))

功能与list_for_each_entry_safe类似,属于反向遍历。

平台通用性及移植

通用性

list_head虽然时使用C语言实现的,但是其设置的初衷是使用在linux内核,所以引入了GUN的一些方法,如typeof,该宏是用于获取参数的类型。typeof在windows,freeRtos等平台是不支持,所以在不同平台使用需要做平台适配。

例如在win32下我们可以如下定义:

#define list_for_each_entry(pos, head, member, type) \

for (pos = list_entry((head)->next, type, member); \

&pos->member != (head); \

pos = list_entry(pos->member.next, type, member))

跨平台移植

跨平台移植时list_head的实现只保留C标注,其余GUN的方法我们需要完全使用C标准的方式进行替换。

示例代码
#include "list.h"
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

struct list_head g_list;/*头指针*/

struct student
{
    char name[32];
    int age;
    struct list_head list;
};

int Data_Add(char *name,int age,int len)
{
    if(name == NULL)
    {
        printf("NULL......\n");
        return -1;
    }
    struct student *st = malloc(sizeof(struct student));
    if(st == NULL)
    {
        printf("malloc failed,reason:%d-%s\n",errno,strerror(errno));
        return -1;
    }

    memcpy(st->name,name,len);
    st->age = age;

    INIT_LIST_HEAD(&(st->list));

    //二选一
    //头插法
    //list_add(&(st->list),&g_list);

    //尾插法
    list_add_tail(&(st->list),&g_list);

    return 0;
}

//方法1遍历
void Data_list_for_each()
{
    struct list_head *postion = NULL;
    struct student *p = NULL;

    /*
        参数1:struct list_head结构体指针
        参数2:头指针
    */
    list_for_each(postion,&g_list)
    {
        /*
            参数1:struct list_head结构体指针
            参数2:链表容器结构体类型,这里即struct student
            参数3:链表容器中链表的成员变量
        */
        //二选一,list_entry的实现内部其实调用了container_of
        p = list_entry(postion,struct student,list);
        //p = container_of(postion,struct student,list);
        printf("%s-%d\n",p->name,p->age);
    }

    return;
}

//方法2遍历
void Data_list_for_each_entry()
{
    struct student *p = NULL;

    /*
        参数1:链表容器结构体指针
        参数2:头指针
        参数3:链表容器中链表的成员变量
    */
    list_for_each_entry(p,&g_list,list)
    {
        printf("%s-%d\n",p->name,p->age);
    }

    return;
}

//从某个节点开始遍历
/*
    需要与list_prepare_entry(pos,head,member) 配合使用,list_prepare_entry的
    返回值作为list_for_each_entry_continue的pos
*/
void Data_list_for_each_entry_continue()
{
    struct student *pos = NULL;
    struct student *n = NULL;

    n = list_prepare_entry(pos,&g_list,list);


    /*
        list_for_each_entry_safe_continue与list_for_each_entry_continue一样,只不过
        list_for_each_entry_safe_continue遍历后可以直接操作节点,比如删除。因此带safe的
        函数多了一个中间的临时变量。
    */
    list_for_each_entry_continue(n,&g_list,list)
    {
        printf("%s-%d\n",n->name,n->age);
    }

    return;
}


//方法1反向遍历
void Data_list_for_each_prev()
{
    struct list_head *postion = NULL;
    struct student *p = NULL;

    //参数定义同Data_list_for_each函数
    list_for_each_prev(postion,&g_list)
    {
        p = list_entry(postion,struct student,list);
        printf("%s-%d\n",p->name,p->age);
    }

    return;
}

//方法2反向遍历
void Data_list_for_each_entry_reverse()
{
    struct student head;
    struct student *p = NULL;

    //参数定义同Data_list_for_each_entry函数
    list_for_each_entry_reverse(p,&g_list,list)
    {
        printf("%s-%d\n",p->name,p->age);
    }


    return;
}

//数据替换1
void Data_list_replace()
{
    //用liangchaowei替换zhouxingxing
    struct student head;
    struct student *pos = NULL;

    struct student *data = malloc(sizeof(struct student));
    if(data == NULL)
    {
        return ;
    }

    memcpy(data->name,"liangchaowei",sizeof("liangchaowei"));
    data->age = 57;

    list_for_each_entry(pos,&g_list,list)
    {
        if(memcmp(pos->name,"zhouxingxing",sizeof("zhouxingxing")) == 0)
        {
            /*list_replace没有将pos的前驱和后继分开,所以建议使用list_replace_init*/
            //list_replace(&pos->list,&data->list);
            list_replace_init(&pos->list,&data->list);
            free(pos);
            break;
        }
    }

    return;
}


//方法1删除
//使用与Data_list_for_each类似的方法进行遍历删除
void Data_list_del_1()
{
    struct list_head *postion = NULL;
    struct list_head *tmp = NULL;
    struct student *p = NULL;

    list_for_each_safe(postion,tmp,&g_list)
    {
        p = list_entry(postion,struct student,list);
        if(memcmp(p->name,"liangchaowei",sizeof("liangchaowei")) == 0)
        {
            list_del(&p->list);
            //list_del_init(&p->list);
            free(p);
        }
    }

    return;
}


//方法2删除
//删除节点名为liangchaowei的节点
void Data_list_del_2()
{
    //删除链表中元素
    struct student *n = NULL;
    struct student *pos = NULL;
    list_for_each_entry_safe(pos,n,&g_list,list)
    {
        if(memcmp(pos->name,"zhangguorong",sizeof("zhangguorong")) == 0)
        {
            /*list_del与list_del_init都是删除节点,但是list_del_init属于安全删除*/
            //list_del(&pos->list);
            list_del_init(&pos->list);
            free(pos);
        }
    }

    return;
}

/*移动节点,将某个第三方节点添加到当前链表上,功能与新增节点相似*/
int Data_list_move(char *name,int age,int len)
{
    if(name == NULL)
    {
        printf("NULL......\n");
        return -1;
    }
    struct student *st = malloc(sizeof(struct student));
    if(st == NULL)
    {
        printf("malloc failed,reason:%d-%s\n",errno,strerror(errno));
        return -1;
    }

    memcpy(st->name,name,len);
    st->age = age;

    INIT_LIST_HEAD(&(st->list));
    /*相当于将节点进行头部插入到链表*/
    //list_move(&(st->list),&g_list);
    /*相当于将节点进行尾部插入到链表*/
    list_move_tail(&(st->list),&g_list);

    return 0;
}

int main()
{
    int res = -1;

    INIT_LIST_HEAD(&g_list);

    //数据添加
    res =  Data_Add("liudehua",50,sizeof("liudehua"));
    res &=  Data_Add("zhangxueyou",51,sizeof("zhangxueyou"));
    res &=  Data_Add("zhourunfa",52,sizeof("zhourunfa"));
    res &=  Data_Add("zhangxueyou",55,sizeof("zhangxueyou"));
    res &=  Data_Add("zhouxingxing",56,sizeof("zhouxingxing"));
    res &=  Data_Add("zhangguorong",60,sizeof("zhangguorong"));

    if(res != 0)
    {
        return -1;
    }

    //方法1遍历
    printf("------------list for each-------------------\n\n");
    Data_list_for_each();

    //方法2遍历
    printf("------------list for each entry-------------------\n\n");
    Data_list_for_each_entry();

    //方法1反向遍历
    printf("------------------list for each prev-------------\n\n");
    Data_list_for_each_prev();

    //方法2反向遍历
    printf("---------------list for each entry reverse----------------\n\n");
    Data_list_for_each_entry_reverse();

    //从某个节点开始遍历
    printf("----------------list for each entry continue---------------\n\n");
    Data_list_for_each_entry_continue();

    //数据替换
    printf("-----------------replace--------------\n\n");
    Data_list_replace();
    Data_list_for_each_entry();

    //数据删除
    printf("-----------------del 1--------------\n\n");
    Data_list_del_1();
    Data_list_for_each_entry();

    //数据删除
    printf("-----------------del 2--------------\n\n");
    Data_list_del_2();
    Data_list_for_each();

    //节点移动
    printf("-----------------move--------------\n\n");
    Data_list_move("linqingxia",61,sizeof("linqingxia"));
    Data_list_move("gutianle",62,sizeof("gutianle"));
    Data_list_for_each();

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/349691.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《python3网络爬虫开发实战 第二版》之基本库的使用-urllib的使用 详解

文章目录1 urllib 库的使用1.1 request模块1.1.1 urlopen类1.1.1.1 最简单的爬虫-爬取百度首页1.1.1.2 urlopen方法的参数1.1.1.2.1 data参数1.1.1.2.2 timeout参数1.1.1.2.3 其他参数1.1.2 Request 类1.1.3 Handler1.2 error模块1.2.1 URLError 类1.2.2 HTTPError类1.2.3 比较…

分布式-分布式服务

微服务API 网关 网关的概念来源于计算机网络&#xff0c;表示不同网络之间的关口。在系统设计中&#xff0c;网关也是一个重要的角色&#xff0c;其中最典型的是各大公司的开放平台&#xff0c;开放平台类网关是企业内部系统对外的统一入口&#xff0c;承担了很多业务&#xf…

【Java】虚拟机JVM

一、运行时数据区域 程序计数器 记录正在执行的虚拟机字节码指令的地址&#xff08;如果正在执行的是本地方法则为空&#xff09; Java虚拟机栈 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程&am…

Mysql数据库的时间(3)一如何用函数插入时间

暂时用下面四个日期函数插入时间 如:insert into Stu(time) values (now()); Mysql的时间函数描述对应的Mysql的时间类型now()/sysdate()NOW()函数以YYYY-MM-DD HH:MM:SS返回当前的日期时间date/time/dateTime/timeStamp/yearcurDate()/current_date()返回当前的日期YYYY-M…

计算机网络笔记(三)—— 数据链路层

数据链路层概述 数据链路层以帧为单位传输数据。 封装成帧&#xff1a;给网络层提供的协议数据单元添加帧头帧尾 差错检测&#xff1a;检错码封装在帧尾 可靠传输&#xff1a;尽管误码不能避免&#xff0c;但如果可以实现发送什么就接受什么&#xff0c;就叫可靠传输 封装成…

RTOS随笔之FreeRTOS启动与同步方法

RTOS启动与同步机制RTOS启动任务切换场景任务同步机制队列信号量事件组任务通知任务延时RTOS启动 FreeRTOS在任务创建完成后调用函数vTaskStartScheduler()启动任务调度器。 vTaskStartScheduler()任务启动函数详解 void vTaskStartScheduler( void ) {BaseType_t xReturn;xR…

项目管理工具dhtmlxGantt甘特图入门教程(九):支持哪些数据格式(下篇)

这篇文章给大家讲解 dhtmlxGantt可以加载或支持哪些数据格式。 dhtmlxGantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表&#xff0c;可满足应用程序的所有需求&#xff0c;是最完善的甘特图图表库 DhtmlxGantt正版试用下载&#xff08;qun&#xff1a;764148812&am…

虚拟机NAT模式无法连外网

虚拟机使用NAT模式连接外网设置时要注意两点 虚拟机的网段要与物理机保持一致各个节点的IP中的GATEWAY要与虚拟机的网关保持一致 1、虚拟机的网段要与物理机保持一致 1.1首先查看物理机的ip&#xff0c;看虚拟机和物理机ip是否在同一网段 winR cmd计入控制台&#xff0c;然…

uni-app前端H5页面底部内容被tabbar遮挡

如果你想在原生 tabbar 上方悬浮一个菜单&#xff0c;之前写 bottom:0。这样的写法编译到 h5 后&#xff0c;这个菜单会和 tabbar 重叠&#xff0c;位于屏幕底部。 原码&#xff1a; <view style"position: fixed;bottom:0;left: 0;background-color: #007AFF;right: …

PythonWeb Django PostgreSQL创建Web项目(二)

安装数据库PostgreSQL并创建数据库 我第一次尝试使用PostgreSQL数据库&#xff0c;why&#xff1f;我喜欢它提供的丰富的数据类型&#xff0c;例如货币类型、枚举类型、几何类型(点、直线、线段、矩形等等)、网络地址类型、文本搜索类型、XML类型JSON类型等等&#xff0c;非常…

Web3中文|聊聊这个让Opensea头疼的新对手Blur ($BLUR)

2022年10月19日&#xff0c;NFT市场迎来一个新的平台。 这个被精心设计的NFT交易市场和聚合器被命名为Blur。 与其他NFT平台不同&#xff0c;Blur旨在提升专业交易者的NFT交易体验。它的开发团队认为其他交易平台存在界面混杂、无法获取分析数据和工具、处理速度缓慢等问题。…

postman使用简介

1、介绍 postman是一款功能强大的网页调试和模拟发送HTTP请求的Chrome插件&#xff0c;支持几乎所有类型的HTTP请求 2、下载及安装 官方文档&#xff1a;https://www.getpostman.com/docs/v6/ chrome插件&#xff1a;chrome浏览器应用商店直接搜索添加即可&#xff08;需墙&…

在魔改PLUS-F5280开发板上使用合封qsp iflash

文章目录引言硬件调整软件调整总结引言 由于目前灵动官网暂未发布正式版的PLUS-F5280开发板&#xff0c;可以使用现有的PLUS-F5270 v1.2开发板&#xff08;下文简称PLUS-F5270开发版&#xff09;替换为MM32F5280微控制器芯片&#xff0c;改装为PLUS-F5280开发板。本文记录了使…

【Mybatis源码解析】mapper实例化及执行流程源码分析

文章目录简介环境搭建源码解析基础环境&#xff1a;JDK17、SpringBoot3.0、mysql5.7 储备知识&#xff1a;《【Spring6源码・AOP】AOP源码解析》、《JDBC详细全解》 简介 基于SpringBoot的Mybatis源码解析&#xff1a; 1.如何对mapper实例化bean 在加载BeanDefinition时&a…

哈希表题目:矩阵置零

文章目录题目标题和出处难度题目描述要求示例数据范围进阶解法一思路和算法代码复杂度分析解法二思路和算法代码复杂度分析解法三思路和算法代码复杂度分析题目 标题和出处 标题&#xff1a;矩阵置零 出处&#xff1a;73. 矩阵置零 难度 3 级 题目描述 要求 给定一个 m…

oracle单库重建undo表空间步骤

前言&#xff1a;undo表空间不足可直接增加空间&#xff1b; alter tablespace UNDOTBS1 add datafile /data/oradata/datafile/UNDOTBS102.dbf size 30g autoextend off; 注&#xff1a;单次增加最大不得超过32G 回收空间 缩小表空间直接可以resize命令缩小&#xff1b…

webpack(高级)--创建自己的loader 同步loader 异步loader loader参数校验

webpack 创建自己的loader loader是用于对模块的源代码进行转换&#xff08;处理&#xff09; 我们使用过很多loader 比如css-loader style-loader babel-loader 我么如果想要自己创建一个loader 首先创建webpack环境 pnpm add webpack webpack-cli -D 之后创建loader模块…

Hadoop初步理解

产生原因 在之前&#xff0c;数据量小&#xff0c;增长速度慢&#xff0c;且数据基本都是文件。储存和处理这些数据并不麻烦&#xff0c;单个存储单元和处理器组合就可以。 之后随着互联网发展&#xff0c;产生了大量多种形式的数据。 非结构化数据&#xff1a;邮件、图像、音…

盘点3个.Net开发的WMS仓库管理系统

更多开源项目请查看&#xff1a;一个专注推荐.Net开源项目的榜单 仓库管理系统在企业中&#xff0c;重要性越来越高&#xff0c;不仅可以提高效率&#xff0c;还能降低企业的压力&#xff0c;企业通过协调和优化资源使用和物料流动&#xff0c;能极大程度地提升了管理效率&…

中国500强|长虹控股集团携手契约锁,推动采购-人事业务电子签

四川长虹电子控股集团&#xff08;以下简称“长虹控股集团”&#xff09;是国内知名的电器制造商之一&#xff0c;拥有六家上市公司、一家新三板的公众公司&#xff0c;入选世界品牌500强、中国企业家协会发布的中国500强企业榜单。此次&#xff0c;长虹控股集团携手契约锁打造…