Au tableau !

Si je vous demande de me faire un programme calculant la moyenne d’une série de nombres, comment feriez-vous ? Avec une boucle, comme nous avons appris dans le chapitre précédent. Mais maintenant, j’ai besoin de les stocker. Comment faire, puisque vous ne savez pas à l’avance combien de notes je veux rentrer ?

Ce chapitre va résoudre cette problématique en vous présentant les tableaux.

Un tableau c'est quoi ?

Les tableaux, en informatique, font partie de la grande famille des structures de données. Ces structures de données sont des façons particulières d’organiser et de stocker des données. Il y a ainsi des structures pour stocker un nombre fixe de données du même type, d’autres pour un nombre variable, d’autre encore pour stocker des données de types différents. Ces objets, conçus spécialement pour stocker des données, sont appelés conteneurs.

Celles qui nous intéressent maintenant sont les tableaux. Ils sont conçus pour stocker des données de même type et ayant des points communs : notes d’un élève à l’école, titres de livres, âge d’utilisateurs. Dès que vous pensez « liste de… », dès que des éléments ont des points communs et qu’il fait sens de les regrouper, alors l’usage d’un tableau peut s’envisager.

Un tableau n’est cependant pas la solution universelle, mais ça, nous le verrons au fur et à mesure, pour ne pas vous encombrer inutilement de détails. ;)

std::vector — Mais quel dynamisme !

Commençons par les tableaux dynamiques, c’est-à-dire dont la taille ne sera connue qu’au moment de l’exécution du programme. C++ nous fournit justement un objet déjà codé pour ça et qui peut facilement s’agrandir ou se rétrécir : std::vector. Et le fichier d’en-tête correspondant se doit bien évidemment d’être inclus en faisant #include <vector>.

Déclarer un std::vector

Les concepteurs de std::vector l’ont créé de telle sorte qu’il soit utilisable avec n’importe quel type. On peut donc utiliser un std::vector de int pour stocker des âges et un std::vector de double pour stocker des moyennes. Voici la façon de faire.

std::vector</* type des éléments du tableau */> identifiant {};

Avec maintenant trois exemples concrets.

#include <string>
// N'oubliez pas cette ligne.
#include <vector>

int main()
{
    std::vector<int> const tableau_de_int {};
    std::vector<double> const tableau_de_double {};
    // Même avec des chaînes de caractères c'est possible.
    std::vector<std::string> const tableau_de_string {};

    return 0;
}

Tous nos std::vector, ou tableaux, sont vides avec ces déclarations. Mais on peut très bien leur affecter des valeurs, comme ci-dessous. Chacun de nos tableaux a un nombre d’éléments différents dès sa création.

#include <string>
#include <vector>

int main()
{
    // On stocke 5 entiers.
    std::vector<int> const tableau_de_int { 1, 2, 3, 4, 5 };
    // Et ici 2 flottants.
    std::vector<double> const tableau_de_double { 2.71828, 3.14159 };
    // Maintenant 3 chaînes de caractères.
    std::vector<std::string> const tableau_de_string { "Une phrase.", "Et une autre !", "Allez, une pour la fin." };

    return 0;
}

Rien ne nous empêche non plus de copier un tableau dans un autre, tout comme on le fait pour des int.

#include <vector>

int main()
{
    std::vector<int> const tableau_de_int { 1, 2, 3 };
    // Maintenant, copie possède les mêmes éléments que tableau_de_int.
    std::vector<int> const copie { tableau_de_int };

    return 0;
}

Manipuler les éléments d’un std::vector

Accès aux éléments

Pour l’instant, nos tableaux sont inutiles, nous ne faisons rien avec. Commençons donc à nous amuser un peu en récupérant un élément du tableau pour l’afficher. On utilise pour cela les crochets devant l’identifiant de notre tableau, avec entre crochets la position de l’élément à récupérer.

identificateur[/* un entier */];
#include <vector>
#include <iostream>
 
int main()
{
    std::vector<int> const nombres { 2, 4, 6, 8 };
    std::cout << "Deuxième élément: " << nombres[1] << '\n';

    return 0;
}

