4_07_GLib库入门与实践_双链表

简介

在Glib中,链表有单向链表和双向链表两种,单向链表只有一个next指针,双向链表有prev和next两个指针。除非对程序的内存使用有非常严格的限制,一般情况下,尽量使用双向链表,这在查找、插入等操作时比单向链表方便很多。
单向链表的数据结构为GSList,双向链表的数据结构为GList,两者操作方式基本相同,只不过单向链表比双向链表少很多操作函数。

数据结构

单向链表数据结构

struct GSList {
  gpointer data;
  GSList *next;
};

双向链表数据结构

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

函数列表

GList *	g_list_append ()
GList *	g_list_prepend ()
GList *	g_list_insert ()
GList *	g_list_insert_before ()
GList *	g_list_insert_sorted ()
GList *	g_list_remove ()
GList *	g_list_remove_link ()
GList *	g_list_delete_link ()
GList *	g_list_remove_all ()
void	g_list_free ()
void	g_list_free_full ()
GList *	g_list_alloc ()
void	g_list_free_1 ()
guint	g_list_length ()
GList *	g_list_copy ()
GList *	g_list_copy_deep ()
GList *	g_list_reverse ()
GList *	g_list_sort ()
gint	(*GCompareFunc) ()
GList *	g_list_insert_sorted_with_data ()
GList *	g_list_sort_with_data ()
gint	(*GCompareDataFunc) ()
GList *	g_list_concat ()
void	g_list_foreach ()
void	(*GFunc) ()
GList *	g_list_first ()
GList *	g_list_last ()
#define	g_list_previous()
#define	g_list_next()
GList *	g_list_nth ()
gpointer	g_list_nth_data ()
GList *	g_list_nth_prev ()
GList *	g_list_find ()
GList *	g_list_find_custom ()
gint	g_list_position ()
gint	g_list_index ()

函数功能分类

创建

GList

插入

GList * 	g_list_append ()
GList * 	g_list_prepend ()
GList * 	g_list_insert ()
GList * 	g_list_insert_before ()

逆序

GList * 	g_list_reverse ()

释放

void	g_list_free ()
void	g_list_free_full ()
void	g_list_free_1 ()

遍历

void 	g_list_foreach ()

测长

guint 	g_list_length ()

访问

GList * 	g_list_first ()
GList * 	g_list_last ()
#define 	g_list_previous()
#define 	g_list_next()

查找

GList * 	g_list_nth ()
gpointer 	g_list_nth_data ()
GList * 	g_list_nth_prev ()
gint 	g_list_position ()
gint 	g_list_index ()
GList * 	g_list_find ()
GList * 	g_list_find_custom ()

排序

GList * 	g_list_sort ()
GList * 	g_list_sort_with_data ()
GList * 	g_list_insert_sorted ()
GList * 	g_list_insert_sorted_with_data ()

删除

GList * 	g_list_remove ()
GList * 	g_list_remove_all ()
GList * 	g_list_remove_link ()
GList * 	g_list_delete_link ()

拼接

GList * 	g_list_concat ()

拷贝

GList * 	g_list_copy ()
GList * 	g_list_copy_deep ()

函数功能说明及综合演示

创建一个链表

GList没有专门创建链表的函数,只需要定义一个节点,即创建了链表。

GList *list;
节点插入

节点的插入有四个基本函数

GList * 	g_list_append ()
GList * 	g_list_prepend ()
GList * 	g_list_insert ()
GList * 	g_list_insert_before ()
节点释放
void	g_list_free () //释放所有节点
void	g_list_free_full () //释放所有节点,并指定私有数据的释放方式
void	g_list_free_1 () //释放一个节点,使用较少

下面来看一个节点插入及释放的例子。
源码见glib_examples\glib_list\glib_list_append_free

#include 

gint main(gint argc, gchar **argv)
{
    GList *list = NULL;
    GList *iter= NULL;

    list = g_list_append(list, GINT_TO_POINTER(1));
    list = g_list_append(list, GINT_TO_POINTER(3));
    list = g_list_append(list, GINT_TO_POINTER(5));

    for (iter = list; iter != NULL; iter = iter->next)
    {
        g_print("data is: %d \n", GPOINTER_TO_INT(iter->data));
    }

    g_list_free(list);

    return 0;
}

运行结果

data is: 1 
data is: 3 
data is: 5

分析:
首先创建一个空链表list,并插入三个整数,然后把链表的值全部打印出来,最后释放整个链表。使用valgrind工具检查一下是否有内存泄露。命令如下
valgrind --tool=memcheck --leak-check=full ./glib_list_append_free

[root@centos7_6 build]# valgrind --tool=memcheck --leak-check=full ./glib_list_append_free
==26247== Memcheck, a memory error detector
==26247== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==26247== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==26247== Command: ./glib_list_append_free
==26247== 
data is: 1 
data is: 3 
data is: 5 
==26247== 
==26247== HEAP SUMMARY:
==26247==     in use at exit: 2,214 bytes in 9 blocks
==26247==   total heap usage: 32 allocs, 23 frees, 102,209 bytes allocated
==26247== 
==26247== LEAK SUMMARY:
==26247==    definitely lost: 0 bytes in 0 blocks
==26247==    indirectly lost: 0 bytes in 0 blocks
==26247==      possibly lost: 0 bytes in 0 blocks
==26247==    still reachable: 2,214 bytes in 9 blocks
==26247==         suppressed: 0 bytes in 0 blocks
==26247== Reachable blocks (those to which a pointer was found) are not shown.
==26247== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==26247== 
==26247== For counts of detected and suppressed errors, rerun with: -v
==26247== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 f

可以看到,没有内存泄露。

链表遍历

上面介绍了一种链表的遍历方法,实际上,链表还有一个函数来专门进行元素遍历。

void
g_list_foreach (GList *list,
                GFunc func,
                gpointer user_data);

这个函数的第二个参数是一个函数指针,也就是常说的回调函数,用来指定如何遍历节点,这在GLib中是一种非常常见的用法,后面会经常见到。
第二个参数的指针函数原型如下:

void
(*GFunc) (gpointer data,
          gpointer user_data);

举例:可以实现如下回调函数,将结构体成员打印出来。

void _list_interger_print(gpointer data, gpointer user_data)
{
    g_print("data is: %d, user_data is: %s \n", GPOINTER_TO_INT(data), (gchar *)user_data);
}

这个回调函数的第一个参数data即是GList链表的data成员,第二个参数user_data是g_list_foreach的第三个参数,为回调函数的私有参数。下面程序演示使用g_list_foreach对链表进行遍历。
源码见glib_examples\glib_list\glib_list_foreach

#include 

void _list_interger_print(gpointer data, gpointer user_data)
{
    g_print("data is: %d, user_data is: %s \n", GPOINTER_TO_INT(data), (gchar *)user_data);
}

gint main(gint argc, gchar **argv)
{
    GList *list = NULL;
//    GList *iter= NULL;

    list = g_list_append(list, GINT_TO_POINTER(1));
    list = g_list_append(list, GINT_TO_POINTER(3));
    list = g_list_append(list, GINT_TO_POINTER(5));
    
//    for (iter = list; iter != NULL; iter = iter->next)
//    {
//        g_print("data is: %d \n", GPOINTER_TO_INT(iter->data));
//    }

    g_list_foreach(list, _list_interger_print, (gchar *)"");

    g_list_free(list);

    return 0;
}

运行结果如下:

