0% ont trouvé ce document utile (0 vote)
58 vues30 pages

Algorithm C++ STL

note on C++ STL algorithms

Transféré par

dominique.t5
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
58 vues30 pages

Algorithm C++ STL

note on C++ STL algorithms

Transféré par

dominique.t5
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd

Algorithm STL:

Vous trouverez la liste complète dans votre documentation favorite.

Nom de la fonction Description


isalpha() Vérifie si le caractère est une lettre.
isdigit() Vérifie si le caractère est un chiffre.
islower() Vérifie si le caractère est une minuscule.
isupper() Vérifie si le caractère est une majuscule.
isspace() Vérifie si le caractère est un espace ou un retour à la ligne.
En plus de cela, il y a deux fonctions tolower()et toupper()qui convertissent une
majuscule en minuscule et inversement.

L'en-tête ctime
Comme son nom l'indique, ce fichier d'en-tête contient plusieurs fonctions liées à la
gestion du temps. La plupart sont assez bizarres à utiliser et donc peu utilisées. De
toute façon, la plupart des autres bibliothèques, comme Qt, proposent des classes
pour gérer les heures, les jours et les dates de manière plus aisée.

Personnellement, la seule fonction de ctimeque j'utilise est la fonction time(). Elle


renvoie le nombre de secondes qui se sont écoulées depuis le 1er janvier 1970.
C'est ce qu'on appelle l'heure UNIX.

#include <iostream>

#include <ctime>

using namespace std;

int main()

int secondes = time(0);

cout << "Il s'est ecoule " << secondes << " secondes depuis le 01/01/1970." <<
endl;

return 0;

}
Utilisez les conteneurs
Les deux catégories de conteneurs

Les vectorsont bien évidemment des séquences puisque, comme je vous l'ai dit, toutes
les cases sont rangées de manière contiguë dans la mémoire.

une liste de tous les conteneurs de la STL triés suivant leur catégorie.

 Séquences :
o vector
o deque
o list
o stack
o queue
o priority_queue
 Conteneurs associatifs :
o set
o multiset
o map
o multimap
Pour utiliser ces conteneurs, il faut inclure le fichier d'en-tête correspondant.

Quelques méthodes communes

if(a.empty())

a.clear(); //Et on vide le tout !

a.swap(b); //On échange le contenu des deux tableaux.

Les séquences et leurs adaptateurs


Les vector, encore et toujours

Les vector ne sont vraiment pas adaptés pour effectuer des opérations mathématiques.

il existe la méthode assign()permettant de remplir tous les éléments d'une


séquence avec la même valeur.
Récapitulons tout cela dans un tableau.

Méthode Description
push_back() Ajout d'un élément à la fin du tableau.
Méthode Description
pop_back() Suppression de la dernière case du tableau.
front() Accès à la première case du tableau.
back() Accès à la dernière case du tableau.
assign() Modification du contenu d'un tableau.

Les deque, ces drôles de tableaux


Deque est en fait un acronyme (bizarre) pour double ended queue, ce qui donne en français, «
queue à deux bouts

l'indice du premier élément est toujours 0.

Les stack, une histoire de pile

Cela se fait via les trois méthodes push(),top()etpop().

En termes techniques, on parle de structure LIFO (Last In First Out). Le dernier élément ajouté
est le premier à pouvoir être ôté
#include <stack>
#include <iostream>
using namespace std;
int main()
{
stack<int> pile; //Une pile vide
pile.push(3); //On ajoute le nombre 3 à la pile
pile.push(4);
pile.push(5);
cout << pile.top() << endl; //On consulte le sommet de la pile (le nombre 5)
pile.pop(); //On supprime le dernier élément ajouté (le nombre 5)
cout << pile.top() << endl; //On consulte le sommet de la pile (le nombre 4)
return 0;
}

Les queue, une histoire de file


on parle de structure FIFO (First In First Out). La différence ici est que l'on ne peut accéder
qu'au premier élément ajouté, exactement comme dans une file de supermarché.
Les priority_queue, la fin de l'égalité

Les priority_queuesont des queuequi ordonnent leurs éléments.

#include <queue> //Attention ! queue et priority_queue sont définies dans le même


fichier
#include <iostream>
using namespace std;
int main()
{
priority_queue<int> file;
file.push(5);
file.push(8);
file.push(3);
cout << file.top() << endl; //Affiche le plus grand des éléments insérés (le nombre
8)
return 0;
}
Les tables associatives
on dit qu'une mapest une table associative permettant de stocker des paires clé-valeur.

#include <map>
#include <string>
using namespace std;
map<string, int> a;
Ce code déclare une table associative qui stocke des entiers mais dont les indices
sont des chaînes de caractères. On peut alors accéder à un élément via les
crochets[]comme ceci :

