dans un fichier .h nommé "liste chainee modulaire.h":
#ifndef LISTE_CHAINEE_MODULAIRE_H_INCLUDED
#define LISTE_CHAINEE_MODULAIRE_H_INCLUDED
typedef struct Element Element;
struct Element
{
int nombre;
Element *suivant;
};
typedef struct Liste Liste;
struct Liste
{
Element *premier;
};
Liste *initialisation();
void insertion(Liste *liste, int nvNombre);
void suppression(Liste *liste);
void afficherListe(Liste *liste);
#endif // LISTE_CHAINEE_MODULAIRE_H_INCLUDED
Et dans un fichier .c:
#include <stdio.h>
#include <stdlib.h>
#include "liste chainee modulaire.h"
Liste *initialisation()
{
Liste *liste = malloc(sizeof(*liste));
Element *element = malloc(sizeof(*element));
if (liste == NULL || element == NULL)
{
exit(EXIT_FAILURE);
}
element->nombre = 0;
element->suivant = NULL;
liste->premier = element;
return liste;
}
void insertion(Liste *liste, int nvNombre)
{
/* Création du nouvel élément */
Element *nouveau = malloc(sizeof(*nouveau));
if (liste == NULL || nouveau == NULL)
{
exit(EXIT_FAILURE);
}
nouveau->nombre = nvNombre;
/* Insertion de l'élément au début de la liste */
nouveau->suivant = liste->premier;
liste->premier = nouveau;
}
void suppression(Liste *liste)
{
if (liste == NULL)
{
exit(EXIT_FAILURE);
}
if (liste->premier != NULL)
{
Element *aSupprimer = liste->premier;
liste->premier = liste->premier->suivant;
free(aSupprimer);
}
}
void afficherListe(Liste *liste)
{
if (liste == NULL)
{
exit(EXIT_FAILURE);
}
Element *actuel = liste->premier;
while (actuel != NULL)
{
printf("%d -> ", actuel->nombre);
actuel = actuel->suivant;
}
printf("NULL\n");
}
Lorsqu'on lance la console le programme affiche les unes derrière les autres avec des flèches qui les sépare: "8 -> 4 -> 0 -> NULL"
Je souhaiterais y ajouter une fonction qui permet d'insérer un nouvel élément au milieu de la liste. Apparemment la fonction doit inclure comme paramètre l'adresse de l'élément qui précède celui à insérer, parcourir la liste puis placer celui à insérer derrière celui qui le précède. Le problème est que je ne vois pas quelles instructions permettent d'y arriver.
//fonction pour insérer un élément au milieu de la liste
void insertionAuMilieu(Liste *liste, Liste *listePrecedente, int nouveauNombre)
{
if (liste == NULL || listePrecedente == NULL)
{
exit(EXIT_FAILURE);
}
}
Quelqu'un saurait-il comment peut-on s'y prendre pour créer cette fonction ?
Apparemment la fonction doit inclure comme paramètre l'adresse de l'élément qui précède. Ca n'est pas ce que tu passes en paramètres! Je te propose plutôt:
void insertionAuMilieu(Liste *liste, Element *elementPrecedent, int nouveauNombre)
Ensuite tu dois rechercher l'élément désigné par elementPrecedent dans la liste. Puis insérer juste après un élément nouvellement créé. Attention, le cas où elementPrecedent est le premier de la liste est peut-être à bien gérer!
D'abord, je n'aime pas l'idée d'initialiser une liste chaînée avec un élément que je qualifie de "fictif". On devrait pouvoir insérer dans une liste vide. Il y a un risque de passer un pointeur vers l'élément cherché, mais ça n'est pas pire que de passer le descripteur de liste. Si on passe un pointeur NULL, on devrait pouvoir insérer à la fin de toute liste (même vide). Il est préférable d'attendre au moment de l'insertion pour créer le nouvel élément. Comme ça, si on a un problème, on ne crée pas un élément inutile.
void insertionAuMilieu(Liste *liste, Element *elementPrecedent, int nouveau) { // Je ne vérifie pas si la liste n'existe pas ... Element *actuel = liste->premier; Element *precedent = NULL; while(actuel != NULL && actuel != elementPrecedent) { precedent = actuel; actuel = actuel->suivant; } if(actuel != NULL) { // Si on veut insérer "après" l'élément cherché. precedent = actuel; actuel = actuel->suivant; } Element *ajout = malloc(sizeof(Element)); ajout->suivant = actuel; ajout->nombre = nouveau; if(precedent != NULL) { precedent->suivant = ajout; } else { liste->premier = ajout; } }
Le Tout est souvent plus grand que la somme de ses parties.
où on récupère l'adresse du premier élément de la liste bricolée.
Pourquoi ? Parce que si on considère qu'une liste est (à peu près) l'adresse d'un maillon, les opérations ajouter, enlever, etc modifient la liste. et donc il serait logique de passer l'adresse de (la structure qui représente) cette liste puisqu'on la modifie
void ajouter_a_la_fin(Liste *Liste, int v) {
....
}
MAIS
Le type Liste n'ayant pas été mis en évidence conceptuellement, on en reste aux Maillons
void ajouter_a_la_fin(Maillon ** m, int v)
{
....
}
et SOUDAIN, C'EST LE DRAME ! la PANIQUE ! Un DOUBLE POINTEUR ! HORREUR, MALHEUR ! "on ne peut quand même pas montrer ça au premier cours sur les pointeurs"
Et donc là, RETRAITE STRATÉGIQUE : si on colle un élément fictif devant (*) il suffit de passer son adresse, c'est lui qui contient l'adresse du premier maillon. Quel génie.
Bref, c'est du boulot complètement salopé.
---
Une autre explication ça pourrait être de refuser d'avoir à appeler des fonctions avec des adresses obtenues par "&"
C'est pas mieux, comme idée. Quand on se lance dans les listes chaînées, normalement on doit avoir pris connaissance des petits secrets de C : des fois, on a besoin que les fonctions modifient ce qu'on leur passe en paramètre, et pour ça on passe l'adresse, que l'on manipule à travers un pointeur. Si on ne sais pas ça on a loupé des étapes, et c'est vraiment pas le moment de s'empêtrer en plus dans les chaînages, qui sont une autre histoire.
(*) qui tient lieu maladroitement de struct Liste (on a un champ valeur qui ne sert à rien, et ça ne permet pas d'avoir un pointeur sur le dernier, ni le nombre d'éléments, ce qui serait une évolution fort utile).
#include <stdio.h>
#include <stdlib.h>
#include "liste chainee modulaire.h"
Liste *initialisation()
{
Liste *liste = malloc(sizeof(*liste));
Element *element = malloc(sizeof(*element));
if (liste == NULL || element == NULL)
{
exit(EXIT_FAILURE);
}
element->nombre = 0;
element->suivant = NULL;
liste->premier = element;
return liste;
}
void insertion(Liste *liste, int nvNombre)
{
/* Création du nouvel élément */
Element *nouveau = malloc(sizeof(*nouveau));
if (liste == NULL || nouveau == NULL)
{
exit(EXIT_FAILURE);
}
nouveau->nombre = nvNombre;
/* Insertion de l'élément au début de la liste */
nouveau->suivant = liste->premier;
liste->premier = nouveau;
}
void suppression(Liste *liste)
{
if (liste == NULL)
{
exit(EXIT_FAILURE);
}
if (liste->premier != NULL)
{
Element *aSupprimer = liste->premier;
liste->premier = liste->premier->suivant;
free(aSupprimer);
}
}
void afficherListe(Liste *liste)
{
if (liste == NULL)
{
exit(EXIT_FAILURE);
}
Element *actuel = liste->premier;
while (actuel != NULL)
{
printf("%d -> ", actuel->nombre);
actuel = actuel->suivant;
}
printf("NULL\n");
}
//fonction pour insérer un élément au milieu de la liste
void insertionAuMilieu(Liste *liste, Element *elementPrecedent, int nouveauNombre)
{
// Je ne vérifie pas si la liste n'existe pas ...
Element *actuel = liste->premier;
Element *precedent = NULL;
while(actuel != NULL && actuel != elementPrecedent) {
precedent = actuel;
actuel = actuel->suivant;
}
if(actuel != NULL) { // Si on veut insérer "après" l'élément cherché.
precedent = actuel;
actuel = actuel->suivant;
}
Element *ajout = malloc(sizeof(Element));
ajout->suivant = actuel;
ajout->nombre = nouveau;
if(precedent != NULL) {
precedent->suivant = ajout;
}
else {
liste->premier = ajout;
}
}
et mon fichier liste chainee modulaire.h
#ifndef LISTE_CHAINEE_MODULAIRE_H_INCLUDED
#define LISTE_CHAINEE_MODULAIRE_H_INCLUDED
typedef struct Element Element;
struct Element
{
int nombre;
Element *suivant;
};
typedef struct Liste Liste;
struct Liste
{
Element *premier;
};
Liste *initialisation();
void insertion(Liste *liste, int nvNombre);
void suppression(Liste *liste);
void afficherListe(Liste *liste);
//prototype de la nouvelle fonction pour insérer un élément au milieu de la liste
void insertionAuMilieu(Liste *liste, Element *elementPrecedent, int nouveauNombre);
#endif // LISTE_CHAINEE_MODULAIRE_H_INCLUDED
Lorsque je tente de lancer la console j'ai le message suivant qui apparemment, me dit que je n'ai pas déclarer "nouveau" dans la fonction:
"error: 'nouveau' undeclared (first use in this function)"
Serait-il convenable si j'ajoutais dans la fonction: "Element *nouveau = malloc(sizeof(*nouveau));" ?
Lorsque je tente de lancer la console j'ai le message suivant qui apparemment, me dit que je n'ai pas déclarer "nouveau" dans la fonction:
Effectivement tu ne l'as pas déclaré. Mais je pense que ce n'est pas nouveau que tu voulais affecter à ajout->nombre mais le paramètre nouveauNombre .
Aussi Quand tu appelles la fonction en question (insertionAuMilieu) tu ne peux pas l'appeler avec un entier en second paramètre puisqu'il est attendu un pointeur sur un élément : Element *elementPrecedent .
C'est pas la console que tu lance (j'espère), mais la compilation de ton programme.... Je dis ça pas juste pour être pénible, mais parce qu'il va falloir distinguer "lancer la compilation" et "lancer l'exécution". Et les messages que tu obtiens ne sont pas l'avis de ChatGPT, mais des indications précises :
"error: 'nouveau' undeclared (first use in this function)"
ça ne laisse pas place au doute. Il y a un "nouveau" qui apparait dans la fonction et sans y avoir été déclaré.
Et surtout, bien regarder de quelle ligne ça cause.
---
> Serait-il convenable si j'ajoutais dans la fonction: "Element *nouveau = malloc(sizeof(*nouveau));" ?
Si tu veux programmer par essais et erreur, le mieux c'est que tu demandes directement au compilateur ce qu'il en pense, plutôt qu'aux participants d'un forum. Et si il accepte, que tu fasses tourner pour voir si ça marche.
Mais nouveau, si c'est le truc que tu veux allouer (un nouvel élément), ben tu en as déjà un qui s'appelle ajout.... Il t'en faut vraiment deux ?
---
Ca vaudrait de coup de remplacer les trois lignes suivantes,
Element *ajout = malloc(sizeof(Element));
ajout->suivant = actuel;
ajout->nombre = nouveau;
Par un appel à une fonction qui fait l'allocation et l'initialisation
Aussi Quand tu appelles la fonction en question (insertionAuMilieu) tu ne peux pas l'appeler avec un entier en second paramètre puisqu'il est attendu un pointeur sur un élément : Element *elementPrecedent .
Peut-être 1Stark s'est-il mal exprimé, et qu'il souhaite ajouter après un maillion dont ptr->nombre contient la valeur passée en paramètre.
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
Aussi Quand tu appelles la fonction en question (insertionAuMilieu) tu ne peux pas l'appeler avec un entier en second paramètre puisqu'il est attendu un pointeur sur un élément : Element *elementPrecedent .
Peut-être 1Stark s'est-il mal exprimé, et qu'il souhaite ajouter après un maillion dont ptr->nombre contient la valeur passée en paramètre.
Je parlais de son code.
Prototype de la fonction :
void insertionAuMilieu(Liste *liste, Element *elementPrecedent, int nouveauNombre);
Appel :
insertionAuMilieu(maListe, 4, 50);
On ne peut pas mettre un entier ou un pointeur est attendu.
J'ai modifié ma fonction comme suit dans mon fichier .c dans laquelle j'ai simplement déclaré "nouveau" comme je l'avais suggéré :
//fonction pour insérer un élément au milieu de la liste
void insertionAuMilieu(Liste *liste, Element *elementPrecedent, int nouveauNombre)
{
// Je ne vérifie pas si la liste n'existe pas ...
Element *actuel = liste->premier;
Element *precedent = NULL;
Element *nouveau = malloc(sizeof(*nouveau));
while(actuel != NULL && actuel != elementPrecedent) {
precedent = actuel;
actuel = actuel->suivant;
}
if(actuel != NULL) { // Si on veut insérer "après" l'élément cherché.
precedent = actuel;
actuel = actuel->suivant;
}
Element *ajout = malloc(sizeof(Element));
ajout->suivant = actuel;
ajout->nombre = nouveau;
if(precedent != NULL) {
precedent->suivant = ajout;
}
else {
liste->premier = ajout;
}
}
L'exécution accepte de se lancer même avec l'erreur d'avoir placé un entier en deuxième paramètre mais comme prévu j'obtiens un résultat non voulu qui me place une adresse avant "NULL": 8 -> 4 -> 0 -> 12916000 -> NULL
Toutes mes excuses pour mon manque de clarté : je voudrais demander à l'ordinateur "place-moi ce maillon contenant telle valeur derrière le tel ième maillon", par exemple "place-moi l'élément contenant 20 derrière le deuxième (et donc qui devra se situer entre 4 et 0)". La difficulté pour moi consiste à trouver la façon de prendre l'adresse de 4 pour compléter la fonction. Comment puis-je m'y prendre ?
Peux-tu expliquer pourquoi ta fonction insertionAuMilieu fait deux allocations mémoire ? (lignes 7 et 16)
>Toutes mes excuses pour mon manque de clarté : je voudrais demander à l'ordinateur "place-moi ce maillon contenant telle valeur derrière le tel ième maillon", par exemple "place-moi l'élément contenant 20 derrière le deuxième (et donc qui devra se situer entre 4 et 0)".
Si c'est ça que tu veux, dans le prototype
void insertionAuMilieu(Liste *liste, Element *elementPrecedent, int nouveauNombre)
quel paramètre correspond au 2 de "deuxième" (la position) ?
Y a un moment, il faut se poser, lâcher le code qu'on a écrit (*), et se poser la question : bon, qu'est-ce que ma fonction doit faire exactement ? Quels paramètres je lui donne ? Quel est l'effet sur la liste ?
Le comment, c'est à dire le code qui est censé faire ça, ça vient après.
--
un bon point de départ, c'est le genre de programme de test que tu as écrit au début
Ca devrait afficher quoi ? Le 4 de l'appel, il dit qu'il fait ajouter un élément après (ou avant ?) celui qui contient 4, où avant/après celui qui est en position 4 (numéroté à partir de 0 ou de 1) ?
Mais déjà ça nous dit que que le second paramètre est un entier. Et ça ne colle pas du tout avec
void insertionAuMilieu(Liste *liste, Element *elementPrecedent, int nouveauNombre)
// [ devrait être int ]
(*) psychologiquement, ce n'est pas facile, parce qu'on croit (fort de l'expérience trompeuse des petits exemples qu'on a programmé avant) comme ça compile, on y est presque. On aime bien croire des trucs, en fait, surtout ceux qui laissent penser qu'en bidouillant ici ou là ça finira par marcher. Bah non, ajouter une déclaration de nouveau, le compilateur arrêtera peut-être de râler, mais probable que ça ne marchera pas quand même.
Pour que tout soit clair, il faut bien définir par ce que l'on entend par "insérer au milieu" ?
1Stark a écrit:
Je souhaiterais y ajouter une fonction qui permet d'insérer un nouvel élément au milieu de la liste. Apparemment la fonction doit inclure comme paramètre l'adresse de l'élément qui précède celui à insérer, parcourir la liste puis placer celui à insérer derrière celui qui le précède.
Dans ce cas le prototype de la fonction est correcte. Mais pour obtenir l'adresse de l'élément, il te faudra une autre fonction qui te retourne cette adresse selon les critères que tu souhaites. (par exemple l'adresse de l'élément ayant pour valeur 8).
Est-ce que le prototype de la fonction est imposé ?
Sinon comme te le suggère Michel tu peux faire en sorte que ta fonction insère le nouvel élément après l'élément qui a une certaine valeur.
A priori, un cours qui parle d' insérer au milieu d'une liste est bon à jeter à la poubelle.
Ajouter au début, ou à la fin : ça a un sens, ok.
Par contre, "au milieu", c'est vaseux. Ca serait quoi le milieu d'une liste à un seul élément ? :-)
Avec le recul, on devine que ce qui est sous-entendu, c'est qu'on n'ajoute pas forcément en tête ni en queue, mais à un autre endroit. Et cet endroit, il faut le spécifier (voir plus haut) par un rang dans la liste (en première, seconde, troisième position, ...) par un repère (avant/après le premier ou le dernier élément qui contient ceci-cela), etc.
Si c'est pas fait, si ça reste sous-entendu, et que l'endroit où il faut ajouter n'est pas explicité, c'est que le cours a été écrit par quelqu'un qui ne maîtrise pas le sujet, et ne sait pas l'expliquer.
<<Insertion d'un élément en milieu de liste : si on veut pouvoir ajouter un élément au milieu, il faut créer une fonction spécifique qui prend un paramètre supplémentaire : l'adresse de celui qui précèdera notre nouvel élément dans la liste. Votre fonction va parcourir la liste chaînée jusqu'à tomber sur l'élément indiqué. Elle y insèrera le petit nouveau juste après.>>
Ben, c'est la cata. Dans la fonction qui prend l'adresse de l'élément précédent (etc), il n'y a absolument pas besoin de faire une boucle. C'est juste une allocation et un bidouillage pour le chaîner.
Peut être qu'il veut dire que "ça serait bien" d'écrire une telle fonction, qu'on utiliserait dans une autre qui s'occupera de chercher l'élément avant lequel on "insèrera le petit nouveau". Ce qui nous ferait deux fonctions. Mais c'est pas ce qu'il dit.
Bref, du travail de goret.
Avec un minimum de conscience professionnelle, les rédacteurs de cours viendraient voir sur les forums qui en parlent, constateraient les problèmes que leur production bâclée cause (depuis des années) aux débutants, et rectifieraient le tir. Ben non.
Ça m'énerve, au cas où vous auriez pas remarqué :-)
Je suis sans doute quelque peu hors sujet, mais dans mon esprit (c'est peut-être là que se trouve le grain de sable), si on utilise une liste chainée, c'est qu'il y a quelque part un critère qui permet de trier cette liste.
Je peux comprendre que l'on fasse mumuse en lisant simplement des données dans en fichier et en les collant en mémoire dans une liste chaînée en utilisant seulement un InsertFirst() ou un InserLast().
Mais dans ce cas, à quoi bon une fonction InsertSomewhereButNotInFirstOrLastPosition() ? Il doit y avoir un critère pour insérer après (ou avant) tel maillon donné ou telle valeur à rechercher. Et si tel est le cas, pourquoi ne pas trier les éléments au fur et à mesure de leur apparition ?
Bien sur, il y a l'aspect manipulation des pointeurs, mais comme le suggère MichelBillaud, pourquoi ne pas faire un tutoriel avec un exemple concret plutôt que d'aller battre la cambrousse avec des fonctions à la con noix.
- Edité par edgarjacobs 7 avril 2023 à 23:39:30
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
Ça m'énerve, au cas où vous auriez pas remarqué :-)
Et nous, on en profite, par exemple en lisant les tutoriels de ton site dont certains, si j'ai bien compris, sont inspirés de ce que tu as vu ici...
Euh, les tutoriels de mon truc ca vient en grande partie de cours que je faisais quand les rédacteurs de celui ci n'étaient probablement pas nés
(Oui, on faisait des listes chaînées en Pascal)
Mais effectivement les horreurs que je peux voir dans les cours ici (et leur effet sur d'innocents débutants), ca peut etre un déclencheur pour rédiger des trucs.
@michelbillaud: Peux-tu me donner le lien vers ton tuto sur les listes chaînées? Pas que j'en ai besoin, mais je cherche trop souvent des exemples de tuto à donner comme références à ceux qui en ont besoin. Et je suis d'accord avec vous tous. Ça prend un critère pour insérer ailleurs qu'au début ou à la fin.
Le Tout est souvent plus grand que la somme de ses parties.
C'est pas vraiment un tutoriel, c'est une partie d'un document qui essaie d'expliquer les pointeurs et ce qu'on peut en faire. Notamment des structures chaînées, mais pas que.
<< Ce document montre les principes de fonctionnement de divers conteneurs (tableau extensible, listes chaînées, dictionnaire, …) en allant jusqu’aux détails d’implémentation.
Pour aller au niveau le plus bas que permet la portabilité, l’implémentation est réalisée en C. >>
Peux-tu expliquer pourquoi ta fonction insertionAuMilieu fait deux allocations mémoire ? (lignes 7 et 16)
J'ai essayé de voir ce que cela donnerait en me doutant que cela n'est pas convenable, car ne connaissant pas les instructions qui permettraient d'arriver à construire la fonction voulut.
quel paramètre correspond au 2 de "deuxième" (la position) ?
La fonction doit prendre en premier paramètre dans cet exemple un élément de type "Liste", en deuxième paramètre l'adresse de tel ième liste et en troisième paramètre l'entier de cet élément Liste. Pour l'exemple ci-dessus, il doit y avoir affiché dans la console : 4 -> 8 -> 50 -> 34 -> 15. Le problème est que j'ignore comment demander l'adresse de tel ième maillon
Il s'agit du nombre que l'on est censé affiché dans la console à telle position : En troisième position dans l'exemple plus haut
Pourquoi affecter un pointeur sur un élément (Element *) à ajout->nombre qui attend un entier (int).
Peut-être une maladresse de ma part
Pour que tout soit clair, il faut bien définir par ce que l'on entend par "insérer au milieu" ?
Cela signifie que la chaîne comprend au minimum deux maillons et que le maillon à insérer peut se trouver n'importe ou entre les deux (si la chaine ne possède que deux maillons, inévitablement le maillon à insérer sera en deuxième position). Je suppose donc que la fonction doit inclure une condition if
Est-ce que le prototype de la fonction est imposé ?
Non, ce prototype à été écrit selon ma compréhension personnelle
Sinon comme te le suggère Michel tu peux faire en sorte que ta fonction insère le nouvel élément après l'élément qui a une certaine valeur.
Si je fais comme cela, je suppose que le code prendra en compte le premier maillon ayant cette valeur, or dans la chaine plusieurs maillons peuvent avoir la même valeur:
1Stark a écrit: La fonction doit prendre en premier paramètre dans cet exemple un élément de type "Liste", en deuxième paramètre l'adresse de tel ième liste et en troisième paramètre l'entier de cet élément Liste. Pour l'exemple ci-dessus, il doit y avoir affiché dans la console : 4 -> 8 -> 50 -> 34 -> 15. Le problème est que j'ignore comment demander l'adresse de tel ième maillon
Je ne sais pas si tu crois que le 3ième paramètre est associé au 2ième. Pour obtenir l'adresse associée au pointeur de type Element, il faut écrire une "autre" fonction. Hé oui. il faut chercher dans cette nouvelle fonction l'endroit où tu veux insérer. On se répète, mais tu dois donner dans cette nouvelle fonction le critère te disant à quel endroit insérer. + Est-ce avant ou après une valeur dans la liste courante? + Est-ce avant ou après la position relative au début (le 6ième élément par exemple)? et on retourne le pointeur obtenu pour "ensuite" s'en servir dans la fonction d'insertion "au milieu" ...
Le Tout est souvent plus grand que la somme de ses parties.
adresseDeLaDeuxiemeListe (attention à ne pas confondre liste et élément).
Tu envois cette variable à ta fonction, ok, mais tu ne l'as pas définie et comment va tu obtenir l'adresse à affecter à cette variable ?
...
Création d'une fonction pour une liste chaînée
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
En recherche d'emploi.
Le Tout est souvent plus grand que la somme de ses parties.
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
Le Tout est souvent plus grand que la somme de ses parties.
Le Tout est souvent plus grand que la somme de ses parties.