Chapitre 14. Les types complémentaires

Table des matières
14.1. Les chaînes de caractères
14.2. Les types utilitaires
14.3. Les types numériques

Le C++ étant un langage basé sur le C, il souffre des mêmes limitations concernant les types de données avancés que celui-ci. Pour pallier cet inconvénient, la bibliothèque standard C++ définit des types complémentaires sous forme de classes C++, éventuellement template, et permettant de satisfaire aux besoins les plus courants. Parmi ces types, on notera avant tout le type basic_string, qui permet de manipuler les chaînes de caractères de manière plus simple et plus sûre qu'avec des pointeurs et des tableaux de caractères. Mais la bibliothèque standard définit également des classes utilitaires qui permettent de manipuler les autres types plus facilement, ainsi que des types capables d'utiliser toutes les ressources de la machine pour les calculs numériques avancés.

14.1. Les chaînes de caractères

La classe template basic_string de la bibliothèque standard, déclarée dans l'en-tête string, facilite le travail du programmeur et permet d'écrire du code manipulant des textes de manière beaucoup plus sûre. En effet, cette classe encapsule les chaînes de caractères C classiques et fournissent des services extrêmement intéressants qui n'étaient pas disponibles auparavant. En particulier, la classe basic_string dispose des caractéristiques suivantes :

Comme il l'a été dit plus haut, la classe basic_string est une classe template. Cela signifie qu'elle est capable de prendre en charge des chaînes de n'importe quel type de caractère. Pour cela, elle ne se base que sur la classe des traits du type de caractère manipulé. Il est donc parfaitement possible de l'utiliser avec des types définis par l'utilisateur, pourvu que la classe des traits des caractères soit définie pour ces types. Bien entendu, la classe basic_string peut être utilisée avec les types de caractères du langage, à savoir char et wchar_t.

Les déclarations de l'en-tête string sont essentiellement les suivantes :

template <class charT, class traits = char_traits<charT>,
    class Allocator = allocator<charT> >
class basic_string
{
public:
    // Types
    typedef traits                     traits_type;
    typedef typename traits::char_type value_type;
    typedef Allocator                  allocator_type;
    typedef typename Allocator::size_type       size_type;
    typedef typename Allocator::difference_type difference_type;
    typedef typename Allocator::reference       reference_type;
    typedef typename Allocator::const_reference const_reference;
    typedef typename Allocator::pointer         pointer;
    typedef typename Allocator::const_pointer   const_pointer;

    // Constante utilisée en interne et représentant la valeur maximale
    // du type size_type :
    static const size_type npos = static_cast<size_type>(-1);

    // Constructeurs et destructeur :
    explicit basic_string(const Allocator &allocateur = Allocator());
    basic_string(const basic_string &source, size_type debut = 0,
        size_type longueur = npos, const Allocator &allocateur = Allocator());
    basic_string(const charT *chaine, size_type nombre,
        const Allocator &allocateur = Allocator());
    basic_string(const charT *chaine,
        const Allocator &allocateur = Allocator());
    basic_string(size_type nombre, charT caractere,
        const Allocator &allocateur  = Allocator());
    template <class InputIterator>
    basic_string(InputIterator debut, InputIterator fin,
        const Allocator &allocateur  = Allocator());
    ~basic_string();

    // Itérateurs :
    typedef type_privé iterator;
    typedef type_privé const iterator;
    typedef std::reverse_iterator<iterator> reverse_iterator;
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
    iterator       begin();
    const_iterator begin() const;
    iterator       end();
    const_iterator end() const;
    reverse_iterator       rbegin();
    const_reverse_iterator rbegin() const;
    reverse_iterator       rend();
    const_reverse_iterator rend() const;

    // Accesseurs :
    size_type size() const;
    size_type length() const;
    size_type max_size() const;
    size_type capacity() const;
    bool empty() const;
    allocator_type get_allocator() const;

    // Manipulateurs :
    void resize(size_type taille, charT caractere);
    void resize(size_type taille);
    void reserve(size_type taille = 0);

    // Accès aux données de la chaîne :
    const_reference operator[](size_type index) const;
    reference operator[](size_type index);
    const_reference at(size_type index) const;
    reference at(size_type index);
    const charT *c_str() const;
    const charT *data() const;
    size_type copy(charT *destination, size_type taille,
        size_type debut = 0) const;
    basic_string substr(size_type debut = 0, size_type taille = npos) const;

    // Affectation :
    basic_string &operator=(const basic_string &source);
    basic_string &operator=(const charT *source);
    basic_string &operator=(charT caractere);
    basic_string &assign(const basic_string &source);
    basic_string &assign(const basic_string &source,
        size_type position, size_type nombre);
    basic_string &assign(const charT *chaine, size_type nombre);
    basic_string &assign(const charT *chaine);
    basic_string &assign(size_type nombre, charT caractere);
    template <class InputIterator>
    basic_string &assign(InputIterator debut, InputIterator fin);

    // Concaténation et ajout :
    basic_string &operator+=(const basic_string &source);
    basic_string &operator+=(const charT *chaine);
    basic_string &operator+=(charT caractere);
    basic_string &append(const basic_string &source);
    basic_string &append(const basic_string &source,
        size_type position, size_type nombre);
    basic_string &append(const charT *chaine, size_type nombre);
    basic_string &append(const charT *chaine);
    basic_string &append(size_type nombre, charT caractere);
    template <class InputIterator>
    basic_string &append(InputIterator debut, InputIterator fin);

    // Insertion et extraction :
    basic_string &insert(size_type position, const basic_string &source);
    basic_string &insert(size_type position, const basic_string &source,
        size_type debut, size_type nombre);
    basic_string &insert(size_type position, const charT *chaine,
        size_type nombre);
    basic_string &insert(size_type position, const charT *chaine);
    basic_string &insert(size_type position, size_type nombre,
        charT caractere);
    iterator insert(iterator position, charT caractere = charT());
    void insert(iterator position, size_type nombre, charT caractere);
    template <class InputIterator>
    void insert(iterator position, InputIterator debut, InputIterator fin);