Pourquoi récupère-t-on le deuxième élément en écrivant 1 ? Le deuxième élément, ça devrait être 2 non ?

Justement non. La subtilité des tableaux en C++, c’est qu'ils commencent à l’indice 0 et non l’indice 1. Pourquoi ça ? C’est un héritage du C que je ne vais pas détailler car il demande des connaissances que vous n’avez pas encore. Souvenez-vous simplement que le premier élément d’un tableau est à l’indice 0 et que donc le xxème élément d’un tableau est à l’indice x1x - 1.

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> const tableau_de_int { 1, 2, 3 };

    std::cout << "Le premier élément est " << tableau_de_int[0] << "." << std::endl;
    std::cout << "Le deuxième élément est " << tableau_de_int[1] << "." << std::endl;
    std::cout << "Le troisième élément est " << tableau_de_int[2] << "."  << std::endl;

    return 0;
}

Et si j’accède à l’élément 4, que se passe-t-il ?

Potentiellement, tout et n’importe quoi. Parfois, le programme va continuer sans rien dire, d’autre fois vous allez avoir des erreurs et des plantages, comme je l’illustre ci-dessous. Parfois, le programme peut aussi péter complètement un câble et lancer un missile nucléaire sur un pays pris au hasard. C’est ce qu’on appelle un comportement indéterminé, en anglais « undefined behavior ». Impossible donc de faire du code de qualité dans ces conditions. Voilà pourquoi il ne faut pas utiliser d’indice inférieur à 0 ou plus grand que la taille de notre tableau moins un.

Un exemple avec Visual Studio 2017 de ce qui arrive.
Un exemple avec Visual Studio 2017 de ce qui arrive.
Comportement indéterminé

Un « UB » est le signe d’une erreur de programmation. Vous avez mal fait quelque chose, quelque part dans le code et le programme ne peut plus être considéré comme fiable désormais. Il faut donc à tout prix les éviter et faire attention en codant !

Premier et dernier élément

Il existe d’ailleurs deux fonctions dont le but est d’accéder spécifiquement au premier ou au dernier élément d’un tableau. Elles se nomment, respectivement, front et back. Leur utilisation est très simple.

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> const tableau_de_int { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

    std::cout << "Le premier élément est " << tableau_de_int.front() << "." << std::endl;
    std::cout << "Le dernier élément est " << tableau_de_int.back() << "." << std::endl;

    return 0;
}

Obtenir la taille d’un tableau

Justement, comment récupère-t-on la taille de notre tableau ? En utilisant la fonction std::size, qui renvoie le nombre d’éléments de n’importe quel conteneur.

Le type de retour de cette fonction est un type nouveau, que nous n’avons pas rencontré jusque-là et qui s’appelle std::size_t. C’est un type entier conçu, entre autres, pour être capable de stocker la taille de n’importe quel tableau, aussi grand soit-il.

En vrai, un int est suffisant et std::size_t est présent car historiquement déjà là en C. De toute façon, avec auto, nous n’avons aucune raison de nous casser la tête là-dessus. :)

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> const tableau_de_int { 1, 2, 3 };

    auto const taille { std::size(tableau_de_int) };
    std::cout << "Mon tableau contient " << taille << " éléments." << std::endl;

    return 0;
}

Afficher les éléments d’un tableau

Comme nous l’avions dit dans le chapitre sur les boucles, depuis l’arrivée de C++11, il existe une variante de la boucle for, conçue exclusivement pour parcourir les éléments d’un conteneur. Ci-dessous, voici un pseudo-code illustrant cette possibilité.

for (/* type d'un élément du tableau ou auto */ identifiant : /* tableau à parcourir */)
{
    // Manipuler identifiant, en l'affichant par exemple.
}

Voici ce que cela donne avec un exemple concret. Pas besoin de récupérer la taille, la boucle est intelligente. Cela nous facilite la vie et rend notre code plus concis et plus facile à lire.

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> const tableau_entiers { 1, 2, 3 };

    std::cout << "Je vais afficher mon tableau en entier.\n";
    for (auto const element : tableau_entiers)
    {
        std::cout << element << std::endl;
    }

    return 0;
}
auto ou pas ?

