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 :
- Utiliser le mot clé
const
sur toutes les méthodes qui ne changent pas l'état de l'objet courant. std::cout
est un objet de typestd::ostream
(défini dans l'entête<ostream>
inclus par<iostream>
)
Constructeur de base et destructeur
- Écrire une classe
Chaine
qui a deux attributs : un entiercapacite
et un tableau dynamique de caractèrestab
(capacite
est la "taille" du tableautab
). - Écrire un constructeur par défaut sans arguments qui initialise les attributs à -1 et
nullptr
pour le pointeur (comme le recommande la norme C++ 2011 et plus). - Écrire les méthodes
c_str()
etgetCapacite()
pour les tests unitaires
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());
}
- Écrire un constructeur qui prend en paramètre une chaine de type langage C (
const char *
)inCS
, qui alloue correctementtab
et qui copieinCS
danstab
. La fonctionstrcpy()
se trouve dans le fichier d'entête standard <cstring>.
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()));
}
- Proposer maintenant un constructeur qui accepte en entrée une capacite – inCapacite - et qui effectue l’initialisation des attributs (allocation dynamique à la bonne capacité pour tab).
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 ?
}
- Vérifier avec valgrind qu'il y a bien une fuite mémoire
- Ajouter à la classe
Chaine
un destructeur qui rend la mémoire allouée. - Vérifier avec valgrind qu'il n'y a plus de problème.
Constructeur de copie
- Considérer le test suivant. Quel sera le problème ? Vérifier avec Valgrind.
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() ));
}
- Corriger le problème en proposant un constructeur de copie et prévoir une ligne dans le constructeur de copie pour afficher sur la sortie de log ou d'erreur standard "Constructeur de copie appelé"
- Écrire une méthode
afficher(std::ostream &)
qui affiche la chaine sur flux donné en paramètre. Ce flux sera la sortie standard par défaut.
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.
- Écrire une fonction
afficherParValeur()
qui prend une instance deChaine
en paramètre et qui appelle la méthodeafficher()
de la chaine en paramètres. - Écrire une autre fonction
afficherParReference()
qui prend une référence sur uneChaine
et qui fait la même chose. - Tester l’appel de ces fonctions et vérifier que le constructeur de copie est bien appelé lors d'un passage de paramètre par valeur.
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 ?
}
- Créer 2 instances puis faire une affectation.
- Exécuter le programme, noter le message d'erreur puis vérifier avec valgrind.
- Corriger l'erreur en proposant un opérateur d’affectation, vérifier avec valgrind. L'opérateur prend en paramètre une référence sur un objet constant et renvoie une référence ("
*this
") pour le chainage d’affectations s1 = s2 = s3. - Bien vérifier que le cas
s = s
ne puisse provoquer d’erreur
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 :-))
}
- Proposer une surcharge de l'opérateur <<. Cet opérateur (une fonction) prend deux références en paramètres : un flux et une chaine non modifiée et renvoie le flux pour rendre le chainage possible.
Pour la suite, il manque les tests unitaires. Je vous laisse les écrire !!!
- Ajouter un opérateur [] (une méthode
operator[](int)
)permettant la modification d’un élément de la chaîne. Tester l’implémentation sur une de vos instances en modifiant la valeur d’un caractère. - Essayer de modifier l’implémentation de l’opérateur << (une fonction
operator<<()
) pour qu’il affiche chaque caractère de la chaîne ligne à ligne à l’aide de l’opérateur précédemment défini. Ex : Pour la chaîne "Toto", on aura l’affichage suivant :
T
o
t
o
Noter le message d’erreur à la compilation. - Pour pallier cette erreur, ajouter un nouvel opérateur [] constant (méthode constante) pour accéder à un caractère de la chaine en lecture seule.
- Ajouter un opérateur de concaténation de chaînes en utilisant le symbole + de manière naturelle.
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 :
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10