• Aucun résultat trouvé

Structures Récursives

Dans le document 1. Ce qu'est un Programme C (Page 110-118)

STRUCTURES DE DONNEES

5. Types Structurés

5.5. Structures Récursives

Une structure peut s'utiliser elle-même, mais par pointeur.

Structures De Données Struct arbre {

int valeur;

struct arbre *FilsGauche;

struct arbre *FilsDroit;

};

Les champs FilsDroit et FilsGauche ne sont pas une structure arbre, mais un pointeur vers structure arbre. Nous y reviendrons (§ 5.7).

5.6. Unions

L'union est une structure qui contient une donnée dont le type varie selon les circonstances de son utilisation. Sa syntaxe est semblable à celle de struct mais avec le mot clé union.

union ent_car { int i;

char c;

} x;

Ici on a deux variables x.i et x.c, qui occupent la même place en mémoire donc une seule donnée. Avec struct au lieu de union, il y aurait eu deux emplacements différents. La taille de x ici est celle du plus grand composant de l'union, donc 4 octets (de int).

x.c = 'A';

printf("i = %d c = %c\n", x.i, x.c);

donne i = 65 c = 'A' (i=65 si le compilateur est bien gentil de remplir les 3 octets restants par 0). C'est en fait i considéré caractère dans le second cas, i.e. x.i et x.c désignent un même objet. &x.i est donc égale à &x.c.

Se pose alors le problème de comment savoir à tout moment si on doit utiliser un entier ou un caractère? Quel type est significatif? En associant une autre donnée qui pourrait être utilisée pour indiquer que la valeur est significative mais pour tel type, comme dans la figure IV-5.

Cette structure StrNoeud, et la variable associée x, contient deux éléments : le champ type et le champ valeur. Le champ type servira, si égale à 'C' par exemple, pour indiquer que

Langage C

106

serait valeur.opérande. Plus précisément x.valeur.operande et x.valeur.opérateur. On peut reconnaître ici un arbre binaire représentant une expression arithmétique où un noeud est parfois opérande parfois opérateur.

struct StrNoeud { char type;

union {

int operande;

char operateur;

} valeur;

} x ;

Fig-IV-5, Une structure avec Union, pour le Nœud d'un Arbre Expression Arithmétique.

On peut utiliser cette structure par switch (x.type) {

case 'I' :

printf (" Operande %d \n", x.valeur.operande);

break;

case 'C' : printf (" Operateur %c \n", x.valeur.operateur);

break;

default : printf (" Pardon? \n");

}

Moralité: l'union n'est utile que si elle est associée à une structure.

Elle peut aussi être combinée avec des pointeurs et des tableaux comme pour les structures. La même notation est appliquée

<NomUnion>.<Champs> ou <NomUnion> -> <Champs>

En d'autres termes, l'union est une structure où tous les champs sont situés au même endroit et se chevauchent.

5.7. Typedef

C permet de définir de nouveaux noms de types. Ce n'est pas de nouveaux types, mais juste un nom pour se référer à un

Structures De Données

type défini par combinaison de types existants, ou pour avoir un type mnémonique.

typedef int NOMBRE;

rend NOMBRE synonyme de int. On peut l'utiliser pour déclarer NOMBRE effectif, taille;

NOMBRE dimensions [4];

On peut aussi créer un type STRING typedef char* STRING;

et déclarer

STRING nom; STRING classe[30];

C'est peut être mieux que char* classe[30];

typedef est très utile avec les structures. En voici deux exemples.

typedef struct { float re;

float im;

} COMPLEXE, *PCOMPLEXE;

De struct à } c'est la définition du type (une structure), et COMPLEXE c'est son nom. PCOMPLEX est un type pointeur vers cette structure. On peut donc déclarer

COMPLEXE x, y, z;

PCOMPLEXE px, py, pz;

Cette dernières écriture est du reste la même que COMPLEXE *px,

*py, *pz.

Mais attention aux opérations permises pour les nouveaux types.

z = x + y; est bien sûr incorrecte. Voici une fonction qui ajoute deux complexes:

Langage C

108

PCOMPLEXE cxPlus (PCOMPLEXE x, PCOMPLEXE y){

COMPLEXE z;

z.re = x->re + y->re;

z.im = x->im + y->im;

return (&z);

}

et qu'on peut utiliser par

pz = cxPlus (px, py); ou par

pz = cxPlus (&x, &y);

Autrement dit, les complexes étant de nouveaux objets, il faut en fournir les opérateurs de manipulations (cf. C++).

Un deuxième exemple de typedef est donné par la figure IV-6.

typedef struct StrArbre { struct StrNoeud nd;

struct StrArbre *fg;

struct StrArbre *fd;

} ARBRE, *PTRARBRE;

Fig-IV-6, Structure Arbre Binaire (définition des types arbre et pointeur vers arbre)

Ici la structure est nommée par StrArbre car il y est fait référence. StrNoeud est la structure déjà définie plus haut (figure IV-5). Voici un exemple d'utilisation

PTRARBRE ArbreAllouer(){

/* allocation d'un arbre */

return ( (PTRARBRE) malloc(sizeof(ARBRE)));

}

