TP C3  2425v2

I.          Les structures en C

 

I.1. Idée / syntaxe

 

Les tableaux nous permettent de regrouper facilement des données de même type puis de les manipuler avec un seul identificateur, par exemple pTab.

Mais comment faire pour manipuler aussi facilement un regroupement de données de types différents ?       

Le langage C propose la notion de structure :

  struct { char lettre; int chiffre; } vGroupe1;

ou peut-être de façon plus lisible :

  struct {

    char lettre;

    int chiffre;

  } vGroupe1;

(pour représenter par exemple les groupes d’IGI-3007/3008 : J2, P3, …)

définit une variable vGroupe1 qui contient un caractère noté vGroupe1.lettre et un entier noté vGroupe1.chiffre .
L’opérateur
. permet d’accéder à un champ de la structure (comme en java ou en python où il permet d’accéder à un attribut d’un objet, mais en C, ces champs sont toujours publics).

 

Quel est l’intérêt par rapport à simplement créer deux variables lettre et chiffre ?

De pouvoir manipuler d’un seul coup un groupe, par exemple pour le recopier :

  vGroupe2 = vGroupe1;   remplace avantageusement :

  vGroupe2.lettre = vGroupe1.lettre;  vGroupe2.chiffre = vGroupe1.chiffre;

 

I.2. Exercice I-2.c

1.      Créez un nouveau programme C qui ne contiendra qu’un programme principal. Il définira la structure de groupe évoquée ci-dessus, mais créera à cette occasion directement 2 variables vGroupe1 et vGroupe2.
Attention ! Déclarer 2 fois la même structure crée 2 types « anonymes » différents, donc incompatibles.

2.      Remplissez les 2 champs de vGroupe1 avec des valeurs que vous choisissez, recopiez vGroupe1 dans vGroupe2, puis affichez les 2 champs de vGroupe2 ; a-t-on bien recopié d’un coup les deux valeurs ?

 

II.        De nouveaux types ?

 

II.1. De l’intérêt du typedef

 

Pour pouvoir passer en paramètre un groupe,  ou le récupérer en valeur de retour d’une fonction, il apparaît nécessaire de créer un nouveau type pour pouvoir facilement utiliser son nom.

  typedef struct { char lettre ; int chiffre ; } Groupe;

A partir de maintenant, le nom Groupe est utilisable comme n’importe quel type :

  Groupe vGroupe1, vGroupe2 ;

  void procedure(Groupe pGroupe) { … }

  Groupe fonction() { … }

et procedure(vGroupe1); remplace avantageusement procedure(vLettre1, vChiffre1);

et vGroupe1 = fonction(); remplace très avantageusement

  vGroupe1.lettre = fonctionL();   vGroupe1.chiffre = fonctionC();

Remarque : La ligne typedef ci-dessus correspondrait à peu près en java à :

  public class Groupe { public char lettre; public int chiffre; }

mais l'énorme différence est qu'ensuite, la déclaration  Groupe vG;  en C crée bien une structure dans laquelle on peut stocker une lettre et un chiffre, alors qu'en java, cela ne crée qu'une référence dans laquelle on ne pourra stocker que l'adresse d'un futur objet de la classe Groupe.

 

II.2 Exercice II-2.c

1.      Recopiez l’exercice I-2.c dans II-2.c.

2.      Transformez la déclaration des 2 variables en créant auparavant le type Groupe. Compilez/testez.

3.      Créez une procédure afficheGroupe qui prend en paramètre un Groupe et qui affiche ses 2 champs pour former un groupe tel que J2 ou P3. Appelez-la dans le programme principal pour afficher vGroupe1 puis vGroupe2. Compilez/testez.

4.      Créez une fonction saisitGroupe sans aucun paramètre qui saisit un caractère (*) puis un nombre (*), et qui retourne un Groupe dont les champs auront été initialisés avec les 2 valeurs saisies. Appelez-la pour initialiser vGroupe1 au lieu de fixer les valeurs dans le programme principal. Compilez/testez.
Attention ! saisitGroupe ne doit rien afficher à la fin.
(*) Avez-vous bien prévu un message qui prévient l’utilisateur de ce qu’on lui demande de saisir ?

5.      En travail personnel (ou à la fin du TP si vous avez le temps), améliorez la fonction pour qu’elle redemande le nombre tant qu’il ne correspond pas à un des 10 chiffres. Compilez/testez.

6.      De même, améliorez la fonction pour qu’elle redemande le caractère tant que ce n’est pas une lettre majuscule (*).
Attention ! Apparaît ici un problème de gestion du caractère de fin de ligne '\n'. En effet, puisque vous lisez éventuellement un 2ème caractère de suite, c’est le '\n' tapé lors de la saisie du caractère précédent qui sera lu avant le 2ème caractère que vous souhaitez saisir. Il faut donc utiliser une variable pour stocker ce caractère « inutile ». Si vous lisez un nombre après un caractère, ce problème n’apparaît pas, car la lecture de nombre saute ce caractère '\n' ne faisant pas partie du nombre.          
(*) En regardant la
Standard Library, vous trouverez dès la première page une fonction permettant de tester facilement votre caractère. Dans quel fichier cette fonction est-elle déclarée ?          
Attention ! Ces fonctions indiquent souvent des
int, mais elles servent bien à manipuler des char, car ces deux types peuvent être convertis l'un en l'autre, ce qui n'est pas le cas en Java.

 

