Vous êtes sur la page 1sur 35

Chapitre 5 - Les fichiers en Turbo Pascal

1 Intérêt des fichiers

Les entrées-sorties, c'est-à-dire la communication d'informations entre la mémoire d'un ordina- teur et ses périphériques ( unités de disques, dérouleurs de bandes, imprimantes, clavier, écran, etc. ) sont modélisées de façon abstraite à l'aide de la notion de fichier. Un fichier peut être considéré comme une suite éventuellement vide de composants de même type déposée sur un support quelconque hors mémoire. C'est le seul moyen offert aux programmes pour communiquer avec le monde extérieur pendant leur exécution.

Tout programme manipule l'information à travers des variables qui résident dans la mémoire centrale de l’ordinateur. L’information n’existe donc que la durée de l’exécution du dit programme.

Les fichiers sont donc aussi le seul moyen :

- de sauvegarder l'information entre plusieurs exécutions,

- de faire communiquer entre eux plusieurs programmes, qu’ils soient sur une même machine ou des machines différentes,

- de transmettre des données entre plusieurs machines.

Les modes de représentations physiques des fichiers varient énormément d'un ordinateur à l'autre. De plus les fichiers sont du domaine du système d'exploitation et chaque langage met donc en oeuvre une gestion de fichier adaptée au système sous lequel il est installé, ce qui posera un grave problème de portabilité des programmes d’une machine à une autre. Le seul type de fichier qui soit presque toujours transférable sans trop de difficulté entre deux matériels est le fichier séquentiel de caractères fréquemment nommé fichier texte. Il est donc difficile d'intégrer la notion de fichier dans un langage de programmation sans courir le risque de choisir un concept trop lié à l'architecture d'un ordinateur ou d'une famille d'ordinateurs particuliers.

2 Les formats de fichiers

En mémoire centrale, l’information est codée en binaire. Outre le fait que l’unité centrale réalise tous les calculs avec des circuits électroniques fonctionnant en logique binaire, l’intérêt principal de ce codage est que la taille de la zone mémoire nécessaire à stocker toutes les valeurs possibles d’une variable est fixe. Cette taille est déterminée par le compilateur lors de la déclaration de type de cette variable. Par exemple un entier occupe 2 cases mémoire ( 2 octets ou 16 bits sur un

micro-ordinateur ) quelle que soit sa valeur, qui est limitée à ±32767 ; en effet avec 16 bits, on a 2 16 combinaisons soit 65536 valeurs possibles. Par convention l’ensemble des Integer est “ centré sur zéro ”, d’où un domaine de définition de -32768 à 32767. De la même façon, une variable de type Word est aussi codée sur 16 bits, mais le domaine de définition n’inclut que des valeurs positives ou nulles soit de 0 à 65535. Il est important de se souvenir que le codage utilisé dépend du matériel (

micro-ordinateur, mini-ordinateur

Si les informations étaient représentées en mémoire en clair, par exemple une série de chiffres décimaux, la place occupée par une variable serait bien supérieure ; il faudrait probablement réserver 5 cases mémoire pour un entier ( une case par chiffre ) sans compter le signe. De plus la taille effectivement occupée dans la zone réservée dépend de la valeur de la variable. La valeur 1 occuperait une seule case sur les cinq et la valeur 32000 occuperait les cinq cases. Il faudrait donc ajouter une information supplémentaire sur le nombre de cases effectivement occupées dans la zone réservée.

) et du langage utilisé.

Lors de l’écriture d’une variable dans un fichier, ou de sa relecture ultérieure, il convient donc de préciser si l’on désire ou non une conversion entre le format binaire interne à la machine et le format usuel utilisé par les humains pour représenter l’information.

On distingue donc deux types de fichiers : binaires et textes. Chacun de ces deux types de fichiers présente des avantages et des inconvénients qui font que ni l’un ni l’autre ne peut être aban- donné.

- les fichiers binaires, où l’information est déposée par une simple recopie de la zone mémoire associée à la variable. A la relecture, le contenu de la zone mémoire est directement relu depuis le disque. Ces fichiers sont souvent nommés fichiers à accès direct car le temps d'accès à chaque composante est sensiblement constant, ce qui n'est absolument pas le cas dans les fichiers textes. Un autre avantage majeur de ces fichiers est la possibilité de lire et d'écrire en même temps sur le même périphérique, ce qui simplifiera beaucoup les opérations de modifications des informations. Cette extension est très simplement obtenue sachant que le fichier est une série de composantes de même type et donc de même taille, ce qui permet au langage de calculer la position physique sur le support d'une composante quelconque à partir de son numéro d'ordre et de sa taille. Un fichier binaire est donc une extension de la notion de tableau à une dimension, extensible par une extrémité. Le seul moyen d'obtenir ce résultat est de communiquer avec le support en format binaire, c’est-à-dire comme une copie exacte du contenu de la mémoire pour la variable à échanger avec ce support. Une conséquence quelle fois gênante est qu'un tel fichier ne peut contenir que des composantes de même type et ne pourra être traité qu'une composante à la fois.

Le fait que les langages n'inscrivent pas au début d’un fichier la nature et la taille des composantes, conduit au problème qu'il n'est généralement pas possible de faire relire à un langage un fichier binaire créé par un autre langage, ni même par le même langage si l'on ne connaît pas la nature ( le type ) des composantes du fichier.

Ces fichiers sont donc parfaitement adaptés à une gestion transactionnelle de l'information, où l'on réalise très fréquemment des mises à jour. Il importera ensuite de convertir ces fichiers en format texte si l'on désire échanger leur contenu avec d'autres programmes ou l'archiver.

- les fichiers textes, où l’information est convertie du format interne en format lisible par un humain lors de l’écriture. A la relecture, l’opération de codage en binaire sera effectuée pour con- vertir de nouveau la série de caractères qui représentent la valeur vers le format interne à la machine.

Un fichier texte est un modèle pour tout périphérique d'ordinateur qui est toujours un support sur lequel les informations ne peuvent être lues ou écrites que les unes à la suite des autres en com- mençant par le début et sans retour arrière possible : bandes magnétiques, imprimantes ou plus an- ciennement lecteurs-perforateurs de cartes perforées ou de rubans.

A partir du moment où l'information est déposée en clair dans un fichier texte, la taille de chaque composante dépend de sa valeur. Il est tout à fait possible de déposer des informations de

type différent dans un tel fichier, à la condition bien sur de respecter une certaine régularité afin de pouvoir relire par une boucle. De plus le programmeur a l’entière liberté d'organiser son fichier comme il le désire ; une ou plusieurs informations par ligne, avec ou sans séparateurs entre chacune

). Il est donc impossible de se positionner directement sur une infor-

mation puisque l'on ne peut pas calculer l'endroit où elle commence, puisque cette position dépend des valeurs des composantes qui la précèdent. Il faut donc à chaque fois parcourir le fichier depuis le début jusqu'à ce que l'on ait lu cette information. Il est aussi impossible de reculer, c'est-à-dire de se remettre au début de l'information après l'avoir lue. En effet, les procédures de lecture dans un fichier ( Read ou Readln ) ne retournent pas le nombre de caractères qu'elles viennent de lire. On ne sait donc jamais de combien de caractères il faudrait reculer. Enfin, il est impossible de modifier une valeur au milieu d'un fichier texte car la place nécessaire à la nouvelle valeur sera très probablement