qui alloue l'espace pour un arbre (sa racine plus exactement).

Pour les fils il doit y avoir la même allocation. Noter la conversion vers le type PTRARBRE du résultat char* de malloc(), et l'usage de sizeof() pour calculer la taille du type ARBRE. D'où l'usage de deux noms: ARBRE et PTRARBRE dans typedef.

Utiliser typedef a donc certains avantages. Il permet d'utiliser des nom courts est mnémoniques (COMPLEXE, ARBRE)

Structures De Données

pour une bonne compréhension de programme, et surtout il peut aider à assurer une meilleur portabilité pour les types dépendants des machines (ce qui le rend très utilisé dans les fichiers include). Le changement sera fait une seule fois dans la ligne typedef. Imaginer qu'il faut partout remplacer int par short, si int est prévu sur 2 octets et il se trouve qu'il est sur 4.

Si on avait

typedef int ENTIERCOURT;, on n'aura qu'à réécrire typedef short ENTIERCOURT;.

5.8. Exemples de Programmes

Nous allons illustrer des structures de données sur deux exemples. L'exemple de la figure IV-7 est une fonction qui évalue le résultat d'une expression donnée sous forme d'arbre (paramètre e de type PTRARBRE). Les déclarations utilisées sont celles des figures IV-5 et IV-6.

Langage C

110 EvalExp( PTRARBRE e ){

if (e == NULL) /* arbre vide au premier appel

*/

return;

else if (e->nd.type == 'I') /* c'est une feuille */

return (e->nd.valeur.operande);

else /* c'est un noeud operateur */

switch (e->nd.valeur.operateur) {

case '+' : return ( EvalExp(e->fg) + EvalExp(e->fd)); break;

case '-' : return ( EvalExp(e->fg) - EvalExp(e->fd)); break;

case '/' : return ( EvalExp(e->fg) / EvalExp(e->fd)); break;

case '*' : return ( EvalExp(e->fg) * EvalExp(e->fd)); break;

default : printf("Operateur bizarre! %c\n", e->nd.valeur.operateur);

};

}

Fig-IV-7, Evaluation d'une Expression Arithmétique (représentée sous forme d'arbre).

L'algorithme commence par tester si e est NULL, auquel cas l'arbre est vide. Ensuite si l'information du nœud (racine de e) est du type entier alors c'est une feuille (un arbre réduit à la racine, i.e. expression réduite à un opérande), et la valeur de e est celle de ce nœud. Sinon, le nœud est un opérateur et le résultat de l'évaluation de e est celui de l'application de cet opérateur aux 2 sous-expressions: sous-arbre gauche et sous-arbre droit, dans cet ordre. Ces sous-expressions sont bien sûr évaluées par appel récursif à la même fonction. Noter que ces appels récursifs ne se font jamais sur une expression NULL. Du moins si l'arbre est bien formé.

Voici un exemple d'arbre bien formé:

Structures De Données

+

- *

3 1 3 /

6 2

Dont l'évaluation donne la valeur 11, i.e. (3-1) + 3*(6/2).

Un deuxième exemple, beaucoup plus simple et pour rester au milieu des arbres, est bien sûr le parcours pré/post/infixé d'un arbre. Voir figure IV-8 pour un programme réalisant le parcours préfixé. La spécification de l'algorithme est

PreFixe (A) = Racine (A) ; PreFixe(A.FG) ; PreFixe(A.FD) où A est un arbre et Racine(A) un traitement quelconque sur le nœud racine de A, comme l'impression de sa valeur dans cet exemple. Le point virgule signifie l'enchaînement. Les autres parcours s'en déduisent simplement en mettant Racine(A) au bon endroit:

PostFixe (A) = PostFixe(A.FG) ; PostFixe(A.FD) ; Racine(A) InFixe (A) = InFixe(A.FG) ; Racine(A) ; InFixe(A.FD)

PreFixe(PTRARBRE e) { if (e != NULL) {

if (e->nd.type == 'I') printf("%d ",

e->nd.valeur.operande);

else

printf("%c",

e->nd.valeur.operateur);

pre(e->fg);

pre(e->fd);

} }

Fig-IV-8, Parcours Prefixé d'Arbre Binaire.

Exercice: Vérifier par programme les parcours (post/in)fixés.

Langage C

112

Voici le résultat donné pour le parcours préfixé du même arbre que ci-dessus.

+ - 3 1 * 3 / 6 2

Un programme principal pour ces fonctions est donné par la figure IV-9. On y a initialisé un arbre par affectations dans les différents nœuds pour illustrer l'usage des structures avec des pointeurs. Les variables a, b,..., i sont des pointeurs pour les différents nœuds. L'arbre construit est (a, (b, (d, e), c, (f, (g, (h, i))))) en notation LISP. Soit:

a

b c

d e f g

h i

Attention, ne pas commettre l'erreur facile de vouloir condenser et écrire a=b=c=...=i=ArbreAllouer(); (Pourquoi c'est faux?)

Dans le document 1. Ce qu'est un Programme C (Page 110-118)

Documents relatifs