C++
Langage de programmation de haut niveau, conservant un accès bas niveau, et multiparadigme.
Historique
Durant sa thèse, Bjarne Stroustrup trouvait de nombreuses fonctionnalités intéressantes au langage Simula, mais son implémentation n'était pas assez performante.

En 1979, dans les laboratoires Bell d'AT&T, Stroustrup utilise la syntaxe du langage C comme base et entreprend d'ajouter la prise en charge des classes, de l'héritage, etc. Bref, en intégrant le paradigme orienté objet. D’ailleurs, le nom du langage était « C with Classes » et, en 1983, « C++ » fut proposé par Rick Mascitti, faisant un clin d’oeil à la syntaxe du langage.
Il est facile de se tirer dans le pied avec le langage C; c’est plus difficile avec le langage C++ mais, lorsque vous y arrivez, c’est la jambe en entier qui explose.
- Bjarne Stroustrup
Types de variable
Les différents types de variable du langage C++ permettent plus de flexibilité et d'efficacité, mais nécessitent une syntaxe et une gestion particulières.
Standard
Une variable standard contient une valeur d'un type de donnée.
Pointeur
Une variable pointeur contient une adresse de la mémoire centrale où se trouve une valeur d'un type de donnée.
Déclaration
Le caractère *, lors de la déclaration, permet d'indiquer qu'une variable est un pointeur :
int entier; // Valeur entière.
int* pEntier; // Adresse en mémoire centrale où se trouve une valeur entière.
Adressage
Il est possible d'obtenir l'adresse en mémoire centrale d'une variable standard en la faisant précéder du caractère & :
int entier = 42; // Valeur entière 42.
int* pEntier = &entier; // Adresse en mémoire centrale où se trouve la valeur entière 42.
Indirection
Le caractère *, devant une variable pointeur, permet d'accéder à la valeur située à l'adresse de la mémoire centrale qu'elle contient :
int copie = *pEntier; // Copie de la valeur entière 42.
Référence
Une variable référence est tout comme un pointeur, mais s'utilise comme une variable standard. Elle doit absolument être initialisée, et le lien ainsi établi est définitif.
int& rEntier = entier; // Référence en mémoire centrale où se trouve la valeur entière 42.
Classe
Les classes sont à la base du paradigme orienté objet.
Définition
Le mot-clé class permet de définir une classe avec l'identificateur de notre choix :
class Identificateur {
// Contenu de la classe.
};
Une définition doit se terminer par un point-virgule.
Modificateurs d'accès
Les modificateurs d'accès permettent de rendre une classe robuste en limitant l'accès aux membres sensibles :
| Accès | Symbole | Description |
|---|---|---|
| privé | - | Accessible uniquement depuis l'implémentation de la classe. |
| protégé | # | Accessible uniquement depuis les implémentations de la classe et de ses dérivées. |
| public | + | Accessible à l'intérieur et à l'extérieur de l'implémentation de la classe. |
Un modificateur d'accès s'applique à tout ce qui le suit :
class Classe {
public:
// Déclarations publiques.
protected:
// Déclarations protégées.
private:
// Déclarations privées.
};
Il n'y a pas d'ordre à respecter, les répétitions sont permises, et « private » est appliqué par défaut si aucun modificateur d'accès n'est déclaré.
Amie
Une classe ou une fonction peut être déclarée amie, ce qui lui permet d'accéder aux membres privés et protégés :
class Classe {
friend class ClasseAmie;
friend void fonctionAmie(const Classe&);
// ...
};
Donnée membre
On nomme donnée membre les variables déclarées à l'intérieur d'une classe :
class Entier {
private:
int* entier;
};
Initialisation
Les données membres peuvent être initialisées lors de leur déclaration :
class Entier {
private:
int* entier = nullptr;
};
Méthode
On nomme méthode les fonctions déclarées à l'intérieur d'une classe :
class Entier {
public:
operator int() const {
return *entier;
}
Entier& operator=(const Entier& instance) {
*entier = *instance.entier;
return *this;
}
private:
int* entier = nullptr;
};
Constructeur
Méthode particulière puisqu'elle est appelée automatiquement lors de l'instanciation.
Puisqu'il s'agit d'une méthode que le compilateur doit différencier des autres méthodes, sa syntaxe comporte quelques exigences :
- Aucun type de retour.
- Même identificateur que la classe.
Le constructeur est principalement utilisé pour affecter des valeurs aux données membres :
class Entier {
public:
Entier(int valeur) {
entier = new int(valeur);
}
operator int() const {
return *entier;
}
Entier& operator=(const Entier& instance) {
*entier = *instance.entier;
return *this;
}
private:
int* entier = nullptr;
};
Défaut
Sans paramètre, ou s'ils ont tous une valeur par défaut, on le nomme constructeur par défaut :
class Entier {
public:
Entier(int valeur = 0) {
entier = new int(valeur);
}
operator int() const {
return *entier;
}
Entier& operator=(const Entier& instance) {
*entier = *instance.entier;
return *this;
}
private:
int* entier = nullptr;
};
Copie
S'il ne contient qu'un seul paramètre étant une référence, généralement constante, du même type que la classe, on le nomme constructeur de copie puisqu'il permet de déterminer comment une instance est dupliquée :
class Entier {
public:
Entier(int valeur = 0) {
entier = new int(valeur);
}
Entier(const Entier& instance) {
entier = new int(*instance.entier);
}
operator int() const {
return *entier;
}
Entier& operator=(const Entier& instance) {
*entier = *instance.entier;
return *this;
}
private:
int* entier = nullptr;
};
Destructeur
Mêmes exigences syntaxiques que le constructeur, mais l'identificateur doit être précédé du caractère tilde (~). Il est appelé automatiquement lors de la destruction de l'instance :
class Entier {
public:
Entier(int valeur = 0) {
entier = new int(valeur);
}
Entier(const Entier& instance) {
entier = new int(*instance.entier);
}
~Entier() {
delete entier;
}
operator int() const {
return *entier;
}
Entier& operator=(const Entier& instance) {
*entier = *instance.entier;
return *this;
}
private:
int* entier = nullptr;
};
Membres statiques
Les membres statiques appartiennent à une classe plutôt qu'à ses instances.
Accès
Les données membres et les méthodes statiques sont donc accessibles sans instance :
Classe::donneeMembre;
Classe::methode();
Déclaration
Pour qu'une variable, une donnée membre ou une méthode soit statique, leur déclaration doit être précédée du mot-clé static.
Espace de stockage
La déclaration dans la classe annonce l'existence de la donnée membre dans cette classe, mais sans lui réserver d'espace de stockage. Il faut donc, à l'extérieur de la classe, la définir :
// Définition de la classe.
};
type Classe::donneeMembreStatique = valeur;
Depuis C++17, une donnée membre statique déclarée inline peut être initialisée directement.
Locale
Déclarée localement, une variable statique est uniquement à portée de sa méthode. Cette variable est initialisée uniquement lors du premier appel de sa méthode.
Dérivation
Une classe peut dériver d'une ou plusieurs autres classes, permettant l'héritage et le polymorphisme :
class Derivee : public Base1, protected Base2, private Base3 {
// Définition de la classe.
};
Héritage
Les classes dérivées héritent des membres de leurs classes de base et un modificateur d'accès de dérivation restreint leur accès :
| Dérivation | Membre public | Membre protégé | Membre privé |
|---|---|---|---|
| publique | reste public | reste protégé | inaccessible |
| protégée | devient protégé | reste protégé | inaccessible |
| privée | devient privé | devient privé | inaccessible |
Tout comme les membres, le modificateur d'accès de dérivation par défaut est privé.
Constructeurs
Si une classe de base ne possède pas de constructeur par défaut, un de ses constructeurs doit être appelé explicitement dans la liste d'initialisation du constructeur dérivé :
Derivee(bool parametreA, int parametreB, double parametreC) : Base1(parametreA), Base2(parametreB) {
donneeMembre = parametreC;
}
Polymorphisme
Lorsqu'une classe dérive d'une autre, ses instances peuvent prendre toutes les formes des classes de base publiques :
Base1* instanceA = new Derivee(true, 7, 0.1);
Derivee* instanceB = new Derivee(false, 42, 2.3);
Il est possible de métamorphoser une instance avec un transtypage dynamique :
Derivee* instanceDerivee = dynamic_cast<Derivee*>(instanceA);
Si la métamorphose n'est pas valide, nullptr sera retourné.
Routage
À venir...
Abstraction
À venir...
Structures de données
La bibliothèque standard du langage offre quelques structures de données.
Dictionnaire
Un dictionnaire comporte des paires dont la première donnée membre est la clé et la seconde, la valeur associée :
std::unordered_map<std::string, std::string> capitales;
Un dictionnaire s'utilise tout comme un tableau, mais l'indexation se fait avec la clé plutôt qu'avec un indice :
capitales["France"] = "Paris";
Parcours
Plusieurs syntaxes permettent de parcourir cette structure de données :
for (auto it = capitales.begin(); it != capitales.end(); ++it)
Puisque le parcours de structure est fréquent, une syntaxe allégée est aussi possible :
for (const auto& paire : capitales)
Et plutôt que d'utiliser les données membres first et second, il est possible de leur attribuer des identifiants :
for (const auto& [key, value] : capitales)
Recherche
La méthode find permet d'effectuer une recherche d'une clé et de retourner un itérateur sur la donnée associée. Dans le cas où la clé n'est pas trouvée, c'est l'itérateur de fin de la structure qui est retourné.
auto france = capitales.find("France");
if (france != capitales.end())
std::cout << "La capitale de " << france->first << " est " << france->second << std::endl;