Le langage C++ offre une variété d'outils d'entrée/ sortie sur des flux de données, et utilise les classes std::istream et std::ostream comme abstractions fondamentales pour représenter (respectivement) les flux en entrée et les flux en sortie.
Appliquant héritage et polymorphisme, ceci permet de gérer sur un même pied les entrées/ sorties à la console (avec, en particulier, std::cin, une instance de std::istream liée à l'entrée standard – typiquement le clavier – et std::cout, une instance de std::ostream liée à la sortie standard – typiquement l'écran en mode console), sur un lien de communication, sur un fichier ou sur tout conteneur respectueux de certaines règles de base.
Pour l'entrée de chaînes de caractères, on aura tendance à utiliser l'opérateur >> tel que défini sur un opérande de gauche de type std::istream& et sur un opérande de droite de type std::string&, profitant ainsi de la grande souplesse du type std::string, dont les instances ont une capacité d'entreposage capable de croître en fonction des besoins (ce qui élimine les problèmes de dépassement de capacité des zones tampons, aussi nommées Buffer Overflow Problems).
#include <string>
#include <fstream>
#include <iostream>
// lit chaque mot d'un fichier texte et les affiche
// à la sortie standard, séparés par des tabulations
int main() {
using namespace std;
ifstream entree{ "in.txt" };
for (string mot; entree >> mot`)
cout << mot << '\t';
}
L'opérateur >> sur un flux en entrée et une std::string a pour comportement de lire sur le flux jusqu'à la rencontre du premier caractère d'espacement.
Si on désire lire une ligne à la fois, par exemple dans un cas où les espaces sont significatifs, alors ce n'est pas le meilleur outil.
Dans ces circonstances, on a habituellement recours à std::getline().
Il se trouve que std::getline() est une fonction très efficace qui prend en paramètre un std::istream& et un std::string& et qui lit des caractères du flux jusqu'à un délimiteur de fin de chaîne (on peut décider soi-même de lire jusqu'à un caractère particulier qui soit différent de '\n' en insérant le délimiteur désiré comme troisième paramètre de la fonction).
Il est très fréquent que des étudiants manipulant ces deux outils rencontrent des problèmes à l'usage de std::getline(). En effet, dans un programme comme celui-ci, la lecture de la chaîne s va être escamotée.
#include <string>
#include <fstream>
#include <iostream>
// lit chaque ligne d'un fichier texte et les affiche
// à la sortie standard, séparées par des sauts de ligne
int main() {
using namespace std;
ifstream entree{"in.txt"};
for (string ligne; getline(entree, ligne); )
cout << ligne << endl;
}
On peut comprendre ce qui se passe en allant tracer le code de std::getline(), qui est un template exposé dans le fichier d'en-tête <string>, mais voici en gros ce qui se produit :
|
|
Je vous propose donc la version ci-dessous, qui (elle) fonctionne bien :
#include <string>
#include <iostream>
int main() {
using namespace std;
cout << "Entrez un entier...";
int val;
cin >> val;
cout << "Entrez une ligne de texte...";
if (char c = cin.peek(); c == '\n' || c == '\r')
cin.get(c);
// lire une ligne à l'entrée standard, la mettre dans s
if (string s; getline(cin, s))
cout << s << endl;
}
L'appel à std::cin.peek() retourne le premier caractère au début du flux en entrée. S'il s'agit d'une fin de ligne, alors on le retire du flux. Il est important de vérifier la présence ou non d'un tel caractère, d'ailleurs, car deux cas sont possibles :
Solution plus simple encore, si le contenu du tampon en entrée peut être éliminé : appeler la méthode ignore() qui « oublie » que le contenu du tampon existe, pour essentiellement repartir à neuf :
#include <string>
#include <iostream>
int main() {
using namespace std;
cout << "Entrez un entier...";
int val;
cin >> val;
cout << "Entrez une ligne de texte...";
cin.ignore();
if (string s; getline(cin, s))
cout << s << endl;
}
Voilà!
Quelques liens pour enrichir le propos.