Date de première publication : 2013/06/11
Concepts objets
L'objectif de cette partie est de créer une classe Point
avec quelques attributs en respectant l'encapsulation et de l'instancier.
Nous reprenons et compilons les exemples du cours.
Le code se place dans deux voire trois fichiers :
- un fichier
Point.hpp
qui contient la déclaration de la classe - un fichier
Point.cpp
qui contient la définition de la classe - il est nécessaire d'avoir une fonction
main()
à placer soit dans un fichiermain.cpp
ou , à la rigueur, dans le fichierPoint.cpp
Le code que l'on va enrichir est le suivant :
// Fichier Point.hpp
// Il manque les gardiens mais je vous laisse les ajouter,
// c'est comme en C et le pragma once c'est nul
class Point {
// par défaut, tout est privé dans une "class"
int x;
public:
int getX();
};
Un fichier entête comme Point.hpp
ne se compile PAS sauf si vous savez ce que vous faites !
Voici maintenant le fichier d'implémentation de la classe Point
. Notez la forme particulière du #include
avec des guillemets
// Fichier Point.cpp
#include <iostream> // Inclusion d'un fichier standard
#include "Point.hpp" // Inclusion d'un fichier du répertoire courant
int Point::getX() {
return x;
}
Voici maintenant le point d'entrée du programme main.cpp
du cours :
// Fichier main.cpp
#include <iostream>
#include "Point.hpp"
int main(int, char**) {
Point p;
std::cout << p.getX();
return 0;
}
#include "fic"
permet d'inclure un fichier fic
à partir du répertoire courant.
#include <fic>
permet, elle, d'inclure un fichier à partir d'un répertoire standard (typiquement /usr/[local]/include
ou d'un chemin donné par l'option -I
(i majuscule)
- Compiler et exécuter le programme. Que peut-on en déduire sur les attributs ?
L'initialisation, c'est comme les antibiotiques, c'est pas automatique ! Et un président bien connu dirait What about f.....g valgrind et son prédécesseur Valgrind bless Our Code
- Ajouter la méthode
setX()
(avec le bon prototype) dans le fichier de déclaration et d'implémentation. Vérifier que l'appel à la méthode dans lemain()
change bien la valeur de l'attributx
- Enlever le point-virgule de la classe et recompiler. Mon compilateur m'annonce plusieurs erreurs avec au milieu ce message : (perhaps a semicolon is missing after the definition of ‘Point’)
- Ajouter l'attribut
y
et les méthodesdeplacerDe()
etdeplacerVers()
- Vérifier que les méthodes font bien ce qu'elles doivent faire.
- Ajouter deux constructeurs, un sans argument et l'autre avec. Faites en sorte que les constructeurs affichent leur nom sur la sortie standard lorqu'ils sont appelés.
- Tester également les syntaxes avec
{}
- Instancier plusieurs objets de classe
Point
en appelant les différents constructeurs. - Instancier également un objet avec l'opérateur
new
- Ajouter un attribut de classe
compteur
avec l'accesseur en lecture et l'initialisation - Vérifier que l'attribut est bien défini avant toute création d'objet et que l'incrémentation est bien faite dans les constructeurs
- Vérifier les différentes manières d'appeler l'accesseur (classe et objet)
- Vérifier qu'il n'est pas possible d'accéder aux attributs
x
ouy
dans une méthode de classe (alors que l'inverse est possible) - Vérifier qu'il n'est pas possible d'accéder directement à un attribut privé (de classe ou d'instance) de l'extérieur
Il existe une autre manière de définir une classe : on peut utiliser le mot-clé struct
. Tous les membres (donnée ou méthode) d'une class
sont privés par défaut
alors qu'ils sont publics pour une struct
.
Une instance "bavarde" avec les différents types d'allocation
Compléter le code de la classe Bavarde
avec un constructeur et un destructeur
qui affichent respectivement "Construction de %" et "Tais-toi %" où % est un paramètre
fourni à la construction (avec une valeur par défaut de 0)
#include <iostream>
class Bavarde {
//
// Mettre votre code ici
//
} bizarre(1);
Bavarde globale(2);
void fonction(Bavarde b) {
std::cout << "code de la fonction";
}
int main(int, char **) {
Bavarde b1(3);
Bavarde b2(4);
Bavarde * p = new Bavarde(5);
// fonction(b1);
return 0;
}
Si on place le code précédent dans des fichiers séparés, il faut se méfier des définitions de bizarre
et globale
.
Si la notion destructeur est nébuleuse, il faut juste savoir que c'est une méthode spéciale qui est appelée lors de la destruction de l'objet. Sa définition peut ressembler à cela :
~Bavarde() {
std::cout << "Tais-toi " << n << std::endl;
}
- Qu'est-ce que
bizarre
? - Vérifier que l'instance
b1
est bien détruite aprèsb2
- Après avoir vérifié le code avec valgrind, que faut-il faire ?
Dans le script original, Terminator ne cherchait pas Sarah Connor mais plutôt l'instance *p
.
- Décommenter la ligne 18. Pourquoi peut-on voir plus de destructions que de constructions ?
Une instance est détruite mais la construction n'est pas signalée. C'est donc qu'il y a une construction implicite quelque part. Cela sera abordé en cours : le C++ passe les variables par valeur (copie) comme en C.
- Ajouter maintenant une méthode qui retourne le paramètre donné à la construction (un "guetteur", quoi !).
- Insérer le code suivant dans la fonction
main()
. Que pouvez-vous en déduire sur la durée de vie de l'objet créé ?
int main(int, char **) {
std::cout << Bavarde(0).get() << std::endl;
}
Pampers ....
class Tableau
{
int * tab;
int taille;
public:
Tableau():tab(nullptr), taille(10)
{
tab = new int[taille]; // si problème ?
}
};
int main(int, char **)
{
Tableau t;
return 0;
}
- Initialiser le tableau tel que
tab[i]=i
- Ajouter une méthode
afficher()
pour afficher le tableau sur la sortie standard et l'appeler dans lemain()
- Exécuter ce programme sous valgrind. Corriger l'erreur
Si vous compilez le code donné tel que avec l'option -O2
, il n'y aura pas d'erreur car le compilateur s'aperçoit que le code n'est pas vraiment utilisé et donc le code n'est pas généré
Makefile
Voici un exemple de fichier makefile avec génération de dépendance automatique. Le principe est d'exploiter l'option -MMD du compilateur g++. Les fichiers de dépendances (*.d) et objets (*.o) sont créés dans un répertoire superfétatoire build.
Ce makefile se lance toujours par la commande make
.
Si vous copiez le code ci-dessous, faites bien attention au fait qu'il faut des tabulations devant les lignes à exécuter
SRC=main.cpp obj.cpp
#SRC=$(wildcard *.cpp)
EXE=nom_executable
CXXFLAGS+=-Wall -Wextra -MMD -g -O2 -fdiagnostics-color=auto
LDFLAGS= #-lSDL
OBJ=$(addprefix build/,$(SRC:.cpp=.o))
DEP=$(addprefix build/,$(SRC:.cpp=.d))
$(EXE): $(OBJ)
$(CXX) -o $(EXE) $^ $(LDFLAGS)
build/%.o: %.cpp
@mkdir -p build
$(CXX) $(CXXFLAGS) -o $@ -c $<
clean:
rm -rf build core *.gch $(EXE)
-include $(DEP)
Un @ en début de commande désactive l'affichage sur la sortie standard.
La commande include permet d'inclure d'autres makefiles comme makefile.dep. Si ces fichiers n'existent pas, le préfixe "-" permet de les ignorer.
Ce makefile efface les éventuels fichiers entêtes précompilés (règle clean).
Pour finir, si vous êtes sur une machine avec plus d'un processeur/cœeur et que la commande make
prend un certain temps,
il est possible d'exploiter la structure de la machine en précisant l'option -j n
où n est le nombre de processeurs/cœurs que vous voulez utiliser pour compiler.
Des envies plus importantes ? La documentation est votre amie
Fil rouge ...gesture
Nous allons faire un projet fil rouge, qui nous accompagnera sur un certain nombre de TPs. On pourra mettre en place une structure et des tests unitaires qui évolueront avec les différentes notions vues en cours.
On va s'intéresser à une application de dessin vectoriel en mode texte (sic :-))
Nous allons manipuler des formes telles que des rectangles et des cercles
Créer une classe Rectangle
qui a pour propriétés : des coordonnées x
et y
, une largeur w
et une hauteur h
. Toutes ces valeurs sont entières. Proposer un constructeur qui permet d'initialiser tous les paramètres. On modélisera plus tard les coordonnées (x,y)
comme étant un Point
Rectangle |
- x : entier - y : entier - w : entier - h : entier |
+ Rectangle(x, y, w, h) + toString() : chaine |
Créer une classe Cercle
qui a les mêmes propriétés que la classe Rectangle
(on considérera qu'un cercle est inscrit dans un rectangle). On ajoutera un constructeur qui prend un centre et un rayon.
Cercle |
- x : entier - y : entier - w : entier - h : entier |
+ Cercle(x, y, w, h) + Cercle(cx, cy , rayon) + toString() : chaine |
La méthode toString()
renvoie une chaine de caractères (std::string
) qui représente l'objet sous forme texte. Voici deux exemples possibles :
CERCLE 10 10 30 30
RECTANGLE 30 30 15 15
Une base de code est clonable à partir de l'URL : https://gitlab.com/filrouge1/step01.git
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10