Quant à utiliser auto ou préciser explicitement le type d’un élément (int par exemple), c’est une question personnelle. Certains ne vont jurer que par auto, d’autres ne le mettront que si le type est long à écrire. Vous êtes libres de choisir ce qu’il vous plaît.

Vérifier si un tableau est vide

Fonction bien utile, std::empty permet de vérifier si un tableau est vide, c’est-à-dire qu’il ne contient aucun élément et donc qu’il a une taille de zéro. Elle renvoie un booléen, donc cela signifie que cette fonction appliquée à un tableau vide renverra true et false pour un tableau avec trois éléments, par exemple.

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> const tableau_vide {};
    std::vector<int> const tableau_rempli { 1, 2, 3 };

    std::cout << std::boolalpha;
    std::cout << "Est-ce que tableau_vide est vide ? Réponse : " << std::empty(tableau_vide) << std::endl;
    std::cout << "Est-ce que tableau_rempli est vide ? Réponse : " << std::empty(tableau_rempli) << std::endl;

    return 0;
}

Ajouter des éléments

Pour l’instant, nos std::vector ne sont pas trop dynamiques. Mais cela va changer avec la fonction push_back, qui va nous donner la possibilité d’ajouter un élément à la fin de notre tableau. Celui-ci doit cependant être du même type que les autres éléments du tableau. On ne pourra ainsi pas ajouter de int à un tableau de std::string.

#include <iostream>
#include <string>
#include <vector>

int main()
{
    std::vector<int> tableau_de_int { 12, 24 };
    // On ajoute un élément...
    tableau_de_int.push_back(36);
    // ...mais on peut en ajouter encore d'autres.
    tableau_de_int.push_back(48);
    tableau_de_int.push_back(100);

    // On affiche pour vérifier.
    for (auto const valeur : tableau_de_int)
    {
        std::cout << valeur << std::endl;
    }

    std::vector<std::string> tableau_de_string { "Salut !", "Voici une phrase." };
    tableau_de_string.push_back("Mais je vais en ajouter une autre.");
    // Ceci ne compilera pas.
    //tableau_de_string.push_back(5);

    for (auto const chaine : tableau_de_string)
    {
        std::cout << chaine << std::endl;
    }

    return 0;
}

Et pour vous prouver que la taille change bien, il suffit d’utiliser std::size comme vu plus haut.

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> tableau_de_int { 12, 24 };

    std::cout << "Taille avant l'ajout : " << tableau_de_int.size() << std::endl;

    tableau_de_int.push_back(36);
    tableau_de_int.push_back(48);
    tableau_de_int.push_back(100);

    std::cout << "Taille après l'ajout : " << std::size(tableau_de_int) << std::endl;

    return 0;
}

Supprimer des éléments

On ajoute, on ajoute, mais on a aussi envie de pouvoir retirer un voire tous les éléments. Cette possibilité nous est également offerte avec les fonctions pop_back et clear. Ces fonctions sont simples, comme l’illustre l’exemple ci-dessous.

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> tableau_de_int { 12, 24 };

    std::cout << "Taille avant l'ajout : " << std::size(tableau_de_int) << std::endl;

    tableau_de_int.push_back(36);
    tableau_de_int.push_back(48);
    tableau_de_int.push_back(100);

    std::cout << "Taille après l'ajout : " << std::size(tableau_de_int) << std::endl;

    // Je retire le dernier élément.
    tableau_de_int.pop_back();
    std::cout << "Taille après avoir retiré le dernier élément : " << std::size(tableau_de_int) << std::endl;

    // Finalement j'en ai marre, je vide le tableau.
    tableau_de_int.clear();
    std::cout << "Taille après avoir vidé le tableau : " << std::size(tableau_de_int) << std::endl;

    return 0;
}
Comportement indéterminé

Appeler pop_back sur un tableau vide est aussi un comportement indéterminé.

Modifier des éléments

