Éléments de syntaxe en C++ par l'exemple (POO)

Table des matières

1 Au sujet de ce document

Ce document rassemble sous forme d'exemples documentés, la petite partie de la syntaxe de C++ à maîtriser dans le cadre du cours de POO. Ce document est un complément au support de cours et est à votre disposition pour vous aider à répondre aux sujets de TD, TP et examens. Les constructions et éléments de syntaxe utilisés dans tout le cours POO (TD, TP, exam) sont dans ce document, c'est donc votre référence, n'hésitez pas à revenir le consulter à tout moment.

2 Programme minimal en C++

Le programme minimal en C++. Un programme C++ contient toujours une fonction main(…). Cette fonction retourne un entier (code erreur). Retourner 0 signifie que le programme se termine bien. En général, la fonction main(…) est décrite dans le fichier main.cpp. Un commentaire d'une ligne en C++ commence par //.

int main() { return 0; } // ceci est le programme minimal

3 Programme qui affiche "Bonjour le monde"

En C++, l'affichage d'information à l'écran utilise en général les opérateurs de flux <<. Les opérateurs de flux sont déclarés dans la bibliothèque standard dans le fichier iostream. Dans le programme suivant, la ligne std::cout << "Bonjour le monde\n" se lit:

  • envoie la chaîne de caractères Bonjour le monde\n sur l'entrée standard (qui s'appelle std::cout en C++).

Ce programme contient également un bloc de commentaires comme en C: il démarre par /* et se termine par */

#include<iostream>
/*
 Ceci est le programme qui affiche 

    "Bonjour le monde"


*/
int main()
{
  std::cout << "Bonjour le monde\n";
  return 0;
}

La notation std::cout signifie que l'opérateur cout est dans l'espace de nommage std (namespace). Cette écriture peut parfois être bien lourde. Si le programme va appeler à plusieurs reprises des fonctions de la bibliothèque standard, on peut éviter de répéter l'écriture de std:: en utilisant l'instruction using namespace std; en début de fichier. Le programme précédent peut donc s'écrire:

#include<iostream>

using namespace std;

int main()
{
  cout << "Bonjour le monde\n";
  return 0;
}

4 Afficher à l'écran et lire sur le clavier (entrées/sorties)

Pour afficher à l'écran on utilise les opérateurs de flux de sorties. Pour lire au clavier, on utilise les flux d'entrées.

4.1 Opérateurs de flux de sorties. <<, std::cout

