[15] Les entrées/sorties via <iostream> et <cstdio>

(Une partie de C++ FAQ Lite fr, Copyright © 1991-2002, Marshall Cline, cline@parashift.com)

Traduit de l'anglais par Fabrice Clerc

Les FAQs de la section [15]


[15.1] Pourquoi utiliser <iostream> plutôt que le traditionnel <cstdio>?
Parce que <iostream.h>est plus sûr du point de vue typage, est moins source d'erreurs, améliore les performances, est extensible, et enfin que certaines des classes qui le composent peuvent être sous-classées.

Même si on peut soutenir que printf()est utilisable et que l'on peut vivre avec scanf() bien qu'elle ne pardonne pas les erreurs, toutes deux montrent des limitations lorsqu'on les compare aux E/S C++. Par rapport aux E/S C (utilisant printf() and scanf()), les E/S C++ (utilisant << et >>) sont:

[ Haut | Bas | Rechercher ]


[15.2] Pourquoi mon programme part-il en boucle infinie quand quelqu'un entre un caractère invalide?

Supposez par exemple que vous ayiez écrit le code suivant, pour lire des entiers à partir de cin:

#include <iostream.h>

int main()
{
cout << "Enter numbers separated by whitespace (use -1 to quit): ";
int i = 0;
while (i != -1) {
cin >> i; // FORME INCORRECTE - Voir les commentaires ci-dessous
cout << "You entered " << i << '\n';
}
}

Le problème de ce code, c'est qu'il ne contient aucun test visant à détecter si un caractère invalide a été tapé. Si quelqu'un saisit quelque chose qui ne ressemble pas un entier (par exemple un 'x'), le flot cin passe dans un "état d'echec" ("failed state"), et toutes les tentatives de saisie ultérieures rendront immédiatement la main au programme sans que rien ne se passe. Autrement dit, le programme part en boucle infinie. Si le dernier chiffre lu avec succès a été 42, le programme va afficher le message You entered 42 indéfiniment.

Une façon simple de vérifier que la saisie est correcte consiste à déplacer l'instruction de saisie depuis le corps de la boucle while vers l'expression-test de cette même boucle. Ce qui donne:

#include <iostream.h>

int main()
{
cout << "Enter a number, or -1 to quit: ";
int i = 0;
while (cin >> i) { // FORME CORRECTE
if (i == -1) break;
cout << "You entered " << i << '\n';
}
}

Les conditions de sortie de la boucle while seront alors les suivantes: quand un caractère de fin de fichier est rencontré, ou quand un entier invalide est rencontré, ou enfin quand -1 est saisi.

(On peut évidemment se passer du break en changeant le while (cin >> i) en while ((cin >> i) && (i != -1)), mais l'objectif de cette section n'est pas de vous donner des conseils liés à la programmation structurée; le sujet est ici plutôt les iostreams.)

[ Haut | Bas | Rechercher ]


[15.3] How can I get std::cin to skip invalid input characters?

Use std::cin.clear() and std::cin.ignore().

#include <iostream>
#include <limits>

int main()
{
   int age = 0;

   while ((std::cout << "How old are you? ")
          && !(std::cin >> age)) {
     std::cout << "That's not a number; ";
     std::cin.clear();
     std::cin.ignore(std::numeric_limits<int>::max(), '\n');
   }

   std::cout << "You are " << age << " years old\n";
   return 0;
 }

Of course you can also print the error message when the input is out of range. For example, if you wanted the age to be between 1 and 200, you could change the while loop to:

  ...
   while ((std::cout << "How old are you? ")
          && (!(std::cin >> age) || age < 1 || age > 200)) {
     std::cout << "That's not a number between 1 and 200; ";
     std::cin.clear();
     std::cin.ignore(std::numeric_limits<int>::max(), '\n');
   }
   ...

Here's a sample run:


 How old are you? foo
 That's not a number between 1 and 200; How old are you? bar
 That's not a number between 1 and 200; How old are you? -3
 That's not a number between 1 and 200; How old are you? 0
 That's not a number between 1 and 200; How old are you? 201
 That's not a number between 1 and 200; How old are you? 2
 You are 2 years old

[ Haut | Bas | Rechercher ]


[15.4] Comment fonctionne cette syntaxe cool du while (cin >> foo)?
Référez-vous à la FAQ précédente pour voir un exemple de cette "syntaxe cool du while (cin >> foo)."

