La STL (Standard Template Library) est un ensemble de structures de données et d'algorithmes génériques développé à l'origine par les laboratoires Hewlett-Packard. Elle se compose de six composants principaux :
- Conteneurs : structures destinées à stocker des données. On distingue les conteneurs séquentiels (vector, deque, list) où l'ordre des éléments dépend de la séquence d'insertion, et les conteneurs associatifs (set, map) organisés sous forme d'arbres binaires avec une clé d'indexation.
- Algorithmes : fonctions de manipulation de données, divisées en algorithmes modificateurs (copie, suppression, remplacement) et non-moidficateurs (recherche, parcours, extrêmes).
- Itérateurs : ponts entre conteneurs et algorithmes. Ils existent en plusiuers catégories : entrée (lecture seule), sortie (écriture seule), avant (lecture/écriture + avancement), bidirectionnel (avance + recul), et à accès aléatoire (le plus puissant).
- Foncteurs : objets-fonctions fournissant des stratégies aux algorithmes.
- Adaptateurs : offrent des interfaces supplémentaires aux algorithmes.
- Allocateurs : gèrent l'allocation mémoire pour les conteneurs et algorithmes.
La classe string
Le type string encapsule une chaîne de caractères dynamique.
Construction
void demoConstruction()
{
std::string chaineVide;
std::cout << "Vide : [" << chaineVide << "]" << std::endl;
std::string message("Bonjour");
std::cout << "Message : " << message << std::endl;
std::string copie = message;
std::cout << "Copie : " << copie << std::endl;
std::string repetee(4, 'z');
std::cout << "Répétée : " << repetee << std::endl;
}
Affectation et assignation
void demoAffectation()
{
std::string texte;
texte = "Dupont";
std::cout << texte << std::endl;
std::string autre;
autre = texte;
std::cout << autre << std::endl;
std::string init;
init.assign("programmation", 7); // 7 premiers caractères
std::cout << init << std::endl;
std::string extrait;
extrait.assign(std::string("abcdef"), 2, 3); // à partir de l'index 2, 3 caractères
std::cout << extrait << std::endl;
}
Taille et accès aux caractères
void demoAcces()
{
std::string mot = "salut";
std::cout << "Taille : " << mot.size() << ", Longueur : " << mot.length() << std::endl;
// [] ne lève pas d'exception si hors limites
std::cout << mot[2] << std::endl;
// at() lève out_of_range en cas de dépassement
std::cout << mot.at(1) << std::endl;
}
Concaténation
void demoConcatenation()
{
std::string phrase = "Hello";
phrase += " World";
phrase += '!';
std::cout << phrase << std::endl;
std::string suite;
suite.append("C++");
suite.append(" est puissant", 3); // " es"
suite.append(std::string("langage"), 0, 2); // "la"
std::cout << suite << std::endl;
}
Recherche et remplacement
void demoRecherche()
{
std::string contenu = "abc123abc456";
int premier = contenu.find('a');
std::cout << "Premier 'a' : " << premier << std::endl;
int dernier = contenu.rfind("abc");
std::cout << "Dernier 'abc' : " << dernier << std::endl;
// Retourne npos (équivalent à -1) si introuvable
int introuvable = contenu.find("xyz");
std::cout << "Introuvable : " << (introuvable == std::string::npos) << std::endl;
contenu.replace(0, 3, "XYZ");
std::cout << contenu << std::endl;
}
Comparaison, extraction, insertion et suppression
void demoManipulations()
{
std::string a = "pomme";
std::string b = "poire";
// compare : > retourne 1, < retourne -1, == retourne 0
std::cout << a.compare(b) << std::endl;
std::string fichier = "document.txt";
std::string ext = fichier.substr(8, 3);
std::cout << "Extension : " << ext << std::endl;
std::string base = "12";
base.insert(1, "abcd");
std::cout << base << std::endl;
base.erase(1, 4);
std::cout << base << std::endl;
}
Conversion avec char*
void demoConversion()
{
std::string chaine = "test";
const char* ptr = chaine.c_str();
char* brut = (char*)"donnee";
std::string depuisChar(brut);
}
Le conteneur vector
Le vector est un tableau dynamique unidirectionnel à mémoire contiguë. Il propose un itérateur à accès aléatoire et double sa capacité lors d'un redimensionnement. Sa structure sous-jacente est un tableau.
Construction et parcours
void demoVectorConstruct()
{
std::vector<int> nombres;
nombres.push_back(10);
nombres.push_back(50);
nombres.push_back(30);
// Parcours avec itérateur
for (auto curseur = nombres.begin(); curseur != nombres.end(); ++curseur)
{
std::cout << *curseur << std::endl;
}
// Construction par intervalle [début, fin)
std::vector<int> sousEnsemble(nombres.begin(), nombres.begin() + 2);
// Construction avec valeur répétée
std::vector<int> rempli(8, 42);
}
Affectation, insertion et suppression
void demoVectorOps()
{
std::vector<int> source = {2, 4, 6, 8, 10};
std::vector<int> dest;
dest.assign(source.begin(), source.begin() + 3);
dest = source; // opérateur =
std::vector<int> echange = {1, 3, 5};
dest.swap(echange);
// Insertion
dest.insert(dest.begin(), 2, 0);
// Suppression
dest.pop_back();
dest.erase(dest.begin() + 1, dest.begin() + 3);
dest.erase(dest.begin());
dest.clear();
}
Accès et gestion de la taille
void demoVectorTaille()
{
std::vector<int> donnees = {7, 14, 21, 28, 35};
std::cout << "at(2) : " << donnees.at(2) << std::endl; // lance out_of_range si hors limites
std::cout << "[1] : " << donnees[1] << std::endl; // comportement indéfini si hors limites
std::cout << "Premier : " << donnees.front() << std::endl;
std::cout << "Dernier : " << donnees.back() << std::endl;
std::cout << "Taille : " << donnees.size() << std::endl;
std::cout << "Capacité : " << donnees.capacity() << std::endl;
donnees.reserve(20); // pré-alloue de l'espace
std::cout << "Capacité après reserve : " << donnees.capacity() << std::endl;
// Technique pour réduire la capacité : swap avec un vecteur temporaire
std::vector<int>(donnees).swap(donnees);
std::cout << "Capacité après shrink : " << donnees.capacity() << std::endl;
}
Types personnalisés et imbrication
class Utilisateur
{
public:
std::string nom;
Utilisateur(const std::string& n) : nom(n) {}
};
void demoVectorPersonnalise()
{
std::vector<Utilisateur> liste;
liste.emplace_back("Alice");
liste.emplace_back("Bob");
liste.emplace_back("Charlie");
for (const auto& u : liste)
{
std::cout << u.nom << std::endl;
}
}
void demoVectorImbrique()
{
std::vector<std::vector<int>> grille;
grille.push_back({1, 2, 3});
grille.push_back({10, 20, 30});
grille.push_back({100, 200, 300});
for (const auto& ligne : grille)
{
for (int val : ligne)
{
std::cout << val << " ";
}
std::cout << std::endl;
}
}
Le contaneur deque
Le deque (double-ended queue) est un espace linéaire bidirectionnel permettant des insertions et suppressions en O(1) aux deux extrémités. Contrairement au vector, il n'a pas de notion de capacité car il est composé de segments de mémoire continue reliés entre eux.
void demoDeque()
{
std::deque<int> fileDouble;
fileDouble.push_back(5);
fileDouble.push_front(1);
fileDouble.push_back(10);
fileDouble.push_front(0);
// Contenu : 0, 1, 5, 10
std::cout << "Tête : " << fileDouble.front() << std::endl;
std::cout << "Queue : " << fileDouble.back() << std::endl;
fileDouble.pop_front();
fileDouble.pop_back();
// Accès indexé
std::cout << fileDouble[0] << std::endl;
std::cout << fileDouble.at(0) << std::endl;
// Insertion et suppression
fileDouble.insert(fileDouble.begin(), 2, 42);
fileDouble.erase(fileDouble.begin());
fileDouble.clear();
}
Le conteneur stack
La pile (stack) fonctionne selon le principe LIFO (dernier entré, premier sorti). Elle ne possède pas d'itérateur et n'expose qu'une seule ouverture.
#include <stack>
void demoStack()
{
std::stack<int> pile;
pile.push(100);
pile.push(200);
pile.push(300);
while (!pile.empty())
{
std::cout << pile.top() << std::endl;
pile.pop();
}
// Affiche : 300, 200, 100
}
Le conteneur queue
La file (queue) fonctionne selon le principe FIFO (premier entré, premier sorti). L'entrée se fait à l'arrière et la sortie à l'avant. Elle ne possède pas non plus d'itérateur.
#include <queue>
void demoQueue()
{
std::queue<int> file;
file.push(1);
file.push(2);
file.push(3);
file.push(4);
while (!file.empty())
{
std::cout << file.front() << std::endl;
file.pop();
}
// Affiche : 1, 2, 3, 4
}
Le conteneur list
La list est une liste doublement chaînée à stockage discontinu. Elle fournit un itérateur bidirectionnel (incrément et décrément, mais pas d'addition/soustraction directe). Les éléments peuvent être dupliqués.
#include <list>
void demoList()
{
std::list<int> liste;
liste.push_back(30);
liste.push_back(10);
liste.push_back(50);
liste.push_back(20);
liste.push_front(5);
liste.pop_back();
liste.pop_front();
// Tri et inversion
liste.sort();
liste.reverse();
for (int val : liste)
{
std::cout << val << std::endl;
}
// Suppression par valeur
liste.remove(30);
}
Les conteneurs set et multiset
Le set stocke des éléments uniques triés automatiquement par valeur clé. La clé et la valeur sont identiques. L'itérateur est en lecture seule : il est impossible de modifier une clé après insertion. La structure sous-jacente est un arbre rouge-noir.
Le multiset est identique au set, mais autorise les doublons.
#include <set>
void demoSet()
{
std::set<int> ensemble;
ensemble.insert(5);
ensemble.insert(2);
ensemble.insert(8);
ensemble.insert(2); // ignoré, doublon
for (auto val : ensemble)
{
std::cout << val << std::endl;
}
// Affiche : 2, 5, 8
// Recherche
auto trouve = ensemble.find(5);
if (trouve != ensemble.end())
std::cout << "Trouvé : " << *trouve << std::endl;
std::cout << "Compte de 2 : " << ensemble.count(2) << std::endl;
// Bornes inférieure et supérieure
auto bas = ensemble.lower_bound(5);
auto haut = ensemble.upper_bound(5);
std::cout << "Lower : " << *bas << ", Upper : " << *haut << std::endl;
auto plage = ensemble.equal_range(5);
std::cout << "Plage first : " << *plage.first << ", second : " << *plage.second << std::endl;
}
// Tri personnalisé (ordre décroissant)
struct ComparateurDecroissant
{
bool operator()(int a, int b) const
{
return a > b;
}
};
void demoSetPersonnalise()
{
std::set<int, ComparateurDecroissant> ensembleTrie;
ensembleTrie.insert(10);
ensembleTrie.insert(30);
ensembleTrie.insert(20);
for (auto val : ensembleTrie)
{
std::cout << val << std::endl;
}
// Affiche : 30, 20, 10
}
Les conteneurs map et multimap
Le map stocke des paires clé-valeur (appelées pairs). Chaque paire est accessible via les membres first (clé) et second (valeur). Les clés sont uniques et triées automatiquement. La structure sous-jacente est un arbre rouge-noir.
Le multimap autorise plusieurs valeurs pour une même clé.
#include <map>
void demoMap()
{
std::map<std::string, int> ages;
// Insertion
ages.insert(std::make_pair("Alice", 25));
ages["Bob"] = 30;
ages["Charlie"] = 28;
// Parcours (trié par clé)
for (const auto& paire : ages)
{
std::cout << paire.first << " : " << paire.second << std::endl;
}
// Recherche
auto it = ages.find("Bob");
if (it != ages.end())
std::cout << "Age de Bob : " << it->second << std::endl;
// Suppression par clé
ages.erase("Alice");
std::cout << "Taille : " << ages.size() << std::endl;
std::cout << "Vide : " << ages.empty() << std::endl;
}
Synthèse des cas d'usage
- vector : tableau dynamique, idéal pour l'enregistrement d'historiques d'actions où l'on consulte fréquemment mais où la suppression est rare. Itérateur à accès aléatoire.
- deque : tableau double extrémité, adapté aux files d'attente (ex : système de tickets) nécessitant retrait en tête et ajout en queue rapides.
- stack : pile LIFO sans itérateur, pour des mécanismes de retour-arrière ou d'annulation.
- queue : file FIFO sans itérateur, pour un traitement séquentiel strict.
- list : liste chaînée, recommandée quand les insertions et suppressions sont fréquentes à des positions aléatoires (ex : passagers dans un bus). Itérateur bidirectionnel.
- set/multiset : ensemble trié à clés uniques (ou non), pour des classements automatiques comme des scores de jeu. Itérateur en lecture seule.
- map/multimap : association clé-valeur triée, parfait pour indexer des utilisateurs par identifiant et effectuer des recherches rapides. Itérateur en lecture seule.