Pour l’instant, nous ne faisons que lire les valeurs de notre tableau, mais il n’y a aucun problème pour les modifier, tant que le tableau n’est pas déclaré comme const bien évidemment. Et la bonne nouvelle, c’est que vous savez déjà comment faire : les crochets []. En effet, s’ils sont suivis d’un = puis d’une valeur, alors l’élément du tableau auquel on accède se voit modifié.

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> tableau { 1, 2, 3 };

    // Finalement, je préfère que la première valeur soit un 15.
    tableau[0] = 15;

    for (auto const valeur : tableau)
    {
        std::cout << valeur << std::endl;
    }

    return 0;
}

De même que pour la lecture, n’essayez pas de modifier la valeur d’un élément en dehors du tableau. Je vous le rappelle, c’est un comportement indéterminé et qui est potentiellement dangereux !

Remplir le tableau avec une valeur unique

Dernière fonction que je vais vous montrer, celle-ci est bien utile pour remplir d’un coup un tableau d’une même valeur. La fonction assign a besoin de savoir combien de valeurs vous comptez insérer et quelle est-elle.

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> valeurs {};
    std::cout << "Taille actuelle de mon tableau : " << std::size(valeurs) << " éléments.\n";

    // On ajoute dix fois la valeur 42.
    valeurs.assign(10, 42);
    std::cout << "Taille actuelle de mon tableau : " << std::size(valeurs) << " éléments.\n";

    for (auto const valeur : valeurs)
    {
        std::cout << valeur << std::endl;
    }

    std::cout << '\n';

    // Finalement on réduit en asignant cinq fois la valeur 3.
    valeurs.assign(5, 3);
    std::cout << "Taille actuelle de mon tableau : " << std::size(valeurs) << " éléments.\n";

    for (auto const valeur : valeurs)
    {
        std::cout << valeur << std::endl;
    }

    std::cout << '\n';

    // Mais on peut très bien augmenter.
    valeurs.assign(12, 78);
    std::cout << "Taille actuelle de mon tableau : " << std::size(valeurs) << " éléments.\n";

    for (auto const valeur : valeurs)
    {
        std::cout << valeur << std::endl;
    }

    return 0;
}

Exercices

Calcul de moyenne

Dans cet exercice, vous allez laisser l’utilisateur entrer autant de valeurs qu’il le souhaite puis en faire la moyenne, c’est-à-dire diviser la somme des notes par le nombre de notes. L’utilisateur pourra taper autant de valeurs positives qu’il le souhaite. Cependant, un nombre négatif signifiera qu’il ne souhaite plus entrer de note.

Correction calcul de moyenne
#include <iostream>
#include <vector>

int main()
{
    std::vector<double> notes {};
    
    while (true)
    {
        std::cout << "Entre une note (inférieur à 0 pour arrêter) : ";
        double note { 0.0 };
        
        // N'oublions pas de protéger notre entrée.
        while (!(std::cin >> note))
        {
            std::cout << "Entrée invalide. Recommence." << std::endl;
            std::cin.clear();
            std::cin.ignore(255, '\n');
        }

        if (note < 0)
        {
            // On s'arrête si la note est inférieur à 0.
            break;
        }

        // On ajoute la note à la liste des notes.
        notes.push_back(note);
    }

    if (!std::empty(notes))
    {
        double total{};
        for (auto const note : notes)
        {
            total += note;
        }

        std::cout << "Ta moyenne est de " << total / notes.size() << "." << std::endl;
    }
    else
    {
        std::cout << "Tu n'as pas rentré de notes, je ne peux pas calculer ta moyenne." << std::endl;
    }
    
    return 0;
}

Aviez-vous pensé à prendre en compte si l’utilisateur ne rentre aucune note car sa seule entrée est négative ? :)

Minimum et maximum

Le but de ce programme est tout simple : il faut trouver le plus petit élément du tableau, ainsi que le plus grand. Pour se simplifier la vie, vous pouvez utiliser un tableau déjà rempli.

Correction maximum et minimum
#include <iostream>
#include <vector>

