Les Fichiers

Le rôle des fichiers est multiple. Tout d'abord ils constituent une nouvelle structure de données munie de propriétés intéressantes. Ensuite ils donnent aux programmes le moyen de communiquer avec l'extérieur. Cette communication peut s'établir avec un terminal, ou un autre appareil: imprimante, lecteur de cartes, disques magnétiques. Le plus souvent cette communication s'établit avec le système sous lequel le programme s'exécute et permet de construire ou de consulter les ensembles de données qui existent avant ou après la période pendant laquelle le programme s'exécute.

Le Pascal utilise quelques concepts simples qui permettent d'utiliser de façon uniforme et portable une partie de ce qu'il est convenu d'appeler les opération sur les fichiers. Le Pascal standard permet seulement d'utiliser des fichiers séquentiels.

Dénomination "fichier".

Un fichier séquentiel est un endroit ou l'on stocke des "fiches" de données de façon ordonnée, c'est à dire que les fiches sont rangées les unes après les autres en séquence.

D'un point de vue informatique, les fiches sont des objets d'un même type et leur nombre n'est pas fixé. Soit un type de données T déterminé, on peut construire un type "fichier" en écrivant:

file of T

On peut bien sûr donner un nom de ce type

TYPE
fichierdeT = file of T;

Et quand il y a un type déclaré, on peut déclarer des variables de ce type:
f: FichierdeT;

Par exemple définissons le type fichier d'entier:

Type
Fichint = File of integer;
Var
f : Fichint;

La valeur de la variable f à un moment donné peut être représentée par une séquence d'emplacements d'entiers:

Si la séquence n'est pas vide, elle a un premier élément (25) et un dernier élément (3). Chaque élément sauf le dernier a un successeur. Certains éléments peuvent avoir la même valeur et l'ordre dans lequel ils sont rangés n'a pas à être en relation avec l'ordre de leurs valeurs.

Une telle séquence fait penser à un tableau à une dimension, mais il y a de nombreuses différences:

Accès aux fichiers

Un fichier doit pouvoir contenir un très gros volume d'information, qui ne peut pas être mis en entier dans la mémoire centrale. A cause de cette contrainte, à un moment donné, on ne pourra avoir accès qu'à l'un de ses éléments. Pour accéder à n'importe quel élément, il faut d'abord préparer le fichier. Cette opération se fait en deux étapes:

  1. Etablir la relation entre le nom de variable de type fichier (le nom interne) avec le nom du fichier qui est défini dans le système d'exploitation (nom externe). Dans le Pascal standard il n'y a pas de moyen pour résoudre ce problème. Cette tâche est laissée à la réalisation concrète. En Turbo Pascal il y a une procédure standard:
    Assign(<nom de variable>,<nom de fichier-chaîne de caractère>)
    Exemple:
    Assign(f,'DONNEES.DAT')
  2. Ouverture d'un fichier
    On peut ouvrir le fichier pour lire avec:
    Reset(f);
    Cette procédure fait dans la mémoire centrale une allocation d'un tampon pour le fichier (engage une place) et place le pointeur du fichier (une variable interne et cachée qui montre ou pointe vers l'élément qui doit être lu) au début du fichier (il pointe vers le premier élément).
    On ouvre le fichier pour écrire avec:
    Rewrite(f);
    Cette procédure alloue un tampon et place le pointeur au début, qui est au même temps la fin du fichier: e le fichier est vide. S'il n'était pas vide auparavant, son contenu sera effacé.

Si le nom du fichier dans l'appel de la procédure Assign n'est pas correct ou le fichier n'existe pas quand on essaie de l'ouvrir pour llecture, le système réagira par une erreur fatale et cessera l'exécution du programme. Mais cela n'est pas toujours la réaction désirée. Car c'est une réaction du système d'exploitation le Pascal standard n'a pas de moyens de réagir à telles erreurs. Mais presque chaque réalisation a des moyens pour franchir cette contrainte. Dans Turbo Pascal il y a les directives du compilateur. On écrit les directives comme les commentaires entre accolades. Mais le premier caractère de la directive est le signe de dollar ($) suivi par le caractère de la directive qui dénote l'action du compilateur, et le signe + ou - qui dénote si l'action est valide ou interdite.

Par exemple quand on veut suspendre les réactions standards d'entrée/sortie on écrit {$I-}

Exemple:

Type
 ouverture = (lire,ecrire)
;
 fichier = File of T;
Procedure Ouvrir_le_fichier(var f : fichier;    mode : ouverture); var succes : boolean);
  Var
   s : String[65]; err : integer;
Begin
 write('Entrez le nom du fichier ');  readln(s);
 Assign(f,s);
 {$I-}
 if mode = lire Then Reset(f)
   Else Rewrite(f);
 err := IOResult;

 {$I+}
 If err <> 0 Then
 Begin
  Write('Le fichier ',s);
  If mode = lire Then
  begin
   Write(' n''existe pas ou');
   Writeln(' le nom n''est pas correct');
   succes := False;
  End Else succes := True;
End;

Lire et écrire dans un fichier

Lecture des éléments (consulter le fichier)

On lit un élément par la procédure
read(<variable de type fichier>, <nom de variable de type de base>)