( espace, tabulations, virgule

différente de celle de l'ancienne. Donc, soit on écrasera le début de la valeur qui suit, soit on laissera entre les deux des caractères parasites. Reconnaissant ce fait, la plupart des langages informatiques, dont Pascal, interdisent d'ouvrir un fichier texte à la fois en lecture, pour en consulter le contenu, et en écriture pour en modifier une partie. Donc toute intervention sur un fichier texte nécessitera de le recopier intégralement dans un second, en intervenant “ au vol ” sur la valeur visée ; soit en ne la re- copiant pas, ce qui a pour effet de la supprimer, soit en recopiant sa nouvelle valeur pour la changer.

Ces considérations pourraient inciter à penser que les fichiers textes devraient être abandonnés au plus vite. C'est malheureusement totalement faux, puisque le format texte est bien le seul qui per- mette des échanges entre langages, programmes ou machines. Sans parler de l'archivage de l'information : il faut que les archives puissent être reconsultées dans de nombreuses années, quand les PC et Turbo Pascal auront disparu depuis longtemps.

3 Organisation des informations sur les mémoires de masse

Bien que les primitives d’accès aux fichiers offertes par Pascal soient relativement de haut niveau, et masquent de ce fait beaucoup de détails de mise en oeuvre réelle, leur comportement est fortement influencé par la façon dont l’information est effectivement déposée sur le disque. Il n’est donc pas inutile d’avoir quelques idées sur l’organisation physique et logique de l’information sur les supports.

On nomme un périphérique, tout organe extérieur à l’unité centrale de l’ordinateur capable d’échanger de l’information avec elle. On distingue les mémoires de masse et les périphériques d’entrée-sortie.

Les mémoires de masse sont des organes périphériques sur lesquels il est possible de stocker des informations de manière permanente. Ces supports sont caractérisés par leur capacité de stockage et le temps moyen pour accéder à une information. Suivant les cas, ces supports peuvent être des bandes magnétiques, des disquettes, des disques dits durs, soit magnétiques, soit magnéto- optiques ou optiques.

Les périphériques d’entrée-sortie sont des organes de communication entre l’unité centrale et le monde extérieur. Certains sont en lecture seule, comme le clavier, d’autres à écriture seule comme l’écran ou une imprimante ou mixtes comme un modem. La communication se fait généralement en mode séquentiel, par échange de caractères. L’information y est stockée de façon semi permanente. En ce sens, l’écran et l’imprimante peuvent être considérées comme des sortes de mémoire de masse. Il n’est donc pas surprenant que les primitives utilisées pour la gestion des fichiers ( et en particulier des fichiers textes ) soient très similaires à celles qui vous connaissez déjà pour les classiques entrées-sorties à l’écran ou au clavier.

3.1 Organisation physique

3.1.1 les bandes magnétiques.

Le stockage sur bandes magnétiques se fait à la suite, c'est à dire que pour accéder à une infor- mation, il faut dérouler la bande jusqu'à l'endroit où se trouve la dite information. Ce support est de grande capacité ( de 300 à 1000 M octets ) mais lent. Il tend à être remplacé par d'autres supports comme les disques optiques. L'utilisation de ce support est réservée uniquement à l'archivage.

3.1.2 les disques.

La surface du disque est recouverte d'oxyde qui peut recevoir ou fournir des informations. Le disque est divisé en pistes ( cercles concentriques ) et en secteurs ( portions de piste ). Chaque secteur contient un certain nombre d'octets ( de 128 à 1024 octets ou plus suivant les technologies ).

Pour se positionner à un endroit donné du disque ( repéré par son numéro de piste et son nu- méro de secteur ), le bras place la tête de lecture/écriture sur la piste par un mouvement horizontal, la rotation du disque permet au bout d'un certain temps de se trouver sur le secteur cherché.

Le temps de positionnement du bras est long ( par rapport au temps de lecture/écriture des in- formations ), c'est pourquoi les transferts ( en lecture ou en écriture ) se font par au moins un secteur. Un secteur est l'unité minimale pour un fichier. Même si un fichier a une taille inférieure à celle d'un secteur, la totalité de ce dernier lui sera réservé.

Afin d'optimiser les accès disque, le système d'exploitation attend d'avoir suffisamment de secteurs en attente d'écriture pour mettre en route la mécanique du disque. Il utilise des tampons mémoire qu'il purge à sa convenance vers le disque. Ceci ne doit jamais être oublié. Tant que l'on n'a pas refermé un fichier, rien ne prouve que les dernières informations ont effectivement été dépo- sées sur le disque. Donc si vous oubliez de refermer un fichier, ou si votre programme s'interrompt inopinément, le fichier associé risque fort d'être endommagé ou incomplet. Et vous ne vous en aper- cevrez qu'à la prochaine exécution, en tentant de le relire.

3.2 Organisation logique

Les grandes capacités d'informations qui peuvent être stockées sur les disques nécessitent une organisation de tous ces fichiers. Chaque périphérique contiendra des fichiers ou des répertoires ( un répertoire sera un espace doté d'un nom et contenant soit des fichiers, soit d'autres répertoires ).

Pour localiser un fichier sur un périphérique, il faudra donner la suite des noms de répertoires qu'il faut traverser depuis la racine jusqu'au répertoire qui le contient. Cette localisation s'appelle le chemin ( path ).

Les noms des périphériques, les noms de répertoires et de fichiers obéissent aux conventions du système d'exploitation utilisé.

Pour manipuler un fichier, il faudra donc impérativement :

- déclarer une variable particulière de type fichier sur laquelle se feront toutes les opéra- tions,

- associer cette variable à un nom physique, selon les conventions en vigueur pour le système d'exploitation.

4 Le type fichier en Pascal

Il y a trois classes de types fichier en Pascal : typé, non typé et texte. Les fichiers typés ou non sont les fichiers binaires. Le nombre de composants d'un fichier ( la taille du fichier ) n'est pas prédé- terminer dans la déclaration du fichier. Un fichier est donc seulement limité par la place disponible sur le support. Pascal garde la trace des accès au fichier grâce à un pointeur de fichier. Chaque fois qu'un composant est écrit ou lu dans un fichier, le pointeur de fichier est automatiquement avancé sur le composant suivant.

4.1 Déclarations des types fichiers

type fichier : type file of type type text Un fichier binaire ( typé )
type fichier
:
type file
of
type
type text
Un
fichier binaire ( typé ) est défini par le mot réservé
File Of
suivi par le
type des

composants de ce fichier. Le type des composants d'un fichier peut être quelconque, sauf un type fichier.

La déclaration suivante crée une variable interne F qui sera ensuite utilisée pour accéder à un fichier externe qui devra contenir une séquence de nombres entiers codés en binaire :

Var F : File Of Integer ;

et celles-ci mettent en place des fichiers binaires contenant des enregistrements de type Tproduit et des chaînes de 80 caractères.

Type

tNomDeProduit = String [ 80 ] ; tProduit = Record

Nom :

tNomDeProduit ;

CodeProduit :

Integer ;

Quantite :

Real ;

StockMini :

Real ;

CodeFourni :

Integer

End ;

Var

FicNoms :

File Of tNomDeProduit ;

FicProduits :

File Of tProduit ;

ou directement

 

FicNoms:

File Of String [ 80 ] ;

Un fichier texte se déclare avec le mot réservé Text :

Var OutFile : Text ;

Le fichier externe qui sera associé à une variable de type Text doit contenir de l’information en clair comme une séquence de caractères groupés en lignes ; chaque ligne est terminée par un indicateur de fin de ligne composée du caractère de code 13 ( CR ou retour chariot ) suivi du caractère de code 10 ( LF ou saut de ligne ). Le fichier est lui-même terminé par une marque de fin de fichier ( EOT de code 26 ). Le code de fin de ligne est émis à la discrétion du programmeur par un ordre Writeln. Quant au code de fin de fichier, il est déposé lors de l’appel à la procédure Close.

Les fichiers sans type se déclarent avec le simple mot File. Ce sont des canaux d'entrée-sortie de bas niveau utilisés en premier lieu pour les accès directs à tout fichier disque, quel que soit son type et sa structure. Ils utilisent par défaut une taille d'enregistrement de 128 octets. Un fichier sans type est compatible avec tout autre fichier. L'emploi d'un fichier sans type sera donc préféré pour re- nommer ou effacer un fichier, ou pour toute autre opération n'impliquant pas d'entrées-sorties. Il est possible de lire ou d’écrire dans de tels fichiers, mais il faudra à chaque fois préciser le nombre d’informations que l’on désire puisque qu’en l’absence du type des composantes, Pascal ne pourra pas le calculer lui-même.

Var DataFile : File ;

De façon interne au Pascal, une variable fichier est décrite par un enregistrement de type FileRec ( exporté par l’unité SYSTEM ). Les variables de type fichier ne peuvent pas apparaître dans les affectations ou les expressions. De même un objet fichier ne peut être passé en paramètre à une procédure que par référence : un passage par valeur impliquerait une recopie du fichier externe asso- cié.

4.2 Utilisation des fichiers

4.2.1 Association entre une variable fichier et un fichier externe

Pour pouvoir être utilisée, une variable fichier ( située en mémoire ) doit impérativement être initialisée en l’associant à un fichier externe grâce à un appel à la procédure Assign ( Var F : File ; Nom : String ) ;

Nom est une expression chaîne résultant en tout nom légal de fichier pour le système d'exploi- tation en vigueur. Le nom de fichier est affecté à la variable fichier interne F et toutes les opérations suivantes sur F affecteront le fichier externe dont le nom a été spécifié dans l'expression Nom. Assign ne doit jamais être utilisé une seconde fois sur un fichier en cours d'utilisation. Il convient dans ce cas de refermer le fichier avant de réaliser de nouveau l’association.

L’expression chaîne peut être une constante ou une variable convenablement initialisée ou le résultat de toute opération valide sur des chaînes de caractères comme :

Var

T : Text ; F : File Of Real ;

Assign ( T , 'c:\tp\travail\essai.txt' ) Assign ( F , 'essai.dat' ) ; Assign ( T , 'm:\essai.txt' ) ;

Const NomFic = ‘c:\tp\travail\donnees.txt’ ;

Assign ( T , NomFic ) ;

Var NomFic : String ;

Write ( ‘sur quel fichier va-t-on travailler ? ’ ) ; Readln ( NomFic ) ; Assign ( F , NomFic ) ;

Const Repertoire = ‘m:’ ; Fichier = ‘essai.txt’ ;

Assign ( T , Repertoire + ’\’ + Fichier ) ;

4.2.2 Ouverture d’un fichier

On doit ensuite procéder à l’ouverture du fichier, c’est-à-dire à la création par le système d’exploitation d’une zone de transfert en mémoire, le tampon d’échange. Selon le mode d’ouverture choisi ( lecture, écriture ou les deux ) l’exploitation du tampon sera différente. Les accès disque ef- fectifs se font via ce tampon à la discrétion du système d’exploitation. Il ne faut jamais préjuger qu’une information est immédiatement déposée sur le disque aussitôt après une opération d’écriture, ni qu’elle vient du disque sur une opération de lecture. Cette considération est particulièrement im- portante lors des accès à un fichier partagé sur un serveur. Ce n’est qu’à la fermeture du fichier que le système purgera le tampon et récupérera la mémoire utilisée.

Une erreur à l’exécution se produit si vous essayez de transférer un fichier qui a été ouvert en lecture à une procédure orientée en sortie ( Write ou Writeln ). De la même manière, c'est une erreur que de transférer un fichier qui a été ouvert en écriture à une procédure orientée en entrée ( Read ou Readln ).

Les procédures à utiliser pour l’ouverture d’un fichier se nomment Reset, Rewrite et Append. Elles ont un comportement diffèrent selon le type du fichier ( binaire, texte ou sans type ). Nous les détaillerons dans chaque cas dans les sections suivantes.

4.2.3 Fermeture d’un fichier

En fin d’utilisation un fichier doit être fermé pour une purge éventuelle du tampon interne ; Le répertoire du disque est mis à jour pour tenir compte des dernières modifications apportées au fichier. Ce n’est qu’après cette opération que l’on peut être assuré que toutes les opérations d’écriture sont physiquement réalisées sur le disque. L’oubli de refermer un fichier, ou un arrêt imprévu du programme avant cette fermeture risque de produire un fichier externe incomplet qu’il sera impossible de relire convenablement lors d’une prochaine exécution du programme. Remarquez qu'il est nécessaire de fermer un fichier même si vous n'avez fait que des opérations de lecture. Sinon vous auriez rapidement trop de fichiers ouverts à la fois ( 20 tampons peuvent être ouvert simultanément sous MsDos ). En Turbo Pascal, il se produit une erreur à l’exécution si l'on referme un fichier qui n'avait pas été ouvert.

4.3 Gestion des erreurs

Tous les appels de procédures et de fonctions d'entrées-sorties font automatiquement l'objet d'un contrôle d'erreur. Si une erreur survient, le programme se termine en affichant un message d'erreur à l'exécution ( Run-time error ). Ce contrôle automatique peut être désactivé et réactivé en utilisant les directives de compilation {$I-} et {$I+}. Lorsque le contrôle d'entrées-sorties est désac- tivé, c'est-à-dire lorsqu'une série d’instructions est précédée de {$I-}, une erreur d'entrée-sortie ne cause pas l'arrêt du programme, mais suspend toute autre opération d'entrée-sortie jusqu'à ce que la fonction standard IOresult soit appelée. Cette particularité permet de tenter sans risque plusieurs opérations disques en séquence et de ne tester qu’une fois à la fin si tout s'est bien passé.

Lorsque ceci est fait, la condition d'erreur est remise à zéro et les opérations d'entrée-sortie sont effectuées de nouveau. Il est alors de la responsabilité du programmeur de corriger l'erreur d'entrée-sortie. La valeur zéro retournée par IOresult indique que tout c'est bien passé. Tout autre valeur signifie qu'une erreur est survenue pendant la dernière opération d'entrée-sortie. La

signification des divers codes d'erreur est disponible dans l’aide en ligne du Turbo Pascal ( menu Help|Error Messages ).

A titre d’exemple la séquence suivante ne provoque pas l’arrêt du programme si vous tapez au clavier des caractères non numériques sur le Readln vers la variable entière N :

Var N : Integer ; {$I-} Repeat Readln ( N ) Until Ioresult = 0 ; {$I+}

Dans les sections suivantes, nous utiliserons de nombreuses fois cette technique pour fiabiliser des parties critiques de programmes.

4.4 Interventions sur le nom physique d’un fichier

Les deux opérations suivantes permettent d’intervenir directement sur le fichier externe associé à une variable fichier interne.

Procedure Erase ( Var T : File ) ;

Le fichier disque associé à T est effacé. Si ce fichier est en cours d’utilisation, il est impératif de le fermer avant de l'effacer. Le fichier physique correspondant doit exister sur le disque.

Une construction standard est la suivante :

Var F : File ; {$I-} Assign ( F , ’c:\tp\travail\essai.dat’ ) ; Erase ( F ) ; If IOresult <> 0 Then Writeln ( ‘impossible d’’effacer le fichier’ ) ; {$I+}

Procedure Rename ( Var T : File ; NouveauNom : String ) ;

Le fichier disque associé à T est renommé en NouveauNom. Le répertoire du disque est mis à jour pour montrer le nouveau nom du fichier et les opérations ultérieures sur T auront lieu sur le fichier avec ce nouveau nom. Rename ne doit jamais être utilisé sur un fichier ouvert.

Remarque : Le programmeur doit s'assurer que le fichier dénoté par NouveauNom n'existe pas déjà. Une construction standard est la suivante :

Assign ( F , NouveauNom ) ; {$I-} Erase ( F ) ; If IOresult <> 0 Then ;

Assign ( F , AncienNom ) ; Rename ( F , NouveauNom ) ; If IOresult <> 0 Then Writeln ( ‘nouveau nom invalide ou ancien nom introuvable’ ) ;

{ si erreur ne rien faire }

{$I+}

5 Les fichiers textes en Turbo Pascal

5.1 Ouverture et fermeture de fichiers textes

Avant de pouvoir être exploitée, une variable fichier interne doit avoir été associée à un fichier externe avec Assign, puis le fichier doit être ouvert par l’une des trois méthodes suivantes :

Procedure Rewrite ( Var T : Text ) ;

Un nouveau fichier disque dont le nom a été préalablement assigné à la variable T est créé et préparé pour le traitement en écriture seule. Le contenu de tout fichier préexistant avec le même nom est détruit. Un fichier disque créé par Rewrite ne contient aucune information. Par exemple les instructions suivantes créent un fichier de taille zéro sur le disque M: Vous pouvez vérifier ce fait en lisant le catalogue de M:

Assign ( T , ’m:vide.txt’ ) ; Rewrite ( T ) ; Close ( T ) ;

Procedure Reset ( Var T : Text ) ;

Le fichier disque dont le nom a été préalablement assigné à la variable T est préparé pour traitement en lecture seule. Le pointeur du fichier est positionné au début de ce fichier. Le nom du fichier externe correspondant doit exister sur le disque, sinon il se produira une erreur fatale d'entrée/sortie. La construction suivante permet d’éviter ce problème.

Assign ( F , SonNom ) ; {$I-} Reset ( F ) ; If IOresult <> 0 Then Begin Rewrite ( F ) ; Reset ( F ) ;

End ;

{$I+}

{ création } { puis réouverture en lecture }

Procedure Append ( Var T : Text ) ;

Ouvre un fichier texte existant en mode ajout, c’est-à-dire en mode écriture avec positionne- ment du pointeur de fichier à la suite de la dernière ligne. Append doit être utilisé à la place de Rewrite si l'on désire ajouter une information à la fin d'un fichier texte. La construction sui- vante permet de résoudre le problème de l’appel à Append vers un fichier qui n’existerait pas déjà sur le disque :

{$I-} Append ( T ) ; If IOresult <> 0 Then Rewrite ( T ) ; {$I+}

{ si échec alors création }

Procedure Close ( Var T : Text ) ;

Close est l’unique méthode pour refermer un fichier. La variable interne redevient disponible pour une autre association ou une ouverture du même fichier externe dans un autre mode.

Procedure Flush ( Var T : Text ) ;

Flush vide le tampon interne associé au fichier T et assure que ce tampon a été écrit sur le disque. Flush permet également de s'assurer que la prochaine instruction de lecture effectuera réellement une lecture physique depuis le disque. En environnement réseau ou pour des données sensibles, il est prudent d’appeler régulièrement Flush pour forcer l’écriture physique des dernières modifications. Flush ne doit jamais être utilisé sur un fichier fermé et ne referme pas le fichier.

5.2 Fonctions d'états sur les fichiers textes

Function Eof ( Var T : Text ) : Boolean ;

Appliquée à un fichier texte, la fonction Eof retourne Vrai si le pointeur de fichier est posi- tionné sur la marque de fin de fichier ( le caractère de code 26 ). Eof est toujours Vrai sur un fichier ouvert avec Rewrite ou Append.

La construction suivante permet un traitement global d’un fichier texte ouvert en lecture. Notez notre choix de la boucle TantQue plutôt que Répéter Jusqu’à qui permet d’éviter un grave problème de lecture impossible si le fichier était initialement vide :

While Not Eof ( T ) Do Begin

{ instructions de lecture d’une information}

{ instructions de traitement de cette information }

End ;

Function SeekEof ( Var T : Text ) : Boolean ;

Similaire à Eof, mais écarte les espaces, les tabulations ou les lignes vides avant de tester la marque de fin de fichier.

Function Eoln ( Var T : Text ) : Boolean ;

Fonction booléenne retournant Vrai si la fin de ligne a été atteinte, c'est-à-dire si le pointeur de fichier est positionné sur le caractère CR de la séquence CR/LF. Si Eof ( T ) est Vrai, alors Eoln ( T ) est également Vrai. Un exemple classique est le suivant où nous lisons le contenu du fichier ligne à ligne. Notez l’instruction Readln ( T ) qui permet lorsque Eoln est devenu Vrai de passer à la ligne suivante :

While Not Eof ( T ) Do Begin While Not Eoln ( T ) Do Begin

{instructions de lecture de la ligne courante}

End ;

End ; Readln ( T )

{ passage à la ligne suivante}

Function SeekEoln ( Var T : Text ) : Boolean ;

Similaire à Eoln, mais écarte les espaces et les tabulations avant de faire le test de fin de ligne.

5.3 Entrée et sortie de textes

Les fichiers texte utilisent les primitives Read, Readln ou Write, Writeln exactement comme pour les entrées-sorties au clavier et à l'écran. Les valeurs sont automatiquement converties à partir de ou vers leur représentation binaire sous forme de suite de caractères.

Par exemple, Read ( F, N ), N étant une variable entière, espère trouver à la position courante du fichier texte une séquence de chiffres représentant un nombre entier. Cette instruction lira cette séquence, la codera en binaire et la stockera dans la variable N. Tout caractère illégal provoquera une erreur à l’exécution, à moins que ce type d’erreur ait été désactivé.

Procedure Read ( Var T : Text ; Var V1 , V2

, Vn ) ;

,Vn sont des

variables de type simple ( caractère, chaîne, entier, ou réel ). Les variables de type booléen, énumérés ou structurés ne peuvent être lues directement et doivent faire l’objet d’un transco- dage ou d’une décomposition en type simple comme dans les exemples suivants :

Read permet l'entrée de caractères, chaînes, et de données numériques. V1, V2,

{ lecture d’un type énuméré depuis un fichier texte } Type tJour = ( lundi , mardi , mercredi , jeudi , vendredi , samedi , dimanche , erreur ) ; Var J : tJour ; L : String ;

Readln ( T , Ligne ) ; If Ligne = ‘lundi’ Then J := lundi Else If Ligne = ‘mardi’ Then J := mardi

Else

Else J := erreur ;

{ les autres cas possibles de mercredi à dimanche }

{ en cas d’autre chose ! }

{ lecture d’un enregistrement disposé sur trois lignes d’un fichier texte } Type tInfo = Record Nom , Prenom : String ; Age : Integer

End ; Var Fiche : tInfo ;

With Fiche Do Begin Readln ( T , Nom ) ; Readln ( T , Prenom ) ; Readln ( T , Age )

End ;

Procedure Readln ( Var T : Text ; Var V1 , V2 ,

, Vn ) ;

La procédure Readln est identique à la procédure Read, excepté que lorsque la dernière varia- ble a été lue, le reste de la ligne est ignoré. Tous les caractères, jusque et y compris la

prochaine séquence CR/LF, sont écartés. Après une instruction Readln, une opération Read ou Readln lira au début de la ligne suivante. Readln peut également être appelée sans paramètre. Dans ce cas, le reste de la ligne est ignoré.

Avec une variable de type Char : Read lit un caractère dans le fichier et affecte ce caractère à cette variable. Eoln devient Vrai si le caractère suivant est un retour chariot ou un la marque de fin de fichier. Eof devient Vrai si le caractère suivant est une marque de fin de fichier ou une fin de fichier physique.

Avec une variable du type String : Read lit autant de caractères que permis par la déclaration de la longueur de la chaîne, à moins que Eoln ou Eof soient rencontrés avant. Tous les caractères présents sont significatifs, y compris les espaces ou les tabulations. Si un fichier texte contient plusieurs informations de type textuel par ligne, il est donc capital de dimensionner correctement les chaînes des caractères qui recevront les valeurs lors de la lecture. Eoln devient Vrai si le caractère suivant est un retour chariot ou la marque de fin de fichier. Eof devient Vrai si le caractère suivant est une marque de fin de fichier ou une fin de fichier physique.

Par exemple si un fichier contient les informations suivantes :

Un prénom sur les 20 premières colonnes de chaque ligne Un nom sur les 20 colonnes suivantes

5}

