• 50 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 25/03/2019

Allez plus loin avec la SL

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Dès le premier chapitre sur la SL, je vous ai prévenus que le sujet était très vaste et qu'il serait difficile d'en faire le tour.
Nous avons étudié les principaux éléments repris du langage C puis nous nous sommes concentrés sur la STL et sur les flux. Il faut quand même que je vous présente les autres possibilités de la bibliothèque standard.

Ce chapitre présente trois domaines différents où la SL va nous aider à améliorer nos programmes. Pour commencer, nous allons reparler des chaînes de caractères et voir comment, là aussi, utiliser des itérateurs. Puis, nous reviendrons sur les tableaux statiques. Nous les avions un peu abandonnés au profit des autres conteneurs mais il est temps d'en reparler et d'utiliser nos nouveaux meilleurs amis : les itérateurs bien sûr !
Enfin, la troisième partie sera assez différente puisque nous y découvrirons quelque chose de complètement nouveau : les outils dédiés au calcul scientifique. Le C++ est en effet très utilisé par les chercheurs en tous genres pour faire des simulations, que ce soit sur un ordinateur classique ou sur un super-calculateur.

Plus loin avec les strings

Bon, les string, on commence à connaître depuis le temps ! Cependant, vous êtes encore loin de tout savoir. Et puisque nous parlons de la STL depuis quelques chapitres, vous vous doutez probablement que nous allons avoir affaire à des itérateurs. Nous avions vu que les stringse comportaient comme des tableaux grâce à la surcharge de l'opérateur[]. Mais ce n'est pas leur seul point commun avec les vector, ils possèdent aussi les méthodes begin()et end()renvoyant un itérateur sur le début et la fin de la chaîne.

string chaine("Salut les zeros!");    //Une chaîne

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

Bref, que des choses déjà bien connues.
Je vous avais présenté, dans le chapitre d'introduction à la SL, les fonctions toupper()et tolower()qui permettent de convertir une minuscule en majuscule et vice-versa. Il est possible d'utiliser ces fonctions dans les algorithmes. Ici, c'est bien sûr l'algorithme transform()qu'il faut utiliser. Il parcourt la chaîne, applique la fonction sur chaque élément et écrit le résultat au même endroit.

#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;
}

Ce code affiche donc le résultat suivant :

SALUT LES ZEROS !

Il n'y pas grand chose de plus à dire sur le sujet. En fait, vous savez déjà presque tout. Sachez seulement que les stringpossèdent aussi des méthodes insert()et erase()qui fonctionnent de manière similaire à celles de vector. Vous pouvez, grâce à elles, insérer et supprimer des lettres au milieu d'une chaîne.

Passons maintenant à une autre vieille connaissance : le tableau statique.

Manipulez les tableaux statiques

Commençons par un bref rappel. Un tableau statique est un tableau dont la taille ne peut pas varier. Il se déclare en utilisant les crochets[]entre lesquels on spécifie le nombre de cases désirées. Par exemple, pour un tableau de 10 entiers nommé tab, on aurait la déclaration suivante :

int tab[10];

Et on peut bien sûr créer des tableaux de n'importe quel type : doublestringou mêmePersonnage(la fameuse classe que nous avions créée lorsque nous avions découvert la POO).
Il y a une seule obligation : les objets doivent posséder un constructeur par défaut.

Comme ces tableaux ne sont pas des objets (comme vectorou deque), ils ne possèdent aucune méthode. Il n'est donc pas possible, par exemple, de connaître leur taille. Il faut toujours stocker la taille du tableau dans une variable supplémentaire. Mais cela, vous le saviez déjà.

Les itérateurs

Ces tableaux ont beau ne pas être des objets, on aimerait quand même bien pouvoir utiliser des itérateurs puisque cela nous ouvrirait la porte des algorithmes. Cependant, il n'existe pas d'itérateur spécifique et bien sûr pas de méthode begin()ou end(). Je vous avais dit que les itérateurs étaient la « version objet » des pointeurs, tout comme vectorest la « version objet » des tableaux statiques. Et là, je vous sens frémir. Effectivement, nous allons utiliser des pointeurs comme itérateurs sur ces tableaux. Que demande-t-on à un itérateur ? Principalement de pouvoir avancer, reculer et de nous renvoyer la valeur pointée grâce à l'opérateur *. Ce sont justement des opérations qui existent pour les pointeurs. Il n'y a donc plus qu'à se jeter à l'eau.

Dans la plupart des cas, on a besoin d'un itérateur sur le premier élément. Dans notre nouveau langage, on dirait qu'on a besoin de l'adresse de la première case. On pourrait donc écrire ceci pour notre itérateur :

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

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

Ah, je vois que cela vous fait peur. Rappelez-vous que l'esperluette (&) renvoie l'adresse d'une variable, ici la première case du tableau. On initialise ensuite notre pointeur d'entiers à cette valeur. Nous avons donc un itérateur.

