Bibliothèques d'E/S en C++

© D. Mathieu     mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 19/01/2000 - Dernière mise à jour : 15/02/2001

Sommaire

Introduction
Flux
Classes de base
Flux standard
Sortie standard des types de base
L'opérateur <<
Sorties standard cerr et clog
E/S d'objets de types utilisateur
Etats d'un flux
Autres opérations sur un flux d'entrée
Autres opérations sur un flux de sortie
E/S formatées
Manipulateurs
Flux et accès direct
Fichiers et flux
Tampons
Fichiers liés
Flux en mémoire (chaînes)

Introduction

    Un programme C++ contient des objets de types de base, prédéfinis dans le langage C++, mais aussi de types et de classes définis par les utilisateurs. Le langage de base C++ ne peut donc offrir la possibilité de réaliser directement les opérations d'Entrées/Sorties (E/S) de tous ces objets. Une bibliothèque de classes et de fonctions indépendantes permet les E/S des objets des types de base du langage : c'est la bibliothèque dont l'interface est dans le fichier <iostream>. C'est à l'utilisateur de développer les E/S des classes et des types qu'il a lui-même construits, à la fois à partir de cette bibliothèque et des mécanismes de base du langage lui-même.

    On supposera dans toute la suite de ce chapitre que tout fichier source contenant les exemples indiqués contient au minimum les lignes suivantes :
 
#include <iostream> #using namespace std;

Sommaire

Flux

    En C++, les E/S à un niveau élevé sont considérées comme effectuées à partir ou vers un flux (stream), entité abstraite qui représente un flot d'informations entre un producteur et un consommateur. Les opérations d'E/S sont donc des lectures (ou extractions) à partir d'un flux, ou des écritures (ou insertions ou injections) dans un flux. La bibliothèque iostream gère aussi les couches basses des E/S, c'est-à-dire les flots d'octets. Les problèmes d'E/S consistent donc en des transformations d'objets typés en séquences d'octets (séquences de "caractères") et inversement. Les problèmes d'E/S binaires peuvent en majorité être traités en considérant un "caractère" comme un bloc de 8 bits sans correspondance avec un alphabet.

Sommaire

Classes de base

    De façon générale, qui peut varier d'une implémentation à l'autre et qui sera affinée ultérieurement, il existe une classe virtuelle ios qui regroupe les caractéristiques communes à tous les flux, d'entrée et de sortie. Cette classe est dérivée en deux sous-classes istream pour les flux d'entrée et ostream pour les flux de sortie, selon le schéma suivant :

Sommaire

Flux standard

    L'utilisateur dispose de 3 ou 4 flux standards (selon les implémentations), prédéfinis, automatiquement ouverts en début de programme et fermés en fin de programme : Sommaire

Sortie standard des types de base

    Une sortie est obtenue par l'opérateur <<, surcharge de l'opérateur binaire de décalage à gauche, appliqué à l'objet cout :
 
int a = 3;

cout << "a = ";   // édition d'une chaîne de caractères
cout << a;        // édition d'un entier
cout << ';';      // édition d'un caractère

provoque l'édition suivante :
 
a = 3;

    Tous les objets de types de base (constantes, variables, expressions, résultats de fonction) peuvent être édités de cette façon  : char, short, int, long, float, double, char*, void*, etc...

    Les opérations peuvent être concaténées dans la même instruction. La ligne suivante provoque la même édition :
 
int a = 3;

cout << "a = " << a << ';';

    Des caractères d'édition peuvent être ajoutés, soit dans les chaînes, soit seuls :
 
int a = 3;

cout << "a = \t" << a << ';' << '\n';

    La valeur 3 est affichée après une tabulation, et la ligne est terminée par le passage à la ligne suivante.

    Cependant, cela ne signifie pas que l'affichage soit immédiat car la sortie standard est tamponnée. Il est possible de vider le tampon tout en passant à la ligne suivante au moyen du manipulateur endl qui s'utilise comme '\n' :
 
cout << "a = \t" << a << ';' << endl;

    Il existe plusieurs manipulateurs, qui seront étudiés plus loin dans ce chapitre.

    Un pointeur de caractères est considéré comme une chaîne. Il est donc édité comme tel.  Par exemple :
 