Les opérateurs de flux de sorties rendent l'affichage à l'écran plus flexible qu'en C et remplace le printf(…) du C. Dans le programme suivant, on affiche à l'écran une succession de chaines de caractères, un entier (le résultat de 20+3), un réel (1.82), deux caractères ('m' et le retour chariot '\n'.

Le programme affiche donc: Age: 23 ans, Taille: 1.82m

#include<iostream>

using namespace std;

int main()
{
  cout << "Age: "
       << (20+3)
       <<  " ans, "
       << "Taille: "
       << 1.82
       << 'm'
       << '\n';
  return 0;
}

4.2 Opérateurs de flux d'entrées >>, std::cin

Les opérateurs de flux d'entrées >> servent notamment à lire des données écrites au clavier (std::cin). Le programme suivant lit deux entiers au clavier et retourne la somme.

#include<iostream>

using namespace std;

int main()
{
  int entier1;
  int entier2;
  cout << "Donnez le premier entier: \n";
  cin >> entier1;
  cout << "\nDonnez le deuxième entier: \n";
  cin >> entier2;
  cout << "\nLa somme de " 
       << entier1 
       << " et de " << entier2 
       << " est " << (entier1+entier2) << '\n';
  return 0;
}

5 Itérations

Les itérations en C++ sont identiques à celles du C. Les trois programmes suivants affichent tous les trois la table de multiplication par 9.

5.1 Boucle for:

#include<iostream>

using namespace std;

int main()
{
   cout << "Table de multiplication de 9:\n";
   for(int i = 0; i <= 10; i++)
   {
      cout << i << '*' << 9 <<  " = " << (i*9) << '\n';
   }
  return 0;
}

5.2 Boucle while:

#include<iostream>

using namespace std;

int main()
{
   cout << "Table de multiplication de 9:\n";
   int i =0;
   while(i <= 10)
   {
      cout << i << '*' << 9 <<  " = " << (i*9) << '\n';
      ++i;
   }
   return 0;
}

5.3 Boucle do while:

#include<iostream>

using namespace std;

int main()
{
   cout << "Table de multiplication de 9:\n";
   int i =0;
   do
   {
      cout << i << '*' << 9 <<  " = " << (i*9) << '\n';
      ++i;
   }
   while(i<11);
   return 0;
}

6 Conditions

Les structures conditionnelles sont identiques à celles du C.

6.1 if then else

Le programme suivant lit un entier sur le clavier et affiche s'il est pair ou impair.

#include<iostream>

using namespace std;

int main()
{
   int i;
   cin >> i;
   if( i % 2 == 0) // % c'est le modulo
   {
      cout << "L'entier " << i << " est pair.\n";
   }
   else
   {
      cout << "L'entier " << i << " est impair.\n";
   }
  return 0;
}

6.2 switch

Le programme suivant lit un caractère sur le clavier et affiche s'il s'agit d'une voyelle ou non.

#include<iostream>

using namespace std;

int main()
{
   char caractereLu;
   cin >> caractereLu;
   switch(caractereLu)
   {
   case 'a' : { cout << "voyelle\n"; break; }
   case 'e' : { cout << "voyelle\n"; break; }
   case 'i' : { cout << "voyelle\n"; break; }
   case 'o' : { cout << "voyelle\n"; break; }
   case 'u' : { cout << "voyelle\n"; break; }
   case 'y' : { cout << "voyelle\n"; break; }
   case 'A' : { cout << "voyelle\n"; break; }
   case 'E' : { cout << "voyelle\n"; break; }
   case 'I' : { cout << "voyelle\n"; break; }
   case 'O' : { cout << "voyelle\n"; break; }
   case 'U' : { cout << "voyelle\n"; break; }
   case 'Y' : { cout << "voyelle\n"; break; }
   default: { cout << "si c'est une lettre, c'est une consonne, sinon ce n'est même pas une lettre\n"; }
   }

   return 0;
}

7 Enumérations

Les énumérations sont des ensembles de constantes organisées en classe très pratique pour coder de façon lisible.

enum class Couleur { BLEU, JAUNE, ROUGE };

On peut les utiliser par exemple dans un switch. On peut écrire la fonction suivante par exemple:

void afficheLaCouleur(Couleur coul)
{
switch(coul)
{
case Couleur::BLEU: { cout << "bleu\n"; break; }
case Couleur::JAUNE: { cout << "jaune\n"; break; }
case Couleur::ROUGE: { cout << "rouge\n"; break; }
};



}

8 Chaine de caractères: std::string

En C++, on utilisera la classe std::string pour gérer les chaines de caractères.

#include<string> // bibliothèque standard du C++, classe string
#include<iostream>

int main()
{
   string chat = "chat";
   string felix = "Felix"
   cout << felix << "le" << chat << '\n';
   string chatte = chat + "te";
   cout << "Le nombre de lettre dans " 
        << chat 
        << " est " 
        << chat.size() << '\n';
   // convertir des entiers en chaine de caractères
   string vingt = to_string(10+10);
   // convertir une chaine de caractères en entier
   int twenty = stoi(vingt);
   return 0;
}

9 Tableau en C++: vector<..>

En C++, on peut utiliser des tableaux comme en C, néanmoins il est préférable de stocker un ensemble de données dans un vecteur.

9.1 Tableau d'entiers

#include<vector>
#include<iostream>
using namespace std;

int main()
{
  vector<int> vecteur;
  vecteur.push_back(10);
  vecteur.push_back(20);
  vecteur.push_back(30);
  for(size_t i = 0;  i < vecteur.size(); ++i)
  {
     cout << "L'entier à l'indice " 
          << i << " dans le vecteur est: " 
          << vecteur.at(i) 
          << '\n';
  }
  // changer la valeur de l'indice 1: 20 -> 40
  vecteur.at(1) = 40;
  // copier un tableau
  vector <int> copie(vecteur);
  // effacer le contenu d'un tableau
  copie.clear();
  // reaffecter le contenu du tableau vecteur
  copie = vecteur;
}

9.2 Tableau de chaînes de caractères

#include<vector>
#include<string>
#include<iostream>
using namespace std;

int main()
{
  vector<string> vecteur;
  vecteur.push_back("vache");
  vecteur.push_back("cochon");
  vecteur.push_back("mouton");
  cout << "Voici les animaux de la ferme: \n";
  for(size_t i = 0;  i < vecteur.size(); ++i)
  {
     cout << vecteur.at(i) << " "  
          << '\n';
  }
  cout << "Le premier animal est " <<  vecteur.front() << '\n';
  cout << "Le dernier animal est " <<  vecteur.back() << '\n';
}

10 Fonctions

Les fonctions en C++ suivent la même syntaxe que les fonctions en C. Parmi l'ensemble des fonctions que peut réunir un programme, il y aura toujours une fonction main() qui est le point de départ d'un programme. En général on déclare les fonctions dans des fichiers .h et on les définit dans des fichiers .cpp. La fonction main() fait exception, on ne la déclare pas en général mais on la définit dans un fichier main.cpp (c'est une convention, pas une obligation).

Exemple: Un fichier fonctions.h contenant trois déclarations de fonction. Notez que les macros #ifndef FONCTIONS__ H #define FONCTIONS__ H #endif sont là pour éviter le phénomène de double inclusion d'un fichier de déclaration. C'est obligatoire mais NETBEANS les génère pour vous, vous ne devriez pas avoir à les écrire vous-même.

#ifndef FONCTIONS_H
#define FONCTIONS_H

void fonction1(int entier, string label); // fonction à deux paramètres, ne retourne rien


int fonction2(string label); // fonction à un paramètre, retourne un entier


B * fonction3(int entier, string label); // fonction à deux paramètres, 
                                         // créer un objet de type B 
                                         // et retourne un pointeur sur cet objet 
                                         // (allocation dynamique)


#endif

Exemple: Le fichier fonctions.cpp correspondant:

void fonction1(int entier, string label)
{
// instructions manipulant l'entier et le label...
// on ne retourne rien
}


int fonction2(string label)
{
   int result;
   // instructions manipulant  le label et modifiant l'entier result;
   // ...
   return result; // on retourne le résultat de la fonction
}


B * fonction3(int entier, string label)
{
  B * result = new B(entier,label); // creation d'un objet B qui dispose d'un constructeur à deux paramètres
  return result;  // on retourne l'objet créé 
  // important ici, l'objet pointé par result n'est pas détruit à la fin de la fonction, il faudra
  // garantir que cet objet soit détruit avec delete ultérieuement dans une autre fonction.
}

Le fichier main.cpp utilisant ces fonctions:

#include "fonctions.h" // on inclut les déclarations de fonctions


int main()
{
 string toto="toto";
 int id = 42;
 fonction1(id,toto);  // le paramètre entier prend la valeur de id (copie)
                      // le paramètre label prend la valeur de toto (copie)
 int result = fonction2(toto);
 B * objet = fonction3(result,toto);
 // autres instructions.
 // ...


 delete objet;  // on n'oublie pas de détruire l'objet créé par fonction3

}


11 Écrire une classe en C++

11.1 Définition d'une classe

On veut définir une classe A. Pour cela, on va créer deux fichiers qui portent le nom de la classe:

  1. un fichier nommé A.h contiendra la déclaration de classe
  2. un fichier nommé A.cpp contiendra la définition de classe

La déclaration dans le fichier A.h suit le schéma suivant:

#ifndef A_H
#define A_H

class A
{
// ici on déclare les méthodes, les attributs, les constructeurs, le destructeur.


};


#endif

Notez que les macros #ifndef A__ H #define A__ H #endif sont là pour éviter le phénomène de double inclusion d'un fichier de déclaration. C'est obligatoire mais NETBEANS les génère pour vous, vous ne devriez pas avoir à les écrire vous-même.

Le fichier A.cpp suit le schéma suivant:

#include"A.h" // on include d'abord la déclaration de la classe A

// definition des méthodes, des constructeurs et de destructeur de la classe A.

11.2 Attributs d'une classe

Les attributs d'une classe sont les variables internes de la classe. Les attributs sont à accès privé.

Dans A.h, quelques exemples:

#ifndef A_H
#define A_H

#include<string>
#include<vector>
#include"B.h"

using namespace std;

class A
{
private: // accès privé
  int _attribut1;  // un simple entier
  char _attribut2; // un simple caractère 
  string _attribut3; // une chaîne de caractère
  vector<int> _attribut4; // un vecteur d'entier
  A * _attribut5; // un pointeur sur un objet de type A
  B _attribut6; // un objet de type B
  vector<B *> _attribut7; // un vecteur de pointeurs sur des objets de type B 
  vector<A *> _attribut8; // un vecteur de pointeurs sur des objets de type A
  // ....


};


#endif

11.3 Méthode d'une classe

Une méthode est une fonction qui est intégrée à la classe. Elle rend un service en exploitant les attributs de la classe. Elle peut consulter les attributs (c'est une méthode accesseur), elle peut modifier la valeur d'un attribut (c'est une méthode mutateur). Elle peut également rendre un service plus complexe comme toute fonction.

11.3.1 Déclaration d'une méthode dans une classe A:

Dans A.h, on déclare la méthode dans le bloc de la déclaration de la classe A. Chaque méthode a un type d'accès (publique/privé/protégé). Voiçi des exemples de déclaration.

#ifndef A_H
#define A_H
#include<string>
#include<vector>

using namespace std;

class A
{
private:
  int methode1(string toto);  // methode acces prive

protected:
  void methode2(A * objet); // methode acces protégée


public:
  int methode3(vector<int> tableau); // methode accès public

};

11.3.2 Définition d'une méthode dans une classe A:

Une fois déclarée dans A.h, la méthode peut être définie dans A.cpp. Remarquez la notation A::

#include "A.h"

int A::methode1(string toto)
{
// ici on ajoute les instructions de la méthode 1
// ..
}
void A::methode2(A * objet)
{
// ici on ajoute les instructions de la méthode 2
// ...
}
int A::methode3(vector<int> tableau)
{
// ici on ajoute les instructions de la méthode 3
// ...
}

11.4 Constructeur d'une classe

Le constructeur est une fonction spéciale qui est appelée quand un objet de la classe est créé en mémoire. Il y a plusieurs types de contructeurs. En général, un constructeur est une fonction à accès publique.

11.4.1 Constructeur par défaut

Voiçi la déclaration d'un constructeur par défaut pour une classe A. Il se nomme A().

#ifndef A_H
#define A_H

class A
{
private:
   int _attribut1;
   B * _attribut2;
   string _attribut3;

public:

   A();  // constructeur par défaut, pas de paramètre


};


#endif

Il se définit dans A.cpp. Son objectif est de donner une valeur par défaut aux atributs. Ici les valeurs par défaut seront 4 pour _attribut1, nullptr pour _attribut2 et "titi" pour _attribut3.

Première façon: (la plus intuitive)

A::A()
{
  _attribut1=4;
  _attribut2=nullptr;  // nullptr est le pointeur nul en C++
  _attribut3="titi";
}

Deuxième façon: (plus rigoureuse et performante, recommandée)

A::A():_attribut1(4),_attribut2(nullptr),_attribut3("titi")
{}

// attention l'ordre des attributs initialisés est important ici
// il s'agit de l'ordre des déclarations dans A.h
// on ne peut pas ecrire:
// A::A():_attribut2(nullptr),_attribut1(4),_attribut3("titi") {} -> erreur

11.4.2 Constructeur paramétré

Un constructeur paramétré est un constructeur muni de paramètres (contrairement au constructeur par défaut)

#ifndef A_H
#define A_H

class A
{
private:
   int _attribut1;
   B * _attribut2;
   string _attribut3;

public:

   A(int attribut1, int * attribut2);  // constructeur à deux paramètres
   A(string label);  // constructeur à un paramètre
   // autant de constructeurs paramétrés qu'on veut...

};


#endif

Il se définit dans A.cpp.

Première façon: (la plus intuitive)

A::A(int attribut1, int * attribut2)
{
  _attribut1=attribut1;
  _attribut2=attribut2;  
  _attribut3="titi"; // on met une valeur par défaut
}

A::A(string label)
{
  _attribut1=4;
  _attribut2=nullptr;  // nullptr est le pointeur nul en C++
  _attribut3=label;
}

Deuxième façon: (plus rigoureuse et performante, recommandée)

A::A(int attribut1, int * attribut2):_attribut1(attribut1),_attribut2(attribut2),_attribut3("titi"){}

A::A(string label):_attribut1(4),_attribut2(nullptr),_attribut3(label){}

11.4.3 Constructeur par copie

C'est un constructeur particulier qui va initialiser les attributs de l'objet créé en copiant les attributs d'un autre objet en paramètre.

Voiçi sa déclaration dans A.h:

#ifndef A_H
#define A_H

class A
{
private:
   int _attribut1;
   B * _attribut2;
   string _attribut3;

public:

   A(const A & objetCopie);  // constructeur par copie

};
#endif

Voiçi sa définition classique dans A.cpp:

Première façon: (la plus intuitive)

A::A(const A & objetCopie)
{
  _attribut1=objetCopie._attribut1;
  _attribut2=objetCopie._attribut2;  
  _attribut3=objetCopie._attribut3; 
}

Deuxième façon: (plus rigoureuse et performante, recommandée)

A::A(const A & objetCopie):_attribut1(objetCopie._attribut1),
                           _attribut2(objetCopie._attribut2),
                           _attribut3(objetCopie._attribut3){}

Notez que la définition du constructeur par copie peut être plus complexe dans certains contextes.

11.5 Destructeur d'une classe

Le destructeur d'une classe est la fonction qui est appelée automatiquement par le programme quand on lui demande de détruire un objet. Il est unique dans sa forme.

Voiçi sa déclaration:

#ifndef A_H
#define A_H

class A
{
private:
   int _attribut1;
   B * _attribut2;
   string _attribut3;

public:

   ~A();  // destructeur unique

};
#endif

Voiçi sa définition dans le cas général:

A::~A()
{}

Ici on lui demande de ne rien faire. Les 3 attributs seront tous les 3 effacés et rien d'autres. Il existe par contre des cas où le destructeur est plus compliqué. Supposons que l'objet pointé par _attribut2 doit être détruit. Le fait de détruire le pointeur _attribut2 ne détruit pas l'objet pointé. Il faut explicitement détruire l'objet pointé avant d'effacer le pointeur. Dans ce cas le destructeur est défini comme suit:

A::~A()
{
   delete _attribut2; // on détruit l'objet pointé par _attribut2
}

12 Utiliser une classe en C++

Supposons que nous ayons la déclaration de classe suivante dans le fichier A.h

#ifndef A_H
#define A_H

class A
{
  private:
  int _entier;
  string _etiquette;

  public:
  A();
  A(const A & a);
  A(string label, int nombre);
  ~A();
  int entier();
  string etiquette();
  void modifieEntier(int  nouvelleEntier);
  void modifieEtiquette(string nouvelleEtiquette);
};

12.1 Créer un objet (allocation statique), le manipuler et le détruire

Pour créer un objet de type A dans une fonction (fonction main() ou toute autre fonction/méthode) de façon statique

Exemple:

#include "A.h"

void uneFonction(string etiquette, int nombre)
{
   A objet1; // creation par défaut
   A objet2(etiquette,nombre); // creation paramétrée
   A objet3(objet2);   // creation par copie

   cout << "Etiquette de l'objet2 " << objet2.etiquette() << '\n';
   cout << "Entier de l'objet1 " << objet1.entier() << '\n';
   objet3.modifieEntier(40);
   objet3.modifieEtiquette("titi");
   cout << "Les étiquettes de objet2 et objet3 sont-elles les mêmes ?" 
        << (objet3.etiquette() == objet2.etiquette());
} // destruction automatique des objets 1 2 et 3 en fin de focntion

12.2 Créer un objet (allocation dynamique), le manipuler et le détruire

Pour créer un objet de type A dans une fonction (fonction main() ou toute autre fonction/méthode) de façon dynamique.

Exemple:

#include "A.h"

void uneFonction(string etiquette, int nombre)
{
   A * objet1 = new A(); // creation par défaut
   A * objet2 = new A(etiquette,nombre); // creation paramétrée
   A * objet3 = new A(objet2);   // creation par copie

   // ici objet1-2-3 sont des pointeurs sur des objets

   cout << "Etiquette de l'objet2 " << objet2->etiquette() << '\n';
   cout << "Entier de l'objet1 " << objet1->entier() << '\n';
   objet3->modifieEntier(40);
   objet3->modifieEtiquette("titi");
   cout << "Les étiquettes de objet2 et objet3 sont-elles les mêmes ?" 
        << (objet3->etiquette() == objet2->etiquette());

   // destruction explicite des objets 1 2 et 3 en fin de fonction s'ils deviennent inutiles
   delete objet1;
   delete objet2;
   delete objet3;
}

13 Héritage simple en C++

13.1 Comment faire hériter une classe B d'une classe A

Supposons que dans A.h on a la déclaration suivante:

class A 
{
 ...


};

Dans B.h, on déclare B comme suit:

#include "A.h"

class B : public A
{


};

13.2 Mise en œuvre d'un constructeur de B.

Dans A.h, on suppose par exemple, l'existence de deux constructeurs A() et A(int i).

class A 
{
  private:
    int _attribA; // attribut de A

  public:  
    A();
    A(int i);
};

On souhaite déclarer les constructeurs B() et B(int i, int j).

class B : public A
{
  private:
    int _attribB; // attribut de B

  public:  
    B();
    B(int i,int j);
};

Et on les met en œuvre dans B.cpp.

#include "B.h"

// appel du constructeur par défaut de A et initialisation de 
// _attribB à 0
B::B():A(),_attribB(0) {}

// appel du constructeur paramétré de A et initialisation de 
// _attribB à j
B::B(int i,int j): A(i),_attribB(j){}

};

13.3 Spécialiser une méthode de A dans B

Supposons que l'on veuille spécialiser la méthode meth(int i) de A dans B. Dans un premier temps, on impose à meth(int i) de A d'être virtuelle (en ajoutant virtual à sa spécification dans A.h) puis on impose au destructeur de A d'être également virtuel.

class A 
{
 ...
     virtual int meth(int i);
 ...
     virtual ~A();
};

On fait de même dans B

class B: public A
{
 ...
     virtual int meth(int i);
 ...
     virtual ~B();
};

Ensuite on met en œuvre la version de meth(int i) de B dans B.cpp. Ici je rajoute 3 à la valeur retournée par la version meth(int i) de A Autrement dit pour mettre en œuvre la version de B, j'utilise celle de A.

int B::meth(int i)
{
   // A::meth est la méthode meth de la classe A
   // B::meth est la méthode meth spécialisée de la classe fille B
   return A::meth(i)+3;
}

13.4 Attribut protégé

Pour que l'attribut _attribA de la classe A soit consultable/modifiable par la classe B, on peut rendre son accès protégé.

class A 
{
  protected:
    int _attribA; // attribut de A protégé, la classe B y a accès


};

14 Pour aller plus loin en C++ (hors programme POO)

C++ est un langage complexe, multiparadigme et en constante évolution. Les éléments de syntaxe présentés dans ce document sont une petite partie du langage et on ne présente ici qu'une façon simple de faire les choses.

Pour les curieux qui veulent approfondir leur connaissance en C++, l'adresse suivante propose le meilleur tutoriel gratuit pour C++ disponible sur internet: https://www.learncpp.com/ C++ est un langage passionant mais très difficile à maîtriser.

Auteur: Yannick Pencolé

Created: 2023-03-11 sam. 23:35

Validate