Latest article: Exporter des données depuis Excel

Cours simplifié de C++

Le C++ a la réputation d’être un langage compliqué et difficile à aborder. Je pense que c’est faux. Cet article est motivé par ma… consternation devant les cours de C++ qu’on a à l’école. On aurait dit que le prof (qui n’avait pas lu le cours), en s’acharnant sur des détails sans intérêt, cherchait volontairement à perdre les élèves.

Alors non, le C++ n’est pas si compliqué. Il peut être complexe, mais uniquement dans des situations complexes. Le but de ce cours est donc de montrer le C++ d’une façon simple, ce C++ qui suffit pour la plupart des cas étudiés en cours.

L’objectif est de donner suffisamment d’armes au lecteur pour qu’il continue son apprentissage du C++ par lui même. Pour un cours complet et exhaustif, consultez l’excellent cours de Christian Casteyde.

Je pars du principe que le lecteur a déjà eu des cours de C, qu’il sait donc ce qu’est une variable, une fonction, un en-tête (fichier .h), un prototype de fonction et la différence avec son implémentation (fichier .cpp) et ce qu’est une structure.

Les espaces de nom (namespace)

Un gros problème lors des grands projets qui impliquent de nombreuses bibliothèques est le conflit des noms. Deux fonctions ou classes ne peuvent pas avoir le même nom. Pour éviter ça, les développeurs C rajoutaient des préfixes à leurs fonctions et structures. C’était assez fastidieux à utiliser et produisait des noms à rallonge.

En C++, nous bénéficions des espaces de nom, ou namespaces. Leur but est d’éviter les ambigüités tout en permettant d’éviter les noms trop long.

Ainsi les instructions using namespace et using permettent de ne plus spécifier ce “préfixe” si il n’y a pas d’ambigüité.

std::string chainecaractere = "blabla";

Le std est l’espace de nom dans lequel est contenu string, il peut être rendu facultatif de deux façons différentes :

using std::string; // plus besoin de spécifier std pour string, et uniquement string
using namespace std; // plus besoin de spécifier std pour tous les composants de l'espace de nom

Attention cependant à ne jamais utiliser ces deux instructions dans un fichier d’en-tête (.h), car en incluant le fichier, elles viendrait s’appliquer à l’ensemble de votre projet. On perdrait alors tout l’intérêt de la chose.

Pour déclarer un espace de nom pour nos propres classes et fonctions :

namespace NotreNamespace
{
    // le prototype de nos fonctions, classes, etc.
}

Lire et écrire dans la console

En C on utilisait printf et scanf (entre autre) pour afficher du texte. Il fallait utiliser une chaine de caractère pour formater le tout, c’était plutôt laborieux.

En C++ on utilisera les flux, en particulier cout et cin pour la console (mais le fonctionnement est le même pour lire et écrire dans les fichiers). Le but n’est pas de détailler comment ils fonctionnent (surcharge des opérateurs << et >>) mais comment les utiliser simplement.

Tout d’abord cout et cin sont des objets globaux définis dans le namespace std. On les utilisera de la façon suivante :

std::cout << "Hello world !" << std::endl;

int annee = 2008;
std::cout << "Nous sommes en " << annee << std::endl;

std::endl est un saut de ligne portable, il équivaut à “\n” sous Linux et Mac et à “\r\n” sous Windows.

Pour lire, on utilisera cin :

int annee;

std::cout << "En quelle année sommes nous ?" << std::endl;
std::cin >> annee;

Attention au sens des double chevrons, pour vous aider, suivez le sens des flèches :

  • On “rentre” dans cout le texte à afficher.
  • On “sort” de cin le contenu des variables.

La programmation orientée objet

La programmation orientée objet est là pour simplifier la tâche des programmeurs, rendre la programmation plus facile. Et oui.

Le but de la “POO” est de représenter le monde réel dans son programme, rendre le tout moins abstrait. Dans la réalité, on agit sur des objets : j’ouvre la porte, je m’assoie sur une chaise, je parle à quelqu’un, etc.