int main()
{
    std::vector<int> const entiers { 5, -2, 74, 455, -12 };
    int minimum { entiers[0] };
    int maximum { entiers[0] };

    for (auto const entier : entiers)
    {
        if (entier < minimum)
        {
            minimum = entier;
        }
        
        if (entier > maximum)
        {
            maximum = entier;
        }
    }

    std::cout << "Le minimum est " << minimum << "." << std::endl;
    std::cout << "Le maximum est " << maximum << "." << std::endl;
    
    return 0;
}

Séparer les pairs des impairs

Cette fois, on veut créer deux nouveaux tableaux qui contiendront, pour le premier les nombres pairs uniquement, pour le second les nombres impairs.

Correction pairs et impairs
#include <iostream>
#include <vector>

int main()
{
    std::vector<int> const entiers{ 5, -2, 74, 455, -12, 10, 11 };
    std::vector<int> pairs {};
    std::vector<int> impairs {};

    for (auto const entier : entiers)
    {
        if (entier % 2 == 0)
        {
            pairs.push_back(entier);
        }
        else
        {
            impairs.push_back(entier);
        }
    }

    std::cout << "Les pairs sont : " << std::endl;
    for (int const entier_pair : pairs)
    {
        std::cout << entier_pair << std::endl;
    }

    std::cout << std::endl << "Les impairs sont : " << std::endl;
    for (int const entier_impair : impairs)
    {
        std::cout << entier_impair << std::endl;
    }
    
    return 0;
}

Compter les occurrences d’une valeur

Une opération très courante avec un tableau est de compter le nombre de fois que revient une certaine valeur. Faites donc un programme qui permet de compter le nombre de fois que revient une lettre dans un tableau de char.

Correction occurrence d’une valeur
#include <iostream>
#include <vector>

int main()
{
    std::vector<char> const lettres { 'a', 'b', 'b', 'c', 'b', 'd', 'd', 'a' };
    char const lettre_a_compter { 'b' };
    int total { 0 };

    for (auto const lettre : lettres)
    {
        if (lettre == lettre_a_compter)
        {
            ++total;
        }
    }

    std::cout << "La lettre '" << lettre_a_compter << "' est présente " << total << " fois." << std::endl;

    return 0;
}

std::array — Le tableau statique

Abordons maintenant l’autre catégorie de tableaux qui existe en C++ : les tableaux statiques, c’est-à-dire dont la taille est connue à la compilation et qui ne peut varier. En contre-partie, ce tableau est plus performant et plus rapide qu’un std::vector puisqu’il n’y a pas d’opération d’ajout ou de retrait d’éléments.

Pour l’utiliser, il faut inclure le ficher d’en-tête <array>.

Déclarer un std::array

Comme pour std::vector, notre nouveau conteneur attend qu’on lui indique non seulement quel type de données il doit stocker, mais également combien. Je rappelle que la taille doit être définie à la compilation. Vous ne pouvez pas déclarer un std::array et lui passer une taille ultérieurement. Voyez vous-mêmes l’exemple ci-dessous.

#include <array>
#include <iostream>

int main()
{
    // Tableau contenant cinq entiers explicitement précisés.
    std::array<int, 5> const tableau_de_5_entiers { 1, 2, 3, 4, 5 };
    for (auto const element : tableau_de_5_entiers)
    {
        std::cout << element << std::endl;
    }

    // Tableau contenant deux décimaux définis puis cinq zéros.
    std::array<double, 7> const tableau_de_7_decimaux { 3.14159, 2.1878 };
    for (auto const element : tableau_de_7_decimaux)
    {
        std::cout << element << std::endl;
    }

    // Tableau qui a trop d'éléments et ne compilera pas.
    //std::array<int, 2> const tableau_qui_ne_compilera_pas { 1, 2, 3, 4 };

    // Il est tout à fait possible de copier un tableau.
    auto copie { tableau_de_5_entiers };
    for (auto const element : copie)
    {
        std::cout << element << std::endl;
    }

    return 0;
}