char * chaine = "Bonjour";
cout << chaine << endl;

provoque l'édition de Bonjour suivi du passage à la ligne suivante.

    Les adresses mémoire peuvent être éditées par :
 
int i;
int &j = i;
int * ptr_i = new int (1);
cout << "adresse de i     : " << &i  << endl
     << "adresse de j     : " << &j  << endl 
     << "contenu de ptr_i : << ptr_i << endl;

qui provoque l'édition suivante (par exemple)
 
adresse de i : 0xffl20d34
adresse de j : 0xffl20d34
contenu de ptr_i : 0x32d3004f

    Le format de l'édition des adresses mémoire dépend à la fois de la machine et de l'implémentation.

    Comme on l'a vu plus haut, l'édition d'un pointeur de caractères n'affiche pas l'adresse de ce caractère c'est-à-dire la valeur du pointeur, mais est considérée comme l'édition d'une chaîne de caractères. Le contenu du pointeur peut être affiché en le transtypant en void * :
 
char * chaine = "Bonjour";

cout << "adresse de la chaîne 'Bonjour' : " << (void *) chaine << endl;

provoque l'édition (par exemple) :
 
adresse de la chaîne 'Bonjour' : 0xd3fc3312

Sommaire

L'opérateur <<

    L'opérateur d'insertion << est un opérateur membre de la classe ostream, surchargé pour tous les types de base. Il est lui-même une surcharge de l'opérateur de décalage à gauche, dont il garde la priorité et la sémantique (évaluation de gauche à droite).

Priorité

    La priorité peut provoquer quelques surprises :
 
cout << 3 << 2   << endl;
cout << (3 << 2) << endl;

la première ligne affiche 32 alors que la seconde affiche 12!

    De même la séquence suivante :
 
inline void aff_max (const int i, const int j)
{
    cout << (i > j) ? i : j;
}
int v1 = 0;
int v2 = 1;

cout  << "le maximum de " << v1 << " et " << v2 << " est : ";
aff_max (vl, v2);
cout << endl;

provoque l'affichage de :
 
le maximum de 0 et 1 est 0

ce qui est légèrement surprenant!  Compte tenu des priorités, l'instruction est en effet interprétée ainsi :
 
(cout << (i > j)) ? i : j;

i > j est faux (= 0), ce qui est affiché, puis, quelle que soit la valeur de l'expression cout << 0, le reste de l'expression ne fait rien. En cas de doute, il est prudent de parenthéser les expressions :
 
cout << ((i > j) ? i : j);

Ordre d'évaluation

    L'instruction cout << 3; doit être interprétée de la façon suivante : l'opérateur binaire << a deux opérandes. L'opérande de gauche désigne (est une référence d')un objet de la classe ostream, l'opérande de droite est une expression entière de valeur 3. L'arbre représentant cette expression est le suivant :

    Puisque l'instruction cout << 3 << 2; provoque l'affichage de 3 suivi de 2, cela signifie que l'instruction est évaluée comme (cout << 3) << 2; qui correspond à l'arbre ci-dessous :

    Il faut donc que le membre de gauche du second opérateur << ait pour valeur une référence à un objet ostream. Pour cela il suffit que le premier opérande << (sous-arbre encadré) renvoie une référence à un objet ostream. Cela correspond aux déclarations du fichier iostream
 
class ostream : public virtual ios

    // ..
  public :
    // ..
    ostream & operator << (int),
    ostream & operator << (long);
    ostream & operator << (double);
    ostream & operator << (char);
    // ..
};

    De façon générale, la définition de l'opérateur << pour un type quelconque t_qcq est la suivante :
 
ostream & ostream operator << (const t_qcq & var)
{
    // affichage de la valeur de var dans le format adéquat 
    return *this;
}

    L'instruction ci-dessus est en réalité interprétée ainsi :
 
(cout.operator << (3)).operator << (2)

Sommaire

Entrée standard des types de base

    Les lectures dans le flux d'entrée standard cin de la classe istream sont très analogues aux écritures.  L'opérateur d'extraction (l'extracteur) est une surcharge du décalage à droite >>. Par exemple :
 
char buf [255];
int i;

cin  >> buf;
cin  >> i;
cout << buf << i << endl;

provoque, à partir du flux d'entrée suivant :
 
        valeur         345