a["salut"] = 3; //La case "salut" de la map vaut maintenant 3

Les map ont un autre gros point fort : les éléments sont triés selon leur clé. Donc si l'on
parcourt une map du début à la fin, on parcourt les clés de la plus petite à la plus grande.

Le problème c'est que, pour parcourir une table associative du début à la fin, il faut utiliser
les itérateurs

Les autres tables associatives

Les setsont utilisés pour représenter les ensembles. On peut insérer des objets dans
l'ensemble et y accéder via une méthode de recherche. Par contre, il n'est pas possible d'y
accéder via les crochets.

Les multisetet multimapsont des copies des setet mapoù chaque clé peut exister en
plusieurs exemplaires.

Utlisez les itérateurs et les foncteurs


Itérateurs
on a :
#include <vector>
using namespace std;

vector<int> tableau(5,4); //Un tableau de 5 entiers valant 4


vector<int>::iterator it; //Un itérateur sur un vector d'entiers

map<string, int>::iterator it1; //Un itérateur sur les tables associatives string-
int

deque<char>::iterator it2; //Un itérateur sur une deque de caractères

list<double>::iterator it3; //Un itérateur sur une liste de nombres à virgule


Voyons cela avec un exemple :
#include<deque>
#include <iostream>
using namespace std;

int main()
{
deque<int> d(5,6); //Une deque de 5 éléments valant 6
deque<int>::iterator it; //Un itérateur sur une deque d'entiers

//Et on itère sur la deque


for(it = d.begin(); it!=d.end(); ++it)
{
cout << *it << endl; //On accède à l'élément pointé via l'étoile
}
return 0;
}

Les itérateurs ne sont pas optimisés pour l'opérateur de comparaison. On ne devrait donc
pas écrireit<d.end()comme on en a l'habitude avec les index de tableau. Utiliser!=est
plus efficace.

Des méthodes uniquement pour les itérateurs


 tab.insert(tab.begin(), "Salut"); //On insère le mot "Salut" au début
 tab.erase(tab.begin()); //On supprime le premier mot

Les différents itérateurs


Il existe en réalité cinq sortes d'itérateurs.

Parmi les cinq types d'itérateurs, seuls deux sont utilisés pour les conteneurs :
les bidirectional iterators et les random access iterators.

Les bidirectional iterators


Ce sont des itérateurs qui permettent d'avancer et de reculer sur le conteneur.

L'important étant que l'on ne peut avancer que d'un seul élément à la fois :
 Donc pour accéder au sixième élément d'un conteneur, il faut partir de la position
begin()puis appeler cinq fois l'opérateur++.
 Cela veut dire que vous pouvez utiliser aussi bien++que—

Ce sont les itérateurs utilisés pour leslist,setetmap.

Les random access iterators


ces itérateurs permettent d'accéder au hasard, ce qui dans un meilleur français veut dire que l'on
peut accéder directement au milieu d'un conteneur:
 Techniquement, ces itérateurs proposent en plus de++et--des opérateurs+et-
permettant d'avancer de plusieurs éléments d'un coup.

 Par exemple pour accéder au huitième élément d'unvector, on peut utiliser la


syntaxe suivante

vector<int> tab(100,2); //Un tableau de 100 entiers valant 2

vector<int>::iterator it = tab.begin() + 7; //Un itérateur sur le 8ème


élément

En plus desvector, ces itérateurs sont ceux utilisés par lesdeque

La pleine puissance des list et map


List: C'est un conteneur assez différent de ce que vous connaissez

 Les éléments ne sont pas rangés les uns à côté des autres dans la mémoire.
 Chaque « case » contient un élément et un pointeur sur la case suivante, située ailleurs
dans la mémoire

L'avantage de cette structure de données:

 on peut facilement ajouter des éléments au milieu: Il n'est pas nécessaire de décaler
toute la suite comme dans l'exemple de la bibliothèque du chapitre précédent.

 Mais (il y a toujours un mais) on ne peut pas directement accéder à une case
donnée… tout simplement parce qu'on ne sait pas où elle se trouve dans la mémoire

o On est obligé de suivre toute la chaîne des éléments  très coûteux.


 Passer de case en case, dans l'ordre, est une mission parfaite pour les itérateurs. Et
puis, il n'y a pas d'opérateur[]pour les listes.

La même chose pour lesmap

 La structure interne des map: utilisent ce qu'on appelle des arbres binaires
 Grâce aux itérateurs, ce n'est pas à vous de vous préoccuper de tout cela. Vous utilisez
simplement les opérateurs++et—

 Chaque élément est en réalité constitué d'une clé et d'une valeur.


