个人对链表的一些理解

一.链表的作用:

个人理解:

链表与数组
相同点:把所要存储的数据连起来,
不同点:链表的是把地址不连续的数据载体链接在一起,链表可以动态链接,链表的大小不是定的,而数组不可以,数组的大小是分配好了的,后续不可以再增加(可以增加的方式是申请一个数组指针,然后动态分配内存,但是不灵活,也不好操作)。

官方理解

一、链表是什么
1、链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,有一系列结点(地址)组成,结点可动态的生成。
2、结点包括两个部分:一、存储数据元素的数据域(内存空间),二、存储指向下一个结点地址的指针域。
3、相对于线性表顺序结构,操作复杂。

二、链表的作用
1、实现数据元素的存储按一定顺序储存,允许在任意位置插入和删除结点。
2、包括单向结点,双向结点,循环接点
3、c/c++/jave都可以实现

三、链表的优缺点
优点、链表实现数据元素储存的顺序储存,是连续的
缺点、因为含有大量的指针域,所以占用空间大,同时因为只有头结点(后面说明)是明确知道地址的,所以查找链表中的元素需要从头开始寻找,非常麻烦。

二.链表的实现分析:(双向循环链表)

1.首先建一个双向链表结构体:即可向前,也可向后遍历

struct list_head {
    struct list_head *prev, *next;
}


2.尾部添加链表节点函数:
   2.1 双向循环链表需要知道 1.头节点 2.头结点的前一个节点 3.新增加的节点
static void __list_add_tail(struct list_head *new, struct list_head *head_prve, struct list_head *head)
{
/*
    1.首先将新节点的前后节点配置好。
    2.更新添加新节点前的head->prev指向和head->prev节点(即最后一个节点)的next指向。----即更新最后一个节点。注意更新的顺序:先更新head->prev.next,后更新head->prev;
 */   
 /* 1.*/
    new->prev      = head_prev; //新节点的前节点
    new->next      = head;       //新节点的后一个节点
    
 /* 2.*/
    head_prev.next = new;
    head->prev     = new;
    
}
3.其次更新查找函数
    3.1双向循环链表,既可以向前查找,也可以向后查找,
       向前查找:即每次查找的是prev指针指向的节点
       3.1.1
        需要一个记录当前节点的指针mod ,
        需要一个知道确定地址节点,而我们只有(head)头节点的地址是知道的。
#define __list_for_each(mod, head)   {  for (mod = head->prev; mod->prev != head; mod = mod->prev)    \ } //这是一个循环。

       向后查找:即每次查找的是next指针指向的节点
#define __list_for_each(mod, head)   {  for (mod = head->next; mod->next != head; mod = mod->next)    \ } //这是一个循环。

//头结点是不会用来存取数据的。

4.获取链表节点首地址的函数和存储数据的结构体
   4.1 要知道链表的当前节点mod
   4.2 需要知道存储数据结构体类型(用于从链表节点中获取偏移地址)
   4.2 需要知道储存结构体类型中为链表的成员名(用于通过偏移地址获取存储结     构体地址)
struct list_test{
    int value;
    struct list_head test;
    char *name;
}

实现:
/*
1.首先获取在链表成员在存储结构体中的偏移地址。
  ((unsigned int)(((tyep *)(0)->list_head))
2.获取当前链表节点的地址。
  (unsigned int)(mod)
3.通过偏移地址和链表节点地址求出对应的存储结构体的地址
#define __list_entry(mod, type, member_name) \
    (type *)((unsigned int)(mod) - ((unsigned int)(&(((type *)(0))->member_name))))
    

三.linux内核下一些常用的操作链表的函数

struct list {
	struct list *next, *prev;
};

#define (__lname)  { &(__lname), &(__lname) }
#define LIST_HEAD(_lname)   struct list _lname = INIT_HEAD(_lname)//连带链表头结点的创建和初始化。
#define INIT_LIST_HEAD(ptr)  do { \
	(ptr)->next = ptr; (ptr)->prev = ptr;   \
	} while (0)

#define list_entry(ptr, type, member) \
	((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

#define list_for_each(curr, head) \
	for (curr = (head)->next; curr != head; curr = (curr)->next)

#define list_for_each_entry(ptr, head, member) \
	for (ptr = list_entry((head)->next, typeof(*ptr), member); \
		&ptr->member != (head); \
		ptr = list_entry(ptr->member.next, typeof(*ptr), member))
//list_for_each_entry 遍历并获取存储结构体的地址到ptr.

分析到这里差不多就完了,其实就是用过链表把很多个数据连载一起(一般是结构体数据类型),然后就通过偏移地址的方式获取存储数据结构的地址的以达获取数据的目的。

猜你喜欢

转载自blog.csdn.net/qq_16919359/article/details/90039042
今日推荐