l'affichage :
 
valeur345

    Il faut noter dès à présent que toute lecture commence par sauter les caractères d'espacement  (espaces, tabulations, passage à la ligne, etc...). Pour une chaîne de caractères, elle se termine au prochain caractère d'espacement et un zéro terminal est automatiquement ajouté.

    Les déclarations dans le fichier iostream.h sont les suivantes
 
class istream : public virtual ios
{
    // ...
  public :
    // ...
    istream & operator >> (int    &);
    istream & operator >> (long   &);
    istream & operator >> (double &);
    istream & operator >> (char   &);
    // ...
};

    Comme pour les écritures, les lectures peuvent être concaténées :
 
cin  >> buf >> i;

    La forme générale de la définition de la surcharge de l'opérateur >> est la suivante :
 
istream & istream::operator >> (t_qcq & var)
{
    // saut des caractères d'espacement 
    // lecture de la valeur de var 
    return *this;
}

    Moyennant une opération de conversion prévue dans la classe ios (voir le paragraphe "Etats d'un flux"), le résultat de l'opération peut indiquer un cas d'échec de lecture (fin de fichier atteinte, valeur ne correspondant pas au type cherché, etc...), et peut donc être directement utilisé comme condition d'arrêt d'une boucle de lecture. Par exemple on peut lire en continu dans le flux d'entrée et afficher chaque chaîne à raison d'une par ligne :
 
char Chaine [MaxTaille];
while (cin >> Chaine) cout << Chaine << endl;

    De même, on peut lire des entiers successifs séparés par n'importe quel nombre de n'importe quel caractère d'espacement, jusqu'à un caractère invalide (ou fin de fichier) et les stocker dans un vecteur :
 
int v [MaxTaille];
int i = 0;

while (cin >> v [i++]); // en supposant qu'il n'y ait pas d'erreur !!

Sommaire

Sorties standard cerr et clog

    Ces deux flux se comportent exactement comme le flux cout.

Sommaire

E/S d'objets de types utilisateur

    Les opérateurs << et >> peuvent être surchargés pour n'importe quel type ou classe, comme le montre l'exemple suivant :
 
class CDate
{
  private :
    int m_Jour, m_Mois, m_Annee;

  public :
    CDate (const int Jour, const int Mois, const int Annee)
      : m_Jour (Jour), m_Mois (Mois), m_Annee (Annee) {}
 int GetJour  () const { return m_Jour;  }
 int GetMois  () const { return m_Mois;  }
 int GetAnnee () const { return m_Annee; }

}; // CDate 

ostream & operator << (const ostream & os, const CDate & date)

{
    return os << date.GetJour  () << ':'
              << date.GetMois  () << ':'
              << date.GetAnnee ();
}
int main ()
{
    CDate Debut (4, 9, 95);
    CDate Fin   (8, 9, 95);

    cout << "date de début : " << Debut << endl
         << "date de fin   : " << Fin   << endl;
    // ...
    return 0;

} // main()

    Une autre solution est de définir l'opérateur << comme ami de la classe CDate :
 
class CDate
{
  private :
    int m_Jour, m_Mois, m_Annee;

  public :
    CDate (const int Jour. const int Mois, const int Annee)
      : m_Jour (Jour), m_Mois (Mois), m_Annee (Annee) {}

    friend ostream & operator << (const ostream & os, const CDate & date);

}; // CDate 

ostream & operator << (const ostream & os, const CDate & date)
{
    return os << date.m_Jour << ':' << date.m_Mois << ':' << date.m_Annee;
}

int main ()
{
    // même fonction que précédemment

} // main()

Sommaire

Etats d'un flux

    Il est possible à tout moment de connaître l'état d'un flux, à la fois pour savoir comment s'est passée la dernière opération d'E/S, et si la prochaine est possible. Cet état est stocké dans un "vecteur" de bits state donnée-membre de la classe mère ios, qui peut prendre des valeurs parmi le type énumératif io_state, et peut être consulté par les quatre fonctions eof(), fail(), bad() et good(). L'état du flux peut être positionné par la fonction clear(), appelée ainsi car en général cela revient à mettre l'état à 0 (goodbit). La valeur de la donnée-membre state peut être obtenue par la fonction rdstate().

    Comme nous l'avons signalé plus haut, le résultat (succès ou échec) d'une opération d'E/S peut être utilisé dans un test (de sortie de boucle par exemple) à condition d'effectuer une opération de conversion.
 
