Choisissez votre style : colorisé, impression

Correction 13
Flots

Les solutions suivantes ne sont évidemment pas uniques. Si vous en trouvez de meilleures (ou si vous trouvez des erreurs), faites-le savoir ! Merci.
Par ailleurs, les solutions proposées correspondent à l'état de vos connaissances au moment où la série est abordée. D'autres solutions pourront être envisagées une fois de nouveaux concepts acquis.
Exercice 1 Exercice 2 Exercice 3

Exercice 1

Exercice n°28 (page 74 et 239) de l'ouvrage C++ par la pratique.
#include <string>
#include <fstream>
#include <iostream>
#include <limits>
using namespace std;

const string nom_fichier("data.dat"); // le nom du fichier

int main()
{
  ofstream fichier(nom_fichier);  // le flot à destination du fichier

  // on teste si l'ouverture du flot s'est bien réalisée
  if (fichier.fail()) {
    cerr << "Erreur: le fichier " << nom_fichier
         << " ne peut etre ouvert en écriture !" << endl;
  } else {

    string nom;        // la donnée "nom" à lire depuis le clavier
    unsigned int age;  // la donnée "age" à lire depuis le clavier

    // itération sur les demande à entrer
    do {
      cout << "Entrez un nom (CTRL+D pour terminer) : " << flush;
      cin >> nom;
      if (!cin.eof()) {

        // L'utilisateur a bien saisi un nom, on peut donc lui demander
        // de saisir l'age.

        cout << "âge : " << flush;
        cin >> age;

	if (cin.fail()) {
	  cout << "Je vous demande un age (nombre entier positif) pas "
	       << "n'importe quoi !" << endl;
	  cout << "Cet enregistrement est annulé." << endl;
	  cin.clear();
	   // "jette" tout le reste de la ligne
          cin.ignore(numeric_limits<streamsize>::max(), '\n');
	} else {
	  // ecriture dans le fichier
	  fichier << nom << ' ' << age << endl;
	}
      } else {
	cout << endl; // purisme pour allez à la ligne après le dernier flush
      }
    } while (!cin.eof()); // et on continue tant que cin est lisible.

    fichier.close(); // fermeture du flot fichier
  }
  return 0;
}

Exercice 2

Exercice n°29 (page 75 et 240) de l'ouvrage C++ par la pratique.
#include <string>
#include <fstream>
#include <iostream>
#include <iomanip>
using namespace std;

const string nom_fichier("data.dat"); // le nom du fichier

int main()
{
  // le flot d'entrée en provenance du fichier
  ifstream fichier(nom_fichier);

  if (fichier.fail()) {
    cerr << "Erreur: le fichier " << nom_fichier
         << " ne peut etre ouvert en lecture !" << endl;
  } else {
    // si l'ouverture s'est bien produite...

    string nom;       // les données à lire dans le fichier...
    unsigned int age; // ...pas nécessaire de les initialiser

    unsigned int nb(0); // variables nécessaires aux différents calculs
    unsigned int age_max(0);
    unsigned int age_min(300); // je pense que 300 ans est assez large !!
    double total(0.0);

    // On commence par l'affichage du cadre
    cout << "+"
	 << setfill('-') << setw(18) << "+"
         << setfill('-') << setw(6)  << "+" << endl
	 << setfill(' ');

    /*
     * Et on boucle directement sur la condition de lecture correcte
     * du couple <nom,age> (en fait, sur la condition de lecture correcte
     * de 'age', mais comme il n'est pas possible de lire 'age' si la
     * lecture de 'nom' à échoué...
     */

    do {
      fichier >> nom >> age;

      if (!fichier.fail()) {
	// mise à jour des variables utilisées pour les calculs finaux
	++nb;
	total += age;
	if (age_min > age) age_min = age;
	if (age_max < age) age_max = age;

	// --------------------------------------------------
	// AFFICHAGE
	// --------------------------------------------------

	cout.setf(ios::left);  // on définit un alignement à gauche;

	// le nom sur 15 caractères, en chaine de taille fixe
	cout << "| " << setw(15) << nom;

	/*  Pour l'affichage de l'age, on enleve le modificateur
	 * 'alignement à gauche'
	 */
	cout.unsetf(ios::left);

	// affiche l'age sur 3 caractères
	cout << " | " << setw(3) << age << " |" << endl;
      }
    } while (!fichier.eof());

    // --------------------------------------------------
    // Partie finale
    // --------------------------------------------------

    fichier.close();  // ne pas oublier de fermer le stream (et le fichier)

    cout << "+"
	 << setfill('-') << setw(18) << "+"
	 << setfill('-') << setw(6)  << "+" << endl
	 << setfill(' ');

    // les infos 'finales' ...
    cout.setf(ios::left);
    cout << setw(18) << "  âge minimum" << ": ";
    cout.unsetf(ios::left);
    cout << setw(3) << age_min << endl;

    cout.setf(ios::left);
    cout << setw(18) << "  âge maxmimal" << ": ";
    cout.unsetf(ios::left);
    cout << setw(3) << age_max << endl;

    cout << setw(2) << nb << " personnes, âge moyen : "
	 << setw(4) << setprecision(3) << (total / nb) << " ans"
	 << endl;
  }
  return 0;
}

