IN204 Programmation orientée objet – Les

Transcription

IN204 Programmation orientée objet – Les
IN204
Programmation orientée objet – Les composants
Séance de Travaux Dirigés du 5 février 2008
B. Monsuez
Partie 1 --- Les containeurs
La notion de containeur
Les conteneurs sont des objets qui en contiennent d'autres: ce terme général inclut un grand
nombre de structures de données: les tableaux, les listes, les queues, ... mais aussi les tableaux
associatifs. Les conteneurs sont implémentés par des modèles: il est nécessaire de spécifier
"ce que" le conteneur contient lors de la déclaration de la variable.
Les conteneurs offrent de nombreux avantages par rapport à des structures de bases comme
les tableaux, ils supportent notamment :
•
•
•
•
L’allocation dynamique et automatique de la mémoire, c’est le conteneur qui alloue la
mémoire et initialise les éléments stockés dans le conteneur.
L’insertion ou la suppression d’éléments, pour certains types de conteneurs
L’utilisation d'algorithmes spécifiques à un type de conteneur
L’utilisation d'algorithmes génériques pour des containeurs
Les conteneurs de la bibliothèque standard
La bibliothèque standard propose de nombreux conteneurs qui correspondent aux structures
de données les plus souvent manipulées:
Conteneurs séquentiels généralistes:
vector
Tableau à une dimension
list
Liste doublement chaînée
deque
Ressemble à un vecteur, sauf que l'insertion et la suppression en tête de liste sont plus
performantes.
queue
File d'attente (premier entré, premier sorti). Une queue est un adaptateur de
conteneur, qui repose (par dé faut) sur un conteneur de type deque.
stack
Pile (dernier entré, premier sorti). C'est un adaptateur de conteneur.
Conteneurs ordonnés généralistes:
map, multimap
Les tableaux associatifs, dans le cas de multimap plusieurs éléments peuvent avoir la
même clé
set, multiset
Les ensembles (set), et multi-ensembles (multiset). Il est possible d’avoir plusieurs
fois le même élément dans un multi-ensemble.
priority_queue
Les files d'attente. On accède uniquement à l'objet situé en haut de la queue. De plus,
le conteneur garantit que l'objet situé en haut est le "plus grand".
Quelques fonctions standards définies pour les conteneurs
Types définis sur les conteneurs
Les conteneurs définissent des types en tant que membres publics dont les plus importants
sont écrits ci-dessous. On supposera dans ce qui suit qu'on travaille avec l'un de ces deux
objets:
•
•
seq<objet> (un conteneur séquentiel)
ord<cle,valeur> ou ord<cle> (un conteneur ordonné paramétré par un
type de clé
et éventuellement de valeur).
Même si cela peut paraitre lourd à première vue, il est important d'utiliser ces types, pour
générer du code portable: peut-être que sur votre système le type size_type est équivalent à
unsigned int. Mais sur un autre système, size_type risque d'être en fait équivalent à
unsigned long. D'où de gros soucis de portabilité.
value_type
Type d'élément: seq<objet>::value_type
reference
value_type& (ici objet &)
const_reference
const value_type & (ici const objet &
size_type
n'est autre que objet
)
Numéros d'indices, nombre d'éléments, etc.
iterator
value_type*,
const_iterator
(ici objet *) permet de balayer le conteneur
Méme chose, mais garantit que les objets récupérés ne seront pas modifiés.
reverse_iterator
Pour balayer le conteneur à l'envers
const_reverse_iterator
no comment
difference_type
Différence entre deux itérateurs
Cas des conteneurs associatifs
Les conteneurs associatifs définissent également les types suivants:
Key
T
ou key_type
Clé d'accès aux éléments. (Ici, cle)
ou data_type
Type d'éléments stockés. (Ici, valeur)
pair<key,T>
Les objets stockés dans un map (donc value_type). On accède à la clé par le champ
first, à la valeur par le champ second. A noter que les paires sont rangées par ordre
croissant du champ key. Il est possible de définir ce qu'est cet ordre (cf. la
documentation de l'objet map).
Accès aux éléments
reference top() const, const_reference top() const
(stack)
Renvoie l'élément situé en haut de la pile. Temps d'exécution constant. La pile ne doit
pas être vide.
reference front() const, const_reference front() const (vector,list,deque)
Renvoie le premier élément
reference back() const,const_reference back() const (vector,list,deque)
Renvoie le dernier élément
reference operator[](size_type n), const_reference operator[](size_type n)
(vector,string,deque )
data_type & operator[](const key_type& k)
(map)
iterator find(const key_type & k), const_iterator find(const key_type & k)
(map,set)
Opérations d'insertion et de suppression d'éléments.
void push(const value_type & x)(stack,queue)
Insère x en haut de la pile ou à la fin de la queue.
void pop()(stack,queue)
Temps d'exécution constant.
Supprime l'élément situé en haut de la pile ou en tête de la queue. Temps d'exécution
constant. La pile ou la queue ne doit pas être vide.
void push_front(const value_type & x) (deque,list)
Insère x au début.
void pop_front() (deque,list)
Supprime x du début. Ces fonctions ont un comportement indéfini si le conteneur est
vide: utiliser la fonction empty() auparavant.
void push_back(const value_type & x) (string, deque,list,vector)
Insère x à la fin.
void pop_back() (string, deque,list,vector)
Supprime x à la fin. Ces fonctions ont un comportement indéfini si le conteneur est
vide: utiliser la fonction empty() auparavant.
pair<iterator, bool> iterator insert(const value_type & x) (set)
iterator insert(iterator p,const value_type & x) (set)
Insère x dans l’ensemble, l’itérateur p peut suggérer l’endroit où il est judicieux
d’essayer d’insérer l’élément
iterator insert(iterator p,const value_type & x) (vector,list,deque)
Insère x avant p et retourne un itérateur pointant sur x.
void insert(iterator p, In first, In last) (vector,list,deque)
Insère les éléments de l'intervalle semi-fermé [first,last[
immédiatement avant p.
iterator erase(iterator p) (vector,deque,list)
void erase(iterator p) (map,set)
Supprime l'élément sur lequel pointe p. La performance dépend du type de
conteneur.
Pour un conteneur séquentiel, renvoie un itéœrateur pointant sur le successeur
immédiat de l'élément détruit.
iterator erase(iterator first, iterator last) (vector,deque,list)
void erase(iterator first, iterator last) (map,set)
Supprime tous les éléments de l'intervalle semi-ouvert [first,last[. La performance
dépend du type de conteneur. Pour un conteneur séquentiel, renvoie un itérateur
pointant sur le successeur immédiat du dernier élément détruit, éventuellement end().
void remove(const value_type & valeur) (list)
Supprime de la liste tous les éléments égaux à valeur
void unique() (list)
Supprime de la liste tous les éléments conécutifs égaux de la liste, sauf un.
void clear() (tous sauf stack)
Efface tous les éléments.
Opérations affectant l'ordre des éléments (uniquement list)
void reverse()
Retourne l'ordre des éléments dans la liste.
void sort()
Trie les éléments de la liste en utilisant operator<
Opérations diverses
(tous)
renvoie le nombre d'éléments dans le conteneur.
bool empty() const (tous)
renvoie true si le conteneur est vide.
size_type size() const
Question 1
Ecrire une fonction qui crée un tableau d’entiers contenant les 101 premiers entiers allant de 0
à 100, vous utiliserez pour créer ce tableau une classe conteneur de la STL.
Question 2
Ecrire une fonction qui affiche le contenu d’un tableau d’entiers comme définie à la question
précédente. Tester cette fonction avec le tableau d’entiers que vous avez créé avec la fonction
définie à la question 1.
Question 3
Nous souhaitons écrire une fonction qui prend un tableau d’entiers en paramètre et lui ajoute
les 100 entiers négatifs allant de -100 à -1 au début de ce tableau (c'est-à-dire avant le premier
élément du tableau).
Quel conteneur est-il souhaitable d’utiliser ? Justifier votre choix ?
Eventuellement modifier la fonction de la question 1 afin d’utiliser le même containeur que
celui que vous souhaitez utiliser dans la question 2.
Question 4
Ecrire une fonction qui crée un ensemble d’entiers contenant les 101 premiers entiers allant de
0 à 100, vous utiliserez pour créer cet ensemble une classe conteneur de la STL.
Les itérateurs
Un itérateur est un objet associé à un conteneur, qui va permettre de référencer un élément qui
se trouve dans le conteneur, et ceci sans avoir aucune idée de la structure de données sousjacente. Un itérateur est une forme de pointeur qui permet de dire que tel ou tel élément
En plus un itérateur peut-être incrémenté afin de référencer l’élément stocké dans le conteneur
et qui se trouve après l’élément référencé par l’itérateur. Un itérateur peut aussi-être
décrémenté afin de référencer un élément stocké dans le conteneur qui se trouver avant
l’élément référencé par l’itérateur.
Par exemple, le code suivant affiche tous les éléments contenus dans un
ensemble. Ce code est le même que les éléments soient stockés dans un
tableau ou dans un ensemble ou dans un multi-ensemble.
std::set<int> V;
V.insert(1);
V.insert(2);
V.insert(3);
...
V.insert(10);
for (set <int>::iterator it=V.begin();it != V.end();++i)
{
cout << *i << endl;
};
Itérateurs valides et invalides
Un itérateur qui référence un élément dans un conteneur est dit valide. Dans ce cas,
*iterateur renvoie un élément du conteneur. Un itérateur peut être valide à un certain
moment de l'exécution du programme, puis être invalide un peu plus tard. Un itérateur peut
être invalide pour les raisons suivantes:
•
Il n'a pas été initialisé
•
•
•
Le conteneur a été redimensionné (par des insertions ou des suppressions, par
exemple).
Le conteneur a été détruit
L'itérateur pointe sur la fin de la séquence (V.end() dans l'exemple ci-dessus), c’est à
dire sur l’élément qui aurait dû se situer après le dernier élément de la séquence.
Les différentes catégories d'itérateurs
Les itérateurs peuvent être classés en plusieurs catégories; suivant les catégories auxquelles ils
appartiennent, nous avons plus ou moins d'opérations à notre disposition;
Accès en lecture seulement (InputIterator)
On ne peut effectuer que 4 opérations:
1.
2.
3.
4.
égalité j = i
Incrémentation ++i ou i++
Déréférencement, en lecture seule A = *i
Test d'égalité i == j
Il est impossible de faire *i = A avec cet itérateur.
Il est impossible de déréférencer plus d'une fois le même élément. Ainsi, le code A = *i; B
= *i ne marche pas.
On doit penser à cet itérateur comme à un objet permettant de lire un fichier.
Accès en écriture seulement (OutputIterator)
On ne peut effectuer que 3 opérations:
1. égalité j = i
2. Incrémentation ++i ou i++
3. Déréférencement, en écriture seule *i = A
Il est impossible de faire A = *i avec cet itérateur.
Il est impossible de déréférencer plus d'une fois le même élément. Ainsi, le code *i = A; *i
= B ne marche pas.
On doit penser à cet itérateur comme à un objet permettant d'écrire un fichier.
Accès en lecture et écriture, balayage en avant seulement (ForwardIterator)
Permet de balayer une séquence du début à la fin, mais sans retour en arrière possible. Les
opérations supportées par les InputIterator et OutputIterator sont également disponibles avec
cet itérateur. On peut également déréférencer l'itérateur autant de fois qu'on veut.
Partout où un InputIterator ou OutputIterator est requis, vous pourrez fournir un
itérateur ForwardIterator.
Itérateurs Bidirectionnels (BidiIterator)
Tout ce qui est permis par un ForwardIterator, avec en plus la possibilité de revenir en
arrière.
1. Décrémentation --i ou i-Partout où un InputIterator, OutputIterator ou ForwardIterator est requis, vous
pourrez fournir un itérateur ForwardIterator.
Intérateurs à accès aléatoire (RandomIterator)
Tout ce qui est permis par un BidiIterator, avec en plus:
1. Opérateur d'indexation i[3]
2. Ajout ou suppression d'entiers j=i+3; j=i-4; i +=2;
3. Opérateur -: la différence entre deux itérateurs est un entier
Partout où un InputIterator, OutputIterator, ForwardIterator ou BidiIterator est
requis, vous pourrez fournir un itérateur ForwardIterator.
Un itérateur random offre les mêmes possibilités qu'un pointeur conventionnel.
Les itérateurs const_iterator
Un itérateur « constant » est un itérateur ne permettant pas de modifier les éléments contenus
dans le conteneur.
Les itérateurs reverse
Ils permettent de balayer la séquence en sens inverse.
Question 1
Nous souhaitons rechercher le plus grand élément stocké dans un conteneur. Nous souhaitons
que notre code fonctionne à la fois pour des tableaux mais aussi pour des ensembles.
Tout d’abord, pour des raisons de simplicité, nous considérons que les éléments stockés dans
les conteneurs supportent la comparaison avec l’opérateur « < ».
Nous considérons la fonction :
template<class container>
template<class container>
typename container::value_type
lePlusGrand(const container& unConteneur)
{
…
}
Cette fonction devra retourner la valeur du plus grand élément stocké dans le conteneur
unConteneur.
Tester cette fonction avec votre tableau d’entiers et que votre ensemble d’entiers.
définis à la première question
Question 2
Dans la STL, il existe une fonction « max_element » dans algorithm qui fait exactement la
même chose. Cependant cette fonction ne prend par les mêmes arguments. Elle prend deux
arguments :
-. L’itérateur pointant sur le premier élément contenant dans le conteneur
-. L’itérateur pointant sur l’élément situé après le dernier élément du conteneur. Il s’agit donc
d’un conteneur qui n’est pas valide.
Cette fonction retourne non pas la plus grande valeur mais un itérateur référençant la plus
grande valeur.
Il s’agit en fait d’un principe général de la STL, la STL propose un ensemble d’algorithmes
qui fonctionnent sur les conteneurs mais ces algorithmes ne prennent pas des conteneurs en
argument mais uniquement les itérateurs référençant le premier élément du conteneur et
l’élément situé après le dernier élément du conteneur.
Réécrivez la fonction précédente en utilisant la fonction « max ».
Question 3
Nous considérons toujours notre tableau d’entiers. Et nous considérons désormais le code
suivant qui enlève le premier élément d’un tableau ou d’une queue.
template<class container>
void
enlevePremierElement(container& unConteneur)
{
container::iterator itElement = unConteneur.begin();
unConteneur.erase(itElement);
std::cout << "Element effacé " << *itElement << "\n";
}
Nous supposons essayer ce code avec la fonction suivante :
void
testEffacement()
{
std::vector<int> vecteur;
enlevePremierElement(vecteur);
std::set<int> ensemble;
creeEnsemble(ensemble);
enlevePremierElement(vecteur);
}
Expliquer ce qui se passe et pourquoi le code de la fonction enlevePremierElement est erroné.

Documents pareils

Le Traitement des données avec la STL (pdf - David Saint

Le Traitement des données avec la STL (pdf - David Saint pointeurs ‘Précédent ‘ ‘Suivant’. Pour un nombre d’objets identiques une liste prend souvent plus de place en mémoire. Tous les conteneurs disposent de 3 fonctions : • begin() renvoie l’itérateur s...

Plus en détail