Entrées - sorties   en   C++

Les termes entrée/sortie reflètent les échanges d'information entre la mémoire centrale et l'extérieur.

Vu depuis l'ordinateur, une entrée, déclenchée par une instruction de lecture, modifie une ou plusieurs zones de la mémoire centrale. Le support extérieur est le clavier ou un fichier situé sur disque, bande ...
Dans l'autre sens, une sortie déclenchée par une instruction d'écriture, va modifier le support extérieur. Le support peut être l'écran, une imprimante, un fichier ...
Les échanges, avec un autre ordinateur ou par modem, qui ne sont pas structurés en terme de fichier par le système d'exploitation, ne sont pas concernés ici.

L'introduction concerne tous les langages; elle présente deux particularités des entrée/sortie. Le reste concerne les instructions du langage C++.
Le paragraphe suivant présente les instructions d'E/S, sans se préoccuper de support extérieur mais en considérant que les écritures envoient des octets dans un 'flot', et que les lectures reçoivent des octets d'un 'flot'. Il n'y est question que des types de base.
Le troisième paragraphe présente des possibilités d'E/S pour des types de l'utilisateur, soit par redéfinition d'opérateurs, soit par échanges binaires.
Le quatrième paragraphe est consacré aux instructions relatives aux fichiers.
Le dernier paragraphe montre comment toutes les possibilités de conversion mises en œuvre pour les échanges avec l'extérieur, sont utilisables en 'interne'.

I. Introduction

Présentons deux particularités des opérations d'entrée/sortie.

  1.   Comparaison des durées d'une E/S et d'un cycle processeur

    La première particularité concerne la durée. Une opération de lecture ou d'écriture concernant un disque par exemple demande au moins 10-4 secondes pour être exécutée; cette valeur est évidemment approximative, mais il faut la comparer avec le temps nécessaire à l'exécution d'une addition par exemple, qui est de l'ordre de 10-9 secondes. On conçoit donc qu'il existe des mécanismes pour que le processeur puisse continuer à travailler pendant qu'une E/S se déroule, à sa vitesse propre.

    Pour un accès disque par exemple, il faut déplacer le bras de lecture, avant la transmission d'octets; ce déplacement est responsable de la lenteur, alors que la transmission est rapide. Les temps pour recopier un octet ou un millier d'octets sont quasiment les mêmes.

    C'est pourquoi, même si l'instruction écrite par le programmeur porte sur deux octets, le premier échange avec le support extérieur va se faire sur un millier d'octets. En effet le système d'exploitation anticipe sur les opérations suivantes, et les prochaines instructions de lecture seront extrêmement rapides car les octets seront disponibles en mémoire centrale. La zone mémoire contenant ces octets pas encore lus par le programme, est appelée tampon (buffer).

    Nous avons là, un mécanisme de transmission en plusieurs phases dont l'une est 'lente' (échange entre le support et le tampon) et les autres rapides (échanges entre le tampon et les zones mémoires concernées par les instructions de lecture ... tant que le tampon contient des octets non encore lus. Ce mécanisme est toujours transparent pour le programmeur.

    Dans le cas de lecture au clavier, la différence de vitesse est encore plus flagrante, mais on ne demande pas à l'utilisateur de taper un millier de caractères avant d'exploiter le tampon. Il existe un mécanisme propre à un échange avec le clavier: quand le tampon reçoit l'octet associé à la touche 'retour chariot' (noté \n dans le langage C), il avertit le programme que de l'information est disponible et ce dernier reprend l'exécution de l'instruction de lecture qui avait été interrompue. Remarquons que si le tampon contenait déjà des caractères, non encore exploités par les lectures précédentes, il n'y aurait pas eu d'interruption.

    Résumons en indiquant succinctement comment se déroule une opération de lecture:

  2.   Changement de forme

    Dans la majorité des instruction d'entrée-sortie, l'information concernée change de forme. Par exemple une information numérique, fournie au clavier comme la suite de caractères '3', '.' ,'1' et '4' (bref 3.14) va être codée dans un format binaire sur 8 octets (type double du langage C), car il y a anticipation sur l'utilisation de cette valeur dans un calcul numérique.

    Les processeurs de calculs demandent des codages binaires spécifiques pour effectuer rapidement les opérations, donc les instructions d'entrée/sortie, de beaucoup de langages, modifient la forme de l'information. A nouveau, ce mécanisme est en général transparent pour le programmeur, mais pas toujours !

    Dans le cas d'une écriture, la conversion d'un format numérique vers une suite de caractères représentant cette valeur en numération décimale est toujours possible. Par contre dans le cas d'une lecture il n'en va pas de même; en effet si l'utilisateur tape un 'E' au clavier au lieu d'un '3', la conversion est impossible et l'opération de lecture se termine de façon inhabituelle en prévenant le programme qui traitera la maladresse de l'utilisateur, si le programmeur y a pensé...

    Suivant le langage, une telle erreur est signalée par l'activation d'une exception ou, de façon plus discrète en positionnant un drapeau. Pour les langages C et C++ un drapeau est levé; en outre, avec C++, toute autre opération de lecture dans le même tampon est interdite avant d'avoir examiné l'erreur.

    On peut se demander pourquoi une valeur numérique placé sur un support extérieur doit être sous forme d'une suite de caractères, plutôt que la suite d'octets du format binaire ? Donnons deux raisons.
    Premièrement, l'information numérique que peut lire un humain est la suite des chiffres de la valeur décimale sous forme caractères, avec éventuellement un signe et un '.'.
    Deuxièmement, les formats binaires ne sont pas les mêmes pour tous les processeurs; donc la suite d'octets peut avoir deux interprétations différentes, donc deux valeurs différentes, sur des processeurs différents. Si votre fichier doit changer de machine, il est préférable que les valeurs numériques ne soient pas codées en format binaire, à moins d'être sûr que les processeurs utilisés ont la même interprétation.