    // Suppression :
    basic_string &erase(size_type debut = 0, size_type longueur = npos);
    iterator erase(iterator position);
    iterator erase(iterator debut, iterator fin);
    void clear();

    // Remplacement et échange :
    basic_string &replace(size_type position, size_type longueur,
        const basic_string &remplacement);
    basic_string &replace(size_type position, size_type longueur,
        const basic_string &remplacement, size_type debut,
        size_type taille);
    basic_string &replace(size_type position, size_type longueur,
        const charT *remplacement, size_type taille);
    basic_string &replace(size_type position, size_type longueur,
        const charT *remplacement);
    basic_string &replace(size_type position, size_type longueur,
        size_type nombre, charT caractere);
    basic_string &replace(iterator debut, iterator fin,
        const basic_string &remplacement);
    basic_string &replace(iterator debut, iterator fin,
        const charT *remplacement, size_type taille);
    basic_string &replace(iterator debut, iterator fin,
        const charT *remplacement);
    basic_string &replace(iterator debut, iterator fin,
        size_type nombre, charT caractere);
    template <class InputIterator>
    basic_string &replace(iterator debut, iterator fin,
        InputIterator debut_remplacement, InputIterator fin_remplacement);
    void swap(basic_string<charT, traits, Allocator> &chaine);

    // Comparaison :
    int compare(const basic_string &chaine) const;
    int compare(size_type debut1, size_type longueur1,
        const basic_string &chaine,
        size_type debut2, size_type longueur2) const;
    int compare(const charT *chaine) const;
    int compare(size_type debut, size_type longueur, const charT *chaine,
        size_type taille = npos) const;

    // Recherche :
    size_type find(const basic_string &motif,
        size_type position = 0) const;
    size_type find(const charT *motif, size_type position,
        size_type taille) const;
    size_type find(const charT *motif, size_type position = 0) const;
    size_type find(charT caractere, size_type position = 0) const;
    size_type rfind(const basic_string &motif,
        size_type position = npos) const;
    size_type rfind(const charT *motif, size_type position,
        size_type taille) const;
    size_type rfind(const charT *motif, size_type position = npos) const;
    size_type rfind(charT caractere, size_type position = npos) const;
    size_type find_first_of(const basic_string &motif,
        size_type position = 0) const;
    size_type find_first_of(const charT *motif, size_type position,
        size_type taille) const;
    size_type find_first_of(const charT *motif,
        size_type position = 0) const;
    size_type find_first_of(charT caractere, size_type position = 0) const;
    size_type find_last_of(const basic_string &motif,
        size_type position = npos) const;
    size_type find_last_of(const charT *motif, size_type position,
        size_type taille) const;
    size_type find_last_of(const charT *motif,
        size_type position = npos) const;
    size_type find_last_of(charT caractere,
        size_type position = npos) const;
    size_type find_first_not_of(const basic_string &motif,
        size_type position = 0) const;
    size_type find_first_not_of(const charT *motif, size_type position,
        size_type taille) const;
    size_type find_first_not_of(const charT *motif,
        size_type position = 0) const;
    size_type find_first_not_of(charT caractere,
        size_type position = 0) const;
    size_type find_last_not_of(const basic_string &motif,
        size_type position = npos) const;
    size_type find_last_not_of(const charT *motif, size_type position,
        size_type taille) const;
    size_type find_last_not_of(const charT *motif,
        size_type position = npos) const;
    size_type find_last_not_of(charT caractere,
        size_type position = npos) const;
};

typedef basic_string<char>    string;
typedef basic_string<wchar_t> wstring;
Les opérateurs de concaténation, de comparaison et de sérialisation dans les flux d'entrée / sortie sont également définis dans cet en-tête et n'ont pas été reportés ici par souci de clarté. Comme vous pouvez le voir, la classe basic_string dispose d'un grand nombre de méthodes. Nous allons à présent les détailler dans les paragraphes suivants.

La bibliothèque standard définit deux types chaînes de caractères pour les types standards de caractères du langage : le type string pour les char, et le type wstring pour les wchar_t. En pratique, ce seront donc ces types qui seront utilisés dans les programmes. Les exemples de la suite de ce document utiliseront donc le type string, mais vous êtes libre d'utiliser des instances de la classe basic_string pour d'autres types de caractères.

14.1.1. Construction et initialisation d'une chaîne

La manière la plus simple de construire une basic_string est simplement de la déclarer, sans paramètres. Cela a pour conséquence d'appeler le constructeur par défaut, et d'initialiser la chaîne à la chaîne vide.

En revanche, si vous désirez initialiser cette chaîne, plusieurs possibilités s'offrent à vous. Outre le constructeur de copie, qui permet de copier une autre basic_string, il existe plusieurs surcharges du constructeur permettant d'initialiser la chaîne de différentes manières. Le constructeur le plus utilisé sera sans aucun doute le constructeur qui prend en paramètre une chaîne de caractères C classique :

string chaine("Valeur initiale");

Il existe cependant une variante de ce constructeur, qui prend en paramètre le nombre de caractères de la chaîne source à utiliser pour l'initialisation de la basic_string. Ce constructeur devra être utilisé dans le cas des tableaux de caractères, qui contiennent des chaînes de caractères qui ne sont pas nécessairement terminées par un caractère nul :

string chaine("Valeur initiale", 6);

La ligne précédente initialise la chaîne chaine avec la chaîne de caractères "Valeur", car seuls les six premiers caractères de la chaîne d'initialisation sont utilisés.

Il est également possible d'initialiser une basic_string avec une partie de chaîne de caractères seulement. Pour cela, il faut utiliser le constructeur template, qui prend en paramètre un itérateur référençant le premier caractère à utiliser lors de l'initialisation de la basic_string et un itérateur référençant le caractère suivant le dernier caractère à utiliser. Bien entendu, ces deux itérateurs sont de simples pointeurs de caractères si les caractères devant servir à l'initialisation sont dans une chaîne de caractères C ou dans un tableau de caractères. Cependant, ce peut être des itérateurs d'un conteneur quelconque, pourvu que celui-ci contienne bien une séquence de caractères et que le deuxième itérateur se trouve bien après le premier dans cette séquence. Notez que le deuxième itérateur ne référence pas le dernier caractère de la séquence d'initialisation, mais bien le caractère suivant. Il peut donc valoir la valeur de fin de l'itérateur du conteneur source. Ainsi, le code suivant :

