1 基本概念

alt text

  • 循环链表的概念:单链表的尾结点指针指向空地址,表示这就是最后的结点了。而循环链表的尾结点指针是指向链表的头结点。

  • 循环链表的特点:和单链表相比,循环链表的优点是从链尾到链头比较方便,当要处理的数据具有环型结构特点时,适合采用循环链表。

  • 双向链表的概念:双向链表也叫双链表,是链表的一种,它的链接方向是双向的,它的每个数据结点中都包含有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。

  • 双向链表的特点:头节点的pre == NULL,尾节点next == NULL

2 GLib双向链表实现

  1. GList 没有在类型系统注册对象,也就是没有 G_LIST,没有引用计数。

  2. 没有new和unref函数,而是 g_list_append 创建链接节点,g_list_free 释放节点内存空间。

  3. GList 不是多线程安全的。

2.1 GList结构体

typedef struct _GList GList;

struct _GList
{
  gpointer data;
  GList *next;
  GList *prev;
};

2.2 常用函数总结

/**
 * @brief: 如果传入参数 @list = NULL,返回@GList是头结点
 *         如果传入参数 @list != NULL, 返回参数等于传入参数
 *         如果传入参数 @list是头结点,返回也是 @list 头结点
 *         如果传入参数 @list是中间节点,返回的也是传入的 @list 中间节点
 * @note: 会遍历到最后去插入新节点
 * 
*/
GList *
g_list_append (GList    *list,
               gpointer  data) {
  GList *new_list;
  GList *last;
  
  new_list = _g_list_alloc ();
  new_list->data = data;
  new_list->next = NULL;
  
  if (list)
    {
      last = g_list_last (list);
      /* g_assert (last != NULL); */
      last->next = new_list;
      new_list->prev = last;

      return list;
    }
  else
    {
      new_list->prev = NULL;
      return new_list;
    }
}


/**
 * @brief: 在@list之前插入节点,无论传入的 list是中间节点还是头结点,只是在@list之前插入新节点,不会遍历到最头部插入。
 * @return: 新插入节点的地址。
*/
GList *
g_list_prepend (GList    *list,
                gpointer  data) {
  GList *new_list;
  
  new_list = _g_list_alloc ();
  new_list->data = data;
  new_list->next = list;
  
  if (list)
    {
      new_list->prev = list->prev;
      if (list->prev)
        list->prev->next = new_list;
      list->prev = new_list;
    }
  else
    new_list->prev = NULL;
  
  return new_list;
}

参考

参考1:看动画理解「链表」实现LRU缓存淘汰算法