Structures and Their Applications in C Programming

Page 1 sur 12Lecteur de document UniversityLib

Structures and Their Applications in C Programming

Programming in C · notes

Voir tous les documents en programmation

Ecole Supérieure de Technologie et d’Informatique

Année universitaire : 2011-2012

Module : Programmation 2

TD N°1 Les structures

Mme BELKADHI CHELBI H. [email protected]

1. Notion d’ « enregistrement » ou de « structure »

Contrairement aux tableaux qui sont des structures de données dont tous les éléments sont de même type, les enregistrements sont des structures de données dont les éléments peuvent être de type différent.

Le langage C possède le concept d'enregistrement avec cependant un problème de vocabulaire :

Ce que tout le monde appelle Enregistrement Champs de l’enregistrement

Le langage C appelle Structure Membre de la structure

Avant de déclarer une variable structure, il faut avoir au préalable définit son type, c'est à dire le nom et le type des membres qui le composent. Le type d'une structure est appelé type structuré.

1.1. Syntaxe

On désire déclarer l'identificateur "personne" comme étant le nom d'un type de structure composée de trois membres, dont le premier est un tableau de 20 caractères nommé "nom", le second un tableau de 20 caractères nommé "prenom", et le dernier un entier nommé "no_employe". Il existe plusieurs méthodes possibles pour ce faire.

Première méthode struct personne {

char nom[20]; char prenom[20]; int no_employe;

};

Dans le jargon du langage C, l'identificateur "personne" est une étiquette de structure. On peut ensuite utiliser ce type structure pour déclarer des variables, de la manière suivante :

struct personne p1,p2;

qui déclare deux variables de type struct personne de noms p1 et p2.

Deuxième méthode On peut déclarer des variables de type structure sans utiliser d'étiquette de structure, par exemple : struct {

char nom[20]; char prenom[20]; int no_employe;

} p1,p2;

Ceci déclare deux variables de noms p1 et p2 comme étant deux structures de trois membres, mais elle ne donne pas de nom au type de la structure.

L'inconvénient de cette méthode est qu'il sera par la suite impossible de déclarer une autre variable du même type.

En effet, si plus loin on écrit : struct {

char nom[20]; char prenom[20]; int no_employe;

} p3;

les deux structures ont beau avoir le même nombre de champs, avec les mêmes noms et les mêmes types, elles seront considérées de types différents. Il sera impossible en particulier d'écrire p3 = p1;.

Troisième méthode On peut combiner déclaration d'étiquette de structure et déclaration de variables, comme ceci : struct personne {

char nom[20]; char prenom[20]; int no_employe;

} p1,p2;

Ceci déclare deux variables p1 et p2 et donne le nom personne à la structure. Là aussi, on pourra utiliser ultérieurement le nom struct personne pour déclarer d'autres variables qui seront du même type que p1 et p2.

struct personne pers1, pers2, pers3;

De ces trois méthodes, c'est la première qui est recommandée, car elle permet de bien séparer la définition du type structure de ses utilisations.

Quatrième méthode Lorsqu'on donne un nom à un type structure par typedef, l'utilisation est beaucoup plus aisée. En effet, si on déclare : struct personne {

...

};

les déclarations de variables se feront par : struct personne p1,p2; Alors que si on déclare : typedef struct {

... } PERSONNE;

les déclarations de variables se feront par :

PERSONNE p1,p2;

On voit que la seconde méthode permet d'éviter d'avoir à répéter struct.

Application 1 On caractérise chaque planète d’un système planétaire par :

- - - -

son nom (20 caractères au maximum), sa densité (float), la distance moyenne de l’étoile autour de laquelle elle gravite (float) en 10E6 km, son nombre de satellites (int).

Définissez un type structuré permettant de représenter une planète.

//Définition de la structure planète struct planete {

Publicité

char nom[20] ; float densite ; float distance ; int nbSatellites ;

};

Initialisation d’une structure Une structure peut être initialisée par une liste d'expressions constantes à la manière des initialisations de tableau.

Par exemple :

struct personne p = {"Jean", "Dupond", 7845};

Application 2 Déclarez et initialisez à la déclaration les planètes suivantes :

