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
modifierProblè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
modifierProblème à résoudre
modifierLa 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
modifierProblème à résoudre
modifierLa 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.