data is: 1, user_data is: <user data> 
data is: 3, user_data is: <user data> 
data is: 5, user_data is: <user data>
链表插入

GLib为节点插入提供了四个函数:

GList * 	g_list_append ()
GList * 	g_list_prepend ()
GList * 	g_list_insert ()
GList * 	g_list_insert_before ()

第一个g_list_append前面已经介绍过了,此函数在大量插入节点时效率较低,原因是GList数据结构没有存储尾节点,因此在尾插时,只能先从头开始遍历,找到尾节点,再插入。在官方文档中介绍了一种常用的节点插入方法,即头插再逆序的方法,使用g_list_prepend插入所有节点,再使用g_list_reverse逆序。g_list_prepend的使用方法非常简单,与g_list_append几乎一模一样。
源码见glib_examples\glib_list\glib_list_prepend

#include 

void _list_interger_print(gpointer data, gpointer user_data)
{
    g_print("data is: %d, user_data is: %s \n", GPOINTER_TO_INT(data), (gchar *)user_data);
}

gint main(gint argc, gchar **argv)
{
    GList *list = NULL;

    list = g_list_prepend(list, GINT_TO_POINTER(1));
    list = g_list_prepend(list, GINT_TO_POINTER(3));
    list = g_list_prepend(list, GINT_TO_POINTER(5));

    g_list_foreach(list, _list_interger_print, (gchar *)"");

    g_list_free(list);

    return 0;
}

运行结果:

data is: 5, user_data is: <user data> 
data is: 3, user_data is: <user data> 
data is: 1, user_data is: <user data>

对上述链表进行逆序。
源码见glib_examples\glib_list\glib_list_prepend_reverse

#include 

void _list_interger_print(gpointer data, gpointer user_data)
{
    g_print("data is: %d, user_data is: %s \n", GPOINTER_TO_INT(data), (gchar *)user_data);
}

gint main(gint argc, gchar **argv)
{
    GList *list = NULL;

    list = g_list_prepend(list, GINT_TO_POINTER(1));
    list = g_list_prepend(list, GINT_TO_POINTER(3));
    list = g_list_prepend(list, GINT_TO_POINTER(5));

    list = g_list_reverse(list);

    g_list_foreach(list, _list_interger_print, (gchar *)"");

    g_list_free(list);

    return 0;
}

可以看到,头插再逆序的方法与尾插法结果相同。

data is: 1, user_data is: <user data> 
data is: 3, user_data is: <user data> 
data is: 5, user_data is: <user data>

接下来来看一下g_list_insert函数,其原型如下:

GList *
g_list_insert (GList *list,
               gpointer data,
               gint position);

第二个参数是要插入的数据,这个数据不需要包含prev和next指针的,链表自身会自动将节点补充完整。
第三个参数是插入的位置,从0开始,如果该值小于0或大于整个链表长度,则节点会被插入到链表尾。
链表插入举例:
源码见glib_examples\glib_list\glib_list_insert

#include 

void _list_interger_print(gpointer data, gpointer user_data)
{
    g_print("data is: %d, user_data is: %s \n", GPOINTER_TO_INT(data), (gchar *)user_data);
}

gint main(gint argc, gchar **argv)
{
    GList *list = NULL;

    list = g_list_append(list, GINT_TO_POINTER(1));
    list = g_list_append(list, GINT_TO_POINTER(3));
    list = g_list_append(list, GINT_TO_POINTER(5));

    list = g_list_insert(list, GINT_TO_POINTER(2), 1);

    g_list_foreach(list, _list_interger_print, (gchar *)"");

    g_list_free(list);

    return 0;
}

运行结果:

data is: 1, user_data is: <user data> 
data is: 2, user_data is: <user data> 
data is: 3, user_data is: <user data> 
data is: 5, user_data is: <user data>

下面介绍另一种插入方法:g_list_insert_before函数和g_list_nth函数的组合。

GList *
g_list_insert_before (GList *list,
                      GList *sibling,
                      gpointer data);

第二个参数表示要在此节点之前插入data数据,很明显,需要先得到这个节点。
链表的访问和查找函数都可以得到一个具体的节点,g_list_nth可以根据下标得到链表中的某一节点。

GList *
g_list_nth (GList *list,
            guint n);

下面举例演示g_list_insert_before函数和g_list_nth函数的用法。
源码见glib_examples\glib_list\glib_list_insert_before

#include 

void _list_interger_print(gpointer data, gpointer user_data)
{
    g_print("data is: %d, user_data is: %s \n", GPOINTER_TO_INT(data), (gchar *)user_data);
}

gint main(gint argc, gchar **argv)
{
    GList *list = NULL;
    GList *l = NULL;

    list = g_list_append(list, GINT_TO_POINTER(1));
    list = g_list_append(list, GINT_TO_POINTER(3));
    list = g_list_append(list, GINT_TO_POINTER(5));

    list = g_list_insert(list, GINT_TO_POINTER(2), 1);


    g_list_foreach(list, _list_interger_print, (gchar *)"");

    l = g_list_nth(list, 3);
    list = g_list_insert_before(list, l, GINT_TO_POINTER(4));

    g_list_foreach(list, _list_interger_print, (gchar *)"");

    g_list_free(list);

    return 0;
}

运行结果:

data is: 1, user_data is: <before g_list_insert_before> 
data is: 2, user_data is: <before g_list_insert_before> 
data is: 3, user_data is: <before g_list_insert_before> 
data is: 5, user_data is: <before g_list_insert_before> 
data is: 1, user_data is: <after g_list_insert_before> 
data is: 2, user_data is: <after g_list_insert_before> 
data is: 3, user_data is: <after g_list_insert_before> 
data is: 4, user_data is: <after g_list_insert_before> 
data is: 5, user_data is: <after g_list_insert_before>
链表访问

链表有以下四个节点访问函数

GList * 	g_list_first ()
GList * 	g_list_last ()
#define 	g_list_previous()
#define 	g_list_next()

举例如下:
源码见glib_examples\glib_list\glib_list_access

#include 

gint main(gint argc, gchar **argv)
{
    GList *list = NULL;
    GList *l = NULL;
#if 1
    list = g_list_append(list, GINT_TO_POINTER(1));
    list = g_list_append(list, GINT_TO_POINTER(3));
    list = g_list_append(list, GINT_TO_POINTER(5));
    list = g_list_append(list, GINT_TO_POINTER(7));
#endif
    l = g_list_last(list);
    g_print("last element: %d \n", (NULL == l) ? 99999 : GPOINTER_TO_INT(l->data));

    l = g_list_previous(l);
    g_print("last->prev element: %d \n", (NULL == l) ? 99999 : GPOINTER_TO_INT(l->data));

    l = g_list_first(list);
    g_print("first element: %d \n", (NULL == l) ? 99999 : GPOINTER_TO_INT(l->data));

    l = g_list_next(l);
    g_print("first->next element: %d \n", (NULL == l) ? 99999 : GPOINTER_TO_INT(l->data));

    g_list_free(list);

    return 0;
}

运行结果:

last element: 7 
last->prev element: 5 
first element: 1 
first->next element: 3
链表查找
GList * 	g_list_nth ()
gpointer 	g_list_nth_data ()
GList * 	g_list_nth_prev ()
gint 	g_list_position ()
gint 	g_list_index ()
GList * 	g_list_find ()
GList * 	g_list_find_custom ()
//g_list_nth是查找里面最简单的一个函数,前面已经介绍过,传入下标,得到节点,注意下标以0开始。
GList *
g_list_nth (GList *list,
            guint n);

