Home | News | Hacking | Sciences | Technology | Ti 92 | Programming | Free articles | Links | Webmaster

C/C++

Homepage / TS / Coding / C/C++

|- /Organisation Modulaire 

|- /Manipulation Syntaxique

|- /Algorithmes de la librairie standard

|- /C and the portability issue

|- /Debugging your Program

|- /Programming in C

|- /Advanced String Techniques in C++

|- /Using Inheritance and Virtual Function

|- /Debugging C and C++ Programs using GDB

|- /C++ Portability Guidelines

|- /A Tutorial on Pointers and Arrays in C

|- /Introduction to Object Oriented Programming using C++

 

Organisation Modulaire C++

Par François Chenebit

Nous allons expliquer pourquoi et comment découper un programmes en modules. Ce découpage ne s'illustre pas par un code car il repose d'avantage sur une utilisation judicieuses du système d'exploitation et du compilateur.

Le but

Tant qu'un programme répond simplement à un objectif simple, il conserve une talle réduite. Mais dès lors qu'on lui ajoute des fonctionnalités, son nombre de classes commence à augmenter. Il devient alors impossible de gérer l'ensemble des classes comme un tout. C'est comme manger un poulet en entier. Le programmeur doit alors répartir son programme en modules. Il commencera par l'aile à moins qu'il ne préfère la cuisse.
Le fait de séparer le programme en modules a plusieurs avantages. En tout premier lieu, cela permet à plusieurs programmeurs de collaborer. Si chacun se consacre à une partie du code, leur travail ne sera pas influencé par celui des autres. Chacun pourra avancer à son rythme, sans attendre que quelqu'un d'autre ait fini une classe pour continuer. Ensuite les classes crées se révèlent plus cohérentes. Il est plus facile pour nos esprits de réfléchir sur un petit nombre d'éléments que sur un nombre plus important. Quand le nombres de classes demeurent réduit, les erreurs de modélisation apparaissent plus clairement et le code produit apparaît plus clairement. Notons également que les modules s'écrivent plus vite. En effet le découpage en module permet de procéder par étapes. On évite ainsi d'incessants retour en arrière provoqués par une mauvaise analyse. Autre constat, le code produit contient moins d'erreurs. Il est plus facile de construire des tests unitaires, qui vont garantir le bon fonctionnement d'un module, que celui d'un programme plus complexe, dont les tests sont plus longs et en général bâclés. Dans certains cas, le code produit est plus efficace, si le système d'exploitation permet de charger les modules séparément. En revanche, le découpage en modules présente aussi des inconvénients. Pour commencer, un code composé de modules est en général réparti dans plusieurs répertoires. On aura donc plus de mal à le compiler qu'un code présent à un endroit unique. Par ailleurs des erreurs peuvent survenir au moment d'intégrer ensemble ces différents modules. Elles proviennent en général des incompréhensions entre différents programmeurs. Elles sont aussi dues au fait qu'un module est mal décrit et a un comportement différent de celui que l'on attend. De plus, des erreurs peuvent survenir au moment de l'exécution ou de la compilation, à cause de la nature même des modules.
Le découpage en modules, implique une réflexion préalable supplémentaire sur le découpage, puis un travail supplémentaire A noter qu'un programme composé de modules reste plus difficile à comprendre pour quelqu'un de l'extérieur. Plus pernicieux, cet inconvénient apparaît tard, quand le programme a besoin d'évoluer ou de se voir corrigé. Les avantages ne sont vraiment perceptible qu'à partir d'un certain nombre de place (en général une trentaine). En revanche, les inconvénients interviennent dès que l'on décide de découper le programme en modules. C'est pourquoi il ne faut découper son programme en module qu'à partir d'une certaine taille.

Le fonctionnement