char *source = "Valeur initiale";
string s(source, source+6);

a strictement le même effet que celui de l'exemple précédent.

Enfin, il existe un constructeur dont le but est d'initialiser une basic_string avec une suite de caractères identiques d'une certaine longueur. Ce constructeur n'est réellement pas difficile à utiliser, puisqu'il suffit de lui fournir en paramètre le nombre de caractères que la basic_string devra contenir et la valeur du caractère qui devra être répété dans cette chaîne.

Vous remarquerez que tous ces constructeurs prennent en dernier paramètre une instance d'allocateur mémoire à utiliser pour les opérations d'allocation et de libération de la mémoire que la chaîne est susceptible d'avoir à faire. Vous pouvez spécifier une instance quelconque, ou utiliser la valeur par défaut fournie par les déclarations des constructeurs. Cette valeur par défaut est tout simplement une instance temporaire de l'allocateur spécifié en paramètre template de la classe basic_string. Par défaut, cet allocateur est l'allocateur standard, pour lequel toutes les instances permettent d'accéder à la même mémoire. Il n'est donc pas nécessaire de spécifier l'instance à utiliser, puisqu'elles sont toutes fonctionnellement identiques. En pratique donc, il est très rare d'avoir à spécifier un allocateur mémoire dans ces constructeurs.

14.1.2. Accès aux propriétés d'une chaîne

La classe basic_string fournit un certain nombre d'accesseurs permettant d'obtenir des informations sur son état et sur la chaîne de caractères qu'elle contient. L'une des informations les plus intéressantes est sans nul doute la longueur de cette chaîne. Elle peut être obtenue à l'aide de deux accesseurs, qui sont strictement équivalents : size et length. Vous pouvez utiliser l'un ou l'autre, selon votre convenance. Par ailleurs, si vous désirez simplement savoir si la basic_string est vide, vous pouvez appeler la méthode empty.

Note : Attention, contrairement à ce que son nom pourrait laisser penser, la méthode empty ne vide pas la basic_string !

La taille maximale qu'une basic_string peut contenir est souvent directement liée à la quantité de mémoire disponible, puisque la chaîne de caractères qu'elle contient est allouée dynamiquement. Il n'y a donc souvent pas beaucoup d'intérêt à obtenir cette taille, mais vous pouvez malgré tout le faire, grâce à la méthode max_size.

La quantité de mémoire réellement allouée par une basic_string peut être supérieure à la longueur de la chaîne de caractères contenue. En effet, la classe basic_string peut conserver une marge de manoeuvre, pour le cas où la chaîne devrait être agrandie à la suite d'une opération particulière. Cela permet de réduire les réallocations de mémoire, qui peuvent être très coûteuses lorsque la mémoire se fragmente (la chaîne doit être recopiée vers un nouvel emplacement si un autre bloc mémoire se trouve juste après le bloc mémoire à réallouer). Cette quantité de mémoire peut être obtenue à l'aide de la méthode capacity. Nous verrons comment réserver de la place mémoire en prévision d'un redimensionnement ultérieur dans la section suivante.

Dans le cas où vous utiliseriez un allocateur différent de l'allocateur par défaut, vous pouvez obtenir une copie de cet allocateur grâce à la méthode get_allocator. Il est relativement rare d'avoir à utiliser cette méthode.

14.1.3. Modification de la taille des chaînes

Une fois qu'une basic_string a été construite, il est possible de la modifier a posteriori pour la réduire, l'agrandir ou augmenter sa capacité. Ces opérations peuvent être réalisées à l'aide de méthodes fournies à cet effet.

La méthode resize permet de modifier la taille de la chaîne de caractères stockée dans la basic_string. Dans sa version la plus simple, elle prend en paramètre la nouvelle taille que la chaîne doit avoir. Si cette taille est inférieure à la taille courante, la chaîne est tronquée. En revanche, si cette taille est supérieure à la taille actuelle, la chaîne est étendue et les nouveaux caractères sont initialisés avec la valeur des caractères définie par le constructeur par défaut de leur classe. Pour les types prédéfinis char et wchar_t, cette valeur est toujours le caractère nul. Les données stockées dans la basic_string représentent donc toujours la même chaîne de caractères C, puisque ces chaînes utilisent le caractère nul comme marqueur de fin de chaîne. Ainsi, la longueur renvoyée par la méthode size peut être différente de la longueur de la chaîne C contenue par la basic_string.

Exemple 14-1. Redimensionnement d'une chaîne

#include <iostream>
#include <string>
#include <string.h>

using namespace std;

int main(void)
{
    string s("123");
    s.resize(10);
    cout << s << endl;
    // La nouvelle taille vaut bien 10 :
    cout << "Nouvelle taille : " << s.length() << endl;
    // mais la longueur de la chaîne C reste 3 :
    cout << "Longueur C : " << strlen(s.c_str()) << endl;
    return 0;
}

Note : La méthode c_str utilisée dans cet exemple sera décrite en détail dans la section suivante. Elle permet d'obtenir l'adresse de la chaîne C stockée en interne dans la basic_string.

La fonction strlen quant à elle est une des fonctions de manipulation des chaînes de caractères de la bibliothèque C. Elle est définie dans le fichier d'en-tête string.h (que l'on ne confondra pas avec l'en-tête string de la bibliothèque standard C++), et renvoie la longueur de la chaîne de caractères qui lui est fournie en paramètre.