o Un itérateur ne peut pointer que sur une seule chose à la fois

o Les itérateurs pointent en réalité sur despair. Ce sont des objets avec deux
attributs publics appelésfirstetsecond. Lespairsont déclarées dans le fichier
d'en-têteutility

 Dans unemap, les objets stockés sont en réalité despair.

ous allons maintenant pouvoir le vérifier facilement.


#include <iostream>
#include <string>
#include <map>
using namespace std;

int main()
{
map<string, double> poids; //Une table qui associe le nom d'un animal à son
poids

//On ajoute les poids de quelques animaux


poids["souris"] = 0.05;
poids["tigre"] = 200;
poids["chat"] = 3;
poids["elephant"] = 10000;

//Et on parcourt la table en affichant le nom et le poids


for(map<string, double>::iterator it=poids.begin(); it!=poids.end(); ++it)
{
cout << it->first << " pese " << it->second << " kg." << endl;
}
return 0;
}

L'opérateur[]permet d'accéder à un élément donné mais il a un « défaut ». Si l'élément n'existe


pas, l'opérateur[]le crée:
 lesmapproposent une méthodefind()qui renvoie un itérateur sur l'élément recherché.

o Si l'élément n'existe pas, elle renvoie simplementend()

int main()
{
map<string, double> poids; //Une table qui associe le nom d'un animal à son
poids

//On ajoute les poids de quelques animaux


poids["souris"] = 0.05;
poids["tigre"] = 200;
poids["chat"] = 3;
poids["elephant"] = 10000;

map<string, double>::iterator trouve = poids.find("chien");

if(trouve == poids.end())
{
cout << "Le poids du chien n'est pas dans la table" << endl;
}
else
{
cout << "Le chien pese " << trouve->second << " kg." << endl;
}
return 0;
}

Foncteur : la version objet des fonctions


Les foncteurs sont des objets possédant une surcharge de l'opérateur(). Ils peuvent ainsi agir
comme une fonction mais être passés en argument à une méthode ou à une autre fonction.

Créez un foncteur
foncteur est une classe possédant si nécessaire des attributs et des méthodes. Mais, en plus de
cela, elle doit proposer un opérateur()

class Addition{
public:

int operator()(int a, int b) //La surcharge de l'opérateur ()


{
return a+b;
}
};

ce foncteur pour additionner deux nombres :


#include <iostream>
using namespace std;

int main()
{
Addition foncteur;
int a(2), b(3);
cout << a << " + " << b << " = " << foncteur(a,b) << endl; //On utilise le
foncteur comme s'il s'agissait d'une fonction
return 0;
}

Les prédicats
 Ce sont des foncteurs prenant un seul argument et renvoyant un booléen

 Ils servent à tester une propriété particulière de l'objet passé en argument

 On les utilise pour répondre à des questions comme :


o Ce nombre est-il plus grand que 10 ?
o Cette chaîne de caractères contient-elle des voyelles ?
o CePersonnageest-il encore vivant ?

Les foncteurs pré-définie:


Pour les opérations les plus simples, le travail est pré-mâché. Tout se trouve dans le fichier d'en-
têtefunctional :
 La STL propose un foncteur nomméplus(quelle originalité) pour faire cela.

.
#include <iostream>
#include <functional> //Ne pas oublier !
using namespace std;

int main()
{
plus<int> foncteur; //On déclare le foncteur additionnant deux entiers
int a(2), b(3);
cout << a << " + " << b << " = " << foncteur(a,b) << endl; //On utilise le
foncteur comme s'il s'agissait d'une fonction
return 0;
}

Fusion des deux concepts


Modifiez le comportement d'unemap

Le constructeur de la classe map prend en réalité un argument : le foncteur de comparaison


entre les clés.

Par défaut, si l'on ne spécifie rien, c'est un foncteur construit à partir de l'opérateur<qui sert de
comparaison

Avec un foncteur comparant la longueur de deuxstring.

#include <string>
using namespace std;
class CompareLongueur
{
public:
bool operator()(const string& a, const string& b)
{
return a.length() < b.length();
}
};

indiquer à notre map que nous voulons utiliser ce foncteur.

int main()
{
//Une table qui associe le nom d'un animal à son poids
map<string, double,CompareLongueur> poids; //On utilise le foncteur comme
critère de comparaison
//On ajoute les poids de quelques animaux
poids["souris"] = 0.05;
poids["tigre"] = 200;
poids["chat"] = 3;
poids["elephant"] = 10000;

//Et on parcourt la table en affichant le nom et le poids


for(map<string, double>::iterator it=poids.begin(); it!=poids.end(); ++it)
{
cout << it->first << " pese " << it->second << " kg." << endl;
}
return 0;
}
Et ce programme donne le résultat suivant :
chat pese 3 kg.
tigre pese 200 kg.
souris pese 0.05 kg.
elephant pese 10000 kg.

