• 10 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 10/02/2022

Utilisez les itérateurs sur les flux

 

Au chapitre sur les itérateurs, je vous avais présenté deux catégories d'itérateurs :

  1. Les random access iterators (itérateurs à accès aléatoires) qui permettent d'accéder directement à n'importe quelle case d'un tableau.

  2. Les bidirectional iterators (itérateurs bidirectionnels) qui, eux, ne peuvent avancer et reculer que d'une case à la fois, sans pouvoir aller directement à une position donnée.

En réalité, j'avais simplifié les choses. Il existe encore deux autres catégories d'itérateurs :

  1. Les itérateurs sur les flux entrants.

  2. Les itérateurs sur les flux sortants.

Voyons tout cela en détail.

Déclarez un itérateur sur un flux sortant

Déclarons pour commencer un itérateur sur le flux sortant cout  :

#include <iostream>
#include <iterator>
using namespace std;

int main()
{
    ostream_iterator<double> it(cout);

    return 0;
}

Ce code déclare un itérateur sur le flux sortant cout  , permettant d'écrire des double  . Vous remarquerez deux choses différentes de ce qu'on a vu jusqu'à maintenant :

  1. On n'utilise pas la syntaxe conteneur::iterator  .

  2. Il faut indiquer entre les chevrons le type des éléments envoyés dans le flux.

Mais, à part cela, tout fonctionne comme d'habitude. On peut utiliser l'itérateur via son opérateur *  :

#include <iostream>
#include <iterator>
using namespace std;

int main()
{
    ostream_iterator<double> it(cout);
    *it = 3.14;
    *it = 2.71;

    return 0;
}

… ce qui aura pour effet d'écrire dans la console :

3.142.71

Les deux nombres ont bien été écrits. Le seul problème, c'est que nous n'avons pas inséré d'espace entre eux.

Essayons de mettre une virgule et un espace pour voir :

#include <iostream>
#include <iterator>
using namespace std;

int main()
{
    ostream_iterator<double> it(cout, ", ");
    *it = 3.14;
    *it = 2.71;

    return 0;
}

Ce qui donne :

3.14, 2.71,

Parfait ! Juste ce que l'on voulait.

Déclarez un itérateur sur un flux entrant

Les itérateurs sur les flux entrants s'utilisent exactement de la même manière.

Pour déclarer l'itérateur, il faut :

  1. Spécifier entre les chevrons le type d'objet.

  2. Et passer en argument du constructeur le flux à lire.

Pour lire depuis un fichier, on aurait ainsi la déclaration suivante :

ifstream fichier("C:/Nanoc/data.txt");
istream_iterator<double> it(fichier);    //Un itérateur lisant des doubles depuis le fichier
#include <fstream>
#include <iterator>
using namespace std;

int main()
{
    ifstream fichier("C:/Nanoc/data.txt");
    istream_iterator<double> it(fichier); 

    double a,b;
    a = *it;    //On lit le premier nombre du fichier
    ++it;       //On passe au suivant
    b = *it;    //On lit le deuxième nombre

    return 0;
}

Bref, ce n'est pas très complexe.

Il faut cependant savoir s'arrêter à la fin du fichier. Heureusement, les concepteurs de la SL ont pensé à tout !

Pour lire un fichier du début à la fin et l'afficher dans la console, on procède ainsi :

#include <fstream>
#include <iterator>
#include <iostream>
using namespace std;

int main()
{
    ifstream fichier("data.txt");
    istream_iterator<double> it(fichier); //Un itérateur sur le fichier                           
    istream_iterator<double> end;         //Le signal de fin

    while(it != end)   //Tant qu'on n'a pas atteint la fin
    {
        cout << *it << endl;  //On lit
        ++it;                 //Et on avance
    }
    return 0;
}

Tiens, cela me donne une idée. Plutôt que d'utiliser directement cout pour afficher les valeurs lues, essayez de réécrire ce code avec un itérateur sur un flux sortant !

Retrouvez les algorithmes

Bon, jusque-là, utiliser ces nouveaux itérateurs n'a rien amené de vraiment intéressant. À part pour frimer dans les discussions de programmeurs, tout cela est un peu inutile. C'est parce que nous n'avons pas encore appris à utiliser les algorithmes ! Comme nous avons des itérateurs, il ne nous reste qu'à les utiliser à bon escient !

Commençons avec l'algorithme qui est certainement le plus utilisé dans ce contexte : copy  . 

Copiez depuis un fichier vers un  vector  avec l'algorithme copy

Il arrive très souvent de devoir lire des valeurs depuis un fichier pour les stocker dans un vector  . Il s'agit simplement de lire les éléments depuis le flux, et de les insérer dans le tableau créé au préalable.

La fonction copyreçoit trois arguments :

  1. Le début de la zone à lire.

  2. La fin de la zone à lire.

  3. Un itérateur sur le début de la zone à écrire.

Pour copier depuis un fichier vers un vector  , on ferait donc ceci :

#include <algorithm>
#include <vector>
#include <iterator>
#include <fstream>
using namespace std;

int main()
{
  vector<int> tab(100,0);
  ifstream fichier("C:/Nanoc/data.txt");
  istream_iterator<int> it(fichier);
  istream_iterator<int> fin;
  copy(it, fin, tab.begin());     //On copie le contenu du fichier du début à la fin dans le vector

  return 0;
}