Si la valeur par défaut utilisée pour les caractères complémentaires dans la méthode resize n'est pas celle qui est désirée, il faut en utiliser une autre version. Cette deuxième version prend, en plus de la nouvelle taille de la chaîne de caractères, le caractère de remplissage à utiliser pour le cas où la nouvelle taille serait supérieure à la taille initiale de la chaîne :

string s("123");
s.resize(10, 'a');

Dans cet exemple, s contient finalement la chaîne de caractères "123aaaaaaa".

Nous avons vu précédemment que les basic_string étaient susceptibles d'allouer plus de mémoire que nécessaire pour stocker leurs données afin de limiter le nombre de réallocation mémoire. Ce mécanisme est complètement pris en charge par la bibliothèque, et le programmeur n'a en général pas à s'en soucier. Cependant, il peut exister des situations où l'on sait à l'avance la taille minimale qu'une chaîne doit avoir pour permettre de travailler dessus sans craindre de réallocations mémoire successives. Dans ce cas, on a tout intérêt à fixer la capacité de la chaîne directement à cette valeur, afin d'optimiser les traitements. Cela est réalisable à l'aide de la méthode reserve. Cette méthode prend en paramètre la capacité minimale que la basic_string doit avoir. La nouvelle capacité n'est pas forcément égale à ce paramètre après cet appel, car la basic_string peut allouer plus de mémoire que demandé.

Exemple 14-2. Réservation de mémoire dans une chaîne

#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    string s;
    cout << s.capacity() << endl;
    s.reserve(15);
    s = "123";
    cout << s.capacity() << endl;
    return 0;
}

Note : Les méthodes resize et reserve peuvent effectuer une réallocation de la zone mémoire contenant la chaîne de caractères. Par conséquent, toutes les références sur les caractères de la chaîne et tous les itérateurs sur cette chaîne deviennent invalide à la suite de leur exécution.

14.1.4. Accès aux données de la chaîne de caractères

Les caractères des basic_string peuvent être accédés de nombreuses manières. Premièrement, la classe basic_string surcharge l'opérateur d'accès aux éléments d'un tableau, et l'on pourra les utiliser pour obtenir une référence à un des caractères de la chaîne à partir de son indice. Cet opérateur n'est défini que pour les indices valides dans la chaîne de caractères, à savoir les indices variant de 0 à la valeur retournée par la méthode size de la chaîne moins un :

#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    string s("azc");
    // Remplace le deuxième caractère de la chaîne par un 'b' :
    s[1] = 'b';
    cout << s << endl;
    return 0;
}

Lorsqu'il est appliqué à une basic_string constante, l'opérateur tableau peut renvoyer la valeur du caractère dont l'indice est exactement la taille de la chaîne. Il s'agit évidemment du caractère nul servant de marqueur de fin de chaîne. En revanche, la référence renvoyée par cet opérateur pour toutes les autres valeurs, ainsi que par l'opérateur tableau appliqué aux chaînes non constante pour le caractère de fin de chaîne ne sont pas valides. Le comportement des programmes qui effectuent de tels accès est imprévisible.

Il existe une autre possibilité pour accéder aux caractères d'une basic_string. Il s'agit de la méthode at. Contrairement à l'opérateur tableau, cette méthode permet d'effectuer un contrôle sur la validité de l'indice utilisé. Elle renvoie, comme l'opérateur de tableau de la classe basic_string, la référence du caractère dont l'indice est spécifié en paramètre. Cependant, elle effectue au préalable un contrôle sur la validité de cet indice, qui doit toujours être strictement inférieur à la taille de la chaîne. Dans le cas contraire, la méthode at lance une exception out_of_range :

#include <iostream>
#include <string>
#include <stdexcept>

using namespace std;

int main(void)
{
    string s("01234");
    try
    {
        s.at(5);
    }
    catch (const out_of_range &)
    {
        cout << "Débordement !" << endl;
    }
    return 0;
}

La classe basic_string ne contient pas d'opérateur de transtypage vers les types des chaînes de caractères C classique, à savoir le type pointeur sur caractère et pointeur sur caractère constant. C'est un choix de conception, qui permet d'éviter les conversions implicites des basic_string en chaîne C classique, qui pourraient être extrêmement dangereuses. En effet, ces conversions conduiraient à obtenir implicitement des pointeurs qui ne seraient plus valides dès qu'une opération serait effectuée sur la basic_string source. Cependant, la classe basic_string fournit deux méthodes permettant d'obtenir de tels pointeurs de manière explicite. Le programmeur prend donc ses responsabilités lorsqu'il utilise ces méthodes, et est supposé savoir dans quel contexte ces pointeurs sont valides.

Ces deux méthodes sont respectivement la méthode data et la méthode c_str. La première méthode renvoie un pointeur sur les données de la basic_string. Ces données ne sont rien d'autre qu'un tableau de caractères, dont la dimension est exactement la valeur retournée par la méthode size ou par la méthode length. Ce tableau peut contenir des caractères de terminaison de chaîne, si par exemple une telle valeur a été écrite explicitement ou a été introduite suite à un redimensionnement de la chaîne. La méthode c_str en revanche retourne un pointeur sur la chaîne de caractères C contenue dans la basic_string. Contrairement aux données renvoyées par la méthode data, cette chaîne est nécessairement terminée par un caractère de fin de chaîne. Cette méthode sera donc utilisée partout où l'on veut obtenir une chaîne de caractères C classique, mais elle ne devra pas être utilisée pour accéder aux données situées après ce caractère de fin de chaîne.

Exemple 14-3. Accès direct aux données d'une chaîne

#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    string s("123456");
    // La chaîne C est coupée au troisième caractère :
    s[3] = 0;
    cout << s.c_str() << endl;
    // Mais on peut quand même obtenir les caractères suivants :
    cout << s.data()[4] << s.data()[5] << endl;
    return 0;
}

Notez que ces méthodes renvoient des pointeurs sur des données constantes. Cela est normal, car il est absolument interdit de modifier les données internes à la basic_string qui a fourni ces pointeurs. Vous ne devez donc en aucun cas essayer d'écrire dans ces tableaux de caractères, même en faisant un transtypage au préalable.