Alors l’idée c’est de représenter ces objets en C++ au travers de classes. Pour ça on va définir des attributs et des méthodes.

Les classes

Pierre et Paul sont tous deux des humains, ce sont des objets appartenant à la même classe “Humain”. Tous les objets instancient une classe. C’est en quelque sorte le type d’un objet. Souvenez-vous des structures en C, et bien une classe c’est la même chose, mais de façon étendue.

Les classes en C++ ont d’ailleurs la même syntaxe que les structures en C (d’ailleurs les deux notions sont confondues en C++) :

class Chat
{
};

Les attributs

Un attribut est une propriété d’un objet. La couleur de la voiture, la taille d’une personne, l’age du chat, tout ça sont des attributs. Ce sont les valeurs qui définissent l’objet, qui font de lui ce qu’il est, qui le distinguent des autres objets similaires.

Pour faire un parallèle avec le C, un attribut est une variable contenue dans une structure. Une fois de plus, la syntaxe est la même :

class Chat
{
    int Age;
};

Les méthodes

Les méthodes sont les actions que peut réaliser l’objet. Le chat peut miauler, notre classe Chat aura donc une méthode miauler. C’est ce qui permet aux objets d’interagir entre eux.

Une méthode peut agir sur la classe elle même (le chat se lave, son action modifie sont état) ou non (le chat miaule).

Elle peut aussi agir sur un autre objet (le chat griffe le chien), ou utiliser un autre objet (le chat mange ses kwiskas).

Une méthode est une fonction, elle peut donc avoir des arguments (on parle aussi de paramètres) et une valeur de retour.

La différence avec le C c’est qu’une structure (une classe) peut désormais contenir des fonctions (des méthodes).

Ainsi :

class Chat
{
    int Age;

    void Miauler();
    void Manger ( Nourriture objetnourriture );
};

objetnourriture est un autre objet de type Nourriture. C’est ainsi qu’interagissent nos objets.

L’encapsulation

Mais attention, tout le monde ne peut pas modifier les attributs d’un objet, ce n’est pas souhaitable.

Il n’est pas logique qu’un autre objet modifie l’age du chat, il convient donc de protéger cet attribut.

C’est l’encapsulation, on va cacher certains attributs et certaines méthodes, et en exposer d’autres. C’est quelque chose de crucial lors du développement en équipe, où vous ne souhaitez pas qu’un autre développeur fasse n’importe quoi avec vos objets, au risque de causer des bugs. L’encapsulation est donc une protection contre les erreurs et les bugs.

Il existe trois niveaux de portée pour les attributs et méthodes :

  • public : l’attribut ou la méthode est accessible par tous les autres objets
  • protected : l’attribut ou la méthode n’est accessible que depuis la classe elle même et ses enfants (nous reviendront sur les enfants et l’héritage plus tard)
  • private : l’attribut ou la méthode n’est accessible que depuis la classe elle même.

Ils s’utilisent de la manière suivante :

class MaClasse
{
    public :

        int Attribut1; // cet attribut est accessible en dehors de la classe

    private :

        int Attribut2; // pas celui là !

};

Créer et utiliser un objet

Un peu de concret, nous allons créer un objet de type Perroquet (on parle d’instanciation) et utiliser ses attributs et méthodes :

class Perroquet
{

    private :

        int Age;

    public :

         void Dire( string paroles );

         void Vieillir();

         string Nom;

};

Nous avons définit la le prototype de la classe, ce code ira dans un fichier d’en-tête (fichier Perroquet.h).

Nous devons maintenant implémenter la méthode Dire :

#include <iostream>
#include "Perroquet.h"
using namespace std;

void Perroquet::Dire ( string paroles )
{
    cout << paroles << endl;
}

void Perroquet::Vieillir()
{
    Age++;
}

Ceci est notre fichier Perroquet.cpp. C’est l’implémentation classique d’une fonction, comme en C, à un détail près : le Perroquet:: devant Dire.

Les double deux points sont l’opérateur de portée, ils servent à dire que la fonction Dire que l’on implémente appartient à la classe Perroquet.

Dans la méthode Vieillir, on utilise une variable nommée Age. Cette variable est en réalité l’attribut Age de la classe Perroquet.

Les méthodes peuvent donc utiliser les attributs, ici la méthode Vieillir du perroquet incrémente l’attribut Age de ce même perroquet.

Voyons comment utiliser notre objet :

#include "Perroquet.h"

int main ()
{

    Perroquet coco;
    coco.Nom = "Coco";
    coco.Dire ( "hello world" );
    coco.Vieillir();

    return 0;
}

L’objet s’utilise comme une variable classique.

Le constructeur et le destructeur

Le constructeur et le destructeur sont des méthodes appelées automatiquement à la construction et la destruction d’un objet.

Ils sont facultatifs (le compilateur en créer par défaut) mais peuvent être spécifiés par le développeur à condition de respecter certaines règles :

  • Ils doivent porter le même nom que la classe
  • Ils ne renvoient pas de valeur de retour (pas même void)
  • Le destructeur a un tilde (~) devant son nom (pour le différencier du constructeur)
  • Le constructeur peut prendre des arguments, le destructeur non
  • On peut avoir autant de constructeurs que l’on veut, mais un seul destructeur
  • Ils doivent être public (sauf dans des cas très particuliers comme le singleton)

Exemple :

class Chat
{
    public :

        // Constructeur
        Chat ( std::string nom );

        // Destructeur
        ~Chat();

        std::string Nom;
        int Age;
};

Et dans le cpp :

Chat::Chat ( std::string nom )
{
    Nom = nom;
    Age = 1;

    std::cout << "On a créer un chat !" << std::endl;
}

Chat::~Chat ()
{
     std::cout << "On a détruit un chat !" << std::endl;
}

Un exemple d’utilisation pour voir quand le constructeur et le destructeur sont appelés :

int main()
{
    Chat monchat ( "Garfield" ); // le constructeur est appelé, on lui passe le nom du chat en paramètre

    std::cout << "Le chat s'appelle " << monchat.Nom << " et a " << monchat.Age << " an(s)" << std::endl;

    // fin du bloc, l'objet monchat est détruit, le destructeur est appelé
    return 0;

}

L’héritage et le polymorphisme

Les termes font peur. Mais en réalité c’est simple à appréhender.

On a dit que Pierre et Paul sont des Humains. Et les Humains sont des Mammifères, qui eux même sont des Etres Vivants.

Un Humain hérite des attributs et méthodes d’un Mammifère, tout comme le Chat. Pour représenter cela, on utilisera l’héritage. Une classe fille hérite d’une classe mère. La syntaxe est la suivante :

class Mammifere
{
    public :

        int Age;

};

class Chat : public Mammifere
{
    // l'attribut Age de Mammifere sera disponible pour Chat
};

class Humain : public Mammifere
{
     // de même pour Humain
};

Le polymorphisme consiste à faire passer une classe fille pour sa classe mère :

void AfficherAge ( Mammifere mammifere )
{
    std::cout << "Age du mammifère : " << mammifere.Age << std::endl;
}

On pourra alors passer comme argument à la fonction AfficherAge un Mammifere, un Humain ou un Chat (puisque l’attribut Age est disponible pour toutes). C’est le principe du polymorphisme.

Mentions

    Discussion

    1. Dana

      May 10, 2009
      3:51 pm

      Excellent cours rapide et simple qui m’a permis une mise à niveau rapide (n’étant pas un débutant dans les langages web) au niveau de la syntaxe du cpp.

      Merci, bonne continuation =)

      Reply

    2. Arthur

      January 21, 2013
      7:45 pm

      Très bon cours rapide et bonne syntaxe du cpp !
      Merci beaucoup !

      Reply

    3. youyou

      December 26, 2013
      12:28 pm

      Excellent cours

      Reply

    4. Skatox Nike

      January 30, 2015
      8:02 pm

      Très bien expliqué!

      Reply

    5. Skatox Nike

      January 30, 2015
      8:03 pm

      Pour les débutant dans le domaine, c’est très bien!

      Reply

    Reply