CentraleSupélecDépartement informatique
Plateau de Moulon
3 rue Joliot-Curie
F-91192 Gif-sur-Yvette cedex
3IF1020 - Concepts des langages de programmation, mise en œuvre en C/C++ - TP n°2



Table des matières

  • Date de la séance de TP : vendredi 25 octobre 2024 - 13h45
  • Date limite de prise en compte de votre version sur votre dépôt GitLab : dimanche 5 novembre 2023 - 23h59
  • Le dernier cours ayant lieu le 10 novembre, la partie avancée peut être livrée jusqu'au dimanche 10 novembre 2024 - 23h59

Utilisez les fichiers Number.hpp, Number.cpp et TestNumber.cpp du dossier clp_tp2 à la racine de votre dépôt GitLab, seul le contenu de ce dossier sera pris en compte pour l'évaluation de votre travail sur ce TP.


Pour cet exercice, et le suivant, Google Test est utilisé pour des tests unitaires. Voir sur cette page les possibilités que vous avez.

1. Présentation

Lors du TD2, nous avons conçu une solution permettant de représenter des naturels de taille quelconque.

Mais le type Number proposé ne permet pas de manipuler ces nombres avec autant de facilité que des int : il n'est pas possible de copier un nombre, on ne peut pas utiliser les opérateurs arithmétiques classiques, l'affichage ne se fait pas de la même façon...

L'objectif de ce TP est de concevoir un type Number amélioré qui solutionne ces problèmes identifiés.

2. Nouveau type (1 point)

2.1. Approche objet

Pour utiliser un Number de la même manière qu'un int, Number doit être une classe. Nous conserverons la liste simplement chainée de Digit, mais cette classe devra être non visible des utilisateurs, c'est pourquoi elle sera définie en privée à l'intérieur de Number.

Nous arrivons donc à la définition initiale suivante :

class Number {
public:
    Number( unsigned long l );
    ~Number();

private:
    using DigitType = unsigned int;
    // Un chiffre décimal par maillon : l'objectif ici n'est pas la performance
    // mais votre code doit être écrit en tenant compte de cet aspect
    static const DigitType number_base{ 10u };

    struct Digit {
        DigitType digit_;
        Digit * next_;
    };
    Digit * first_;
};
  • Définir le constructeur et le destructeur de Number.

Vous devez utiliser aussi une approche objet pour la classe Digit, donc vous devez ajouter un constructeur, un destructeur et d'autres méthodes plus tard à celle-ci. Le non respect de cette conception sera pénalisé.

La récursion telle que mise en œuvre dans le TD N°2 se fera sur les méthodes de Digit.


2.2. Affichage

  • Définir dans Number une méthode
void print( std::ostream & out ) const;

qui affiche le nombre sur le flux de sortie.

Vous ne devez pas construire une chaîne de caractères et l'afficher ensuite. Il faut afficher directement les chiffres sur le flux avec une méthode dans Digit.


  • Ajouter dans Number.hpp la définition suivante qui permet d'afficher un nombre avec le protocole de la bibliothèque standard d'entrée/sortie C++ :
inline std::ostream & operator<<( std::ostream & out, const Number & n )
{
    n.print( out );
    return out;
}

2.3. Tests de construction et d'affichage

  • Compiler et exécuter votre programme pour vérifier que les tests unitaires suivants (dans TestNumber.cpp) réussissent.
TEST( TestNumber, TestNumber0 )
{
    Number n{ 0 };
    std::ostringstream os;
    os << n;
    EXPECT_EQ( os.str(), "0" );
}

TEST( TestNumber, TestNumber12345678 )
{
    Number n{ 12345678 };
    std::ostringstream os;
    os << n;
    EXPECT_EQ( os.str(), "12345678" );
}

TEST( TestNumber, TestNumberBig )
{
    Number n{ 12345123451234512345UL };
    std::ostringstream os;
    os << n;
    EXPECT_EQ( os.str(), "12345123451234512345" );
}

TEST( TestNumber, TestNumberRandom )
{
    auto r{ std::rand() };
    Number n{ static_cast< unsigned long >( r )};
    std::ostringstream os;
    os << n;
    EXPECT_EQ( os.str(), std::to_string( r ));
}

