Copy of https://perso.isima.fr/loic/cpp/tp03.fr.php
tete du loic

 Loïc YON [KIUX]

  • Enseignant-chercheur
  • Référent Formation Continue
  • Responsable des contrats pros ingénieur
  • Référent entrepreneuriat
  • Responsable de la filière F2 ingénieur
  • Secouriste Sauveteur du Travail
mail
loic.yon@isima.fr
phone
(+33 / 0) 4 73 40 50 42
location_on
Institut d'informatique ISIMA
  • twitter
  • linkedin
  • viadeo

[C++] TP 3

 Cette page commence à dater. Son contenu n'est peut-être plus à jour. Contactez-moi si c'est le cas!
Read in English

Date de première publication : 2013/09/24

Causettes

Les exercices suivants proposent d'analyser quelques situations afin d'éviter de les reproduire

On réutilise la classe Bavarde développée au TP précédent

Tableaux verbeux

Ajouter une méthode afficher() qui affiche sur la sortie standard "Affichage de %" et exécuter le code suivant.

int main(int, char **) {
  const int TAILLE = 20;
  Bavarde   tab1[TAILLE];
  Bavarde * tab2 = new Bavarde[TAILLE];
  // Combien d'instances sont créées ?

  for (int i =0; i < TAILLE; ++i) {
     tab1[i].afficher();
     tab2[i].afficher();
  }

  // Combien d'instances sont détruites ?
  return 0;
}

Évidemment, la mémoire allouée à tab2 n'est pas libérée...Ce qu'il faut faire avec la version adaptée de delete. Il faut également noter que des instances ont été créées lors de la création des tableaux.

Objet complexe : un couple

Écrire une classe Couple qui possède deux attributs de type Bavarde.

L'option -Wreorder permet d'être prévenu de cette situation, elle est incluse dans -Wall

Objet complexe : une famille nombreuse

malloc/free vs new/delete

Instancier un objet de classe Bavarde avec un malloc(). Afficher le champ "valeur" de l'objet. Que se passe-t-il ? (à comparer avec l'utilisation de new)

Exécutez valgrind !!!

Pour inclure un fichier d'entête C (malloc() et free() sont définies dans stdlib.h), c'est facile, il faut préfixer par -c- et omettre l'extension :

#include <cstdlib>

Héritage simple public

Illustration

Un objet de classe Fille est avant tout un objet de la classe Mere, c'est pourquoi construire un nouvel objet Fille fait appel en premier lieu au constructeur de la classe Mere. La destruction se fait en sens inverse.

Un objet de classe Fille est un objet de classe Mere donc le constructeur de la classe mère par défaut est appelé, même si on ne le précise pas. Je préconise de le spécifier tout de même, cela "documente" le code.

Il faut un attribut de classe dans la classe Mere pour faire cela

On peut bien dire que la méthode a été héritée car elle est disponible sans avoir eu à la réécrire dans la classe Fille.

Le constructeur avec paramètre de Mere n'a pas été hérité. Il faut en écrire un pour Fille. En C++11, il est possible de récupérer les constructeurs de la classe mère.

Mere  *pm = new Mere("mere_dyn");
Fille *pf = new Fille("fille_dyn");
Mere  *pp = new Fille("fille vue comme mere");
pm->afficher(); // affiche Mere
pf->afficher(); // affiche Fille
pp->afficher(); // affiche Fille

Une petite question ...

Que fait le programme suivant ?

class Mere {
  protected:
    std::string nom;
  public: 
    Mere(string s="pas fourni"):nom(s) {
    }
   
   void methode1() {
    std::cout << "Methode1(): " << nom << std::endl;
   }
};

class Fille : public Mere {
  private:
    std::string nom;
  public:
  
  Fille():Mere("noname") {
  }

  void methode2() {
    std::cout << "Methode2(): " << nom << std::endl;
    }
};

int main(int, char**) {
   Fille f;

   f.methode1();
   f.methode2();
}

Vous pouvez copier-coller ou retaper le code mais vous pouvez aussi le récupérer grâce à git (question.cpp)

Si vous avez configuré l'environnement git, tapez les commandes suivantes :

git clone https://gitlab.com/kiux/CPP3.git

L'attribut nom de Mere est masqué dans la classe Fille. Pour le retrouver, il faut utiliser Mere::nom

Messages

Cet exercice n'est pas difficile au niveau de la modélisation mais il est nécessaire de bien séparer la déclaration de l'implémentation. Vous ne devez pas y passer plus de 10 minutes

