La dimension objet, le C++
C inclus en C++
Tout ce que nous avons vu sur le langage C aux chapitres précédents est inclus dans le C++ à savoir :
-
Variables simples, char, short, int long, float, double, signed, unsigned
-
Opérations arithmétiques +, -, *, /, %,
-
Opérations bit à bit &, |, ˆ, ~
-
Opérateur de comparaison <, >, <=, >=, !=, ==
-
Sauts conditionnels i, if - else, if - else if - else
-
Branchements switch, goto
-
Tests multiconditions &&, ||
-
Boucles while, do-while, for
-
Fonctions retour et paramètre
-
Structures struct
-
Définition de types typedef
-
Tableaux [ ]
-
Pointeurs &, ,, -> , [ ], malloc, free, NULL, void*
Les principes et la syntaxe en sont rigoureusement les mêmes. Un programme C peut être compilé en C++.
Pour le vérifier, nous allons porter en C++ le programme de l’automate cellulaire donné au chapitre Structuration d’un programme - Structuration d’un programme, étude d’un automate cellulaire. Le premier point est de faire un projet C++ selon l’environnement de développement (IDE) et le compilateur avec lesquels vous travaillez.
1. Un projet console en C++
Sous Code::Blocks et Mingw32 sélectionnons un projet console. En standard nous obtenons le main suivant :
#include <iostream>
using namespace std;
int main()
{
cout << "Hello world!" << endl;
return 0;
}
Il n’y a pas de différence fondamentale dans la structure du main(). Juste des variations au niveau des inclusions (#include) et la curieuse fonction d’affichage cout<< <<endl qui remplace le printf() habituel en C (son utilisation est présentée plus loin). L’extension du fichier source passe à point cpp au lieu de point c.
Sous Visual C++ de Microsoft, il y a deux possibilités pour créer un projet console en C++. La première consiste à sélectionner un projet console dans la fenêtre Nouveau Projet. Cette solution fait apparaître un projet console avec du C++ non standard et spécifique à Microsoft. Pour rester dans le C++ standard qui est l’objet d’étude ici, il faut sélectionner un projet vide, créer une page de code cpp et y copier le code ci-dessus.
Si nous compilons le projet, nous obtenons...
C augmenté en C++
Nous allons explorer ici des apports du C++ qui modifient l’écriture du C mais sans modifier la conception de programme, c’est-à-dire sans les classes et les objets.
1. Entrée-sortie console : cout et cin
a. Utiliser cout et cin
Les fonctions printf() et scanf(), même si elles sont toujours utilisables, sont avantageusement remplacées par les objets cout et cin.
Avec cout, nous pouvons écrire dans la fenêtre console. Par exemple :
cout<<"Salut !;
écrit "Salut !" dans la fenêtre console. L’opérateur << utilisé indique une sortie (il ne s’agit pas dans ce contexte d’un opérateur bit à bit de décalage).
Avec cin il est possible d’affecter à une variable une valeur tapée au clavier :
int i;
cin>>i;
Dans la fenêtre console, le curseur en écriture clignote et attend que l’utilisateur entre une valeur et tape [Entrée] (touche [Enter] ou [Return]). Si l’utilisateur entre une valeur non conforme au type de la variable, la variable n’est pas touchée. Cette fois c’est l’opérateur >> qui est utilisé (ce n’est pas un opérateur bit à bit). À noter également l’absence de l’opérateur adresse de & pour la variable, à la différence de scanf().
Mais l’intérêt de cout et cin est aussi la disparition des formats (%d, %f, %c etc.). Il n’est plus nécessaire de se soucier du type de ces variables ni de la chaîne formatée. Par exemple :
#include <iostream> // à ne pas oublier pour avoir cout et cin
using namespace std;
int main(int argc, char *argv[])
{
int i=5;
float f;
char s[80];
cout<<"entrer une valeur entiere :"<<endl;
cin>>i;
cout<<"entrer une valeur flottante :"<<endl;
cin>>f;
cout<<"entrer un nom :"<<endl;
cin>>s;
cout<<"vous avez entré :"<<endl;
cout<<"entier "<<i<<'\n'<<"float "<<f<<endl<<"nom "<<s<<endl;
return 0;
}...
Classes, objets
Nous arrivons maintenant au cœur de la proposition C++, la notion d’objet, considérée comme révolutionnaire à son arrivée. Au départ elle répond à deux objectifs essentiels par rapport à l’utilisation du C : sécuriser le code et faciliter sa réutilisation. Mais dans la pratique la portée de l’objet s’est avérée plus grande. Nous pouvons en effet considérer qu’un objet est finalement un petit programme autonome au sein d’un programme plus vaste constitué de nombreux objets en relation les uns avec les autres. De ce fait la conceptualisation par objets devient un enjeu plutôt stimulant d’autant que l’objet apparaît en même temps que se développent les réseaux. C’est un modèle de données qui permet à des programmes de dialoguer entre eux assez naturellement.
1. Une classe, des objets
Pour vérifier qu’un objet est un programme, nous allons transformer un programme C, l’automate cellulaire avec lequel nous avons commencé ce chapitre, en un objet. Cette mutation mettra en évidence quelques aspects importants de cette nouvelle dimension qu’est la programmation objet.
a. Qu’est-ce qu’une classe ?
Nous pouvons lire dans le livre Le langage C++ de Bjarne Stroustrup, créateur du langage, que "L’objectif du concept de classe en C++ est de fournir aux programmeurs un outil de création de nouveaux types, aussi facile d’utilisation que les types intégrés [float, int, etc.]".
La classe comme la structure est un type défini par l’utilisateur. Comme la structure elle permet de regrouper des variables de n’importe quel type. Mais avec les variables, elle offre aussi la possibilité de regrouper les fonctions associées aux traitements sur ces variables. Les éléments d’une classe sont appelés les membres de la classe. Ce sont les "données membres" dites attributs ou propriétés pour les variables et les "fonctions membres" ou également "méthodes" pour les fonctions. Ainsi avec des variables pour le stockage des informations et des fonctions pour les traitements à opérer dessus, la classe constitue...
Associations entre objets
Un aspect important de la programmation objet est de mettre en relation des objets, de les relier entre eux. Plusieurs options techniques sont envisageables. L’idée est ici d’en amorcer l’exploration et l’expérimentation. Il s’agit de stimuler l’intuition en vue d’implémentations pertinentes de relations entre objets dans un récit applicatif
1. Principes des associations pour les relations entre objets
Des classes peuvent être interdépendantes et des objets peuvent communiquer, c’est-à-dire échanger des informations sous formes de valeurs. C’est ce qu’en UML on appelle des associations. UML (Unified Modeling Language) est un langage pour la modélisation souvent utilisé en amont de la programmation objet dont il reprend les grands principes et clarifie les possibilités d’interprétation. Il y a essentiellement trois degrés d’associations avec trois moyens de les implémenter.
a. Association simple
Des objets séparés s’échangent des valeurs. On dit qu’ils s’envoient des messages. Pour ce faire le mieux est d’utiliser des méthodes avec en paramètre des valeurs de propriétés d’objets, des objets ou des références d’objets. En UML c’est ce que l’on appelle une association simple. Le lien est alors faible entre deux classes et consiste uniquement en un échange de valeurs considérées comme des « messages ».
b. Agrégation
Des objets contiennent des pointeurs sur des objets existants distinctement en dehors de la classe. Le lien entre deux objets est alors plus fort que dans une association simple : un objet en contient un autre. Cependant l’interdépendance qui les unit n’est pas complète parce que la disparition de l’un n’entraîne pas nécessairement la disparition de l’autre. La disparition de l’objet pointeur, le conteneur, n’entraîne pas la disparition de l’objet pointé, le contenu. En UML c’est ce que l’on appelle une agrégation. Par exemple un humain et son téléphone portable. La disparition du portable n’entraîne pas la disparition de l’humain et réciproquement....
Héritage
L’héritage consiste à ajouter des champs ou des méthodes à une classe existante en définissant une nouvelle classe qui prolonge la première. La classe initiale est appelée classe de base, classe mère ou superclasse, et l’autre classe dérivée, classe fille ou sous-classe. En résumé un objet d’une classe dérivée contient :
-
Ses propres champs et ceux de la classe mère, hors les champs private.
-
Ses propres méthodes et celles de la classe mère, hors les méthodes private.
Seul l’opérateur private permet de réduire l’accès aux données et méthodes de la classe de base depuis une classe héritée.
Du point de vue de la modélisation, nous pouvons avoir un système d’ensembles et de sous-ensembles avec cette particularité informatique que le sous-ensemble est plus large que l’ensemble de base parce qu’il lui ajoute des données et des fonctions. Le sous-ensemble précise l’ensemble de base en l’orientant dans un certain sens.
Par exemple à partir d’une classe véhicule, nous pouvons dériver une classe voiture, une classe camion, une classe deux roues. Les voitures sont des véhicules avec des spécificités propres, différentes de celles des camions et des deux roues. La classe voiture elle-même peut être décomposée ensuite en plusieurs types de voitures avec une classe pour les voitures familiales, une classe pour les voitures de course, une autre classe pour les utilitaires. De même à partir de la classe camion il peut y avoir plusieurs ensembles dérivés, chacun précisant une catégorie de camion. Idem pour les deux roues. Chaque classe dérivée implémente un nouveau type d’ensemble qui hérite des propriétés de la classe de base. La classe dérivée ajoute des précisions et oriente dans un certain sens l’ensemble de base. La totalité des données et fonctions du programme est organisée sous la forme d’un arbre.
1. Définir une classe dérivée
Soit la classe de base suivante :
class nomBase
{
// données membres
// fonctions...
Polymorphisme, virtualité
En C++ est apparue la notion de typage dynamique également appelée "polymorphisme". L’idée est qu’un pointeur puisse changer de type en fonction de l’objet sur lequel il pointe. C’est-à-dire qu’il est possible avec un pointeur de reconnaître dynamiquement un objet afin d’accéder à ses membres. Cette propriété est introduite en C++ pour des fonctions redéfinies dans des classes dérivées. Elle permet d’introduire des listes d’objets de types différents dans les programmes. Typiquement c’est un tableau de pointeurs dans lequel chacun non seulement pointe sur un objet différent mais surtout peut sans cast accéder à certaines fonctions de cet objet. Ce changement important obéit à des règles strictes que nous allons explorer et vérifier.
1. Accès pointeurs par défaut aux fonctions redéfinies
Soit une classe A et une classe B dérivée de A :
class A { . . . }
class B : public A { . . . }
Par défaut un pointeur de type A* auquel est affectée une adresse de type B* n’accède qu’aux données et fonctions membres de A :
B b;
A *ptr = &b; // ptr reste de type A*
Le programme suivant permet de le constater :
#include <iostream>
using namespace std;
class A
{
public :
int val;
A(){val=0;}
A(int v):val(v){}
void affiche(){cout<<"A : "<<val<<endl;}
};
class B : public A
{
public :
int val;
B(int a, int b) : A(a),val(b){}
void affiche(){cout<<"B : "<<val<<endl;}
};
int main()
{
A *ptr;
B b(1,2);
ptr=&b;
ptr->affiche(); // affiche de A
return 0;
}
Le programme affiche :
A : 1
bien que le pointeur a contienne l’adresse d’un objet B.
Mais il y a en C++ une possibilité pour que ce pointeur accède néanmoins à des fonctions membres redéfinies en B. Il faut que ces fonctions soient redéfinies en B et déclarées virtual en A.