//g_list_nth_data与g_list_nth相仿,只不过g_list_nth返回节点,g_list_nth_data返回节点的值。
gpointer
g_list_nth_data (GList *list,
                 guint n);

//注意此函数:不是返回第n个节点的前一个节点,而是返回链表中某一节点的向前第n个节点,n为函数的第二个参数。
GList *
g_list_nth_prev (GList *list,
                 guint n);

//根据链表中的一个节点找到其在链表中的位置
gint
g_list_position (GList *list,
                 GList *llink);

//根据值找到节点在链表中的位置
gint
g_list_index (GList *list,
              gconstpointer data);

//根据节点的值找到节点。
GList *
g_list_find (GList *list,
             gconstpointer data);

//根据节点的值找到节点,但比较函数由用户自己实现。
//链表中的每一个节点,都会通过比较函数进行比较,若比较函数返回0,则代表节点已找到,//g_list_find_custom退出。
//本函数我们放到最后再讲。
GList *
g_list_find_custom (GList *list,
                    gconstpointer data,
                    GCompareFunc func);

下面是对查找函数的举例演示:
源码见glib_examples\glib_list\glib_list_find

#include 

gint main(gint argc, gchar **argv)
{
    GList *list = NULL;
    GList *l = NULL;
    gint n = 0;

    list = g_list_append(list, GINT_TO_POINTER(1));
    list = g_list_append(list, GINT_TO_POINTER(3));
    list = g_list_append(list, GINT_TO_POINTER(5));
    list = g_list_append(list, GINT_TO_POINTER(7));

    l = g_list_nth(list, 1);
    g_print("nth(list,1) element: %d \n", (NULL == l) ? 99999 : GPOINTER_TO_INT(l->data));

    n = GPOINTER_TO_INT(g_list_nth_data(list, 1));
    g_print("nth_data(list,1) element: %d \n", n);

    l = g_list_last(list);
    //7->5->3->1
    l = g_list_nth_prev(l, 3);
    g_print("nth_prev(last,3) element: %d \n", (NULL == l) ? 99999 : GPOINTER_TO_INT(l->data));

    l = g_list_last(list);
    n = g_list_position(list, l);
    g_print("last position: %d \n", n);

    n = g_list_index(list, GINT_TO_POINTER(5));
    g_print("element 5 position: %d \n", n);

    l = g_list_find(list, GINT_TO_POINTER(5));
    g_print("element 5 data: %d \n", (NULL == l) ? 99999 : GPOINTER_TO_INT(l->data));

    g_list_free(list);

    return 0;
}

运行结果:

nth(list,1) element: 3 
nth_data(list,1) element: 3 
nth_prev(last,2) element: 3 
last position: 3 
element 5 position: 2 
element 5 data: 5

可以看到,在GList中,节点以0为下标,即链表的第一个节点的下标为0。

链表排序
GList * 	g_list_sort ()
GList * 	g_list_sort_with_data ()
GList * 	g_list_insert_sorted ()
GList * 	g_list_insert_sorted_with_data ()
//链表排序,排序函数用户自己指定。
GList *
g_list_sort (GList *list,
             GCompareFunc compare_func);
//排序函数原型:
gint
(*GCompareFunc) (gconstpointer a,
                 gconstpointer b);
//排序函数返回值:negative value if a < b ; zero if a = b ; positive value if a > b;ab返回正值

//与g_list_sort功能相似,不过可以向比较函数传入用户数据。
GList *
g_list_sort_with_data (GList *list,
                       GCompareDataFunc compare_func,
                       gpointer user_data);

//插入一个数据data,其插入位置由比较函数决定。
//其实现方式为,先通过比较函数找到要插入的链表位置,再将数据插入链表。
//此处要求链表需要是有序的,如果原链表无序,此函数并不能实现插入数据后使链表变为有序状态的功能。
GList *
g_list_insert_sorted (GList *list,
                      gpointer data,
                      GCompareFunc func);

//与g_list_insert_sorted功能相似,不过可以向比较函数传入用户数据。
GList *
g_list_insert_sorted_with_data (GList *list,
                                gpointer data,
                                GCompareDataFunc func,
                                gpointer user_data);