Exercice 3

Exercice n°30 (page 76 et 242) de l'ouvrage C++ par la pratique.
#include <iostream>
#include <iomanip>
#include <fstream>
#include <array>
using namespace std;

// ===== CONSTANTES =====

// nombre maximum de demandes en cas d'erreur
constexpr unsigned short int NB_DEMANDES(3);

// taille maximum d'une Statistique : au plus 256 car il n'y a pas plus
// que 256 char - attention, avec des entiers signés, on ne peut aller
// au-dela du 127è caractère (sinon, les indices sont négatifs).
constexpr unsigned short int TAILLE(256);

// bornes sur les caractères à prendre en compte
constexpr char start(' ');
constexpr char stop('}');

// ===== DEFINITIONS DE TYPES ======

typedef array<unsigned long int, TAILLE> Statistique;


// ===== FONCTIONS ======
bool demander_fichier(ifstream& f,
		      unsigned short int max_demandes = NB_DEMANDES);

void initialise_statistique(Statistique& a_initialiser);

unsigned long int collecte_statistique(Statistique& a_remplir,
				       ifstream& fichier_a_lire);

void affiche(const Statistique a_afficher, unsigned long int total = 0,
	     unsigned short int taille = TAILLE);

// ======================================================================
int main()
{
  ifstream f;
  if (! demander_fichier(f)) {
    cout << "=> j'abandonne !" << endl;
  } else {
    Statistique stat;
    initialise_statistique(stat);
    affiche(stat, collecte_statistique(stat, f), stop-start+1);
    f.close();
  }

  return 0;
}

/* ======================================================================
 * Fonction demander_fichier
 * ----------------------------------------------------------------------
 * In:   Un ifstream (par référence) à ouvrir et le nombre maximum de demande
 *       (par défaut NB_DEMANDES).
 * Out:  Ouvert ou non ?
 * What: Demande à l'utilisateur (au plus max fois) un nom de fichier et
 *       essaye de l'ouvrir
 * ====================================================================== */
bool demander_fichier(ifstream& f, unsigned short int max)
{
  string nom;
  unsigned short int nb(0);

  do {
    f.clear(); ++nb;

    // demande le nom du fichier
    do {
      cin.clear();
      cout << "Nom du fichier à lire : " << flush;
      cin >> nom;
    } while (cin.fail());

    // essaye d'ouvrir le fichier
    f.open(nom);

    // est-ce que ça a marché ?
    if (f.fail()) {
      cout << "-> ERREUR, je ne peux pas lire le fichier "
	   << nom << endl;
    } else {
      cout << "-> OK, fichier " << nom << " ouvert pour lecture."
	   << endl;
    }
  } while (f.fail() && (nb < NB_DEMANDES));

  return !f.fail();
}


/* ======================================================================
 * Fonction initialiser_statistique
 * ----------------------------------------------------------------------
 * In:   Une Statistique (par référence) à initialiser.
 * What: Initialiser tous les éléments d'une Statistique à zéro.
 * ====================================================================== */
void initialise_statistique(Statistique& stat)
{
  for (int i(0); i < TAILLE; ++i) {
    stat[i] = 0;
  }
}