II. Présentation C++

La programmation des E/S en C++ est particulièrement facile pour la plupart des besoins du programmeur. En effet, avec les 2 opérateurs '<<' et '>>' et les 2 mots 'cin' et 'cout' on gère les dialogues avec le clavier et l'écran; et avec 2 mots supplémentaires, 'ofstream' et 'ifstream' on accède aux fichiers, car pour ces deux classes, l'ouverture est réalisée dans le constructeur et la fermeture dans le destructeur.

Quelques besoins supplémentaires nécessitent cependant d'en savoir un peu plus; citons:

L'ensemble des classes peut être présenté en plusieurs parties; nous ne présentons pas toutes les classes dont les noms peuvent dépendre de l'environnement de programmation utilisé. Signalons simplement:

Signalons les quatre flots mis à la disposition du programmeur, déclarés dans iostream.h:
extern istream cin;
extern ostream cout;
extern ostream cerr;
extern ostream clog;

III. Flots

Presque toutes les lectures des types de base se font avec le seul operator>> (et ses redéfinitions presque toutes les écritures des types de base également avec operator<<.

Dans ce paragraphe sont exposées tout d'abord les instructions sur flot de sortie, puis les instructions depuis le flot d'entrée; ensuite nous voyons la gestion d'erreurs, puis les possibilités de mise en forme de l'information (largeur, nombre de chiffres après la virgule ...) et enfin l'utilisation des manipulateurs.

  1.   Instructions avec flot de sortie (class ostream)

    Classes, opérations ... sont déclarées dans iostream.h. Signalons simplement les classes ios, et ostream. Dans le langage et sa bibliothèque standard, sont définies les opérations de sortie, sur les types de base, par operator<<.
    L'extrait ci-dessous de la classe ostream montre quelques redéfinitions permettant d'écrire les types de base; les redéfinitions pour les qualificatifs 'signed' ou 'unsigned' des types char ou int, existent aussi, mais n'ont pas été recopiées. class ostream : virtual public ios { ... ostream & operator<<(char c) ostream & operator<<(short ii) ostream & operator<<(long n) ostream & operator<<(int i) ostream & operator<<(float xf) ostream & operator<<(double x) ostream & operator<<(long double xx) ostream & operator<<(const char* ch); ostream & operator<<(const void* ptr); ostream & put(char c) ostream & flush() ... } Remarquez des redéfinitions différentes pour les types const void * et const char *
    Le fait que operator<< renvoie un flot, donne un sens à une écriture telle que:
    cout << "Valeur : " << i;

    Voici d'autres méthodes de la classe ostream:

  2.   Instructions avec flot d'entrée (class istream)

    L'opérateur de lecture, operator>> est aussi simple à utiliser que son équivalent en écriture, mais il a un comportement vis à vis des espaces, qui empêche leur lecture. Par exemple, avec les instructions:
    char c; cin >> c;
    il ne peut pas y avoir d'espace dans la variable c, non plus qu'un des octets équivalent à un espace du point de vue du langage C (\t \n \r \f \v).
    Ignorer ces caractères est pratique pour saisir des informations numériques, mais l'est moins quand il est question de chaînes de caractères.

    Précisons comment est exécutée l'intruction:
    cin >> v;

    Suivant le type de v:

    Quelques méthodes de la classe istream:

  3.   Lecture sécurisée de chaînes de caractères

    Pour éviter les débordements de la zone mémoire réservée lors d'une lecture de chaîne de caractères, il faut utiliser l'une des deux méthodes:
    istream& get(char* tc, int taille, char delim='\n');
    istream& getline(char* tc, int taille, char delim='\n');
    pour lesquelles on précise la taille de la zone recevant les octets lus.

    A la différence de operator>>, ces méthodes permettent également de mémoriser les espaces lus. Elles placent toujours en mémoire l'octet \0, marque de fin de chaîne; le caractère delim n'est pas mémorisé.

    Avec la méthode get() le caractère delim est toujours dans le tampon, ce qui présente:

    Précisons que lecture et mémorisation s'arrêtent au premier des événements qui survient:

  4.   Gestion d'erreur

    L'état de chaque flot est conservé dans un entier (type int) qu'on peut consulter et modifier en utilisant des méthodes et des constantes définies dans la classe ios; cet état peut être modifié par toute opération sur le flot.

    Quand le flot est en état d'erreur toute opération, autre que forcer le changement d'état avec clear() ou setstate(), échoue; cela oblige à considérer l'erreur.

    On trouve dans la classe ios des constantes symboliques associées aux bits d'erreur:
     enum io_state {
        goodbit     = 0x00,     
        badbit      = 0x01,      // erreur matérielle sur E/S
        eofbit      = 0x02,      // fin de flot dépassée
        failbit     = 0x04       // erreur logicielle sur E/S
        }
    
    On utilisera les constantes sous la forme: ios::goodbit par exemple.

    Tester un flot est réalisé par une des 4 fonctions dont l'utilisation est évidente:
    • int eof() const ;
    • int fail() const ;
    • int bad() const ;
    • int good() const ;
    Une opération ne peut réussir sur le flot f que si f.good() est non nul.

    Pour tester l'état d'un flot, on peut, en outre, utiliser l'une des formes suivantes, en remarquant que les deux premières sont équivalentes:
          if(f.good())  ...               // flot en état correct
          if( f )  ...                    // flot en état correct
          if( ! f )  ...                  // flot en état erreur
    
    Cela est rendu possible grace à la définition de la conversion d'un flot en void *, et par une redéfinition de operator! (voir la classe ios).

    Examen ou modification du mot d'état:
  5.   Mise en forme

    a) Un exemple de mise en forme

    les informations suivantes concernant le numéro de compte et le solde de trois clients de la banque Route, sont plus ou moins lisibles. Voici deux présentations:
      Pair Noël 87654 432.1
      Delajaretière Amélie Antoinette 123 -43210.321
      Lebon Max 456789 7.65432
    
               Pair Noël              ####87654    +432.10
      Delajaretière Amélie Antoinette ######123  -43210.32
              Lebon Max               ###456789      +7.65
    
    La deuxième utilise les éléments suivants de mise en forme:

    b) Programmation avec manipulateurs

    La deuxième présentation est obtenu par le petit programme ci-dessous: #include <iostream.h> #include <iomanip.h> struct Enr{ char nom[20],prenom[20]; int num; double solde; }; void aff(Enr e, ostream & f) { f << setw(17) << e.nom << ' ' << setw(18) << setiosflags(ios::left) << e.prenom << resetiosflags(ios::left) << setiosflags(ios::right) << setfill('#') << setw(9) << e.num << setiosflags(ios::fixed) << setprecision(2) << setfill(' ') << setw(11) << e.solde << endl; } Commentons successivement certaines lignes:

    c) Remarques

    Voici quelques précisions relatives à la largeur de champ:

    Les informations de formatage sont mémorisées dans

    Les constantes suivantes, définies dans la classe ios, permettent le formatage; les valeurs numériques associées ne sont pas nécessairement les mêmes pour tout environnement. typedef int fmtflags; enum fmt_flags { // Un ou plusieurs bits à utiliser dans .setf(int bits) boolalpha = 0x0001, showbase = 0x0200, // montrer la base de numération showpoint = 0x0400, // showpos = 0x0800, // afficher le signe skipws = 0x1000, // ignorer les espaces unitbuf = 0x2000, uppercase = 0x4000, // chiffres hexadécimaux en majuscule // Bit à utiliser dans .setf(int bit, int champ) // Un seul des trois bits suivants est activé (champ:basefield) dec = 0x0002, hex = 0x0008, oct = 0x0040, // Un seul des trois bits suivants est activé (champ:ajustfield) internal = 0x0010, left = 0x0020, right = 0x0080, // Un seul des deux bits suivants est activé (champ:floatfield) fixed = 0x0004, scientific = 0x0100, // Champs à utiliser dans .setf(int bit, int champ) basefield = dec | oct | hex, adjustfield = left | right | internal, floatfield = scientific | fixed };

    d) Programmation sans manipulateur

    La mise en forme peut s'écire sans utiliser de manipulateurs comme on le voit ci-dessous où le programme précédent est réécrit, en utilisant uniquement les méthodes de la classe ios. void aff(Enr e, ostream & f) { // nom sur 17 positions f.width(17); f << e.nom; // prénom sur 17 positions, cadré à droite f.width(18); f.setf(ios::left,ios::adjustfield); f<<e.prenom; // on revient au cadrage à gauche f.setf(ios::right,ios::adjustfield); // numéro sur 9 postions, avec remplissage par # f.fill('#'); f.width(9); f << e.num; // affichage en 'virgule fixe', 2 chiffres après la virgule f.setf(ios::fixed,ios::floatfield); f.precision(2); // solde sur 11 positions (remplissage par espace) f.fill(' '); f.width(11); f << e.solde; f << endl; }