Si le programmes doit être découper en modules, il convient de choisir ceux-ci. Normalement une phase de spécification préalable a permis de mettre en évidence les grandes fonctionnalités. En général, elles vont former les différents modules. Toutefois, ce n'est pas toujours si simple. Dans notre exemple, les 2 fonctionnalités sont la lecture du fichier scénario et la gestion de la partie. Cependant, ses fonctions sont effectuées par les mêmes classes et ne peuvent pas être séparées. En revanche, la prochaine fonction, qui va permettre de résoudre des actions plus complexes à l'aide de formules conditionnelles, va utiliser des classes de lecture et d'interprétation de ces formules. Elles vont probablement former un modules indépendant du premier. Le schéma des classes indique lorsque l'on peut ou non séparer facilement des classes, en mettant en évidences les liens qui existent entre elles.
En C++, le découpage d'un programme peut se faire de 2 façons complémentaires. La première se résume à répartir les différents fichiers qui contiennent les classes du programme entre plusieurs répertoires. La seconde consiste à créer des modules de code qui seront chargés séparément en mémoire par le système d'exploitation.
Pour répartir les classes entre plusieurs répertoires, il faut bien comprendre la manière dont fonctionne les compilateurs C++. Dans un premier temps, le compilateur transforme les fichiers contenant des classes (en général il porte l'extension *.cpp), en fichiers objets (*.o). Durant cette étape, le compilateur vérifie que tous les symboles utilisés soient définis. C'est pourquoi il faut déclarer les classes exploitées à l'intérieur de la classe en cours d'exploitation. Pour cela les classes sont déclarées dans des fichiers d'en-tête (*.h), inclus là où ces classes sont utilisées. Ensuite, les fichiers objets se voient liés entre eux. Dans cette étape, tous les symboles sont remplacés par leur adresse dans le code généré. Pour cela, le linker doit pouvoir accéder à tous les fichiers objets. Ce système impose de séparer en 3 répertoires les fichiers d'en-tête, de code et les fichiers objets. De plus, il est toujours judicieux de tester le programme module par module. Le code utilisé pour tester un module doit donc être crée et le module en lui-même semble le meilleur endroit pour le situer. ensuite, il y a moyen de compiler et de linker son code de plusieurs manières différentes. En général nous en retenons 2 : une avec les options de debug et une sans que nous appelons "livraison". Enfin, il est aussi judicieux de placer dans le module la documentation qui s'y rapporte.

/programme
 makefile
 /module1
 /module2
 /test
 /documentation_generale
/module1
 makefile
  /h
   classe1.h
   classe2.h
  /cpp
   classe1.cpp
   classe2.cpp
 /test
   mainTest1.c
 /obj_test
   mainTest1.o
  /obj_debug
    classe1.o
    classe2.o
 /obj_livraison
   classe1.o
   classe2.o
  /documentation
/module2
 makefile
  /h
   classe3.h
   classe4.h
  /cpp
   classe3.cpp
   classe4.cpp
   main.c
 /test
   mainTest1.c
   mainTest2.c
   mainTest3.c
 /obj_test
   mainTest1.o
  /obj_debug
    classe3.o
    classe4.o
    programme.exe
 /obj_livraison
   classe3.o
   classe4.o
   programme.exe
  /documentation

Dans cet exemple, le programme se scinde en 2 modules. Le second contient le main du programme et une panoplie de tes plus complète. De plus, chaque module contient un makefile. Ce fichier doit permettre par un jeu d'options de compiler un programme de test, ainsi que les fichiers objets de debug ou de livraison. En outre, il faut qu'ils aient la capacité d'accéder au fichier en-tête ou objet de l'autre module le cas échéant.
Dans cet exemple, le programme exécutable se constitue d'un seul bloc. Il est possible sous certains systèmes de faire en sorte qu'il soit chargé en mémoire sous la forme de plusieurs blocs. Ainsi, lorsque plusieurs programmes exploitent le même module, celui-ci ne sera chargé qu'une seule fois en mémoire. On parle alors de bibliothèque plutôt que de modules. Sous Unix, elle portent l'extension *.so. Toutefois, si les programmes utilisant une bibliothèque ne sont pas reliés une fois la bibliothèque modifiée, des erreurs apparaîtront à l'exécution qui risquent d'être particulièrement difficiles à diagnostiquer.

Manipulation Syntaxique C++

Référence > C++ (ed. Micro Application)

Notre sujet concerne 2 sujets de la STL : les strings et les streams. Les premiers permettent de manipuler un ensemble de caractères comme une classe à part entière, et non plus comme un tableau de "char". Les seconds remplacent le bibliothèques d'entrées/sorties, en implémentant le concept de flux.

Les Strings

Les strings furent développés pour effectuer une tâche courante à l'intérieur des programmes : le traitement de texte. La solution adoptée par le C - un tableau de char terminé par un 0 - ne s'avérait guère satisfaisante. Elle laisse à la charge du programmeur les opérations de gestion de la mémoire et rend complexe l'affectation, ainsi que la comparaison avec des chaînes littérales. De plus, les chaînes de caractères en C se trouvaient liées au jeu de caractères ASCII. Le C++, pour sa part, utilise un template appelé basic_string. Celui-ci procure la capacité de concevoir des classes reposant sur un jeu de caractères propre. La classe string désigne une implémentation de ce template avec le jeu de symboles ASCII étendu.
Il existe différentes solutions pour élaborer un string : utiliser une chaîne littérale, créer une chaîne vide, recopier une chaîne avec l'opérateur d'affectation ou le constructeur de copie... Vous pouvez aussi utiliser la méthode substr d'une chaîne pour en engendrer une autre. Notez que cette méthode prend en paramètre la position d'origine et la longueur copiée, et non l'origine et la fin. En revanche, il reste impossible de faire apparaître une chaîne à l'aide d'un char. Les manipulations de ces chaînes s'effectuent grâce à des méthodes uniformisées. Elles ne sont pas séparées en blocs de styles différents comme les fonctions C. En outre, une chaîne adapte sa taille en fonction des actions de l'utilisateur, qui n'a plus à gérer la mémoire associée. On se trouve également en mesure de recourir à des opérateurs d'addition sur les chaînes de manière à effectuer des concaténations. Pour accéder à un élément à l'intérieur d'une chaîne, il existe des méthodes de recherche qui commence par le mot find. De surcroît, on a l'opportunité d'accéder à un char avec sa position grâce à l'opérateur [] ou à la méthode at. La seconde solution comporte l'avantage de retourner une exception si la position recherchée se situe en dehors de la chaîne. Avec [], le programme est interrompu.

#include <string>
#include <iostream>
using namespace std;
void main(){
 string s2 = "world";
 string s1;
 s1 = "hello ";
 string s3 = s2;
 string s4("chaine 4");
 string s5(s4);
 //string s6('a'); ne fonctionne pas avec un char
 //string s6(43); ne fonctionne pas avec un code ASCII

 string s6('a');
 string s7 = s4.substr(2,2);// s7 vaut "ai"
 s7.append("4");
 s7.append(s3); // inutile de gérer l'augmentation de la mémoire1
 char c1 = s7[5];
 char c2 = s7.at(5); // c1 = c2
 cout << s1 + s2 + s3 + s4 + s5 + s6 + s7 << c1 << c2 << endl
}

>hello worldworldchaine 4chaine 4aai4worldrr

Les Streams

En C les entrées/sorties formatées se font via la famille de fonctions printf / scanf de la bibliothèque standard, qui prennent comme paramètre une spécification de format :

#include <stdio.h>
int i;
float f;
...
printf("la valeur de i est %d, celle de f est %f", i, f);

Un inconvénient de ces fonctions est que si on modifie le type d'une variable, il faut également modifier la spécification de format. Par ailleurs ce schema n'est pas extensible aux struct.

Les iostream de C++ permettent d'effectuer des entrées/sorties formatées sans avoir à spécifier de format, grâce aux opérateurs d'insertion << et d'extraction >>. On peut insérer sur un ostream (écriture) et extraire d'un istream (lecture). C++ prédéfinit deux ostream, cout et cerr, qui correspondent respectivement à stdout et stderr de C, et un istream , cin, qui correspond à stdin. Ils s'utilisent comme suit :

cout << "entrez un nombre : ";
int i;
cin >> i;
cout << "vous avez saisi la valeur " << i << endl;

Ce mécanisme est extensible aux struct : il suffit de définir les fonctions appropriées. Celles-ci ont pour nom operator << pour l'insertion, et operator >> pour l'extraction. Ce sont des fonctions libres.

#include <iostream>
#include <cmath> /* hypot */
struct Pt { int x, y; };
ostream & operator << (ostream & os, Pt const & p) { // opérateur d'insertion pour les Pt
    return cout << "[" << p.x << "," << p.y << "]";
}
int main()
    Pt p; p.x = 3; p.y = 4;
    cout << "le point " << p << " est à la distance "
         << hypot(p.x, p.y) << " de l'origine" << endl;
    // génère la ligne suivante sur la sortie standard :
    // le point [2,3] est à la distance 5 de l'origine

}

