SELC - Langage C : Travaux pratiques
Bertrand Dupouy
SOMMAIRE
Quelques documentations :
- Le support de cours :
ici
-
Le chapitre pointeurs du polycopié :
ici
-
Le chapitre types élaborés (struct, enum, ...) du polycopié :
ici
-
Le chapitre structures de données du polycopié :
ici
Voici ce que l'on se propose de faire dans cette suite de TP sur le langage C :
- (TP 1) lire un fichier contenant des informations sur les élèves du groupe (nom, prénom, etc)
et les ranger dans un tableau. Ces fichiers se trouvent là:
- (TP 2) puis créer, à partir de ce tableau, une liste chainée
dont chaque élément représente un élève,
- (TP 3) donner des fonctions de base pour gérer cette liste : la trier, y insérer un élément,
chercher si elle contient un élément,
- écrire une interface simple qui permette d'utiliser ces commandes de recherche et d'insertion
et aussi d'envoyer un message à un élève
Le premier TP permet de se familiariser avec le langage C en essayant quelques unes de ses fonctions d'entrées-sorties.
Il existe de très nombreuses fonctions pour faire des entrées-sorties
en langage C, dans ce premier TP nous verrons :
- comment afficher des informations à l'écran avec printf,
équivalent de System.out.println en Java,
- saisir des données depuis le clavier avec scanf,
- lire dans un fichier texte en utilisant fonctions fscanf,
- écrire dans un fichier texte en utilisant fonctions fprintf,
L'exemple utilisé sera réutilisable pour les exercices suivants.
2.1 Quelques différences entre C et Java
- Chaîne de production :
Pour passer d'un fichier source à un fichier exécutable, on utilise la commande gcc.
Elle comporte de nombreuses options (cf. man gcc), nous l'utiliserons comme suit :
pour produire, par exemple, à partir du fichier source exo1.c à l'exécutable exo1.c :
gcc -Wall exo1.c -o exo1
où:
-Wall signifie "warnings all"
-o signifie "output"
- Utilisation des arguments passés à main :
- en C le nombre de mots inclut le nom de la commande (du programme),
- en C main reçoit aussi, et en premier argument, le nombre de mots se trouvant sur la ligne de commande,
Exemple :
| Progamme Java |
Programme C |
public static void main(String[] args)
...
int arg1;
String nom;
// Verification du nombre d'arguments passes
// sur la ligne de commande, ici on en attend 2 :
if (args.length != 2) {
System.out.printlnc("Utilisation : arg1 nom");
System.exit(1);
}
arg1 = Integer.parseInt(args[0]);
nom = args[1];
...
|
int main(int argc, char* argv[]){
...
int arg1;
char nom[80];
// Verification du nombre d'arguments passes
// sur la ligne de commande, ici on en attend 3 :
if (argc != 3){
printf("Utilisation : %s arg1 nom\n", argv[0]);
return 1;
}
arg1 = atoi(argv[1]);
strcpy(nom, argv[2]);
...
|
2.2 Exercices
- Pour vous permettre de commencer, on donne un exemple complet
ici
Ce programme introduit les fonctions d'E/S; printf, scanf, fopen, fscanf
sur une exemple de lecture de mots dans un fichier texte avec leur affichage à l'écran.
Il montre également l'utilisation de #ifdef :
pour avoir la version qui initialise les paramètres sur la ligne de commande, on
utilise gcc ainsi :
gcc -DARGS -Wall exo1.c -o exo1
- Exercice 1
Exécuter ce programme pour lire le fichier contenant la liste des élèves ou tout autre fichier texte,
le source du programme, par exemple.
- Exercice 2
Reprendre le programme précédent et le modifier pour lire le fichier des élèves ligne par ligne
et le ranger dans un tableau Eleve_t tab_eleves[100] dont chaque entrée est du type :
typedef struct eleve {
char nom[30];
char prenom[20];
char login[20];
char photo[20];
int info;
} Eleve_t;
Déclaration du tableau :
Eleve_t tab_eleves[100];
Pour lire le fichier, on utilise encore, dans une boucle while, la fonction fscanf :
ret_lec = fscanf(fichier, "%s %s %s %s %d", tab_eleves[ilig].nom,
tab_eleves[ilig].prenom, tab_eleves[ilig].login,
tab_eleves[ilig].photo, &tab_eleves[ilig].info);
Cette fonction, qui renvoie un int, renvoie la valeur EOF
quand elle atteint la fin du fichier.
Après lecture du fichier, on visualisera à l'écran le contenu du tableau tab_eleves.
- Exercice 3
On va maintenant effectuer dans une fonction la lecture et le rangement des données lues dans le tableau.
Cette fonction lire_fichier qui prend en arguments le tableau et le nom du fichier va
renvoyer le nombre de lignes lues dans le fichier.
La signature (ou prototype) de cette fonction qui renvoie donc un int et
prend en argument un tableau du type Eleve_t
et une chaine de caractéres (un tableau de char) est :
int lire_fichier(Eleve_t *tab, char * nom);
Le programme aura la structure suivante :
...
/******************** main ********************/
int main(int argc, char*argv[]) {
...
printf ("nom du fichier ?\n");
scanf( "%s", nomfic);
/****** appel a la fonction *******/
ilig = lire_fichier(tab_eleves, nomfic);
/****** visualisation du tableau a l ecran *******/
/* A COMPLETER */
return 1;
}
/******************** lire_fichier ******************
* lire le fichier ligne par ligne et le ranger dans le
* tableau dont chaque entree est du type Eleve_t
********************************************************/
int lire_fichier(Eleve_t *table, char * nomfic){
/* A COMPLETER */
return ilig;
Le corrige est là .
Résumé de ce que l'on va faire :
- Lire le fichier contenant la liste des élèves et les ranger dans un tableau en reprenant
le dernier exercice du TP1.
- Créer une liste chainée à partir des informations contenues dans ce tableau,
(par insertion en début de liste, cest une pile).
Chaque élément de la liste chaînée a le type suivant :
typedef struct maillon{
Eleve_t eleve;
struct maillon * suivant;
} Maillon_t;
- Visualiser cette liste à l'écran (utiliser printf)
et l'écrire dans un fichier (utiliser fprintf),
2.2 Exercices
Il faut écrire les fonctions de création de la liste à partir du tableau.
Ces fonctions seront rangées dans un second fichier qu'on appelera gere-liste.c.
Les prototypes correspondants seront rangés dans gere-liste.h.
Les types utilisés (Eleve_t, Maillon_t) seront rangés dans tpselc_types.h
Voici la liste des fonctions à écrire :
- nouveau_maillon qui crée un nouvel élémement de la chaine à partir d'une entrée du tableau
(utilisation de malloc)
- chainer qui insère un maillon au début de la liste,
- ecrire_liste qui affiche la liste à l'écran (utilisation de printf),
- ecrire_fic_liste qui range la liste dans un fichier (utilisation de fprintf),
le nom de ce fichier sera celui du fichier d'entrée auquel on concatène ".out"
(utilisation de strcpy et strcat),
Voici leurs signatures :
Maillon_t * nouveau_maillon(Eleve_t un_eleve);
Maillon_t * chainer(Maillon_t * debut, Maillon_t * maillon_a_chainer);
int ecrire_fic_liste (Maillon_t * debut, char * nom_fichier);
void ecrire_liste(Maillon_t * debut);
Comment faire :
- On pourra partir du canevas proposé dans le fichier disponibles
ici
(gere-liste-V0.c)
et
ici
(tpselc-V0.c).
-
Consulter le paragraphe sur les piles dans le chapitre structures de données
du polycopié :
ici
- Si vous avez eu le cours sur les makefile, un exemple de fichier makefile
se trouve
là
(les fichiers de type .h sont rangés dans le répertoire include).
- Les corrigés sont
là
et là .
3.2 Exercices
On va écrire quelques fonctions gérer cette liste :
chercher si elle contient un élément,
y insérer un élément,
la trier.
On ne passera à l'écriture d'une fonction qu'après avoir testé la précédente.
On pourra faire ces tests comme dans la fonction main proposée ci-après dans le paragraphe
"Comment faire" .
- chercher qui vérifie si un nom se trouve dans la liste et renvoie
l'adresse de l'élément correspondant, ou NULL si aucun ne correspond
(utiliser strcmp()).
- inserer qui insère un maillon dans la liste supposée triée et renvoie le début de la liste,
- trier qui trie la liste en la copiant dans une nouvelle liste.
Le tri se fera sur le nom et de la façon suivante :
- Prendre un à un les maillons de la liste chainee de debut "debut" pour
construire une nouvelle liste chaînee triee en utilisant inserer.
- Renvoyer le debut de la nouvelle liste triee.
- liberer qui rend la memoire occupee par une liste chainee.
Cette fonction ne renvoie rien.
Voici leurs signatures :
Maillon_t * chercher (Maillon_t * debut, char *nom);
Maillon_t * inserer(Maillon_t * debut, Maillon_t * maillon_a_inserer);
Maillon_t * trier(Maillon_t * debut);
void liberer(Maillon_t *debut);
Comment faire :
On va maintenant écrire une interface qui présente un menu à l'utilisateur.
Ce menu va permettre d'envoyer des commandes au programme, ces commandes sont :
- chercher, (search) qui demande le nom d'un élève, appelle la fonction
chercher et, si ce nom est dans la liste, affiche les inforamtions associées à ce nom,
- lister, (list) qui appelle la fonction ecrire_liste,
- (facultatif) envoyer, (send) qui demande le nom d'un élève, appelle la fonction
chercher et crée dans /tmp un fichier contenant un message pour l'élève en question,
- fin, (stop) qui termine le programme.
La structure de données utilisée est :
typedef struct commande {
char nom [80];
char name[80];
int fonc;
} Commande_t;
Elle sera initialisée comme suit :
Commande_t Comm []= {
{"chercher", "search", 1},
{"lister", "list", 2},
{"envoyer", "send", 3},
{"fin", "stop", 4},
};
Le canevas du programme d'interrogation sera du type :
while (1) {
printf ("Commandes disponibles : Available commands :\n");
for (Compteur=0; Compteur < NBRE_COMMANDES; Compteur++)
printf(" %s ", Comm[Compteur].nom);
scanf ("%s", Une_comm);
/* Verifier si la commande est bien dans la liste */
....
/* si la commande existe appeler la fonction correspondante
en utilisant un switch */
switch (Comm[Compteur].fonc) {
case 1 :
...
...
...
case 4 : /* fin */
return 1;
}
Pour traiter la fonction facultative :
- Pour créer le fichier on utilisera la commande :
/bin/echo bonjour > /tmp/FICHIER
où FICHIER sera le nom du fichier construit a partir du nom de l'eleve.
Si celui-ci est NOM, ce nom sera "pour_NOM".
- pour exécuter cette commande, on construira la commande dans une chaine de caractères
ma_commande et
on appelera la fonction system(ma_commande),
Un corrigé est ici.