下面是针对排序函数的演示程序。
源码见`glib_examples\glib_list\glib_list_sort

#include 

void _list_interger_print_func(gpointer data, gpointer user_data)
{
    g_print("data is: %d, user_data is: %s \n", GPOINTER_TO_INT(data), (gchar *)user_data);
}

gint _list_interger_cmp_func(gconstpointer a, gconstpointer b)
{
    return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
}

gint main(gint argc, gchar **argv)
{
    GList *list = NULL;

    list = g_list_append(list, GINT_TO_POINTER(1));
    list = g_list_append(list, GINT_TO_POINTER(5));
    list = g_list_append(list, GINT_TO_POINTER(3));
    list = g_list_append(list, GINT_TO_POINTER(7));

    g_list_foreach(list, _list_interger_print_func, (gchar *)"before insert_sorted");

    list = g_list_insert_sorted(list, GINT_TO_POINTER(4), _list_interger_cmp_func);

    g_list_foreach(list, _list_interger_print_func, (gchar *)"after insert_sorted");

    list = g_list_sort(list, _list_interger_cmp_func);

    g_list_foreach(list, _list_interger_print_func, (gchar *)"after sorted");

    g_list_free(list);

    return 0;
}

运行结果:

data is: 1, user_data is: before insert_sorted 
data is: 5, user_data is: before insert_sorted 
data is: 3, user_data is: before insert_sorted 
data is: 7, user_data is: before insert_sorted 
data is: 1, user_data is: after insert_sorted 
data is: 4, user_data is: after insert_sorted 
data is: 5, user_data is: after insert_sorted 
data is: 3, user_data is: after insert_sorted 
data is: 7, user_data is: after insert_sorted 
data is: 1, user_data is: after sorted 
data is: 3, user_data is: after sorted 
data is: 4, user_data is: after sorted 
data is: 5, user_data is: after sorted 
data is: 7, user_data is: after sorted

先调用g_list_insert_sorted,再调用g_list_sort,可以看出,当链表本身无序时,g_list_insert_sorted仅能在找到比自己大的数据后插入到其前面,并不能排序,而g_list_sort函数可以对链表进行排序。

链表删除
GList * 	g_list_remove ()
GList * 	g_list_remove_all ()
GList * 	g_list_remove_link ()
GList * 	g_list_delete_link ()
//删除链表中的节点,如果有多个相同的节点,则仅第一个被删除
GList *
g_list_remove (GList *list,
               gconstpointer data);

//删除链表中的节点,如果有多个相同的节点,则所有节点都会被删除
GList *
g_list_remove_all (GList *list,
                   gconstpointer data);

//将节点从链表移出,但并不释放该节点,返回的是一个自包含的链表,该链表的prev和next指针均为空。调用完本函数后,用户需要自己释放该节点,否则会造成内存泄露。
GList *
g_list_remove_link (GList *list,
                    GList *llink);

//删除节点,并且节点会被释放
GList *
g_list_delete_link (GList *list,
                    GList *link_);

节点删除功能演示:
源码见glib_examples\glib_list\glib_list_remove

#include 

void _list_interger_print_func(gpointer data, gpointer user_data)
{
    g_print("data is: %d, user_data is: %s \n", GPOINTER_TO_INT(data), (gchar *)user_data);
}

gint main(gint argc, gchar **argv)
{
    GList *list = NULL;
    GList *l = NULL;

    list = g_list_append(list, GINT_TO_POINTER(1));
    list = g_list_append(list, GINT_TO_POINTER(5));
    list = g_list_append(list, GINT_TO_POINTER(3));
    list = g_list_append(list, GINT_TO_POINTER(5));
    list = g_list_append(list, GINT_TO_POINTER(5));
    list = g_list_append(list, GINT_TO_POINTER(7));

    g_list_foreach(list, _list_interger_print_func, (gchar *)"original list");

    list = g_list_remove(list, GINT_TO_POINTER(5));

    g_list_foreach(list, _list_interger_print_func, (gchar *)"after remove 5");

    list = g_list_remove_all(list, GINT_TO_POINTER(5));

    g_list_foreach(list, _list_interger_print_func, (gchar *)"after remove_all 5");

    l = g_list_last(list);

    list = g_list_remove_link(list, l);

    g_list_free(l);

    l = g_list_first(list);

    list = g_list_delete_link(list, l);

    g_list_foreach(list, _list_interger_print_func, (gchar *)"after remove first and last");

    g_list_free(list);

    return 0;
}

运行结果:

data is: 1, user_data is: original list 
data is: 5, user_data is: original list 
data is: 3, user_data is: original list 
data is: 5, user_data is: original list 
data is: 5, user_data is: original list 
data is: 7, user_data is: original list 
data is: 1, user_data is: after remove 5 
data is: 3, user_data is: after remove 5 
data is: 5, user_data is: after remove 5 
data is: 5, user_data is: after remove 5 
data is: 7, user_data is: after remove 5 
data is: 1, user_data is: after remove_all 5 
data is: 3, user_data is: after remove_all 5 
data is: 7, user_data is: after remove_all 5 
data is: 3, user_data is: after remove first and last

屏蔽掉g_list_free(l);一行,使用valgrind工具,可以看到,如果不对g_list_remove_link移出的节点进行释放,会导致内存泄露。
valgrind --tool=memcheck --leak-check=full ./glib_list_remove

[root@centos7_6 build]# valgrind --tool=memcheck --leak-check=full ./glib_list_remove
==27844== Memcheck, a memory error detector
==27844== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==27844== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==27844== Command: ./glib_list_remove
==27844== 
data is: 1, user_data is: original list 
data is: 5, user_data is: original list 
data is: 3, user_data is: original list 
data is: 5, user_data is: original list 
data is: 5, user_data is: original list 
data is: 7, user_data is: original list 
data is: 1, user_data is: after remove 5 
data is: 3, user_data is: after remove 5 
data is: 5, user_data is: after remove 5 
data is: 5, user_data is: after remove 5 
data is: 7, user_data is: after remove 5 
data is: 1, user_data is: after remove_all 5 
data is: 3, user_data is: after remove_all 5 
data is: 7, user_data is: after remove_all 5 
data is: 3, user_data is: after remove first and last 
==27844== 
==27844== HEAP SUMMARY:
==27844==     in use at exit: 2,238 bytes in 10 blocks
==27844==   total heap usage: 107 allocs, 97 frees, 500,479 bytes allocated
==27844== 
==27844== 24 bytes in 1 blocks are definitely lost in loss record 6 of 10
==27844==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==27844==    by 0x4E856D0: g_malloc (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4002.0)
==27844==    by 0x4E9B2ED: g_slice_alloc (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4002.0)
==27844==    by 0x4E7C503: g_list_append (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4002.0)
==27844==    by 0x400B80: main (glib_list.c:18)
==27844== 
==27844== LEAK SUMMARY:
==27844==    definitely lost: 24 bytes in 1 blocks
==27844==    indirectly lost: 0 bytes in 0 blocks
==27844==      possibly lost: 0 bytes in 0 blocks
==27844==    still reachable: 2,214 bytes in 9 blocks
==27844==         suppressed: 0 bytes in 0 blocks
==27844== Reachable blocks (those to which a pointer was found) are not shown.
==27844== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==27844== 
==27844== For counts of detected and suppressed errors, rerun with: -v
==27844== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
链表拼接
GList * 	g_list_concat ()

拼接后,不可再释放第二个链表,举例:
源码见glib_examples\glib_list\glib_list_concat

#include 

void _list_interger_print_func(gpointer data, gpointer user_data)
{
    g_print("data is: %d, user_data is: %s \n", GPOINTER_TO_INT(data), (gchar *)user_data);
}

gint main(gint argc, gchar **argv)
{
    GList *list1 = NULL;
    GList *list2 = NULL;

    list1 = g_list_append(list1, GINT_TO_POINTER(1));
    list1 = g_list_append(list1, GINT_TO_POINTER(5));
    list2 = g_list_append(list2, GINT_TO_POINTER(3));
    list2 = g_list_append(list2, GINT_TO_POINTER(7));

    g_list_foreach(list1, _list_interger_print_func, (gchar *)"list1");
    g_list_foreach(list2, _list_interger_print_func, (gchar *)"list2");

    list1 = g_list_concat(list1, list2);
    g_list_foreach(list1, _list_interger_print_func, (gchar *)"list1-concat");
    g_list_foreach(list2, _list_interger_print_func, (gchar *)"list2-concat");

    g_list_free(list1);
    // g_list_free(list2);  //should not free list2

    return 0;
}

使用g_list_concat移动链表中的元素。
源码见glib_examples\glib_list\glib_list_move

#include 

void _list_interger_print_func(gpointer data, gpointer user_data)
{
    g_print("data is: %d, user_data is: %s \n", GPOINTER_TO_INT(data), (gchar *)user_data);
}

gint main(gint argc, gchar **argv)
{
    GList *list1 = NULL;
    GList *list2 = NULL;
    GList *l = NULL;

    list1 = g_list_append(list1, GINT_TO_POINTER(1));
    list1 = g_list_append(list1, GINT_TO_POINTER(5));
    list2 = g_list_append(list2, GINT_TO_POINTER(3));
    list2 = g_list_append(list2, GINT_TO_POINTER(7));


    g_list_foreach(list1, _list_interger_print_func, (gchar *)"list1");
    g_list_foreach(list2, _list_interger_print_func, (gchar *)"list2");

    g_print("===test concat=== \n");

    list1 = g_list_concat(list1, list2);
    g_list_foreach(list1, _list_interger_print_func, (gchar *)"list1-concat");
    g_list_foreach(list2, _list_interger_print_func, (gchar *)"list2-concat");

    g_print("===test move last to front=== \n");
    l = g_list_last(list1);
    list1 = g_list_remove_link(list1, l);
    list1 = g_list_concat(l, list1);

    g_list_foreach(list1, _list_interger_print_func, (gchar *)"list1-move-to-front");
    g_list_foreach(list2, _list_interger_print_func, (gchar *)"list2-move-to-front");

    g_list_free(list1);

    return 0;
}

运行结果:

data is: 1, user_data is: list1 
data is: 5, user_data is: list1 
data is: 3, user_data is: list2 
data is: 7, user_data is: list2 
===test concat=== 
data is: 1, user_data is: list1-concat 
data is: 5, user_data is: list1-concat 
data is: 3, user_data is: list1-concat 
data is: 7, user_data is: list1-concat 
data is: 3, user_data is: list2-concat 
data is: 7, user_data is: list2-concat 
===test move last to front=== 
data is: 7, user_data is: list1-move-to-front 
data is: 1, user_data is: list1-move-to-front 
data is: 5, user_data is: list1-move-to-front 
data is: 3, user_data is: list1-move-to-front 
data is: 3, user_data is: list2-move-to-front
链表拷贝
GList * 	g_list_copy ()
GList * 	g_list_copy_deep ()

举例
源码见glib_examples\glib_list\glib_list_deep_copy_err

#include 

void _list_str_print_func(gpointer data, gpointer user_data)
{
    g_print("user_data is: %s, data is: %s \n", (gchar *)user_data, (gchar*)data);
}

static gpointer _list_str_copy_deep (gconstpointer value, gpointer data)
{
  return (gpointer)g_strdup((const gchar *)value);
}

gint main(gint argc, gchar **argv)
{
    GList *list = NULL;
    GList *list2 = NULL;
    GList *list3 = NULL;

    gchar str1[32] = {0};
    gchar str2[32] = {0};

    g_strlcpy(str1, "hello", sizeof("hello"));
    g_strlcpy(str2, "world", sizeof("world"));
    
    list = g_list_append(list, (gpointer)str1);
    list = g_list_append(list, (gpointer)str2);

    g_print("===test copy===\n");

    list2 = g_list_copy(list);

    g_list_foreach(list, _list_str_print_func, (gchar *)"list");
    g_list_foreach(list2, _list_str_print_func, (gchar *)"list2");

    g_strlcpy(str2, "glib", sizeof("glib"));
    
    g_list_foreach(list, _list_str_print_func, (gchar *)"list-change");
    g_list_foreach(list2, _list_str_print_func, (gchar *)"list2-change");

    g_print("===test deep copy===\n");

    list3 = g_list_copy_deep(list, _list_str_copy_deep, NULL);

    g_list_foreach(list, _list_str_print_func, (gchar *)"list");
    g_list_foreach(list2, _list_str_print_func, (gchar *)"list2");
    g_list_foreach(list3, _list_str_print_func, (gchar *)"list3");

    g_strlcpy(str1, "welcome", sizeof("welcome"));

    g_list_foreach(list, _list_str_print_func, (gchar *)"list-deep-copy");
    g_list_foreach(list2, _list_str_print_func, (gchar *)"list2-deep-copy");
    g_list_foreach(list3, _list_str_print_func, (gchar *)"list3-deep-copy");

    g_list_free(list);
    g_list_free(list2);
    g_list_free(list3);

    return 0;
}

运行结果:

===test copy===
user_data is: list, data is: hello 
user_data is: list, data is: world 
user_data is: list2, data is: hello 
user_data is: list2, data is: world 
user_data is: list-change, data is: hello 
user_data is: list-change, data is: glib 
user_data is: list2-change, data is: hello 
user_data is: list2-change, data is: glib 
===test deep copy===
user_data is: list, data is: hello 
user_data is: list, data is: glib 
user_data is: list2, data is: hello 
user_data is: list2, data is: glib 
user_data is: list3, data is: hello 
user_data is: list3, data is: glib 
user_data is: list-deep-copy, data is: welcome 
user_data is: list-deep-copy, data is: glib 
user_data is: list2-deep-copy, data is: welcome 
user_data is: list2-deep-copy, data is: glib 
user_data is: list3-deep-copy, data is: hello 
user_data is: list3-deep-copy, data is: glib

运行结果表明,g_list_copy类似于linux文件系统的符号链接,当链表内容有变化时,符号链接所指向的值也会变化。而g_list_copy_deep则不会受原链表内容变化的影响,因为g_list_copy_deep是对原链表的数值内存进行了拷贝。
上述代码有一处问题。
使用g_list_copy_deep得到了新链表,但未对新链表中使用g_strdup拷贝的节点主动释放,造成了内存泄露。使用valgrind进行检测,可以直观地看到内存泄露的地方。

[root@centos7_6 build]# valgrind --tool=memcheck --leak-check=full ./glib_list_deep_copy_err
==28540== Memcheck, a memory error detector
==28540== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==28540== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==28540== Command: ./glib_list_deep_copy_err
==28540== 
===test copy===
user_data is: list, data is: hello 
user_data is: list, data is: world 
user_data is: list2, data is: hello 
user_data is: list2, data is: world 
user_data is: list-change, data is: hello 
user_data is: list-change, data is: glib 
user_data is: list2-change, data is: hello 
user_data is: list2-change, data is: glib 
===test deep copy===
user_data is: list, data is: hello 
user_data is: list, data is: glib 
user_data is: list2, data is: hello 
user_data is: list2, data is: glib 
user_data is: list3, data is: hello 
user_data is: list3, data is: glib 
user_data is: list-deep-copy, data is: welcome 
user_data is: list-deep-copy, data is: glib 
user_data is: list2-deep-copy, data is: welcome 
user_data is: list2-deep-copy, data is: glib 
user_data is: list3-deep-copy, data is: hello 
user_data is: list3-deep-copy, data is: glib 
==28540== 
==28540== HEAP SUMMARY:
==28540==     in use at exit: 2,225 bytes in 11 blocks
==28540==   total heap usage: 151 allocs, 140 frees, 732,465 bytes allocated
==28540== 
==28540== 5 bytes in 1 blocks are definitely lost in loss record 3 of 11
==28540==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==28540==    by 0x4E856D0: g_malloc (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4002.0)
==28540==    by 0x4E9CBCE: g_strdup (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4002.0)
==28540==    by 0x400AB4: _list_str_copy_deep (glib_list_deep_copy_err.c:10)
==28540==    by 0x4E7C259: g_list_copy_deep (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4002.0)
==28540==    by 0x400C38: main (glib_list_deep_copy_err.c:42)
==28540== 
==28540== 6 bytes in 1 blocks are definitely lost in loss record 4 of 11
==28540==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==28540==    by 0x4E856D0: g_malloc (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4002.0)
==28540==    by 0x4E9CBCE: g_strdup (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4002.0)
==28540==    by 0x400AB4: _list_str_copy_deep (glib_list_deep_copy_err.c:10)
==28540==    by 0x4E7C22E: g_list_copy_deep (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4002.0)
==28540==    by 0x400C38: main (glib_list_deep_copy_err.c:42)
==28540== 
==28540== LEAK SUMMARY:
==28540==    definitely lost: 11 bytes in 2 blocks
==28540==    indirectly lost: 0 bytes in 0 blocks
==28540==      possibly lost: 0 bytes in 0 blocks
==28540==    still reachable: 2,214 bytes in 9 blocks
==28540==         suppressed: 0 bytes in 0 blocks
==28540== Reachable blocks (those to which a pointer was found) are not shown.
==28540== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==28540== 
==28540== For counts of detected and suppressed errors, rerun with: -v
==28540== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

需要对在深拷贝时申请的内存进行释放。此处用到了链表释放函数g_list_free_full,本函数原型如下:

void
g_list_free_full (GList *list,
                  GDestroyNotify free_func);

本函数需要传入一个回调函数,提供每一个节点的释放方法。本函数对链表进行遍历,并根据提供的节点自定义释放方法对每一个节点进行释放。
源码见glib_examples\glib_list\glib_list_deep_copy

#include 

static void _list_str_print_func(gpointer data, gpointer user_data)
{
    g_print("user_data is: %s, data is: %s \n", (gchar *)user_data, (gchar*)data);
}

static gpointer _list_str_copy_deep (gconstpointer value, gpointer data)
{
  return (gpointer)g_strdup((const gchar *)value);
}

static void _list_str_copy_deep_free_func(gpointer data)
{
    g_free(data);
}

gint main(gint argc, gchar **argv)
{
    GList *list = NULL;
    GList *list2 = NULL;
    GList *list3 = NULL;

    gchar str1[32] = {0};
    gchar str2[32] = {0};

    g_strlcpy(str1, "hello", sizeof("hello"));
    g_strlcpy(str2, "world", sizeof("world"));
    
    list = g_list_append(list, (gpointer)str1);
    list = g_list_append(list, (gpointer)str2);

    g_print("===test copy===\n");

    list2 = g_list_copy(list);

    g_list_foreach(list, _list_str_print_func, (gchar *)"list");
    g_list_foreach(list2, _list_str_print_func, (gchar *)"list2");

    g_strlcpy(str2, "glib", sizeof("glib"));
    
    g_list_foreach(list, _list_str_print_func, (gchar *)"list-change");
    g_list_foreach(list2, _list_str_print_func, (gchar *)"list2-change");

    g_print("===test deep copy===\n");

    list3 = g_list_copy_deep(list, _list_str_copy_deep, NULL);

    g_list_foreach(list, _list_str_print_func, (gchar *)"list");
    g_list_foreach(list2, _list_str_print_func, (gchar *)"list2");
    g_list_foreach(list3, _list_str_print_func, (gchar *)"list3");

    g_strlcpy(str1, "welcome", sizeof("welcome"));

    g_list_foreach(list, _list_str_print_func, (gchar *)"list-deep-copy");
    g_list_foreach(list2, _list_str_print_func, (gchar *)"list2-deep-copy");
    g_list_foreach(list3, _list_str_print_func, (gchar *)"list3-deep-copy");

    g_list_free(list);
    g_list_free(list2);
    g_list_free_full(list3, _list_str_copy_deep_free_func);

    return 0;
}

再次使用valgrind检测,无内存泄露。

通过上面这个例子,可以得出结论:
GList只会开辟GList结构体的内存,即两个指针和一个指向实际数据的指针,不会为实际数据开辟内存,因此,如果想长期保存实际数据,则不可使用栈内存。如本例所示,若链表为全局变量,而str1,str2为局部变量,则函数运行结束时,str1和str2因为是局部变量而被释放,全局链表的值也变得未知。

链表浅拷贝要注意内存重复释放问题,见下面的例子:
源码见glib_examples\glib_list\glib_list_copy_err

#include 

static void _list_str_print_func(gpointer data, gpointer user_data)
{
    g_print("user_data is: %s, data is: %s \n", (gchar *)user_data, (gchar*)data);
}

static gpointer _list_str_copy_deep (gconstpointer value, gpointer data)
{
  return (gpointer)g_strdup((const gchar *)value);
}

static void _list_str_free_func(gpointer data)
{
    g_free((gchar *)data);
}

gint main(gint argc, gchar **argv)
{
    GList *list = NULL;
    GList *list2 = NULL;
    GList *list3 = NULL;

    gchar *str1 = NULL;
    gchar *str2 = NULL;

    str1 = g_strdup("hello");
    str2 = g_strdup("world");
    
    list = g_list_append(list, (gpointer)str1);
    list = g_list_append(list, (gpointer)str2);

    g_print("===test copy===\n");

    list2 = g_list_copy(list);

    g_list_foreach(list, _list_str_print_func, (gchar *)"list");
    g_list_foreach(list2, _list_str_print_func, (gchar *)"list2");

    g_print("===test deep copy===\n");

    list3 = g_list_copy_deep(list, _list_str_copy_deep, NULL);

    g_list_foreach(list, _list_str_print_func, (gchar *)"list");
    g_list_foreach(list2, _list_str_print_func, (gchar *)"list2");
    g_list_foreach(list3, _list_str_print_func, (gchar *)"list3");

    g_list_free_full(list, _list_str_free_func);
    g_list_free(list2);  // #ERROR!!!:  g_list_free_full(list2, _list_str_free_func);
    g_list_free_full(list3, _list_str_free_func);

    return 0;
}

运行结果

===test copy===
user_data is: list, data is: hello 
user_data is: list, data is: world 
user_data is: list2, data is: hello 
user_data is: list2, data is: world 
===test deep copy===
user_data is: list, data is: hello 
user_data is: list, data is: world 
user_data is: list2, data is: hello 
user_data is: list2, data is: world 
user_data is: list3, data is: hello 
user_data is: list3, data is: world

上述例子看似正常,但使用valgrind检查,会发现内存问题,Invalid free()。

[root@centos7_6 build]# valgrind --tool=memcheck --leak-check=full ./glib_list_copy_err
==5414== Memcheck, a memory error detector
==5414== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==5414== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==5414== Command: ./glib_list_copy_err
==5414== 
===test copy===
user_data is: list, data is: hello 
user_data is: list, data is: world 
user_data is: list2, data is: hello 
user_data is: list2, data is: world 
===test deep copy===
user_data is: list, data is: hello 
user_data is: list, data is: world 
user_data is: list2, data is: hello 
user_data is: list2, data is: world 
user_data is: list3, data is: hello 
user_data is: list3, data is: world 
==5414== Invalid free() / delete / delete[] / realloc()
==5414==    at 0x4C2BDEC: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5414==    by 0x400A2E: _list_str_free_func (glib_list_copy_err.c:15)
==5414==    by 0x4E7C657: g_list_foreach (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4002.0)
==5414==    by 0x4E7C67A: g_list_free_full (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4002.0)
==5414==    by 0x400B89: main (glib_list_copy_err.c:49)
==5414==  Address 0x5961040 is 0 bytes inside a block of size 6 free'd
==5414==    at 0x4C2BDEC: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5414==    by 0x400A2E: _list_str_free_func (glib_list_copy_err.c:15)
==5414==    by 0x4E7C657: g_list_foreach (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4002.0)
==5414==    by 0x4E7C67A: g_list_free_full (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4002.0)
==5414==    by 0x400B78: main (glib_list_copy_err.c:48)
==5414== 
==5414== 
==5414== HEAP SUMMARY:
==5414==     in use at exit: 2,214 bytes in 9 blocks
==5414==   total heap usage: 93 allocs, 86 frees, 400,768 bytes allocated
==5414== 
==5414== LEAK SUMMARY:
==5414==    definitely lost: 0 bytes in 0 blocks
==5414==    indirectly lost: 0 bytes in 0 blocks
==5414==      possibly lost: 0 bytes in 0 blocks
==5414==    still reachable: 2,214 bytes in 9 blocks
==5414==         suppressed: 0 bytes in 0 blocks
==5414== Reachable blocks (those to which a pointer was found) are not shown.
==5414== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==5414== 
==5414== For counts of detected and suppressed errors, rerun with: -v
==5414== ERROR SUMMARY: 2 errors from 1 contexts (suppressed: 0 from 0)

原因是list2是浅拷贝过来的,只需要释放链表自身,不需要释放其中的每个节点。如果查看g_list_copy的源码,可以发现list2的每一个节点都是动态申请的新内存,但其中的三个指针(prev,next,data)均指向list节点,并未开辟新的内存。因此,在函数结束时,只需要释放list2的每一个节点,也就是GList结构体的三个指针自身所占的内存,释放list2并不影响list,list的值仍存在。
g_list_free_full(list2, _list_str_free_func);改为g_list_free(list2);即可消除内存错误。

用户自定义数据

如果是用户自定义的数据,又该如何使用上面的函数呢?
首先定义一个结构体,里面存的是公司员工工号和姓名。

typedef struct employee_info_tag {
    gint id;
    gchar *name;
}employee_info_t;

员工工号int型就够了,由于员工姓名有长有短,所以这里使用指针来存储不定长度的员工姓名。下面演示插入四个员工信息到链表。
源码见glib_examples\glib_list\glib_list_custom_data_insert

#include 

typedef struct employee_info_tag {
    gint id;
    gchar *name;
}employee_info_t;

static void _list_struct_print_func(gpointer data, gpointer user_data)
{
    employee_info_t *einfo = NULL;

    einfo = (employee_info_t *)data;
    if(NULL == einfo) {
        g_print("input param error! \n");
        return;
    }

    g_print("user_data:%s, einfo->id:%d, einfo->name:%s \n", (gchar *)user_data, einfo->id, einfo->name);
}

static void _list_struct_free_func(gpointer data)
{
    employee_info_t *einfo = NULL;

    einfo = (employee_info_t *)data;

    if( (NULL != einfo) && (NULL != einfo->name) ) {
        g_free(einfo->name);
    }

    if(NULL != einfo) {
        g_free(einfo);
    }
}

gint main(gint argc, gchar **argv)
{
    GList *list = NULL;
    GList *l = NULL;

    employee_info_t *einfo[4] = {NULL, NULL, NULL, NULL};

    einfo[0] = g_new0(employee_info_t, 1);
    einfo[1] = g_new0(employee_info_t, 1);
    einfo[2] = g_new0(employee_info_t, 1);
    einfo[3] = g_new0(employee_info_t, 1);

    einfo[0]->id = 1000;
    einfo[0]->name = g_strdup("Jack");
    einfo[1]->id = 1001;
    einfo[1]->name = g_strdup("Pony");
    einfo[2]->id = 1002;
    einfo[2]->name = g_strdup("Robin");
    einfo[3]->id = 1003;
    einfo[3]->name = g_strdup("NoName");

    list = g_list_append(list, (gpointer)einfo[0]); //Jack
    list = g_list_prepend(list, (gpointer)einfo[1]); //Pony->Jack
    list = g_list_insert(list, (gpointer)einfo[2], 1); //Pony->Robin->Jack

    l = g_list_nth(list, 0);
    list = g_list_insert_before(list, l, (gpointer)einfo[3]); //NoName->Pony->Robin->Jack

    g_list_foreach(list, _list_struct_print_func, "GList");

    g_list_free_full(list, _list_struct_free_func);

    return 0;
}

运行结果:

user_data:GList, einfo->id:1003, einfo->name:NoName 
user_data:GList, einfo->id:1001, einfo->name:Pony 
user_data:GList, einfo->id:1002, einfo->name:Robin 
user_data:GList, einfo->id:1000, einfo->name:Jack

下面重点分析一下上面链表的内存创建和释放过程。
请将下面几行逐行复制到文本编辑器并调整字体为等宽字体如宋体、Courier New等再查看。

Node1*<---->Node2*<---->Node3*<---->Node4*
 ├prev       ├prev       ├prev       ├prev
 ├next       ├next       ├next       ├next
 └data*      └data*      └data*      └data*
   └einfo[0]   └einfo[1]   └einfo[2]   └einfo[3]
     ├id(1003)   ├id(1001)   ├id(1002)   ├id(1000)
     └name*      └name*      └name*      └name*

上述链表的每个节点都由三部分内存构成,以Node1为例。

  • 第一部分是Node1节点的内存,包括prev,next和data三个指针,这些内存在调用append/prepend/insert时创建,在调用g_list_free时被释放;
  • 第二部分是data所指向的数据,即einfo[0],这部分内存在g_new0创建,在g_list_free_full的自定义释放函数中释放,即g_free(einfo)语句;
  • 第三部分是name所指向的数据,这部分存的是如Pony、Robin、Jack之类的员工姓名,通过g_strdup()创建,在g_list_free_full的自定义释放函数中释放,g_free(einfo->name);

上述三部分内存的释放顺序为,先释放name,再释放einfo[0],最后释放Node1,其余节点也按照这个顺序释放,直至整个链表销毁。

由于使用了自定义数据,在操作链表(排序、查找、深拷贝)时,就会用到很多自定义函数,如下所示。

g_list_sort
g_list_find_custom
g_list_copy_deep

下面举例演示。
源码见glib_examples\glib_list\glib_list_custom_data_ops

#include 

typedef struct employee_info_tag {
    gint id;
    gchar *name;
}employee_info_t;

static void _list_struct_print_func(gpointer data, gpointer user_data)
{
    employee_info_t *einfo = NULL;

    einfo = (employee_info_t *)data;
    if(NULL == einfo) {
        g_print("input param error! \n");
        return;
    }

    g_print("user_data:%s, einfo->id:%d, einfo->name:%s \n", (gchar *)user_data, einfo->id, einfo->name);
}

static void _list_struct_free_func(gpointer data)
{
    employee_info_t *einfo = NULL;

    einfo = (employee_info_t *)data;

    if( (NULL != einfo) && (NULL != einfo->name) ) {
        g_free(einfo->name);
    }

    if(NULL != einfo) {
        g_free(einfo);
    }
}

static gint _list_struct_name_cmp_func(gconstpointer a, gconstpointer b)
{
    employee_info_t *einfo_a = NULL;
    employee_info_t *einfo_b = NULL;

    einfo_a = (employee_info_t *)a;
    einfo_b = (employee_info_t *)b;

    if( (NULL == einfo_a) || (NULL == einfo_b) ) {
        g_print("input param error! \n");
        return 0;
    }

    return g_strcmp0(einfo_a->name, einfo_b->name);
}

static gint _list_struct_id_cmp_func(gconstpointer a, gconstpointer b)
{
    employee_info_t *einfo_a = NULL;
    employee_info_t *einfo_b = NULL;

    einfo_a = (employee_info_t *)a;
    einfo_b = (employee_info_t *)b;

    if( (NULL == einfo_a) || (NULL == einfo_b) ) {
        g_print("input param error! \n");
        return 0;
    }

    return (einfo_a->id - einfo_b->id);
}

static gint _list_struct_id_find_func(gconstpointer a, gconstpointer b)
{
    employee_info_t *einfo_a = NULL;
    gint id;

    einfo_a = (employee_info_t *)a;
    id = GPOINTER_TO_INT(b);

    if (NULL == einfo_a) {
        g_print("input param error! \n");
        return 0;
    }

    return (einfo_a->id - id);
}

static gint _list_struct_name_find_func(gconstpointer a, gconstpointer b)
{
    employee_info_t *einfo_a = NULL;
    gchar *name = NULL;

    einfo_a = (employee_info_t *)a;
    name = (gchar *)b;

    if (NULL == einfo_a) {
        g_print("input param error! \n");
        return 0;
    }

    return g_strcmp0(einfo_a->name, name);
}

static gpointer _list_struct_copy_func(gconstpointer src, gpointer data)
{
    employee_info_t *einfo = NULL;

    einfo = g_new0(employee_info_t, 1);

    einfo->id = ((employee_info_t *)src)->id + GPOINTER_TO_INT(data);
    einfo->name = g_strdup(((employee_info_t *)src)->name);

    return (gpointer)einfo;
}

gint main(gint argc, gchar **argv)
{
    GList *list = NULL;
    GList *l = NULL;
    GList *list2 = NULL;

    employee_info_t *einfo[4] = {NULL, NULL, NULL, NULL};

    einfo[0] = g_new0(employee_info_t, 1);
    einfo[1] = g_new0(employee_info_t, 1);
    einfo[2] = g_new0(employee_info_t, 1);
    einfo[3] = g_new0(employee_info_t, 1);

    einfo[0]->id = 1000;
    einfo[0]->name = g_strdup("Jack");
    einfo[1]->id = 1001;
    einfo[1]->name = g_strdup("Pony");
    einfo[2]->id = 1002;
    einfo[2]->name = g_strdup("Robin");
    einfo[3]->id = 1003;
    einfo[3]->name = g_strdup("NoName");

    list = g_list_append(list, (gpointer)einfo[0]); //Jack
    list = g_list_prepend(list, (gpointer)einfo[1]); //Pony->Jack
    list = g_list_insert(list, (gpointer)einfo[2], 1); //Pony->Robin->Jack

    l = g_list_nth(list, 0);
    list = g_list_insert_before(list, l, (gpointer)einfo[3]); //NoName->Pony->Robin->Jack

    g_list_foreach(list, _list_struct_print_func, "ori");

    // #sort
    list = g_list_sort(list, _list_struct_id_cmp_func);

    g_list_foreach(list, _list_struct_print_func, "id sort");

    list = g_list_sort(list, _list_struct_name_cmp_func);

    g_list_foreach(list, _list_struct_print_func, "name sort");

    // #find
    l = g_list_find_custom(list, GINT_TO_POINTER(1002), _list_struct_id_find_func);
    if( NULL != l) {
        g_print("found by id! id=%d, name=%s \n", ((employee_info_t *)l->data)->id, ((employee_info_t *)l->data)->name);
    } else {
        g_print("can not found by id! \n");
    }

    l = g_list_find_custom(list, (gpointer)"Jack", _list_struct_name_find_func);
    if( NULL != l) {
        g_print("found by name! id=%d, name=%s \n", ((employee_info_t *)l->data)->id, ((employee_info_t *)l->data)->name);
    } else {
        g_print("cat not found by name! \n");
    }

    // #copy
    list2 = g_list_copy_deep(list, _list_struct_copy_func, GINT_TO_POINTER(5000));

    g_list_foreach(list2, _list_struct_print_func, "list2");
    g_list_free_full(list2, _list_struct_free_func);

    g_list_free_full(list, _list_struct_free_func);

    return 0;
}

运行结果:

user_data:ori, einfo->id:1003, einfo->name:NoName 
user_data:ori, einfo->id:1001, einfo->name:Pony 
user_data:ori, einfo->id:1002, einfo->name:Robin 
user_data:ori, einfo->id:1000, einfo->name:Jack 
user_data:id sort, einfo->id:1000, einfo->name:Jack 
user_data:id sort, einfo->id:1001, einfo->name:Pony 
user_data:id sort, einfo->id:1002, einfo->name:Robin 
user_data:id sort, einfo->id:1003, einfo->name:NoName 
user_data:name sort, einfo->id:1000, einfo->name:Jack 
user_data:name sort, einfo->id:1003, einfo->name:NoName 
user_data:name sort, einfo->id:1001, einfo->name:Pony 
user_data:name sort, einfo->id:1002, einfo->name:Robin 
found by id! id=1002, name=Robin 
found by name! id=1000, name=Jack 
user_data:list2, einfo->id:6000, einfo->name:Jack 
user_data:list2, einfo->id:6003, einfo->name:NoName 
user_data:list2, einfo->id:6001, einfo->name:Pony 
user_data:list2, einfo->id:6002, einfo->name:Robin

分析:
创建一个带有自定义节点的链表,先根据员工工号进行排序,再根据员工姓名进行排序,再通过员工工号进行查找,再通过员工姓名进行查找,最后深拷贝一个链表,同时把链表中的所有员工工号增加5000。

思考:
如果上述数据结构,name不是一个字符串指针,而是一个固定长度的数组呢?比name是指针处理起来复杂还是简单,如何实现?

typedef struct employee_info_tag {
    gint id;
    gchar name[32];
}employee_info_t;

专题

浅谈链表的内存空间

前面在讲链表拷贝时提到链表中的节点不能是局部变量,那么如果是局部变量,会导致什么后果呢,下面通过例子演示一下效果。
源码见glib_examples\glib_list\glib_list_mem

#include 

GList *global_list = NULL;

static void _list_str_print_func(gpointer data, gpointer user_data)
{
    g_print("user_data is: %s, data is: %s \n", (gchar *)user_data, (gchar*)data);
}

static void _list_local_var_str_append(void)
{
    gchar str1[32] = {0};
    gchar str2[32] = {0};

    g_strlcpy(str1, "hello", sizeof("hello"));
    g_strlcpy(str2, "world", sizeof("world"));

    global_list = g_list_append(global_list, (gpointer)str1);
    global_list = g_list_append(global_list, (gpointer)str2);
}

static void _list_const_var_str_append(void)
{
    gchar *str1 = "hello";
    gchar *str2 = "world";

    global_list = g_list_append(global_list, (gpointer)str1);
    global_list = g_list_append(global_list, (gpointer)str2);
}

static void _list_heap_var_str_append(void)
{
    gchar *str1 = g_strdup("hello");
    gchar *str2 = g_strdup("world");

    global_list = g_list_append(global_list, (gpointer)str1);
    global_list = g_list_append(global_list, (gpointer)str2);
}

static void _list_heap_var_str_free_func(gpointer data)
{
    g_free((gchar *)data);
}

gint main(gint argc, gchar **argv)
{

    _list_local_var_str_append();
    g_list_foreach(global_list, _list_str_print_func, "local var");
    g_list_free(global_list);
    global_list = NULL;

    _list_const_var_str_append();
    g_list_foreach(global_list, _list_str_print_func, "const var");
    g_list_free(global_list);
    global_list = NULL;

    _list_heap_var_str_append();
    g_list_foreach(global_list, _list_str_print_func, "heap var");
    g_list_free_full(global_list, _list_heap_var_str_free_func);
    global_list = NULL;

    return 0;
}

运行结果:

[Invalid UTF-8] user_data is: local var, data is:  \xee\xf2 
[Invalid UTF-8] user_data is: local var, data is: \xa4\x0d@ 
user_data is: const var, data is: hello 
user_data is: const var, data is: world 
user_data is: heap var, data is: hello 
user_data is: heap var, data is: world

分析:在栈(局部变量)、静态存储区(字符串)和堆(malloc申请)上分别申请内存,并将这三种内存类型的数据加入链表。结合上面的运行结果,可以得出如下结论:

  • 在栈上分配的内存,加入链表后,无法打印出来,原因是_list_local_var_str_append函数调用结束,str1和str2这两个局部变量已经被释放掉了。
  • 在静态存储区的字符串,在函数调用结束时,不会被释放。
  • 存储在堆内存上的字符串,只能靠用户手动释放,因此需要调用g_list_free_full对节点的值进行释放,否则就会造成内存泄露。

你可能感兴趣的:(GLib库入门与实践,链表,list,数据结构,GLib,c语言)