Introduction au C++ : De C vers C++

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. std est l'espace de noms de la bibliothèque standard C++.
  • Fonction main : Point d'entrée du programme. Le type de retour int indique 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/cout fait correspondre automatiquement les types, tandis que scanf/printf nécessite une specification manuelle du format (source d'erreurs).
  • Extensibilité : cin/cout prend en charge la surcharge d'opérateurs (les types personnalisés peuvent être affichés directement), tandis que scanf/printf né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 strcpy en 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/delete sont des opérateurs, malloc/free sont des fonctions.
  • new appelle automatiquement le constructeur, delete appelle automatiquement le destructeur.
  • new retourne un pointeur du type correspondant, malloc retourne void* (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 blocs catch pour 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 mutable pour ê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.

Étiquettes: C++ langage de programmation Programmation orientée objet STL pointeurs

Publié le 24 juillet à 20h30