Nom Mercure Venus Terre

Densité 5.42 5.25 5.52

Distance moyenne 58 108.2 149.6

Nombre de satellites 0 0 1

//Initialisation de 3 planètes struct planete p1={"Mercure", 5.42, 58, 0}; struct planete p2={"Venus", 5.25, 108.2, 0}; struct planete p3={"Terre", 5.52, 149.6, 1};

1.2. Opérateurs sur les structures a. Accès aux membres de la structure

Pour désigner un membre d'une structure, il faut utiliser l'opérateur de sélection de membre qui se note "." (point). Par exemple, si p1 et p2 sont deux variables de type struct personne, on désignera le membre nom de p1 par p1.nom et on désignera le membre no_employe de p2 par p2.no_employe. Les membres ainsi désignés se comportent comme n'importe quelle variable et par exemple, pour accéder au premier caractère du nom de p2, on écrira : p2.nom[0].

b. Affectation de structures

On peut affecter une structure à une variable structure de même type, grâce à l'opérateur d'affectation : struct personne p1,p2; ... p1 = p2;

c. Comparaison de structures

Aucune comparaison n'est possible sur les structures, même pas les opérateurs == et !=

Application 3 Déclarez trois variables structurées de type struct planete et leur affecter les planètes précédentes. Déclarez une quatrième planète p, demandez à l’utilisateur de remplir les différents champs de cette planète et enfin l’afficher. Modifier la planète Mercure en multipliant sa densité par 1.2 et en lui rajoutant 3 satellites.

//Affectation des 3 planètes à 3 nouvelles planètes struct planete p4=p1; struct planete p5=p2; struct planete p6=p3;

//Déclaration d'une nouvelle planète struct planete p; //Lecture des champs de la planète printf ("\nLe nom de la planete: "); gets(p.nom); printf ("La densite de la planete: "); scanf("%f",&(p.densite)); printf ("La distance (10E6 km): "); scanf("%f",&(p.distance)); printf ("Le nombre de ses satellites: "); scanf("%d",&(p.nbSatellites)) ;

//Affichage de la planète printf ("\n\nLa planete est: ") ; puts(p.nom); printf ("Sa densite est: %.2f", p.densite); printf ("\nSa distance : %.2f * 10E6 km", p.distance); printf ("\nElle possede %d satellites.", p. nbSatellites);

//Modification de paramètres de Mercure p4.densite=p4.densite*1.2; p4.nbSatellites+=3;

1.3. Passage de structure en paramètre

Supposons que l'on ait fait la déclaration suivante : struct date {

int jour, mois, annee;

};

Une fonction de comparaison de deux dates pourra s'écrire : enum {AVANT, EGAL, APRES};

int cmp_date( struct date d1, struct date d2) {

if (d1.annee > d2.annee) return(APRES); if (d1.annee < d2.annee) return(AVANT);

... //comparaison portant sur mois et jour

}

Une utilisation de cette fonction pourra être : struct date d1,d2; if (cmp_date(d1,d2) == AVANT) …

Application 4 Vous remarquez que le nombre de lignes de code augmente pour chaque saisie et chaque affichage de planète. Comme vous l’avez appris, nous allons écrire des fonctions permettant de structurer notre application :

1. Ecrire une fonction en C, struct planete creerPlanete() qui invite à la saisie une planète au

clavier et la retourne (par l’utilisation de return).

2. Ecrire une fonction en C, void afficherPlanete(), qui affiche clairement les caractéristiques

de la planète reçue en paramètre.

3. Ecrire une fonction en C, int egales(struct planete p1, struct planete p2) qui compare les caractéristiques des planètes p1 et p2. La fonction retourne 0 (signifie false en C) si les planètes ont des caractéristiques différentes, et 1 (signifie true en C) si les deux planètes ont les mêmes caractéristiques.

//Fonction de création de planète struct planete creerPlanete() {

struct planete p; printf ("\nLe nom de la planete: "); gets(p.nom); printf ("\nLa densite de la planete: ");

scanf("%f",&(p.densite)); printf ("\nLa distance (10E6 km): "); scanf("%f",&(p.distance)); printf ("\nLe nombre de ses satellites: "); scanf("%d",&(p.nbSatellites)) ;

