Aller au contenu principal

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.

Bjarne Stroustrup

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èsSymboleDescription
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érivationMembre publicMembre protégéMembre privé
publiquereste publicreste protégéinaccessible
protégéedevient protégéreste protégéinaccessible
privéedevient 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;