La valeur de l'élément qui est désigné par le pointeur est copiée dans la variable et le pointeur se déplace vers l'élément suivant. Quand le pointeur du fichier arrive à la fin du fichier (la fin du fichier est atteinte) et qu'il n'y a pas un autre élément à lire, on dit qu'on est en fin de fichier. Il y a une fonction à résultat booléen - eof(f), qui est vrai si et seulement si la fin de fichier a été atteinte.

Construction d'un fichier

On écrit (ajoute) un élément avec:
write(<variable de type fichier>, <valeur de type de base>)

La valeur s'écrit à la fin du fichier, c'est à dire que quand on écrit, on est toujours en fin de fichier. Le pointeur se déplace à la fin et est prêt à écrire un nouvel élément.

Fermeture d'un fichier

Quand on a fini à utiliser le fichier f, on doit le fermer avec
Close(f)

Puis le fichier peut être ouvert encore une fois, soit avec un nouveau nom, soit avec le nom existant.

Exemple: Ecrivez un programme qui lit un nombre indéfini de nombres réels, calcule leurs carrés et écrit ces derniers dans un fichier. Puis il lit les carrés et les affiche sur l'écran.

program fishier;
var fich:file of real;
    a,carre : real;
begin
 assign(fich,'carre.dat');
 rewrite(fich);
 while not eof do
 begin
  readln(a);
  carre := sqr(a);
  write(fich,a,carre);
 end;
 close (fich);
 reset(fich);
 while not eof(fich) do
 begin
  read(fich,a,carre);
  writeln('Le carre de ',a:7:2,' est',carre:7:2);
 end;
 close(fich);
end.

Les fichiers "Text"

Les fichiers "text" servent à travailler avec des données et des résultats écrits en clair, c'est à dire sous forme de caractères lisibles tels qu'on peut les taper sur un clavier ou les écrire sur un écran ou une imprimante. La notion de page est accessoire et n'a d'utilité que pour la sortie de résultats imprimés. La notion de ligne est beaucoup plus importante et doit être contrôlée précisément. En effet, si le découpage en pages est causé par des considérations esthétiques, le découpage en lignes est très important pour la signification des choses écrites.

On peut lire dans les manuels de Pascal que le type text est un type prédéfini ainsi:
Type text = file of char;

Cette définition n'est pas complètement vraie particulièrement dans Turbo Pascal. Ici le type text est prédéfini, mais il y a quelques différences entre lui et le type file of char.

  1. Le type text permet l'entrée et la sortie des valeurs d'autres types que caractères, en traduisant automatiquement (en passant) les valeurs d'une représentation à une autre.
    Entrée - d'une chaîne de caractères (représentation externe) à une représentation interne.
    Sortie - d'une représentation interne à une représentation externe (chaîne de caractères).
    Les types qui peuvent être traduits sont les types prédéfinis (integer, real, boolean) et leurs intervalles.
    Le type file of char et un type de fichier binaire avec un type de base char. Alors le programmeur doit prendre soin lui même de ces transformations.
  2. Le fichier de type text est découpé en lignes. On peut utiliser les procédures readln et writeln. On les utilise de la façon décrite avant avec la différence que le premier paramètre effectif est le nom d'un fichier ouvert. De plus quand on lit un fichier de ce type on peut inspecter si on se trouve en fin de ligne avec la fonction booléen eoln(f), qui est vraie si et seulement si le pointeur du fichier atteint la fin de la ligne. Les fichiers standards "input" et "output" sont du type text et ils sont toujours ouverts.
    La fin de la ligne est marquée par deux caractères spéciaux chr(13) et chr(10). Quand on travaille avec un fichier de type file of char, le fichier est une seule chaîne de caractères continue et on doit prendre soin de placer ces caractères pour contrôler les lignes.

Exemple: Deux procédures qui permettent de transferer des nombres entiers entre la mémoire centrale et un fichier de type text.

Type
 Fichier = File of char;
Procedure ReadInt(var f:fichier; var res:integer);
Var
 c : char;
 sign : Boolean;
Begin
  {Ommetre les blancs}
 Repeat
  Read(f,c);
 Until c <> ' ';
 res := 0; sign := True;
  {Lire le nombre entier}
 If c = '-' Then sign := False;
 While (c <'0') Or (c >'9') Do
  Read(f,c);
 While (c>='0') And (c <= '9') Do
 Begin
  res := res*10 + ord(c) - ord('0');
  c := ' ';
  If not eof(f) Then
    read(f,c);
 End;
 If not sign Then res := -res;
End;
Procedure WriteInt(var f:fichier; v,p:integer);
var s:string;
    s1:string[15]; s2,s3: string[1];
    sign : boolean;
    i : integer;
Begin
 s := ''; s1[0] := chr(1); sign := v>=0;
 v := abs(v); s2 := '-'; s3 := ' ';
 Repeat
  s1[1] := chr(v mod 10 + ord('0'));
  Insert(s1,s,1);
  v := v div 10;
 Until v = 0;
 If not sign Then Insert(s2,s,1);
 For i:= Length(s)+1 To p Do
   Insert(s3,s,1);
 For i:=1 To Length(s) Do Write(f,s[i]);
End;

Devoir à la maison: Comment déclarer un fichier dans lequel on peut écrire les valeurs de deux types différents (par exemple nombres réels et valeurs logiques)?