add, commit & push


3. Classe concrète (1 point)

  • Rendre cette classe concrète (comportements de construction et d'affectation par copie).

Vous devez utiliser l'idiome du swap(). Le non respect de cette conception sera pénalisé.

Avez-vous vraiment besoin de l'affectation par copie d'un Digit ?




  • Écrire des tests unitaires pour ces comportements.


add, commit & push

4. Calculs (1 point)

Soit à calculer n!. On sait que 13! est trop grand pour pouvoir être représenté sur 32 bits. On choisit donc de représenter le résultat par un Number. Nous avons pour cela besoin d'ajouter et de multiplier un Number avec un unsigned int.

  • Ajouter les comportements d'addition et de multiplication d'un Number par un unsigned int en définissant les méthodes de signature :
void add( unsigned int i );
void multiply( unsigned int i );

Ces méthodes modifient l'objet courant.

Votre code doit être écrit de façon à tenir compte du nombre de chiffres décimaux stockés dans un Digit, et ne doit pas provoquer de débordements.


  • Écrire des tests unitaires pour ces comportements.
  • Écrire une fonction calculant la factorielle d'un entier, ayant comme signature :
Number factorial( unsigned int i );
  • Voici un test unitaire pour ce comportement.
TEST( TestNumber, TestFactorial123 )
{
    std::ostringstream os;
    os << factorial( 123 );;
    EXPECT_EQ( os.str(), "121463043670253296757662432418812958554542170884833823153289181618292"
                         "358923621676688311569606126402021707358352212940477825910915704116514"
                         "72186029519906261646730733907419814952960000000000000000000000000000" );
}

5. Lecture (0,5 point)

Nous avons vu lors du TD n°2 que la lecture chiffre par chiffre en commençant par ceux de poids forts n'est pas adaptée dès que que l'on souhaite mettre plus d'un chiffre décimal par Digit. Nous contournerons cette difficulté en permettant la construction d'un Number à partir d'une chaîne de caractères.

  • Définir un constructeur de signature
Number( std::string s );

Une exception std::invalid_argument sera émise si la chaîne de caractères est vide ou contient un caractère qui n'est pas un chiffre décimal.

Vous pouvez utiliser std::isdigit pour tester si un caractère est un chiffre décimal.

La valeur d d'un chiffre qui s'écrit avec le caractère c s'obtient par l'expression

unsigned int d{ static_cast< unsigned int >( c - '0' )}
Besoin d'aide ?

Vous pouvez ajouter un chiffre et multiplier par 10 votre nombre en construction.


  • Écrire des tests unitaires pour ce comportement.
  • Déclarer dans Number.hpp la fonction :
std::istream & operator>>( std::istream & in, Number & n );
  • Définir dans Number.cpp cette fonction :
std::istream & operator>>( std::istream & in, Number & n )
{
    // TODO
    return in;
}

permettant la lecture d'un Number selon le protocole de la bibliothèque standard d'entrée/sortie de C++. Cette fonction ignore les blancs au départ, et modifie l'argument n uniquement si elle a réussi à lire au moins un chiffre ensuite ; elle s'arrète dès qu'un caractère qui n'est pas un chiffre est rencontré.

Voici un extrait de code pour lire le flux d'entrée caractère par caractère :

  // in est le flux d'entrée

  // ignore les blancs au début
  in >> std::ws;
  while( in.good() ) {
      int c{ in.get() };
      if( std::isdigit( c )) {
          // ... à vous de l'utiliser ...
      }
      else {
          in.putback( c );
          break;
      }
  }




  • Écrire des tests unitaires pour ce comportement.



add, commit & push

6. Partie avancée (0,5 point)

La surcharge des opérateurs et les constructions et affectations par déplacement sont vus au dernier cours.

  • Remplacez les méthodes add() et multiply() par les opérateurs += et *= ; ajouter les opérateurs + et *.
  • Ajouter les comportements de construction et d'affectation par déplacement et les tests unitaires correspondants.


add, commit & push