while (cin >> Chaine) cout << Chaine << endl;

    Pour que l'instruction ci-dessus ne soit pas syntaxiquement fausse, il faut que le résultat de l'expression cin >> Chaine soit un entier ou puisse être convertie en entier. Comme c'est un istream et qu'il n'existe pas de conversion implicite de istream en int, il faut construire un opérateur de conversion. Non seulement cette conversion doit renvoyer un entier, mais cet entier doit traduire l'état du flux, c'est-à-dire la valeur de la fonction good(). Cet opérateur peut par exemple être implémenté dans la classe mère ios sous la forme suivante :
 
class ios 
{
  protected :
    int state;           // bits d'état

  public :
    enum io_state
    {
        goodbit  0x00,   // no bit set: all is ok
        eofbit   0x01,   // at end of file
        failbit  0x02,   // last I/0 operation failed
        badbit   0x04,  // invalid operation attempted
        hardfail 0x8O    // unrecoverable error
    };

    int eof  () const;   // vrai si fin de fichier visible
    int fail () const;   // vrai si une opération a échoué,
    int bad  () const;   // vrai si une erreur s'est produite
    int good () const;   // vrai si aucun bit d'état n'est positionné 
                         //      (précédente opération d'E/S réussie)

    void clear (int i = goodbit);
    int  rdstate () const;
    // ...
};
class ios
{
     //
  public :
    operator int () { return good(); }
    //
};

Sommaire

Autres opérations sur un flux d'entrée

    Plusieurs fonctions-membres sont implémentées, qui permettent des opérations diverses sur les flux d'entrée.
 
istream & putback(char c);
istream & ignore (int n = 1, char delim = EOF);
istréam & get    (char & c);                             // caractère
istream & get    (char & buf, int n, char delim = '\n'); // chaîne

    La fonction putback() injecte le caractère c dans le flux d'entrée de façon que ce soit le prochain caractère lu. La fonction ignore() saute au maximum n caractères ou jusqu'au caractère delim s'il est rencontré.

    La première fonction get() lit un caractère et le renvoie dans c. Les caractères d'espacement sont ici traités comme des caractères quelconques. L'exemple suivant illustre la recopie dans le flux de sortie de tous les caractères du flux d'entrée jusqu'à la fin de fichier.
 
int main ()
{
    char c;
    while (cin.get(c)) cout << c;
    return 0;

} // main()

    La même conversion que précédemment est utilisée ici pour tester le résultat de l'opération : si la lecture est un échec, c'est que la fin de fichier a été atteinte.

    La seconde fonction get() lit une chaîne de caractères : elle lit les caractères successifs et les range à partir de l'adresse buf. Elle s'arrête soit à la rencontre du caractère délimiteur delim, soit après avoir lu au maximum n - 1 caractères. Elle place toujours un '\0' en fin des caractères lus, et laisse le délimiteur dans le flux d'entrée si elle l'a atteint. Ainsi toute chaîne lue est valide, et le test du prochain caractère à lire permet de savoir si le buffer était de taille suffisante. L'exemple suivant montre une première lecture dangereuse si la longueur de la chaîne lue est supérieure à la taille du buffer, la seconde permet une vérification a posteriori.
 
int main ()
{
    const int MaxTaille = 128;

    char buff [MaxTaille];
    char c;

    cin >> buff;                        // dangereux en cas de dépassement
    cin.get(buff, MaxTaille, '\n');     // prudent
    if (cin.get (c) && c != '\n') 
    {
        // attention, la chaîne est plus longue que prévu 
    }
    // ...
    return 0;

} // main()

    Certaines implémentations proposent les fonctions peek() et eatwhite(). On trouve aussi les fonctions read() et gcount() qui permettent d'essayer de lire un nombre donné de caractères, de connaître le nombre de caractères effectivement lus, la fonction getline(), etc...

Sommaire