return p;

}

//Procédure d'affichage d'une planète void afficherPlanete(planete p) {

printf ("\n\nLa planete est: "); puts(p.nom); printf ("Sa densite est: %.2f", p.densite); printf ("\nSa distance : %.2f * 10E6 km", p.distance); printf ("\nElle possede %d satellites.", p.nbSatellites);

}

//Test d'égalité de 2 planètes int egales(struct planete p1, struct planete p2) {

Publicité

if (strcmp(p1.nom, p2.nom)==0 && p1.densite==p2.densite && p1.distance==p2.distance && p1.nbSatellites==p2.nbSatellites )

else

return 1;

return 0;

}

1.4. Tableaux de structures

Une déclaration de tableau de structures se fait selon le même modèle que la déclaration d'un tableau dont les éléments sont de type simple. Supposons que l'on ait déjà déclaré la structure personne, si on veut déclarer un tableau t de 100 personnes, on écrira :

struct personne t[100];

Pour référencer le nom de la personne qui a l'index i dans t on écrira : t[i].nom

Application 5 Ecrire une procédure ...initSysteme(...) qui initialise un système planétaire comportant n planètes Ecrire une procédure ...afficherSysteme(...) qui affiche les n planètes contenues dans un tableau de planètes Ecrire une fonction ... nbMoy(..) qui renvoie le nombre moyen de satellites par planète ainsi que la densité moyenne des planètes d’un système de n planètes

//Procédure d'initialisation de système planétaire void initSysteme(struct planete systeme[ ], int n) {

int i; for (i=0; i<n; i++) {

systeme[i] = creerPlanete( );

}

}

//Procédure d'affichage de système planétaire void afficheSysteme(planete systeme[ ], int n) {

int i; for (i= 0; i<n; i++) {

afficherPlanete (systeme[i]);

}

}

//Procédure de calcul de moyennes void nbMoyen(struct planete s[ ],int n, float *pnb, float *pdens) {

int i ; int sommeNombreSatellites = 0 ; float sommeDensite = 0;

for (i= 0 ; i < n ; i++) {

nombreSatellites = nombreSatellites + systeme[i]. nbSatellites ; sommeDensite = sommeDensite + systeme[i].densite ;

}

*pnb = (float) nombreSatellites/n; *pdens = sommeDensite/n;

}

2. Pointeurs vers une structure

Supposons que l'on ait défini la structure personne à l'aide de la déclaration : struct personne {

...

};

On déclarera une variable de type pointeur vers une telle structure de la manière suivante :

struct personne *p;

On pourra alors affecter à p des adresses de structure personne. Par exemple : struct personne {

...

};

int main() {

struct personne pers; //pers est une variable de type struct personne struct personne *p; //p est un pointeur vers une struct personne p = &pers; …

}

Utilisation de « typedef » En ce qui concerne les pointeurs, il est plus difficile d'écrire et de comprendre : struct personne {

...

}; struct personne *p1,*p2; // p1 et p2 pointeurs vers des struct

que la version suivante qui donne un nom parlant au type pointeur vers struct typedef struct {

... } PERSONNE;

typedef PERSONNE *P_PERSONNE; //P_PERSONNE type pointeur vers struct P_PERSONNE p1,p2; //p1 et p2 pointeurs vers des struct

2.1. Accès aux éléments d'une structure pointée

Supposons que nous ayons déclaré p comme étant de type pointeur vers une structure personne, comment écrire une référence à un membre de la structure pointée par p ?

Publicité

Etant donné que *p désigne la structure, on serait tenté d'écrire *p.nom pour référencer le membre nom. Mais il faut savoir que les opérateurs d'indirection (*) et de sélection (.), tout comme les opérateurs arithmétiques, ont une priorité. Et il se trouve que l'indirection a une priorité inférieure à celle de la sélection. Ce qui fait que *p.nom sera interprété comme signifiant *(p.nom). Cela aurait un sens si p était une structure dont un des membres s'appelait nom et était un pointeur. Dans notre cas, il faut écrire (*p).nom pour forcer l'indirection à se faire avant la sélection. Cette écriture étant assez lourde, le langage C a prévu un nouvel opérateur noté -> qui réalise à la fois l'indirection et la sélection : p->nom est identique à (*p).nom.