Récapitulatif des conteneurs les plus courants

vector
Exemple :vector<int>

 éléments stockés côte-à-côte ;


 optimisé pour l'ajout en fin de tableau ;
 éléments indexés par des entiers.

vector

deque

Exemple :deque<int>
 éléments stockés côte-à-côte ;
 optimisé pour l'ajout en début et en fin de tableau ;
 éléments indexés par des entiers.

deque

list

Exemple :list<int>
 éléments stockés de manière « aléatoire » dans la mémoire ;
 ne se parcourt qu'avec des itérateurs ;
 optimisé pour l'insertion et la suppression au milieu.

list

map
Exemple :map<string,int>

 éléments indexés par ce que l'on veut ;


 éléments triés selon leurs index ;
 ne se parcourt qu'avec des itérateurs.

map

set
Exemple :set<int>

 éléments triés ;
 ne se parcourt qu'avec des itérateurs.
Décrouvrez la puissance des algorithms

Un premier exemple

Std::generate
#include <algorithm>

int main()
{
list<int> tab; //Une liste d'entiers

//Quelques manipulations pour créer des éléments…

Remplir f(0);

generate(tab.begin(), tab.end(), f); //On applique f aux éléments de la liste

return 0;
}

Comptez, cherchez, triez


Comptez des éléments
Pour compter le nombre d'éléments égaux au nombre 2, c'est très simple :

int nombre = count(tab.begin(), tab.end(), 2);

Example:
#include <iostream>
#include <cstdlib> //pour rand()
#include <ctime> //pour time()
#include <vector>
#include <algorithm>
using namespace std;

class Generer
{
public:
int operator()() const
{
return rand() % 10; //On renvoie un nombre entre 0 et 9
}
};

int main()
{
srand(time(0));

vector<int> tab(100,-1); //Un tableau de 100 cases

generate(tab.begin(), tab.end(), Generer()); //On génère les nombres


aléatoires
int const compteur = count(tab.begin(), tab.end(), 5); //Et on compte les
occurrences du 5

cout << "Il y a " << compteur << " elements valant 5." << endl;

return 0;
}
Le retour des prédicats
Si vous pensiez que vous pourriez vous en sortir sans ces drôles de foncteurs, vous
vous trompiez ! Je vous avais dit au chapi
l s'appelle count_if(). La différence avec count()est que le troisième argument n'est pas
une valeur mais un prédicat.

#include <algorithm>
#include <string>
#include <vector>
using namespace std;

class TestVoyelles
{
public:
bool operator()(string const& chaine) const
{
for(int i(0); i<chaine.size(); ++i)
{
switch (chaine[i]) //On teste les lettres une à une
{
case 'a': //Si c'est une voyelle
case 'e':
case 'i':
case 'o':
case 'u':
case 'y':
return true; //On renvoie 'true'
default:
break; //Sinon, on continue
}
}
return false; //Si on arrive là, c'est qu'il n'y avait pas de voyelle du
tout
}
};

int main()
{
vector<string> tableau;

//… On remplit le tableau en lisant un fichier, par exemple.

int const compteur = count_if(tableau.begin(), tableau.end(), TestVoyelles());

//… Et on fait quelque chose avec 'compteur'

return 0;
}

Cherchez
Chercher un élément dans un tableau est aussi très facile.
 On utilise l'algorithme find()oufind_if()

Triez !
class ComparaisonLongueur
{
public:
bool operator()(const string& a, const string& b)
{
return a.length() < b.length();
}
};

int main()
{
vector<string> tableau;

//… On remplit le tableau en lisant un fichier par exemple.

sort(tableau.begin(), tableau.end(), ComparaisonLongueur());

//Le tableau est maintenant trié par longueur de chaîne

return 0;
}

Encore plus d'algos

Std::for_each()

class Sommer
{
public:
Sommer()
:m_somme(0)
{}

void operator()(int n)
{
m_somme += n;
}

int resultat() const


{
return m_somme;
}

private:
int m_somme;

};

int main()
{
srand(time(0));
vector<int> tab(100, -1);
generate(tab.begin(), tab.end(), Generer()); //On génère des nombres
aléatoires
Sommer somme;

//On somme les éléments et on récupère le foncteur utilisé


somme = for_each(tab.begin(), tab.end(), somme);

cout << "La somme des elements generes est : " << somme.resultat() << endl;

return 0;
}

Utilisez deux séries à la fois: std::transform

#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