Enfin, la classe basic_string définit des itérateurs à accès aléatoires permettant d'accéder à ses données comme s'il s'agissait d'une chaîne de caractères standard. Les méthodes begin et end permettent d'obtenir respectivement un itérateur sur le premier caractère de la chaîne et la valeur de fin de ce même itérateur. De même, les méthodes rbegin et rend permettent de parcourir les données de la basic_string en sens inverse. Prenez garde au fait que ces itérateurs ne sont valides que tant qu'aucune méthode modifiant la chaîne n'est appelée.

14.1.5. Opérations sur les chaînes

La classe basic_string fournit tout un ensemble de méthodes permettant d'effectuer les opérations les plus courantes sur les chaînes de caractères. C'est cet ensemble de méthodes qui font tout l'intérêt de cette classe par rapport aux chaînes de caractères classiques du langage C, car elles prennent en charge automatiquement les allocations mémoire et les copies de chaînes que l'implémentation de ces fonctionnalités peut imposer. Ces opérations comprennent l'affectation et la concaténation de deux chaînes de caractères, l'extraction d'une sous-chaîne, ainsi que la suppression et le remplacement de caractères dans une chaîne.

14.1.5.1. Affectation et concaténation de chaînes de caractères

Plusieurs surcharges de l'opérateur d'affectation sont définies dans la classe basic_string. Ces surcharges permettent d'affecter une nouvelle valeur à une chaîne, en remplaçant éventuellement l'ancienne valeur. Dans le cas d'un remplacement, la mémoire consommée par l'ancienne valeur est automatiquement réutilisée ou libérée si une réallocation est nécessaire. Ces opérateurs d'affectation peuvent prendre en paramètre une autre basic_string, une chaîne de caractères C classique ou même un simple caractère. Leur utilisation est donc directe, il suffit simplement d'écrire une affectation normale.

Il est impossible, avec l'opérateur d'affectation, de fournir des paramètres supplémentaires comme ceux dont les constructeurs de la classe basic_string disposent par exemple. C'est pour cette raison qu'une autre méthode, la méthode assign, a été définie pour permettre de faire des affectations plus complexes.

Les premières versions de ces méthodes permettent bien entendu d'effectuer l'affectation d'une autre basic_string ou d'une chaîne de caractères C classique. Cependant, il est également possible de spécifier la longueur de la portion de chaîne à copier en deuxième paramètre pour les chaînes C, et la position ainsi que le nombre de caractères à copier dans le cas d'une basic_string. Il est également possible de réinitialiser une basic_string avec un caractère spécifique, en donnant et le nombre de caractères à dupliquer dans la chaîne en premier paramètre et la valeur de ce caractère en deuxième paramètre. Enfin, il existe une version template de cette méthode permettant d'affecter à la basic_string la séquence de caractères compris entre deux itérateurs d'un conteneur.

Exemple 14-4. Affectation de chaîne de caractères

#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    string s1, s2;
    char *p="1234567890";
    // Affecte "345" à s1 :
    s1.assign(p+2, p+6);
    cout << s1 << endl;
    // Affecte les deux premiers caractères de s1 à s2 :
    s2.assign(s1, 0, 2);
    cout << s2 << endl;
    // Réinitialise s1 avec des 'A' :
    s1.assign(5, 'A');
    cout << s1 << endl;
    return 0;
}

De manière similaire, l'opérateur d'affectation avec addition '+=' a été surchargé afin de permettre les concaténations de chaînes de caractères de manière intuitive. Cet opérateur permet d'ajouter aussi bien une autre basic_string qu'une chaîne de caractères C classique ou un unique caractère à la fin d'une basic_string. Comme cet opérateur est trop restrictif lorsqu'il s'agit de concaténer une partie seulement d'une autre chaîne à une basic_string, un jeu de méthodes append a été défini. Ces méthodes permettent d'ajouter à une basic_string une autre basic_string ou une chaîne de caractères C bien entendu, mais aussi d'ajouter une partie seulement de ces chaînes ou un nombre déterminé de caractères. Toutes ces méthodes prennent les mêmes paramètres que les méthodes assign correspondantes et leur emploi ne devrait pas poser de problème particulier.

Exemple 14-5. Concaténation de chaînes de carctères

#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    string s1 = "abcef";
    string s2 = "ghijkl";
    // Utilisation de l'opérateur de concaténation :
    s1+=s2;
    cout << s1 << endl;
    // Utilisation de la méthode append :
    s1.append("mnopq123456", 5);
    cout << s1 << endl;
    return 0;
}

14.1.5.2. Extraction de données d'une chaîne de caractères

Nous avons vu que les méthodes data et c_str permettaient d'obtenir des pointeurs sur les données des basic_string. Cependant, il est interdit de modifier les données de la basic_string au travers de ces pointeurs. Or, il peut être utile, dans certaines situations, d'avoir à travailler sur ces données, il faut donc pouvoir en faire une copie temporaire. C'est ce que permet la méthode copy. Cette fonction prend en paramètre un pointeur sur une zone de mémoire devant accueillir la copie des données de la basic_string, le nombre de caractères à copier, ainsi que le numéro du caractère de la basic_string à partir duquel la copie doit commencer. Ce dernier paramètre est facultatif, car il prend par défaut la valeur 0 (la copie se fait donc à partir du premier caractère de la basic_string.

Exemple 14-6. Copie de travail des données d'une basic_string

#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    string s="1234567890";
    // Copie la chaîne de caractères dans une zone de travail :
    char buffer[16];
    s.copy(buffer, s.size(), 0);
    buffer[s.size()] = 0;
    cout << buffer << endl;
    return 0;
}

La basic_string doit contenir suffisamment de caractères pour que la copie puisse se faire. Si ce n'est pas le cas, elle lancera une exception out_of_range. En revanche, la méthode copy ne peut faire aucune vérification quant à la place disponible dans la zone mémoire qui lui est passée en paramètre. Il est donc de la responsabilité du programmeur de s'assurer que cette zone est suffisamment grande pour accueillir tous les caractères qu'il demande.