On peut utiliser copy pour écrire dans un fichier ou dans la console, et donc remplacer la boucle d'affichage des valeurs par un appel à copy  , comme ceci :

int main()
{
    srand(time(0));
    vector<int> tab(100,-1); //Un tableau de 100 cases

    //On génère les nombres aléatoires
    generate(tab.begin(), tab.end(), Generer());      
    //On trie le tableau   
    sort(tab.begin(), tab.end());                     
    //Et on l'affiche
    copy(tab.begin(), tab.end(), ostream_iterator<int>(cout, "\n");
    return 0;
}

C'est simple et efficace. On ne s'embête plus avec des boucles. Tout est caché derrière des noms de fonctions qui décrivent bien ce qui se passe. Le code est ainsi devenu plus lisible et compréhensible, et il n'a bien sûr rien perdu en efficacité.

Gérez le problème de la taille des tableaux

Lorsqu'on lit des données dans un fichier pour les insérer dans un tableau, il y a un problème qui survient assez souvent : celui de la taille à donner au tableau.

On ne sait pas forcément, avant de lire le fichier, combien de valeurs il contient. Et ce serait dommage de le lire deux fois, simplement pour obtenir cette information !

Pour déclarer un de ces itérateurs sur un vector  , on écrit ceci :

vector<string> tableau; //Un tableau vide de chaînes de caractères

//Un itérateur capable de faire grandir le tableau
back_insert_iterator<vector<string> >  it2(tableau);

Cet itérateur s'utilise comme n'importe quel autre itérateur.

La seule différence se ressent au moment de l'appel à l'opérateur  *  . Au lieu de modifier une case, l'itérateur en ajoute une nouvelle à la fin du tableau.

Nous pouvons donc reprendre le code qui copiait un fichier dans un tableau pour l'améliorer :

#include <algorithm>
#include <vector>
#include <iterator>
#include <fstream>
using namespace std;

int main()
{
  vector<int> tab;   //Un tableau vide
  ifstream fichier("C:/Nanoc/data.txt");
  
  istream_iterator<int> it(fichier);
  istream_iterator<int> fin;
  back_insert_iterator<vector<int> > it2(tab);
  
  //L'algorithme ajoute les cases nécessaires au tableau
  copy(it, fin, it2);  

  return 0;
}

Terminons par quelques autres algorithmes utilisables avec des fichiers.

Voici par exemple les lignes permettant de trouver le minimum des valeurs dans un fichier :

ifstream fichier("C:/Nanoc/data.txt");
cout << *min_element(istream_iterator<int>(fichier), istream_iterator<int>())<< endl;

Insérez un nombre dans une string

Les flux sont un concept tellement puissant que les créateurs de la bibliothèque standard ont décidé de l'appliquer également aux chaînes de caractères.

Jusqu'à maintenant, vous avez appris à modifier les string via l'opérateur []  , mais vous n'avez jamais vu comment insérer un nombre dans une chaîne de caractères.

Pour créer de tels objets, il suffit de passer en argument au constructeur la chaîne sur laquelle le flux va travailler. On peut alors récupérer la chaîne de caractères en utilisant la méthode str()  .

Auparavant, il faut, comme toujours, inclure le bon fichier d'en-tête : sstream  .

#include <string>
#include <sstream>
#include <iostream>
using namespace std;

int main()
{
  ostringstream flux;   //Un flux permettant d'écrire dans une chaîne            

  flux << "Salut les";  //On écrit dans le flux grâce à l'opérateur <<          
  flux << " developpeurs";
  flux << " !";

  string const chaine = flux.str(); //On récupère la chaîne                     

  cout << chaine << endl;  //Affiche 'Salut les developpeurs !'                        
  return 0;
}

Une fois que le flux est déclaré, on utilise simplement les chevrons pour écrire dans la chaîne.

Si vous souhaitez insérer un nombre dans unestring  , il n'y a aucune différence. Tout se passe comme si on utilisait cout  :

string chaine("Le nombre pi vaut: ");
double const pi(3.1415);

ostringstream flux;
flux << chaine;
flux << pi;

cout << flux.str() << endl;

On combine la simplicité d'utilisation des string à la liberté sur l'écriture des types que donne l'utilisation des flux. C'est assez magique, je trouve !

Enfin, sachez que l'on peut tout à fait utiliser les itérateurs sur les ostringstream etistringstream comme sur n'importe quel autre flux. Vous pouvez ainsi coupler la puissance des itérateurs et algorithmes à tout ce que vous savez sur les string  . Mais personne ne procède ainsi : la solution correcte est présentée au prochain chapitre !

En résumé

  • Il existe des itérateurs sur les flux. Ils ne peuvent qu'avancer. Ils ne possèdent donc que l'opérateur ++ et l'opérateur *  .

  • On peut utiliser ces itérateurs avec les algorithmes pour simplifier nos programmes.

  • On peut écrire et lire dans les chaînes de caractères grâce aux istringstream etostringstream  . Cela permet de combiner la puissance des flux à la simplicité des string  .

  • On utilise les stringstream pour convertir des nombres en chaîne et vice versa.

Vous savez maintenant tout sur les itérateurs !

C'est bientôt la fin de cette deuxième partie, plus qu’un chapitre : nous allons évoquer quelques notions intéressantes à connaître pour se perfectionner en C++ avec la SL. C’est parti !

Exemple de certificat de réussite
Exemple de certificat de réussite