Cette uniformité de traitement de tous les types vis à vis de l'insertion ou de l'extraction s'avère très pratique à l'usage. En particulier, elle permet l'écriture de macros comme celle ci :

#define SHOW(X) cout << #X << " = " << (X) << endl
    ...
    int a = 2, b = 3;
    Pt p; p.x = 1; p.y = 2;
    SHOW(a);    // génère la ligne a = 2 sur la sortie standard
    SHOW(a+b);  // génère la ligne a+b = 5 sur la sortie standard
    SHOW(p);    // génère la ligne p = [1,2]sur la sortie standard

Par ailleurs, il existe différents types d'iostream : les fstream qui travaillent sur des fichiers, les strstream qui travaillent sur des chaînes de caractères, etc. Chacun peut se définir ses propres variantes d'iostream, soit en variant la source ou la destination des caractères (fichiers, chaînes de caractères, sockets...) soit en variant le comportement (par exemple impression préfixée par un numéro de ligne, ou lecture sautant les commentaires). Les opérateurs << et >> fonctionneront avec tous les types d'iostream.

De plus ce principe autorise une manipulation uniforme des données issues ou à destination de l'entrée/sortie standard, d'un fichier ou d'une chaîne. Il connaît toutefois des limites, car la concept d'entrée/sortie standard n'a pas de sens avec un bureau graphique
Signalons par ailleurs qu'il existe 2 sortes de flux : ceux d'entrée et ceux de sortie. Certains combinent ces 2 genres mais c'est plus rares. D'autre part, les streams se manipulent grâce à des opérateurs surchargés : >> pour extraire un flux d'entrée et << pour envoyer vers un flux de sortie. Par conséquent on peut associer plusieurs manipulations sur une seule ligne. Ces opérateurs surchargés donne l'opportunité d'écrire ou de lire des objets propres pour chaque applications. Il faut noter que l'opérateur ne lit pas les espaces (au sens C, cad espace, tabulation, retour chariot, saut de page...), et va rechercher le premier objet à lire.
En dehors de ces opérateurs, il existe un autre moyen d'accéder à un flux d'entrée : les méthodes get, getline, read, peek et ignore. Elles confèrent la capacité d'accéder au flux de caractère, afin d'effectuer des contrôles de plis bas niveau, comme pour analyser mot à mot l'entrée. La méthode get, pour sa part, lit un caractère ou un nombre défini de caractères suivant l'appel (grâce à la surcharge). Cette lecture s'arrête lorsqu'elle rencontre un délimiteur, par défaut, la fin de ligne. La méthode getline, quant à elle, procède de la même façon, mais en outre, elle ajoute le délimiteur aux caractères lus. De son côté, la méthode read lit un nombre défini de caractères. La méthode peek, enfin, permet de tester la valeur du caractère à lire, tandis que la méthode ignore extrait des caractères du stream sans en faire usage. Ces méthodes favorisent donc une manipulation des streams identique à celle des librairies C.
Le formatage des données en sortie, quant à elle, représente une autre fonctionnalité des streams. Elle procure aux streams Elle procure aux streams les même capacités que les fonctions printf. Dans une telle perspective, il existe 2 solutions : les méthodes de manipulations et les manipulators. Nous nous intéressons uniquement à ces derniers. Les manipulators sont simplement envoyé à l'ostream comme n'importe quel objet. Le plus facile d'emploi - endl - effectue un saut de ligne. D'autres manipulators comparables, sans arguments, donnent le moyen de formater la sortie. Par exemple dec, précise que la base d'affichage des nombres est décimale. De plus, on trouve également des manipulators avec arguments. Ceux-ci figurent dans le headers iomanip. Par exemple, setprecision() met à n le nombre de chiffres minimum à afficher. Signalons en outre qu'il demeure concevable de créer ses propres manipulators, mais nous ne traitons pas de cet aspect ici.