La méthode copy permet d'obtenir la copie d'une sous-chaîne de la chaîne contenue dans la basic_string. Toutefois, si l'on veut stocker cette sous-chaîne dans une autre basic_string, il ne faut pas utiliser cette méthode. La méthode substr permet en effet d'effectuer ce travail directement. Cette méthode prend en premier paramètre le numéro du premier caractère à partir de la sous-chaîne à copier, ainsi que sa longueur. Comme pour la méthode copy, il faut que la basic_string source contienne suffisamment de caractères faute de quoi une exception out_of_range sera lancée.

Exemple 14-7. Extraction de sous-chaîne

#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    string s1 = "1234567890";
    string s2 = s1.substr(2, 5);
    cout << s2 << endl;
    return 0;
}

14.1.5.3. Insertion et suppression de caractères dans une chaîne

La classe basic_string dispose de tout un jeu de méthodes d'insertion de caractères ou de chaînes de caractères au sein d'une basic_string existante. Toutes ces méthodes sont des surcharges de la méthode insert. Ces surcharges prennent toutes un paramètre en première position qui indique l'endroit où l'insertion doit être faite. Ce paramètre peut être soit un numéro de caractère, indiqué par une valeur de type size_type, soit un itérateur de la basic_string dans laquelle l'insertion doit être faite. Les autres paramètres permettent de spécifier ce qui doit être inséré dans cette chaîne.

Les versions les plus simples de la méthode insert prennent en deuxième paramètre une autre basic_string ou une chaîne de caractères C classique. Leur contenu sera inséré à l'emplacement indiqué. Lorsque le deuxième paramètre est une basic_string, il est possible d'indiquer le numéro du premier caractère ainsi que le nombre de caractères total à insérer. De même, lors de l'insertion d'une chaîne C classique, il est possible d'indiquer le nombre de caractères de cette chaîne qui doivent être insérés.

Il existe aussi des méthodes insert permettant d'insérer un ou plusieurs caractères à un emplacement donné dans la chaîne de caractères. Ce caractère doit alors spécifié en deuxième paramètre, sauf si l'on veut insérer plusieurs caractères identiques dans la chaîne, auquel cas on doit indiquer le nombre de caractères à insérer et la valeur de ce caractère.

Enfin, il existe une version de la méthode insert qui prend en paramètre, en plus de l'itérateur spécifiant la position à partir de laquelle l'insertion doit être faite dans la basic_string, deux autres itérateurs d'un quelconque conteneur contenant des caractères. Utilisé avec des pointeurs de caractères, cet itérateur peut être utilisé pour insérer un morceau quelconque de chaîne de caractères C dans une basic_string.

Toutes ces méthodes renvoient généralement la basic_string sur laquelle ils travaillent, sauf les méthodes qui prennent en paramètre un itérateur. Ces méthodes supposent en effet que la manipulation de la chaîne de caractères se fait par l'intermédiaire de cet itérateur, et non par l'intermédiaire d'une référence sur la basic_string. Cependant, la méthode insert permettant d'insérer un caractère unique à un emplacement spécifié par un itérateur renvoie la valeur de l'itérateur référençant le caractère qui vient d'être inséré afin de permettre de récupérer ce nouvel itérateur pour les opérations ultérieures.

Exemple 14-8. Insertion de caractères dans une chaîne

#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    string s = "abef";
    // Insère un 'c' et un 'd' :
    s.insert(2, "cdze", 2);
    // Idem pour 'g' et 'h', mais avec une basic_string :
    string gh = "gh";
    s.insert(6, gh);
    cout << s << endl;
    return 0;
}

Il existe également trois surcharges de la méthode erase, dont le but est de supprimer des caractères dans une chaîne en décalant les caractères suivant les caractères supprimés pour remplir les positions ainsi libérées. La première méthode erase prend en premier paramètre la position du premier caractère et le nombre des caractères à supprimer. La deuxième méthode fonctionne de manière similaire, mais prend en paramètre l'itérateur de début et l'itérateur de fin de la sous-chaîne de caractères qui doit être supprimée. Enfin, la troisième version de erase permet de supprimer un unique caractère, dont la position est spécifiée encore une fois par un itérateur. Ces deux dernières méthodes renvoient l'itérateur référençant le caractère suivant le dernier caractère qui a été supprimé de la chaîne. S'il n'y avait pas de caractères après le dernier caractère effacé, l'itérateur de fin de chaîne est renvoyé.

Enfin, il existe une méthode dédiée pour l'effacement complet de la chaîne de caractères contenue dans une basic_string. Cette méthode est la méthode clear.

Exemple 14-9. Suppression de caractères dans une chaîne

#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    string s = "abcdeerrfgh";
    // Supprime la faute de frappe :
    s.erase(5,3);
    cout << s << endl;
    // Efface la chaîne de caractères complète :
    s.clear();
    if (s.empty()) cout << "Vide !" << endl;
    return 0;
}

14.1.5.4. Remplacements de caractères d'une chaîne

Comme pour l'insertion de chaînes de caractères, il existe tout un jeu de fonctions permettant d'effectuer un remplacement d'une partie de la chaîne de caractères stockée dans les basic_string par une autre chaîne de caractères. Ces méthodes sont nommées replace et sont tout à fait similaires dans le principe aux méthodes insert. Cependant, contrairement à celles-ci, les méthodes replace prennent un paramètre supplémentaire pour spécifier la longueur ou le caractère de fin de la sous-chaîne à remplacer. Ce paramètre doit être fourni juste après le premier paramètre, qui indique toujours le caractère de début de la sous-chaîne à remplacer. Il peut être de type size_type pour les versions de replace qui travaillent avec des indices, ou être un itérateur, pour les versions de replace qui travaillent avec des itérateurs. Les autres paramètres des fonctions replace permettent de décrire la chaîne de remplacement, et fonctionnent exactement comme les paramètres correspondants des fonctions insert.

Exemple 14-10. Remplacement d'une sous-chaîne dans une chaîne

