Retrouvez cet article dans : Linux Magazine 86
Introduction
Lorsque vous souhaitez stocker une liste de données, vous disposez de plusieurs possibilités. La première que les apprentis programmeurs découvrent est le tableau statique :int data[10];Très vite, ils ressentent le besoin d’avoir une liste dynamique, que l’on peut étendre à volonté. Avec
int *data; data = malloc (n * sizeof (*data));Le problème de ce tableau est qu’une fois la taille déterminée et le tableau créé, il ne peut plus changer de taille (ce qui est d’ailleurs inexact avec un
glib
Dans les sources deUn peu de théorie
Les listes chaînées sont un concept un peu différent. Au lieu d’avoir un gros bloc de données, nous avons une cellule qui ne contient qu’une seule donnée. En multipliant les cellules, nous arrivons à stocker nos données. Et pour en obtenir une liste, nous établissons des liens entre les cellules, chacune d’elle pointant vers la suivante. Voici comment coder chaque cellule avectypedef struct _GSList GSList;
struct _GSList
{
gpointer data;
GSList *next;
};
Une cellule est donc composée de la donnée qu’elle va contenir, ou plutôt un pointeur vers cette donnée, et un pointeur vers la cellule suivante 01 typedef struct _GList GList;
02
03 struct _GList
04 {
05 gpointer data;
06 GList *next;
07 GList *prev;
08 };

Fig. 1 : Cellule de liste chaînée
Dans la suite de l’article, nous allons nous limiter aux listes doublement chaînées à peine différentes de celles simplement chaînées. Vous vous référerez donc seulement aux fichiers glist.c et glist.h. Une opération sur une cellule (ajout, suppression, déplacement) consiste donc à simplement changer les liens entre chaque cellule (Fig. 2).
Créer une liste
D’après la documentation de glib, une liste chaînée vide est un pointeur vers NULL. Voici donc comment créer une liste, initialement vide :
Fig. 2 : Liste chaînée
GList *liste; liste = NULL;
Ajouter des données
Avant d’ajouter des données, distinguons deux cas. Allez-vous les ajouter au début ou à la fin de la liste ? Mais quel que soit le cas, nous créons une nouvelle cellule, ce que faitAjout à la fin
01 GList*
02 g_list_append (GList *list,
03 gpointer data)
04 {
05 GList *new_list;
06 GList *last;
07
08 new_list = _g_list_alloc ();
09 new_list->data = data;
10 new_list->next = NULL;
11
12 if (list)
13 {
14 last = g_list_last (list);
15 /* g_assert (last != NULL); */
16 last->next = new_list;
17 new_list->prev = last;
18
19 return list;
20 }
21 else
22 {
23 new_list->prev = NULL;
24 return new_list;
25 }
26 }
Après la création de la cellule et son remplissage (champ 01 GList*
02 g_list_last (GList *list)
03 {
04 if (list)
05 {
06 while (list->next)
07 list = list->next;
08 }
09
10 return list;
11 }
Il consiste simplement pour une liste donnée (si elle existe – ligne 4) à la parcourir jusqu’à son dernier élément, donc à itérer sur son champ Ajout au début
01 GList*
02 g_list_prepend (GList *list,
03 gpointer data)
04 {
05 GList *new_list;
06
07 new_list = _g_list_alloc ();
08 new_list->data = data;
09 new_list->next = list;
10
11 if (list)
12 {
13 new_list->prev = list->prev;
14 if (list->prev)
15 list->prev->next = new_list;
16 list->prev = new_list;
17 }
18 else
19 new_list->prev = NULL;
20
21 return new_list;
22 }
Comme précédemment, après la création de la cellule et son remplissage (champ 11 if (list) 12 list->prev = new_list; 13 14 new_list->prev = NULL; 15 16 return new_list; 17 }En début de liste, le champ
GList *liste;
gchar *chaine;
/* Initialisation de la liste */
liste = NULL;
/* Insertion d’une chaîne de caractères */
chaine = g_strdup("bonjour");
liste = g_list_prepend(liste, chaine);
/* Insertion d’une autre chaîne de caractères */
chaine = g_strdup("salut");
liste = g_list_prepend(liste, chaine);
Ajout au milieu
Il existe également deux autres fonctions d’ajout,Supprimer des données
Supprimer une cellule
Supprimer une cellule s’effectue en deux étapes. Vous devez délier la cellule de la liste chaînée, puis la détruire en libérant l’espace qui lui était alloué. Voici le code de01 GList*
02 g_list_remove (GList *list,
03 gconstpointer data)
04 {
05 GList *tmp;
06
07 tmp = list;
08 while (tmp)
09 {
10 if (tmp->data != data)
11 tmp = tmp->next;
12 else
13 {
14 if (tmp->prev)
15 tmp->prev->next = tmp->next;
16 if (tmp->next)
17 tmp->next->prev = tmp->prev;
18
19 if (list == tmp)
20 list = list->next;
21
22 _g_list_free1 (tmp);
23
24 break;
25 }
26 }
27 return list;
28 }
D’abord, nous commençons par chercher la cellule à supprimer en fonction de la donnée qu’elle contient. C’est l’intérêt de la boucle ligne 8, avec l’itération sur #define _g_list_free1(list) g_slice_free (GList, list)
Digression sur l’ancienne gestion de la mémoire
Tiens, mais pourquoi avoir une telle macro et non un appel direct Ã01 static inline void
02 _g_list_free_1 (GList *list)
03 {
04 if (list)
05 {
06 list->data = NULL;
07
08 #ifdef ENABLE_GC_FRIENDLY
09 list->prev = NULL;
10 #endif /* ENABLE_GC_FRIENDLY */
11
12 G_LOCK (current_allocator);
13 list->next = current_allocator->free_lists;
14 current_allocator->free_lists = list;
15 G_UNLOCK (current_allocator);
16 }
17 }
En plus d’être joyeusement incompréhensible au premier abord, ce code a la mauvaise idée de présenter une gestion de la mémoire propre aux listes. En effet, lignes 13 et 14, vous remarquez que nous travaillons sur 01 static inline GList*
02 _g_list_alloc (void)
03 {
04 GList *list;
05
06 G_LOCK (current_allocator);
07 if (!current_allocator)
08 {
09 GAllocator *allocator = g_allocator_new ("GLib default
10 GList allocator", 128);
11 g_list_validate_allocator (allocator);
12 allocator->last = NULL;
13 current_allocator = allocator;
14 }
15 if (!current_allocator->free_lists)
16 {
17 list = g_chunk_new (GList, current_allocator->mem_chunk);
18 list->data = NULL;
19 }
20 else
21 {
22 if (current_allocator->free_lists->data)
23 {
24 list = current_allocator->free_lists->data;
25 current_allocator->free_lists->data = list->next;
26 list->data = NULL;
27 }
28 else
29 {
30 list = current_allocator->free_lists;
31 current_allocator->free_lists = list->next;
32 }
33 }
34 G_UNLOCK (current_allocator);
35 list->next = NULL;
36 list->prev = NULL;
37
38 return list;
39 }
Ligne 7, nous vérifions que la liste de cellules vides existe. Sinon, nous la créons lignes 9 à 13. Ligne 15, elle existe, mais dispose-t-elle encore de cellules vides ? Sinon, il suffit d’en créer, et de l’initialiser tant qu’à faire (lignes 17 et 18). Si nous disposions de cellules vides, nous observons un code assez amusant : ces cellules peuvent être stockées soit dans Délier une cellule de la liste
Mais revenons à la suppression de cellules. Il existe une autre fonction,01 GList*
02 g_list_remove_link (GList *list,
03 GList *link)
04 {
05 return _g_list_remove_link (list, link);
06 }
Ooops, manque de chance, cette fonction fait appel à une fonction interne. Elle se trouve juste au-dessus dans le fichier 01 static inline GList*
02 _g_list_remove_link (GList *list,
03 GList *link)
04 {
05 if (link)
06 {
07 if (link->prev)
08 link->prev->next = link->next;
09 if (link->next)
10 link->next->prev = link->prev;
11
12 if (link == list)
13 list = list->next;
14
15 link->next = NULL;
16 link->prev = NULL;
17 }
18
19 return list;
20 }
Vous reconnaissez le code lignes 7 à 10 pour délier la cellule, ou plutôt pour raccorder les deux morceaux de listes chaînées une fois la nôtre enlevée. Vous remarquez ligne 12 un test pour s’assurer que la cellule à retirer n’est pas celle du début. Sinon, ligne 13, il faut modifier le pointeur vers le début de la liste, pointeur qui sera renvoyé ligne 19.
Comme notre cellule n’est pas supprimée de la mémoire, nous devons faire le nécessaire pour qu’elle ne pointe plus sur les autres cellules de la liste à laquelle elle était attachée. Pour cela, nous faisons pointer ses champs Les autres fonctions relatives à la suppression de cellules
Nous avons vu la macro01 void
02 g_list_free_1 (GList *list)
03 {
04 _g_list_free1 (list);
05 }
Il existe encore quelques fonctions qui peuvent vous intéresser pour supprimer des cellules d’une liste. La fonction Quelques utilitaires pour listes chaînées
Voici quelques utilitaires pour listes chaînées. Certains peuvent sembler inutiles comme la fonction pour obtenir le début d’une liste chaînée. Mais dans certains cas particuliers, ils ont leur utilité.g_list_first()
01 GList*
02 g_list_first (GList *list)
03 {
04 if (list)
05 {
06 while (list->prev)
07 list = list->prev;
08 }
09
10 return list;
11 }
Cette fonction itère sur une liste jusqu’à trouver une cellule dont le champ g_list_last()
01 GList*
02 g_list_last (GList *list)
03 {
04 if (list)
05 {
06 while (list->next)
07 list = list->next;
08 }
09
10 return list;
11 }
Cette fonction fait le même travail que la précédente, mais sur le champ g_list_length()
En y réfléchissant bien, si vous reprenez la fonction précédente en lui ajoutant un compteur, vous pouvez obtenir le nombre de cellules dans la liste. Voici le code :01 guint
02 g_list_length (GList *list)
03 {
04 guint length;
05
06 length = 0;
07 while (list)
08 {
09 length++;
10 list = list->next;
11 }
12
13 return length;
14 }
Il existe une autre façon, amusante parce qu’inhabituelle, de coder cela :
06 for(length = 0; list; list = list->next) 07 length++; 08 09 return length; 10 }
g_list_find()
01 GList*
02 g_list_find (GList *list,
03 gconstpointer data)
04 {
05 while (list)
06 {
07 if (list->data == data)
08 break;
09 list = list->next;
10 }
11
12 return list;
13 }
Cette fonction recherche la première cellule dont le champ Les listes triées
Une liste triée n’est rien d’autre qu’une liste, mais elle se distingue par le fait qu’elle est triée en permanence. Pour y arriver, il existe deux moyens. Soit vous partez d’une liste vide et, à chaque ajout, vous utilisezInsertion d’une cellule dans une liste triée
01 GList*
02 g_list_insert_sorted (GList *list,
03 gpointer data,
04 GCompareFunc func)
05 {
06 return g_list_insert_sorted_real (list, data, (GFunc) func, NULL);
07 }
01 GList*
02 g_list_insert_sorted_with_data (GList *list,
03 gpointer data,
04 GCompareDataFunc func,
05 gpointer user_data)
06 {
07 return g_list_insert_sorted_real (list, data, (GFunc) func, user_data);
08 }
Ces deux fonctions font la même chose, et vous voyez que g_list_insert_sorted(liste, donnees, ftri) est équivalent à 01 static GList*
02 g_list_insert_sorted_real (GList *list,
03 gpointer data,
04 GFunc func,
05 gpointer user_data)
06 {
07 GList *tmp_list = list;
08 GList *new_list;
09 gint cmp;
10
11 g_return_val_if_fail (func != NULL, list);
Après quelques initialisations, nous devons tester si la fonction de tri existe. Sinon, nous ne pouvons rien faire ! Ci-dessous, lignes 13 à 18, nous traitons le cas où la liste est vide. Nous n’avons en effet qu’à créer une cellule et renvoyer un pointeur dessus.
12
13 if (!list)
14 {
15 new_list = _g_list_alloc0 ();
16 new_list->data = data;
17 return new_list;
18 }
19
Lignes 20 à 27, à l’aide de notre fonction de comparaison, nous itérons sur chaque élément de la liste, jusqu’à trouver un élément qui ne soit pas supérieur à notre donnée de référence 20 cmp = ((GCompareDataFunc) func) (data, tmp_list->data, user_data);
21
22 while ((tmp_list->next) && (cmp > 0))
23 {
24 tmp_list = tmp_list->next;
25
26 cmp = ((GCompareDataFunc) func) (data, tmp_list->data, user_data);
27 }
28
Une fois trouvé l’endroit où insérer notre nouvelle cellule, il ne reste plus qu’à faire ce que nous avons déjà vu dans la fonction 29 new_list = _g_list_alloc0 ();
30 new_list->data = data;
31
32 if ((!tmp_list->next) && (cmp > 0))
33 {
34 tmp_list->next = new_list;
35 new_list->prev = tmp_list;
36 return list;
37 }
Premier cas particulier : notre cellule est supérieure à toutes les autres (38
39 if (tmp_list->prev)
40 {
41 tmp_list->prev->next = new_list;
42 new_list->prev = tmp_list->prev;
43 }
44 new_list->next = tmp_list;
45 tmp_list->prev = new_list;
46
Dans l’hypothèse où nous aurions inséré notre cellule en début de liste, nous devons retourner un pointeur vers notre nouvelle cellule (ligne 48). Sinon, le début de liste est inchangé et nous le renvoyons tel quel (ligne 50).
47 if (tmp_list == list) 48 return new_list; 49 else 50 return list; 51 }
Tri d’une liste chaînée
Si vous partez d’une liste qui n’est pas triée, ces fonctions d’insertion n’ont pas l’effet escompté. Vous devez trier votre liste, grâce Ã01 GList *
02 g_list_sort (GList *list,
03 GCompareFunc compare_func)
04 {
05 return g_list_sort_real (list, (GFunc) compare_func, NULL);
06
07 }
01 GList *
02 g_list_sort_with_data (GList *list,
03 GCompareDataFunc compare_func,
04 gpointer user_data)
05 {
06 return g_list_sort_real (list, (GFunc) compare_func, user_data);
07 }
Ces deux fonctions font appel à 01 static GList*
02 g_list_sort_real (GList *list,
03 GFunc compare_func,
04 gpointer user_data)
05 {
06 GList *l1, *l2;
07
08 if (!list)
09 return NULL;
10 if (!list->next)
11 return list;
12
13 l1 = list;
14 l2 = list->next;
15
16 while ((l2 = l2->next) != NULL)
17 {
18 if ((l2 = l2->next) == NULL)
19 break;
20 l1 = l1->next;
21 }
22 l2 = l1->next;
23 l1->next = NULL;
24
25 return g_list_sort_merge (g_list_sort_real (list, compare_func, user_data),
26 g_list_sort_real (l2, compare_func, user_data),
27 compare_func,
28 user_data);
29 }
Avant d’expliquer cette fonction, veuillez décortiquer les lignes 25 à 29. Nous faisons appel à 01 static GList *
02 g_list_sort_merge (GList *l1,
03 GList *l2,
04 GFunc compare_func,
05 gpointer user_data)
06 {
07 GList list, *l, *lprev;
08 gint cmp;
09
10 l = &list;
11 lprev = NULL;
12
13 while (l1 && l2)
14 {
15 cmp = ((GCompareDataFunc) compare_func) (l1->data, l2->data, user_data);
16
17 if (cmp <= 0)
18 {
19 l->next = l1;
20 l1 = l1->next;
21 }
22 else
23 {
24 l->next = l2;
25 l2 = l2->next;
26 }
27 l = l->next;
28 l->prev = lprev;
29 lprev = l;
30 }
31 l->next = l1 ? l1 : l2;
32 l->next->prev = l;
33
34 return list.next;
35 }
Outre les deux listes à fusionner (Itérer sur une liste chaînée
Avant de nous quitter, voyons comment itérer sur une liste chaînée. Nous avons vu plus haut plusieurs façons de faire. En général, voici comment coder une boucle :01 GList *l;
02 for(l = list, l; l = l->next) {
03 /* votre code ici */
04 }
01 void
02 g_list_foreach (GList *list,
03 GFunc func,
04 gpointer user_data)
05 {
06 while (list)
07 {
08 GList *next = list->next;
09 (*func) (list->data, user_data);
10 list = next;
11 }
12 }
Cette fonction propose une boucle avec une itération directement sur Conclusion
Nous avons, grâce au code contenu dans la bibliothèque- Le site de GTK+ : http://www.gtk.org/
- C en action (O’Reilly) – chapitre 6
Retrouvez cet article dans : Linux Magazine 86





Donnez votre avis
Vous devez avoir ouvert une session pour écrire un commentaire.