Copy of https://perso.isima.fr/loic/cpp/tp05.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 5

Read in English

Date de première publication : 2013/10/03

La classe Chaine

Même si nous l'avons vu en cours, nous vous proposons d'implémenter la classe Chaine

Pour mémoire, le code que l'on cherche à exécuter est le suivant :

int main(int, char **) {
   Chaine s1;
   Chaine s2(6);
   Chaine s3("essai");
   Chaine s4(s3);
   Chaine s5 = s3;

   s2 = s3;
   s3.remplacer("oups");
   s3.afficher();
   cout << s3.c_str();
   cout << s3[0];
   cout << s3;

   return 0;
}

Vous allez le développer en suivant des tests unitaires et en utilisant valgrind.

La base de l'exercice est clonable ici :

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

Notes :

Constructeur de base et destructeur

TEST_CASE("Constructeur par defaut") {
   Chaine c;
   REQUIRE( -1       == c.getCapacite());
   REQUIRE(  nullptr == c.c_str()); // 0 ou NULL pour les vieux compilos
}

Nous manipulons en interne de la classe Chaine une chaine de caractères C : un tableau de caractères qui se termine par le caractère '\0'. On a besoin de cette information en interne. En externe, la capacité est bien le nombre maximal de caractères que l'on peut stocker dans la chaîne et donc getCapacite() et capacite non pas la même valeur.

Dès que l'on peut, il faut déclarer les méthodes comme constantes. Voici comment vérifier que c'est fait :

TEST_CASE("Verification des const sur les accesseurs") {
   const Chaine c;
   CHECK( -1       == c.getCapacite());
   CHECK(  nullprt == c.c_str()); 
}
TEST_CASE("Constructeur par chaine C") {
    char  source []= "rien";
    Chaine c1(source);
    CHECK( (signed) strlen(source) == c1.getCapacite() );
    CHECK( 0 == strcmp(source, c1.c_str()) ); 

    Chaine c2 = "";
    CHECK( 0 == c2.getCapacite() );
    CHECK( 0 == strcmp("", c2.c_str())); 
}
TEST_CASE("Constructeur avec capacite") {
    Chaine c1(6);
    CHECK( 6 == c1.getCapacite());
    CHECK( 0 == strlen(c1.c_str())); 

    // Est-ce que la libération mémoire est faite ?
}

Constructeur de copie

TEST_CASE("Constructeur de copie") {
    Chaine s1(10);   // une chaine vide
    Chaine s2 = s1;  // une autre chaine vide
    
    CHECK( s1.getCapacite() == s2.getCapacite());
    // tous les problemes vont venir de la
    // j'ai converti en void * uniquement pour l'affichage de catch
    CHECK( (void *)s1.c_str() != (void *)s2.c_str() );
    CHECK( 0 == strcmp(s1.c_str(), s2.c_str() ));
}
TEST_CASE("methode afficher") {
    const char * original = "une chaine a tester";
    const Chaine c1(original);
    std::stringstream ss;
    
    c1.afficher(); // on verifie juste que ca compile
    c1.afficher(std::cout); // ca compile, mais pas d'interet pour les tests
    c1.afficher(ss); // utilisable pour les tests

    CHECK( ss.str() == original ); //  test de std::string :-)
}

Le flux std::stringstream est un flux std::ostream particulier. On s'en sert pour les tests pour vérifier que les opérations de flux fonctionnent correctement. Cela évite la manipulation de fichiers.

Surcharge de l’opérateur d’affectation

TEST_CASE("operateur d'affectation") {
    Chaine s1("une premiere chaine");
    Chaine s2("une deuxieme chaine plus longue que la premiere");
    
    s1 = s2;

    CHECK( s1.getCapacite() == s2.getCapacite()); 
    CHECK( (void *)s1.c_str() != (void *)s2.c_str() );
    CHECK( 0 == strcmp(s1.c_str(), s2.c_str() ));

    s1 = s1; // est ce que cela va survivre a l execution ?
}

Surcharge d’opérateurs(<<, [] et +)

TEST_CASE("Surcharge <<") {
  const char * chaine = "une nouvelle surcharge";
  Chaine s(chaine);
  std::stringstream ss;
  // ss << s;  // :-)

  CHECK( ss.str() == chaine ); //  test de std::string, again :-))
}

Pour la suite, il manque les tests unitaires. Je vous laisse les écrire !!!

Quelle est la différence entre l'operator[] et la méthode at() pour la classe standard std::string

Nous ajouterons la gestion des erreurs au prochain TP.

Cet exercice est à faire une fois que vous avez jeté un coup d'oeil au TP suivant.

Un vecteur dynamique

Le but de cet exercice est de coder un conteneur , c'est-à-dire un objet dont la vocation est d'en contenir d'autres, et plus précisément un agrégateur. On va s'intéresser au plus simple des conteneurs : le vecteur dynamique (dont la capacité peut évoluer en cours d'exécution de programme) et on va mettre de nombres réels dedans.

Cette structure de données se comporte comme un tableau usuel et permet un accès direct aux éléments lorsque leur position est connue.

Elle encapsule un tableau d'éléments d'une classe donnée. Quand le tableau est plein, un nouveau tableau de taille plus grande est alloué et reprend le contenu du premier tableau

La capacité initiale du tableau peut être donnée à la construction (avec une valeur par défaut : 10 par exemple). Quand le tableau devient trop petit, sa capacité est multipliée par 2.

La fonction C memcpy() est une fonction super rapide pour copier un tableau dans un autre. Vous pouvez aussi utiliser std::copy()

La méthode capacity() permet de connaître la capacité du tableau, la méthode size() le nombre d'éléments réellement contenus

Une méthode push_back() permet d'ajouter un élément en fin de tableau et déclenche l'augmentation potentielle de la capacité du tableau

Proposer quelques opérateurs comme la redirection, l'indexation et la concaténation

Vous n'oublierez pas de respecter la forme normale de Coplien !

Ce conteneur sera réutilisé au cours des deux séances suivantes de TP.

Le code pour tester et la manière de faire sont décrits sur la fiche d'explication de la bibliothèque de tests :