#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    string s = "abcerfg";
    // Remplace le 'e' et le 'r' par un 'd' et un 'e' :
    s.replace(3, 2, "de");
    cout << s << endl;
    return 0;
}

Dans le même ordre d'idée que le remplacement, on trouvera la méthode swap de la classe basic_string, qui permet d'intervertir le contenu de deux chaînes de caractères. Cette méthode prend en paramètre une référence sur la deuxième chaîne de caractères, avec laquelle l'interversion doit être faite. La méthode swap pourra devra être utilisée de préférence pour réaliser les échanges de chaînes de caractères, car elle est optimisée et effectue en fait l'échange par référence. Elle permet donc d'éviter de faire une copie temporaire de la chaîne destination et d'écraser la chaîne source avec cette copie.

Exemple 14-11. Échange du contenu de deux chaînes de caractères

#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    string s1 = "abcd";
    string s2 = "1234";
    cout << "s1 = " << s1 << endl;
    cout << "s2 = " << s2 << endl;
    // Intervertit les deux chaînes :
    s1.swap(s2);
    cout << "s1 = " << s1 << endl;
    cout << "s2 = " << s2 << endl;
    return 0;
}

14.1.6. Comparaison de chaînes de caractères

La comparaison des basic_string se base sur la méthode compare, dont plusieurs surcharges existent afin de permettre des comparaisons diverses et variées. Les deux versions les plus simples de la méthode compare prennent en paramètre soit une autre basic_string, soit une chaîne de caractères C classique. Elles effectuent donc la comparaison de la basic_string sur laquelle elles s'appliquent avec ces chaînes. Elles utilisent pour cela la méthode eq de la classe des traits des caractères utilisés par la chaîne. Si les deux chaînes ne diffèrent que par leur taille, la chaîne la plus courte sera déclarée inférieure à la chaîne la plus longue.

Les deux autres méthodes compare permettent d'effectuer la comparaison de sous-chaînes de caractères entre elles. Elles prennent toutes les deux l'indice du caractère de début et l'indice du caractère de fin de la sous-chaîne de la basic_string sur laquelle elles sont appliquées, un troisième paramètre indiquant une autre chaîne de caractères, et des indices spécifiant la deuxième sous-chaîne dans cette chaîne. Si le troisième argument est une basic_string, il faut spécifier également l'indice de début et l'indice de fin de la sous-chaîne. En revanche, s'il s'agit d'une chaîne C classique, la deuxième sous-chaîne commence toujours au premier caractère de cette chaîne, et il ne faut spécifier que la longueur de cette sous-chaîne.

La valeur renvoyée par les méthodes compare est de type entier. Cet entier est nul si les deux chaînes sont strictement égales (et de même taille), négatif si la basic_string sur laquelle la méthode compare est appliquée est plus petite que la chaîne passée en argument, soit en taille, soit au sens de l'ordre lexicographique, et positif dans le cas contraire.

Exemple 14-12. Comparaisons de chaînes de caractères

#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    const char *c1 = "bcderefb";
    const char *c2 = "bcdetab";   // c2 > c1
    const char *c3 = "bcderefas"; // c3 < c1
    const char *c4 = "bcde";      // c4 < c1
    string s1 = c1;
    if (s1 < c2) cout << "c1 < c2" << endl;
    else cout << "c1 >= c2" << endl;
    if (s1.compare(c3)>0) cout << "c1 > c3" << endl;
    else cout << "c1 <= c3" << endl;
    if (s1.compare(0, string::npos, c1, 4)>0)
        cout << "c1 > c4" << endl;
    else cout << "c1 <= c4" << endl;
    return 0;
}

Bien entendu, les opérateurs de comparaison classiques sont également définis afin de permettre des comparaisons simples entre chaîne de caractères. Grâce à ces opérateurs, il est possible de manipuler les basic_string exactement comme les autres types ordonnés du langage. Plusieurs surcharge de ces opérateurs ont été définies et travaillent avec les différents types de données avec lesquels il est possible pour une basic_string de se comparer. L'emploi de ces opérateurs est naturel et ne pose pas de problèmes particuliers.

Note : Toutes ces comparaisons se basent sur l'ordre lexicographique du langage C. Autrement dit, les comparaisons entre chaînes de caractères ne tiennent pas compte de la locale et des conventions nationales. Elles sont donc très efficaces, mais ne pourront pas être utilisées pour comparer des chaînes de caractères humainement lisibles. Vous trouverez de plus amples renseignements sur la manière de prendre en compte les locales dans les comparaisons de chaînes de caractères dans le Chapitre 16.

14.1.7. Recherche dans les chaînes

Les opérations de recherche dans les chaînes de caractères constituent une des fonctionnalités des chaînes les plus courantes. Elles constituent la plupart des opérations d'analyse des chaînes, et sont souvent le pendant de la construction et la concaténation de chaînes. La classe basic_string fournit donc tout un ensemble de méthodes permettant d'effectuer des recherches de caractères ou de sous-chaînes dans une basic_string.

Les fonctions de recherche sont toutes surchargées afin de permettre de spécifier la position à partir de laquelle la recherche doit commencer d'une part, et le motif de caractère à rechercher. Le premier paramètre indique toujours quel est ce motif, que ce soit une autre basic_string, une chaîne de caractères C classique ou un simple caractère. Le deuxième paramètre est le numéro du caractère de la basic_string sur laquelle la méthode de recherche s'applique et à partir duquelle elle commence. Ce deuxième paramètre peut être utilisé pour effectuer plusieurs recherches successives, en repartant de la dernière position trouvée à chaque fois. Lors d'une première recherche ou lors d'une recherche unique, il n'est pas nécessaire de donner la valeur de ce paramètre, car les méthodes de recherche utilisent la valeur par défaut qui convient (soit le début de la chaîne, soit la fin, selon le sens de recherche utilisé par la méthode). Les paramètres suivants permettent de donner des informations complémentaires sur le motif à utiliser pour la recherche. Il n'est utilisé que lorsque le motif est une chaîne de caractères C classique. Dans ce cas, il est en effet possible de spécifier la longueur du motif dans cette chaîne.

