TP C3   2627v1

Durée : 4h (ou 2 x 2h)

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 de certaines unités : J2, P3, A4, …

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. Celui-ci 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 vGroupe2 sous la forme indiquée ci-dessus (*) ; a-t-on bien recopié d'un coup les deux valeurs ?
Attention ! Comme on pouvait initialiser en une fois toutes les cases d'un tableau, on peut avec la même syntaxe, initialiser tous les champs d'une structure, mais avec la même contrainte : uniquement lors de la déclaration.

 

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 (après avoir affiché un message demandant une lettre) puis un nombre (après avoir affiché un message demandant un chiffre), 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.


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 (en saisissant d'abord 42).


6.      En travail personnel (ou à la fin du TP si vous avez le temps), améliorez la fonction pour qu'elle redemande le caractère tant que ce n'est pas une lettre majuscule (**).

7.      Au lieu de recopier vGroupe2 dans vGroupe1, appelez saisitGroupe pour saisir des valeurs dans les deux groupes. Compilez/testez. Un problème ?
Attention ! Apparaît ici un problème de gestion du caractère de fin de ligne '\n' qui est saisi lorsqu'on tape sur la touche ENTRÉE.
En effet, avec les 4 printf+scanf qui s'enchaînent, vous demandez à lire successivement un caractère, un entier, un caractère, un entier.
Et si vous voulez saisir par exemple J 2 P 3, vous devrez taper sur les 8 touches suivantes : 'J' '\n' '2' '\n' 'P' '\n' '3' '\n'. Donc :
  1. scanf("%c",… absorbe uniquement le caractère 'J'
  2. scanf("%d",… ignore tout ce qui n'est pas un chiffre (*) donc '\n', avant d'absorber le caractère '2' en tant qu'entier
  3. scanf("%c",… absorbe uniquement le caractère '\n'
  4. scanf("%d",… ignore tout ce qui n'est pas un chiffre (*) donc 'P' et '\n', avant d'absorber le caractère '3' en tant qu'entier
Il faut donc utiliser une variable pour stocker ce caractère '\n' « inutile ».
(*) Pour être tout à fait exact, en plus des chiffres, la lecture de nombres entiers doit aussi accepter les caractères '-' et '+'.


(**) 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'était pas le cas en Java. Comprenez-vous pourquoi et comment on peut convertir un entier en caractère et vice-versa ?

 

III.      Des recopies inutiles ?

 

III.1. Passage par valeur ou par adresse ?

 

Comme, par défaut, le passage de paramètre se fait par valeur, donc par recopie, afficheGroupe recopie les 2 valeurs de son paramètre avant de les afficher. Si la structure comportait 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 juste pour que son paramètre soit passé par adresse. Compilez/testez.
Aide : Il est normal que ce changement de type de paramètre ait provoqué des erreurs de compilation dans la procédure et lors de son appel. Corrigez à ces deux endroits et retestez.

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 plus 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.

S'il reste du temps, commencez la 2ème partie ci-dessous.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

2ème partie

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, c'est-à-dire qui pourrait par exemple stocker "phrase qui a trente caracteres".
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 lors de la saisie).


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.
Attention à ne pas utiliser de variables intermédiaires dans la fonction de saisie, remplissez directement la structure !


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 (jusqu'au titre Convertissez la chaîne en nombre).


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 (utile pour le nom) et strtocap (utile pour le prénom) (optionnel pour l'instant, mais obligatoire si vous avez le temps à la fin du TP).
Aide : Des fonctions de cette page peuvent vous être utiles.


8.    Pour nous entraîner en attendant d'avoir le temps de développer ces deux parties optionnelles, 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'autres étudiants pour qu'il y en ait au moins dans les 4 premières cases du tableau (3 doivent avoir le même nom, dont 2 aussi le même prénom, mais pas la même promo).

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 le même prénom, et dans ce cas, affichez-les.
Aide : 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 et le VI.2, il est fortement conseillé de commencer la partie I. du TP C4, car il est difficile à terminer en 4h.