Autres opérations sur un flux de sortie

    Les flux de sortie possèdent aussi plusieurs fonctions-membres parmi lesquelles flush(), put() et write(). La fonction flush() vide le flux. Symétriquement à la fonction istream::get(), la fonction ostream::put() injecte un caractère dans le flux. Enfin la fonction write() permet d'écrire un certain nombre de caractères dans le flux.
 
ostream & flush ();
ostream & put   (char c);  // existe aussi pour signed et unsigned char
ostream & write (const char * buf, int n); // idem

Sommaire

E/S formatées

    Les E/S sont effectuées selon un format (largeur d'édition, base des entiers, etc...) dont les caractéristiques (aussi appelées indicateurs de formatage) sont stockées dans des données-membres de la classe mère ios, et sont accessibles/modifiables par un ensemble de fonctions-membres. Les fonctions sans paramètre renvoient la valeur courante de l'indicateur, les fonctions avec paramètre positionnent l'indicateur et renvoient sa valeur avant modification.  Les "flags" (bits) de formatage peuvent être consultés/modifiés par les fonctions flags(), setf() et unsetf(). Ils restent positionnés jusqu'à une nouvelle modification. Le positionnement des "flags" se fait en utilisant les valeurs du type énumératif.
 
class ios 
{
  public :
    enum
    {
        skipws     = 0x0001,      // skip whitespace oh input

        left       = 0x0002,      // left-adjust output
        right      = 0x0004,      // right-adjust output
        internal   = 0x0008,      // padding after sign or base indicator

        dec        = 0x00l0,      // decimal conversion
        oct        = 0x0020,      // octal conversion
        hex        = 0x0040,      // hexadecimal conversion

        showbase   = 0x0080,      // use base indicator on output
        showpoint  = 0x0I00,      // force decimal point (floating output)
        uppercase  = 0x0200,      // upper-case hex output
        showpos    = 0x0400,      // add '+' to positive integers

        scientific = 0x0800,      // use 1.2345E2 floating notation
        fixed      = 0x1000,      // use 123.45 floating no ation

        unitbuf    = 0x2000,      // flush all streams after insertion
        stdio      = 0x4000       // flush stdout, stderr after insertion
    };
    // ...

}; // ios

    Certaines caractéristiques nécessitent seulement de (dé)positionner un bit. Cela peut être obtenu par les fonctions flags(), setf() et unsetf().
 
class ios, 
{
    // ...
  public :
    // 
    long flags  ();
    long flags  (long);
    long setf   (long);
    long setf   (long _setbits, long _field);
    long unsetf (long);
    // ...

}; // ios

    La fonction flags() positionne tous les bits selon la valeur indiquée dans son paramètre. Dans l'exemple suivant on désire que les réels soient édités en "virgule fixe" et que le buffer soit vidé après chaque sortie, les autres options sont fixées par défaut :
 
long old__flags;

old_flags = cout.flags (ios::fixed | ios::unitbuf);
// ...
cout.flags (old_flags); // restauration des anciennes valeurs des flags

    Il faut rappeler ici que la définition d'identificateurs dans la partie publique d'une classe permet de limiter la "pollution" de l'espace des identificateurs globaux. Ici les identificateurs des différents types énumératifs de la classe ios ne peuvent entrer en conflit avec des identificateurs identiques, globaux ou exportés par d'autres classes, puisque leur utilisation de façon globale (c'est-à-dire en dehors des fonctions membres de la classe elle-même) doit être accompagnée de l'opérateur de résolution de portée :: .

    On peut ajouter l'option d'affichage de la base pour les valeurs entières de la façon suivante :
 
cout.flags (cout.flags() | ios::showbase); 

    Au contraire les fonctions setf() et unsetf() ne (dé)positionnent que les bits indiqués dans le paramètre. Ainsi, l'instruction suivante est équivalente à la ligne ci-dessus :
 
cout.setf (ios::showbase); 

    Certains positionnement de bits doivent être faits de façon exclusive. Par exemple la base ne peut être à la fois binaire, décimale et hexadécimale. L'utilisation de la première version de la fonction permet de positionner simultanément plusieurs de ces bits. Il faut donc utiliser la seconde version à deux paramètres qui annule d'abord tous les bits de la même option avant de repositionner le bit choisi. Le second paramètre indique le groupe de bits choisi parmi l'ensemble suivant :
 
class ios 
{
    // ...
  public :
    // ...
    static const long basefield;    // dec | oct | hex
    static const long adjustfield;  // left | right | internal
    static const long floatfield;   // scientific | fixed
    // ...
}; // ios

    Par exemple l'ajustement de l'affichage à droite de la zone d'édition, l'affichage des entiers en base hexadécimale et l'affichage des réels en notation scientifique sont obtenus par les instructions suivantes :
 
cout.setf (ios::right,      ios::adjustfield);
cout.setf (ios::hex,        ios::basefield);
cout.setf (ios::scientific, ios::floatfield);

    Certaines caractéristiques nécessitent l'introduction de données supplémentaires.  Cela est réalisé par les fonctions width(), fill() et precision().
 
class ios
{
    // ...
  public :
    // ...
    int  width     ();
    int  width     (int);
    char fill      (char)
    int  precision (int);
    //   ..
};

    La fonction width(int) fixe la largeur minimale de la prochaine édition de chaîne ou de numérique.  Elle n'est positionnée que pour une édition puis reprend sa valeur par défaut (la largeur minimale nécessaire).

    La fonction fill() permet d'indiquer un caractère de remplissage des positions non significatives de la zone d'édition (zéros non significatifs à droite ou à gauche des numériques, caractères de remplissage à droite ou à gauche des chaînes en fonction de la justification). La valeur par défaut est l'espace.

   La fonction precision() permet de fixer la précision d'édition des réels (le nombre de chiffres significatifs). Elle reste positionnée. Par exemple, la séquence suivante :
 
int i = 42;

cout.width (4);
cout.fill ('$');
cout << '<' << i << '>' << endl;
cout.width (4);
cout.fill ('$');
cout << "valeur de i - " << i << endl;

provoque l'édition suivante :
 
<$$42>
valeur de i : 42

Sommaire

Flux et accès direct

    Lorsque les flux d'E/S sont associés (redirection par exemple) à des fichiers, ils peuvent être considérés comme des vecteurs d'octets dans lesquels il est possible de se positionner en n'importe quelle position (valide) grâce aux déclarations suivantes  :
 
typedef long streampos;
typedef long streamoff;

class ios
{
    // ...
  public :
    // ...
    enum seek_dir (beg = 0, cur = 1; end = 2 }; // stream seek direction
    // ...

}; // ios

class _EXPCLASS istream : virtual public ios
{
    // ...
  public :
    // set/read the get pointer's position 
    istream & seekg (streampos);
    istream & seekg (streamoff, ios::seek_dir); 
    streampos tellg ();
    // ...
}; // istream
class _EXPCLASS ostream : virtual public ios
{
    // ...
  public :
    // ...
    // set/read the put pointer's position 
    ostream & seekp (streampos); 
    ostream & seekp (streamoff, ios::seek_dir); 
    streampos tellp ();
    // ...

} // ostream

    Les fonctions terminées par g (get) font référence à un flux en entrée. Les fonctions terminées par p (put) font référence à un flux en sortie. Il y a lieu de les distinguer car il est possible de créer par dérivation double des flux d'entrée-sortie permettant à la fois des lectures et des écritures, et pour lesquels les positions de lecture et d'écriture peuvent être différentes. streampos permet de typer une position dans le flux, streamoff permet de typer un déplacement (offset), alors que les trois valeurs du type énumératif seek_dir indiquent le sens du déplacement : à partir du début du flux (position 0), de la position courante ou de la fin du flux.

    La fonction tellx() renvoie la position courante dans le flux (première position = position 0). La fonction seekx (streampos pos) permet un positionnement absolu en position pos. La fonction seekx  (streamoff off, ios::seek_dir dir) permet un décalage de off caractères par rapport à la position indiquée par dir.

Sommaire

Fichiers et flux

    Les fichiers peuvent être considérés comme des flux d'octets, et les différentes classes et fonctions disponibles sont déclarés dans le fichier fstream.h. Les classes ifstream et ofstream sont dérivées de la classe iostream qui est elle-même dérivée des classe istream et ostream, qui dérivent de la classe ios, selon le schéma simplifié suivant :

    Les constructeurs de ces classes possèdent comme paramètre le nom du fichier (une chaîne de caractères) ainsi que des options facultatives prises parmi le type énumératif open_mode fourni dans la classe mère ios :
 
enum open_mode
{
    in        = 0x01,  // open for reading
    out       = 0x02,  // open for writing
    ate       = 0x04,  // seek to eof upon original open
    app       = 0x081  // append mode: all additions at eof
    trunc   = 0x10,  // truncate file if already exists
    nocreate  = 0x20,  // open fails if file doesn't exist
    noreplace = 0x40,  // open fails if file already exists
    binary    = 0x80   // binary (not text) file
};

    Quelques fonctions supplémentaires  sont offertes par ces classes : open(), close(), attach() qui permet d'associer un flux à un identificateur de fichier (fileid) ainsi que plusieurs fonctions en rapport avec l'utilisation des buffers.

Sommaire

Tampons

    Les opérations d'E/S sont réalisées à travers des tampons que l'utilisateur n'a en général pas à gérer directement. Le fichier iostream.h met à disposition la classe streambuf qui offre toutes les fonctionnalités nécessaires : des données-membres telles qu'un pointeur vers le début du buffer, un pointeur vers la prochaine position libre ou remplie, etc..., ainsi que des fonctions-membres comme l'allocation-mémoire, le test de débordement, etc... L'une des données-membres protégées de la classe mère ios est un pointeur vers un objet streambuf.  Ainsi toutes les classes dérivées en héritent. Mais l'utilisateur n'y a pas accès directement.

Sommaire

Fichiers liés

    Les opérations d'E/S étant tamponnées, il n'y a aucune raison pour que les buffers se vident tout seuls. Considérons par exemple la séquence suivante :
 
char message [80];

cout << "message : ";
if (cin >> message)
    cerr << "echec";
else
    cout << " bien reçu ";
cout << "fin de l'essai" << endl,

    Les flux cin, cerr et cout ayant des buffers distincts, il n'y a aucune raison que les messages apparaissent dans l'ordre chronologique. C'est pourquoi la classe ios permet de lier certains flux entre eux afin qu'une action sur l'un d'eux provoque une action sur l'autre. Un flux d'entrée ou de sortie peut être lié à un flux de sortie : toute opération d'E/S sur le premier flux provoque un vidage préalable du flux de sortie. Par défaut, les fichiers cin, cerr (et c1og) sont liés à cout, par des instructions du type :
 
cin.tie (cout);

    Ainsi, dans l'exemple précédent, les messages apparaissent correctement, car le programme est équivalent à :
 
char message [80];

cout << "message : " << flush;
if (cin >> message)
    cerr << "echec"  << flush;
else
    cout << " bien reçu " << flush;
cout << "fin de l'essai" << flush;

    Les déclarations sont les suivantes :
 
class ios
{
    // ...
  public :
    // ...
    // reading/setting ostream tied to this stream 
    ostream * tie ();
    ostream * tie (ostream *);
    // ...

}; // ios

Sommaire

Flux en mémoire (chaînes)

    Il est possible de considérer une chaîne de caractères en mémoire comme un flux d'entrée ou de sortie sur lequel toutes les opérations d'E/S sont possibles. Cela ouvre de nombreuses possibilités comme par exemple de préparer des sorties avant de les rediriger effectivement vers un flux de sortie ou au contraire de stocker provisoirement tout ou partie d'un fichier pour le traiter localement. Il est aussi possible de préparer des chaînes en utilisant toutes les possibilités de formatage offertes par la classe ios . Ces possibilités sont accessibles au moyen du fichier strstream.h . Un des avantages majeurs est la possibilité de laisser au flux le soin de réserver en mémoire dynamique l'espace nécessaire pour stocker les informations, au fur et à mesure des besoins. L'espace ainsi réservé est libéré à l'appel automatique du destructeur de l'objet, lorsqu'on sort de la portée du flux.
Les déclarations ci-dessous  montrent quelques possibilités offertes :
 
class istrstream  : public strstreambase, public istream
{
  public:
    istrstream (char str);
    istrstream (char str, int n);

}; // istrstream

class ostrstream : public strstreambase, public ostream
{
  public:
    ostrstream (char *, int, int = ios::out);
    ostrstream ();

    char * str ();
    int pcount ();

}; // ostrstream

Sommaire

© D. Mathieu     mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique