C++ Exercises from Claude Delannoy's 3rd Edition

Kart yok

The provided material is a collection of C++ exercises from a PDF document titled "Exercices en langage C++" by Claude Delannoy, 3rd edition. The exercises cover a wide range of C++ topics, including basic syntax, control structures, functions, arrays, pointers, strings, structures, classes, inheritance, virtual functions, operator overloading, templates, exception handling, and the C++ Standard Library. The exercises also include solutions and explanations for many of them.

Introduction au langage C++ et aux concepts de Programmation Orientée Objet

Le langage C++ est une extension du C, introduisant des concepts puissants de Programmation Orientée Objet (POO). Cet ensemble de notes vise à éclaircir les fondamentaux du C++, en se concentrant sur les exercices et les applications pratiques.

Principes Fondamentaux du C++

  • Syntaxe de base: Un programme C++ minimal inclut `iostream` pour les entrées-sorties et utilise l'espace de noms `std`.
    #include <iostream>
    using namespace std ;
    main()     // en-tête
    { ... // corps du programme
    }
  • Déclarations de variables: Toutes les variables doivent être déclarées avec leur type, et peuvent être initialisées.
    int i ;   // i est une variable de type int nommée i
    float x = 5.25 ;  // x est une variable de type float nommée x
    const int NFOIS = 5; // NFOIS est une variable constante
  • Entrées/Sorties Standard:
    • Affichage: Le flot `cout` est utilisé pour afficher des informations.
      cout << n << 2*p ; // affiche les valeurs de n et de 2*p sur l'écran
    • Lecture: Le flot `cin` est utilisé pour lire des informations du clavier.
      cin >> x >> y ; // lit deux valeurs au clavier et les affecte à x et à y

Types de base, Opérateurs et Expressions

Un aperçu des types de données fondamentaux et des opérateurs disponibles en C++.

Types de base

Les types de base sont les blocs de construction pour tous les autres types.
  • Types entiers: `short int`, `int`, `long int`. Également des versions non signées: `unsigned short int`, `unsigned int`, `unsigned long int` pour la manipulation de motifs binaires. Des constantes entières peuvent être en hexadécimal (ex: `0xF54B`) ou octal (ex: `014`).
  • Types flottants: `float`, `double`, `long double`. Leur précision et domaine dépendent de l'implémentation.
  • Types caractères: `signed char`, `unsigned char`, `char`. Les constantes caractères sont entre apostrophes (ex: `'A'`). Des caractères spéciaux utilisent le préfixe `\` (ex: `'\n'`, `'\x41'`).
  • Type booléen: `bool` avec les constantes `true` et `false`.

Opérateurs

Les opérateurs en C++ sont classifiés par catégorie et associativité.
Catégorie Opérateurs Associativité
Résolution de portée `::` (unitaire globale, binaire classe) <--
Référence `()` `[]` `->` `.` -->
Unaire `+` `-` `++` `--` `!` `~` `*` `&` `sizeof` `cast` `dynamic_cast` `static_cast` `reinterpret_cast` `const_cast` `new` `new[]` `delete` `delete[]` <---
Sélection `->*` `.*` <--
Arithmétique `*` `/` `%` -->
Arithmétique `+` `-` -->
Décalage `<<` `>>` -->
Relationnels `<` `<=` `>` `>=` -->
Relationnels `==` `!=` -->
Manipulation de bits `&` -->
Manipulation de bits `^` -->
Manipulation de bits `|` -->
Logique `&&` -->
Logique `||` -->
Conditionnel (ternaire) `? :` -->
Affectation `=` `+=` `-=` `*=` `/=` `%=` `&=` `^=` `|=` `<<=` `>>=` <---
Séquentiel `,` -->

Conversions Implicites

  • Ajustement de type: `int -> long -> float -> double -> long double` ou `unsigned int -> unsigned long -> float -> double -> long double`.
  • Promotions numériques: `char`, `bool`, `short` sont convertis en `int`.

Opérateurs Logiques (`&&`, `||`, `!`)

Ces opérateurs acceptent des opérandes numériques ou pointeurs, où toute valeur non nulle est considérée comme `vrai`.
Opérande 1 Opérateur Opérande 2 Résultat
0 `&&` 0 faux
0 `&&` non nul faux
non nul `&&` 0 faux
non nul `&&` non nul vrai
0 `||` 0 faux
0 `||` non nul vrai
non nul `||` 0 vrai
non nul `||` non nul vrai
`!` 0 vrai
`!` non nul faux
Les opérateurs `&&` et `||` sont à "court-circuit", le second opérande n'est évalué que si nécessaire.

Opérateurs d'Affectation

L'opérande de gauche doit être une `lvalue` (quelque chose de modifiable). Les conversions peuvent être dégradantes.
valeur = 2*n-1 ? a : b ;
// affecte à "valeur" la valeur de "a" si (2*n-1) est non nul, sinon la valeur de "b".

Opérateurs d'Incrémentation et Décrémentation (`++`, `--`)

  • Préfixe (`++n`): Modifie la valeur puis la fournit.
  • Postfixe (`n++`): Fournit la valeur puis la modifie.

Opérateur `cast`

Permet de forcer la conversion d'une expression dans un type choisi.
(double) n / p // Convertit n en double avant la division
// équivalent à static_cast<double>(n/p)

Instructions de contrôle

Une instruction peut être simple (terminée par `;`), structurée (choix, boucle) ou un bloc (`{ ... }`).

Instructions Conditionnelles

  • `if-else`:
    if (expression) instruction_1
    else instruction_2
    Un `else` se rapporte toujours au dernier `if` sans `else` attribué.
  • `switch`: Évalue une expression entière et exécute le bloc d'instructions correspondant à une étiquette `case`.
    switch (expression) { bloc_d_instructions }
    • Les valeurs `case` doivent être des expressions `constantes`.
    • `break` permet de sortir du bloc `switch`.
    • `default` est exécuté si aucune valeur `case` ne correspond.

Instructions Itératives (Boucles)

L'expression de contrôle de la boucle est convertie en `bool` (non nul = vrai, nul = faux).
  • `do-while`: Exécute l'instruction au moins une fois, puis répète tant que l'expression est vraie.
    do instruction while (expression) ;
  • `while`: Répète l'instruction tant que l'expression est vraie.
    while (expression) instruction
  • `for`: Pour des boucles avec une initialisation, une condition et une incrémentation.
    for ([expression_déclaration_1] ; [expression_2] ; [expression_3]) instruction
    Équivalent à:
    { expression_déclaration_1 ;
    while (expression_2) { instruction ;
    expression_3 ;
    }
    Les crochets indiquent que le contenu est facultatif.

Instructions de Saut

  • `break`: Interrompt l'exécution de la boucle ou du `switch` le plus interne.
  • `continue`: Passe au tour de boucle suivant.
  • `goto`: Permet un branchement inconditionnel à une étiquette de programme.

Fonctions en C++

Une fonction est un bloc d'instructions, potentiellement paramétré et pouvant retourner une valeur.

Définition et Déclaration

  • Définition: Corps de la fonction et sa signature.
    float fexple(float x, int b, int c)  // en-tête
    {  // corps de la fonction
    }
  • Déclaration (Prototype): Spécifie la signature de la fonction avant son utilisation.
    float fepxle(float, int, int) ; // Déclaration (prototype)
    La déclaration peut être omise si la définition est rencontrée avant l'appel.

Transmission des Arguments

  • Par valeur (par défaut): Une copie de l'argument est passée à la fonction. Les modifications n'affectent pas l'original.
  • Par référence (`&`): Le type de l'argument est suivi de `&`. Les modifications dans la fonction affectent l'argument effectif. L'argument doit être une `lvalue` du même type. Si `const` est utilisé, une copie temporaire peut être créée.

Valeur de Retour

  • `return`: Termine l'exécution de la fonction et retourne une valeur.
  • `void`: Indique qu'une fonction ne retourne aucune valeur.
  • Liste d'arguments vide: `fSansArguments ()` pour une fonction sans arguments.

Valeurs par Défaut pour les Arguments

Des valeurs par défaut peuvent être spécifiées pour les derniers arguments dans le prototype.
float fct (char, int = 10, float = 0.0);
L'appel `fct('a')` est équivalent à `fct('a', 10, 0.0)`.

Portée et Classe d'Allocation des Variables

  • Variables globales: Déclarées en dehors de toute fonction; portée au-delà de leur déclaration dans le fichier. Classe d'allocation statique (emplacement mémoire fixe).
  • Variables locales: Déclarées dans une fonction (ou un bloc); portée limitée à celle-ci. Classe d'allocation automatique (allouées à l'entrée, libérées à la sortie).
  • Variables `static` locales: Locales mais de classe d'allocation statique. Initialisées une seule fois à zéro par défaut.

Surdéfinition des Fonctions (Surcharge)

Plusieurs fonctions peuvent avoir le même nom si leurs signatures (nombre et types d'arguments) diffèrent. Le compilateur choisit la bonne fonction en fonction des arguments. Peut impliquer des conversions (promotions numériques, conversions standard, conversions définies par l'utilisateur).

Fonctions `inline`

Fonctions dont le code est inséré directement à l'endroit de l'appel pour éviter le coût d'un appel usuel.
  • Déclarées avec le mot-clé `inline`.
  • La définition doit être présente lors de la déclaration.

Tableaux, Pointeurs et Chaînes de caractères à la C

Ce chapitre couvre la manipulation des structures de données fondamentales en C++.

Tableaux à un indice

Un tableau est une collection d'éléments de même type, accessibles par un indice (le premier élément est à l'indice 0).
float t [10] ; // Déclare un tableau de 10 floats
  • Un élément de tableau est une `lvalue`.
  • L'indice peut être n'importe quelle expression arithmétique de type entier.
  • La taille du tableau doit être une expression constante.

Tableaux à plusieurs indices

Déclarés avec plusieurs paires de crochets (ex: `int t[5][3];`). Les éléments sont stockés en mémoire en faisant varier le dernier indice en premier.

Initialisation des tableaux

  • Les tableaux de classe statique sont initialisés à zéro par défaut.
  • Les tableaux de classe automatique ne sont pas initialisés par défaut.
  • L'initialisation pendant la déclaration est possible:
    int t1[5] = { 10, 20, 5, 0, 3 } ;
    int t2 [5] = { 10, 20 } ; // Les éléments restants sont à zéro
  • Pour les tableaux multidimensionnels, on peut utiliser des listes imbriquées ou une liste plate:
    int tab [3] [4] = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9,10,11,12 } } ;
    int tab [3] [4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 } ;

Pointeurs

Une variable pointeur stocke l'adresse d'une autre variable.
int * adi ; // pointeur vers un entier
float * adf ; // pointeur vers un flottant
  • Opérateur `&`: Retourne l'adresse d'une variable (ex: `adi = &n;`).
  • Opérateur `*`: Déréférence un pointeur, accédant à la valeur stockée à l'adresse (ex: `int p = *adi;`).
  • Pointeur générique: `void *` peut pointer vers n'importe quel type de données.

Arithmétique des pointeurs

Les pointeurs peuvent être incrémentés ou décrémentés. L'unité de modification est la taille du type pointé.
adi++; // adi pointe sur l'entier suivant
adi -= 10; // adi pointe 10 entiers avant

Relations entre tableaux et pointeurs

  • Un nom de tableau est une constante pointeur vers son premier élément.
    int t[10];
    t+1 est équivalent à &t[1];
    t[i] est équivalent à *(t+i).
  • Lorsqu'un tableau est passé en argument à une fonction, c'est son adresse qui est transmise.
  • Déclarations équivalentes pour un argument de fonction:
    void fct (int t[10])
    void fct (int t[])
    void fct (int * t)

Allocation dynamique avec `new` et `delete`

new type [n] // Alloue n éléments de 'type', retourne un pointeur
delete adresse // Libère la mémoire allouée par new
En cas d'échec d'allocation, une exception `bad_alloc` est déclenchée.

Pointeurs sur fonctions

Permettent de manipuler des fonctions comme des variables.
int (* adf) (double, int) ; // adf est un pointeur sur une fonction

Chaînes de caractères à la C

Suite d'octets terminée par un octet nul (`\0`).
char ch[20] = "bonjour" ;
// Équivalent à:
char ch[20] = { 'b', 'o', 'n', 'j', 'o', 'u', 'r', '\0' } ;
Les `cout` et `cin` peuvent manipuler directement ces chaînes.

Arguments de la fonction `main`

La fonction `main` peut recevoir des arguments au lancement du programme:
main (int nbarg, char * argv[])
où `nbarg` est le nombre d'arguments et `argv` est un tableau de chaînes de caractères (les arguments).

Structures

Les structures en C++ permettent de regrouper des données de types différents sous un seul nom.

Définition d'une structure

La déclaration d'une structure définit un nouveau type, mais n'alloue pas de mémoire.
struct enreg {
    int numero ;
    int qte ;
    float prix ;
};
  • Le type `enreg` est maintenant défini.
  • Des variables peuvent être déclarées avec ce type : `enreg art1, art2 ;`.

Accès aux membres d'une structure

Les champs d'une structure sont accessibles avec l'opérateur `.` (point).
art1.numero ; // Accède au champ 'numero' de la structure 'art1'
art2.prix ;   // Accède au champ 'prix' de la structure 'art2'

Affectation et initialisation des structures

  • Une structure peut être affectée à une autre structure de même type (`art1 = art2;`).
  • Initialisation lors de la déclaration :
    enreg art1 = { 100, 285, 200 } ;
  • Les structures de classe statique initialisent leurs champs à zéro par défaut. Celles de classe automatique ne sont pas initialisées.

Transition du C au C++

C++ est presque un sur-ensemble du C, mais présente des différences clés et des fonctionnalités additionnelles. Ce chapitre est pertinent pour les programmeurs C souhaitant passer à C++.

Incompatibilités et Spécificités

  • Déclaration de fonctions: En C++, toutes les fonctions doivent être déclarées via un prototype avant leur utilisation. Le C permettait des déclarations implicites ou incomplètes.
  • Fonctions sans arguments: En C++, utilisez `float fct()`. En C, `float fct(void)` était également courant.
  • Fonctions sans valeur de retour: En C++, utilisez `void` explicitement (ex: `void fct(int, double)`). En C, `void` était facultatif.
  • Variables `const` globales: En C++, elles ont une portée limitée au fichier. En C, elles pouvaient être `extern`. De plus, les `const` C++ peuvent être utilisées dans des expressions constantes (contrairement au C qui nécessitait `#define`).
  • `void *`: En C++, un `void *` ne peut pas être converti implicitement en un autre type de pointeur lors d'une affectation (un `cast` explicite est requis).

Nouvelles fonctionnalités C++

  • Entrées-sorties avec `iostream`: `cout` et `cin` remplacent `printf` et `scanf`, offrant une gestion des types plus souple.
    cout << expression_1 << expression_2 ;
    cin >> lvalue_1 >> lvalue_2 ;
    Les opérateurs `<<` et `>>` gèrent automatiquement les conversions de type.
  • Commentaires de fin de ligne: `//` commente le reste de la ligne.
  • Déclarations de variables dynamiques: Les déclarations ne sont plus limitées au début des blocs ou fonctions.
    for (int i=0 ; ... ; ...) { ... } // Portée de i limitée à la boucle
  • Transmission par référence: Via l'opérateur `&`. Permet de modifier l'argument original.
    void fonction(int &arg) ;
  • Valeurs par défaut pour les arguments: Définies dans le prototype de la fonction.
    float fct (char, int = 10, float = 0.0);
  • Surdéfinition de fonctions: Permet à plusieurs fonctions de porter le même nom si leur signature diffère, avec résolution par le compilateur.
  • Opérateurs `new` et `delete`: Remplacent `malloc`, `calloc` et `free` pour l'allocation et la désallocation dynamique.
    new type [n] ; // Alloue n éléments
    delete adresse ; // Libère la mémoire
  • Fonctions `inline`: Mot-clé `inline` pour optimiser les appels de fonctions.

Notions de Classe, Constructeur et Destructeur

Le cœur de la POO en C++ se trouve dans les classes, qui encapsulent données et fonctions.

Concept de Classe

Une classe est un type défini par l'utilisateur qui associe des données (membres donnée) et des fonctions (fonctions membre ou méthodes). L'encapsulation signifie que l'accès aux données se fait généralement via les méthodes.

Déclaration d'une Classe

Spécifie les membres `public` (accessibles) et `private` (inaccessibles) en utilisant les mots-clés correspondants. Un troisième mot-clé, `protected`, intervient avec l'héritage.
class point
{
private:
    int x;
    int y;
public:
    void initialise(int, int);
    void deplace(int, int);
    void affiche();
};

Définition des Fonctions Membre

Les fonctions membre sont définies en spécifiant le nom de la classe avec l'opérateur de résolution de portée `::`.
void point::initialise(int abs, int ord)
{
    x = abs; y = ord;
}
À l'intérieur de la définition, les membres sont directement accessibles.

Déclaration et Utilisation d'Objets

Un objet est une instance d'une classe.
point a, b; // Déclare deux objets 'a' et 'b' de type 'point'
a.initialise(5, 2); // Appelle une fonction membre
L'affectation d'objets de même type est possible et recopie les champs de données. Pour les pointeurs membres, une "surdéfinition" de l'opérateur d'affectation peut être nécessaire pour une copie profonde.

Constructeurs et Destructeurs

  • Constructeur: Fonction membre portant le même nom que la classe. Appelée après l'allocation mémoire pour un objet, initialise ses membres. Ne retourne pas de valeur.
  • Destructeur: Fonction membre précédée d'un tilde (`~`) et portant le nom de la classe. Appelée avant la libération de la mémoire de l'objet. Ne prend pas d'arguments et ne retourne pas de valeur.

Membres `static`

Un membre donnée déclaré `static` est partagé par tous les objets de la classe et existe même si aucun objet n'est créé. Il doit être initialisé en dehors de la classe.
static int nb_pts = 0; // Initialisation du membre statique

Organisation des fichiers pour les classes

  • Fichier en-tête (`.h`): Contient la déclaration de la classe.
  • Fichier source (`.cpp`): Contient la définition des fonctions membre.

Propriétés des Fonctions Membre

Ce chapitre explore les caractéristiques avancées des fonctions membre.

Fonctions Membre `inline`

Peuvent être définies directement dans la déclaration de la classe (implicitement `inline`) ou avec le mot-clé `inline` si définies à l'extérieur.

Arguments de type classe

Une fonction membre peut prendre un objet de la même classe (accès aux membres `private`) ou d'une autre classe (accès aux membres `public` seulement) comme argument. La transmission par valeur peut créer des problèmes avec les pointeurs dynamiques (nécessite un "constructeur par recopie").

Valeurs de retour de type classe

Une fonction membre peut retourner un objet. La transmission par valeur est souvent nécessaire, car retourner l'adresse ou la référence d'un objet local (`auto`) peut être dangereux.

Pointeur `this`

Dans une fonction membre, `this` est un pointeur vers l'objet qui a appelé la fonction.

Fonctions Membre `static`

Déclarées avec `static`, elles agissent indépendamment de tout objet. Peuvent être appelées via `Classe::nomFonction()`.

Objets `const` et fonctions membre `const`

Pour des objets `const`, seules les fonctions membre déclarées avec `const` (ex: `void affiche() const;`) peuvent être appelées.

Construction, destruction et initialisation des objets

Ce chapitre approfondit le cycle de vie des objets.

Quand le constructeur/destructeur est appelé

  • Constructeur: Après l'allocation mémoire.
  • Destructeur: Avant la libération mémoire.

Objets automatiques et statiques

  • Automatiques: Créés via une déclaration dans une fonction ou un bloc. Durée de vie limitée au bloc.
  • Statiques: Créés en dehors de toute fonction ou avec le mot-clé `static` dans une fonction/bloc. Créés avant `main`, détruits après `main`.

Objets temporaires

Créés par l'appel explicite d'un constructeur dans une expression (ex: `a = point(1.5, 2.25);`). Détruits automatiquement quand ils ne sont plus utiles.

Objets dynamiques

Créés avec `new`, détruits avec `delete`.
point * adp = new point(2.5, 5.32);
delete adp;
Pour les tableaux d'objets, utilisez `delete [] adr;`.

Membres de type classe

Si une classe B contient un membre de type classe A, le constructeur de A est appelé à partir du constructeur de B, via une "liste d'initialisation" :
pointcol(float abs, float ord, int coul) : p(abs, ord) { /* ... */ }

Initialisation par recopie

Se produit dans des situations comme `point b = a;`, ou lors de la transmission d'objets par valeur (argument ou valeur de retour). Un "constructeur par recopie" (`type(const type&);`) est utilisé. La transmission par référence est obligatoire pour le constructeur par recopie pour modifier l'objet.

Tableaux d'objets

Créent plusieurs objets en appelant le constructeur pour chaque élément.
point courbe[20]; // 20 objets 'point'
point * adcourbe = new point[20]; // Allocation dynamique
La destruction d'un tableau d'objets dynamique nécessite `delete [] adcourbe;`.

Fonctions Amies

Une fonction amie peut accéder aux membres `private` et `protected` d'une classe.

Déclaration d'une fonction amie

Utilisez le mot-clé `friend` dans la déclaration de la classe.
class A {
    friend void fct(---) ; // fct peut accéder aux membres privés de A
};

Fonction membre d'une autre classe comme amie

class A {
    friend void B::fct(---) ; // B::fct peut accéder aux membres privés de A
};
La déclaration de la classe B doit être connue.

Classe entière comme amie

class A {
    friend class B ; // Toutes les fonctions de B peuvent accéder aux membres privés de A
};
Utile pour les collaborations étroites entre classes.

Surdéfinition d'Opérateurs

Principe

Permet de donner une nouvelle signification aux opérateurs existants lorsqu'ils agissent sur des objets de type classe.

Définition

Une fonction `operator op` est définie comme:
  • Fonction indépendante (généralement amie): `operator op(a, b)`
  • Fonction membre: `a.operator op(b)` (pour opérateur binaire)

Règles

  • Ne peut surdéfinir que les opérateurs existants, en conservant leur arité (unaire, binaire).
  • Doit toujours avoir au moins un opérande de type classe.
  • Les opérateurs `[]`, `()`, `->`, `new` et `delete` doivent être des fonctions membre.
  • Les opérateurs `=` et `&` ont une signification par défaut qui peut être surdéfinie.

Tableau des opérateurs surdéfinissables (par priorité décroissante)

Pluralité Opérateurs Associativité
Binaire `()` `[]` `->` `->`
Unaire `+` `-` `++` `--` `!` `~` `*` `&` `new` `new []` `delete` `delete []` `(cast)` `<-`
Binaire `*` `/` `%` `->`
Binaire `+` `-` `->`
Binaire `<<` `>>` `->`
Binaire `<` `<=` `>` `>=` `->`
Binaire `==` `!=` `->`
Binaire `&` `->`
Binaire `^` `->`
Binaire `||` `->`
Binaire `&&` `->`
Binaire `|` `->`
Binaire `=` `+=` `-=` `*=` `/=` `%=` `&=` `^=` `|=` `<<= ` `>>=` `<-`
Binaire `,` `->`

Conversions de Type Définies par l'Utilisateur (CDU)

Permet de définir comment un objet d'une classe peut être converti en un autre type (classe ou de base).

Types de fonctions pour les CDU

Obligatoirement des fonctions membre:
  • Constructeurs à un argument: Convertit le type de l'argument en le type de la classe. Peut être `explicit` pour interdire les conversions implicites.
  • Opérateurs de cast: Permettent de convertir la classe en un autre type.
    operator x () ; // Convertit la classe en type 'x'
    Le type de retour n'est pas spécifié dans le prototype.

Règles d'utilisation des CDU

  • Les conversions ne sont mises en œuvre que si nécessaire.
  • Une seule CDU peut intervenir dans une chaîne de conversions.
  • Pas d'ambiguïté (plusieurs chaînes de conversions vers le même type).
Aucune conversion n'est possible avec une transmission par référence non-`const`.

La Technique de l'Héritage

L'héritage permet de créer de nouvelles classes à partir de classes existantes, en réutilisant et en étendant leur code.

Dérivation de classes

Une classe dérivée (B) hérite d'une classe de base (A).
class B : public A // ou private A, ou protected A
{
    // Membres supplémentaires ou redéfinitions
}

Statut des membres

  • `private`: Inaccessible de l'extérieur ou des classes dérivées.
  • `protected`: Inaccessible de l'extérieur, mais accessible aux fonctions membre des classes dérivées.
  • `public`: Accessible de l'extérieur et des classes dérivées.

Types de dérivation

  • `public`: Les membres de la classe de base conservent leur statut. Le plus courant.
  • `private`: Tous les membres de la classe de base deviennent `private` dans la classe dérivée.
  • `protected`: Les membres `public` de la classe de base deviennent `protected` dans la classe dérivée.
En cas de redéfinition, `Base::membre` accède au membre de la classe de base.

