Exercices en langage C/Fonctions
Ces exercices concernent l'utilisation des fonctions de la bibliothèque standard du langage C.
Filtre qui passe le texte en majuscule Modifier
Problème à résoudre Modifier
Écrivez un programme majuscule.c qui lit des données sur le flux stdin et écrits sur stdout après avoir transformé les caractères lus en majuscules. Vous utiliserez les fonctions getchar
, putchar
(stdio.h) et toupper
(ctype.h).
Vous testerez votre programme en lui faisant convertir son propre fichier source majuscule.c.
- majuscule.exe < majuscule.c
Solution proposée Modifier
/**
* Programme . : majuscule
* Rôle ...... : convertit les caractères lus sur stdin en majuscule.
* Paramètres : Aucun
* Compilation et EDL : gcc -Wall -o majuscule.exe majuscule.c
* Auteur ...... : Thierry46
* Version ..... : 1.0 du 4/2/2008
* Licence ..... : GNU GPL.
*/
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
int main(void)
{
int c;
while ((c=getchar()) != EOF)
{
(void)putchar(toupper(c));
}
return EXIT_SUCCESS;
}
Lire une ligne longue avec fgets Modifier
Problème à résoudre Modifier
La fonction fgets
de la bibliothèque standard du langage C permet de lire une chaîne de caractère de longueur limitée dans un flux.
Vous allez compléter une fonction lire_ligne répondant au spécifications suivantes :
- Retour d'une ligne lue dans un flux texte passé en paramètre.
- Vous éliminerez les caractères de saut de ligne lus.
- La longueur des lignes lues n'est pas limitée.
- Contrôle des paramètres et retour des codes d'erreurs systèmes, détection de la fin du fichier.
- Vous utiliserez au maximum les fonctions de la bibliothèque standard du langage C : allocation mémoire, chaînes de caractères...
- Son prototype est donné par lire_ligne.h.
- Vous utiliserez le programme de main_lire_ligne.c pour lire_ligne.
- Vous devrez traiter le fichier test_lire_ligne.txt fourni.
- Les instructions de compilation et d'édition de lien sont dans les commentaires des fichiers fournis.
Éléments fournis Modifier
/**
* Fonction .. : lire_ligne
* Rôle ...... : tente de lire une ligne entière depuis le flux.
* - Le caractère de saut de ligne '\n' final est enlève.
* - L’appelant doit se charger de la désallocation de la chaîne retournée.
*
* Paramètres :
* - pChaine : adresse de retour de la chaîne
* - tailleBufferLecture : taille du buffer de lecture,
* dans la pratique on pourra choisir _POSIX_MAX_INPUT (ref limits.h).
* - flux : un pointeur sur le descripteur du fichier a lire.
*
* - Valeur retournée :
* - Si OK : EXIT_SUCCESS.
* - Si Fin de fichier atteinte sans rencontrer \n : EOF,
* - la chaîne retournée contient le début de la ligne longue.
* - Si rien a lire, retour d'un pointeur NULL pour la chaîne.
* - En cas de problème : Le code d'erreur système utilisable par perror.
* et retour d'un pointeur NULL pour la chaîne.
*
* Auteur ...... : Votre nom
* Version ..... : 1.0 du date
* Licence ..... : GNU GPL.
lire_ligne : Lecture d'une ligne longue dans un fichier texte
Copyright (C) 2008 Thierry46
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Compilation . : gcc -pedantic -Wall -std=c99 -c lire_ligne.c
* Fichiers lies : main_lire_ligne.c, lire_ligne.h
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <errno.h>
#include "lire_ligne.h"
/*@null@*/ char *lire_ligne(size_t tailleBufferLecture,
FILE * restrict flux, int *pCodeRetour)
{
// Début partie à compléter...
// Fin partie à compléter...
} // char *lire_ligne(...
/*
* Nom .... : lire_ligne.h
* Rôle ... : Définitions et prototype de lire_ligne.h
* Auteur ...... : Thierry46
* Version ..... : 1.0 du 30/1/2008
* Licence ..... : GNU GPL.
lire_ligne : Lecture d'une ligne longue dans un fichier texte
Copyright (C) 2008 Thierry46
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Fichier lie : lire_ligne.c
*/
#ifndef LIRE_LIGNE_H
#define LIRE_LIGNE_H
extern /*@null@*/ char *lire_ligne(size_t tailleBufferLecture,
FILE * restrict flux, int *pCodeRetour);
#endif
/*
* Nom ......... : main_lire_ligne.c
* Rôle ........ : Tester la fonction lire_ligne
* Paramètre ... : Aucun
* Code retour . : EXIT_SUCESSS (0)
* Auteur ...... : Thierry46
* Version ..... : 1.1 du 4/2/2008
* Licence ..... : GNU GPL.
lire_ligne : Lecture d'une ligne longue dans un fichier texte
Copyright (C) 2008 Thierry46
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Compilation . :
* Voir lire_ligne .c pour sa compilation
* gcc -pedantic -Wall -std=c99 -o test_lire_ligne.exe main_lire_ligne.c lire_ligne.o
* Exécution : ./test_lire_ligne.exe
* Fichiers lies : lire_ligne.c, lire_ligne.h, test_lire_ligne.txt
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lire_ligne.h"
#define FIC_TEST "test_lire_ligne.txt"
// Valeur faible de TAILLE_BUFFER pour test débordements et lecture multiple.
#define TAILLE_BUFFER (size_t)10
#define TAILLE_MAX_MESSAGE 256
int main(void)
{
FILE *hFicTest = NULL;
char *chaine = NULL;
int numLigne = 1;
int retour = EXIT_SUCCESS;
char message[TAILLE_MAX_MESSAGE];
hFicTest = fopen(FIC_TEST, "r");
if (hFicTest == NULL)
{
(void)printf("%s : le fichier %s n'a pas pu être ouvert !\n",
__func__, FIC_TEST);
// Je laisse poursuivre pour tester assertion dans lire_ligne. //
}
do
{
chaine = lire_ligne(TAILLE_BUFFER, hFicTest, &retour);
if (retour == EXIT_SUCCESS)
{
(void)strncpy(message, "Pas d'erreur", sizeof(message) - 1);
}
else if (retour == EOF)
{
(void)strncpy(message, "Fin de fichier", sizeof(message) - 1);
}
else
{
(void)strncpy(message, strerror(retour), sizeof(message) - 1);
}
message[sizeof(message) - 1] = '\0';
(void)printf("Ligne %d : retour = %d (%s), chaîne lue = \"%s\".\n",
numLigne, retour, message,
(chaine == NULL) ? "! Pointeur NULL !" : chaine);
free(chaine);
chaine = NULL;
} while (retour == EXIT_SUCCESS);
if (hFicTest != NULL)
{
(void)fclose(hFicTest);
}
return EXIT_SUCCESS;
} // int main(void)
1 1234567880 ligne longue. 12345678901234567890123456789012345678901234567890 fin
Solution proposée Modifier
/**
* Fonction .. : lire_ligne
* Rôle ...... : tente de lire une ligne entière depuis le flux.
* - Le caractère de saut de ligne '\n' final est enleve.
* - L’appelant doit se charger de la désallocation de la chaîne retournée.
*
* Paramètres :
* - pChaine : adresse de retour de la chaîne
* - tailleBufferLecture : taille du buffer de lecture,
* dans la pratique on pourra choisir _POSIX_MAX_INPUT (ref limits.h).
* - flux : un pointeur sur le descripteur du fichier a lire.
*
* - Valeur retournée :
* - Si OK : EXIT_SUCCESS.
* - Si Fin de fichier atteinte sans rencontrer \n : EOF,
* - la chaîne retournée contient le début de la ligne longue.
* - Si rien a lire, retour d'un pointeur NULL pour la chaîne.
* - En cas de problème : Le code d'erreur système utilisable
* par perror et retour d'un pointeur NULL pour la chaîne.
*
* Auteur ...... : Thierry46
* Version ..... : 1.1 du 4/2/2008
* Licence ..... : GNU GPL.
lire_ligne : Lecture d'une ligne longue dans un fichier texte
Copyright (C) 2008 Thierry46
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Compilation . : gcc -pedantic -Wall -std=c99 -c lire_ligne.c
* Fichiers lies : main_lire_ligne.c, lire_ligne.h
*
* Remarques :
* - Beaucoup de commentaires sont présents pour un usage didactique.
* - Ce programme utilise une syntaxe specifique C99.
* - Ce programme a ete controle avec l'outil de verification statique
* - Splint v3.1.2 du 19 Jan 2008 : <http://www.splint.org/>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <errno.h>
#include "lire_ligne.h"
/*@null@*/ char *lire_ligne(size_t tailleBufferLecture,
FILE * restrict flux, int *pCodeRetour)
{
// Declarations
char *chaineRetour = NULL;
bool litEncore = true;
bool rienLu = true;
int codeRetour = EXIT_SUCCESS;
// Controle rapide des parametres.
// Si quelque chose ne va pas, arret.
assert(tailleBufferLecture > 0);
assert(flux != NULL);
// On reinitialise errno
errno = 0;
// Boucle de lecture d'une ligne eventuellement longue
do
{
size_t positionEcriture;
char *nouvelleChaine;
litEncore = false;
// Demande d'une nouvelle zone memoire
positionEcriture =
(chaineRetour == NULL) ? 0 : strlen(chaineRetour);
nouvelleChaine = realloc(chaineRetour,
positionEcriture + tailleBufferLecture);
if (nouvelleChaine == NULL) // Si probleme d'allocation
{
codeRetour = errno;
free(chaineRetour);
chaineRetour = NULL;
}
else // Si le systeme a accorde la memoire
{
chaineRetour = nouvelleChaine;
if (fgets(chaineRetour+positionEcriture,
(int)tailleBufferLecture, flux)
!= NULL)
{
// Recherche si un \n a ete lu en fin de chaine.
char *positionNewLine = strrchr(chaineRetour, '\n');
rienLu = false;
if (positionNewLine != NULL)
{
// Suppression du caractere de fin de ligne \n,
*positionNewLine = '\0';
}
else // fgets n'a pas pu lire la ligne complete.
{
litEncore = true;
}
} else if (ferror(flux) != 0)
{
codeRetour = errno;
free(chaineRetour);
chaineRetour = NULL;
} else if (feof(flux) != 0)
{
codeRetour = EOF;
if (rienLu)
{
free(chaineRetour);
chaineRetour = NULL;
}
}
} // else if (nouvelleChaine == NULL)
} while (litEncore);
// Retour des resultats.
*pCodeRetour = codeRetour;
return chaineRetour;
} // char *lire_ligne(...
Remarques sur l'exercice Modifier
- Le test des paramètres dans la solution est expéditif.
- Pour obtenir des programmes robustes, le langage C oblige à une gestion pénible des erreurs. Avec le langage Java par exemple, les mécanismes d'exception facilitent la tâche du programmeur.
- L'utilisation de l'allocation dynamique de mémoire est risquée : fuite mémoire. Avec le langage Java par exemple, le ramasse miettes (Garbage collector) se charge de la libération de la mémoire.
- En Java des classes comme String et StringBuffer prennent en charge les chaines de caractères longues.
Test d'un générateur de nombre aléatoire Modifier
Problème à résoudre Modifier
La bibliothèque standard du langage C offre au programmeur plusieurs fonctions pour générer des nombres aléatoires. La plus connue est rand()
.
Vous allez écrire un programme verifrand.c qui estime la répartition des nombres aléatoires générés : moyenne et dispersion. Nous allons traiter l'ensemble des nombres générés comme une série discrète regroupée.
- Générez 1 million (NB_TIRAGE) notes (xi) entre 0 et 20 (N) comprises (NB_NOTE = 21) à l'aide de la fonction
rand()
. - Répartissez-les dans le tableau effectif (n) où ni représente l'effectif (nombre d'occurrences cumulées) de la note xi.
- calculez et affichez la moyenne arithmétique :
- calculez et affichez l'écart moyen de la série :
Solution proposée Modifier
/*
Nom : verifrand.c
Auteur : Thierry46
Role : Verifier generateur de nombre aleatoire
Licence : GNU GPL
Version : 1.0 du 17/2/2008
Compilation : gcc -Wall -pedantic -std=c99 -o verifrand.exe verifrand.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
// NB_NOTE est appele N dans les formules
// 20 notes : de 0 à 20 inclus
#define NB_NOTE 21
#define NB_TIRAGE 1000000UL
int main(void)
{
unsigned long effectif[NB_NOTE];// appele n dans les formules
unsigned short note; // appelee i dans les formules, ici xi = i
double moyenne; // appelee x barre dans les formules
double ecartMoyen;
unsigned long numTirage;
unsigned short noteLaMoinsGeneree;
unsigned short noteLaPlusGeneree;
// Init a 0 du tableau des nombres d'occurence
for (note=0; note<NB_NOTE; note++)
{
effectif[note] = 0;
}
// Generation des nombres aleatoires
(void)printf("Generation et repartition %lu notes dans tableau effectif.\n",
NB_TIRAGE);
for (numTirage=0; numTirage<NB_TIRAGE; numTirage++)
{
effectif[rand()%NB_NOTE]++;
}
// Determination des notes les moins et les plus generees
for (note=1, noteLaMoinsGeneree=0, noteLaPlusGeneree=0; note<NB_NOTE; note++)
{
if (effectif[note] < effectif[noteLaMoinsGeneree])
{
noteLaMoinsGeneree = note;
}
if (effectif[note] > effectif[noteLaPlusGeneree])
{
noteLaPlusGeneree = note;
}
}
(void)printf("Note la moins generee : %hu (%lu fois)\n"
"Note la plus generee : %hu (%lu fois)\n",
noteLaMoinsGeneree, effectif[noteLaMoinsGeneree],
noteLaPlusGeneree, effectif[noteLaPlusGeneree]);
// Calcul de la moyenne de la série
for (note=0, moyenne=0.0; note<NB_NOTE; note++)
{
moyenne += (double)effectif[note]*(double)note;
}
moyenne /= (double)NB_TIRAGE;
(void)printf("La moyenne = %lf\n", moyenne);
// Estimation de l'écart moyen de la série
for (note=0, ecartMoyen=0; note<NB_NOTE; note++)
{
ecartMoyen += (double)effectif[note]*fabs((double)note - moyenne);
}
ecartMoyen /= (double)NB_TIRAGE;
(void)printf("ecart moyen = %lf\n", ecartMoyen);
return EXIT_SUCCESS;
} // int main(void)
Résultats obtenus :
MacMini-TM:~/Documents/c thierry$ ./verifrand.exe Generation et repartition des 1000000 notes dans tableau effectif. Note la moins generee : 14 (47263 fois) Note la plus generee : 6 (48047 fois) La moyenne = 9.991110 ecart moyen = 5.236995
Remarques sur l'exercice Modifier
- La série générée avec
rand()
sur ma machine est bien centrée sur la moyenne théorique 10/20 : les nombres de notes en dessous et au dessus de 10 sont quasi-identiques. L'écart moyen de la série est proche de 5 ce qui indique que les notes sont bien réparties. - La fonction
rand()
est pseudo-aléatoire : deux exécutions du programme donnent exactement la même série de note. - Vous pouvez tester d'autres fonctions comme
random()
ouarc4random()
. Cette dernière, de qualité cryptographique, utilise l'algorithme nommé ARC4. Elle est aléatoire.