Avez-vous remarqué que, contrairement à std::vector, un tableau statique défini avec std::array contient des éléments par défaut ? Ainsi, même si l’on ne précise pas tous les éléments, comme pour tableau_de_7_decimaux, nous avons la garantie que les autres seront initialisés à zéro.

Remplir un tableau

Il existe une fonction permettant de remplir le tableau, mais contrairement à std::vector qui peut être redimensionné et donc attend le nouveau nombre d’éléments, la fonction fill pour std::array n’attend que la valeur utilisée pour remplir le tableau.

#include <array>
#include <iostream>

int main()
{
    std::array<int, 5> valeurs { 1, 2, 3, 4, 5 };
    
    std::cout << "Avant" << std::endl;
    for (auto const valeur : valeurs)
    {
        std::cout << valeur << std::endl;
    }

    // On remplit de 42.
    valeurs.fill(42);

    std::cout << std::endl << "Après" << std::endl;
    for (auto const valeur : valeurs)
    {
        std::cout << valeur << std::endl;
    }

    return 0;
}

Accéder aux éléments

Comme précédemment, pour accéder à un élément du tableau, nous allons utiliser les crochets []. Et comme pour std::vector, faites attention à ne pas accéder à un élément en dehors des limites du tableau.

#include <array>
#include <iostream>

int main()
{
    std::array<double, 7> tableau_de_7_decimaux { 3.14159, 2.1878 };
    // C'est le nombre d'or.
    tableau_de_7_decimaux[2] = 1.61803;

    // Aïe aïe aïe, catastrophe !
    //tableau_de_7_decimaux[7] = 1.0;

    for (auto const element : tableau_de_7_decimaux)
    {
        std::cout << element << std::endl;
    }

    return 0;
}

Les fonctions front et back sont toujours de la partie.

#include <array>
#include <iostream>

