FAQ C++Consultez toutes les FAQ
Nombre d'auteurs : 34, nombre de questions : 368, dernière mise à jour : 14 novembre 2021 Ajouter une question
Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums de http://www.developpez.com et de l'expérience personnelle des auteurs.
Je tiens à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur ou si vous souhaitez devenir rédacteur, lisez ceci.
Sur ce, nous vous souhaitons une bonne lecture.
- 15.1. Manipulation de la console (8)
- 15.2. Manipulation des fichiers (14)
- 15.3. Représentation - Formatage (4)
- À quoi sert std::endl ?
- Comment utiliser les flux pour afficher ou saisir mes objets ?
- Comment effacer le contenu d'un ostringstream ?
- [Exemple] Comment convertir un tableau en chaîne ?
- Comment rediriger l'entrée ou la sortie standard ?
- Comment utiliser les itérateurs de flux ?
- Comment fonctionne le test de réussite de conversion if ( str >> num ) ?
- Qu'est-ce qu'un tampon, un buffer ?
- Que signifie vider ou purger un tampon ?
En plus de faire un retour à la ligne en ajoutant un caractère '\n', std::endl purge le buffer de sortie et force ainsi son écriture en appelant ostream::flush() (cela a le même fonctionnement que la fonction fflush() du C).
Les deux lignes de code suivantes sont donc équivalentes :
Code c++ : | Sélectionner tout |
1 2 | std::cout << "coucou" << std::endl; std::cout << "coucou\n" << std::flush; |
Il faut donc être prudent avec son utilisation, notamment avec les fichiers, car une opération de flush n'est pas gratuite. Son utilisation fréquente peut même sérieusement grever les performances en annulant tous les bénéfices d'une écriture bufferisée.
Pour injecter un objet de type quelconque dans un flux de sortie, il suffit de définir un opérateur << prenant en paramètre le flux, ainsi que l'objet à injecter.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <ostream> #include <string> class MaClasse { friend std::ostream& operator <<(std::ostream&, const MaClasse&); private : int Integer; std::string String; }; std::ostream& operator <<(std::ostream& Stream, const MaClasse& Obj) { Stream << Obj.Integer << " " << Obj.String; return Stream; // N'oubliez pas de renvoyer le flux, afin de pouvoir chaîner les appels } MaClasse Obj; std::cout << "Ma classe : " << Obj << std::endl; |
Le fonctionnement est le même pour l'extraction de valeurs à partir d'un flux d'entrée, via l'opérateur >> :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <istream> #include <string> class MaClasse { friend std::istream& operator >>(std::istream&, MaClasse&); private : int Integer; std::string String; }; std::istream& operator >>(std::istream& Stream, MaClasse& Obj) { Stream >> Obj.Integer >> Obj.String; return Stream; // N'oubliez pas de renvoyer le flux, afin de pouvoir chaîner les appels } MaClasse Obj; std::cin >> Obj; |
Notez bien que ce genre de surcharge ne peut pas être membre de la classe, car cela impliquerait que l'opérande gauche soit l'objet et non le flux.
Enfin, si vous devez gérer l'injection et l'extraction sur une hiérarchie d'objets polymorphes, il faudra mettre en place une petite astuce (voir Comment surcharger correctement l'opérateur << pour afficher des objets polymorphes ?).
La fonction membre ostringstream::str() a deux surcharges : une sans paramètre qui renvoie le contenu du flux sous forme de string, et une autre qui accepte une chaîne en paramètre pour réinitialiser le contenu du flux. Pour vider le flux il suffit donc d'appeler cette fonction avec comme paramètre la chaîne vide.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <sstream> #include <string> std::ostringstream oss; std::string Fichier[10]; // Génère une suite de noms de fichiers numérotés de 0 à 9 for (int i = 0; i < 10; ++i) { oss.str(""); oss << "Image_" << i << ".bmp"; Fichier[i] = oss.str(); } |
De la même manière que dans Comment obtenir la représentation hexadécimale d'un entier ?, on peut obtenir la représentation hexadécimale d'un buffer de caractères en représentation hexadécimale à l'aide du manipulateur std::hex.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include <algorithm> #include <iomanip> #include <sstream> #include <string> std::string array_to_string(const unsigned char* buffer, std::size_t size) { std::ostringstream oss; // Afficher en hexadécimal et en majuscule oss << std::hex << std::uppercase; // Injecter tous les caractères sous forme d'entiers dans le flux std::copy(buffer, buffer + size, std::ostream_iterator<int>(oss, "")); return oss.str(); } |
Si l'on souhaite personnaliser la représentation hexadécimale, on peut développer la boucle de copie comme ceci :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #include <algorithm> #include <iomanip> #include <sstream> #include <string> std::string array_to_string(const unsigned char* buffer, std::size_t size) { std::ostringstream oss; // Afficher en hexadécimal et en majuscule oss << std::hex << std::uppercase; // Remplir les blancs avec des zéros oss << std::setfill('0'); for (std::size_t i = 0; i < size; ++i) { // Séparer chaque octet par un espace if (i != 0) oss << ' '; // Afficher sa valeur hexadécimale précédée de "0x" // setw(2) permet de forcer l'affichage à 2 caractères oss << "0x" << std::setw(2) << static_cast<int>(buffer[i]); } return oss.str(); } int main() { const std::size_t size = 5; const unsigned char data[size] = {0x0C, 0x10, 0xFF, 0x00, 0xDA}; std::cout << array_to_string(data, size) << '\n'; } |
0x0C 0x10 0xFF 0x00 0xDA
Afin de rediriger l'entrée ou l'une des sorties standard (cin, cout, cerr ou clog) vers un autre flux, il suffit de réaffecter leur streambuf (le streambuf est l'objet qui fait le lien entre le flux et ce vers quoi il lit / écrit).
Afin d'accéder au streambuf d'un flux, il faut utiliser la fonction membre rdbuf (ses deux surcharges pour être précis) :
Code c++ : | Sélectionner tout |
1 2 3 4 5 | // Récupérer le streambuf d'un flux std::streambuf* std::ios::rdbuf(); // Changer le streambuf d'un flux void std::ios::rdbuf(std::streambuf*); |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <fstream> #include <iostream> int main() { std::cout << "Affichage sur la console"<< std::endl; // Redirection de std::cout vers le fichier Toto.txt std::ofstream Out("Toto.txt"); std::streambuf* OldBuf = std::cout.rdbuf(Out.rdbuf()); std::cout << "Affichage dans Toto.txt"<< std::endl; // Restauration du streambuf initial de std::cout (affichage sur la console) std::cout.rdbuf(OldBuf); return 0; } |
À noter également que si vous ne souhaitez effectuer une telle redirection que ponctuellement et pour toute la durée de l'exécution, vous pouvez plus simplement utiliser une redirection au niveau de votre invite de commande (avec une syntaxe dépendante de l'OS) :
>MonProgramme.exe > Toto.txt
La STL propose deux classes pour itérer sur les flux : istream_iterator et ostream_iterator (définies dans l'en-tête <iterator>).
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 | std::vector<int> Tab; Tab.push_back(1); Tab.push_back(3); Tab.push_back(6); // Plus besoin de boucle pour afficher les éléments du tableau : std::copy(Tab.begin(), Tab.end(), std::ostream_iterator<int>(std::cout, "\n")); |
Le second paramètre de l'itérateur est simplement le délimiteur qui sera placé entre chaque élément.
Vous pouvez également utiliser les stream_iterator avec des classes quelconques, pourvu que leur opérateur << soit correctement défini.
Cela marche aussi dans l'autre sens : avec un std::istream_iterator vous pourrez extraire des valeurs / objets d'un flux les unes après les autres, et les placer par exemple dans un tableau.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | // On va extraire à partir d'un fichier, par exemple std::ifstream File("Data.txt"); // Méthode 1 : std::copy std::vector<MaClasse> Tab1; std::copy(istream_iterator<MaClasse>(File), istream_iterator<MaClasse>(), std::back_inserter(Tab1)); // std::back_inserter va remplacer les affectations par des push_back // Méthode 2 : utiliser le constructeur prenant une paire d'itérateurs std::vector<MaClasse> Tab2(istream_iterator<MaClasse>(File), (istream_iterator<MaClasse>())); |
La paire de parenthèses a priori inutile autour du second istream_iterator est nécessaire : sans cela le compilateur croirait à une déclaration de fonction. Pour éviter cela vous pouvez également déclarer les variables correspondant aux itérateurs en dehors de l'appel :
Code c++ : | Sélectionner tout |
1 2 3 | istream_iterator<MaClasse> Begin(File); istream_iterator<MaClasse> End; std::vector<MaClasse> Tab2(Begin, End); |
Pour que le code suivant compile :
Code c++ : | Sélectionner tout |
1 2 3 4 5 | istringstream s( "10" ); int n; if ( s >> n ) { } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 | istringstream s( "10" ); int n; if ( s.operator >>( n ) ) { } |
Le compilateur essaye alors de convertir cette référence (qui correspond à s après l'appel de l'opérateur >>, donc après avoir tenté la conversion en int) en booléen. istream ne définit pas d'opérateur de conversion vers bool, mais une conversion implicite est possible grâce à l'opérateur operator void*(). Ce dernier retourne un pointeur nul si les indicateurs d'échec du flux sont positionnés, ce qui est le cas en cas d'échec de conversion. L'ensemble du processus effectué par le compilateur correspond donc aux appels explicites suivants :
Code c++ : | Sélectionner tout |
1 2 3 4 5 | istringstream s( "10" ); int n; if ( s.operator >>( n ).operator void*() != 0 ) { } |
Un tampon (buffer en anglais) est un moyen d'assouplir l'utilisation d'un système lent par un autre plus rapide.
Au lieu d'accéder directement au système lent, l'utilisateur va simplement compléter une liste de demandes en attente.
Ainsi, l'utilisateur n'a plus besoin d'attendre le traitement, et peut poursuivre son activité.
En C++, le flux de sortie standard, std::cout, utilise comme tampon un tableau de caractères (caché dans la bibliothèque standard).
Cela, parce que l'image affichée à l'écran n'est pas disponible en permanence. Il en est de même avec un fichier.
On retrouve cette idée avec les files d'attente d'un péage, ou celles à l'entrée d'un routeur.
Le mécanisme d'un tampon est assez simple :
À chaque occasion qui se présente, le système lent regarde si le tampon contient au moins une entrée. Si c'est le cas, il en retire une et la traite.
Laquelle est tirée en premier dépend du besoin, mais c'est souvent la plus ancienne.
En général, un tampon est limité en taille. Quand il est plein, deux traitements sont possibles : rejeter les nouvelles demandes ou mettre en attente l'utilisateur.
Un routeur rejette les demandes, ce qui provoque des messages d'erreur tels que "Cette page ne répond pas."
Le péage va plutot prendre son temps, l'utilisateur se retrouve à attendre (c'est un bouchon).
Les tampons fonctionnent bien quand les demandes arrivent en rafales : une série de demandes, puis rien pendant un temps largement suffisant pour toutes les traiter.
Un tampon permet de réduire l'impact de la lenteur d'accès à une ressource, mais désynchronise celle-ci de l'utilisateur.
Purger un tampon (flush en anglais) consiste à demander à la ressource de traiter toutes les demandes et attendre qu'il l'ait fait.
Cela permet de resynchroniser l'ensemble, par exemple pour s'assurer que le système lent est bien dans l'état demandé.
Cela signifie ralentir le programme, certes, mais c'est un bon moyen de synchroniser des choses qui ne le sont pas de base, comme le flux de sortie standard et celui de saisie, ou encore les flux de sortie standard et d'erreur: std::cout et std::cerr.
En C++, il y a deux manipulateurs de flux de sortie qui sont utilisables pour cela: std::flush et std::endl.
La seule différence entre les deux, c'est que endl ajoute un retour à la ligne avant de purger le buffer.
Ainsi cout << 2 << flush affiche 2 immédiatement, parce que cela ajoute 2 au bout du buffer puis purge celui-ci.
De même, cout << endl se comporte comme cout << '\n' << flush.
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.