{12345678901234567890123456789012345678901234567890}

{0

1

2

3

4

BERNARD

DUPONT

ALAIN

DURAND

MICHEL

DUBOIS

Il faudra lire ce fichier en déclarant deux variables Prénom et Nom exactement dimensionnées comme des String [ 20 ] comme suit. Notez l’utilisation de la Function Trim, de l’unité Chaînes qui permet de supprimer les espaces situés à la fin du Prénom et du Nom qui ont aussi été lus dans le fichier :

Uses Chaines ; Var Nom , Prenom : String [ 20 ] ;

While Not Eof ( T ) Do Begin Readln ( T , Prenom , Nom ) ; Prenom := Trim ( Prenom ) ; Nom := Trim ( Nom ) ; Traiter ( Prenom , Nom )

End ;

Une autre solution serait de lire le fichier un caractère à la fois et de reconstituer les chaînes nom et prénom en utilisant le premier espace comme indicateur de séparation entre les deux.

Uses Chaines ; Var C : Char ;

While Not Eof ( T ) Do Begin Nom := ‘’ ; Prenom := ‘’ ;

C := ‘*’ ;

While ( C <> ‘ ‘ ) Do

Begin

Read ( T , C ) ; Prenom := Prenom + C

End ;

While C

While Not Eoln ( T ) Do

Begin

= ‘ ‘ Do Read ( T , C ) ;

Read ( T , C ) ; Nom := Nom + C

End ; Nom := Trim ( Nom ) ; Readln ( T )

End ;

{ lecture du prénom }

{ ignorer espaces avant le nom} { lecture du nom }

{ ligne suivante }

Une solution plus simple serait de lire le fichier ligne à ligne dans une chaîne de caractères et de décomposer ensuite cette ligne en un Prénom et un Nom en recherchant le premier caractère espace. Les fonctions Pos, Copy et Delete sont des opérations standard du Turbo Pascal. ( voir l’aide en ligne ).

Var Ligne : String ;

While Not Eof ( T ) Do

Begin Readln ( T , Ligne ) ;

P := Pos ( ‘ ‘ , Ligne ) ;

Prenom := Copy ( Ligne , 1 , P - 1 ) ;

Delete ( Ligne , 1 , P ) ; Nom := Trim ( Ligne )

End ;

En conclusion, et si l’on a le choix, il est beaucoup plus simple de créer un fichier texte avec une information par ligne, ce qui permettra de le relire ultérieurement par une simple série de Readln.

Avec une variable numérique ( entier ou réel ) : Read attend une chaîne de caractères se conformant au format d'une constante numérique du type adéquat. Tous les espaces, les tabulations, les retours chariots ou les fins de ligne précédant la chaîne sont écartés. La chaîne ne doit pas avoir plus de 30 caractères, et elle doit être suivie par un espace, une tabulation, un retour chariot, ou une marque de fin de fichier.

Si la chaîne ne se conforme pas au format attendu, il se produit une erreur d'entrée/sortie. Dans le cas contraire, la chaîne numérique est convertie en une valeur du type approprié qui est affectée à la variable.

Eoln est Vrai si la chaîne est suivie d’un retour chariot ou d’une fin de fichier, et Eof est Vrai si la chaîne est terminée par une fin de fichier.

Cas particulier : Si Eoln ou Eof est Vrai au début de l'opération Read ( si on lit seulement un retour chariot sur le clavier ), aucune valeur n'est affectée à la variable qui reste inchangée.

Par exemple un fichier qui contiendrait quatre nombres entiers par ligne, séparés par des tabulations ( ou des espaces ) comme celui-ci :

1

2

3

4

5

6

7

8

9

10

11 12

devrait être lu par la séquence suivante ( avec A, B, C, D déclarées comme des entiers ) :

While Not Eof ( T ) Do Begin Readln ( T , A , B , C , D ) ; Traiter ( A , B , C , D )

End ;

Procedure Write ( Var T : Text ; Var V1 ,

La procédure Write permet la sortie de variables de type simple comme les caractères,

, simple, suivies éventuellement du signe ':' et d'une expression entière définissant la longueur du champ de sortie ( paramètres d’écriture ).

Vn sont des variables du type

chaînes, valeurs booléennes, et valeurs numériques. V1, V2 ,

, Vn ) ;

Le format d'un paramètre d'écriture dépend du type de la variable. Dans les descriptions

suivantes des différents formats et de leurs effets, nous utilisons les symboles suivants :

l, m, n

représentent une expression entière.

R

représente une expression réelle.

Ch

représente une expression caractère.

S

représente une expression chaîne.

Write ( T , Ch ) Le caractère Ch est sorti.

Write ( T , Ch:n )

dire que Ch est précédé par n - 1 espaces.

Write ( T , S ) La chaîne S est sortie.

Write ( T , S:n )

précédée par n - Length ( S ) espaces.

Le caractère Ch est sorti, justifié a droite dans un champ de n caractères, c'est-à-

La chaîne S est sortie, justifiée à droite dans un champ de n caractères. S est

Write ( T , I ) La représentation décimale de la valeur I est sortie.

Write ( T , I:n )

La représentation décimale de la valeur de I est sortie, justifiée à droite dans un

champ de n caractères.

Write ( T , R ) La représentation de la valeur de R est sortie en notation scientifique.

Write ( T , R:n )

La représentation décimale de la valeur de R est sortie, justifiée à droite dans un

champ de n caractères, en utilisant la notation en virgule flottante.

Write ( T , R:n:m ) La représentation décimale de la valeur de R est sortie, justifiée à droite dans un champ de n caractères, en utilisant la notation en virgule flottante avec m chiffres après le point déci- mal. Si m est égal à zéro, il n'y a pas de partie décimale ni de point sortis. Le nombre est précédé par un nombre d'espaces approprié pour remplir un champ de n caractères.

Procedure Writeln ( Var T :

La procédure Writeln est identique à la procédure Write, avec l'émission d'une séquence CR/LF après la dernière valeur.

Une instruction Writeln sans paramètres d'écriture écrit seulement une ligne vide ou termine la ligne en cours.

Text ; Var V1 , V2 ,

, Vn ) ;

6 Manipulations simples de fichiers textes

Le programme suivant crée un fichier texte contenant les nombres de 10 à 20 à raison de un par ligne, soit onze lignes.

Program CreeFichierDeNombres ;

Var

I : Integer ; Begin Assign ( T , 'NB10A20.TXT' ) ; Rewrite ( T ) ;

T : Text ;

{ ouverture en écriture }

For I := 10 To 20 Do Writeln ( T , I ) ; { accès en écriture}

Close ( T )

{ fermeture}

End .

Puisque le fichier ainsi créé est un texte, vous pouvez le relire avec n'importe quel traitement de texte et éventuellement le modifier. Pour en voir le contenu il suffit de l'ouvrir avec Turbo Pascal, en n'oubliant pas de spécifier l'extension ( TXT ). Vous devriez voir apparaître dans une nouvelle fenêtre :

10

11

12

20

exercice 6.1 : quel sera l'aspect du fichier NB10A20.TXT si on remplace le Writeln ( T , I ) par Write ( T , I ) ou par Write ( T , I:4 ) ? exercice 6.2 : quel serait l'aspect du fichier NB10A20.TXT si on omettait l'instruction Close ?

Nous pouvons aussi le relire par un programme Pascal symétrique, qui ouvre le fichier en lec- ture, puis relit chaque nombre par des Readln, puisque nous avions déposé un nombre par ligne, et les affiche à l'écran :

Program RelitFichierDeNombres ;

Var

T : Text ;

I , N : Integer ;

Begin Assign ( T , 'NB10A20.TXT' ) ; Reset ( T ) ; For I := 1 To 11 Do

Begin

Readln ( T , N ) ; Writeln ( N )

End ; Close ( T ) ; Readln

End .

{ ouverture en lecture seule }

{ lecture d'un nombre } { et affichage }

{ fermeture } { pour avoir le temps de voir le résultat ! }

exercice 6.3 : le programme suivant n'affiche pas correctement les 11 nombres. Pourquoi ? Assign ( T , 'NB10A20.TXT' ) ; Reset ( T ) ; For I := 1 To 11 Do Begin

Readln ( F , I ) ; Writeln ( I )

End ; Close ( T ) ;

Le programme précédent risque de poser problème si le fichier NB10A20.TXT ne contient pas exactement 11 nombres. S'il en a moins, il s’arrêtera avec une erreur fatale d'entrée-sortie ( lecture au-delà de la fin d'un fichier ) et s'il y en plus, il n'affichera que les onze premiers. Nous lui préfére- rons donc la version suivante qui affiche l'ensemble du fichier, quelle que soit sa taille grâce à l'utilisation de la fonction Eof :

Program RelitFichierDeNombres ;

Var

T : Text ;

I : Integer ;

Begin Assign ( T , 'NB10A20.TXT' ) ; Reset ( T ) ; While Not Eof ( T ) Do

Begin

Readln ( T , I ) ; Writeln ( I )

End ; Close ( T ) ; Readln

End .

exercice 6.4 : comment relire le fichier NB10A20.TXT s'il avait été fabriqué avec Write ( T , I ) ou Write ( T , I:4 ) ?

Un des gros avantages des fichiers textes est qu'ils peuvent toujours être considérés comme des fichiers de caractères. Pour les relire il suffit donc d'en extraire l'information un caractère à la fois comme le montre l'exemple suivant. Remarquez l'utilisation de Read qui lit exactement la quantité

d'information spécifiée par le type de son paramètre ( ici un caractère ) et non pas de Readln qui chercherait à changer de ligne entre chaque caractère du fichier.

Program RelitToutFichierTexte ;

Var

Nom : String ; C : Char ; Begin Write ( 'nom du fichier à lire : ' ) ; Readln ( Nom ) ; Assign ( T , Nom ) ; Reset ( T ) ; While Not Eof ( T ) Do Begin

T : Text ;

Read ( T , C ) ; Write ( C )

End ; Close ( T ) ; Readln

End .

Si le fichier est organisé en lignes, on peut aussi le relire une ligne à la fois, par une série de Readln vers une variable de type chaîne de caractères. Il importe seulement qu'aucune ligne ne soit plus longue que la taille maximale permise par un type String, soit 256 caractères.

Program RelitToutFichierTexteAvecDesLignespasTropLongues ;

Var

Nom , Ligne : String ; I , N : Integer ; Begin Write ( 'nom du fichier à lire : ' ) ; Readln ( Nom ) ; Assign ( T , Nom ) ; Reset ( T ) ; While Not Eof ( T ) Do

T : Text ;

Begin

Readln ( T , Ligne ) ; Writeln ( Ligne )

End ; Close ( T ) ; Readln

End .

exercice 6.5 : écrire un programme qui affiche le nombre de lignes d'un fichier texte, d'abord en admettant que chaque ligne a une longueur inférieure à 256 caractères puis en levant cette restric- tion.

exercice 6.6 : écrire un programme qui affiche le nombre de mots d'un fichier texte, d'abord en ad- mettant que chaque ligne a une longueur inférieure à 256 caractères puis en levant cette restriction.

exercice 6.7 : écrire un programme qui affiche le nombre de chiffres et de lettres contenu dans un fichier texte.

exercice 6.8 : écrire un programme qui affiche le numéro de chacune des 26 lettres de l'alphabet contenu dans un fichier texte. On utilisera une fonction CarMajuscule ( C : Char ) : Char qui con- vertit en majuscule un caractère si nécessaire et le renvoie inchangé sinon. Pour tester ce pro- gramme, il vous suffira d'utiliser l'unité Chaînes disponible sur le serveur qui exporte cette fonction.

7 Modification du contenu d'un fichier texte

Comme nous l'avons déjà signalé, un fichier texte ne peut pas être ouvert simultanément en lecture et en écriture. L'appel à Reset interdit l'utilisation des primitives de type Write et l'appel à Rewrite commence par détruire le contenu du fichier ! Il est donc impossible de modifier le contenu d'un fichier texte en écrivant la nouvelle valeur d'une information par dessus l'ancienne, et encore moins d'y insérer ou d'y supprimer une information. En effet ces opérations nécessitent que l'on lise le contenu du fichier jusqu'à l'information voulue, puis que l'on écrive à cet endroit précis, et enfin que l'on continue à lire la suite du fichier.

Toute intervention sur un fichier texte nécessite donc l'utilisation d'un second fichier texte tem- poraire selon la méthode générale suivante :

- on ouvre en lecture le fichier originel,

- on ouvre en écriture le fichier temporaire, ce qui aura pour effet de le créer s'il n'existe pas ou d'en détruire un contenu précédent, - on recopie le fichier originel dans le fichier temporaire jusqu'à ce que l'on ait atteint l'information visée,

- on réalise l'intervention sur le fichier temporaire comme suit :

-

écriture d'une autre valeur en cas de modification,

-

écriture d'une nouvelle information en cas d'ajout,

-

pas d'action pour une suppression,

-

- on poursuit la recopie du fichier originel vers le fichier temporaire jusqu’à sa fin.

Et ce n'est pas fini ! A la fin de cette opération, le fichier disque qui a le nom utilisé par le pro- gramme contient toujours l'ancienne version de l'information. C'est le fichier dont le nom est celui choisi pour le fichier temporaire qui contient la bonne version. Il faut donc :

- soit recopier le fichier temporaire dans le fichier originel et supprimer le fichier temporaire,

- soit “ échanger ” les noms sur le disque par une utilisation judicieuse des primitives Rename et Erase vues ci-dessus.

Si vous avez déjà réalisé des insertions de morceaux de musique au beau milieu de cassettes audio, cette méthode doit vous êtres familière. Il vous faut deux lecteurs de cassettes, un pour lire et l'autre pour enregistrer et à la fin vous mettez à jour les étiquettes sur les bandes.

Afin de simplifier l'écriture de ces algorithmes, nous allons utiliser les procédures utilitaires ci- dessous :

procedure OuvreEnLecture ( Var F : Text ; SonNom : String ) ; Ouvre le fichier texte F en lecture, en l'associant au fichier physique SonNom.

procedure OuvreEnEcriture ( Var F : Text ; SonNom : String ) ; Ouvre le fichier texte F en écriture, en l'associant au fichier physique SonNom.

Procedure SupprimeFichier ( SonNom : String

);

Faire disparaître du disque le fichier de nom SonNom.

procedure DuplicFichier ( NomOriginel , NomFinal : String ) ; Duplique le contenu du fichier disque dont le nom est NomOriginel vers celui dont le nom est NomFinal et supprime du disque le fichier NomOriginel.

Le code de chacune de ces procédures est fort simple.

Procedure OuvreEnLecture ( Var F : Text ; SonNom : String ) ; Begin Assign ( F , SonNom ) ; Reset ( F )

End ;

Procedure OuvreEnEcriture ( Var F : Text ; SonNom : String ) ; Begin Assign ( F , SonNom ) ; Rewrite ( F )

End ;

Procedure SupprimeFichier ( SonNom : String ) ;

Var

Begin Assign ( F , SonNom ) ; Erase ( F )

End ;

F : Text ;

Procedure DuplicFichier ( NomOriginel , NomFinal : String ) ;

Var

C : Char ; Begin OuvreEnLecture ( F , NomOriginel ) ; OuvreEnEcriture ( G , NomFinal ) ; While Not Eof ( F ) Do Begin

F, G : Text ;

Read ( F , C ) ; Write ( G , C )

End ; Close ( F ) ; Close ( G ) ; SupprimeFichier ( NomOriginel ) End ;

En reprenant l'exemple du fichier de nombres, avec un nombre par ligne, voici la procédure d'ajout d'un nouveau nombre à la fin de ce fichier. Notez le choix d'un nom suffisamment exotique pour le fichier temporaire afin de ne pas détruire un fichier qui existerait sur le disque

Procedure AjouteEnFin ( NomFichier : String ; UnNombre : Integer ) ;

Var

X : Integer ; Begin OuvreEnLecture ( F , NomFichier ) ; OuvreEnEcriture ( Tempo , 'ABC.$$$' ) ; While Not Eof ( F ) Do

F , Tempo : Text ;

{recopie d'abord tout le fichier original }

End ;

Begin

Readln ( F , X ) ; Writeln ( Tempo , X )

{ dans le fichier temporaire}

End ; Writeln ( Tempo , UnNombre ) ; Close ( F ) ; Close ( Tempo ) ; DuplicFichier ( 'ABC.$$$' , NomFichier )

{ puis écrit le nouveau nombre à la suite}

{ recopie du temporaire

{ vers l'original et destruction }

{ du fichier temporaire }

exercice 7.1 : le Turbo Pascal possède une primitive non standard nommée Append, qui permet d'ouvrir un fichier texte existant en écriture sans le détruire et de se positionner directement à la fin. Exploiter cette primitive pour obtenir une version plus simple de AjouteEnfin.

exercice 7.2 : sur le modèle précédent écrire une procédure AjouteEntete qui insère un nouveau nombre passé en paramètre au début d'un fichier texte.

exercice 7.3 : sur le modèle précédent écrire une procédure AjouteApres qui insère un nouveau nombre après un autre nombre ( passés en paramètre ) contenu dans un fichier texte.

exercice 7.4 : sur le modèle précédent écrire une procédure AjouteAvant qui insère un nouveau nombre avant un autre nombre ( passés en paramètre ) contenu un fichier texte.

exercice 7.5 : comment se comportent les deux procédures précédentes si le nombre après ou avant lequel il faut ajouter le nouveau existe plusieurs fois dans le fichier texte ? Comment résoudre ce problème ?

La suppression d'une information de notre fichier de nombres est très similaire. Il faut recopier l'originel dans le temporaire, sauf les nombres qui seraient trouvés égaux à celui que l'on cherche à éliminer. Notez le paramètre supplémentaire Ok qui vaudra Vrai en sortie si l'on a réalisé au moins une suppression.

Procedure SupprimeTout ( NomFichier : String ; UnNombre : Integer ; Var Ok : Boolean ) ; { supprime toutes les occurrences de UnNombre }

Var

X : Integer ; Begin OuvreEnLecture ( F , NomFichier ) ;

F , Tempo: Text ;

OuvreEnEcriture ( Tempo , 'ABC.$$$' ) ; Ok := False ;

While Not Eof ( F ) Do Begin Readln ( F , X ) ; If X <> UnNombre Then Writeln ( Tempo , X )

{ tant que l'on a pas atteint la fin du fichier }

{ on ne recopie que les nombres différents } { de celui à supprimer }

Else

End ;

Ok := True

{ on l'a trouvé, donc on ne recopie pas et } { on indique le succès }

End ; Close ( F ) ; Close ( Tempo ) ; DuplicFichier ( 'ABC.$$$' , NomFichier )

exercice 7.6 : sur le modèle ci-dessus, écrire une procédure nommée SupprimeNieme qui supprime la Nieme occurrence d'un certain nombre d'un fichier texte. En déduire une nouvelle version de SupprimeTout. Quelle est son temps d’exécution par rapport à la version ci-dessus ?

exercice 7.7 : de façon analogue écrire des procédures nommées RemplaceNieme et RemplaceTout qui remplacent la Nieme occurrence ou toutes les occurrences d'un certain nombre par une nouvelle valeur dans un fichier texte.

Une version plus subtile de DuplicFichier serait de ne pas recopier les contenus des fichiers, mais simplement d'échanger leurs noms sur le disque. L'idée d'utiliser directement la procédure Rename du Turbo Pascal ne fonctionne pas, car il n'est pas possible d’affecter à un fichier un nom qui existerait déjà sur le disque. Dans ce contexte d'appel, NomOriginel représente le nom 'ABC.$$$' et NomFinal 'NB10A20.TXT' et ces deux fichiers existent réellement sur le disque.

Procedure DuplicFichier ( NomOriginel , NomFinal : String ) ; { fausse } Var F : Text ; Begin Assign ( F , NomOriginel ) ; Rename ( F , NomFinal )

End ;

D'ou la version suivante où l'on commence par supprimer du disque le fichier ayant le nom NomFinal ( 'NB10A20.TXT' ) puis on renomme le fichier ABC.$$$ en NB10A20.TXT. Bien sûr cette procédure ne doit être appelée que dans un contexte où le nom spécifié par NomOriginel existe bien sur le disque, ce qui est notre cas.

Procedure DuplicFichier ( NomOriginel , NomFinal : String ) ; Var F : Text ; Begin Assign ( F , NomFinal ) ; Erase ( F ) ; Assign ( F , NomOriginel ) ; Rename ( F , NomFinal )

End ;

Une version beaucoup plus générale est la suivante. Pour renommer un fichier en un autre il faut essayer de supprimer le nouveau nom du disque, au cas où il existerait, ignorer l'erreur s'il n'existait pas et réaliser enfin le renommage.

Procedure DuplicFichier ( NomOriginel , NomFinal : String ) ; Var F : Text ; Begin Assign ( F , NomFinal ) ;

End ;

{$I-} Erase ( F ) ; If IoResult <> 0 Then ; Assign ( F , NomOriginel ) ; Rename ( F , NomFinal ) ; If IoResult <> 0 Then {$I+}

{ pas d'arret sur erreur }

{ si il y a eu erreur tant pis }

{ si il y a eu erreur tant pis } { retour à la normale }

8 Les fichiers textes pour l'échange de données

Comme nous l’avons déjà signalé, l’intérêt majeur des fichiers textes est de permettre l’échange d’informations entre des programmes différents. Par exemple, le logiciel tableur Excel est capable de relire directement le contenu d'un fichier texte organisé en lignes. Les valeurs à placer dans les différentes colonnes de chaque ligne Excel doivent être séparées par une tabulation ( le caractère de code ASCII 9 ). Si nous avions fabriqué un fichier texte avec un nom, un prénom et une note par ligne, il est possible de le récupérer directement avec Excel comme le montre la boîte de dialogue Ouvrir ci- dessous :

Les données peuvent alors être traitées sous Excel ( triées, harmonisées, etc. ). Excel peut

Les données peuvent alors être traitées sous Excel ( triées, harmonisées, etc. ).

être traitées sous Excel ( triées, harmonisées, etc. ). Excel peut enfin enregistrer de nouveau cette

Excel peut enfin enregistrer de nouveau cette feuille de calcul modifiée au format texte avec divers séparateurs comme le montre la boîte de dialogue Enregistrer sous ci-dessous :

Pour notre fichier, nous obtiendrons le contenu suivant : nom prénom note dubois roger 8

Pour notre fichier, nous obtiendrons le contenu suivant :

nom prénom note dubois roger

8

durand michel

4

dupont rené

12

duval

martin paul

pierre 14

10

exercice 8.1 : écrire un programme en Turbo Pascal qui crée un fichier texte contenant sur chaque ligne un nom, un prénom et une note. Relire le fichier avec Excel et calculer la somme, moyenne et écart-type de ces notes.

exercice 8.2 : créer avec Excel un fichier texte contenant une colonne de noms, une colonne de prénoms et une colonne de notes. Relire ce fichier avec un programme Pascal qui affichera les nom, prénom et note et calculera la moyenne des notes.

9 Périphériques dans Turbo Pascal

Turbo Pascal et le système d'exploitation MsDos considèrent les équipements externes tels que le clavier, l'écran et l'imprimante comme des périphériques. Du point de vue du programmeur, un périphérique est traité comme un fichier texte et les opérations sont exécutées avec les mêmes procé- dures et fonctions. Les périphériques MsDos sont implémentés grâce à des noms de fichiers réservés

ayant une signification particulière. En réalité, Turbo Pascal n'a pas conscience du fait qu'une varia- ble fichier se réfère à un périphérique plutôt qu'à un fichier sur disque. Par exemple, le programme '

écrira la chaîne de caractères 'Hello la planète exactement la même que pour un fichier sur disque.

sur l'imprimante, bien que la syntaxe utilisée soit

Var Sortie : Text ; Begin Assign ( Sortie , 'LPT1:' ) ; Rewrite ( Sortie ) ; Writeln ( Sortie , 'Hello la planète Close ( Sortie )

'

) ;

End .

Nous donnons ci-dessous la liste des périphériques MSDOS les plus connus :

Le périphérique sans nom

Ce périphérique correspond à la console, qui permet d'envoyer des informations vers l'écran et d'en recevoir à partir du clavier. Les fichiers standard Input et Output et tous les fichiers assignés à un nom de fichier vide se référent implicitement à ce périphérique. Lorsque les entrées et les sorties ne sont pas redirigées, Input et Output sont automatiquement ouverts au début du programme. De la même manière, Input et Output sont automatiquement refermés à la fin du programme. Tous les ordres Read et Write sans paramètres indiquant le nom du fichier se réfèrent à ce périphérique.

Dans le cas de redirection des entrées-sorties au niveau de MsDos, il suffit d’associer Input- Output au périphérique sans nom pour que toutes les entrées-sorties écran-clavier s’adressent aux fichiers redirigés.

Par exemple, le programme suivant ( REDIR.PAS ) vous demande votre nom et vous salue.

Program Redirection ; Var Nom : String ; Begin { prise en compte d’une éventuelle rédirection par MsDos } Assign ( Output , '' ) ; Rewrite ( Output ) ; Assign ( Input , '' ) ; Reset ( Input ) ; Writeln ( ‘votre nom ‘ ) ; Readln ( Nom ) ; Writeln ( ‘ Bonjour ’ , Nom ) ; Close ( Output ) ; Close ( Input )

End .

Si vous le lancer par REDIR, vous verrez apparaître à l’écran la question, vous répondrez au clavier et le salut s’affichera à l’écran.

Si vous le lancer par REDIR > BONJOUR.TXT , tous les ordres Writeln se feront vers un fichier texte BONJOUR.TXT. par contre vous devrez taper au clavier votre nom.

< MOI.TXT , il cherchera à lire votre nom

Si vous le lancer par REDIR > BONJOUR.TXT

dans le fichier MOI.TXT puis déposera la réponse dans le fichier BONJOUR.TXT.

Les périphériques LPT1, LPT2 et LPT3

La famille de périphériques LPTx concerne les trois imprimantes que vous pouvez utiliser sur votre système. Pour la première imprimante, vous pouvez utiliser le synonyme PRN.

Note : l'unité standard Printer contient la déclaration d'une variable de fichier texte appelée Lst

qu'elle associe au périphérique LPT1. Pour facilement écrire quelque chose sur l'imprimante depuis l'un de vos programmes, il vous suffit d'inclure l'unité Printer dans la clause Uses du programme et

d'utiliser Write ( Lst ,

) ou Writeln ( Lst ,

).

Les périphériques COM1 et COM2

Les périphériques de communication concernent les deux ports de communication série. Le synonyme AUX peut être utilisé pour COM1. Ce périphérique est peu exploitable tel quel car MsDos gère très mal la communication à travers les ports série.

Le périphérique NUL

Ce périphérique ignore toute écriture et génère une fin de fichier immédiate lorsque vous es- sayez d'en lire le contenu. Vous l'utiliserez lorsque vous ne voudrez pas créer de fichier particulier, alors que le programme désire tout de même une entrée ou une sortie d'information.

Le programme suivant montre l'emploi des périphériques pour sortir à volonté le contenu d'un fichier typé nommé PRODUITS.DAT vers l’écran, l’imprimante ou un véritable fichier texte ( PRODUITS.TXT ).

Program ListProducFile ;

Type

{ déclarations articles du fichier binaire } tProduit = Record

Nom :

String [ 80 ] ;

CodeProduit : Integer ;

 

Quantite :

Real ;

CodeFourni :

Integer ;

End ;

Var

FicProduits :

Produit :

Sortie :

Rep :

File Of tProduit ;

{ le fichier binaire}

tProduit ;

{ tampon de lecture }

Text ;

{ le fichier texte de sortie}

Char ;

Begin Assign ( FicProduits , 'PRODUITS.DAT' ) ; Reset ( FicProduits ) ; Writeln ( ‘sortie vers Ecran Imprimante Fichier (E I F )’) ;

Repeat Readln ( Rep ) Until Upcase ( Rep ) In [ ‘E’ , ’I’ , ’F’ ] ; Case Rep Of

‘E’ , ’e’ :

Assign ( Sortie , ’’ ) ;

{ vers l’écran fichier anonyme}

‘I’ , ’i’ :

Assign ( Sortie , ’PRN’ ) ;

{ PRN : nom de l’imprimante}

‘F’ , ’f’ :

Assign ( Sortie , ’PRODUITS.TXT’ )

{ un vrai fichier texte}

End ; Rewrite ( Sortie ) ; Writeln ( Sortie , ’Code Produit’:10 , ’Nom ‘:20 , ’Quantité’:20 , ’Code Fournisseur’:10 ) ; While Not Eof ( FicProduits ) Do Begin Read ( FicProduits , Produit ) ; With Produit Do Writeln ( Sortie , CodeProduit:10 , Nom:20 , Quantite:20:2 , CodeFourni:10 )

End ; Close ( FicProduits ) ; Close ( Sortie ) ;

End .

10 Les fichiers binaires en Turbo Pascal

10.1 Ouverture et fermeture des fichiers binaires

Tout comme avec les fichiers textes, une variable fichier binaire doit avoir été associée à un fichier externe avec Assign, puis être ouverte par Reset ou Rewrite. Notez que les procédures Reset et Rewrite ont un comportement identique à celles des fichiers textes mais que dans les deux cas le fichier sera ouvert en lecture et écriture. La procédure Append n’est pas permise avec ce type de fichier.

Il est regrettable que Turbo Pascal n’ait pas une procédure spécifique d’ouverture d’un fichier binaire avec création automatique si nécessaire. Un problème sérieux est celui de la création du fichier lors de la première exécution d’un programme. La première fois, le fichier doit être créé avec un appel à Rewrite, qui reste la seule méthode pour créer un fichier vide. Un appel à Reset conduirait à une erreur d’exécution puisque le fichier n’existe pas encore sur le disque. Par contre, dés la seconde exécution il faut impérativement faire appel à Reset pour ouvrir simplement le fichier sans en détruire le contenu qui aurait été déposé à l’exécution précédente. Comment un programme peut-il “ savoir ” si c’est la première fois qu’il est utilisé ?

La méthode standard est la suivante. On désactive les erreurs d’entrée-sortie, puis on essaie d’utiliser Reset. S’il y a eu une erreur, c’est que le fichier n’existait pas et on le crée avec Rewrite :

Assign ( F , SonNom ) ; {$I-} Reset ( F ) ; If IOresult <> 0 Then Rewrite ( F ) ; {$I+}

{ création }

L’appel à Close reste l’unique méthode pour refermer un fichier. La variable interne redevient disponible pour une autre association ou une ouverture du même fichier externe dans un autre mode.

La procédure Flush qui permettait de forcer une écriture physique du tampon vers le disque n’existe plus avec les fichiers binaires. Pour la remplacer il faut refermer le fichier et le ré-ouvrir de nouveau par :

Close ( F ) ; Reset ( F )

A la différence de Flush, cette opération remet le pointeur de fichier au début du fait de l’appel à Reset.

10.2 Fonctions d'états sur les fichiers binaires

De façon interne, un fichier binaire peut être considéré comme un “ tableau hors mémoire ” de composantes de même type. Par analogie avec une simple boîte à fiches, nous nommerons fiche une composante. Chaque fiche est repérée par un indice variant de 0 à la taille du fichier moins 1. La valeur de cet indice, nommé le pointeur de fichier est privée au type File et ne peut pas être manipu- lée directement. Le type de cet indice est Longint ( entier long codé sur 32 bits ) soit une valeur maximale de 2 31 - 1 ( environ 2 milliards ) puisque l’indice ne peut être négatif.

A chaque opération de lecture ou d’écriture la valeur du pointeur de fichier est automatique- ment incrémentée.

On le manipule à travers une série de procédures et fonctions comme :

Function FilePos ( Var F : File ) : Longint ;

Fonction entière retournant la valeur actuelle du pointeur de fichier. Avec un fichier qui vient d’être ouvert la valeur est zéro.

Function FileSize ( Var F : File ) : Longint ;

Fonction entière retournant la taille d'un fichier disque exprimée en nombre de fiches. Elle cor- respond donc à la valeur maximale que l’on peut affecter au pointeur de fichier. Si FileSize est égale à 0 le fichier est vide. Pour retrouver la taille exacte en octets d’un fichier ( comme af- fichée par le catalogue de la disquette ) il faut donc multiplier FileSize ( F ) par la taille en octets de chaque fiche obtenue à partir de sa déclaration de type composante ou grâce à la fonction standard Sizeof.

Var F : File Of Integer ;

Write ( ‘taille du fichier en octets égale = ‘ ) ; Writeln ( FileSize ( F ) * 2 ) ) ; ou mieux Writeln ( FileSize ( F ) * Sizeof ( Integer ) ) ;

Procedure Seek ( Var F : File ; N : Longint ) ;

Seek déplace le pointeur de fichier sur la Nieme fiche du fichier référencé par F. N est une ex- pression entière de type entier long ( Longint ). La position de la première fiche est 0. Donc l’instruction Seek ( F , 0 ) permet de se replacer au début du fichier.

Pour pouvoir agrandir un fichier on doit déplacer le pointeur juste après la dernière fiche et écrire à cet endroit. On utilise la construction suivante :

Seek ( F , FileSize ( F ) ) ;

FileSize retourne le nombre de fiches d'un fichier et comme les éléments commencent au nu- méro 0, le nombre retourné est le rang du dernier composant plus 1. A cet endroit il n’est pos- sible que d’écrire puisque l’on se trouve au-delà de la fin physique du fichier.

Function Eof ( Var F : File ) : Boolean ;

Appliquée à un fichier binaire, la fonction Eof retourne Vrai si le pointeur de fichier est posi- tionné après la dernière composante d’un fichier. C’est à dire si FileSize est égal à FilePos. Eof reste toujours Vrai sur un fichier ouvert avec Rewrite.

La construction suivante permet un traitement global d’un fichier binaire ouvert. Notez notre choix de la boucle TantQue plutôt que Répéter Jusqu’à qui permet d’éviter un grave problème de lecture impossible si le fichier était initialement vide :

Seek ( F , 0 ) ; While Not Eof ( F ) Do Begin

{ instructions de lecture d’une information}

{ instructions de traitement de cette information }

End ;

Nous préférons cette construction à la suivante qui est équivalente, mais utilise une variable I supplémentaire :

Var I : Longint ;

For I := 0 To FileSize ( F ) - 1 Do Begin Seek ( F , I ) ; { lire et traiter la composante courante }

End ;

Procedure Truncate ( Var F : File ) ;

Coupe le fichier à la position courante ( définit cette position en tant que fin du fichier ). Noter qu’il s’agit de l’unique moyen de réduire la taille d’un fichier disque sans le recopier dans un autre.

La séquence suivante détruit la dernière fiche d’un fichier et réduit donc sa taille sur le disque d’une fois la taille d’une fiche.

Seek ( F , FileSize ( F ) - 1 ) ; Truncate ( F )

Celle-ci détruit l’ensemble du contenu d’un fichier. Elle est équivalent à Rewrite ( F ) :

Seek ( F , 0 ) ; Truncate ( F )

10.3 Entrée et sortie de composantes

Les fichiers binaires utilisent les primitives Read ou Write pour échanger des composantes ( fiches ) entre des variables du type approprié et le fichier physique. Readln et Writeln ne sont pas autorisés car il n’y a pas de conversion binaire texte dans ce cas.

Procedure Read ( Var F : File ; Var C ) ;

La procédure Read permet la lecture de composantes depuis le fichier. Le type de la variable C doit être exactement identique à celui cité dans la déclaration “ File Of ” de la variable fichier F.

La séquence suivante traite l’ensemble des fiches d’un fichier supposé être de type tFiche :

{ lecture des enregistrements d’un fichier binaire } Type tFiche = Record Nom , Prenom : String ; Age : Integer

Var

End ;

Fiche : tFiche ; F : File Of tFiche ;

Seek ( F , 0 ) ; While Not Eof ( F ) Do Begin Read ( F , Fiche ) ; Traiter ( Fiche )

End ;

Notez qu’il n’est pas permis de lire une partie d’une composante. La construction suivante sera rejetée par le compilateur avec une erreur de type :

Var

F : File Of tFiche ; Fiche : tFiche ;

Read ( F , Fiche.Nom ) ;

Si l’on a besoin de consulter le nom d’une personne, il faut lire l’intégralité de cette fiche dans une variable du type approprié puis en extraire le nom. L’exemple suivant affiche les noms inscrits sur toutes les fiches d’un fichier déclaré comme ci-dessus :

Seek ( F , 0 ) ; While Not Eof ( F ) Do Begin Read ( F , Fiche ) ; Writeln ( Fiche.Nom )

End ;

Comme nous l’avons déjà signalé, il importe qu’il y ait une correspondance exacte entre le type réel des fiches contenues dans un fichier et la déclaration qui en est faite en Pascal. Si vous ignorez le type des fiches, il est impossible d’exploiter le fichier. Rien ne vous interdit de créer un fichier avec un certain type de fiche et de le relire ultérieurement avec un autre. Mais il est plus que probable que le traitement du fichier se passera très mal ; soit parce que les valeurs binaires lues n’auront aucun rapport soit parce que vous attendrez inopinément la fin du fichier au beau milieu d’une lecture parce que la taille déclarée de chaque fiche ne correspondra pas à la réalité.

exercice machine 10.3.1 : créer un fichier binaire contenant une dizaine de nombres réels ( File Of Real ). Puis relire ce fichier comme un File Of LongInt et un File Of Integer.

exercice machine 10.3.2 : créer un fichier contenant des fiches de type Nom, Prenom : String [ 10 ] puis relire ce fichier comme un fichier de type Nom, Prenom : String [ 10 ] ; Age : Integer. Que constatez -vous ? Que faudrait-il faire pour ajouter le champ âge à chaque fiche du fichier ?

Procedure Write ( Var F : File ; C ) ; La procédure Write permet d’écrire des fiches à la position courante dans le fichier. Le type de la variable C doit être exactement identique à celui cité dans la déclaration “ File Of ” de la variable fichier F.

Comme avec Read, il y a avance automatique du pointeur de fichier à la fiche suivante. Il est de nouveau interdit d’écrire une partie d’une fiche.

L’exemple suivant ajoute un an à l’âge de toutes les fiches de type ( nom, prénom, âge ) du fichier supposé ouvert :

Seek ( F , 0 ) ; While Not Eof ( F ) Do Begin Read ( F , C ) ; C.Age := C.Age + 1 ; Seek ( F , FilePos ( F ) - 1 ) ; Write ( G , C ) ;

End ;

exercice 10.3.3 : quel est le but de l’instruction Seek ( dessus ?

F , FilePos ( F ) - 1 ) dans l’exemple ci-

11 Modifications du contenu de fichiers binaires

Le fait que les fichiers binaires soient très similaires à des tableaux pourrait laisser penser que les algorithmes habituels de mise à jour de l’information ( ajouts, suppressions, modifications, etc. ) leur sont applicables ; ces algorithmes sont détaillés dans le chapitre 3 ( Méthodologie de développement ). Ceci est probablement vrai avec de petits fichiers mais certainement pas avec des applications en grandeur réelle.

La différence majeure est qu’un fichier ne réside pas en mémoire mais sur un disque. La taille d’un fichier est souvent beaucoup trop importante pour que l’on puisse envisager de le lire en mémoire dans un tableau ( ou une autre structure de données ) pour le traiter avec un algorithme classique puis de réécrire le résultat sur le disque. Les algorithmes à utiliser doivent donc travailler une fiche à la fois, avec d’incessants échanges entre une variable de type tFiche et la composante correspondante du fichier.

Ces opérations sont longues et dangereuses puisque durant tout le traitement le fichier est ouvert et reste vulnérable. Toute anomalie de fonctionnement du matériel ou du logiciel le laisserait dans un état irrécupérable.

De plus, au contraire d’un tableau où l’on peut aisément gérer le nombre maximal de composantes, se pose dans le cas des fichiers le problème de la capacité disque qu’il convient de surveiller avant de tenter tout agrandissement ou toute recopie d’un fichier.

Imaginons l’insertion d’une fiche d’une centaine de caractères (nom, prénom, adresse et code postal ) au début d’un fichier de 10000 adresses. Cette opération nécessite :

- soit, en exploitant l’accès direct, le décalage des 10000 fiches d’une position vers le bas c’est- à-dire 10000 lectures et 10000 écritures. Avec un disque dur rapide ( temps d'accès 10ms, temps d’écriture d’un octet 0.02 ms ) ces opérations prendraient 20000 * 10ms ( accès ) + 10000 * 100 * 0.02ms ( lecture ) + 10000 * 100 * 0.02 ms ( écriture ) soit 240 secondes ( 3 minutes ! )

- soit, comme nous l’avons fait avec les fichiers textes, la création d’un second fichier sur

lequel on écrira d’abord la nouvelle fiche puis ou l’on recopiera à la suite le contenu du fichier

originel ( 10000 lectures et 10000 écritures de nouveau ). Cette méthode aura l’avantage de la sûreté puisque l’on aura toujours sur le disque la copie de l’original. En espérant qu’il y aura sur le disque suffisamment de place pour deux copies du fichier. Sans compter l’obligation de recopier le nouveau fichier sur l’ancien, ou si le système le permet, de détruire l’ancien fichier et de renommer le nouveau.

Ces temps devront être multipliés par plusieurs dizaines si l’on effectue un tri de ce fichier, c’est-à-dire par des opérations répétées d’insertion et de décalages des fiches pour les mettre à leur place dans l’ordre désiré.

Pour éviter toute réorganisation, les fiches sont systématiquement ajoutées à la fin du fichier par la construction suivante:

Seek ( F , FileSize ( F ) ) ; Write ( F , NouvelleFiche ) ) ;

Le fichier ne peut donc pas être trié selon un critère de recherche. Il n’est plus possible d’utiliser des algorithmes de recherche plus performants ( dichotomie, recherche avec sentinelle, etc. ). L’algorithme de recherche doit donc être de type séquentiel et ne permet donc de trouver que la première fiche ayant le critère recherché. Il est donc capital que chaque fiche contienne une clé

unique dans tout le fichier ( code produit, nom + prénom maintenue lors des ajouts et des modifications.

Si on désire un ordre, il convient d’associer au fichier principal une table d'accès triée ( index ) que l’on sauvera dans un second fichier et qui devra être mis à jour lors de chaque modification du fichier principal ( technique du “ séquentiel indexé ” ). Le fichier n’est pas ordonné mais l’index le sera. Cette table devra contenir pour chaque fiche la valeur de sa clé et le numéro de la fiche correspondante dans le fichier. Les recherches se feront alors exclusivement sur cette table qui délivrera en retour le numéro de la fiche cherchée. Il est possible d’avoir plusieurs tables d'accès et donc plusieurs critères de recherche pour un même fichier ( exemple : le fichier des écritures d’une comptabilité que l’on pourrait rechercher par numéro de compte, date ou journal d’imputation ).

et l’unicité de cette clé doit être

)

Le problème de la suppression est aussi délicat à gérer. Il n’est pas raisonnable de recopier le fichier dans un autre en “ oubliant ” la fiche à supprimer, ni de décaler d’une place vers le haut les fiches suivantes. La méthode proposée ici est de “ marquer ” la fiche comme supprimée en mettant une valeur particulière dans une de ses rubriques. Elle est très efficace en temps ( 2 accès disque ). Elle a néanmoins comme désavantage de laisser dans le fichier des “ trous ”, et donc d’encombrer inutilement le disque. Deux améliorations sont possibles :

- soit de compacter périodiquement le fichier en ne recopiant que les fiches restant valides. Ceci a l’avantage de permettre d’annuler une suppression tant que ce compactage n’a pas eu lieu.

- soit de modifier l’algorithme d’ajout pour qu’il recherche d’abord un “ trou ” avant de mettre

la fiche en fin de fichier. Le même algorithme que la recherche pourrait être utilisé si la “ marque ” est une valeur particulière ( et impossible normalement ) d’une des rubriques de la fiche.

Une méthode fréquemment utilisée est d’inscrire dans la première fiche du fichier ( qui ne contient donc pas l’information normale ) le numéro de fiche du premier “ trou ” ou -1 s’il n’y en a aucun. Le premier trou contient le numéro du suivant et ainsi de suite jusqu’au dernier qui contient - 1. On constitue ainsi dans le fichier lui-même une liste chaînée des trous. On peut donc trouver sans recherche séquentielle la position du premier trou en consultant simplement la première fiche. En cas de récupération d’un trou, on récupère le premier de la liste et on met à jour la première fiche pour qu’elle contienne le numéro du trou suivant ( ou -1 ) si ce trou était le dernier.

Bien entendu, les algorithmes de balayage séquentiel du fichier ( recherche, impression, recopie, etc. ) doivent tester que la fiche courante n’est pas marquée comme supprimée.

12 Les fichiers non typés

Dans le cas des fichiers non typés, les procédures Reset et Rewrite acceptent un paramètre supplémentaire optionnel permettant de spécifier la taille d’une fiche utilisée lors des transferts.

Pour des raisons historiques, la taille par défaut est de 128 octets. La taille conseillée est de 1 octet ; c'est en effet la seule valeur qui permette de manipuler correctement un fichier de n'importe quelle taille ( aucun enregistrement incomplet n'est à redouter lorsque la taille est de 1 ).

Var F : File ; Assign ( F , ’PRODUIT.DTA’ ) ; Reset ( F ,1 ) ; ou Reset ( F ) ; ou Reset ( F , Sizeof ( tInfo ) ) ;

{ prêt pour un accès 1 octet à la fois}

{ prêt pour un accès 128 octets à la fois}

{ prêt pour un accès une fiche à la fois}

Toutes les procédures et les fonctions de manipulation de fichiers, excepté Read, Write sont autorisées sur les fichiers sans type. Read et Write sont remplacés par deux procédures BlockRead et BlockWrite qui attendent comme paramètre supplémentaire le nombre de fiches à lire ou à écrire. La syntaxe d'appel à ces procédures est :

BlockRead ( Var F : File ; Var V ; NbALire : Integer ) ; BlockWrite ( Var F : File ; Var V ; NbAEcrire : Integer ) ;

ou

BlockRead ( Var F : File ;Var V ; NbALire : Integer ; Var NbLus : Integer ) ; BlockWrite ( Var F : File ; Var V ; NbAEcrire : Integer ; Var NbEcrits : Integer ) ;

F est une variable de fichier sans type, V est toute variable et NbALire ( ou NbAEcrire ) est une expression entière donnant le nombre de fiches ( dont la taille a été définie par le second paramètre de Reset ou Rewrite ) à transférer entre le fichier disque associé à F et la variable V. Le paramètre facultatif NbLus ( ou NbEcrits ) retourne le nombre de fiches réellement transférées. Le transfert commence avec le premier octet occupé par la variable V. Le programmeur doit s'assurer que la variable V a suffisamment d'espace pour le transfert tout entier. Un appel à BlockRead ou BlockWrite avance le pointeur de fichier de NbLus ( ou NbEcrits ) enregistrements.

La fonction Eof fonctionne comme pour les fichiers typés, ainsi que les fonctions FilePos, FileSize et la procédure Seek qui utilisent la taille d’une fiche comme spécifiée par le programmeur dans Reset ou Rewrite ( ou 128 par défaut ).

Le programme suivant utilise des fichiers sans type pour dupliquer n’importe quel fichier. Re- marquez l'emploi du quatrième paramètre facultatif, avec BlockRead, pour suivre le nombre d'enre- gistrements lus dans le fichier source et donc la fin de la copie :

Program CopieDeFichiers ;

Const

TailleTampon = 10000 ;

Type tTampon = Array [ l

TailleTampon

] Of Byte ;

Var

Source , Dest : File ; SourceName , DestName : String ; Tampon : tTampon ; NbRecsLus : Integer ;

{ taille du tampon d’échange}

{ les deux fichiers sans type} {les deux nomes externes} { le tampon d’échange} { contrôle du nombre octets lus}

Begin Write ( 'nom du fichier source ') ; Readln ( SourceName ) ; Assign ( Source , SourceName ) ; Reset ( Source , 1 ) ; Write ( 'nom du fichier destination ' ) ; Readln ( DestName ) ; Assign ( Dest , DestName ) ; Rewrite ( Dest , 1 ) ; Repeat

{ prêt à la lecture du fichier source } { 1 octet à la fois ,doit exister }

{ prêt à l’écriture du fichier destinat.} { 1 octet à la fois, doit être nouveau }

{ lire TailleTampon enregistrements dans le fichier source vers le tampon }

{ soit ici 10000 * 1 octets } BlockRead ( Source , Tampon , TailleTampon , NbRecsLus ) ;

{ et recopier dans le fichier destination le nombre effectivement lus } If NbRecLus > 0 Then BlockWrite ( Dest , Tampon , NbRecsLus )

End .

Until NbRecsLus = 0 ; Close ( Source ) ; Close ( Dest )

{ plus rien à lire dans la source}

La mise à la disposition des programmeurs de primitives de bas niveau ( BlockRead,

) est caractéristique de l’évolution du langage Pascal

dans ses versions modernes. En annulant ou en réduisant les contrôles de type normalement effectués par le compilateur, on ajoute une plus grande souplesse d’utilisation au langage et on réduit de ce fait la différence avec les langages dit “ professionnels ” comme “ C ”.

L’effort du concepteur a été pratiquement nul puisque les primitives de plus haut niveau

) appellent en fait celles de plus bas niveau avec les paramètres calculés

automatiquement par le compilateur à partir du type des objets manipulés.

( Read,

BlockWrite

) et de types génériques ( File

Write

Read

( F , C ) ;

est équivalent à BlockRead

( F , C , Sizeof ( C ) , NbLus ) ;

If NbLus <> Sizeof ( C ) Then Ioresult := une valeur <> 0

Write ( F , C ) est équivalent à BlockWrite ( F, C , Sizeof ( C ) , NbEcrits ) ; If NbEcrits <> Sizeof ( C ) ) Then Ioresult := une valeur <> 0

;

Un autre intérêt des fichiers sans type est de pouvoir mettre dans un même fichier des informa- tions de type différent, ce qui n’est pas possible avec un fichier binaire typé. Bien entendu, chaque fiche devra être précédée d’un indicateur de son type ( et éventuellement du nombre de fois où elle est présente ) afin d’en permettre la relecture plus tard par un ordre BlockRead adapté. Cette mé- thode permet de créer des fichiers nommés des flots ( “ streams ” ) où des informations diverses sont écrites à la suite les unes des autres sans aucun contrôle de type par le langage. On retrouve donc un des avantages des fichiers textes sans souffrir de la lenteur de la conversion binaire - texte et en bénéficiant de la confidentialité de l’information puisqu’elle ne pourra être ni lue ni modifiée par un autre programme que le notre, puisqu’elle sera en binaire.

exercice 12.1(***) : un programme de dessin désire relire dans un unique fichier des objets graphi- ques de type différents comme:

une ligne

tLigne =

Record X1 , Y1 , Y2 , Y2 : Integer End ;

un rectangle

tRect =

Record X , Y , L , H : Integer End ;

un cercle

tCercle =

Record X , Y , R : Integer End ;

un point

tPoint=

Record X , Y : Integer End ;

les objets sont stockés dans le fichier sans aucun ordre et chacun est précédé d’un entier indiquant son type ( 0 = point, 1 = Ligne, 2 = rectangle, 3 = cercle ). Ecrire un programme Pascal qui dessine la figure contenue dans le fichier DESSIN.DAT.