« Structures de données en C/Les listes simples » : différence entre les versions
Contenu supprimé Contenu ajouté
Ortho |
balise source |
||
Ligne 8 :
[[Image:Structures_de_données_en_C-list.png|140 px|Représentation graphique du type <code>List</code>]]
Les déclarations nécessaires pour représenter le type des listes en langage C sont:
<source lang="c">
typedef struct s_List List;
struct s_List
{
void *data; /* pointeur sur une donnée générique */
};
</source>
=== Initialisation ===
L'utilisation primaire d'une liste consiste à la déclarer et à l'initialiser à la liste vide.
<source lang="c">
List *list = NULL;
</source>
=== Création d'une liste d'un élément ===
Ligne 25 ⟶ 29 :
La création d'une liste d'un élément demande d'allouer de la mémoire au gestionnaire de mémoire. Il convient de toujours tester le retour d'un <code>malloc</code> car la documentation dit que le retour peut être <code>NULL</code> si le système ne dispose plus de mémoire suffisante. Si l'allocation a réussi, il n'y a plus qu'à affecter les champs de la structure <code>List</code>
<source lang="c">
List *
list_create (void *data)
{
List *list = malloc(sizeof(*list)); /* allocation (en vert sur le diagramme) et affectation à la variable list (en bleu) */
if (list) /* si l'allocation a réussi */
{
list->
list->next = <b>NULL</b>; /* affectation du champ next à la liste vide */
}
return list; /* retour de la liste (correctement allouée et affectée ou NULL) */
}
</source>
[[Image:Structures_de_données_en_C-list_create.png|108 px|Création d'une liste d'un élément]]
Ligne 44 ⟶ 50 :
[[Image:Structures_de_données_en_C-list_prepend.png|334 px|Ajout d'un élément en tête de liste]]
<source lang="c">
List *
list_prepend(List *old, void *data)
{
List *list = list_create(data); /* création et affectation d'une liste d'un élément (en vert sur le diagramme) */
if (list
return list; /* retour de la nouvelle liste (ou NULL si l'allocation a échoué) */
}
</source>
==== Ajout d'un élément en fin de liste ====
L'algorithme est relativement simple. Il consiste à rechercher le pointeur <code>NULL</code> dans la liste marquant sa fin. Une fois trouvé, il est modifié en allouant une nouvelle liste de 1 élément contenant <code>data</code>. La difficulté principale est l'utilisation d'un pointeur de liste (un <code>List **</code>) permettant de traiter indifféremment le cas où la liste d'origine est vide et le cas où elle n'est pas vide.
<source lang="c">
List *
{
List **plist = &
while (*plist)
*plist = list_create(data);
if (*plist)
else
return NULL;
}
</source>
===== Cas où la liste d'origine n'est pas vide =====
<source lang="c">
...
List new_list=list_append(mylist,data);
...
</
* instruction <code>List **plist = &list;</code>
Ligne 86 ⟶ 98 :
===== Cas où la liste d'origine est vide =====
<source lang="c">
...
List *new_list=list_append(<b>NULL</b>,data);
...
</source>
* instruction <code>List **plist = &list;</code>
Ligne 101 ⟶ 115 :
Le retrait du premier élément consiste à libérer le pointeur utilisé pour le premier élément et à retourner le reste de la liste
<source lang="c">
list_remove_first(List *list) List *first = list;
list = list->next;
</source>
Lors d'un appel à list_remove_first la liste passée en argument n'est pas modifiée
Ligne 114 ⟶ 130 :
[[Image:Structures_de_données_en_C-list_remove_first.png|336 px|Résultat de la ligne <code>List *new_list = list_remove_first(mylist);</code>]]
<source lang="c">
...
List *new_list = list_remove_first(mylist);
...
</
==== Destruction de la liste entière ====
La destruction d'une liste concerne la libération de la mémoire préalablement réservée par le chainage de la liste. Je pense (et c'est discutable) que les bibliothèques ne doivent libérer que ce qu'elles ont alloué. Les données n'ont donc pas à être libérées. Elles peuvent être notamment des pointeurs vers des chaînes de caractères statiques, et je vous laisse imaginer les effets d'un <code>free</code> sur ce genre de pointeur.
<source lang="c">
void
list_destroy(List *list)
{
while
list = list_remove_first(list); /* retrait du premier élément de la liste */
}
</source>
Il est à noter que le pointeur original n'est pas modifié par la ligne <code>list = list->next</code>
Exemple, lors de la destruction de la liste <code>mylist</code> dans le code suivant, <code>mylist</code> n'est pas modifié.
<source lang="c">
...
list_destroy(mylist);
...
</
Lors de l'appel à <code>list_destroy</code>, l'argument <code>list</code> prend la valeur de <code>mylist</code>. Les deux pointeurs sont donc confondus
Ligne 148 ⟶ 168 :
=== Longueur d'une liste ===
Pour calculer la longueur d'une liste, il suffit de compter le nombre de pointeurs de type <code>List</code> différents du pointeur <code>NULL</code>
<source lang="c">
int
list_length(List *list)
{
int length = 0; /* initialisation de la longueur à 0 */
while (list) /* tant que la fin de la liste d'est pas atteinte */
{
length++;
list = list->next;
}
</source>
De même que pour la destruction, l'affectation de <code>list</code> ne modifie pas la valeur de la liste de la fonction appelante.
=== Itérer sur les listes ===
Il y a plusieurs moyens d'effectuer une itération sur les éléments d'une liste. Le plus simple étant présenté ici, mais j'enjoins les lecteurs à parcourir le chapitre consacré au concept d'[[Structures de données en C/Les itérateurs|itérateurs]].
<source lang="c">
...
List *tmp=list; /* opération en vert sur le diagramme */
while (tmp)
{
/* traitement de tmp->data */
tmp = tmp->next; /* opération en bleu sur le diagramme */
}
...
</source>
[[Image:Structures_de_données_en_C-list_iteration_1.png|300 px|Début de l'itération</code>]]
Ligne 181 ⟶ 207 :
La fonction de création consiste à allouer un tel pointeur et à l'initialiser à la liste
<source lang="c">
Iterator *list_iterator_create(void *list)
{
return iterator_create(list, NULL, list_iterator_has_next, list_iterator_next);
}
</
La fonction pour savoir si il existe un élément suivant teste simplement la valeur de <code>*iterator</code>
<source lang="c">
static int list_iterator_has_next(const void *list)
{
return (int)list;
}
</
La fonction pour obtenir le prochain élément d'un itérateur de liste retourne la donnée et modifie l'itérateur en le faisant pointer sur la prochaine donnée.
<source lang="c">
static void *list_iterator_next(void *plist)
{
void *data = (*(List**)plist)->data;
*(List**)plist = (*(List**)plist)->next; /* modification de l'itérateur */
return data;
}
</
Le code classique pour parcourir une liste devient
<source lang="c">
...
Iterator *iterator = list_iterator_create(list);
if (iterator)
while (iterator_has_next(iterator))
void *data = iterator_next(iterator);
/* traitement de data */
iterator_destroy(iterator);
else
/* traitement de l'erreur d'allocation */
...
</
== Exercices ==
|