int main()
{
vector<double> a(50, 0.); //Trois tableaux de 50 nombres à virgule
vector<double> b(50, 0.);
vector<double> c(50, 0.);

//Remplissage des vectors 'a' et 'b'….

transform(a.begin(), a.end(), b.begin(), c.begin(), plus<double>());

//À partir d'ici les cases de 'c' contiennent la somme des cases de 'a' et 'b'

return 0;
}

Utilisez les itérateurs sur les flux


Dans ce chapitre, nous allons apprendre à utiliser des itérateurs sur les flux
Finalement, nous verrons qu'il existe aussi des flux sur les string

Les itérateurs de flux


sur les itérateurs, je vous avais présenté deux catégories d'itérateurs :

 les random access iterators, qui permettent d'accéder directement à n'importe


quelle case d'un tableau ;
 les bidirectional iterators qui, eux, ne peuvent avancer et reculer que d'une
case à la fois sans pouvoir aller directement à une position donnée.

Une des propriétés importantes des flux (iterateur des flux)


 ils ne peuvent être lus et modifiés que dans un seul sens: On ne peut pas lire un fichier à
l'envers ou écrire une phrase dans la console en sens inverse

 Les itérateurs sur les flux ont donc la propriété de ne pouvoir qu'avancer: ils ne
possèdent que l'opérateur++ et pas le--

 les itérateurs sur les flux entrants


o (cin, les fichiersifstream,… ) ne peuvent pas modifier les éléments sur
lesquels ils pointent (on ne peut que lire dans cin, pas y écrire)

 les itérateurs sur les flux sortant


o (cout, les fichiers ofstream,… ) ne peuvent pas lire la valeur des éléments,
seulement y écrire.

Déclarez un itérateur sur un flux sortant


Les itérateurs de flux (et plus généralement tous les itérateurs) sont déclarés dans l'en-
tête iteratorde la SL.: #include <iterator>nsole :

#include <iostream>
#include <iterator>
using namespace std;

int main()
{
ostream_iterator<double> it(cout);
*it = 3.14;
*it = 2.71;

return 0;
}
vous devriez obtenir ceci :
3.142.71

Le seul problème, c'est que nous n'avons pas inséré d'espace entre eux

SOLUTION: Delimiteur
On peut spécifier ce qu'on appelle un délimiteur, c'est-à-dire le ou les symboles qui
seront insérés entre chaque écriture faite via l'opérateur*
#include <iostream>
#include <iterator>
using namespace std;

int main()
{
ostream_iterator<double> it(cout, ", ");
*it = 3.14;
*it = 2.71;

return 0;
}
Ce qui donne :
3.14, 2.71,

Pour obtenir un retour à la ligne entre chaque écriture, il faut spécifier le délimiteur ''\n''.

Déclarez un itérateur sur un flux entrant


différence avec les ostream_iteratorest qu'il faut explicitement les faire avancer
après chaque lecture. Et bien sûr, cela se fait grâce à l'opérateur++.
Des qu’il pointe directement vers ce qu'on appelle un end-of-stream iterator, une sorte de
signal de fin de flux.  fin du fichier

#include <fstream>
#include <iterator>
#include <iostream>
using namespace std;

int main()
{
ifstream fichier("data.txt");
istream_iterator<double> it(fichier); //Un itérateur sur le fichier
istream_iterator<double> end; //Le signal de fin

while(it != end) //Tant qu'on a pas atteint la fin


{
cout << *it << endl; //On lit
++it; //Et on avance
}
return 0;
}

Le retour des algorithmes


L'algorithmecopy
Il arrive très souvent que l'on doive lire des valeurs depuis un fichier pour les stocker dans
un vectorpar exemple

#include <algorithm>
#include <vector>
#include <iterator>
#include <fstream>
using namespace std;

int main()
{
vector<int> tab(100,0);
ifstream fichier("C:/Nanoc/data.txt");
istream_iterator<int> it(fichier);
istream_iterator<int> fin;
copy(it, fin, tab.begin()); //On copie le contenu du fichier du debut à la
fin dans le vector

return 0;
}
Il faut absolument que votre vectorsoit assez grand pour contenir tous les nombres lus

On peut bien sûr utiliser copy()pour écrire dans un fichier ou dans la console