/* ======================================================================
 * Fonction collecte_statistique
 * ----------------------------------------------------------------------
 * In:   Une Statistique (par référence) à remplir et le fichier à lire.
 * Out:  Le nombre d'éléments comptés dans la Statistique.
 * What: Lit tous les caractères dans le fichier et compte dans la Statistique
 *       combien de fois chaque caractère apparait dans le fichier.
 * ====================================================================== */
unsigned long int collecte_statistique(Statistique& stat, ifstream& f)
{
  char c;  // le charactère lu
  unsigned long int nb(0); // le nombre d'éléments comptés

  while (f.get(c)) {
    if ((c >= start) and  (c <= stop)) {
      ++(stat[c-start]);
      ++nb;
    }
  }

  return nb;
}

/* ======================================================================
 * Fonction affiche
 * ----------------------------------------------------------------------
 * In:   La Statistique à afficher, le nombre par rapport auquel on affiche
 *       les pourcentages (si 0 recalcule ce nombre comme la somme des
 *       éléments) et la taille du tableau.
 * What: Affiche tous les éléments d'une Statistique (valeurs absolue et
 *       relative).
 * ====================================================================== */
void affiche(const Statistique stat, unsigned long int nb,
	     unsigned short int taille)
{
  if (nb == 0) {
    for (unsigned short int i(0); i < taille; ++i)
      nb += stat[i];
  }

  const double total(nb);

  cout << "STATISTIQUES :" << endl << setprecision(3);
  for (unsigned short int i(0); i < taille; ++i) {
    if (stat[i] != 0) {
      cout << char(i+start) << " : " << setw(11) << stat[i] << " - "
	   << setw(5) << 100.0 * stat[i] / total << "%" << endl;
    }
  }
}

Remarques

En C/C++, le type char peut être un type signé (prenant valeur dans [-128,127]) ou non-signé ([0,255]), selon les systèmes (avec la plupart des compilateurs - dont gcc - il est cependant possible de préciser, lors de la compilation, la convention que l'on souhaite voir appliquer). Par conséquent, si l'on souhaite établir une statistique comprenant l'ensemble des caractères du code ANSI (ou simplement des caractères au-dela du 127è), le code précédent ne fonctionne plus.

Il faut alors soit procéder à une gymnastique compliquée lors des comparaisons des valeurs ou des calculs d'indices, soit déclarer comme caractères non signés tous les char du programme (soit unsigned char). Le problème est qu'on ne peut demander à la fonction get() d'extraire un caractère signé(signed char) ou non signé(unsigned char); elle ne sait qu'extraire un caractère (char). Pour pallier ce manque au niveau de la librairie standard, il n'y a guère d'autre choix que de lire un caractère (signé ou non, celui qui écrit le programme ne pouvant le savoir à l'avance) au moyen d'une variable intermédaire de type char, et de convertir ensuite ce caractère en un entier non signé; idéalement, cela se fait avec l'un des opérateurs de transtypage (cast)...

Comme nous ne souhaitons pas entrer sur ce terrain (il y aurait beaucoup à dire pour que vous puissiez comprendre les subtilités de ces opérations et ce qu'elles impliquent), voici une version 'calculatoire' (donc peu efficace relativement à l'autre solution) :

#include <iostream>
#include <iomanip>
#include <fstream>

...

// bornes sur les caractères à prendre en compte
const unsigned char start(' ');
const unsigned char stop('ÿ');

...

/* ======================================================================
 * Fonction collecte_statistique
 * ----------------------------------------------------------------------
 * In:   Une Statistique (par référence) à remplir et le fichier à lire.
 * Out:  Le nombre d'éléments comptés dans la Statistique.
 * What: Lit tous les caractères dans le fichier et compte dans la Statistique
 *       combien de fois chaque caractère apparait dans le fichier.
 * ====================================================================== */
unsigned long int collecte_statistique(Statistique& stat, ifstream& f)
{
  char c_lu;        // le caractère lu (signé ou non, suivant le système)
  unsigned char c;  // le caractère lu dans sa forme non signée
  unsigned long int nb(0); // le nombre d'éléments comptés

  while (f.get(c_lu)) {
   	c = (256 + c_lu) % 256;  // obtenir la version positive de la représentation
    if ((c >= start) && (c <= stop)) {
      ++(stat[c-start]);
      ++nb;
    }
  }

  return nb;
}

...




Dernière mise à jour : $Date: 2024/12/02 14:45 $