Écrire deux classes A et B. La classe A possède un entier i, et la classe B un entier j. Ces deux classes ont chacune une méthode exec() et une méthode send() qui leur permet d’envoyer un message à un objet de l’autre classe.

L’exécution du corps d’une méthode send() lance un exec() sur l’objet distant avec une constante de votre choix. Ainsi unA.send(&unB) active la méthode send() de la classe A qui lance la méthode exec() de la classe B.

Pour que cet exercice soit formateur, il faut :

La classe B a besoin de la classe A et la classe A a besoin de la classe B. Les déclarations de classe se mordent la queue. Pour que cela marche, il faut utiliser les déclarations anticipées mais il faut surtout ne manipuler que des références et des pointeurs sur des objets de l'autre classe sous peine de ne pas arriver à instancier des objets qui ne sont pas complètement définis.

Exercice de modélisation

Pour faire cet exercice, il faut tout d'abord lire la fiche sur les flux et la fiche sur les tests. Le squelette du programme à écrire se trouve également sur le git :

git clone https://gitlab.com/kiux/CPP3.git

Nous voulons rendre service à un personne qui fait des statistiques sur des données provenant de différentes sources (des producteurs).

TEST_CASE("Producteur_Initialisation") {
  Producteur p;
  REQUIRE( p.getTravail() == 0);
}
TEST(Producteur, Initialisation) {
  Producteur p;
  ASSERT_EQ(0, p.getTravail());
}
TEST_CASE("Producteur_travail2") {
  Producteur p;
  p.produire(10, "test01.txt");
  p.produire(10, "test01.txt");
  p.produire(10, "test01.txt");
  REQUIRE( p.getTravail() == 3);
}
TEST(Producteur, Travail2) {
  Producteur p;
  p.produire(10, "test01.txt");
  p.produire(10, "test01.txt");
  p.produire(10, "test01.txt");
  ASSERT_EQ(3, p.getTravail());
}
TEST_CASE("Producteur_Travail3") {

    const int         DEMANDE    = 10;
    const std::string NOM_FICHIER("test01.txt");
    int               lecture, i;
    Producteur        p; 


    p.produire(DEMANDE, NOM_FICHIER.c_str());

    std::ifstream fichier(NOM_FICHIER.c_str());

    REQUIRE(fichier.is_open());

    if (!fichier.eof()) {
      fichier >> lecture; 
      REQUIRE(DEMANDE == lecture);
      for (i = 0; i < DEMANDE; ++i) {
        fichier >> lecture;
        REQUIRE( lecture == (i+1) );
      }
    
    REQUIRE(i == DEMANDE);
    // CHECK(fichier.eof());
    fichier.close();

    REQUIRE(p.getTravail() == 1);
  }
}
TEST(Producteur, Travail3) {

    const int         DEMANDE    = 10;
    const std::string NOM_FICHIER("test01.txt");
    int               lecture, i;
    Producteur        p; 


    p.produire(DEMANDE, NOM_FICHIER.c_str());

    std::ifstream fichier(NOM_FICHIER.c_str());

    ASSERT_TRUE(fichier.is_open());

    if (!fichier.eof()) {
      fichier >> lecture; 
      EXPECT_EQ(DEMANDE, lecture);
      for (i = 0; i < DEMANDE; ++i) {
        fichier >> lecture;
        EXPECT_EQ(i+1, lecture);
      }
    
    EXPECT_EQ(i, DEMANDE);
    // EXPECT_TRUE(fichier.eof());
    fichier.close();

    ASSERT_EQ(p.getTravail(), 1);
  }
}
TEST_CASE("Statisticien_Initialisation") {
  Statisticien p;
  REQUIRE_FALSE(p.aCalcule());
}
TEST(Statisticien, Initialisation) {
  Statisticien p;
  ASSERT_FALSE(p.aCalcule());
}

Si vous avez vu en cours la notion de classe abstraite :

Fil rouge ...gesture

Nous allons continuer l'application fil rouge.

Liste
+ cercles : tableau
+ nb_c : entier
+ rectangles : tableau
+ nb_r : entier
+ compteur : entier
+ Liste()
+ getCompteur() : entier
+ toString() : chaine

Cette manière de stocker les objets n'est ni pratique ni efficace, le C++ nous permet de faire bien mieux avec le modèle objet, ce que l'on fera plus tard !