© 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
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; |
int a = 3;
cout << "a = "; // édition d'une chaîne
de caractères
|
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 |
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 : ";
|
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) |
char buf [255];
int i; cin >> buf;
|
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 !! |
class CDate
{ private : int m_Jour, m_Mois, m_Annee; public :
}; // CDate ostream & operator << (const ostream & os, const CDate & date) {
cout << "date de début : " <<
Debut << endl
} // 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 :
friend ostream & operator << (const ostream & os, const CDate & date); }; // CDate ostream & operator << (const ostream & os, const CDate
& date)
int main ()
} // main() |
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 :
int eof () const; // vrai si
fin de fichier visible
void clear (int i = goodbit);
|
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];
cin >> buff;
// dangereux en cas de dépassement
} // 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...
ostream & flush ();
ostream & put (char c); // existe aussi pour signed et unsigned char ostream & write (const char * buf, int n); // idem |
class ios
{ public : enum { skipws = 0x0001, // skip whitespace oh input left
= 0x0002, // left-adjust output
dec
= 0x00l0, // decimal conversion
showbase
= 0x0080, // use base indicator on output
scientific = 0x0800,
// use 1.2345E2 floating notation
unitbuf
= 0x2000, // flush all streams 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);
|
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);
|
provoque l'édition suivante :
<$$42>
valeur de i : 42 |
typedef long streampos;
typedef long streamoff; class ios
}; // ios class _EXPCLASS istream : virtual public ios
} // 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.
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.
char message [80];
cout << "message : ";
|
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;
|
Les déclarations sont les suivantes :
class ios
{ // ... public : // ... // reading/setting ostream tied to this stream ostream * tie (); ostream * tie (ostream *); // ... }; // ios |
class istrstream : public strstreambase, public istream
{ public: istrstream (char str); istrstream (char str, int n); }; // istrstream class ostrstream : public strstreambase, public ostream
char * str ();
}; // ostrstream |
© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique