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
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...