TP C3
2425
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();
public class Groupe { public char lettre; public int chiffre; }
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 (*).
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 * 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 [ ]
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. 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. 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. 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).
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.
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.
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.
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 ->
Il pourrait aussi y avoir des structures à l’intérieur des structures …
Une chaîne de caractères est-elle un tableau ou une structure ?
Pour corriger ces défauts, il faut malheureusement pas mal de travail
(optionnel
si vous avez le temps à la fin du TP) décrit sur cette longue page .
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).
Les deux fonctions de cette
page
peuvent vous être utiles.
La fonction strcmp pourra vous être utile
(si vous lisez bien sa documentation ;-).