int main()
{
    std::array<int, 9> const tableau_de_int { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

    std::cout << "Le premier élément est " << tableau_de_int.front() << "." << std::endl;
    std::cout << "Le dernier élément est " << tableau_de_int.back() << "." << std::endl;

    return 0;
}

Connaître la taille

Sans surprise, c’est la même fonction que pour std::vector, je parle de std::size. Le code qui suit ne devrait donc pas vous surprendre.

#include <array>
#include <iostream>

int main()
{
    std::array<int, 5> const valeurs { 1, 2, 3, 4, 5 };
    std::cout << "Et la taille de mon tableau vaut bien " << std::size(valeurs) << "." << std::endl;
    return 0;
}

Vérifier si le tableau est vide

De même que vu plus haut, rien de dépaysant, la fonction std::empty renvoie false si le tableau contient des éléments et true s’il est vide. En pratique, cette fonction ne vous renverra jamais true car avoir un tableau vide signifie ici que la taille donnée est 0. Autant vous dire que c’est inutile d’avoir un tableau vide.

#include <array>
#include <iostream>

int main()
{
    std::array<int, 5> const valeurs { 1, 2, 3, 4, 5 };
    std::cout << std::boolalpha;
    std::cout << "Est-ce que mon tableau est vide ? " << std::empty(valeurs) << " Ah, je le savais !" << std::endl;
    return 0;
}

std::string — Un type qui cache bien son jeu

Vous vous demandez peut-être toujours à quoi servent vraiment les tableaux. Vous voudriez un exemple concret d’un tableau qui rend de grands services. Ça tombe très bien puisque vous en utilisez un depuis le début de ce cours : std::string.

En effet, vous vous souvenez que C++ utilise le type char pour stocker un caractère. De là, stocker du texte se fait en stockant un ensemble de char. Le type std::string n’est donc rien d’autre qu'un tableau de char, conçu et optimisé spécialement pour le stockage de texte.

Connaître la taille d’une chaîne

En toute originalité, on peut récupérer la taille d’une std::string grâce à la fonction std::size.

#include <cassert>
#include <iostream>
#include <string>

int main()
{
    std::string const phrase { "Voici une phrase normale." };
    std::cout << "Voici la longueur de cette phrase : " << std::size(phrase) << " caractères." << std::endl;

    return 0;
}

Accéder à un caractère

Comme pour les deux conteneurs que nous avons découverts plus tôt dans ce chapitre, nous pouvons utiliser les crochets [] pour accéder à un caractère en particulier. Et, exactement comme pour les deux autres, il faut faire attention à bien utiliser un indice qui est inférieur à la taille de la chaîne.

#include <cassert>
#include <iostream>
#include <string>

int main()
{
    std::string const phrase { "Ceci me servira d'exemple." };
    auto const indice { 5 };

    std::cout << "Voici le caractère à la position " << indice << " : " << phrase[indice] << std::endl;
    return 0;
}

On peut bien entendu modifier un caractère sans problème.

#include <iostream>
#include <string>

int main()
{
    std::string verbe { "Estamper" };
    verbe[3] = 'o';
    std::cout << "Verbe choisi : " << verbe << std::endl;

    return 0;
}

Premier et dernier caractère

Les deux fonctions front et back nous rendent le même service que précédemment, en nous permettant de récupérer le premier et le dernier caractère de la chaîne.

#include <iostream>
#include <string>

int main()
{
    std::string const phrase { "Bonjour tout le monde !" };
    std::cout << "Première lettre : " << phrase.front() << std::endl;
    std::cout << "Dernière lettre : " << phrase.back() << std::endl;

    return 0;
}

Vérifier qu’une chaîne est vide

Toujours sans surprise pour vous, nous allons utiliser la même fonction std::empty que pour std::vector et std::array.

#include <iostream>
#include <string>

int main()
{
    std::string const texte { "Du texte." };

    std::cout << std::boolalpha;
    std::cout << "Est ce que 'texte' est vide ? " << std::empty(texte) << std::endl;

    std::string const rien { "" };
    std::cout << "Est ce que 'rien' est vide ? " << std::empty(rien) << std::endl;

    return 0;
}

Ajouter ou supprimer un caractère à la fin

Nous bénéficions également des fonctions push_back et pop_back pour, respectivement, ajouter et supprimer un caractère à la fin de la chaîne. Faites toujours attention à ne pas retirer le dernier caractère d’une chaîne vide, c’est un comportement indéfini.

#include <iostream>
#include <string>

int main()
{
    std::string phrase { "Une phrase !" };

    phrase.pop_back();
    phrase.pop_back();

    phrase.push_back('.');

    std::cout << phrase << std::endl;

    return 0;
}

Supprimer tous les caractères

Nous avons une méthode portant le doux nom de clear, de l’anglais « débarrasser, nettoyer, vider », qui va nous laisser une chaîne vide.

#include <cassert>
#include <iostream>
#include <string>

int main()
{
    std::string phrase { "Blablabla du texte." };
    phrase.clear();
    std::cout << "Rien ne va s'afficher : " << phrase << std::endl;

    return 0;
}

Boucler sur une chaîne

On peut bien évidemment boucler sur tous les caractères qui composent une chaîne à l’aide de for.

#include <iostream>
#include <string>

int main()
{
    std::string const phrase { "Voici un ensemble de lettres." };
    for (char lettre : phrase)
    {
        std::cout << lettre << std::endl;
    }

    return 0;
}

Bien entendu, std::string est encore plus fourni que ça et permet de faire de nombreuses autres choses. Nous aurons l’occasion de les examiner plus tard dans ce tutoriel. Retenez simplement que std::string peut être manipulé comme un tableau.


En résumé

  • Les tableaux nous permettent de stocker un ensemble de valeurs liées entre elles (notes, âges, moyennes, caractères, etc).
  • Il existe plusieurs types de tableaux en C++ : dynamique quand la taille est connue à l’exécution et statique quand elle est connue à la compilation.
  • std::vector est un tableau dynamique, dont le nombre d’éléments peut augmenter ou diminuer à volonté.
  • std::array est un tableau statique, dont la taille est fixée définitivement à la compilation, et dont on ne peut pas ajouter ni supprimer d’éléments.
  • std::string est un tableau dynamique spécialement conçu pour la gestion du texte.