L'expression (cin >> foo) appelle la bonne version d'operator>> (dans le cas où foo est de type int, par exemple, elle invoquera l'operator>> qui prend un istream à gauche et un int& à droite). La convention est que les fonctions operator>> d'istream retournent leur argument de gauche, ce qui signifie qu'elles vont dans ce cas retourner cin. Le compilateur se rend ensuite compte que l'istream retourné se trouve dans un contexte où un booléen est attendu, et convertit alors cet istream en un booléen.

Pour convertir un istream en un booléen, le compilateur appelle une fonction membre appelée istream::operator void*(). Cette fonction va retourner un pointeur void*, et c'est ce pointeur qui sera effectivement converti en un booléen (NULL est converti en false, toutes les autres valeurs du pointeur en true). Le compilateur génère donc dans ce cas un appel à cin.operator void*(), comme si vous aviez explicitement casté cin en écrivant (void*)cin.

L'opérateur de cast operator void*() renvoie un pointeur non-NULL si le flot est dans un état valide (good state), ou bien NULL si le flot est dans un état d'échec (failed state). Par exemple, si vous avez lu une fois de trop (vous étiez déjà à la fin du fichier), ou si la donnée effectivement présente dans le flot d'entrée n'est pas valable pour foo (p.ex., si foo est un int et que la donnée est un caractère 'x'), le flot passera dans un état d'échec et l'opérateur de cast renverra NULL.

La raison pour laquelle operator>> ne renvoie pas simplement un bool (ou un void*) qui indiquerait si l'opération a réussi ou échoué se situe dans le fait qu'on veut supporter une syntaxe "en cascade":

cin >> foo >> bar;

operator>> étant associatif à gauche, l'expression ci-dessus est interprétée comme:

(cin >> foo) >> bar;

Et si on remplace operator>> par un nom de fonction classique tel que readFrom(), l'expression devient:

readFrom( readFrom(cin, foo), bar);

L'expression évaluée en premier est toujours celle qui se trouve le plus à l'intérieur. Puisque operator>> est associatif à gauche, ça correspond ici à l'expression la plus à gauche, cin >> foo. Cette expression retourne cin (ou plus précisément, une référence à son argument de gauche) à l'expression suivante. L'expression suivante retourne elle aussi (une référence à) cin, mais cette deuxième référence est ignorée puisqu'elle est le résultat de l'expression la plus à l'extérieur, et que ce résultat de n'est ici pas utilisé.

[ Haut | Bas | Rechercher ]


[15.5] Pourquoi mon programme se comporte-t-il comme s'il lisait au-delà de la fin du fichier?
[Ajout récent (10/99): j'ai renforcé le message en utilisant un autre exemple.]

Parce qu'il est possible que le flot ne passe dans l'état fin de fichier (eof) qu'au moment où on tente de lire après la fin du fichier. Lire le dernier octet d'un fichier ne fait pas forcément passer un flot dans l'état fin de fichier. Supposez par exemple que le flot d'entrée soit associé à un clavier. Dans ce cas, la bibliothèque C++ ne peut absolument pas deviner si le caractère qui vient d'être tapé est le dernier caractère ou non.

Dans le code suivant, le compteur i pourrait valoir un de plus que le nombre de valeurs effectivement saisies:

int i = 0;
while (! cin.eof()) { // MAUVAIS! (pas fiable)
cin >> x;
++i;
// Travailler avec x ...
}

Voilà plutôt ce qu'il faut écrire:

int i = 0;
while (cin >> x) { // BON! (fiable)
++i;
// Travailler avec x ...
}

[ Haut | Bas | Rechercher ]


[15.6] Pourquoi mon programme ignore-t-il les requêtes de saisie après la première itération?
Parce que l'extracteur numérique laisse dans le tampon d'entrée les caractères qui ne sont pas des chiffres.

Si votre code ressemble à ceci:

char name[1000];
int age;

for (;;) {
cout << "Name: ";
cin >> name;
cout << "Age: ";
cin >> age;
}

Voilà plutôt ce qu'il vous faut:

for (;;) {
cout << "Name: ";
cin >> name;
cout << "Age: ";
cin >> age;
cin.ignore(INT_MAX, '\n');
}

[ Haut | Bas | Rechercher ]


[15.7] Should I end my output lines with std::endl or '\n'?

Using std::endl flushes the output buffer after sending a '\n', which means std::endl is more expensive in performance. Obviously if you need to flush the buffer after sending a '\n', then use std::endl; but if you don't need to flush the buffer, the code will run faster if you use '\n'.

This code simply outputs a '\n':

void f()
{
	std::cout << ...stuff... << '\n';
}

This code outputs a '\n', then flushes the output buffer:

void g()
{
	std::cout << ...stuff... << std::endl;
}

This code simply flushes the output buffer:

void h()
{
	std::cout << ...stuff... < std::flush;
}

Note: all three of the above examples require #include <iostream>

[ Haut | Bas | Rechercher ]


[15.8] Comment faire pour que les objets ma classFred puissent être affichés?
[Ajout récent (10/99): main() a maintenant un type de retour.]

Fournissez, grâce à la surcharge d'opérateur, un opérateur ami de décalage à gauche (operator<<).

#include <iostream.h>

class Fred {
public:
friend ostream& operator<< (ostream& o, const Fred& fred);
// ...
private:
int i_; // à titre d'illustration
};

ostream& operator<< (ostream& o, const Fred& fred)
{
return o << fred.i_;
}

int main()
{
Fred f;
cout << "My Fred object: " << f << "\n";
}

On utilise une fonction non-membre (ici une fonction amie) puisque l'objet Fred doit être l'opérande de droite de l'opérateur <<. Si on voulait que l'objet Fred apparaise à gauche du << (et dans ce cas, on devra écrire myFred << cout plutôt que cout << myFred), on aurait pu utiliser une fonction membre appelée operator<<.

Notez que l'operator<< retourne le flot, ce afin de pouvoir permettre une cascade des opérations d'affichage.

[ Haut | Bas | Rechercher ]


[15.9] But shouldn't I always use a printOn() method rather than a friend function?

No.

The usual reason people want to always use a printOn() method rather than a friend function is because they wrongly believe that friends violate encapsulation and/or that friends are evil. These beliefs are naive and wrong: when used properly, friends can actually enhance encapsulation .

This is not to say that the printOn() method approach is never useful. For example, it is useful when providing printing for an entire hierarchy of classes . But if you use a printOn() method, it should normally be protected, not public.

For completeness, here is "the printOn() method approach." The idea is to have a member function (often called printOn() that does the actual printing, then have operator<< call that printOn() method. When it is done wrongly, the printOn() method is public so operator<< doesn't have to be a friend — it can be a simple top-level function that is neither a friend nor a member of the class. Here's some sample code:

#include <iostream>

class Fred {
public:
	void printOn(std::ostream& o) const;
	...
};

// operator<< can be declared as a non-friend [NOT recommended!]
std::ostream& operator<< (std::ostream& o, const Fred& fred);

// The actual printing is done inside the printOn() method [NOT recommended!]
void Fred::printOn(std::ostream& o) const
{
	...
}

// operator<< calls printOn() [NOT recommended!]
std::ostream& operator<< (std::ostream& o, const Fred& fred)
{
	fred.printOn(o);
	return o;
}

People wrongly assume that this reduces maintenance cost "since it avoids having a friend function." This is a wrong assumption because:

  1. The member-called-by-top-level-function approach has zero benefit in terms of maintenance cost. Let's say it takes N lines of code to do the actual printing. In the case of a friend function, those N lines of code will have direct access to the class's private/protected parts, which means whenever someone changes the class's private/protected parts, those N lines of code will need to be scanned and possibly modified, which increases the maintenance cost. However using the printOn() method doesn't change this at all: we still have N lines of code that have direct access to the class's private/protected parts. Thus moving the code from a friend function into a member function does not reduce the maintenance cost at all. Zero reduction. No benefit in maintenance cost. (If anything it's a bit worse with the printOn() method since you now have more lines of code to maintain since you have an extra function that you didn't have before.)
  2. The member-called-by-top-level-function approach makes the class harder to use, particularly by programmers who are not also class designers. The approach exposes a public method that programmers are not supposed to call. When a programmer reads the public methods of the class, they'll see two ways to do the same thing. The documentation would need to say something like, "This does exactly the same as that, but don't use this; instead use that." And the average programmer will say, "Huh? Why make the method public if I'm not supposed to use it?" In reality the only reason the printOn() method is public is to avoid granting friendship status to operator<<, and that is a notion that is somewhere between subtle and incomprehensible to a programmer who simply wants to use the class.

Net: the member-called-by-top-level-function approach has a cost but no benefit. Therefore it is, in general, a bad idea.

Note: if the printOn() method is protected or private, the second objection doesn't apply. There are cases when that approach is reasonable, such as when providing printing for an entire hierarchy of classes . Note also that when the printOn() method is non-public, operator<< needs to be a friend.

[ Haut | Bas | Rechercher ]


[15.10] Comment faire pour que les objets de ma classFred puissent être saisis?
[Ajout récent (10/99): main() a maintenant un type de retour.]

Fournissez, grâce à la surcharge d'opérateur, un opérateur ami de décalage à droite (operator>>). C'est un peu la même chose que l'opérateur d'affichage , à la différence près que le paramètre n'est pas const : c'est "Fred&" plutôt que "const Fred&".

#include <iostream.h>

class Fred {
public:
friend istream& operator>> (istream& i, Fred& fred);
// ...
private:
int i_; // à titre d'illustration
};

istream& operator>> (istream& i, Fred& fred)
{
return i >> fred.i_;
}

int main()
{
Fred f;
cout << "Enter a Fred object: ";
cin >> f;
// ...
}

Notez que l'operator>> retourne le flot, ce afin de pouvoir permettre des opérations de saisie en cascade et/ou dans une boucle while ou dans une instruction if.

[ Haut | Bas | Rechercher ]


[15.11] Comment faire pour que les objets de toute une hiérarchie de classe puissent être affichés?
Utiliser un operator<< amiqui appelle une fonction virtuelleprotégée:

class Base {
public:
friend ostream& operator<< (ostream& o, const Base& b);
// ...
protected:
virtual void print(ostream& o) const;
};

inline ostream& operator<< (ostream& o, const Base& b)
{
b.print(o);
return o;
}

class Derived : public Base {
protected:
virtual void print(ostream& o) const;
};

Au final, l'operator<< se comporte comme s'il était soumis à la liaison dynamique, bien que ce soit une fonction amie. Cet idiome s'appelle l'Idiome de la Fonction Amie Virtuelle.

Notez que les classes dérivées se contentent de surcharger print(ostream&) const et ne fournissent pas leur propre version de l'operator<<.

Si Base est une CBA , Base::print(ostream&)const peut bien évidemment être déclarée virtuelle pure, en utlisant la syntaxe du "= 0".

[ Haut | Bas | Rechercher ]


[15.12] How can I open a stream in binary mode?

Use std::ios::binary.

Some operating systems differentiate between text and binary modes. In text mode, end-of-line sequences and possibly other things are translated; in binary mode, they are not. For example, in text mode under Windows, "\r\n" is translated into "\n" on input, and the reverse on output.

To read a file in binary mode, use something like this:

#include <string>
#include <iostream>
#include <fstream>

void readBinaryFile(const std::string& filename)
{
	std::ifstream input(filename.c_str(), std::ios::in | std::ios::binary);
	char c;
	while (input.get(c)) {
		 ...do something with c here...
	}
}

Note: input >> c discards leading whitespace, so you won't normally use that when reading binary files.

[ Haut | Bas | Rechercher ]


[15.13] Comment faire pour "réouvrir" cin et cout en mode binaire sous DOS et/ou sous OS/2?
ça dépend de l'implémentation. Cherchez dans la documentation fournie avec le compilateur.

Supposez par exemple que vous vouliez faire des E/S binaires en utilisant cin et cout. Supposez aussi que votre système d'exploitation (à l'instar de DOS et d'OS/2) insiste pour transformer les "\r\n" en "\n" au niveau des entrées depuis cin, et pour transformer les "\n" en "\r\n" au niveau des sorties sur cout ou cerr.

Il n'y a pas hélas de moyen portable de faire en sorte que cin, cout, et/ou cerr soient ouverts en mode binaire. Fermer ces flots et essayer de les réouvrir en mode binaire peut avoir des conséquences imprévues et pas forcément souhaitables.

Sur les systèmes pour lesquels cela fait une différence, il est possible que l'implémentation offre la possibilité de faire de ces flots des flots binaires, mais il faut que vous regardiez dans la doc pour le savoir.

[ Haut | Bas | Rechercher ]


[15.14] How can I write/read objects of my class to/from a data file?
Read the section on object serialization .

[ Haut | Bas | Rechercher ]


[15.15] How can I send objects of my class to another computer (e.g., via a socket, TCP/IP, FTP, email, a wireless link, etc.)?
Read the section on object serialization .

[ Haut | Bas | Rechercher ]


[15.16] Pourquoi est-ce que je n'arrive pas à ouvrir un fichier dans un répertoire différent, comme par exemple "..\test.dat"?

Parce que "\t" est le caractère tab.

Utilisez des slash plutôt que des backslash dans vos noms de fichiers, même si vous êtes sur un OS qui utilise des backslash comme DOS, Windows, OS/2, ou autre. Par exemple:

#include <iostream.h>
#include <fstream.h>

int main()
{
#if 1
ifstsream file("../test.dat"); // CORRECT!
#else
ifstsream file("..\test.dat"); // INCORRECT!
#endif

// ...
}

Rappelez-vous que le backslash ("\") est utilisé dans les littéraux chaînes pour insérer des catactères spéciaux: "\n" est un passage à la ligne, "\b" un retour arrière (backspace), "\t" un tab, "\a" une "alerte", "\v" un tab vertical, etc. Ainsi, le nom de fichier représenté par la chaîne "\version\next\alpha\beta\test.dat" va se trouver interprété comme contenant des caractères très bizarres; utilisez plutôt "/version/next/alpha/beta/test.dat" , même sur des systèmes qui utilisent le "\" comme séparateur de répertoire (DOS, Windows, OS/2, etc). Cela est rendu possible par le fait que les fonctions de bibliothèque sur ces systèmes travaillent indifféremment avec des "/" ou "\".

Bien sur, vous pourriez utiliser "\\version\\next\\alpha\\beta\\test.dat ", mais ca pourrait vous blesser (il y a toujours la possibilité d'oublier un des "\"s, c'est plutot difficile de repérer une telle erreur) et ça n'aide pas vraiment (quel est l'avantage de "\\" sur "/"). De plus, "/" est plus portable puisque il fonctionne sous Unix, Plan 9, Inferno, Windows, OS/2, ... mais "\\" ne fonctionne lui que sur un sous-ensemble de cette liste. Donc "\\" coûte et n'apporte rien : utilisez "/".

[ Haut | Bas | Rechercher ]


[15.17] How can I tell {if a key, which key} was pressed before the user presses the ENTER key?

This is not a standard C++ feature -- C++ doesn't even require your system to have a keyboard!. That means every operating system and vendor does it somewhat differently.

Please read the documentation that came with your compiler for details on your particular installation.

(By the way, the process on UNIX typically has two steps: first set the terminal to single-character mode, then use either select() or poll() to test if a key was pressed. You might be able to adapt this code.)

[ Haut | Bas | Rechercher ]


[15.18] How can make it so keys pressed by users are not echoed on the screen?

This is not a standard C++ feature — C++ doesn't even require your system to have a keyboard or a screen. That means every operating system and vendor does it somewhat differently.

Please read the documentation that came with your compiler for details on your particular installation.

[ Haut | Bas | Rechercher ]


[15.19] How can I move the cursor around on the screen?

This is not a standard C++ feature — C++ doesn't even require your system to have a screen. That means every operating system and vendor does it somewhat differently.

Please read the documentation that came with your compiler for details on your particular installation.

[ Haut | Bas | Rechercher ]


[15.20] How can I clear the screen? Is there something like clrscr()

This is not a standard C++ feature — C++ doesn't even require your system to have a screen. That means every operating system and vendor does it somewhat differently.

Please read the documentation that came with your compiler for details on your particular installation.

[ Haut | Bas | Rechercher ]


[15.21] How can I change the colors on the screen?

This is not a standard C++ feature — C++ doesn't even require your system to have a screen. That means every operating system and vendor does it somewhat differently.

Please read the documentation that came with your compiler for details on your particular installation.

[ Haut | Bas | Rechercher ]


E-mail Marshall Cline Ecrire à l'auteur, au traducteur, ou en savoir plus sur la traduction.
C++ FAQ Lite fr | Table des matières | Index | A propos de l'auteur | © | Téléchargez votre propre copie ]
Dernière révision Sun Apr 13 23:54:24 PDT 2003