IV. Aspects divers

  1.   Redéfinition d'opérateurs

    La classe Enreg ci-dessous regroupe le nom, le prénom, le numéro de compte et le solde du client d'une banque par exemple; de plus la méthode aff permet d'afficher ces informations sur un client.
    Nous redéfinissons operator<<, puis nous l'utilisons pour afficher un objet 'Enreg'. class Enreg{ public: char nom[20],prenom[20]; int num; double solde; void aff(ostream & f){f<<nom<<' '<<prenom<<' '<<num<<' '<<solde;} }; ostream & operator<<(ostream & f, Enreg & e) { e.aff(f); return f; } Enreg e={"Beng","Ali",3333,44.44}; cout << e << endl;
  2.   E/S en binaire

    Ces opérations sont de simples copies d'octets, sans interprétation; elles sont donc immédiatement utilisables pour des types définies par l'utilisateur.
    Elles permettent par exemple d'avoir la notion d'enregistrements de longueur fixe dans un fichier et donc de pouvoir effectuer des accès directs.
    Attention cependant car les fichiers ainsi créés ne sont pas lisibles sur toute machine.

    Il existe une opération d'écriture et une opération de lecture.

    ostream& write(const char* s,int n);
    permet de copier les n octets à l'adresse s vers le flot.
    Voici un exemple de copie d'un tableau de 3 enregistrements en une seule instruction.
    Enreg te[]={{"Pair", "Noël", 87654, 432.1}, {"Delajaretière", "Amélie Antoinette", 123, -43210.321}, {"Lebon Max", 456789, 7.65432} }; flotSortie.write((char *)te,sizeof(te)); istream& read(char* s,int n);
    permet de copier n octets depuis un flot.
    Voici un exemple de lecture des trois premiers enregistrements d'un fichier, tous du type Enreg, directement dans un tableau. La lecture est effectuée en une seule instruction, puis on affiche dans une boucle les trois enregistrements en utilisant la méthode aff de la classe Enreg.
    ifstream flotEntree("testEnr"); if(flotEntree) { Enreg tt[3]; int n=3; flotEntree.read( (char *)tt, n*sizeof(Enreg)); flotEntree.close(); for(int i=0; i<n; i++) { tt[i].aff(cout); cout << endl; } } else cerr<< "Erreur à la lecture de testEnr" << endl;
  3.   Manipulateurs

    En utilisant astucieusement la redéfinition des opérateurs << ou >>, on peut préciser une action à effectuer.

    On a déjà utilisé ceci, par exemple en écrivant:
    cout << "Valeurs " << endl << 125;
    on sait que 'endl' provoque, entre autre, le passage à la ligne suivante.

    Ceci est possible car on trouve dans iostream.h:

    Donnons un exemple de création d'un manipulateur, appelé SEP qui permet de séparer des champs, en insérant un ';':

    ostream & SEP(ostream & f) { f << ';'; return f;}
    qui peut être utilisé comme suit:
    cout<<"Pair"<<SEP<<"Jean"<<SEP<<4321 <<SEP<<123.4<<endl;
    pour fournir le résultat:
    Pair;Jean;4321;123.40
    La fonction SEP est, comme endl, une fonction de prototype fixé, qui ne prend qu'un paramètre (type ostream& ), le flot utilisé, et renvoie ce flot.

    Des types plus complexes, déclarés dans iomanip.h, permettent d'avoir des actions paramétrées, comme par exemple celles des manipulateurs:
    setfill(char remplissage), setprecision(int nb_chiffres)

    Manipulateur Fonction
    ostream& endl(ostream& i) ; envoie \n sur le flot et vide le tampon
    ostream& ends(ostream& i) ; envoie \0 sur le flot
    ostream& flush(ostream&) ; vide le tampon
    ios& dec(ios&) ; passe en numération décimale
    ios& hex(ios&) ; passe en numération hexadécimale
    ios& oct(ios&) ; passe en numération octale
    istream& ws(istream&) ; enlèves les prochains espaes du flot d'entrée

    Les manipulateurs suivants sont plus complexes, car ils s'utilisent avec un paramètre; ils nécessitent l'inclusion de iomanip.h.

    Manipulateur Fonction
    resetiosflags(int bits) Permet de remettre à 0 les bits précisés; ces bits sont spécifiés par une combinaison logique de constantes de type ios::fmtflags.
    setiosflags(int bits) Permet d'activer les bits précisés
    setbase(int base) Fixe la base de numérotation à utiliser, qui peut être 8, 10 ou 16
    cout << setbase(16)
    << setiosflags(ios::showbase|ios::uppercase) << 250;
    setprecision(int nbchiffres) Fixe le nombre de chiffres après le point décimal .
    setw(int largeur) Fixe la largeur minimale du champ pour l'écriture suivante; n'est valable que pour une seule écriture.
    setfill(char car) Fixe le caractère de remplissage.

  4.   Entrée / sortie en C

    Nous pouvons utiliser, en plus des instructions C++, les fonctions d'E/S du langage C pour lire ou écrire. Cela peut poser un problème, par exemple, pour les affichages à l'écran car deux flots, stdout pour le langage C et cout pour C++ sont dirigés vers l'écran, mais il n'y a aucune raison pour qu'ils soient synchronisés, car ils utilisent des tampons distincts.

    Pour résoudre ce problème, la méthode suivante de la classe streambuf,

    static bool sync_with_stdio(int sync = 1);
    permet d'activer la synchronisation, ce qui est fait par défaut.

    Exemples: cout.sync_with_stdio(false); printf(" -> avec printf"); cout << " -> avec operator<< " << endl; printf("\n"); cout << "cout.sync_with_stdio(false), puis operator<< et printf" << endl; cout << " -> avec operator<< "; printf(" -> avec printf \n"); cout << endl;