#include <iostream>
#include <ios>
#include <iomanip>
using namespace std;
void main(){
 int i = 15;
 double f = 14.589
 cout << i << " " << f << endl;
 // 15 14.589
 // manipulator pour changer la base

 cout << hex << i << " " << oct << i << " " << dec << i << endl;
 // f 17 15
 // autre possibilité avec un manipulator à paramètres

 cout << setbase(16) << i << endl;
 // f
 // manipulator pour afficher la base et ma lettre hexa en majuscule

 cout << showbase << uppercase << hex << i << " " << oct << i << " " << dec << i << endl;
 // 0XF 017 15
 // afficher le signe pour les valeurs positives

 cout << showpos << i << endl;
 // + 15
 // afficher un nombre minimum char et spécifier le char de remplissage

 cout << setw(10) << setfill('0') << i << endl;
 // 0000000+15
 // NB : impossible de spécifier un nombre maximum de char
 // modifier la précision des données
 cout << setprecision(5) << f << " " << setprecision(3) << f

Algorithmes de la librairie standard

Par François Chenebit
Références >
Bruce Eckel ; Cetus

Un conteneur ne présente pas, en lui-même, un grand intérêt. Il faut pouvoir lui appliquer des opérations afin de pouvoir l'ordonner, d'accéder à ses éléments... Les algorithmes interviennent alors : il permettent d'effectuer simplement des opérations classiques, quel que soit son type ou la classe qu'il contient. Leur but consiste à se transformer en nouvelle fonctionnalités du C++, et de rendre leur emploi aussi naturel que celui des fonctions d'entrées/sorties du C, comme printf. Cet article ne présente pas un inventaire exhaustif des différents algorithmes standards car il nous faudrait un livre entier pour faire le tour de la question. Il se contente d'introduire les différents principes qui s'avèrent nécessaires à la manipulation et de proposer une brève classification.

Utilisation d'un algorithme standard

Le principe d'un algorithme standard consiste à effectuer un traitement sur un conteneur. Celui-ci, afin de se montrer le plus paramétrable possible, est appelé avec une fonctionne paramètre. Par exemple, l'algorithme for_each applique à tous les éléments d'une séquence la fonction qu'il reçoit en argument. En effet, si il reçoit en paramètre une fonction qui imprime un entier, il accomplira cette tâche pour tout ceux d'un vecteur. Ainsi les algorithmes génériques se voient développés sans qu'il faille se préoccuper du type de données.

Les objets fonctions

Il existe 2 manières de passer des fonctions en paramètre d'un algorithme standard. Le première est héritée du C. Nous avons affaire à des pointeurs de fonctions. La seconde se montre plus conforme au paradigme objet : il s'agit donc des objets fonctions. Ceux-ci consIl existe 2 manières de passer des fonctions en paramètre d'un algorithme standard. Le première est héritée du C. Nous avons affaire à des pointeurs de fonctions. La seconde se montre plus conforme au paradigme objet : il s'agit donc des objets fonctions. Ceux-ci constituent des instanciations des classes pour lesquelles l'opérateur () se montre surchargé.

class ImprimerCarre{
public:
 void operator() (int& i)
{cout << "carre de " << i << "= " << i*i << endl ;}
};

Un objet fonction, créé à partir de cette classe, permet d'afficher le carré d'un entier qu'il reçoit en paramètre.

void main()
{
 vector<int> vint;
 vector<int>::iterator it;
 int i;
 for(i=0; i<5;i++)
 {
  vint.push_back(i);
 }
 for(it=vint.begin();it !=vint.end();it++)
 {cout << *it << endl; }

 for_each(vint.begin(),vint.end(),ImprimerCarre());
}

Comme vous le voyez, les objets fonctions ressemblent aux routines callback, parfois mises à contribution dans le C. Ceux-ci se classent par leur nombre de paramètres et par le renvoi d'un booléen ou de tout autre type de données. De son côté, un générateur ne prend aucun paramètre et retourne une donnée du type spécifié. Les objets fonctions unitaire et binaire, lorsqu'il renvoient de surcroît un booléen. Par ailleurs, nous qualifions également les classes qui font office de paramètres des fonctions objets. Elles sont comparables, assignables ou ordonnables si elles possèdent respectivement un opérateur "égal", "affectation" ou "inférieur à". De plus, nous avons tout loisir de manipuler les objets fonctions et de les combiner entre eux. Il existe notamment un template binder qui autorise la transformation d'un objet fonction binaire en un objet fonction unitaire. Par exemple, l'objet fonction "plus" est binaire : il inclut ses 2 paramètres. Pour ajouter toujours le même nombre, on le binde, afin que son second paramètre ait toujours la même valeur.

#include <vector>
#include <algorithm>
#include <iostream>
#include <functional>
using namespace std;
void main()
{
 vector<int> vint,vint2,vres,vres2;
 vector<int>::iterator it;
 vector<bool> vboolres;
 vector<bool>::iterator itbool;
 int i;
 for(i=0, i<10;i++)
 {
  vint.push_back(i);
 }
 // plus : objet fonction binaire qui ajoute ses 2 paramètres
 // il est bindé pour en faire un objet fonction unitaire, qui incrémente 10
 // il est utilisé avec transform car for_each ne modifie pas ses paramètres


transform(vint.begin(),vint.end(),back_inserter(vres),bind2nd(plus<int>(),10));
 for(it=vres.begin();it !=vres.end();it++)
 {cout << *it << " "; }
 cout << endl
 // equal_to : predicat binaire qui teste si ces 2 paramètres sont égaux
 // il renvoit true si oui, et false sinon
 // il est utilise avec une autre forme de transform

 vint2.paush_back(0);
 vint2.paush_back(1);
 for(i=2; i<10;i++)
 {
  vint2.paush_back(i+2);
 }
 // ainsi seuls les 1 premiers entiers sont égaux

transform(vint.begin(),vint.end(),vint2.begin(),back_inserter(vboolres),equal_to<int>());
 for(itbool=vboolres.begin();itbool !=vboolres.end();itbool++)
 {cout << *itbool << " "; }
 cout << endl
 // negat : objet fonction unitaire qui renvoit l'inverse d'un paramètre

transform(vint.begin(),vint.end(),back_inserter(vres2),negate<int>());
 for(it=vres2.begin();it !=vres2.end();it++)
 {cout << *it << " "; }
}

Enfin, comme nous l'avons évoqué en introduction, nous avons l'opportunité d'employer à la place d'un objet fonction un pointeur sur une fonction, comme en C. Rappelons à ce propos que le nom d'une fonction est considéré comme un pointeur de fonction.

#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
void ImprimeCarre(int& i)
{cout << "carre de " << i << " = " << i*i << endl ;}
void main()
{
 vector<int> vint
 vector<int>::iterator it;
 int i;
 for(i=0, i<5;i++)
 {
  vint.push_back(i);
 }
 for(it=vint.begin();it !=vint.end();it++)
 {cout << *it << endl; }

for_each(vint.begin(),vint.end(),ImprimeCarre);
}

Récapitulatif des algorithmes standards

A l'aide des objets fonctions, vous vous trouvez en mesure d'utiliser les algorithmes standards comme vous le souhaitez. Vous pouvez avoir recours à fill et generate afin d'initialiser des séquences. count fera le compte des élément égaux à une valeur donnée. Il faut donc que la classe contenue soit comparable. De plus, count_if fonctionne d'une manière similaire, avec un prédicat unitaire à la place d'une valeur. copy, pour sa part, procède à la copie d'une séquence vers une cible, tandis que d'autres algorithmes comme permutation ou swap_range favorisent des manipulations plus complexes. Par ailleurs, find vous offre la capacité de rechercher un élément égal à une valeur, et find_if, celui qu'un prédicat unitaire vérifiera. min_element et max_element quant à eux cherchent le plus petit et le plus grand élément d'une séquence. De surcroît, equal vous donne le moyen de savoir si 2 séquences sont identiques et mismatch indique le point à partir duquel elles diffèrent. Pour supprimer des éléments, il faut faire confiance à remove, qui ordonne une séquence en les plaçant à la fin et renvoie un itérateur sur le premier d'entre eux. Grâce à celui-ci, nous pouvons utiliser erase : celui-ci supprime les éléments entre 2 itérateurs. Signalons au passage que vous avez le moyen de trier n'importe quelle séquence à l'aide de la commande sort. Les listes, quant à elle, possède une méthode sort certainement plus efficace. Une fois la séquence triée, nous nous voyons à même de recourir aux algorithmes binary_search, lower_bound et upper_bound pour trouver l'élément le plus petit et le plus grand de la séquence. Il demeure aussi envisageable de copier 2 séquences triées dans un 3ème, qui se verra également ordonnancée par merge. Les opérations sur les ensembles sont également concevables sur les séquences triées : vérifiez si une séquence constitue le sous-ensemble d'une autre avec Include et créez l'union des 2 séquences avec set_union, la différence avec set_difference, ainsi que le comportement avec set_symetric. Comme nous l'avons vu, l'application d'un traitement à tous les éléments d'une séquences grâce à for_each s'avère réalisable. L'algorithme transform pour sa part effectue un travail similaire et redirige en outre le résultat vers une sortie.
Tous ces algorithmes se révèlent accessibles grâce au header algorithm. Il existe un autre header à l'intérieur de la STL - numeric - qui concerne des séquences de nombres, c'est-à-dire d'objets comportant une définition des opérateurs + - / et *. Avec accumulate, vous pouvez effectuer la somme des éléments d'une séquence, mais vous opérerez le produit vectoriel de 2 séquences à l'aide d'inner_product. Vous trouverez enfin d'autres algorithmes utilisés par la STL dans les headers utility et iterators que nous n'examinerons pas ici.


depuis le 30/09/2003