Introduction au C++
Le C++ est un langage de programmation multi-paradigmes qui prend en charge la programmation procédurale, la programmation orientée objet (POO) et la programmation générique. Il est compatible avec le langage C tout en améliorant les vérifications de types et les fonctionnalités. Les avantages principaux incluent l'encapsulation, l'héritage et le polymorphisme, les modèles et la programmation générique, la gestion des exceptions et la bibliothèque standard de modèles (STL).
Programme minimal C++ et structure
L'exemple suivant illustre la structure fondamentale d'un programme C++ :
#include <iostream>
#include <string>
int main() {
std::string message = "Bonjour le monde !";
std::cout << message << std::endl;
return 0;
}
Les éléments essentiels sont :
- Fichiers d'en-tête : Ils fournissent les déclarations de fonctions et de classes. Les fichiers d'en-tête standard C++ n'ont pas d'extension
.h(comme<iostream>). Les fichiers compatibles C sont renommés (comme<cstdio>au lieu de<stdio.h>). - Espaces de noms : Ils permettent de résoudre les conflits de noms de symboles.
stdest l'espace de noms de la bibliothèque standard C++. - Fonction main : Point d'entrée du programme. Le type de retour
intindique le statut d'exécution (0 signifie que le programme s'est terminé normalement).
Espaces de noms (Namespace)
Principes de base
Un espace de noms est un mécanisme qui permet de diviser les portées personnalisées en C++. Son objectif principal est de résoudre les conflits de noms de symboles globaux dans les projets de grande envergure (comme les variables et fonctions homonymes provenant de différentes bibliothèques ou modules). Les symboles homonymes dans différents espaces de noms sont indépendantes les uns des autres, ce qui permet de gérer efficacement les limites entre les modules de code.
Définition et utilisation
Définir un espace de noms
Un espace de noms est défini avec le mot-clé namespace et peut contenir des variables, des fonctions, des classes, et même des espaces de noms imbriqués :
namespace outilsMathematiques {
int valeurBase = 5;
double calculerMoitie(int nombre) {
return nombre / 2.0;
}
}
Accéder aux symboles d'un espace de noms
L'opérateur de résolution de portée :: permet de spécifier l'espace de noms d'un symbole. Le format est nomEspace::symbole :
#include <iostream>
namespace outilsMathematiques {
extern int valeurBase;
extern double calculerMoitie(int nombre);
}
int main() {
std::cout << outilsMathematiques::valeurBase << std::endl;
std::cout << outilsMathematiques::calculerMoitie(8) << std::endl;
return 0;
}
Instructions using (simplification d'accès)
L'utilisation répétée de nomEspace::symbole peut alourdir le code. L'instruction using permet de simplifier l'accès :
Importer tout un espace de noms
using namespace outilsMathematiques;
int main() {
std::cout << valeurBase << std::endl;
std::cout << calculerMoitie(8) << std::endl;
return 0;
}
Importer des symboles spécifiques
Cette approche évite la "pollution de l'espace de noms" où les symboles non utilisés peuvent causer des conflits potentiels :
using outilsMathematiques::valeurBase;
using outilsMathematiques::calculerMoitie;
int main() {
std::cout << valeurBase << std::endl;
std::cout << calculerMoitie(8) << std::endl;
return 0;
}
Syntaxe avancée
Espaces de noms imbriqués
Les espaces de noms peuvent être définis de manière hiérarchique, formant des structures de portée à plusieurs niveaux. L'accès nécessite de spécifier chaque niveau :
namespace projetPrincipal {
int configurationGlobale = 1;
namespace moduleCalcul {
int configurationLocale = 2;
int codeResultat = 100;
}
}
int main() {
std::cout << projetPrincipal::configurationGlobale << std::endl;
std::cout << projetPrincipal::moduleCalcul::configurationLocale << std::endl;
std::cout << projetPrincipal::moduleCalcul::codeResultat << std::endl;
return 0;
}
Extensibilité des espaces de noms
Un même espace de noms peut être complété à différents endroits. Les définitions sont fusionnées en une seule portée complète :
namespace outilsMathematiques {
int valeurBase = 5;
}
namespace outilsMathematiques {
int limiteMaximale = 666;
}
int main() {
std::cout << outilsMathematiques::valeurBase << std::endl;
std::cout << outilsMathematiques::limiteMaximale << std::endl;
return 0;
}
Portée globale
La portée globale est anonyme. Si un symbole local a le même nom qu'un symbole global, on peut accéder au symbole global avec ::symbole :
int main() {
int compteurGlobal = 200;
std::cout << compteurGlobal << std::endl;
std::cout << ::compteurGlobal << std::endl;
return 0;
}
Résolution pratique des conflits de noms
Problème : Une fonction personnalisée porte le même nom qu'une fonction de la bibliothèque standard (comme max). Solution : Placer la fonction personnalisée dans un espace de noms séparé pour éviter les conflits :
#include
namespace mathematiquesPersonnelles {
int valeurMaximale;
void trouverMaximum(float premierNombre, float deuxiemeNombre) {
valeurMaximale = premierNombre > deuxiemeNombre ? premierNombre : deuxiemeNombre;
}
}
int main() {
mathematiquesPersonnelles::trouverMaximum(3.0f, 4.0f);
std::cout << mathematiquesPersonnelles::valeurMaximale << std::endl;
return 0;
}
Flux d'entrée-sortie (cin/cout)
C++ utilise des objets de flux standard pour l'entrée et la sortie, offrant une approche plus sécurisée et plusSimple que les fonctions scanf/printf du C.
Objets de flux principaux
- cout : Flux de sortie standard, affiche les données sur la console avec déduction automatique de type.
- cin : Flux d'entrée standard, lit les données depuis la console et fait correspondre automatiquement le type de variable.
- cerr : Flux d'erreur standard, sans tampon, affiche directement les messages d'erreur.
Exemple
#include
#include
int main() {
int entier;
double decimal;
std::string chaine;
std::cout << "Entrez un entier, un décimal et une chaîne :" << std::endl;
std::cin >> entier >> decimal >> chaine;
std::cout << "Entier : " << entier << std::endl;
std::cout << "Décimal : " << decimal << std::endl;
std::cout << "Chaîne : " << chaine << std::endl;
std::cerr << "Exemple de message d'erreur" << std::endl;
return 0;
}
Comparaison avec les entrées-sorties du C
- Sécurité des types :
cin/coutfait correspondre automatiquement les types, tandis quescanf/printfnécessite une specification manuelle du format (source d'erreurs). - Extensibilité :
cin/coutprend en charge la surcharge d'opérateurs (les types personnalisés peuvent être affichés directement), tandis quescanf/printfnécessite un formatage manuel.
Types de données et chaînes
Types de données fondamentaux
C++ est compatible avec tous les types fondamentaux du C (char, short, int, long, float, double), et ajoute le type bool avec les valeurs true et false.
Types chaînes (string)
Le langage C utilise char[] ou char* pour gérer les chaînes (avec gestion manuelle de la mémoire), tandis que C++ fournit std::string qui gère automatiquement la mémoire et prend en charge l'expansion dynamique.
#include
#include
int main() {
std::string texte1 = "Bonjour";
std::string texte2 = "Univers";
std::string texte3 = texte1 + " " + texte2;
std::cout << "Résultat de la concaténation : " << texte3 << std::endl;
std::cout << "Longueur : " << texte3.length() << std::endl;
std::cout << "Sous-chaîne : " << texte3.substr(0, 7) << std::endl;
if (texte1 == "Bonjour") {
std::cout << "texte1 est égal à Bonjour" << std::endl;
}
return 0;
}
Avantages
- Gestion automatique de la mémoire,避免了 les dépassements de tampon (comme les risques avec
strcpyen C). - Prend en charge la surcharge d'opérateurs (
+pour concaténer,=pour assigner,==pour comparer, etc.).
Mécanisme de références
Concept
Une référence est un alias d'une variable existante. Elle partage l'adresse mémoire avec la variable originale. Manipuler la référence équivaut à manipuler la variable originale.
Syntaxe et caractéristiques
#include
int main() {
int variableA = 10;
int& referenceA = variableA;
referenceA = 20;
std::cout << "variableA = " << variableA << std::endl;
std::cout << "referenceA = " << referenceA << std::endl;
std::cout << "Adresse de variableA : " << &variableA << std::endl;
std::cout << "Adresse de referenceA : " << &referenceA << std::endl;
int variableB = 30;
referenceA = variableB;
std::cout << "variableA = " << variableA << std::endl;
return 0;
}
Règles fondamentales
- Initialisation obligatoire : Une référence doit être liée à une variable valide lors de sa définition.
- Pas de recombinaison : Après initialisation, il est impossible de changer la variable pointée.
- Pas de mémoire supplémentaire : Une référence est un alias et n'occupe pas de mémoire supplémentaire (le compilateur l'optimise en un pointeur constant).
- Pas de référence nulle : Doit être liée à un objet valide.
Applications des références
Passage de paramètres aux fonctions (évite la copie, améliore l'efficacité)
void echanger(int& premier, int& deuxieme) {
int temporaire = premier;
premier = deuxieme;
deuxieme = temporaire;
}
int main() {
int valeurX = 5, valeurY = 10;
echanger(valeurX, valeurY);
std::cout << "valeurX = " << valeurX << ", valeurY = " << valeurY << std::endl;
return 0;
}
Valeur de retour de fonction (référence vers une variable globale/statique)
int& obtenirVariableStatique() {
static int variableStatique = 10;
return variableStatique;
}
int main() {
int& ref = obtenirVariableStatique();
ref = 20;
std::cout << obtenirVariableStatique() << std::endl;
return 0;
}
Attention : Éviter de retourner une référence vers une variable locale (la variable locale est détruite à la fin de la fonction, la référence devient "dangereuse").
Référence constante (lecture seule, compatible avec les objets temporaires)
void afficherTexte(const std::string& texte) {
std::cout << texte << std::endl;
}
int main() {
afficherTexte("Bonjour Univers");
std::string chaîne = "Test";
afficherTexte(chaî
ne);
return 0;
}
Comparaison entre références et pointeurs
| Caractéristique | Référence | Pointeur |
|---|---|---|
| Initialisation | Obligatoire | Peut être nul ou assigné afterward |
| Syntaxe | Pas de */-> |
Nécessite * pour déréférencer, -> pour accéder aux membres |
| Occupation mémoire | Pas de mémoire supplémentaire | Occupe de la mémoire (4 octets en 32 bits, 8 octets en 64 bits) |
| Sécurité | Pas de risque de référence nulle | Nécessite de vérifier les pointeurs nuls |
| Accès multiple | Une seule référence | Supporte les pointeurs multiples (comme int**) |
Conversions de types
C++ est compatible avec les anciennes conversions du C (comme (int)f), et ajoute de nouvelles conversions plus sûres et plus lisibles.
const_cast (supprimer les attributs const/volatile)
Utilisation : Uniquement pour les pointeurs ou références, permet de supprimer les qualificateurs const ou volatile.
#include
int main() {
const int constanteA = 10;
const int* pointeurConstant = &constanteA;
int* pointeurNonConstant = const_cast(pointeurConstant);
*pointeurNonConstant = 20;
std::cout << "constanteA = " << constanteA << std::endl;
const int& referenceConstante = constanteA;
const_cast(referenceConstante) = 30;
std::cout << "constanteA = " << constanteA << std::endl;
return 0;
}
Attention : Impossible de supprimer le const de la variable elle-même. Seul le qualificatif du pointeur/référence peut être supprimé.
static_cast (conversion statique)
Utilisation : Pour les conversions de types compatibles (comme les types numériques, les pointeurs entre classes родитель et enfant), avec vérification de sécurité des types à la compilation.
#include
int main() {
float flottant = 3.14;
int entier = static_cast(flottant);
std::cout << "entier = " << entier << std::endl;
class ClasseBase {};
class ClasseDerivee : public ClasseBase {};
ClasseDerivee obj;
ClasseBase* base = static_cast(&obj);
return 0;
}
Avantage : Plus facile à identifier que les anciennes conversions, et peut bloquer les conversions de types incompatibles.
dynamic_cast (conversion dynamique)
Utilisation : Uniquement pour les hiérarchies de classes, avec vérification du type à l'exécution (nécessite des fonctions virtuelles dans la classe), pour une conversion descendante seguridad vers le bas (classe parent vers classe enfant).
reinterpret_cast (reconversion)
Utilisation : Conversion forcée de pointeurs/références de types arbitraires (à haut risque), utilisée uniquement pour les opérations de bas niveau.
Mot-clé auto
auto permet au compilateur de déduire automatiquement le type de la variable, simplifient le code (particulièrement utile pour les types complexes).
#include
#include
int main() {
auto variableA = 10;
auto variableB = 3.14;
auto variableC = "Bonjour";
std::vector vecteur = {1, 2, 3};
for (auto it = vecteur.begin(); it != vecteur.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
Attention : Les variables auto doivent être initialisées (le compilateur a besoin de la valeur initiale pour déduire le type).
Conventions de nommage
C++ n'impose pas de convention de nommage obligatoire, mais des règles générales peuvent améliorer la lisibilité du code :
- Noms de classes/structures : PascalCase (première lettre en majuscule), comme
ArbrePommier,Etudiant. - Noms de variables/fonctions : camelCase (première lettre en minuscules), comme
nomEtudiant,calculerSomme. - Noms de constantes : TOUTES_MAJUSCULES_AVEC_SOULIGNES, comme
TAILLE_MAX,PI. - Noms d'espaces de noms : minuscules, comme
mon_espace.
Caractéristiques des fonctions
Paramètres par défaut
Les paramètres de fonction peuvent avoir des valeurs par défaut. Si aucun argument n'est passé lors de l'appel, la valeur par défaut est utilisée.
double calculerVolumeCone(double rayon, double hauteur = 1.0) {
const double PI = 3.1415926;
return (1.0 / 3) * PI * rayon * rayon * hauteur;
}
int main() {
std::cout << "Volume (hauteur par défaut) : " << calculerVolumeCone(2) << std::endl;
std::cout << "Volume (hauteur = 3) : " << calculerVolumeCone(2, 3) << std::endl;
return 0;
}
Règles :
- Les paramètres par défaut doivent être définis de droite à gauche de manière continue.
- Les paramètres par défaut ne doivent pas être répétés dans la déclaration et la définition.
Paramètres placeholders
Les paramètres de fonction ne déclarent que le type, sans nom de paramètre. Ils sont utilisés pour réserver une interface ou maintenir la compatibilité avec l'ancien code.
void fonctionExample(int parametreUtilisable, int) {
std::cout << "parametreUtilisable = " << parametreUtilisable << std::endl;
}
int main() {
fonctionExample(10, 20);
return 0;
}
Surcharge de fonctions**
Dans une même portée, des fonctions de même nom mais avec des types/nombres/ordres de paramètres différents peuvent former une surcharge (le compilateur différencie les appels selon les paramètres).
Cas de surcharge légitimes
#include
void afficher(int a) {
std::cout << "Entier : " << a << std::endl;
}
void afficher(int a, int b) {
std::cout << "Deux entiers : " << a << ", " << b << std::endl;
}
void afficher(double a) {
std::cout << "Flottant : " << a << std::endl;
}
class MaClasse {
public:
void afficher() {
std::cout << "afficher() normal" << std::endl;
}
void afficher() const {
std::cout << "afficher() const" << std::endl;
}
};
void traiter(char* ptr) {
std::cout << "Pointeur normal : " << ptr << std::endl;
}
void traiter(const char* ptr) {
std::cout << "Pointeur constant : " << ptr << std::endl;
}
int main() {
afficher(10);
afficher(3.14);
afficher(10, 20);
MaClasse obj;
const MaClasse objConstant;
obj.afficher();
objConstant.afficher();
char* chaîne = "test";
const char* chaîneConstante = "test constant";
traiter(chaîne);
traiter(chaîneConstante);
return 0;
}
Cas de surcharge illégitimes
- Valeurs de retour différentes : Impossible à surcharger.
- Modificateurs static différents : Impossible à surcharger.
- Modificateurs const différents (non pointeur/référence) : Impossible à surcharger.
- Ambiguïté entre référence et valeur : L'appel
fonction(10)peut être ambigu.
Gestion de la mémoire dynamique
Le langage C utilise malloc/free pour gérer la mémoire du tas, tandis que C++ fournit new/delete (plus sûr, prend en charge la construction/destruction des objets).
new/delete pour un objet unique
#include
int main() {
int* pointeur = new int(10);
std::cout << *pointeur << std::endl;
delete pointeur;
pointeur = nullptr;
class Test {
public:
Test() { std::cout << "Constructeur Test" << std::endl; }
~Test() { std::cout << "Destructeur Test" << std::endl; }
};
Test* objetTest = new Test();
delete objetTest;
return 0;
}
Tableaux avec new[]/delete[]
int main() {
int* tableau = new int[5]{1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
std::cout << tableau[i] << " ";
}
std::cout << std::endl;
delete[] tableau;
tableau = nullptr;
return 0;
}
Comparaison malloc/free
new/deletesont des opérateurs,malloc/freesont des fonctions.newappelle automatiquement le constructeur,deleteappelle automatiquement le destructeur.newretourne un pointeur du type correspondant,mallocretournevoid*(nécessite une conversion).
Énumérations et boucles for basadas sur la plage
Énumérations (enum/enum class)
Énumération simple
enum Couleur {
Rouge,
Vert,
Bleu
};
int main() {
Couleur couleur = Rouge;
std::cout << couleur << std::endl;
return 0;
}
Énumération à type fort (enum class)
Ajoutée en C++, évite les conflits entre valeurs d'énumération et offre une sécurité de type.
enum class Direction {
Gauche,
Droite
};
int main() {
Direction direction = Direction::Gauche;
std::cout << static_cast(direction) << std::endl;
return 0;
}
Boucle for basée sur la plage (C++11)**
Permet de parcourir des tableaux, conteneurs et autres objets itérables, simplifiant le code de boucle.
#include
#include
int main() {
int tableau[] = {1, 2, 3, 4, 5};
for (int valeur : tableau) {
std::cout << valeur << " ";
}
std::cout << std::endl;
std::vector vecteur = {"a", "b", "c"};
for (auto& élément : vecteur) {
std::cout << élément << " ";
}
std::cout << std::endl;
return 0;
}
Mécanisme extern "C"
C++ peut appeler du code C en utilisant extern "C" pour désactiver le Name Mangling de C++, garantissant que le compilateur C++ traite les noms de fonctions selon les règles du C.
// Fichier d'en-tête C (c_func.h)
#ifdef __cplusplus
extern "C" {
#endif
void fonctionC(int x);
#ifdef __cplusplus
}
#endif
// Implémentation C (c_func.c)
#include "c_func.h"
#include
void fonctionC(int x) {
printf("Fonction C : x=%d\n", x);
}
// Appel C++ (main.cpp)
#include "c_func.h"
int main() {
fonctionC(10);
return 0;
}
Principe : Pour prendre en charge la surcharge de fonctions, C++ modifie les noms de fonctions (par exemple, add(int) devient _Z3addi), tandis que le langage C ne modifie pas les noms. extern "C" permet au compilateur C++ de générer des noms de fonctions selon les règles du C, garantissant la liaison réussie.
Partitions de mémoire
La mémoire d'un programme C++ en cours d'exécution est divisée en cinq zones :
| Zone mémoire | Contenu | Cycle de vie | Mode de gestion |
|---|---|---|---|
| Zone de pile | Variables locales, paramètres de fonction, adresses de retour | Libérée automatiquement à la fin de la fonction | Allocation et libération automatiques par le compilateur |
| Zone de tas | Objets alloués avec new/malloc | Libéré manuellement avec delete/free | Gestion manuelle par le programmeur |
| Zone globale/statique | Variables globales, variables static | Allouée au démarrage, libérée à la fin | Allocation statique par le compilateur |
| Zone constante | Constantes de chaîne, constantes globales const | Pendant l'exécution du programme | Lecture seule, non modifiable |
| Zone de code | Instructions binaires, code des fonctions | Pendant l'exécution du programme | Lecture seule, mémoire partagée |
Gestion des exceptions
Concepts de base
Une exception est une situation anormale apparaissant pendant l'exécution du programme (comme une division par zéro, un accès à un pointeur nul, des paramètres invalides, etc.). C++ gère les exceptions via un mécanisme "lancer-capturer" : throw lance une exception, try-catch la capture et la traite, évitant que le programme ne s'effondre directement et améliorant la robustesse.
Lancer et capturer des exceptions
Lancer une exception (throw)
Le mot-clé throw permet de lancer une exception de n'importe quel type :
float diviser(float dividende, float diviseur) {
if (diviseur == 0) {
throw "Le diviseur ne peut pas être zéro";
}
return dividende / diviseur;
}
Capturer une exception (try-catch)
- Bloc
try: Encadre le code qui peut lancer une exception ; - Bloc
catch: Capture et traite l'exception par type, avec possibilité de plusieurs blocscatchpour différents types ; catch(...): Capture toutes les exceptions non correspondantes.
#include
int main() {
float dividende, diviseur, résultat;
while (true) {
std::cin >> dividende >> diviseur;
try {
résultat = diviser(dividende, diviseur);
std::cout << résultat << std::endl;
} catch (const char* messageErreur) {
std::cout << "Erreur : " << messageErreur << std::endl;
break;
} catch (...) {
std::cout << "Erreur inconnue" << std::endl;
}
}
return 0;
}
Types d'exceptions
Différents types peuvent être utilisés pour distinguer les scénarios d'erreur :
#include
#include
void vérifierEntrée(int valeur) {
if (valeur == 1) throw 666;
if (valeur == 2) throw 3.14f;
if (valeur == 3) throw std::runtime_error("Paramètre hors plage légale");
}
int main() {
int entrée;
std::cin >> entrée;
try {
vérifierEntrée(entrée);
} catch (int codeErreur) {
std::cout << "Erreur entière : " << codeErreur << std::endl;
} catch (float codeErreurFlottant) {
std::cout << "Erreur flottante : " << codeErreurFlottant << std::endl;
} catch (std::runtime_error& erreurStandard) {
std::cout << "Exception стандартная : " << erreurStandard.what() << std::endl;
}
return 0;
}
Classes d'exceptions standard**
La bibliothèque standard C++ fournit une série de classes d'exceptions (héritant de exception), les plus courantes étant :
invalid_argument: Paramètre invalide ;out_of_range: Accès hors limites ;runtime_error: Erreur d'exécution ;logic_error: Erreur logique.
Exemple : Utilisation des classes d'exceptions standard
#include
#include
float diviser(float dividende, float diviseur) {
if (diviseur == 0) {
throw std::invalid_argument("Le diviseur ne peut pas être zéro");
}
return dividende / diviseur;
}
int main() {
try {
diviser(10, 0);
} catch (std::invalid_argument& e) {
std::cout << e.what() << std::endl;
}
return 0;
}
Différence entre exceptions et retour de valeurs d'erreur
| Caractéristique | Retour de valeur d'erreur | Gestion des exceptions |
|---|---|---|
| Propagation | Transmission manuelle du code d'erreur | Propagation automatique vers le haut |
| Obligatoire | Facile à ignorer | Traitement obligatoire |
| Capacité d'information | Types de base uniquement | Objets携带ant des détails |
| Lisibilité du code | Mixage de la logique métier et du traitement d'erreurs | Séparation de la logique métier et du traitement d'erreurs |
Exceptions dans les destructeurs
Les exceptions dans les destructeurs peuvent provoquer un effondrement du programme. Par conséquent, toutes les exceptions doivent être capturées dans les destructeurs :
#include
bool étatConnexionBase = false;
void fermerConnexionBase() {
if (!étatConnexionBase) throw 100;
else std::cout << "Connexion à la base fermée normalement" << std::endl;
}
class ConnecteurBase {
bool estFermé = false;
public:
~ConnecteurBase() {
if (!estFermé) {
try {
fermerConnexionBase();
} catch (...) {
std::cout << "Échec de la fermeture de la base lors de la destruction" << std::endl;
}
}
}
void fermerManuellement() {
fermerConnexionBase();
estFermé = true;
}
};
int main() {
ConnecteurBase connecteur;
try {
connecteur.fermerManuellement();
} catch (...) {
std::cout << "Échec de la fermeture manuelle, correction de l'état" << std::endl;
étatConnexionBase = true;
}
return 0;
}
Fonctions Lambda (fonctions anonymes)
Concept de base**
Les fonctions Lambda (fonctions anonymes) sont des objets de fonction introduits en C++11. Elles peuvent être définies directement dans le code sans nom explicite, supportent la capture de variables externes, peuvent être passées en paramètres et utilisées comme valeurs de retour. Elles sont souvent utilisées pour simplifier le code (comme les fonctions uniques) et implémenter la programmation fonctionnelle.
Structure语法
[capture](paramètres) mutable -> type_retour { corps }
- Liste de capture : Spécifie comment capturer les variables externes (par valeur ou par référence) ;
- Liste de paramètres : Paramètres de la fonction (comme une fonction normale) ;
- mutable : Permet de modifier les variables capturées par valeur (par défaut, la capture par valeur est en lecture seule) ;
- Type de retour : Peut être omis, le compilateur le déduit automatiquement ;
- Corps de la fonction : Logique métier.
Utilisation de la liste de capture
Capture par valeur ([variable])
Capture une copie de la variable externe. Les modifications à l'intérieur du Lambda n'affectent pas la variable externe :
#include
int main() {
int premier = 1, second = 2;
auto somme = [premier, second]() {
return premier + second;
};
std::cout << somme() << std::endl;
return 0;
}
Capture par référence ([&variable])
Capture une référence de la variable externe. Les modifications à l'intérieur du Lambda affectent la variable externe :
int main() {
int nombre = 10;
auto modifierNombre = [&nombre]() {
nombre *= 2;
};
modifierNombre();
std::cout << nombre << std::endl;
return 0;
}
Capturer toutes les variables externes
[=]: Capture par valeur toutes les variables externes ;[&]: Capture par référence toutes les variables externes.
int main() {
int a = 1, b = 2;
auto afficherTout = [=]() {
std::cout << "a=" << a << ", b=" << b << std::endl;
};
afficherTout();
auto modifierTout = [&]() {
a += b;
b *= 2;
};
modifierTout();
std::cout << "a=" << a << ", b=" << b << std::endl;
return 0;
}
Cas d'utilisation des fonctions Lambda
Como paramètre de fonction
Souvent utilisées avec les algorithmes STL (comme sort, count_if) pour simplifier la logique des prédicats :
#include
#include
#include
int main() {
std::vector vecteur = {3, 1, 4, 1, 5, 9};
int nombrePairs = std::count_if(vecteur.begin(), vecteur.end(),
[](int nombre) {
return nombre % 2 == 0;
});
std::cout << "Nombre de nombres pairs : " << nombrePairs << std::endl;
return 0;
}
Como valeur de retour d'une fonction
Les Lambda peuvent être retournées par une fonction (doivent utiliser auto pour déduire le type) :
auto créerAdditionneur(int addonFixe) {
return [addonFixe](int addonVariable) {
return addonFixe + addonVariable;
};
}
int main() {
auto ajouterCinq = créerAdditionneur(5);
std::cout << ajouterCinq(3) << std::endl;
std::cout << ajouterCinq(10) << std::endl;
return 0;
}
Appel de fonctions d'ordre supérieur
Les Lambda sont passées en paramètres à des fonctions personnalisées d'ordre supérieur :
void vérifierNombre(int nombreCible, auto condition) {
if (condition(nombreCible)) {
std::cout << nombreCible << " satisfies la condition" << std::endl;
} else {
std::cout << nombreCible << " ne satisfaire pas la condition" << std::endl;
}
}
int main() {
vérifierNombre(5, [](int nombre) {
return nombre % 2 != 0;
});
vérifierNombre(8, [](int nombre) {
return nombre > 10;
});
return 0;
}
Introduction à la programmation fonctionnelle
Les Lambda sont le fondement de la programmation fonctionnelle, qui met l'accent sur le mappage des données plutôt que sur les "instructions étape par étape". Les caractéristiques principales :
- Les fonctions sont des citoyenne de première classe (peuvent être utilisées comme paramètres, valeurs de retour) ;
- Éviter les états mutables, privilégier les fonctions pures (une entrée donnée produit toujours la même sortie).
Exemple : Implémentation fonctionnelle de l'inversion de chaîne
#include
std::string inverserChaîne(const std::string& chaîne) {
return chaîne.empty() ? chaîne :
inverserChaîne(chaîne.substr(1)) + chaîne[0];
}
int main() {
std::string texte = "abcdef";
std::cout << inverserChaîne(texte) << std::endl;
return 0;
}
Précautions concernant les Lambda**
- Si la liste de capture est vide, il faut écrire
[](ne peut pas être omis), ne peut pas capturer les variables statiques ; - Les variables capturées par valeur sont en lecture seule par défaut, doivent être
mutablepour être modifiées ; - Le cycle de vie d'une Lambda est le même que celui d'une variable normale. Si les variables externes capturées sont détruites, l'accès par la Lambda entrainera un comportement indéfini.