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.
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 :
- 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 :
- 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 :
- 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 :
- 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 :
- 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 :
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 :
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 :
- 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 :
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 :
- putc(c,ofp);
Nous pourrions donc écrire une fonction fdemandeligne lisant à partir d'un pointeur de fichier arbitraire une ligne de texte ASCII :
- #include <stdio.h>
- #include <stdlib.h>
-
- #define MAXLIGNE 100
-
- int fdemandeligne(FILE *fp, char ligne[], int max) {
- int nombrecaractere = 0;
- int caractere;
- max--; /* Laissez de l'espace pour le caractère de fin '\0' */
- while((caractere = getc(fp)) != EOF) {
- if(caractere == '\n') break;
- if(nombrecaractere < max){
- ligne[nombrecaractere] = caractere;
- nombrecaractere++;
- }
- }
- if(caractere == EOF && nombrecaractere == 0) return EOF;
- ligne[nombrecaractere] = '\0';
- return nombrecaractere;
- }
-
- int main() {
- FILE *ifp;
- if((ifp = fopen("entree.dat", "r")) == NULL) {
- printf("Impossible d'ouvrir le fichier !\n");
- exit(EXIT_FAILURE);
- } else {
- char ligne[MAXLIGNE];
- fdemandeligne(ifp, ligne, MAXLIGNE);
- }
- return 0;
- }
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 :
où 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 :
- 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.