Heureusement, il existe une manière plus simple d'écrire cela. Il faut savoir que tabest lui aussi, en réalité, un pointeur (on n'avait jamais eu besoin de cette information jusqu'ici et j'espère que vous ne m'en voudrez pas de ne pas l'avoir dit plus tôt) ! Ce pointeur pointe sur la première case, justement ce qu'il nous faut. On écrit donc généralement plutôt ceci :

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

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

L'itérateur de fin

Comme toujours, on a besoin de spécifier la fin du tableau via un deuxième itérateur. La solution est de réfléchir aux cases qui sont accessibles. Dans l'exemple précédent ,itpointe sur la première case. Donc, it+1pointera sur la deuxième, it+2sur la troisième, etc. Un itérateur pointant sur la première case en dehors du tableau sera, en suivant cette logique, it+10. Si on itère de ità it+10exclu, on aura parcouru toutes les cases du
tableau. En règle générale, on stocke la taille du tableau dans une variable et on écrirait le code suivant pour obtenir le début et la fin d'un tableau :

int const taille(10);
int tab[taille]; //Un tableau de 10 entiers

int* debut(tab); //Un itérateur sur le début
int* fin(tab+taille); //Un itérateur sur la fin

Nous avons ainsi un équivalent de begin()et un équivalent de end(). Il ne nous reste plus qu'à utiliser les algorithmes. Mais cela, vous savez déjà le faire. En tout cas je l'espère…
Bon, je vous donne quand même un exemple. Pour trier un tableau de nombres, on peut écrire ceci :

#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;
}

Je crois que vous auriez trouvé par vous-mêmes. Vous êtes devenu des pros de la STL depuis le temps. ;)

Faites du calcul scientifique

Dans tous les programmes scientifiques, il y a, vous vous en doutez, beaucoup de calculs. Ce sont des programmes qui manipulent énormément de nombres en tous genres. Vous connaissez déjà les intet les doubleainsi que les fractions mais, dans certains projets, on utilise également des nombres complexes (si vous ne savez pas ce que c'est, ce n'est pas grave, vous pouvez simplement sauter cette section pour attaquer celle parlant des valarray).

Les nombres complexes

Comme c'est une brique de base, la SL se devait de fournir un moyen de manipuler ces nombres. C'est pour cela qu'il existe l'en-tête complex, dans lequel se trouve la définition de la classe du même nom.
Pour déclarer un nombre complexe $\($$$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.)

Il faut spécifier le type des nombres à utiliser pour représenter la partie réelle et la partie imaginaire des nombres complexes. Il est très rare d'utiliser pour cela autre chose que des double, mais on ne sait jamais…
À partir de là, on peut utiliser les opérateurs usuels pour faire des additions, multiplications, divisions, etc. avec ces nombres. La force de la surcharge des opérateurs est à nouveau visible. 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é. Vous trouverez une liste complète des possibilités dans votre documentation préférée.

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

Toutes les fonctions ont leur nom habituel en maths, il n'y a donc aucune difficulté. Il faut juste savoir qu'elles existent, ce qui est chose faite maintenant. ;)

Lesvalarray

L'autre élément que l'on retrouve dans beaucoup de programmes de simulation est bien sûr le tableau de nombres. Vous en connaissez déjà beaucoup mais il y a une forme particulièrement bien adaptée aux calculs : 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.
On peut par exemple calculer la somme de deux tableaux élément par élément simplement en utilisant l'opérateur+.

#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;
}

On n'a ainsi pas besoin d'écrire des boucles pour effectuer ces opérations de base. Remarquez au passage que le constructeur des valarrayprend ses arguments dans l'ordre inverse des vector. Il faut d'abord indiquer la valeur que l'on souhaite puis le nombre de cases. Faites attention, on se trompe souvent !

Tous les opérateurs usuels sont surchargés de sorte qu'ils travaillent sur tous les éléments séparément. Par exemple, l'opérateur==compare un par un tous les éléments du tableau et renvoie un tableau de bool. On peut alors savoir quels sont les éléments identiques et ceux qui sont différents en lisant la case correspondante de ce tableau.

Enfin, on peut aussi utiliser la méthode apply()pour appliquer un foncteur aux éléments du tableau. On s'économise ainsi l'utilisation d'un algorithme et des itérateurs. C'est un confort de notation supplémentaire. Pour calculer le cosinus de tous les éléments d'un valarray, on écrirait ceci :

#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;
}

À nouveau, faites un tour dans votre documentation favorite pour découvrir toutes les fonctionnalités de ces tableaux. Ils sont vraiment pratiques.

En résumé

  • Les stringproposent eux aussi des itérateurs. On peut donc utiliser les algorithmes également sur les chaînes de caractères.

  • Les tableaux statiques ne possèdent pas d'itérateurs mais on utilise pour les remplacer les pointeurs.

  • La SL propose quelques outils pour le calcul scientifique, notamment une classe de nombres complexes et des tableaux optimisés pour effectuer des opérations mathématiques.

Exemple de certificat de réussite
Exemple de certificat de réussite