Constructeurs et Destructeurs dans l'Héritage

  • Le constructeur de la classe dérivée doit appeler un constructeur de la classe de base (sauf si la classe de base n'a pas de constructeur ou en a un sans arguments).
    B(int x, int y, char coul) : A(x, y) { /* ... */ }
  • Les destructeurs sont appelés dans l'ordre inverse de la construction.

Initialisation par recopie avec héritage

Si la classe dérivée n'a pas de constructeur par recopie explicite, un constructeur par recopie par défaut est appelé:
  • Appel du constructeur par recopie de la classe de base.
  • Initialisation des membres propres à la classe dérivée.

Compatibilité entre pointeurs et objets de classes liées par héritage

  • Un objet de classe dérivée peut être affecté à un objet de classe de base (`a = b`).
  • Un pointeur sur une classe dérivée peut être affecté à un pointeur sur une classe de base (`ada = adb`).
  • Les inversions sont illégales sans `cast` explicite.

L'Héritage Multiple

Une classe peut hériter de plusieurs classes de base.

Principe

Une classe peut dériver de plusieurs autres :
class C : public B, public A { /* ... */ };
L'ordre de dérivation (ici `B`, puis `A`) détermine l'ordre d'appel des constructeurs.

Accès aux membres

L'opérateur de résolution de portée `::` est crucial pour lever les ambiguïtés si des membres ont le même nom dans différentes classes de base.

Constructeurs et Destructeurs

  • Les constructeurs des classes de base sont appelés dans l'ordre de leur déclaration dans la classe dérivée.
  • Les destructeurs sont appelés dans l'ordre inverse.

Héritage virtuel

Résout le problème du "diamant" où une classe hérite plusieurs fois de la même classe (via des chemins différents). Ajoutez `virtual` à la dérivation de la classe commune pour s'assurer que ses membres n'apparaissent qu'une seule fois.
class B : public virtual A { /* ... */ };
class C : public virtual A { /* ... */ };
class D : public B, public C { /* ... */ };
Le constructeur d'une classe virtuelle est toujours appelé avant les autres.

Les Fonctions Virtuelles

Permettent le polymorphisme, c'est-à-dire le comportement dynamique des fonctions en fonction du type réel de l'objet.

Typage statique vs. dynamique

  • Typage statique: La fonction appelée est déterminée à la compilation, basée sur le type déclaré du pointeur.
  • Typage dynamique: Avec les fonctions `virtual`, la fonction appelée est déterminée à l'exécution, basée sur le type réel de l'objet pointé.

Déclaration d'une fonction virtuelle

Utilisez le mot-clé `virtual` dans la classe de base.
class A {
public:
    virtual void fct();
};

Règles des fonctions virtuelles

  • `virtual` n'est utilisé qu'une fois (dans la classe de base).
  • Une fonction virtuelle peut ne pas être redéfinie dans les classes dérivées.
  • Un constructeur ne peut pas être virtuel, un destructeur le peut (`virtual ~Classe();`).
  • Le mécanisme de ligature dynamique est limité à une hiérarchie de classes.

Fonctions virtuelles pures

Déclarent une fonction virtuelle avec `= 0`, rendant la classe abstraite (on ne peut pas créer d'objets de son type).
virtual void affiche() = 0; // Fonction virtuelle pure
Les classes dérivées doivent implémenter toutes les fonctions virtuelles pures pour être instanciables.

Les Flots d'Entrée et de Sortie

Les flots (streams) sont des canaux pour gérer les transferts de données.

Principes des flots

  • Un flot d'entrée utilise `istream` (ex: `cin`).
  • Un flot de sortie utilise `ostream` (ex: `cout`).

Flot de Sortie (`ostream`)

  • Opérateur `<<`: Surdéfini pour divers types de données (int, float, char*, etc.).
  • Fonctions membre:
    • `put(char c)`: Transmet un caractère.
    • `write(void *adr, int long)`: Envoie un bloc de caractères.

Flot d'Entrée (`istream`)

  • Opérateur `>>`: Surdéfini pour des types de base (sauf pointeurs génériques).
  • Fonctions membre:
    • `get(char &c)` / `get()`: Extrait un caractère.
    • `read(void *adr, int taille)`: Lit un bloc de caractères.

Flot d'Entrée/Sortie (`fstream`)

`iostream` est dérivé de `istream` et `ostream` pour les opérations conversationnelles.

Statut d'erreur du flot

Chaque flot a un statut d'erreur (bits):
  • `eofbit`: Fin de fichier.
  • `failbit`: La prochaine opération échouera.
  • `badbit`: Flot dans un état irrécupérable.
  • `goodbit`: Pas d'erreur.
Fonctions pour vérifier le statut: `eof()`, `bad()`, `fail()`, `good()`, `rdstate()`, `clear()`.

Surdéfinition de `<<` et `>>` pour les classes

Généralement, ces opérateurs sont surdéfinis comme fonctions amies.
ostream &operator<<(ostream &output, YourClass &obj) { /* ... */ return output; }
istream &operator>>(istream &input, YourClass &obj) { /* ... */ return input; }

Formatage des flots

Utilise un `statut de formatage` (mot d'état, gabarit, précision, caractère de remplissage).
  • Bits du mot d'état: `ios::dec`, `ios::oct`, `ios::hex` (base), `ios::showbase`, `ios::showpoint`, `ios::scientific`, `ios::fixed`.
  • Manipulateurs non paramétriques: `dec`, `hex`, `oct`, `endl`, `ends`.
  • Manipulateurs paramétriques: `setbase(int)`, `setprecision(int)`, `setw(int)`. Nécessite `iomanip`.

Fichiers (`fstream`)

  • `ofstream` pour la sortie vers un fichier.
    ofstream flot("fichier.txt", ios::out);
  • `ifstream` pour l'entrée depuis un fichier.
    ifstream flot("fichier.txt", ios::in);
  • Fonctions `seekp()`/`seekg()` pour positionner le pointeur de fichier.
  • `close()` pour fermer le fichier.

Les Patrons de Fonctions

Permettent de créer des fonctions génériques, opérant sur différents types.

Principe

Une seule définition de fonction avec des "paramètres de type" (ex: `template `) ou "paramètres expression" (ex: `template `). Le compilateur instancie les fonctions nécessaires.
template  T carre(T a) { return a * a; }

Paramètres de type

Déclarés avec `class` (ou `typename`) `T`. `T` peut être utilisé comme un type régulier dans la fonction. Le compilateur recherche une "correspondance absolue" des types lors de l'instanciation.

Paramètres expression

Des arguments normaux passés au patron, leur type n'a pas besoin de correspondre exactement.
template  int compte(T * tab, int n) { /* ... */ }

Surdéfinition des patrons de fonctions

Plusieurs patrons du même nom peuvent exister, le compilateur choisit le plus approprié. Il est possible de fournir une "spécialisation" pour des types spécifiques.

Les Patrons de Classes

Permettent de créer des classes génériques, opérant sur différents types.

Principe

Une seule définition de classe avec des paramètres de type et/ou d'expression.
template  class gene { /* ... */ };

Définition des fonctions membre

Si une fonction membre est définie en dehors de la classe, la liste `template` doit être répétée.
template  gene::gene(...) { /* ... */ }

Déclaration d'instances de patrons de classes

Fournir des paramètres effectifs (types ou expressions constantes).
gene c1; // T = int, N = 5
Les paramètres expression doivent être des constantes.

Membres statiques de patrons de classes

Chaque instance de la classe a son propre jeu de membres statiques.

Spécialisation des patrons de classes

On peut spécialiser un patron pour des types ou valeurs d'expressions spécifiques.
// Spécialisation complète
template<> class tableau { /* ... */ };

// Spécialisation partielle
template class point { /* ... */ };

Héritage et patrons de classes

Différentes combinaisons sont possibles:
  • Classe ordinaire dérivée d'un patron de classes.
  • Patron de classes dérivé d'une classe ordinaire.
  • Patron de classes dérivé d'un autre patron de classes.

Gestion des Exceptions

Mécanisme pour gérer les erreurs et les situations anormales de manière structurée.

Déclenchement et interception

  • `throw expression`: Déclenche (lève) une exception.
  • Bloc `try`: Encapsule le code susceptible de lever une exception.
  • Bloc `catch`: Gère une exception d'un type spécifique.
    try { /* ... */ throw n; }
    catch (int n) { /* ... */ }
    catch (...) { /* Gère toutes les autres exceptions */ }

Recherche du gestionnaire

  • Un `catch` est recherché en fonction du type de l'exception (type exact, type de base, pointeur vers classe dérivée).
  • Le premier gestionnaire correspondant est exécuté.

Propagation de l'exception

Une exception non gérée dans un bloc `try` est propagée à la fonction appelante et ainsi de suite. Si aucun gestionnaire n'est trouvé, la fonction `terminate` est appelée (qui par défaut appelle `abort`).

Spécification des exceptions

Une fonction peut spécifier les exceptions qu'elle peut lever avec `throw(...)`.
void maFonction() throw(int, MonException) { /* ... */ }
Une exception non spécifiée lève la fonction `unexpected`.

Exercices de Synthèse

Ces exercices applicatifs combinent les concepts appris pour résoudre des problèmes plus complexes en C++.

Classe `set_int` (ensemble d'entiers)

Implémente un ensemble d'entiers avec des opérateurs surdéfinis pour l'ajout (`<<`), l'appartenance (`%`), et l'affichage (`<<` sur `ostream`). Gère également l'affectation et la transmission par valeur (copie profonde).

Classe `vect` (vecteur dynamique)

Un vecteur dynamique avec accès (`[]`), comparaison (`==`), affichage (`<<` sur `ostream`), et gestion de la copie/affectation.

Classe `bit_array` (tableau de bits)

Un tableau de bits optimisé en mémoire, avec des opérateurs pour activer (`+=`), désactiver (`-=`), compléments (`~`), tester (`[]`), et afficher (`<<` sur `ostream`).

Classe `big_int` (grands entiers)

Manipule des entiers de taille arbitraire (sans signe ici), avec un constructeur par chaîne, addition (`+`), et affichage (`<<` sur `ostream`). Gère les expressions mixtes et la copie/affectation.

Utilisation des Composants Standard (STL)

Mettre en pratique les conteneurs et algorithmes de la STL.

`set` pour les ensembles de caractères

Le conteneur `set` de la STL fournit une implémentation des ensembles, gérant automatiquement la mémoire et l'ordonnancement. L'itérateur `set::iterator` permet de parcourir l'ensemble.

`set` pour les ensembles d'entiers

Similaire à `set`, mais pour les entiers. La gestion interne est automatique, rendant triviale la question de la transmission par valeur ou de l'affectation.

`vector` pour les vecteurs dynamiques

Le conteneur `vector` remplace les tableaux dynamiques manuels, offrant une gestion automatique de la taille et de la mémoire.
  • Opérateur `[]` pour un accès direct.
  • Fonction `at()` qui lève `out_of_range` en cas de débordement d'indice.

`vector>` pour les tableaux 2D dynamiques

Une composition de `vector` pour implémenter des tableaux bidimensionnels dont les dimensions peuvent être définies à l'exécution.

`stack` pour la gestion de piles

Le conteneur `stack` est un adaptateur de conteneur qui modifie l'interface d'un conteneur sous-jacent (comme `vector`) pour simuler une pile. Fonctionnalités: `empty()`, `top()`, `push()`, `pop()`.

`vector` pour les tableaux de bits

Un `vector` optimisé pour stocker des booléens, où chaque élément n'occupe qu'un seul bit de mémoire. L'utilisation de la STL simplifie souvent considérablement la gestion de la mémoire, les itérateurs et les opérations courantes, réduisant le besoin de réimplémenter des fonctionnalités de bas niveau.

Bir quiz başla

Bilgini etkileşimli sorularla test et