:
int main()
{
srand(time(0));
vector<int> tab(100,-1); //Un tableau de 100 cases

//On génère les nombres aléatoires


generate(tab.begin(), tab.end(), Generer());
//On trie le tableau
sort(tab.begin(), tab.end());
//Et on l'affiche
copy(tab.begin(), tab.end(), ostream_iterator<int>(cout, "\n");
return 0;
}
C'est simple et efficace. On ne s'embête plus avec des boucles.

Le problème de la taille
Lorsqu'on lit des données dans un fichier pour les insérer dans un tableau, il y a un problème qui
survient assez souvent : celui de la taille à donner au tableau.

SOLUTION: BACK_INSERTER

#include <algorithm>
#include <vector>
#include <iterator>
#include <fstream>
using namespace std;
int main()
{
vector<int> tab; //Un tableau vide
ifstream fichier("C:/Nanoc/data.txt");
istream_iterator<int> it(fichier);
istream_iterator<int> fin;
back_insert_iterator<vector<int> > it2(tab);
//L'algorithme ajoute les cases nécessaires au tableau
copy(it, fin, it2);
return 0;
}

Voici par exemple les lignes permettant de trouver le minimum des valeurs dans un
fichier :

ifstream fichier("C:/Nanoc/data.txt");

cout << *min_element(istream_iterator<int>(fichier), istream_iterator<int>())<<


endl;

Encore un retour sur les strings !


Les flux sur les strings'appellent ostringstreamet istringstreamselon qu'on
lit la chaîne ou qu'on y écrit.

#include <string>
#include <sstream>
#include <iostream>
using namespace std;
int main()
{
ostringstream flux; //Un flux permettant d'écrire dans une chaîne
flux << "Salut les"; //On écrit dans le flux grâce à l'opérateur <<
flux << " zeros";
flux << " !";
string const chaine = flux.str(); //On récupère la chaine
cout << chaine << endl; //Affiche 'Salut les zeros !'
return 0;
}
Allez plus loin avec la SL
Plus loin avec les strings
string chaine("Salut les zeros!"); //Une chaîne

string::iterator it = chaine.begin(); //Un itérateur sur le début

les fonctions toupper()et tolower()qui permettent de convertir une minuscule en majuscule

#include <iostream>
#include <string>
#include <algorithm>
#include <cctype>
using namespace std;

class Convertir
{
public:
char operator()(char c) const
{
return toupper(c);
}
};

int main()
{
string chaine("Salut les zeros !");
transform(chaine.begin(), chaine.end(), chaine.begin(), Convertir());
cout << chaine << endl;
return 0;
}

Manipulez les tableaux statiques

int tab[10]; //Un tableau de 10 entiers

int* it(&tab[0]); //On récupère l'adresse de la première case

OU PLUTOT

int tab[10]; //Un tableau de 10 entiers


int* it(tab); //Un itérateur sur ce tableau

L'itérateur de fin
Faire du sorting sur tableau normal

#include <algorithm>
using namespace std;
int main()
{
int const taille(1000);
double tableau[taille]; //On déclare un tableau
//Remplissage du tableau…
double* debut(tableau); //Les deux itérateurs
double* fin(tableau+taille);
sort(debut, fin); //Et on trie
return 0;
}

Faites du calcul scientifique


Les nombres complexes
déclarer un nombre complexe $$$2+3i$$$$$$2+3i$$$ et l'afficher, on utilise le
code suivant :
#include <complex>
#include <iostream>
using namespace std;

int main()
{
complex<double> c(2,3);
cout << c << endl;
return 0;
}
Ce code produit le résultat suivant :
(2., 3.)

En plus des opérations arithmétiques de base, il existe aussi quelques fonctions


mathématiques bien pratiques comme la racine carrée ou les fonctions
trigonométriques.

complex<double> a(1., 2.), b(-2, 4), c;


c = sqrt(a+b);

a = cos(c/b) + sin(b/c);

Bref, tout ce qui est nécessaire pour faire des maths un peu poussées. Enfin, il
existe des fonctions spécifiques aux nombres complexes comme la norme ou le
conjugué.

complex<double> a(3,4);
cout << norm(conj(a)) << endl; //Affiche '5'

Les valarray
 Ils sont plus restrictifs que les vectordans le sens où l'on ne peut pas facilement ajouter
des cases à la fin mais, comme ce n'est pas une opération très courante, ce n'est pas un
problème.
 La grande force des valarrayest la possibilité d'effectuer des opérations
mathématiques directement avec l'ensemble du tableau.

#include<valarray>
using namespace std;
int main()
{
valarray<int> a(10, 5); //5 éléments valant 10
valarray<int> b(8, 5); //5 éléments valant 8
valarray<int> c = a + b; //Chaque élément de c vaut 18
return 0;
}

la méthode apply(): pour appliquer un foncteur aux éléments du tableau

#include<valarray>
#include<cmath>
using namespace std;

class Cosinus //Un foncteur pour le calcul du cosinus


{
public:
double operator()(double x) const
{
return cos(x);
}
};

int main()
{
valarray<double> a(10); //10 éléments

//Remplissage du tableau…

a.apply(Cosinus);

//Chaque case contient maintenant le cosinus de son ancienne valeur

return 0;
}

Gérez des erreurs avec les exceptions


La gestion des exceptions
Souvenez-vous, un throw doit toujours se trouver dans un bloctryqui doit lui-même être
suivi d'un bloccatch. Cela donne la structure suivante :

int division(int a,int b)


{
try
{
if(b == 0)
throw string("Division par zéro !");
else
return a/b;
}
catch(string const& chaine)
{
cerr << chaine << endl;
}
}
Cela donne le résultat suivant :
Valeur pour a : 3
Valeur pour b : 0
ERREUR : Division par zéro !

Le mieux à faire serait alors de lancer l'exception dans la fonction et de récupérer l'erreur, si elle
se produit, dans lemain. De cette manière, celui qui appelle la fonction a conscience qu'une
erreur s'est produit

int division(int a,int b) // Calcule a divisé par b.


{
if(b==0)
throw string("ERREUR : Division par zéro !");
else
return a/b;
}

int main()
{
int a,b;
cout << "Valeur pour a : ";
cin >> a;
cout << "Valeur pour b : ";
cin >> b;

try
{
cout << a << " / " << b << " = " << division(a,b) << endl;
}
catch(string const& chaine)
{
cerr << chaine << endl;
}
return 0;
}

HOW TO DO IT IN A GENERIC WAY: std::exception

#include <exception>
using namespace std;

class Erreur: public exception


{
public:
Erreur(int numero=0, string const& phrase="", int niveau=0) throw()
:m_numero(numero),m_phrase(phrase),m_niveau(niveau)
{}

virtual const char* what() const throw()


{
return m_phrase.c_str();
}

int getNiveau() const throw()


{
return m_niveau;
}

virtual ~Erreur() throw()


{}

private:
int m_numero; //Numéro de l'erreur
string m_phrase; //Description de l'erreur
int m_niveau; //Niveau de l'erreur
};
On pourrait alors réécrire la fonction de division de 2 entiers de la manière suivante :
int division(int a,int b) // Calcule a divisé par b.
{
if(b==0)
throw Erreur(1,"Division par zéro",2);
else
return a/b;
}

int main()
{
int a,b;
cout << "Valeur pour a : ";
cin >> a;
cout << "Valeur pour b : ";
cin >> b;

try
{
cout << a << " / " << b << " = " << division(a,b) << endl;
}
catch(std::exception const& e)
{
cerr << "ERREUR : " << e.what() << endl;
}

return 0;
}
Cela donne à l'exécution :
Valeur pour a : 3
Valeur pour b : 0
ERREUR : Division par zéro

Quel est l'intérêt de dériver la classeexceptionalors qu'on pourrait faire sa propre


classe sans aucun héritage ?
Excellente question. Il faut savoir que vous n'êtes pas les seuls à lancer des
exceptions. Certaines fonctions standard lancent elles aussi des exceptions. Toutes
les exceptions lancées par les fonctions standard dérivent de la classeexceptionce
qui permet, avec un code générique,

La bibliothèque standard peut lancer 5 types d'exceptions différents résumés


dans le tableau suivant :

Nom de la classe Description


bad_alloc Lancée s'il se produit une erreur en mémoire.
bad_cast Lancée s'il se produit une erreur lors d'un dynamic_cast.
bad_exception Lancée si aucuncatchne correspond à un objet lancé.
bad_typeid Lancée s'il se produit une erreur lors d'untypeid.
ios_base::failure Lancée s'il se produit une erreur avec un flux.
On peut par exemple observer un exemple debad_allocavec le code suivant :
#include <iostream>
#include <vector>
using namespace std;

int main()
{
try
{
vector<int> a(1000000000,1); //Un tableau bien trop grand
}
catch(exception const& e) //On rattrape les exceptions standard de tous types
{
cerr << "ERREUR : " << e.what() << endl; //On affiche la description de
l'erreur
}
return 0;
}
Cela donne le résultat suivant dans la console :
ERREUR : std::bad_alloc
Si l'on avait attrapé l'exception par valeur et pas par référence (c'est-à-dire sans le &), le
message aurait étéstd::exceptioncar le polymorphisme n'est pas conservé. C'est pour cela
que l'on attrape toujours les exceptions par référence. Il est fort quand même ce
polymorphisme !

Toutes les exceptions présentées dérivent de la classeexceptionet possèdent un


constructeur prenant en argument une chaîne de caractères qui décrit le problème.
Nom de la classe Catégorie Description
domain_error logique Erreur de domaine mathématique.
invalid_argument logique Argument invalide passé à une fonction.
length_error logique Taille invalide.
out_of_range logique Erreur d'indice de tableau.
logic_error logique Autre problème de logique.
range_error exécution Erreur de domaine.
overflow_error exécution Erreur d'overflow.
underflow_error exécution Erreur d'underflow.

runtime_error exécution Autre type d'erreur.


Si vous ne savez pas quoi choisir, prenez simplementruntime_error, cela n'a de
toute façon que peu d'importance.

Et comment les utilise-t-on ?


Reprenons une dernière fois notre exemple de division. Nous avons une erreur de
domaine mathématique si l'argumentbest nul. Choisissons donc de lancer
unedomain_error.
int division(int a,int b) // Calcule a divisé par b.
{
if(b==0)
throw domain_error("Division par zéro");
else
return a/b;
}

Les exceptions de vector

C'est pour cela que lesvector(et lesdeque) proposent une méthode appeléeat()qui
fait exactement la même chose que les crochets mais qui lance une exception en
cas d'indice erroné.
#include <vector>
#include <iostream>
using namespace std;

int main()
{
vector<double> tab(5, 3.14); //Un tableau de 5 nombres à virgule

try
{
tab.at(8) = 4.; //On essaye de modifier la 8ème case
}
catch(exception const& e)
{
cerr << "ERREUR : " << e.what() << endl;
}
return 0;
}
Cela nous donne :
ERREUR : vector::_M_range_check

Relancez une exception


Il est possible de relancer une exception reçue par un bloccatchafin de la traiter une
deuxième fois, plus loin dans le code. Pour ce faire, il faut utiliser le mot-
cléthrowsans expression derrière.
catch(exception const& e) // Rattrape toutes les exceptions
{
//On traite une première fois l'exception
cerr << "ERREUR: " << e.what() << endl;

throw; // Et on relance l'exception reçue pour la retraiter


// dans un autre bloc catch plus loin dans le code.
}
Les assertions
Les exceptions c'est bien mais il y a des cas où mettre en place tous ces
blocstry/catchest fastidieux

Il existe un autre mécanisme de détection et de gestion qui vient du langage C : les


assertions.

Claquez une assertion


 Si c'est vrai, rien ne se passe et le programme continue.
 Par contre, si le test est négatif, le programme s'arrête brutalement et un
message d'erreur s'affiche dans le terminal.
#include <cassert>
using namespace std;

int main()
{
int a(5);
int b(5);

assert(a == b) ; //On vérifie que a et b sont égaux

//reste du programme
return 0;

Désactivez les assertions


Un autre point fort des assertions :
 la possibilité de les désactiver totalement. Dans ce cas, le compilateur ignore
simplement les lignesassert(...)et n'effectue pas le test qui se trouve entre
les parenthèses.

 Pour désactiver les assertions, il faut ajouter l'option-DNDEBUGà la ligne de


compilation.

Créez des templates


La spécialisation
Pour l'instant, nous n'avons essayé la fonctionmaximum()qu'avec des types de base.
Essayons-la donc avec une chaîne de caractères :
int main()
{
cout << "Le plus grand est: " << maximum<std::string>("elephant","souris") <<
endl;

return 0;
}
Le résultat de ce petit programme est :

Le plus grand est: souris


On l'a déjà vu, l'opérateur<pour les chaînes de caractères compare suivant l'ordre
lexicographique. Mais imaginons (comme précédemment) que le critère de comparaison qui
nous intéresse est la longueur de la chaîne. Cela se fait en spécialisant la fonction
template.

La spécialisation
La spécialisation emploie la syntaxe suivante :

template <>
string maximum<string>(const string& a, const string& b)
{
if(a.size()>b.size())
return a;
else
return b;
}
Vous remarquerez deux choses:

 la première ligne ne comporte aucun type entre<et>;


 le prototype de la fonction utilise cette fois le type que l'on veut et plus le type
génériqueT.
Avec cette spécialisation, on obtient le comportement voulu :
int main()
{
cout << "Le plus grand est: " << maximum<std::string>("elephant","souris") <<
endl;

return 0;
}
qui donne :

Le plus grand est: elephant

La seule difficulté de la spécialisation est la syntaxe qui commence par la


lignetemplate<>.
Vous pouvez évidemment spécialiser la fonction pour plusieurs types différents. Il
vous faudra alors créer une spécialisation par type.

L'ordre des fonctions


Pour pouvoir compiler et avoir le comportement voulu, votre programme devra être organisé
d'une manière spéciale. Il faut respecter un ordre particulier :

1. la fonction générique ;
2. les fonctions spécialisées.

L'ordre est essentiel.


Lors de la compilation, le compilateur cherche une fonction spécialisée. S'il n'en
trouve pas, alors il utilise la fonction générique déclarée au-dessus.
Les classes templates

Vous aimerez peut-être aussi