V. Fichiers

Les opérations sur fichiers se répartissent dans le temps.

On commence par localiser le fichier, vérifier que les accès demandés sont compatibles avec les droits de l'utilisateur, puis préparer les zones mémoires en vue des E/S ultérieures; c'est l'opération d'ouverture. Viennent ensuite une suite d'opérations d'échanges entre mémoire et support extérieur, avec éventuellement des changements explicites de position sur le support, comme recommencer une lecture au début ... il y a toujours des changements de position implicites à chaque transmission, mais le programmeur n'a pas à s'en préoccuper. Enfin le programmeur peut indiquer qu'il a terminé l'exploitation du fichier, pour libérer les ressources mobilisées et laisser le fichier dans son état définitif; c'est l'opération dite de fermeture.

Nous allons voir comment ouvrir un fichier, comment s'y déplacer explicitement et comment le fermer. Les opérations d'échanges ont déja été vues car ce sont celles sur les flots.

Les déclarations nécessaires sont dans iostream.h et dans fstream.h; ce dernier fichier regroupe les déclarations des 3 classes ifstream (fichier en lecture) ofstream (fichier en écriture) et fstream (fichier en lecture écriture).

  1.   déclaration,   ouverture

    Elle est faite soit par le constructeur, soit par un appel à la méthode open.
    Exemple de déclaration et d'ouverture d'un fichier en écriture, en écrasant s'il existait, le contenu du fichier
    ofstream fic("unfichier"); if(!fic) { cerr << "Erreur en créant " << nomFic << endl; }
    Voici un exemple d'ouverture d'un fichier en lecture, en se positionnant à la fin du fichier (ios::ate), puis en demandant la taille du fichier pour l'afficher.
    flotEntree.open("testEnr",ios::ate); if(flotEntree) { long taille=flotEntreep; tellg(); cout << "taille de testEnr: " << taille << endl; flotEntree.close(); } else cerr<< "Erreur à la lecture de testEnr" << endl; ifstream( const char * nomFic, int mode=ios::in,
            int prot=filebuf::openprot)
    constructeur gérant l'ouverture dans le mode précisé; s'il s'agit d'une création ( ios::nocreate est inactif) prot est utilisé
    ofstream( const char * nomFic, int mode=ios::out,
            int prot=filebuf::openprot)
    constructeur gérant l'ouverture dans le mode précisé; s'il s'agit d'une création ( ios::nocreate est inactif) prot est utilisé
    void open( const char * nomFic, int mode,
            int prot=filebuf::openprot)
    ouverture dans le mode précisé; s'il y a création, prot est utilisé dans les systèmes unix.
    Dans les exemples, le fichier 'donnees1' est créé, et un fichier de même nom est écrasé s'il existait; alors qu'avec l'autre cas, si 'donnees2' existe, l'ouverture échoue, et on ne perdra pas son contenu.
    f.open("donnees1", ios:out) f.open("donnees2", ios:out | ios:noreplace) void is_open( )
    renvoie une valeur non nulle si le fichier est ouvert
    fstream(const char *s, int mode=ios_base::in | ios_base::out,
            long protection= 0666 );
    fichier en lecture et écriture

     

    Modes d'ouverture
    in fichier en lecture
    out fichier en écriture
    binary précise le contenu du fichier pour les systèmes d'exploitation, comme dos ou windows, qui distinguent les types de fichiers textes et binaires.
    trunc fichier tronqué ou ecrasé à l'ouverture en écriture, donc le contenu avant ouverture est perdu
    app en écriture le fichier est ouvert à la fin; donc les opérations vont ajouter des octets; le contenu avant ouverture n'est pas perdu.
    ate fichier ouvert à la fin; si l'ouverture est en écriture, on va ajouter des octets en conservant le contenu antérieur. Si l'ouverture est en lecture seule, cela permet d'avoir la taille du fichier, puis il faut se repositionner au début du fichier pour effectuer des lectures.
    nocreate le fichier doit exister, sinon l'ouverture échoue; peut être utile pour s'assurer qu'on ajoute effectivement à un fichier qui existe.
    noreplace le fichier ne doit pas exister, sinon l'ouverture échoue; évite d'écraser un fichier existant

  2.   position

    A tout instant le flot associé à un fichier connait la position dans le fichier, du prochain octet à lire pour un flot en lecture, ou à écrire pour un flot en écriture. Deux fonctions permettent au programmeur d'avoir cette position. Le type streampos est équivalent à un entier sur 4 octets.

    streampos tellg();
    position du prochain octet à lire.
    Dans l'exemple qui suit, on demande d'ouvrir et d'être placé à la fin du fichier; dans ce cas la position fournie, correspond à la taille du fichier.
    flotEntree.open("testEnr",ios::ate); if(flotEntree) { long taille=flotEntree.tellg(); cout << "taille de testEnr: " << taille << endl; flotEntree.close(); } else cerr<< "Erreur à la lecture de testEnr" << endl;
    streampos tellp() ;
    position du prochain octet à écrire
  3.   déplacement

    Il est possible de se déplacer dans le flot pour aller à une position désirée, en utilisant les fonctions seekg() pour un flot en lecture, et seekp()pour un flot en écriture. Deux versions pour chaque fonction, la première s'utilisant avec un postion absolue, la deuxième s'utilisant avec une position relative.

    istream& seekg(streampos p)
    istream& seekp(streampos p)
    le prochain octet à lire pour seekg() ou à écrire pour seekp() se trouve à p octets du début du fichier.
    istream& seekg(streamoff dep, ios::seek_dir rel) ;
    istream& seekp(streamoff dep, ios::seek_dir rel) ;
    le premier paramètre est un déplacement en octets, le deuxième paramètre indique comment doit être interprété le déplacement, par une des trois valeurs (définies dans la classe ios):
    • ios::beg     dep octets par rapport au début du fichier (appel équivalent à la fonction avec un seul paramètre)
    • ios::cur     dep octets par rapport à la position courante
    • ios::end     dep octets par rapport à la fin du fichier; dans ce cas dep est négatif.
  4.   fermeture

    void close()
    Dans l'exemple suivant, après ouverture, on écrit en une seule fois, puis on ferme le fichier.
    fstream flotSortie("testEnr"); if(flotSortie) { flotSortie.write((char *)te,sizeof(te)); flotSortie.close(); } else { cerr<< "Erreur à la création de testEnr" << endl;

VI. Flots en mémoire

Toutes les possibilités de conversion entre les types de base et les suites de caractères, vues ci-dessus pour les flots orientés fichiers sont utilisables pour des suites de caractères exploitées en mémoire.
Ces outils sont utilisés quand le programmeur échange des informations par le biais d'objets d'interface utilisateur, comme un champ de saisie par exemple; dans ce cas les échanges se font entre les variables du programme et les zone mémoires de ces objets graphiques qui doivent toujours avoir les informations sous forme suite de caractères.

Il existe la classe ostrstream pour les conversions à partir des types de base vers les suites de caractères, et la classe istrstream pour les conversions dans l'autre sens.
L'usage de ces classes nécessite l'inclusion de strstream.h

  1. classe ostrstream

    Un objet ostrstream pourra recevoir, comme un flot, autant de caractères qu'on le désire; cette suite, codée est accessible par la fonction membre str qui renvoie un char *.
    Si la suite d'octets ainsi constiutée doit être interprétée comme une chaîne du langage C il faudra la faire suivre par \0, ce qui est réalisée par ends, qui insère \0, comme 'endl' insére l'octet \n.

    Voici quelques méthodes de la classe ostrstream: deux constructeurs, la méthode pcount() et la méthode str().

    ostrstream()
    Ce constructeur permet de préparer un objet à recevoir un flot. On peut ensuite placer autant d'octets que l'on veut dans ce flot, sans se soucier de gestion de place en mémoire.
    ostrstream fm; fm << "valeur: "; fm << '+' << 32*32; ostrstream( char *tab, int lgtab, ios::openmode=ios::out)
    Avec ce constructeur, la zone mémoire recevant les octets est précisée (adresse et longueur) par le programmeur. Comme on fournit la longueur de cette zone, il n'y aura jamais de débordement réalisé; tous les octets envoyé dans le flot, en plus de lgtab sont ignorés.
    Dans l'exemple ci-dessous, le tablau ch est remplii, avec au maximum 25 octets. indirectement par operator<< appliqué à l'objet fch.
    char ch[25]; int nch=sizeof(ch); ostrstream fch(ch,nch); fch << "valeur: " << '+' << 32*32 << ends; cout << ch << endl; int pcount() const;
    fournit le nombre total d'octets transmis au flot, incluant \0 si celui-ci est transmis.
    char ch[25]; int nch=sizeof(ch); ostrstream fch(ch,nch); fch << "valeur: " << '+' << 32*32 << ends; cout << "Nombre d'octets: " << fch.pcount() << endl; char *str();
    fournit l'adresse du premier octet du flot constitué.
    Après un appel à str() il n'est plus possible d'envoyer de nouveaux octets à ce flot, et la zone de mémoire dynamique doit être gérée par le programmeur. S'il n'y pas pas eu d'appel à str, cette zone mémoire est gérée par le destructeur de la classe ostrstream.
    ostrstream fm; fm << "valeur: " << '+' << 32*32 << ends; char * txt=fm.str(); ... utilisations de txt delete txt;
  2. classe istrstream

    On peut récupérer toute variable d'un type de base à partir d'un objet de la classe istrstream, comme on le ferait à partir du clavier, avec un peu plus de souplesse cependant pour le programmeur.

    istrstream(char *tc, long int taille);
    Dans l'exemple ci-dessous, on récupère une entrée clavier dans le tableau de caractères ligne , puis on teste à partir de là si la valeur tapée est un entier court, en vérifiant que le caractère qui suit la valeur numérique, doit être équivalent à un espace ou \0.
    Ainsi on détecte une entrée telle que 12e4 , dans laquelle le carcatère e est incorrect.
    Une valeur telle que 40000 est aussi détectée comme une erreur (voir la variable cr), car elle dépasse la capacité de codage d'un short int .
    char ligne[80]; short int e; char car; int cr; cout << "entier: "; cin.getline(ligne,sizeof(ligne)); istrstream fe(ligne,sizeof(ligne)); fe >> e; cr=fe.fail(); // compte rendu d'erreur fe.get(car); // Lecture correcte si la suite de chiffres est suivie par un // espace ou par \n (alors car == marque de fin de chaine) if( !cr && (isspace(car)||car=='\0')) cout << "e=" << e << endl; else cout << "! erreur ! (" << cr << ')' << endl;

VII. Références

Les références signalées concernent C++ dans son ensemble.
Des livres
C. Delannoy - Programmer en langage C++ / Eyrolles
B. Stroustrup - Le langage C++ / Addison Wesley

En ligne