1. Méthodes virtuelles vs switch
2. Classe abstraite et concrète
3. Polymorphisme
4. Destructeur virtuel
5. Fonctionnement interne du virtuel...
6. Classe template
7. Paramètre de template qui n'est pas un type
8. Typedef et enum dans des classes
9. STL: Standard Template Library
10. Itérateurs
11. Algorithmes
12. Classe vector
13. Classe list
14. Classe deque
15. Classes set & multiset
16. Classes map & multimap
0. Retour sur la séance pratique.
1. Méthodes virtuelles vs switch
- Les méthodes virtuelles permettent d'écrire des algorithmes
généraux sans avoir à tout remodifier si jamais une
nouvelle classe était ajoutée et devait être traitée
par cet algorithme.
- Pour les éléments finis, il existe un algorithme spécifique
à chaque type d'élément qui lui permet de calculer
son barycentre. Alors, lorsqu'on demande à un élément
de calculer son barycentre, on voudrait que le programme choisisse dynamiquement
(lors de l'exécution) le bon code à exécuter.
- Si nous déclarons la méthode calculBarycentre
virtuelle dans la classe de base (voir ex.) et que nous utilisons un pointeur
ou une référence à un objet d'une classe dérivée
et que nous appelons la méthode sur le pointeur (ex. : lElementPtr->calculBarycentre()),
alors le programme va choisir le bon code à exécuter (celui
défini dans la classe dérivée).
Exemple :
class Element
{
public:
...
virtual Vecteur3D calculBarycentre() const;
...
};
class Element1D : public Element
{
public:
...
virtual Vecteur3D calculBarycentre() const;
...
private:
Vecteur3D aNoeud0, aNoeud1;
};
- Si on veut faire un switch sur le type de classe, on devrait modifier BEAUCOUP de code en cas d'ajout d'une nouvelle classe.
2. Classe abstraite et concrète
- Une classe est normalement pensée pour instancier des objets.
- Il existe des types de classes qui ne voudraient rien dire, mais
représenteraient quand même un certain concept (par exemple,
notre classe Elément ne représente pas un type d'élément
concret, mais bien les notions qui sont communes à tous les types
d'éléments).
- On ne peut instancier des objets de ce type, seulement des pointeurs
et références.
- Une classe devient abstraite, dès que l'on ajoute un =0 à
la fin du prototype d'une méthode (cette méthode est alors
qualifiée "méthode virtuelle pure").
- Une méthode virtuelle pure doit obligatoirement être
redéfinie dans les classes dérivées.
Exemple :
class Element {
public:
...
virtual Vecteur3D calculBarycentre() const = 0;
...
};
- Possibilité pour des objets de différentes classes reliés
à une même classe de base de répondre différemment
à un message.
- Le polymorphisme permet l'extensibilité du code existant.
En effet, on peut ajouter facilement une nouvelle classe héritière
à une classe de base et on s'assure ensuite de répondre correctement
à chacune des méthodes virtuelles qui sont définies
dans le parent.
- On peut avoir un vecteur de pointeur vers des éléments
et leur demander calculeBarycentre(), ou toute méthode
(virtuelle) définie sur la classe de base.
- Nécessaire avec l'allocation dynamique.
- Un fait à remarquer : normalement, une méthode virtuelle
doit porter le même nom dans le parent et les enfants pour que l'appel
de la méthode sur le parent "descende" jusqu'aux enfants.
Le destructeur est une exception, puisqu'il change de nom selon la classe.
Les destructeurs ne portent donc pas le même nom, mais sont une même
méthode virtuelle quand même.
- On comprend toute l'importance du destructeur virtuel lorsqu'on appelle
l'opérateur delete sur une classe de base.
5. Fonctionnement interne du virtuel...
- Chaque classe a une vtable qui est un vecteur qui contient
les adresses de toutes les méthodes virtuelles.
- Chaque objet a son pointeur à la bonne vtable de
sa classe.
- À l'exécution, on a la fonction qui détermine
le offset (l'indice du vecteur) dans la vtable, pour la méthode
appelée.
- On a utilisé les templates avec la fonction min et norme, afin
de réutiliser le même algorithme, peu importe le type passé.
- Il existe aussi des classes templates (patrons de classe en bon français).
- Une liste chaînée et un vecteur sont des exemples où,
peu importe le type, le comportement du conteneur demeure inchangé.
- On ajoute les mots clefs template <class PTType> devant
la classe.
Exemple :
template <class PTType>
class VectDyn {
public:
VectDyn();
VectDyn(int pDim);
...
PTType& operator[](int pIndice);
...
private:
PTType * aVect;
int aDim;
};
int main() {
VectDyn<double> lVectDouble;
VectDyn<Heure> lVectHeure;
VectDyn<Element*> lVectElement;
}
- La définition des méthodes doit comporter quelques nouveaux
éléments:
Exemple :
template <class PTType>
VectDyn<PTTYpe>::VectDyn()
: aVect(0), aDim(0)
{
}
template <class PTType>
VectDyn<PTTYpe>::VectDyn(int pDim)
{
assert(pDim > 0 && "Dimension inférieure
ou égale à 0");
aDim = pDim;
aVecteur = new PTType[aDim];
}
7. Paramètre de template qui n'est pas un type
- On peut passer autre chose que des types pour des templates.
- On peut aussi passer des valeurs comme des entiers.
Exemple :
template <class PTType, int PTDim>
class VectFixe {
public:
VectFixe();
...
private:
PTType aVect[PTDim];
};
int main() {
VectFixe<double, 4> lVectDouble;
VectFixe<Heure, 10> lVectHeure;
VectFixe<Element*, 20> lVectElement;
}
8. Typedef et enum dans des classes
- Lorsque l'on fait un typedef dans une fonction ou dans le <<scope>> global, on peut utiliser directement le nom de type que l'on vient de définir. C'est la même chose pour les énumérations.
Exemple :
typedef int Entier;
enum Couleur {ROUGE, BLANC};
int main()
{
Entier lNo = 10;
Couleur lCoul = ROUGE;
}
- Dans une classe, on peut aussi faire des typedef et déclarer des énumérations. Toutefois, pour utiliser ces dernières, il faudra d'abord faire précéder leur nom du <<scope>> de la classe.
Exemple :
class Maillage
{
public:
typedef Arete TypeArete;
typedef Sommet TypeSommet;
typedef Sommet* IterateurSommet;
...
};
class ParcoursElementProcheEnProche
{
public:
enum TypeVoisin {AvecAutreDimAvecMultiple =
111,
SansAutreDimAvecMultiple,
SansAutreDimSansMultiple};
...
};
int main()
{
Maillage::TypeArete lArete;
Maillage::IterateurSommet lIterSommet = lArete.reqSommet(0);
lIterSommet->reqCoor();
...
ParcoursElementProcheEnProche::TypeVoisin lTypeVoisin;
lTypeVoisin = ParcoursElementProcheEnProche::SansAutreDimAvecMultiple;
...
}
9. STL: Standard Template Library
- STL est divisé en 3 parties : conteneurs, itérateurs
et algorithmes.
- Toutes les classes et fonctions de STL sont définies dans
le namespace std.
- On peut voir un conteneur comme un livre que plusieurs peuvent lire en même temps et dans lequel on a placé plusieurs signets (les itérateurs).
Séquentiels:
- vector
- deque
- list
Associatifs:
- set
- multiset
- map
- multimap
Adaptateur de conteneur:
- stack
- queue
- priority_queue
Méthodes:
- Constructeur, CC & destructeur
- bool empty() // true si le conteneur
est vide
- size()
// retourne la dimension
- operator = // Opérateur
d'assignation
- swap
// Interchange 2 conteneurs du même type
- begin(), end() // Retourne le début/fin
du conteneur
- rbegin(), rend() // Retourne la fin/début du conteneur,
permet de parcourir le conteneur à l'envers
- erase
// Enlève une ou des entrées du conteneur
- void clear() // Vide le conteneur
Typedef dans les conteneurs:
- value_type
- reference & const_reference
- pointer & const_pointer
- iterator & const_iterator
- reverse_iterator & const_reverse_iterator
- difference_type // (résultat
de la soustraction de 2 itérateurs)
- size_type
//(le type utilisé pour compter les entrées)
10. Itérateurs
- On a vu comment parcourir un vecteur avec des pointeurs.
- Les itérateurs servent à parcourir (se déplacer
dans) les conteneurs.
- Les conteneurs STL offrent les méthodes begin() et end() qui
retournent des itérateurs vers le début et la fin du conteneur.
Exemple :
int main() {
vector<double> lVect(10);
vector<double>::iterator lIterVect, lIterFin;
lIterVect = lVect.begin();
lIterFin = lVect.end();
while (lIterVect != lIterFin) {
cout << *lIterVect
<< endl;
++lIterVect;
}
}
- Les algorithmes utilisent les itérateurs et non les conteneurs.
- Ils sont définis à l'extérieur des conteneurs,
favorisant la réutilisation du même algorithme, peu importe
le conteneur.
- Il y en a près de 70 fournis par STL...
12. Classe vector
- Le vector ressemble au vecteur C (les array).
- méthodes:
size() //
Retourne la dimension du vecteur
capacity() // Retourne la capacité
maximale actuelle du vecteur
reserve() // Réserve
de la mémoire: augmente la capacité, sans changer la dimension
resize() // Augmente
la capacité et la dimension
push_back() // Ajoute une entrée
à la fin du vecteur, augmentant ainsi sa dimension et la capacité,
si nécessaire
pop_back(); // Enlève le dernier
élément, diminuant la dimension
operator[]; // Accède à
l'entrée de l'indice passé
13. Classe list
- Représente un liste chaînée. Très pratique
pour ajouter et effacer partout
- méthodes:
- push_front() // Ajoute une entrée au
début de la liste
- pop_front() // Enlève une entrée
au début de la liste
- sort()
// trie la liste
- splice() // Enlève
les entrées de la 2e liste et les déplace dans la liste.
- merge() // Avec
les 2 listes triées, prend toutes les entrées de la 2e liste
et les insère au bon endroit dans la liste. Le résultat est
une liste triée et la 2e liste vide.
- unique() // Dans une
liste triée, enlève toutes les entrées doubles.
- swap()
// Interchange le contenu de 2 listes.
- reverse() // Inverse l'ordre
des entrées du conteneur.
- remove() // Enlève
les entrées de valeur X.
14. Classe deque
- S'utilise comme un vecteur avec des crochets pour accéder aux
composantes ([])
- Optimisé pour l'ajout/l'effacement à la fin et au début
du conteneur.
- Le set est un conteneur qui contient des entrées triées
et uniques.
- Le multiset est comme le set, mais il peut contenir plusieurs entrées
égales.
- Le map est un conteneur associatif qui associe une clef à une
définition (ou une clef à une valeur).
- Les entrées sont triées et uniques.
- Le multimap est comme un map, sauf qu'il peut contenir plusieurs
clefs égales.