Programmation en C++

Transcription

Programmation en C++
Introduction à la programmation orientée objet en C++
Utilisation d’un type (class)
#include <fstream>
#define _USE_MATH_DEFINES
#include <cmath>
int main()
{
std::ofstream of("exemple.txt");
const int npt=1000;
for (double x=0.0; x<=M_PI; x+=M_PI/npt)
of << x << " " << sin(x) << std::endl;
}
Fichier contenant la déclaration
de la classe ofstream
Déclaration d’un objet
de type ofstream
Utilisation de l’opérateur
<< surchargé pour ofstream
Définition d’une classe
#ifndef COMPLEXE_H_INCLUDED
#define COMPLEXE_H_INCLUDED
class complexe
{
public:
complexe(double re=0.0, double im=0.0) : re_(re), im_(im) {}
double re() const
{
return re_;
}
double im() const
{
return im_;
}
complexe conjug() const;
protected:
double re_, im_;
};
#endif
Organisation du code pour les
classes
y Sauf pour les cas très simples (classes non réutilisables)
on sépare le code des classes en deux fichiers :
y Un fichier en-tête (avec extension .h) contenant la
déclaration de la classe et les définitions des fonctions
inline.
y Un fichier source (.cpp) contenant les définitions des autres
fonctions membres.
Membres
y Une classe contient des membres.
y Un membre peut être :
y Une fonction (méthode)
y Une variable
y Parmi les fonctions on peut avoir :
y Des opérateurs surchargés.
y Des constructeurs
y Des destructeurs
Méthodes
y Une fonction membre d’une classe s’appelle une méthode.
y Une méthode a accès à tous les membres.
y Une méthode définie dans le corps de la classe est
automatiquement inline.
y Une méthode qui ne modifie pas l’état de l’objet auquel
elle s’applique doit être déclarée const.
y Une fonction membre qui surcharge un opérateur a un
argument implicite (l’objet auquel elle s’applique).
Méthodes statiques
y Une fonction membre précédé du mot clé static n’est pas
attachée à un objet précis.
y Une fonction statique est utilisée en la préfixant par le
nom de la classe à laquelle elle est attachée suivie de
l’opérateur de portée (::) et non pas de . comme pour les
membres non statiques.
Const
y Un objet utilisé dans un contexte const ne peut pas voir
son état (contenu) modifié.
y Un exemple de contexte const est une fonction où un des
arguments est déclaré const.
y Si on appelle un membre d’une classe celui-ci est supposé
changer le contenu de l’objet associé sauf s’il a été déclaré
const.
y Un membre (variable) peut être précédé du mot clé
muttable si on considère que sa modification ne change
pas l’état de l’objet.
Constructeurs
y Un constructeur est une fonction membre qui porte le
même non que la classe.
y Un constructeur est automatiquement exécuté chaque fois
qu’un objet est crée.
y Un constructeur ne retourne rien (même pas void).
y Il peut avoir plusieurs constructeurs surchargés, on les
distingue par leurs paramètres.
y Un constructeur à un seul argument sera utilisé comme
une conversion de type sauf s’il est préfixé par
« explicit ».
Destructeurs
y Les destructeurs portent le même nom que la classe
précédé de ~.
y Un destructeur est appelé automatiquement chaque fois
qu’un objet est détruit.
y Il ne peut y avoir que un destructeur par classe.
y En général on utilise les destructeurs pour libérer des
ressources qui ont été acquis dans le constructeur.
Opérateurs membres
y Si le premier argument d’un opérateur qu’on souhaite
redéfinir est un objet de type A, alors cette opérateur peut
être défini en tant que membre A.
y Un membre opérateur a un argument en moins que le
même opérateur non membre (par exemple un opérateur
binaire + aura un seul argument). Le premier argument est
l’objet auquel l’opérateur s’applique.
Exemple
Class MyComplex
{
Public:
MyComplex operator+(const MyComplex &that) const
{
return MyComplex(re_+that.re_,im_+that.im_);
}
};
Permet d’écrire la somme de deux objets de la classe MyComplex
Comme
MyComplex w1(0,1), w2(1,0);
MyComplex z=w1+w2;
Exemple
y L’exemple précédent à pour équivalent en fonction non
membre:
MyComplex operator+(const MyComplex &w,const MyComplex &z)
{
return MyComplex(w.re()+z.re(),w.im()+z.im());
}
Exemple
y L’expression z+w où z et w sont du type MyComplex
équivaut à :
y operator+(z,w) si l’opérateur est une fonction globalle.
y z.operator+(w) si l’opérateur est une fonction membre.
Opérateur de copie
y L’opérateur =, membre d’une classe et qui prend comme
argument un objet du type de cette classe est un opérateur
de copie.
y Si on ne définit pas un opérateur de copie pour une classe
un opérateur qui copie chaque membre est
automatiquement généré.
y L’opérateur de copie doit prendre comme argument une
référence constante et retourner une référence.
y En général on retourne *this.
Opérations par défaut
y Si une classe ne définit pas une des opérations :
constructeur par copie, opérateur d’assignation,
destructeur alors le compilateur crée une version triviale.
y Le constructeur par copie et l’opérateur d’assignation par
défaut copient tous les membres.
y Le destructeur par défaut est vide.
y Si une classe ne définit aucun constructeur le compilateur
crée un constructeur par défaut (sans arguments) trivial.
Accès aux membres
y Nous pouvons restreindre l’accès à chaque membre d’une
classe.
y Il y a trois types d’accès :
y public, accessible partout (pas de restrictions)
y private, accessible uniquement aux membres de la classe.
y protected, accessible aux membres de la classe et des classes
dérivées.
y Une classe ou une fonction déclarée « friend » à un accès
similaire à celui des membres.
Variables statiques
y Un membre statique d’une classe est partagé par tous les
objets de cette classe.
y Les membres statiques doivent être initialisées dans un
fichier source (.cpp) en général le même où on définit les
fonctions membre.
y On accède à une variable statique en utilisant l’opérateur
de portée, par exemple :
y MyComplex::epsilon, si epsilon est static double membre de
MyComplex
Patrons de classes
(templates)
y Si plusieurs classes partagent le même code on peut
utiliser les patrons (templates en anglais).
y On dit que les templates sont une façon de définir du
polymorphisme statique (par opposition au
polymorphisme dynamique des classes dérivées).
y Si nous voulons définir des complexes en précision
simple où double (ou même des entiers de Gauss) alors il
suffit de transformer la classe MyComplex en template.
Exemple de template de
classe
template<typename T>
Class MyComplex
{
public:
MyComplex(const T &re, const T &im) : re_(re), im_(im)
{}
private:
T re_;
T im_;
};
Exemple de template de
classe
y Pour créer une classe à partir du template de MyComplex
on doit faire :
MyComplex<float> z;
MyComplex<double> w;
Dans l’exemple précédent les variables z et w ont des types différents.
Patrons de fonctions
y Nous pouvons aussi définir une famille de fonctions
similaires par un template.
y L’exemple le plus simple est celui de la fonction max :
template<typename T>
T& max(const T& x, const T& y)
{
if (x<y)
return y;
return x;
}
y Pour appeler la fonction max on fait simplement:
double x=1.0, y=2.0;
double z=max(x,y);
Le compilateur choisit la version de la fonction max en fonctions des
arguments de l’appel.
Ici on remplacera T par double et on appellera la fonction :
max<double>(const double &, const double &);
Classes dérivées
y Une classe dérivée définit un type qui a les mêmes
propriétés que une classe existante (la classe de base) et
les étend.
y Une classe dérivée hérite tous les membres de sa classe de
base.
y La syntaxe pour dire que la classe B dérive de la classe B
est:
y class B : public A { … };
Classes dérivées
y Si B est une classe dérivée de A, alors un objet de type B
est aussi de type A.
y On peut affecter un objet de type B à une référence de
type A, où un pointeur vers un objet B à un pointeur vers
un objet de type A.
class B : public A;
class A;
A *myB= new B;
Fonction virtuels
y Si une fonction non virtuelle apparaît dans une classe et
dans sa classe dérivée alors la fonction appelée dépend de
la déclaration de la variable contenant l’objet.
y Une fonction est virtuelle si le mot clé « virtual » précède
la fonction.
y Une fonction déclaré virtuelle dans une classe de base fait
que la fonction appelée dépend de l’objet et non pas de la
déclaration de la variable le définissant.
class A
{
void f();
virtual void g();
};
void func(A &obj)
{
obj.f();
obj.g();
}
class B : public A
{
void f();
virtual void g();
};
int main()
{
A myA;
B myB;
myA.f();
myB.f();
func(B);
}
A::f() est toujours appelé
car f() est
n’est
par virtuelle
B::g()
toujours
appeléet
obj
estest
déclaré
A. et
car g()
virtuelle
obj une référence vers un
objet de type B.
Exceptions
y Dans un projet complexe de C++ la détection et le
traitement des erreurs ne peuvent, en général, se faire
simultanément :
y Dans un bibliothèque on peu savoir qu’une erreur s’est
produit, mais on ne sait pas comment le traiter.
y Dans un programme utilisant une bibliothèque on sait traiter
l’erreur (par exemple en affichant un message) mais la
détection peut être très difficile ou couteuse.
Les techniques traditionnelles des langages non objet (C,
etc.) qui consistent à traiter les erreurs par code sont
incompatibles avec des fonctions qui retournent des valeurs,
par exemple:
FILE *fp;
fp = fopen(“myfile”, “r”);
if (fp == (FILE *) NULL)
{
fprintf(stderr,”Erreur à l’ouverture de myfile\n”);
exit(1);
}
Montre bien que on suppose que NULL n’est pas une
valeur possible, sinon cette technique ne marche pas.
Cet exemple montre bien qu’on utilise un type (FILE *)
sensé représenter un fichier pour traiter une erreur
d’ouverture de fichier.
L’exemple montre aussi que si cette fonction est membre
d’une bibliothèque elle peut être appelé dans un contexte
où la sortie d’erreurs standard (stderr) n’est pas visible par
l’utilisateur. Si on est dans un contexte graphique il serait
mieux d’ouvrir une fenêtre d’erreur.
Catch
y En C++ pour détecter des erreurs on utilise catch.
y La syntaxe est la suivante :
try
{
…
}
catch (T var)
{
…
}
…
catch (…)
{
…
}
Code à exécuter où des exceptions peuvent se
produire.
Traitement de l’erreur de type T qui est
accessible dans la variable var.
Nombre quelconque de blocs catch avec des
erreurs de types différents.
Bloc à exécuter pour des type d’erreurs non
encore traités (optionnel).
Throw
y On signale qu'une erreur s’est produite avec l’instruction
throw.
y On dit qu’on jette une exception.
y La syntaxe de throw est très simple :
throw var;
y La stdc++ propose des classes adaptés au traitement des
erreurs.
y Toutes les classes d’exceptions de stdc++ derivent de
std::exception (déclarée dans <exception>.
y L’interface publique de std::exception contient une
fonction membre what() qui retourne un const char *.
y La liste des classes peut etre consultée dans le livre de
Josuttis.
Assert et invariants
y On appelle assertion un test d’une condition qui doit
toujours être vraie.
y Si le test d’un assert est faux on arrête le programme
(quelques fois on peut aussi faire appel à un déboguer)
y Une assertion n’est vérifiée que en mode debug.
y Les assert sont très utiles pour s’assurer la correction d’un
programme.
y Une fois compilé en mode release les asserts n’ont aucun
effet (ils ne pénalisent pas le temps d’exécution.
On peut utiliser les asserts de la bibliothèque C.
#include <iostream>
#include <cassert>
int main(int argc, char*argv[])
{
assert(argc>1);
std::cout << argv[1] << std::endl;
return 0;
}
Fichier en-tête contenant assert
assert(condition). Si la macro
NDEBUG est définie cette
disparaît.
Compilation en mode debug :
$ g++ -o assert assert.cpp
$ ./assert abc
abc
$ ./assert
assertion "argc>1" failed: file "assert.cpp", line 6
7 [sig] assert 4320 c:\pedro\work\Enseignement\Evry\DESS IM\assert.exe: *
* fatal error - called with threadlist_ix -1
Hangup
C0mpilation en mode release:
$ g++ -D NDEBUG -o assert assert.cpp
$ ./assert
$ ./assert abc
abc
Tests unitaires
y Un test unitaire vérifie que un morceau de C++ (une
classe, une fonction, etc.) fait bine ce qu’il est sensé de
faire.
y L’idée de tests unitaire est très utilisée en Java où le
framework junit est très populaire.
y En C++ le framework cppunit est disponible gratuitement.
Documentation
y Écrire la documentation d’un projet C++ peut s’avérer un
travail long et peu motivant.
y Si on ne fait pas preuve d’organisation les documents
auront du mal à rester actualisés.
y Celui qui écrit le code est le plus compétant pour initier la
documentation.
y Maintenir la documentation à coté du code permet une
gestion plus simplifiée.
Doxygen utilise des commentaires spéciaux :
• /** …. */
• ///
Ces commentaires doivent être positionnés devant
l’unité syntaxiques qu’ils documentent (classe, fonction,
membre, etc.)
Quelques commentaires peuvent commencer par une
commande qui indique à quoi ils s’appliquent : par
exemple
/** @file f.ccp
*/
s’applique au fichier source f.cpp.
Doxygen permet de générer du html et du LaTeX (et
avec ça du pdf).
Pour générer un document avec doxygen :
1.Créer le fichier de configuration de doxygen (doxygen
–g f.doxy)
2.Éditer le fichier de configuration généré, faire attention
aux variables qui guident les fichiers à inclure où à
exclure du projet.
3.Lancer doxygen.