Section courante

A propos

Section administrative du site

Entrée et sortie

Les fonctions d'entrée et de sortie ne font pas partie du langage C lui-même. Mais le langage de programmation C contient une bibliothèque standard C avec un ensemble de fonctions fournissant des entrées et des sorties, la gestion des chaînes de caractères, la gestion d'entreposage, des routines mathématiques et une série d'autres services pour les programmes écrit en langage de programmation C. Pour les entrées et les sorties, la norme ANSI définit des fonctions de bibliothèque avec précision, afin qu'elles puissent exister sous une forme compatible sur tout système où C existe. Les programmes confient leurs interactions système aux installations fournies par la bibliothèque standard C pouvant être déplacés d'un système à un autre sans changement. Les propriétés des fonctions de bibliothèque sont spécifiées dans plus d'une douzaine d'entêtes; nous en avons déjà vu plusieurs, dont <stdio.h>, <string.h> et <ctype.h>.

La fonction printf est bien connus pour afficher la sortie formatée sur la sortie standard (où qu'elle se trouve) et la fonction getchar pour lire des caractères uniques à partir de l'entrée standard, ainsi que putchar pour écrire des caractères uniques dans la sortie standard. Toutefois, l'entrée standard et la sortie standard sont deux flux d'entrée/sortie prédéfinis étant implicitement disponible pour tous.

Entrée et sortie standard

Comme nous l'avons dit dans la page Les premiers pas, la bibliothèque implémente un modèle simple d'entrée et de sortie de texte. Un flux de texte se compose d'une séquence de lignes ; chaque ligne se termine par un caractère de nouvelle ligne. Si le système ne fonctionne pas de cette façon, la bibliothèque fait tout ce qui est nécessaire pour faire en sorte qu'il apparaisse comme tel. Par exemple, la bibliothèque peut convertir le retour chariot et le saut de ligne en nouvelle ligne à l'entrée et inversement à la sortie.

Le mécanisme d'entrée le plus simple consiste à lire un caractère à la fois à partir de l'entrée standard, normalement le clavier, avec getchar :

  1. int getchar(void)

getchar renvoie le caractère d'entrée suivant à chaque fois qu'il est appelé, ou EOF lorsqu'il rencontre la fin du fichier. La constante symbolique EOF est définie dans <stdio.h>. La valeur est généralement -1, les tests de bus doivent être écrits en termes d'EOF afin d'être indépendants de la valeur spécifique.

Dans de nombreux environnements, un fichier peut être substitué au clavier en utilisant la convention < pour la redirection d'entrée : si un programme prog utilise getchar, alors la ligne de commande :

prog < infile

oblige prog à lire les caractères du fichier infile à la place. La commutation de l'entrée est effectuée de telle manière que prog lui-même ne se rend pas compte du changement ; en particulier, la chaîne de caractères "<infile" n'est pas incluse dans les paramètres de la ligne de commande dans argv. La commutation d'entrée est également invisible si l'entrée provient d'un autre programme via un mécanisme de pipe : sur certains systèmes, la ligne de commande :

otherprog | prog

exécute les deux programmes otherprog et prog, et redirige la sortie standard de otherprog vers l'entrée standard de prog.

La fonction :

  1. int putchar(int)

est utilisé pour la sortie : putchar(c) place le caractère c sur la sortie standard, étant par défaut l'écran. putchar renvoie le caractère écrit, ou EOF si une erreur se produit. Là encore, la sortie peut généralement être dirigée vers un fichier avec >filename: si prog utilise putchar :

prog >outfile

écrira la sortie standard dans outfile à la place. Si les pipes sont pris en charge :

prog | anotherprog

place la sortie standard de prog dans l'entrée standard d'un autre prog.

La sortie produite par printf trouve également son chemin vers la sortie standard. Les appels à putchar et printf peuvent être entrelacés - la sortie se produit dans l'ordre dans lequel les appels sont effectués.

Chaque fichier source faisant référence à une fonction de bibliothèque d'entrée/sortie doit contenir la ligne :

  1. #include <stdio.h>

avant la première référence. Lorsque le nom est entouré de < et >, une recherche de l'entête est effectuée dans un ensemble standard d'emplacements (par exemple, sur les systèmes UNIX, généralement dans le répertoire /usr/include).

De nombreux programmes ne lisent qu'un seul flux d'entrée et n'écrivent qu'un seul flux de sortie ; pour de tels programmes, l'entrée et la sortie avec getchar, putchar et printf peuvent être tout à fait adéquates, et sont certainement suffisantes pour commencer. Cela est particulièrement vrai si la redirection est utilisée pour connecter la sortie d'un programme à l'entrée du suivant. Par exemple, considérons le programme lower, convertissant son entrée en minuscules :

  1. #include <stdio.h>
  2. #include <ctype.h>
  3.  
  4. main() { /* lower: convertir l'entrée en minuscules */
  5.  int c
  6.  while ((c = getchar()) != EOF) putchar(tolower(c));
  7.  return 0;
  8. }    

La fonction tolower est définie dans <ctype.h> ; elle convertit une lettre majuscule en minuscule et renvoie les autres caractères intacts. Comme nous l'avons mentionné précédemment, les «fonctions» comme getchar et putchar dans <stdio.h> et tolower dans <ctype.h> sont souvent des macros, évitant ainsi la surcharge d'un appel de fonction par caractère. Quelle que soit la manière dont les fonctions <ctype.h> sont implémentées sur une machine donnée, les programmes les utilisant sont protégés de la connaissance de l'ensemble de caractères.

Sortie formatée - printf

La fonction de sortie printf traduit les valeurs internes en caractères. La description ici couvre la plupart des utilisations courantes mais n'est pas complète :

  1. int printf(char *format, arg1, arg2, ...);    

printf convertit, formate et affiche ses paramètres sur la sortie standard sous le contrôle du format. Il renvoie le nombre de caractères affichés.

La chaîne de caractères format contient deux types d'objets : des caractères ordinaires, étant copiés dans le flux de sortie, et des spécifications de conversion, chacune d'elles provoquant la conversion et l'affichage de paramètre successif suivant de printf. Chaque spécification de conversion commence par un % et se termine par un caractère de conversion. Entre le % et le caractère de conversion, il peut y avoir, dans l'ordre :

Les caractères de conversion sont indiqués dans le tableau suivant. Si le caractère après le % n'est pas une spécification de conversion, le comportement est indéfini.

Caractère Type de paramètre ; Affiché comme
d,i int; nombre décimal
o int; nombre octal non signé (sans zéro non significatif)
x,X int; nombre octal non signé (sans zéro non significatif) nombre hexadécimal non signé (sans 0x ou 0X non significatif), en utilisant abcdef ou ABCDEF pour 10, ..., 15.
u int; nombre décimal non signé
c int; caractère unique
s char *; affiche les caractères de la chaîne de caractères jusqu'à un '\0' ou le nombre de caractères donné par la précision.
f double; [-]m.dddddd, où le nombre de d est donné par la précision (par défaut 6).
e,E double; [-]m.dddddde+/-xx ou [-]m.ddddddE+/-xx, où le nombre de d est donné par la précision (par défaut 6).
g,G double; utilisez %e ou %E si l'exposant est inférieur à -4 ou supérieur ou égal à la précision ; sinon utilisez %f. Les zéros et le point décimal de fin ne sont pas affichés.
p void *; pointeur (représentation dépendante de l'implémentation)
% aucun paramètre n'est converti ; affiche un %

Une largeur ou une précision peut être spécifiée sous la forme *, auquel cas la valeur est calculée en convertissant le paramètre suivant (devant être un int). Par exemple, pour afficher au plus max caractères d'une chaîne de caractères s :

  1. printf("%.*s", max, s);

La plupart des conversions de format ont été illustrées dans les pages précédents. Une exception est la précision relative aux chaînes de caractères. Le tableau suivant montre l'effet de diverses spécifications sur l'affichage de «bonjour le monde» (16 caractères). Nous avons placé des deux points autour de chaque champ afin que vous puissiez voir son étendue :

:%s:          :bonjour le monde:
:%10s:        :bonjour le monde:
:%.10s:       :bonjour le:
:%-10s:       :bonjour le monde:
:%.17s:       :bonjour le monde:
:%-17s:       :bonjour le monde :
:%15.10s:     :    bonjour le:
:%-15.10s:    :bonjour le :

Attention : printf utilise son premier paramètre pour décider du nombre de paramètres suivant et de leur type. Il y aura confusion et vous obtiendrez de mauvaises réponses s'il n'y a pas assez de paramètres ou s'ils ne sont pas du bon type. Vous devez également être conscient de la différence entre ces deux appels :

  1. printf(s); /* ÉCHEC si s contient % */
  2. printf("%s", s); /* SÛRE */    

La fonction sprintf effectue les mêmes conversions que printf, mais entrepose la sortie dans une chaîne de caractères :

  1. int sprintf(char *string, char *format, arg1, arg2, ...);

sprintf formate les paramètres dans arg1, arg2,..., selon le format comme précédemment, mais place le résultat dans une chaîne de caractères au lieu de la sortie standard ; la chaîne de caractères doit être suffisamment grande pour recevoir le résultat.

Listes de paramètres de longueur variable

Cette section contient une implémentation d'une version minimale de printf, pour montrer comment écrire une fonction traitant une liste de paramètre de longueur variable de manière portable. Comme nous nous intéressons principalement au traitement des paramètres, minprintf traitera la chaîne de caractères de format et les paramètres, mais appellera le vrai printf pour effectuer les conversions de format.

La déclaration appropriée pour printf est :

  1. int printf(char *fmt, ...)

où la déclaration ... signifie que le nombre et les types de ces paramètres peuvent varier. La déclaration ... ne peut apparaître qu'à la fin d'une liste de paramètres. Notre minprintf est déclaré comme :

  1. void minprintf(char *fmt, ...)    

car nous ne renverrons pas le nombre de caractères que printf fait.

Le point délicat est la façon dont minprintf parcourt la liste de paramètres alors que la liste n'a même pas de nom. L'entête standard <stdarg.h> contient un ensemble de définitions de macros définissant comment parcourir une liste de paramètres. L'implémentation de cet entête varie d'une machine à l'autre, mais l'interface qu'il présente est uniforme.

Le type va_list est utilisé pour déclarer une variable faisant référence à chaque paramètre à tour de rôle ; dans minprintf, cette variable est appelée ap, pour «argument pointer». La macro va_start initialise ap pour pointer vers le premier paramètre sans nom. Elle doit être appelée une fois avant que ap ne soit utilisée. Il doit y avoir au moins un paramètre nommé ; le dernier paramètre nommé est utilisé par va_start pour démarrer.

Chaque appel de va_arg renvoie un paramètre et fait passer ap au suivant ; va_arg utilise un nom de type pour déterminer le type à renvoyer et la taille du pas à franchir. Enfin, va_end fait tout le nettoyage nécessaire. Il doit être appelé avant le retour du programme.

Ces propriétés constituent la base de notre printf simplifié :

  1. #include <stdarg.h>
  2.  
  3. /* minprintf: printf minimal avec liste de paramètres variable */
  4. void minprintf(char *fmt, ...) {
  5.  va_list ap; /* pointe vers chaque paramètre sans nom à tour de rôle */
  6.  char *p, *sval;
  7.  int ival;
  8.  double dval;
  9.  
  10.  va_start(ap, fmt); /* faire pointer ap vers le 1er paramètre sans nom */
  11.  for (p = fmt; *p; p++) {
  12.   if (*p != '%') {
  13.    putchar(*p);
  14.    continue;
  15.   }
  16.   switch (*++p) {
  17.    case 'd':
  18.     ival = va_arg(ap, int);
  19.     printf("%d", ival);
  20.     break;
  21.    case 'f':
  22.     dval = va_arg(ap, double);
  23.     printf("%f", dval);
  24.     break;
  25.    case 's':
  26.     for (sval = va_arg(ap, char *); *sval; sval++)
  27.     putchar(*sval);
  28.     break;
  29.    default:
  30.     putchar(*p);
  31.     break;
  32.   }
  33.  }
  34.  va_end(ap); /* nettoyer une fois terminé */
  35. }    

Entrée formatée - scanf

La fonction scanf est l'analogue d'entrée de printf, offrant de nombreuses fonctions de conversion identiques dans la direction opposée :

  1. int scanf(char *format, ...)

scanf lit les caractères de l'entrée standard, les interprète selon la spécification dans format et entrepose les résultats via les paramètres restants. Le paramètre format est décrit ci-dessous ; les autres paramètres, devant chacun être un pointeur, indiquent où l'entrée convertie correspondante doit être entreposée. Comme avec printf, cette section est un résumé des fonctionnalités les plus utiles, pas une liste exhaustive.

scanf s'arrête lorsqu'il épuise sa chaîne de caractères de format, ou lorsqu'une entrée ne correspond pas à la spécification de contrôle. Il renvoie comme valeur le nombre d'éléments d'entrée correctement mis en correspondance et attribués. Cela peut être utilisé pour décider du nombre d'éléments trouvés. À la fin du fichier, EOF est renvoyé ; notez que cela est différent de 0, ce qui signifie que le caractère d'entrée suivant ne correspond pas à la première spécification dans la chaîne de caractères de format. L'appel suivant à scanf reprend la recherche immédiatement après le dernier caractère déjà converti.

Il existe également une fonction sscanf lisant à partir d'une chaîne de caractères au lieu de l'entrée standard :

  1. int sscanf(char *string, char *format, arg1, arg2, ...)

Il analyse la chaîne de caractères selon le format dans format et entrepose les valeurs résultantes via arg1, arg2,... Ces paramètres doivent être des pointeurs.

La chaîne de caractères de format contient généralement des spécifications de conversion, étant utilisées pour contrôler la conversion de l'entrée. La chaîne de caractères de format peut contenir :

Une spécification de conversion dirige la conversion du champ d'entrée suivant. Normalement, le résultat est placé dans la variable pointée par le paramètre correspondant. Si la suppression d'affectation est indiquée par le caractère *, cependant, le champ d'entrée est ignoré ; aucune affectation n'est effectuée. Un champ d'entrée est défini comme une chaîne de caractères non blancs ; il s'étend soit jusqu'au caractère blanc suivant, soit jusqu'à ce que la largeur du champ spécifiée soit épuisée. Cela implique que scanf lira au-delà des limites pour trouver son entrée, puisque les nouvelles lignes sont des espaces blancs. (Les caractères blancs sont les espaces vides, la tabulation, la nouvelle ligne, le retour chariot, la tabulation verticale et le saut de page.)

Le caractère de conversion indique l'interprétation du champ d'entrée. L'argument correspondant doit être un pointeur, comme l'exige la sémantique d'appel par valeur de C. Les caractères de conversion sont indiqués dans le tableau suivant :

Caractère Données d'entrée ; type de paramètre
d entier décimal; int *
i entier; int *. L'entier peut être au format octal (0 en tête) ou hexadécimal (0x ou 0X en tête).
o entier octal (avec ou sans zéro non significatif) ; int *
u entier décimal non signé ; unsigned int *
x entier hexadécimal (avec ou sans 0x ou 0X au début); int *
c caractères ; char *. Les caractères d'entrée suivants (par défaut 1) sont placés à l'emplacement indiqué. L'espace blanc de saut normal est supprimé ; pour lire le prochain caractère non blanc, utilisez %1s
s chaîne de caractères (sans guillemets) ; char *, pointant vers un tableau de caractères suffisamment long pour la chaîne de caractères et un '\0' de fin étant ajouté.
e,f,g nombre à virgule flottante avec signe facultatif, point décimal facultatif et exposant facultatif ; float *
% littéral %; aucune affectation n'est effectuée

Les caractères de conversion d, i, o, u et x peuvent être précédés de h pour indiquer qu'un pointeur vers short plutôt que int apparaît dans la liste des paramètres, ou de l pour indiquer qu'un pointeur vers long apparaît dans la liste des paramètres.

Comme premier exemple, une calculatrice rudimentaire peut être écrite avec scanf pour effectuer la conversion d'entrée :

  1. #include <stdio.h>
  2.  
  3. main() { /* calculatrice rudimentaire */
  4.  double sum, v;
  5.  sum = 0;
  6.  while (scanf("%lf", &v) == 1) printf("\t%.2f\n", sum += v);
  7.  return 0;
  8. }

Supposons que nous voulions lire des lignes d'entrée contenant des dates de la forme :

16 Feb 1974

L'instruction scanf est :

  1. int day, year;
  2. char monthname[20];
  3. scanf("%d %s %d", &day, monthname, &year);

Non, & n'est pas utilisé avec le nom du mois, car un nom de tableau est un pointeur.

Des caractères littéraux peuvent apparaître dans la chaîne de caractères de format scanf ; ils doivent correspondre aux mêmes caractères dans l'entrée. Nous pourrions donc lire des dates au format mm/jj/aa avec l'instruction scanf :

  1. int day, month, year;
  2. scanf("%d/%d/%d", &month, &day, &year);

scanf ignore les espaces et les tabulations dans sa chaîne de caractères de format. De plus, il ignore les espaces blancs (espaces, tabulations, sauts de ligne,...) lorsqu'il recherche des valeurs d'entrée. Pour lire une entrée dont le format n'est pas fixe, il est souvent préférable de lire une ligne à la fois, puis de la décortiquer avec scanf. Par exemple, supposons que nous voulions lire des lignes pouvant contenir une date sous l'une des formes ci-dessus. Nous pourrions alors écrire :

  1. while (getline(line, sizeof(line)) > 0) {
  2.  if (sscanf(line, "%d %s %d", &day, monthname, &year) == 3) printf("valide: %s\n", line); else /* Format 16 Feb 1974 */
  3.  if (sscanf(line, "%d/%d/%d", &month, &day, &year) == 3) printf("valide: %s\n", line); /* Format mm/dd/aa */
  4.                                                     else printf("invalide: %s\n", line); /* Format invalide */
  5. }

Les appels à scanf peuvent être mélangés avec des appels à d'autres fonctions d'entrée. Le prochain appel à une fonction d'entrée commencera par lire le premier caractère non lu par scanf.

Un dernier avertissement : les paramètres de scanf et sscanf doivent être des pointeurs. L'erreur la plus courante est de loin celle consistant à écrire :

  1. scanf("%d", n);

au lieu de :

  1. scanf("%d", &n);

Cette erreur n'est généralement pas détectée au moment de la compilation.

Accès aux fichiers

Les exemples jusqu'à présent ont tous lu l'entrée standard et écrit la sortie standard, étant automatiquement définies pour un programme par le système d'exploitation local.

L'étape suivante consiste à écrire un programme accédant à un fichier n'étant pas déjà connecté au programme. Un programme illustrant la nécessité de telles opérations est cat, concaténant un ensemble de fichiers nommés dans la sortie standard. cat est utilisé pour afficher des fichiers à l'écran et comme collecteur d'entrée à usage général pour les programmes n'ayant pas la capacité d'accéder aux fichiers par leur nom. Par exemple, la commande :

cat x.c y.c

affiche le contenu des fichiers x.c et y.c (et rien d'autre) sur la sortie standard.

La question est de savoir comment organiser la lecture des fichiers nommés, c'est-à-dire comment connecter les noms externes auxquels un utilisateur pense aux instructions lisant les données.

Les règles sont simples. Avant de pouvoir être lu ou écrit, un fichier doit être ouvert par la fonction de bibliothèque fopen. fopen prend un nom externe comme x.c ou y.c, effectue un peu de ménage et de négociation avec le système d'exploitation (dont les détails ne nous concernent pas) et renvoie un pointeur à utiliser dans les lectures ou écritures ultérieures du fichier.

Ce pointeur, appelé pointeur de fichier, pointe vers une structure contenant des informations sur le fichier, telles que l'emplacement d'un tampon, la position actuelle du caractère dans le tampon, si le fichier est en cours de lecture ou d'écriture, et si des erreurs ou une fin de fichier se sont produites. Les utilisateurs n'ont pas besoin de connaître les détails, car les définitions obtenues à partir de <stdio.h> incluent une déclaration de structure appelée FILE. La seule déclaration nécessaire pour un pointeur de fichier est illustrée par :

  1. FILE *fp;
  2. FILE *fopen(char *name, char *mode);    

Cela signifie que fp est un pointeur vers un FILE et que fopen renvoie un pointeur vers un FILE. Notez que FILE est un nom de type, comme int, et non une balise de structure ; il est défini avec un typedef.

L'appel à fopen dans un programme est :

  1. fp = fopen(name, mode);    

Le premier paramètre de fopen est une chaîne de caractères contenant le nom du fichier. Le second paramètre est le mode, également une chaîne de caractères, indiquant comment on souhaite utiliser le fichier. Les modes autorisés incluent la lecture («r»), l'écriture («w») et l'ajout («a»). Certains systèmes font la distinction entre les fichiers texte et binaires ; pour ces derniers, un «b» doit être ajouté à la chaîne de caractères de mode.

Si un fichier n'existant pas est ouvert pour l'écriture ou l'ajout, il est créé si possible. L'ouverture d'un fichier existant pour l'écriture entraîne la suppression de l'ancien contenu, tandis que l'ouverture pour l'ajout le conserve. Essayer de lire un fichier n'existant pas est une erreur, et il peut y avoir d'autres causes d'erreur également, comme essayer de lire un fichier lorsque vous n'avez pas l'autorisation. En cas d'erreur, fopen renverra NULL.

La prochaine chose nécessaire est un moyen de lire ou d'écrire le fichier une fois qu'il est ouvert. getc renvoie le caractère suivant d'un fichier ; il a besoin du pointeur de fichier pour lui indiquer de quel fichier il s'agit :

  1. int getc(FILE *fp)

getc renvoie le caractère suivant du flux référencé par fp ; il renvoie EOF pour fin de fichier ou erreur.

putc est une fonction de sortie :

  1. int putc(int c, FILE *fp)

putc écrit le caractère c dans le fichier fp et renvoie le caractère écrit, ou EOF si une erreur se produit. Comme getchar et putchar, getc et putc peuvent être des macros au lieu de fonctions.

Lorsqu'un programme C est démarré, l'environnement du système d'exploitation est responsable de l'ouverture de trois fichiers et de la fourniture de pointeurs pour eux. Ces fichiers sont l'entrée standard, la sortie standard et l'erreur standard ; les pointeurs de fichiers correspondants sont appelés stdin, stdout et stderr, et sont déclarés dans <stdio.h>. Normalement, stdin est connecté au clavier et stdout et stderr sont connectés à l'écran, mais stdin et stdout peuvent être redirigés vers des fichiers ou des tubes.

getchar et putchar peuvent être définis en termes de getc, putc, stdin et stdout comme suit :

  1. #define getchar()  getc(stdin)
  2. #define putchar(c) putc((c), stdout)

Pour l'entrée ou la sortie formatée de fichiers, les fonctions fscanf et fprintf peuvent être utilisées. Elles sont identiques à scanf et printf, sauf que le premier paramètre est un pointeur de fichier spécifiant le fichier à lire ou à écrire ; la chaîne de caractères de format est le deuxième paramètre :

  1. int fscanf(FILE *fp, char *format, ...)
  2. int fprintf(FILE *fp, char *format, ...)

Ces préliminaires étant terminés, nous sommes maintenant en mesure d'écrire le programme cat pour concaténer des fichiers. La conception est celle s'étant avérée pratique pour de nombreux programmes. S'il y a des paramètres de ligne de commande, ils sont interprétés comme des noms de fichiers et traités dans l'ordre. S'il n'y a pas de paramètres, l'entrée standard est traitée :

  1. #include <stdio.h>
  2.  
  3. /* cat: concaténer des fichiers, version 1 */
  4. main(int argc, char *argv[]) {
  5.  FILE *fp;
  6.  void filecopy(FILE *, FILE *)
  7.  
  8.  if (argc == 1) filecopy(stdin, stdout); /* pas de paramètres ; copier l'entrée standard */
  9.  else
  10.   while(--argc > 0)
  11.    if ((fp = fopen(*++argv, "r")) == NULL) {
  12.     printf("cat: Impossible d'ouvrir %s\n, *argv);
  13.     return 1;
  14.    } else {
  15.     filecopy(fp, stdout);
  16.     fclose(fp);
  17.    }
  18.  return 0;
  19. }
  20. /* filecopy: copier le fichier ifp dans le fichier ofp */
  21. void filecopy(FILE *ifp, FILE *ofp) {
  22.  int c;
  23.  while ((c = getc(ifp)) != EOF)
  24.  putc(c, ofp);
  25. }

Les pointeurs de fichiers stdin et stdout sont des objets de type FILE *. Ce sont des constantes, mais pas des variables, il n'est donc pas possible de leur attribuer des valeurs.

La fonction :

  1. int fclose(FILE *fp)

est l'inverse de fopen, il brise la connexion entre le pointeur de fichier et le nom externe ayant été établi par fopen, libérant le pointeur de fichier pour un autre fichier. Comme la plupart des systèmes d'exploitation ont une limite sur le nombre de fichiers qu'un programme peut ouvrir simultanément, c'est une bonne idée de libérer les pointeurs de fichiers lorsqu'ils ne sont plus nécessaires, comme nous l'avons fait dans cat. Il existe également une autre raison pour laquelle fclose est utilisé sur un fichier de sortie : il vide le tampon dans lequel putc collecte les résultats. fclose est appelé automatiquement pour chaque fichier ouvert lorsqu'un programme se termine normalement. (Vous pouvez fermer stdin et stdout s'ils ne sont pas nécessaires. Ils peuvent également être réaffectés par la fonction de bibliothèque freopen.)

Pointeurs de fichiers et fopen

Comment spécifierons-nous que nous voulons accéder à un fichier de données particulier ? Il serait théoriquement possible de mentionner le nom d'un fichier à chaque fois que l'on souhaite y lire ou y écrire. Mais une telle approche présenterait un certain nombre d'inconvénients. Au lieu de cela, l'approche habituelle (et celle adoptée dans la bibliothèque stdio de C) consiste à mentionner le nom du fichier une fois, au moment où vous l'ouvrez. Par la suite, vous utilisez un identificateur de descripteur de fichier, dans ce cas, le pointeur de fichier, gardant la trace (à la fois pour votre bien et pour celui de la bibliothèque) du fichier dont vous utilisez. Chaque fois que vous souhaitez lire ou écrire dans l'un des fichiers avec lesquels vous travaillez, vous identifiez ce fichier à l'aide de son pointeur de fichier (c'est-à-dire le pointeur de fichier que vous avez obtenu lorsque vous avez ouvert le fichier). Vous entreposez les pointeurs de fichiers dans des variables comme vous entreposez toutes les autres données que vous manipulez. Il est donc possible d'ouvrir plusieurs fichiers, à condition d'utiliser des variables distinctes pour entreposer les pointeurs de fichiers. Vous déclarez une variable pour entreposer un pointeur de fichier comme ceci :

  1. FILE *fp;

Le type FILE est prédéfini par l'entête <stdio.h>. Il s'agit d'une structure de données contenant les informations dont la bibliothèque d'entrée/sortie standard a besoin pour suivre le fichier pour vous. Pour des raisons historiques, vous déclarez une variable étant un pointeur vers ce type FILE. Le nom de la variable peut (comme pour toute variable) être tout ce que vous choisissez; il est traditionnel d'utiliser les lettres fp dans le nom de la variable (puisque nous parlons d'un pointeur de fichier). Si vous lisiez deux fichiers à la fois, vous utiliseriez probablement deux pointeurs de fichiers :

  1. FILE *fp1, *fp2;

Si vous lisez un fichier et écrivez dans un autre, vous pouvez déclarer et saisir un pointeur de fichier et un pointeur de fichier de sortie :

  1. FILE *ifp, *ofp;

Comme toute variable de pointeur, un pointeur de fichier ne sert à rien tant qu'il n'est pas initialisé pour pointer vers quelque chose. En fait, aucune variable de n'importe quel type n'est très bonne tant que vous ne l'avez pas initialisée. Pour ouvrir réellement un fichier et recevoir l'identificateur de descripteur de fichier que vous entreposez dans votre variable de pointeur de fichier, vous appelez fopen. La fonction fopen accepte un nom de fichier (sous forme de chaîne de caractères) et une valeur de mode indiquant entre autres si vous avez l'intention de lire ou d'écrire ce fichier. (La variable de mode est également une chaîne de caractères.) Pour ouvrir le fichier entree.dat en lecture, vous pouvez appeler :

  1. ifp = fopen("entree.dat", "r"); 

La chaîne de caractères de mode "r" indique la lecture. Le mode "w" indique l'écriture, vous pourriez donc ouvrir sortie.dat pour une sortie comme ceci :

  1. ofp = fopen("sortie.dat", "w"); 

Les autres valeurs de la chaîne de caractères de mode sont moins fréquemment utilisées. Le troisième mode majeur est "a" pour ajouter. Si vous utilisez "w" pour écrire dans un fichier existant déjà, son ancien contenu sera ignoré. Vous pouvez également ajouter un caractère «+» à la chaîne de caractères de mode pour indiquer que vous voulez à la fois lire et écrire, ou un caractère ab pour indiquez que vous souhaitez effectuer des entrées/sorties binaires par opposition au texte.

Une chose à prendre en compte lors de l'ouverture de fichiers est qu'il s'agit d'une opération pouvant échouer. Le fichier demandé peut ne pas exister ou il peut être protégé contre la lecture ou l'écriture. Ces possibilités devraient être évidentes, mais il est facile de les oublier. La fonction fopen renvoie un pointeur nul s'il ne peut pas ouvrir le fichier demandé, et il est important de vérifier ce cas avant de partir et d'utiliser la valeur de retour de fopen comme un pointeur de fichier. Chaque appel à la fonction fopen sera généralement suivi d'un test, comme ceci :

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. int main()
  5. {
  6.     FILE *ifp = fopen("entree.dat", "r");
  7.     if(ifp == NULL) {
  8.         printf("Impossible d'ouvrir le fichier !\n");
  9.         exit(EXIT_FAILURE);
  10.     }
  11.     return 0;
  12. }

Si la fonction fopen renvoie un pointeur nul et que vous l'entreposez dans votre variable de pointeur de fichier et que vous essayez de faire des entrées/sorties avec lui, votre programme plantera généralement. Il est courant de réduire l'appel à fopen et l'affectation avec le test :

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. int main()
  5. {
  6.     FILE *ifp;
  7.     if((ifp = fopen("entree.dat", "r")) == NULL) {
  8.         printf("Impossible d'ouvrir le fichier !\n");
  9.         exit(EXIT_FAILURE);
  10.     }
  11.     return 0;
  12. }

Vous n'avez pas à écrire ces tests réduits si vous n'êtes pas à l'aise, mais vous les verrez dans le code d'autres personnes, vous devriez donc pouvoir les lire.

Entrée/sortie avec des pointeurs de fichier

Pour chacune des fonctions de bibliothèque d'entrée/sortie que nous avons utilisées jusqu'à présent, il existe une fonction compagnon acceptant un paramètre de pointeur de fichier supplémentaire lui indiquant où lire ou écrire. La fonction associée à printf est fprintf et le paramètre du pointeur de fichier vient en premier. Pour afficher une chaîne de caractères dans le fichier sortie.dat que nous avons ouvert précédemment, nous pouvons appeler :

  1. fprintf(ofp, "Bonjour Gladir.com!\n"); 

La fonction compagnon de getchar est getc et le pointeur de fichier est son seul paramètre. Pour lire un caractère du fichier entree.dat que nous avons ouvert précédemment, nous pourrions appeler :

  1. int c;
  2. c = getc(ifp); 

La fonction compagnon de putchar est putc et le paramètre du pointeur de fichier vient en dernier. Pour écrire un caractère dans sortie.dat, nous pourrions appeler :

  1. putc(c,ofp); 

Nous pourrions donc écrire une fonction fdemandeligne lisant à partir d'un pointeur de fichier arbitraire une ligne de texte ASCII :

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. #define MAXLIGNE 100
  5.  
  6. int fdemandeligne(FILE *fp, char ligne[], int max) {
  7.     int nombrecaractere = 0;
  8.     int caractere;
  9.     max--; /* Laissez de l'espace pour le caractère de fin '\0' */
  10.     while((caractere = getc(fp)) != EOF) {
  11.         if(caractere == '\n') break;
  12.         if(nombrecaractere < max){
  13.             ligne[nombrecaractere] = caractere;
  14.             nombrecaractere++;
  15.         }
  16.     }
  17.     if(caractere == EOF && nombrecaractere == 0) return EOF;
  18.     ligne[nombrecaractere] = '\0';
  19.     return nombrecaractere;
  20. }
  21.  
  22. int main() {
  23.     FILE *ifp;
  24.     if((ifp = fopen("entree.dat", "r")) == NULL) {
  25.         printf("Impossible d'ouvrir le fichier !\n");
  26.         exit(EXIT_FAILURE);
  27.     } else {
  28.         char ligne[MAXLIGNE];
  29.         fdemandeligne(ifp, ligne, MAXLIGNE);
  30.     }
  31.     return 0;
  32. }

Flux de données prédéfinis

Outre les pointeurs de fichiers que nous ouvrons explicitement en appelant fopen, il existe également trois flux de données prédéfinis. Le stdin est un pointeur de fichier constant correspondant à l'entrée standard, et stdout est un pointeur de fichier constant correspondant à la sortie standard. Les deux peuvent être utilisés partout où un pointeur de fichier est appelé; par exemple, getchar() est identique à getc(stdin) et putchar(c) est identique à putc(c, stdout). Le troisième flux de données prédéfini est stderr. Comme stdout, le stderr est généralement connecté à l'écran par défaut. La différence est que stderr n'est pas redirigé lorsque la sortie standard est redirigée. Par exemple, sous Unix, DOS ou Windows, lorsque vous appelez :

program > filename

tout ce qui est affiché sur stdout est redirigé vers le nom du fichier, mais tout ce qui est affiché sur stderr va toujours à l'écran. L'intention derrière stderr est qu'il s'agit de la sortie d'erreur standard; les messages d'erreur y étant affichés ne disparaissent pas dans un fichier de sortie. Par exemple, une façon plus réaliste d'afficher un message d'erreur lorsqu'un fichier ne peut pas être ouvert serait :

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. int main()
  5. {
  6.     char nomdufichier[] = "entree.dat";
  7.     FILE *ifp;
  8.     if((ifp = fopen(nomdufichier, "r")) == NULL) {
  9.         fprintf(stderr, "Impossible d'ouvrir le fichier %s\n", nomdufichier);
  10.         exit(EXIT_FAILURE);
  11.     }
  12.     return 0;
  13. }

nomdufichier est une variable chaîne de caractères indiquant le nom du fichier à ouvrir. Non seulement le message d'erreur est affiché sur stderr, mais il est également plus informatif en ce qu'il mentionne le nom du fichier n'ayant pas pu être ouvert.

Fermeture des fichiers

Bien que vous puissiez ouvrir plusieurs fichiers, le nombre d'enregistrements que vous pouvez ouvrir simultanément est limité. Si votre programme ouvre plusieurs fichiers à la suite, vous voudrez fermer chacun d'eux comme vous en avez terminé avec lui; sinon, la bibliothèque d'entrée/sortie standard C pourrait manquer des ressources qu'elle utilise pour garder une trace des fichiers ouverts. La fermeture d'un fichier consiste simplement à appeler fclose avec le pointeur de fichier comme paramètre :

  1. fclose(fp); 

L'appel de la fonction fclose fait en sorte que (si le fichier était ouvert pour la sortie), toute dernière sortie tamponnée est finalement écrite dans le fichier et que les ressources utilisées par le système d'exploitation (et la bibliothèque C) pour ce fichier sont libérées. Si vous oubliez de fermer un fichier, il se fermera automatiquement à la fermeture du programme.

Gestion des erreurs - stderr et sortie

Le traitement des erreurs dans cat n'est pas idéal. Le problème est que si l'un des fichiers n'est pas accessible pour une raison quelconque, le diagnostic est affiché à la fin de la sortie concaténée. Cela peut être acceptable si la sortie est envoyée vers un écran, mais pas si elle est envoyée vers un fichier ou vers un autre programme via un pipeline.

Pour mieux gérer cette situation, un deuxième flux de sortie, appelé stderr, est attribué à un programme de la même manière que stdin et stdout. La sortie écrite sur stderr apparaît normalement sur l'écran même si la sortie standard est redirigée. Révisons cat pour écrire ses messages d'erreur sur l'erreur standard.

  1. #include <stdio.h>
  2.  
  3. /* cat: concaténer des fichiers, version 2 */
  4. main(int argc, char *argv[]) {
  5.  FILE *fp;
  6.  void filecopy(FILE *, FILE *);
  7.  char *prog = argv[0]; /* nom du programme pour les erreurs */
  8.  if (argc == 1 ) /* pas de paramètres ; copier l'entrée standard */
  9.   filecopy(stdin, stdout);
  10.  else
  11.   while (--argc > 0)
  12.    if ((fp = fopen(*++argv, "r")) == NULL) {
  13.     fprintf(stderr, "%s: ne peut pas ouvrir %s\n", prog, *argv);
  14.     exit(1);
  15.    } else {
  16.     filecopy(fp, stdout);
  17.     fclose(fp);
  18.    }
  19.    if (ferror(stdout)) {
  20.     fprintf(stderr, "%s: erreur d'écriture de stdout\n", prog);
  21.     exit(2);
  22.    }
  23.  exit(0);
  24. }

Le programme signale les erreurs de deux manières. Tout d'abord, la sortie de diagnostic produite par fprintf va sur stderr, de sorte qu'elle trouve son chemin vers l'écran au lieu de disparaître dans un pipeline ou dans un fichier de sortie. Nous avons inclus le nom du programme, à partir de argv[0], dans le message, donc si ce programme est utilisé avec d'autres, la source d'une erreur est identifiée.

Deuxièmement, le programme utilise la fonction exit de la bibliothèque standard, qui termine l'exécution du programme lorsqu'elle est appelée. Le paramètre exit est disponible pour tout processus appelé celui-ci, de sorte que le succès ou l'échec du programme peut être testé par un autre programme utilisant celui-ci comme sous-processus. Par convention, une valeur de retour de 0 signale que tout va bien ; les valeurs non nulles signalent généralement des situations anormales. exit appelle fclose pour chaque fichier de sortie ouvert, pour éliminer toute sortie en mémoire tampon.

Dans main, return expr est équivalent à exit(expr). exit présente l'avantage de pouvoir être appelé à partir d'autres fonctions et de pouvoir être trouvé à l'aide d'un programme de recherche de motifs.

La fonction ferror renvoie une valeur non nulle si une erreur s'est produite sur le flux fp.

  1. int ferror(FILE *fp)

Bien que les erreurs de sortie soient rares, elles se produisent (par exemple, si un disque est plein), donc un programme de production doit également vérifier cela.

La fonction feof(FILE *) est analogue à ferror ; elle renvoie une valeur différente de zéro si la fin du fichier s'est produite sur le fichier spécifié.

  1. int feof(FILE *fp)

Nous ne nous soucions généralement pas de l'état de sortie dans nos petits programmes illustratifs, mais tout programme sérieux doit prendre soin de renvoyer des valeurs d'état raisonnables et utiles.

Entrée et sortie de ligne

La bibliothèque standard fournit une routine d'entrée et de sortie fgets étant similaire à la fonction getline que nous avons utilisée dans les pages précédentes :

  1. char *fgets(char *line, int maxline, FILE *fp)    

fgets lit la ligne d'entrée suivante (y compris la nouvelle ligne) du fichier fp dans le tableau de caractères line ; au plus maxline-1 caractères seront lus. La ligne résultante se termine par '\0'. Normalement, fgets renvoie line ; à la fin du fichier ou en cas d'erreur, il renvoie NULL. (Notre getline renvoie la longueur de la ligne, étant une valeur plus utile ; zéro signifie la fin du fichier.)

Pour la sortie, la fonction fputs écrit une chaîne de caractères (n'ayant pas besoin de contenir de nouvelle ligne) dans un fichier :

  1. int fputs(char *line, FILE *fp)    

Elle renvoie EOF si une erreur se produit, et non négative sinon.

Les fonctions de bibliothèque gets et puts sont similaires à fgets et fputs, mais fonctionnent sur stdin et stdout. De manière déroutante, gets supprime le '\n' de fin et puts l'ajoute.

Pour montrer qu'il n'y a rien de spécial dans les fonctions comme fgets et fputs, les voici, copiées de la bibliothèque standard sur notre système :

  1. /* fgets: demande au plus n caractères de l'iop */
  2. char *fgets(char *s, int n, FILE *iop) {
  3.  register int c;
  4.  register char *cs;
  5.  cs = s;
  6.  while (--n > 0 && (c = getc(iop)) != EOF) if ((*cs++ = c) == '\n') break;
  7.  *cs = '\0';
  8.  return (c == EOF && cs == s) ? NULL : s;
  9. }
  10.  
  11. /* fputs: mettre la chaîne de caractères s sur le fichier iop */
  12. int fputs(char *s, FILE *iop) {
  13.  int c;
  14.  while (c = *s++) putc(c, iop);
  15.  return ferror(iop) ? EOF : 0;
  16. }

Sans raison apparente, la norme spécifie des valeurs de retour différentes pour ferror et fputs.

Il est facile d'implémenter notre getline à partir de fgets :

  1. /* getline: lire une ligne, renvoyer la longueur */
  2. int getline(char *line, int max) {
  3.  if (fgets(line, max, stdin) == NULL) return 0;
  4.                                  else return strlen(line);
  5. }

Fonctions diverses

La bibliothèque standard propose une grande variété de fonctions. Cette section est un bref résumé des plus utiles.

Opérations sur les chaînes de caractères

Nous avons déjà mentionné les fonctions de chaîne de caractères strlen, strcpy, strcat et strcmp, présentes dans <string.h>. Dans ce qui suit, s et t sont des char *, et c et n sont des int.

Fonction Description
strcat(s,t) Concaténer t à la fin de s
strncat(s,t,n) Concaténer n caractères de t jusqu'à la fin de s
strcmp(s,t) Renvoie négatif, zéro ou positif pour s < t, s == t, s > t
strncmp(s,t,n) Identique à strcmp mais uniquement dans les n premiers caractères.
strcpy(s,t) Copier t en s
strncpy(s,t,n) Copier au plus n caractères de t vers s.
strlen(s) Longueur de retour de s
strchr(s,c) Renvoie le pointeur vers le premier c dans s, ou NULL s'il n'est pas présent
strrchr(s,c) Renvoie le pointeur vers le dernier c dans s, ou NULL s'il n'est pas présent

Test et conversion de classes de caractères

Plusieurs fonctions de <ctype.h> effectuent des tests et des conversions de caractères. Dans ce qui suit, c est un int pouvant être représenté comme un unsigned char ou EOF. La fonction renvoie int.

Fonction Description
isalpha(c) Non nul si c est alphabétique, 0 sinon
isupper(c) Non nul si c est en majuscule, 0 sinon.
islower(c) Différent de zéro si c est en minuscule, 0 sinon.
isdigit(c) Différent de zéro si c est un chiffre, 0 sinon
isalnum(c) Différent de zéro si isalpha(c) ou isdigit(c), 0 sinon
isspace(c) Différent de zéro si c est vide, tabulation, nouvelle ligne, retour, saut de page, tabulation verticale.
toupper(c) Renvoie c converti en majuscule.
tolower(c) Renvoie c converti en minuscule.

ungetc

La bibliothèque standard fournit une version plutôt restreinte de la fonction ungetch ; elle s'appelle ungetc :

  1. int ungetc(int c, FILE *fp)    

repousse le caractère c dans le fichier fp et renvoie soit c, soit EOF en cas d'erreur. Un seul caractère de pushback est garanti par fichier. ungetc peut être utilisé avec n'importe laquelle des fonctions d'entrée telles que scanf, getc ou getchar.

Exécution de la commande

La fonction system(char *s) exécute la commande contenue dans la chaîne de caractères s, puis reprend l'exécution du programme en cours. Le contenu de s dépend fortement du système d'exploitation local. À titre d'exemple trivial, sur les systèmes UNIX, l'instruction :

  1. system("date");

provoque l'exécution du programme date ; il affiche la date et l'heure du jour sur la sortie standard. system renvoie un entier dépendant du système status à partir de la commande exécutée. Dans le système UNIX, le retour status est la valeur renvoyée par exit.

Gestion d'entreposage

Les fonctions malloc et calloc obtiennent des blocs de mémoire de manière dynamique.

  1. void *malloc(size_t n)

renvoie un pointeur vers n octets d'entreposage non initialisé, ou NULL si la demande ne peut pas être satisfaite.

  1. void *calloc(size_t n, size_t size)    

renvoie un pointeur vers un espace libre suffisant pour un tableau de n objets de la taille spécifiée, ou NULL si la requête ne peut pas être satisfaite. L'entreposage est initialisé à zéro.

Le pointeur renvoyé par malloc ou calloc a l'alignement approprié pour l'objet en question, mais il doit être converti dans le type approprié, comme dans :

  1. int *ip;
  2. ip = (int *) calloc(n, sizeof(int));

free(p) libère l'espace pointé par p, où p a été obtenu à l'origine par un appel à malloc ou calloc. Il n'y a aucune restriction sur l'ordre dans lequel l'espace est libéré, mais c'est une erreur terrible de libérer quelque chose n'étant pas obtenu en appelant malloc ou calloc.

C'est aussi une erreur d'utiliser quelque chose après qu'il a été libéré. Un morceau de code typique mais incorrect est cette boucle libérant des éléments d'une liste :

  1. for (p = head; p != NULL; p = p->next) /* MAUVAIS */
  2.   free(p);

La bonne façon est de sauvegarder tout ce qui est nécessaire avant de libérer :

  1. for (p = head; p != NULL; p = q) {
  2.  q = p->next;
  3.  free(p);
  4. }

Fonctions mathématiques

Il existe plus de vingt fonctions mathématiques déclarées dans <math.h> ; voici quelques-unes des plus fréquemment utilisées. Chacune prend un ou deux paramètres double et renvoie un double :

Fonction Description
sin(x) Sinus de x, x en radians
cos(x) Cosinus de x, x en radians
atan2(y,x) Arctangente de y/x, en radians
exp(x) Fonction exponentielle ex
log(x) Logarithme naturel (base e) de x (x>0)
log10(x) logarithme commun (base 10) de x (x>0)
pow(x,y) xy
sqrt(x) Racine carrée de x (x>0)
fabs(x) Valeur absolue de x

Génération de nombres aléatoires

La fonction rand() calcule une séquence d'entiers pseudo-aléatoires dans l'intervalle de zéro à RAND_MAX, étant définie dans <stdlib.h>. Une façon de produire des nombres à virgule flottante aléatoires supérieurs ou égaux à zéro mais inférieurs à un est :

  1. #define frand() ((double) rand() / (RAND_MAX+1.0))

(Si votre bibliothèque fournit déjà une fonction pour les nombres aléatoires à virgule flottante, elle aura probablement de meilleures propriétés statistiques que celle-ci.)

La fonction srand(unsigned) définit la valeur de départ pour rand.



Dernière mise à jour : Dimanche, le 8 novembre 2020