Par exemple : si p est de type pointeur vers la structure personne définie précédemment, pour affecter une valeur au membre no_employe de la structure pointée par p, on peut écrire :

p->no_employe = 123456;

2.2. Structures dont un des membres pointe vers une structure du

même type

Une des utilisations fréquentes des structures, est de créer des listes de structures chaînées. Pour cela, il faut que chaque structure contienne un membre qui soit de type pointeur vers une structure du même type. Cela se fait de la façon suivante : struct personne {

... //les différents membres struct personne *suivant;

};

Le membre de nom suivant est déclaré comme étant du type pointeur vers une struct personne. La dernière structure de la liste devra avoir un membre suivant dont la valeur sera le pointeur NULL.

3. Détermination de la taille allouée à un type

Pour connaître la taille en octets de l'espace mémoire nécessaire pour une variable, on dispose de l'opérateur sizeof. Cet opérateur est un opérateur unaire préfixé que l'on peut employer de deux manières différentes : soit sizeof expression ou sizeof (nom-de-type).

Exemple 1 int i,taille; taille = sizeof i; taille = sizeof (short int); taille = sizeof (struct personne);

Exemple 2 int t[10]; if (sizeof(t) / sizeof(int) != 10) printf("sizeof mal implémenté\n"); else printf("sizeof ok\n");

3.1. Allocation et libération d'espace pour les structures

Nous allons voir dans ce paragraphe trois fonctions de la bibliothèque standard permettant d'allouer et de libérer de l'espace.

3.1.1.

Allocation d'espace : fonctions « malloc » et « calloc »

Quand on crée une liste chaînée, c'est parce qu'on ne sait pas à la compilation combien elle comportera d'éléments à l'exécution (sinon on utiliserait un tableau). Pour pouvoir créer des dynamiquement. On dispose pour cela de deux fonctions : malloc et calloc.

il est donc nécessaire de pouvoir allouer de

l'espace

listes,

a. Allocation d'un élément : fonction « malloc »

La fonction malloc admet un paramètre qui est la taille en octets de l'élément désiré et elle rend un pointeur vers l'espace alloué.

Utilisation typique : #include <stdlib.h> struct personne *p; p = malloc(sizeof(struct personne));

b. Allocation d'un tableau d'éléments : fonction « calloc »

Elle admet deux paramètres :

- -

le premier est le nombre d'éléments désirés ; le second est la taille en octets d'un élément.

Son but est d'allouer un espace suffisant pour contenir les éléments demandés et de rendre un pointeur vers cet espace.

Utilisation typique : #include <stdlib.h> struct personne *p; int nb_elem; ... /* init de nb_elem */ p = calloc(nb_elements, sizeof(struct personne));

On peut alors utiliser les éléments p[0], p[1], ... p[nb_elem-1].

3.2. Libération d'espace : procédure « free »

On libère l'espace alloué par malloc ou calloc au moyen de la procédure free qui admet un seul paramètre : un pointeur précédemment rendu par un appel à malloc ou calloc.

Utilisation typique : #include <stdlib.h> struct personne *p; p = malloc(sizeof(struct personne)); ... /* utilisation de la structure allouée */ free(p);

Application 6 Refaire le même exemple précédent en utilisant les tableaux dynamiques

On doit ajouter la bibliothèque stdlib.h #include <stdlib.h>

//Procédure d'initialisation de système planétaire void initSysteme(struct planete* systeme, int n) {

int i; for (i=0; i<n; i++)

*(systeme+i) = creerPlanete();

}

//Procédure d'affichage de système planétaire void afficherSysteme(struct planete* systeme, int n) {

struct planete *i; for (i=systeme; i<systeme+n; i++) afficherPlanete(*i);

}

//Programme principal void main() {

int n; printf("\nEntrer la taille du tableau:"); scanf("%d",&n); struct planete *s=(struct planete*) calloc(n,sizeof(struct planete)); initSysteme(s,n); afficherSysteme(s,n); free(s);

}