III.      Des recopies inutiles ?

 

III.1. Passage par valeur ou par adresse ?

 

Comme le passage de paramètre se fait par défaut par valeur, donc par recopie, afficheGroupe recopie les 2 valeurs de son paramètre avant de les afficher. Si la structure comportant des dizaines de champs, ce serait coûteux.

Et si on passait la structure par adresse ? Il suffirait d’envoyer un pointeur sans avoir à recopier les valeurs.

 

III.2 Exercice III-2.c

1.      Recopiez l’exercice II-2.c dans III-2.c.

2.      Transformez afficheGroupe et son appel pour que son paramètre soit passé par adresse. Compilez/testez.

3.      Vous avez été obligés d’écrire (*PointeurDeStructure).champ car sans les parenthèses,  l’opérateur . est prioritaire par rapport à l’opérateur unaire *   
Heureusement, il existe une notation appropriée : PointeurDeStructure->champ qui désigne le champ de la structure pointée par le pointeur qui précède l’opérateur ->

4.      Modifiez votre programme pour utiliser cette nouvelle notation que nous utiliserons par la suite à chaque fois que nous accèderons à un champ à partir d’un pointeur de structure plutôt qu’à partir de la structure.

 

IV.      Des tableaux dans des structures

IV.1. Un cas fréquent : les chaînes de caractères

Parmi les champs qu’on peut mettre dans des structures, il y a fréquemment les chaines de caractères, ce qui nous conduit à manipuler des tableaux dans des structures. Il faudra un peu jongler avec les . et les [ ]
Il pourrait aussi y avoir des structures à l’intérieur des structures …

IV.2 Exercice IV-2.c

1.      Recopiez l’exercice III-2.c dans IV-2.c.

2.      Créez le type Chaine30 permettant de stocker toute chaîne d’au maximum 30 caractères utiles.
Une chaîne de caractères est-elle un tableau ou une structure ?

3.      Remplacez le type Groupe par un nouveau type Etudiant comportant les champs nom, prenom, et promo de types respectifs Chaine30, Chaine30, et entier (de 1 à 5, à vérifier).

4.      Dans le programme principal, remplacez la recopie de 2 groupes par une recopie de 2 étudiants.

5.      Modifiez le nom et adaptez afficheGroupe et saisitGroupe.

6.    La saisie des nom et prénom en utilisant scanf est loin d’être idéale puisque chaque chaîne ne peut être composée que d’un seul mot, et qu’il peut y avoir un débordement de capacité du tableau, et donc éventuellement une erreur de segmentation.           
Pour corriger ces défauts, il faut malheureusement pas mal de travail (optionnel pour l'instant, mais obligatoire si vous avez le temps à la fin du TP) décrit sur
cette longue page .

      Attention à ne pas utiliser de variables intermédiaires dans la fonction de saisie, remplissez directement la structure !

7.   On peut aussi améliorer les chaînes stockées en s’assurant que le nom est entièrement en majuscules et que le prénom est entièrement en minuscules sauf les initiales de chaque mot en majuscules.       
Comme ces fonctions n’existent pas dans
string.h, créez deux procédures strtoupper (pour le nom) et strtocap (pour le prénom) (optionnel pour l'instant, mais obligatoire si vous avez le temps à la fin du TP).

8.    En attendant pour nous entraîner, on va juste mettre en majuscule la première lettre du prénom et mettre en minuscule sa dernière lettre (vous avez le droit d’utiliser la fonction qui calcule la longueur).
Des fonctions de cette page peuvent vous être utiles.


V.        Des tableaux de structures

V.1. Rien ne nous empêche de créer des tableaux de structures :

V.2. Exercice V-2.c

1.      Recopiez l’exercice IV-2.c dans V-2.c.

2.      Ajoutez un nouveau type Esiee permettant de stocker 2222 étudiants.

3.      Remplacez la déclaration des 2 étudiants par celle d’une variable vEE de type Esiee.

4.      Remplacez les manipulations des 2 étudiants par les manipulations des 2 premiers éléments de vEE.

V.3. Exercice V-3.c

1.      Recopiez l’exercice V-2.c dans V-3.c.

2.      Ajoutez la saisie d’au moins 3 étudiants pour qu’il y en ait au moins dans les 4 premières cases du tableau (2 doivent avoir le même nom).

3.      Détection de doublons : recherchez parmi tous les étudiants que vous avez ajoutés dans le tableau, ceux qui portent le même nom, et dans ce cas affichez-les.
La fonction
strcmp pourra vous être utile (si vous lisez bien sa documentation ;-).          
 

VI.      Fin du TP

VI.1. Programmez les parties optionnelles dans le II.2 (5. et 6.) et le IV.2 (6. et 7.).

VI.2. Si ce n’est pas déjà fait, terminez les TP précédents.

VI.3. S'il reste encore un peu de temps après avoir entièrement terminé le VI.1, il est fortement conseillé de commencer le TP C4, car il comporte plus d'exercices.