Les différentes fonctions de recherche disponibles sont présentées dans le tableau suivant :

Tableau 14-1. Fonctions de recherche dans les chaînes de caractères

MéthodeDescription
find

Cette méthode permet de rechercher la sous-chaîne correspondant au motif passé en paramètre dans la basic_string sur laquelle elle est appliquée. Elle retourne l'indice de la première occurrence de ce motif dans la chaîne de caractères, ou la valeur npos si le motif n'y apparaît pas.

rfind

Cette méthode permet d'effectuer une recherche similaire à celle de la méthode find, mais en parcourant la chaîne de caractères en sens inverse. Notez bien que ce n'est pas le motif qui est inversé ici, mais le sens de parcours de la chaîne. Ainsi, la méthode rfind retourne l'indice de la dernière occurrence du motif dans la chaîne, ou la valeur npos si le motif n'a pas été trouvé.

find_first_of

Cette méthode permet de rechercher la première occurrence d'un des caractères présents dans le motif fourni en paramètre. Il ne s'agit donc plus d'une recherche de chaîne de caractères, mais de la recherche de tous les caractères d'un ensemble donné. La valeur retournée est l'indice du caractère trouvé, ou la valeur npos si aucun caractère du motif n'est détecté dans la chaîne.

find_last_of

Cette méthode est à la méthode find_first_of ce que rfind est à find. Elle effectue la recherche du dernier caractère de la basic_string qui se trouve dans la liste des caractères du motif fourni en paramètre. La valeur retournée est l'indice de ce caractère s'il existe, et npos sinon.

find_first_not_of

Cette méthode travaille en logique inverse par rapport à la méthode find_first_of. En effet, elle recherche le premier caractère de la basic_string qui n'est pas dans le motif fourni en paramètre. Elle renvoie l'indice de ce caractère, ou npos si celui-ci n'existe pas.

find_last_not_of

Cette méthode effectue le même travail que la méthode find_first_not_of, mais en parcourant la chaîne de caractères source en sens inverse. Elle détermine donc l'indice du premier caractère en partant de la fin qui ne se trouve pas dans le motif fourni en paramètre. Elle renvoie npos si aucun caractère ne correspond à ce critère.

Exemple 14-13. Recherches dans les chaînes de caractères

#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    string s = "Bonjour  tout    le monde !";
    // Recherche le mot "monde" :
    string::size_type pos = s.find("monde");
    cout << pos << endl;
    // Recherche le mot "tout" en commençant par la fin :
    pos = s.rfind("tout");
    cout << pos << endl;
    // Décompose la chaîne en mots :
    string::size_type debut = s.find_first_not_of(" \t\n");
    while (debut != string::npos)
    {
        // Recherche la fin du mot suivant :
        pos = s.find_first_of(" \t\n", debut);
        // Affiche le mot :
        if (pos != string::npos)
            cout << s.substr(debut, pos - debut) << endl;
        else
            cout << s.substr(debut) << endl;
        debut = s.find_first_not_of(" \t\n", pos);
    }
    return 0;
}

Note : Toutes ces fonctions de recherche utilisent l'ordre lexicographique du langage C pour effectuer leur travail. Elles peuvent donc ne pas convenir pour effectuer des recherches dans des chaînes de caractères saisies par des humains, car elles ne prennent pas en compte la locale et les paramètres nationaux de l'utilisateur. La raison de ce choix est essentiellement la recherche de l'efficacité dans la bibliothèque standard. Nous verrons dans le Chapitre 16 la manière de procéder pour prendre en compte les paramètres nationaux au niveau des chaînes de caractères.

14.1.8. Fonctions d'entrée / sortie des chaînes de caractères

Pour terminer ce tour d'horizon des chaînes de caractères, signalons que la bibliothèque standard C++ fournit des opérateurs permettant d'effectuer des écritures et des lectures sur les flux d'entrée / sortie. Les opérateurs '<<' et '>>' sont donc surchargés pour les basic_string, et permettent de manipuler celles-ci comme des types normaux. L'opérateur '<<' permet d'envoyer le contenu de la basic_string sur le flux de sortie standard. L'opérateur '>>' lit les données du flux d'entrée standard, et les affecte à la basic_string qu'il reçoit en paramètre. Il s'arrête dès qu'il rencontre le caractère de fin de fichier, un espace, ou que la taille maximale des basic_string a été atteinte (cas improbable) :

#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    string s1, s2;
    cin >> s1;
    cin >> s2;
    cout << "Premier mot : " << endl << s1 << endl;
    cout << "Deuxième mot : " << endl << s2 << endl;
    return 0;
}

Cependant, ces opérateurs peuvent ne pas s'avérer suffisants. En effet, l'une des principales difficultés dans les programmes qui manipulent des chaînes de caractères est de lire les données qui proviennent d'un flux d'entrée ligne par ligne. La notion de ligne n'est pas très claire, et dépend fortement de l'environnement d'exécution. La bibliothèque standard C++ suppose, quant à elle, que les lignes sont délimitées par un caractère spécial servant de marqueur spécial. Généralement, ce caractère est le caractère '\n', mais il est également possible d'utiliser d'autres séparateurs.

Pour simplifier les opérations de lecture de textes constitués de lignes, la bibliothèque fournit la fonction getline. Cette fonction prend en premier paramètre le flux d'entrée sur lequel elle doit lire la ligne, et la basic_string dans laquelle elle doit stocker cette ligne en deuxième paramètre. Le troisième paramètre permet d'indiquer le caractère séparateur de ligne. Ce paramètre est facultatif, car il dispose d'une valeur par défaut qui correspond au caractère de fin de ligne classique '\n'.

Exemple 14-14. Lecture de lignes sur le flux d'entrée

#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    string s1, s2;
    getline(cin, s1);
    getline(cin, s2);
    cout << "Première ligne : " << s1 << endl;
    cout << "Deuxième ligne : " << s2 << endl;
    return 0;
}