© 2005 - Patrice Jacquemin Libre de droits de diffusion.
© 2005 - Patrice Jacquemin Libre de droits de diffusion.
I
T A B L E D E S M A T I E R E S PRESENTATION 1. 2. 3.
CENTRE DE METZ OBJECTIFS PEDAGOGIQUES PROGRAMME 3.1. Conception et programmation orientée objet avec Java 4. ORGANISATION 5. DESCRIPTION DES HEURES D’ENSEIGNEMENTS 6. LES MODALITES DE VALIDATION 7. BIBLIOGRAPHIE 8. SITES INTERNET
1 1 1 1 1 1 1 1 1 1
CONCEPTION ET PROGRAMMATION ORIENTEE OBJET AVEC C++
2
INTRODUCTION
3
LES FONCTIONS EN C++
4
1.
GENERALITES 1.1. Streams et opérateurs de lecture/écriture 1.2. Opérateur d'écriture << 1.3. Opérateur de lecture >> 1.4. Attribut const 2. PARAMETRES D'UNE FONCTION 2.1. Surcharge de fonctions 3. PASSAGE DE PARAMETRES PAR VALEUR OU PAR REFERENCE 4. PARAMETRES INITIALISES PAR DEFAUT 5. RESULTAT D'UNE FONCTION 5.1. Application du passage de résultat par référence 6. MECANISME INLINE 7. EXERCICES
LES CLASSES 1. 2. 3. 4. 5. 6. 7.
DEFINITION COMPOSANTS D’UNE CLASSE PROTECTION DES MEMBRES INITIALISATION DESTRUCTEUR FONCTIONS MEMBRES EXEMPLE COMPLET DE CLASSE: LA CLASSE FRACTIONS 7.1. Définition 7.2. Fonction Somme 7.3. Constructeur Fraction() 7.4. Constructeur Fraction(const int &a) 7.5. Fonction Oppose 7.6. Fonction Est_egal 7.7. Fonction Produit 7.8. Fonction Difference 7.9. Fonction Inverse 7.10. Fonction Quotient 7.11. Fonction Ecrire 7.12. Fonction Lire 8. EXERCICE 8.1. Fonction Det 8.2. Fonctions Pgcd 8.3. Fonction ResoutSystem2x2 8.4. Fonction Simplifie 9. COMPILATION SEPAREE 9.1. Editions des liens 9.2. Inclusion dans les fichiers
4 4 4 4 4 5 5 6 7 7 8 9 9
13 13 13 13 13 13 13 13 13 14 15 15 15 15 16 16 16 16 16 17 17 17 17 18 18 19 19 19
I I
LA SURCHAGE D’OPERATEURS 1. 2.
DEFINITION CATEGORIE D’OPERATEUR 2.1. Opérateur classique : expression x expression → expression 2.2. Opérateur : objet x expresion → expression 2.3. Opérateur unaire : expresion → expression 2.4. Opérateur unaire : objet → expression 2.5. Opérateur binaire : expression x expression → objet 2.6. Opérateur binaire : expression → objet 2.7. Opérateur particulier 3. SYNTAXE DE REDEFINITION 3.1. Syntaxe de définition pour une fonction ordinaire 3.1.1. Opérateur binaire 3.1.2. Opérateur unaire 3.2. Syntaxe de définition pour une fonction membre 3.2.1. Opérateur binaire 3.2.2. Opérateur unaire 4. EXERCICE 4.1. Opérateur + 4.2. Opérateur - unaire 4.3. Operateur * 4.4. Operateur == 4.5. Operateur < 4.6. Operateur ++ 4.7. Operateur += 4.8. Opérateur << 4.9. Opérateur >> 4.10. Autres opérateurs
LES CONVERSIONS 1.
CONVERSION A L'AIDE D'UN CONSTRUCTEUR 1.1. Application 1.2. Généralisation 2. CONVERSION A L'AIDE D'UN OPERATEUR DE CONVERSION 3. CONVERSION EXPLICITE 4. INCONVENIENTS DES CONVERSIONS
EXEMPLE COMPLET DE CLASSE 1. 2. 3. 4.
RAPPEL SUR LES CHAINES DE CARACTERES EN C EXERCICE LA CLASSE CHAINE FONCTIONS MEMBRES 4.1. Fonction privée Alloue 4.2. Constructeur Chaine(const char *s) 4.3. Constructeur Chaine(const char &ch) 4.4. Constructeur Chaine(const char &c) 4.5. Constructeur Chaine() 4.6. Constructeur Chaine (const Naturel &n, const char &c) 4.7. Destructeur 4.8. Opérateur + 4.9. Opérateur = 4.10. Opérateur [] 4.11. Opérateur <= 4.12. Fonction Ecrire() 4.13. Opérateur << 4.14. Opérateur >> 4.15. Fonction Longueur()
L’HERITAGE ET LES CLASSES DERIVEES 1. 2. 3.
INTRODUCTION HERITAGE ET CONVERSION PROTECTION DES MEMBRES ET HERITAGE
LES FONCTIONS VIRTUELLES
20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 23 23 23 23 23 24
25 25 25 26 26 27 27
29 29 29 30 31 31 31 31 31 31 32 32 32 32 33 33 33 34 34 34
35 35 35 36
39
I I I
LES CLASSES ABSTRAITES
40
LES PATRONS (TEMPLATES)
41
1. 2. 3.
INTRODUCTION MODELE DE FONCTION MODELE DE CLASSE
41 42 42
LES EXCEPTIONS
45
PROJET ANNUEL
46
PROJET ANNUEL DE PROGRAMMATION ORIENTEE OBJET EN LANGAGE C++
47
1. 2. 3. 4.
ORGANISATION DU TRAVAIL TRAVAIL DEMANDE MODALITES DE LA SOUTENANCE SUJET 4.1. Problème 1. Gestion de fichiers génériques 4.1.1. Cahier des charges de la classe fichier de X 4.1.2. Gestion des erreurs 4.2. Problème 2 : Gestion d’ensembles génériques 4.2.1. Cahier des charges de la classe Ensemble de X : 4.3. Problème 3. Dérivation formelle
47 47 47 47 47 47 47 47 48 48
ANNEXES
49
PROGRAMMES
50
1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
ENTIERS.H ENTIERS.CPP ERREURS.H ERREURS.CPP FRACTIONS.H FRACTIONS.CPP SYSTEMES.H SYSTEMES.CPP CHAINES.H CHAINES.CPP
LES LIBRAIRIES 1. 2.
L’INTERET DES LIBRAIRIES LIBRAIRIE C STANDARD 2.1. generalites (#include
) 2.2. stdio (#include ) 2.3. stdlib (#include ) 2.4. math (#include ) 2.5. ctype (#include ) 2.6. string (#include ) 2.7. signal (#include ) 3. IOSTREAM (#INCLUDE ) 3.1. ostream 3.2. istream 3.3. iostream 3.4. Etat d’un flot 3.5. Association d’un flot à un fichier 4. L++ (STANDARD COMPONENTS) 5. QUELQUES AUTRES LIBRAIRIES
50 50 50 50 51 52 55 55 56 56
59 59 59 60 60 66 70 72 73 75 76 76 76 76 77 78 78 79
ANNALES
80
1ERE SESSION 2002/2003
81
1.
EXERCICE 1 : QUESTIONS A CHOIX MULTIPLE
81
1
PRESENTATION
P R E S E N T A T I O N UV 16472 : B3 - Conception et développement du logiciel. 1 valeur de cours 140 heures. Responsable national : Louis DEWEZ professeur des universités. Les coordonnées du secrétariat du responsable national : [email protected] .
1.
CENTRE DE METZ
Enseignant : M. MICHEL, [email protected] . Dates : du 3 janvier au juin 2005. Horaires : les lundis (et quelques jeudis) de 18h30 à 21h30. Lieu : IUT, Ile du Saulcy, METZ.
2.
OBJECTIFS PEDAGOGIQUES
Le cours est conçu en deux parties complémentaires. La première partie traite les aspects fondamentaux des phases de spécification, prototypage, conception détaillée et validation des algorithmes. La deuxième partie traite de la conception générale, de l'architecture et de l'intégration des composants logiciels. Le langage Java est présenté et appliqué à l'implantation de problèmes spécifiés en première partie.
3.
PROGRAMME 3.1. CONCEPTION ET PROGRAMMATION ORIENTEE OBJET AVEC J AVA • • •
4.
Modularité, typage, structures de données. classe, objet, héritage. Concurrence, modélisation.
ORGANISATION
Examen partiel et examen continu. L'ensemble des supports (cours, exercices travaux pratiques) sera disponible et mis à jour sur le Web (la présence est cependant indispensable à la compréhension).
5.
DESCRIPTION DES HEURES D’ENSEIGNEMENTS
Cours : 60 heures. Exercices dirigés : 60 heures.
6.
LES MODALITES DE VALIDATION
Examens finals, devoirs hebdomadaires pour BONUS à l'examen.
7.
BIBLIOGRAPHIE
Auteurs P. LEBEUX KERNIGHAN & RICHIE BJARNE STRSUSTRUP J. O’LOGHIEN C. DELANNOY SCHILDT HARTSMANN
8.
Titre Pascal par la pratique (SYBEX). Le Langage C (DUNOD). Le langage C++ (ADDISON WESLEY). Programmation avancée en C++ (ADDISON WESLEY). Programmation en C++ (EYROLLES). Java 2 reference complete (FIRST INTERACTIVE). Au Coeur de Java 2 Tome 1 Notions fondamentales, Tome 2 Fonctions avancées (CAMPUS PRESS).
SITES INTERNET
Conception et programmation orientée objet avec C++
CO N CE P T I O N E T P R O G R AM M AT I O N O R I E N T E E O B J E T AVE C C+ +
Conception et programmation orientée objet avec C++
3
INTRODUCTION
I N T R O DU C I O N C T Le langage C++ est dit langage de programmation orienté objet (LOO). De nombreux autres langages orientes objet existent tels Smalltalk, Simula, Java, Ada. Le concept du LOO a émergé assez naturellement après plusieurs dizaines d’années d’expérience de programmation. On peut dire qu’elle est la dernière évolution des langages de programmation. Historiquement l’évolution a été : • Le langage machine (1950). • L’assembleur (années 50). • Les langages de haut niveau (années 70). Ils ont apporté la notion de t ypage. Pascal, Fortrant (57), Cobol, C. • LOO. C++ (80/90), smalltalk (70), simula, java (95). L’assembleur est encore très utilisé notamment dans les domaines des automates, du temps réels, l’exploitation de « puces ». Il est caractérisé par son haut niveau de performances et le peu de place requis en mémoire. Les LOO ont un intérêt pour les très gros programmes nécessitant de nombreuses lignes de codes. Les LOO peuvent utiliser des couches en C pour les performances. Java a pour caractéristique sa portabilité en contrepartie du coût élevé du temps d’exécution, du temps machine. Au cours de cette évolution les langages s’éloignent des contraintes du matériel et se rapprochent soit du langage mathématique ou du langage naturel. Les LOO permettent d’adapter la programmation à la résolution du problème traité quel que soit le domaine (CAO, base de données, génie logiciel, réseau). Les LOO tentent (réussissent ?) d’apporter des solutions aux problèmes rencontrés aux langages classiques surtout lors de la résolution de logiciels de grandes tailles. Ils apportent , par rapport à ces derniers, une plus grande souplesse de programmation, une meilleure rigueur de programmation, la possibilité de contrôler très efficacement le code produit, un meilleur formalisme très algébrique, une meilleure réutilisabilité du code. Mise en garde: une plus grande souplesse de programmation entraîne plus de contrôle laissé à la machine, donc plus de décisions laissées à la machine d'où des ambiguïtés voir même des bugs ! En conclusion: la grande liberté accordée au programmeur ne doit pas être réalisée au détriment de la rigueur. Il faut au contraire exploiter les possibilités du langage pour produire un code plus sûr. Le concept clé des LOO est la notion de classe. En programmation classique il y a séparation complète entre les données et le traitement. Les LOO, au contraire, proposent de réunir données et traitement en une seule entité : la classe. Ainsi on peut définir les objets avec leur structure, les opérations sur les objets, les propriétés de ces objets et réunir le tout en une seule définition. La classe introduit, entre autres, deux notions importantes : • La protection des membres. • La possibilité d’initialiser un objet dès sa création. Il en découle qu’un objet a la possibilité de parfaitement contrôler les opérations faites sur lui. Il est toujours responsable des traitements effectués sur lui ce qui donne un meilleur contrôle des logiciels, une facilité de débugage, une programmation plus simple. Les autres notions importantes pour les LOO sont : • L’héritage. • Le polymorphisme. • La généricité (les patrons). Remarque sur le C++ : ce langage reprends et étends le langage C, ce qui explique sa grande diffusion. Donc le langae C est un sous-ensemble de C++. En conséquence : • Tout ce qu’il est possible de faire en C l’est en C++. • Une certaine redondance entre les deux langages existe. Note de l’auteur : les différents programmes ont été testés et validés en utilisant l’IDE Dev-C++ 5.0 beta 9.2 (4.9.9.2) avec Mingw/GCC 3.4.2 que vous trouverez sur http://www.bloodshed.net/dev/devcpp.html , voir également http://www.bloodshed.net/ . Ils sont précédés de la marque ☺. Remerciements à Alain Beck pour l’information.
Conception et programmation orientée objet avec C++
4
LES FONCTIONS EN C++
L E S F O N C T I O N S E N C ++ 1.
GENERALITES 1.1. S TREAMS ET OPERATEURS DE LECTURE /ECRITURE
En C++ les fichiers (FILE *) ont été avantageusement remplacés par les flux (streams). On peut considérer que les flux sont des sortes de « super fichier ». Parmi les différentes sortes de flux, il y a les flux d'entrée (classe istream) et les flux de sortie (classe ostream ). Les classes istream et ostream sont décrites dans le fichier header iostream.h. • cout est une variable globale de type « ostream » (en faite une instance de la classe ostream) et correspond à la sortie standard (par défaut l'écran). • cin est une variable globale de type « istream » (en faite une instance de la classe istream) et correspond à l'entrée standard (par défaut le clavier).
1.2. OPERATEUR D'ECRITURE << La classe ostream est munie d'un opérateur d'affichage << qui remplace la fonction printf du C. Il permet d'afficher n'importe quel type de données à l'écran. Il reconnaît les types de données et permet donc d'éviter l'emploi des formats fastidieux de printf. Exemple: ☺
#include using namespace std; main() //programme principal { float x=3; double y=2.5; int i=2; cout << x; //affiche la valeur de cout << y; //affiche la valeur de cout << i; //affiche la valeur de cout << c; //affiche la valeur de cout << "x= " << x << "y= " << y ligne + retour ligne }
char *c="abc"; x sur la console y sur la console i sur la console c sur la console << "i= " << i << "c= " << c << "\n"; //endl :fin de
1.3. OPERATEUR DE LECTURE >> Il est le symétrique de l'opérateur <<. La classe istream est munie de l'opérateur >> (opérateur de lecture) qui remplace la fonction scanf du C. Il permet de lire n'importe quelles données, prédéfinies du C, au clavier. Il reconnaît les types et se passe donc de format. Exemple: ☺
#include using namespace std; main() //programme principal { float x; double y; char c[10]; cout << "Tapez un réel \n"; cin >> x; //x reçoit la valeur entrée au clavier cout << "Encore un réel \n"; cin >> y; cout << "Tapez une chaîne d'au plus 9 caractères \n"; cin >> c; //ou bien en combinant les appels: cin >> x >> y >> c; //espace ou tab pour distinguer les données }
1.4. A TTRIBUT CONST L'attribut const permet de déclarer qu'une « chose » soit constante. Il a pour effet que la « chose » déclarée constante n'a pas le droit d'être placée à gauche d'opérateur d'affectation ( op ∈ {+,-,*, /,%, <<, >> ,|, &,||,&&,^} ) car ces opérateurs modifient leur opérande gauche. Et de manière générale, une « chose » déclarée constante n'a pas le droit d'être modifiée. La « chose » peut être une « variable », un paramètre d'une fonction ou un résultat d'une fonction. Exemple : ☺
using namespace std; main() { const int x; x = 4; //refusé
Conception et programmation orientée objet avec C++
5
LES FONCTIONS EN C++
const int y = 3; //refusé const int z(7); z = 8; //refusé } struct Vecteur //struct :mot clé pour définir une classe sans méthode { double x,y; }; //ne pas oublier le; à la fin de la classe main() { Vecteur v; v.x = 3; v.y = 5; const Vecteur u; u.x = 7; //refusé car u est constant } Remarque : les paramètres et les résultats d'une fonction en C++ sont aussi structurées que vous voulez. Exemple: fonction calculant la somme de deux vecteurs. ☺
#include using namespace std; Vecteur Somme(Vecteur a, Vecteur b) { Vecteur r; r.x = a.x + b.x; r.y = a.y + b.y; return r; } main() { Vecteur u,v,w; u.x = u.y = 3; v.x = -1; v.y=9; w = Somme(u,v); cout << "résultat de vecteur u(" << u.x << "," << u.y << ") + vecteur v(" << v.x << "," << v.y << ") = vecteur w(" << w.x << "," << w.y << ")" << endl; }
2.
PARAMETRES D'UNE FONCTION 2.1. SURCHARGE DE FONCTIONS
Plusieurs fonctions peuvent avoir le même nom. A l'appel, la distinction se fait grâce à la nature des paramètres et/ou a u nombre de paramètres. Exemple : ☺
#include using namespace std; double max(double a, double b) //calcul le maximum de a et b { if (a > b) return a; //équivalent en C: (a > b ? a : b); else return b; } double max(double a, double b, double c) { return max(a,max(b, c)); } main() { double x, z, y; x = 7; y = 4; z = 11; cout << "max(" << x << "," << y << ")=" << max(x,y) << endl; cout << "max(" << x << "," << y << "," << z << ")=" << max(x,y,z); } Conception et programmation orientée objet avec C++
6
☺
LES FONCTIONS EN C++
⎛x⎞ ! ⎛x⎞ ! !⎜ ⎟ ! 2 2 Exemple : U ⎜ ⎟ ∈ U = x + y et V ⎜ y ⎟ ∈ V = x2 + y2 + z2 ⎝y⎠ ⎜z⎟ ⎝ ⎠ # include #include using namespace std; struct Vecteur2D { double x,y; }; struct Vecteur3D { double x,y,z; }; double Norme(Vecteur2D u) { return sqrt(u.x * u.x + u.y *u .y); } double Norme (Vecteur3D u) { return sqrt(u.x * u.x + u.y * u.y + u.z * u.z); } main() { Vecteur2D u ={7,3}; Vecteur3D v = {-4,7,3}; cout << "||u||= " << Norme(u) << "||v||= " << Norme(v) << "\n"; }
3.
PASSAGE DE PARAMETRES PAR VALEUR OU PAR REFERENCE
Les paramètres d'une fonction sont passés soit par valeur, soit par référence (notions indépendantes des langages de programmation). En C le passage de paramètres par référence n'existe pas, il est simulé à l'aide de pointeurs. En C++ les deux modes de transmission existent. Par défaut, un paramètre est passé par valeur (et un paramètre passé par référence est précédé du symbole "&"). Exemple : ☺
mémoire
#include using namespace std; void F(int a, int &b) { cout <<"a= " << a << " b= " << b << "\n"; ++a; ++b; //équivaut à b = b + 1 cout << "a= " << a << " b= " << b << "\n"; }
1
1
main
3
x
4
y
7
2 x b,y a
3
4
7
Copie
4
Console
main() { int x,y; x = 4; y = 7; cout << "x= " << x << " y= " << y << "\n"; F(x,y); cout << "x= " << x << " y= " << y << "\n"; }
2
F
1
x=4
2
a=4
b=7
a=5
b=8
x=4
y=8
3
y=7
A l’étape 2 a est transmis par valeur. x est dit paramètre effectif, a est dit paramètre formel. Conclusion: La fonction F modifie le paramètre y car elle travaille avec l'original : b est un autre nom pour accéder au même emplacement mémoire, x n'est pas modifié car F travaille avec ce qui est une copie temporaire de x. Le passage de paramètres par valeur est utilisé pour communiquer des données dans le sens du programme appelant vers fonction. Il laisse le paramètre intact : utilisé lorsque la fonction doit consulter un paramètre sans le modifier. Le passage de paramètres par valeur est plus coûteux en temps et en espace mémoire, augmentant en fonction de la taille du paramètre.
Conception et programmation orientée objet avec C++
x
4
y
7
7
LES FONCTIONS EN C++
Le passage de paramètres par référence est utilisé pour communiquer des résultats dans le sens fonction vers programme appelant. Il permet de modifier le paramètre : utilisé lorsque la fonction devra ranger un résultat par le biais de ce paramètre Le passage de paramètres par référence est plus économique en temps d'exécution, espace mémoire, car il économise le temps de réaliser la copie et il n'exige pas d'espace mémoire supplémentaire Un paramètre peut être une donnée de la fonction (entrée), un résultat de la fonction (sortie), une donnée et résultat (entrée/sortie). Lorsque le paramètre est de grande taille, le passage de paramètres par valeur peut devenir très coûteux en temps et espace. Il peut être intéressant de passer par référence même s'il doit être simplement consulté par la fonction. Exemple : ☺
Vecteur Somme(Vecteur &a, Vecteur &b) { Vecteur r; r.x = a.x + b.x; r.y = a.y + b.y; return r; } Le problème d'écrire cela de cette façon, c'est que l'on peut modifier les vecteurs passés en paramètre alors que ce sont des données. De plus si l'on travaille en équipe, les autres peuvent penser que l'on modifie les vecteurs passés par paramètre alors que ce n'est pas le cas. Il faudrait donc écrire la fonction de cette façon:
Vecteur Somme(const Vecteur &a, const Vecteur &b){} Avec l'attribut const, cette fonction reste rigoureuse en ayant les avantages du passage de paramètre par référence.
4.
PARAMETRES INITIALISES PAR DEFAUT
II est possible en C++ que certains paramètres soient initialisés par défaut. Si à l'appel, des paramètres effectifs manquent, les valeurs par défaut sont utilisées. Exemple : ☺
#include using namespace std; struct Vecteur3D { double x,y,z; }; Vecteur3D CreeVecteur3D(const double &a = 8, const double &b = -12, const double &c = 9) { Vecteur3D R; R.x = a; R.y = b; R.z = c; return R; } main() { Vecteur3D u,v,w,t; u = CreeVecteur3D(4,8,-11); v = CreeVecteur3D(7,0); //v = CreeVecteur3D(7,0,9) w = CreeVecteur3D(5); // w = CreeVecteur3D(5,-12,9) t = CreeVecteur3D(); // t = CreeVecteur3D(8,-12,9) }
5.
RESULTAT D'UNE FONCTION
Celui renvoyé par l’instruction return. Par défaut le résultat d'une fonction est passé par valeur (c'est le passage classique), mais en C++, il est possible de passer un résultat par référence. C'est à dire qu'une fonction peut renvoyer non pas une expression mais un objet.
Rappel sur la différence entre une expression et un objet : • Un objet représente un espace mémoire. En conséquence on peut lui affecter quelque chose, il peut être placé à gauche d’un opérateur d’affectation, on peut en prendre l’adresse. • Une expression ne correspond pas à un espace mémoire. En conséquence on ne peut pas lui affecter quelque chose, elle ne peut pas être placée à gauche d’un opérateur d’affectation, on peut en prendre l’adresse. Exemple :
Conception et programmation orientée objet avec C++
8
LES FONCTIONS EN C++
main() { int a,b,c; float x; char t[5]; Vecteur A; x; //objet sqrt(x); //expression a + b; //expression t[2]; //objet 3; //expression t; //expression A.x; //expression "abc"; //expression } Une fonction ne peut prendre un résultat par référence que si ce résultat est un objet persistant : • Objet : espace mémoire. • Persistant : survit à l’appel de la fonction.
5.1. A PPLICATION DU PASSAGE DE RESULTAT PAR REFERENCE Comme pour la passage de paramètres par référence, la passage de résultat par référence sert à : • Modifier un objet par le biais de l’appel à la fonction. Conséquence : l’appel à la fonction peut être placé à gauche d’un opérateur d’affectation. • Sert à optimiser le passage du résultat en temps et en espace. Exemple :
main() { int a,b,c; a = 5; b = 3; a += b; c = a += b; //c= a; a= a + b; (a += b) = c; //refusé } Ecrire une fonction Ajout qui simule += pour les vecteurs : ☺
void Ajout(Vecteur const Vecteur &b) /*effectue a += b*/ { a.x += b.x; a.y += b.y; }
&a,
#include using namespace std; main() { Vecteur u,v; u.x = 3; u.y = 7; v.x = 11; v.y = -4; cout << "Ajout[u(" << u.x << "," << u.y << "),v(" << v.x << "," << v.y << ")] = "; Ajout(u,v); cout << "vecteur u(" << u.x << "," << u.y << ")" << endl;}
Mémoire
a,u
b,v
3
14
7
3
11 -4
Données : u,v. Résultat : u. ☺
Vecteur &Ajout(Vecteur &a, const Vecteur &b) /*effectue a += b*/ { a.x += b.x; a.y += b.y; return a; }
#include using namespace std; main() { Vecteur u,v,w; u.x = 3; u.y = 7; v.x = 11; v.y = -4; w = Ajout(u,v); } Données : u,v. Résultat : w.
Conception et programmation orientée objet avec C++
Mémoire a,u
3 7
b,v
11 -4 14
w
3
9
LES FONCTIONS EN C++
Exemple abérant (ou exemple de ce qu 'il ne faut pas faire) :
int &f(int a, int b) { int t; t = a + b; return t; } main() { int *p; //* signifie pointeur sur entier p = &f(2,3); //p reçoit adresse de f donc p prends adresse de t *p=10; }
6.
MECANISME INLINE
Les mécanismes inline généralisent les macros du langage C. Il s'agit d'une substitution de texte. Exemple de macro :
#define PI 3.1415926 #define abs(x) ((x) > 0 ? (x) : -(x)) main() { double teta, alfa, x; int a; teta = PI / 2; alfa = PI / 4; x = 7; teta = abs(x); x = abs(a); } inline int Max(const int &a, const int &b) { return (a > b ? a : b); } main() { int u, v; u = 7; v = 9; cout << Max(u,v); cout << (u > v ? u : v); cout << Max(15, 3); cout << (15 > 3 ? 15 : 3); } Avantage du mécanisme inline : plus rapide que d’écrire des vrais fonctions. Inconvénient du mécanisme inline : rallonge la taille de l’exécutable. Conclusion: pour tirer profit au maimum de ce mécanisme, on le réserve à de petites fonctions appelées très souvent. Mais à ne pas utiliser pour les traitements récursifs.
7.
EXERCICES 2
Ecrire une fonction qui résout une équation dans " ax + bx + c = 0 avec a, b, c ∈ " et une fonction main qui utilise la fonction écrite pour résoudre une équation dont les paramètres sont données par l’utilisateur. Exemple : 3x² - 7x + 4 = 0. Méthode classique avec a ≠ 0.
∆ = b2 − 4ac
si ∆ > 0 deux solutions : x1 =
−b − ∆ −b + ∆ et x2 = 2a 2a
si ∆ = 0 solution double : x =
−b a
si ∆ < 0 pas de solution réelle Les données sont a, b, c et le résultat x1et x2.
Conception et programmation orientée objet avec C++
1 0 ☺
LES FONCTIONS EN C++
#include using namespace std; bool ResoutEquationDegre2(const double &a, const double &b,const double &c, double &x1, double &x2) /*données a, b, c hypothèse a ≠ 0 Tache : résout dans " ax2 + bx + c = 0 Résultats : x1 et x2 et un booléen Convention : si renvoie vrai alors x1 et x2 ont été calculé Si renvoie faux alors x1 et x2 sont indéterminés*/ { if (a == 0) return false; long delta = b * b – 4 * a * c; if (delta >= 0) { x1 = ((-b) – sqrt(delta)) / (2 * a); x2 = ((-b) + sqrt(delta)) / (2 * a); return true; } else return false; } Attention : il se pose un problème avec les doubles quand les nombres ne sont pas de la même grandeur : l’arrondi. Le test sur le 0 est difficile – if (delta == 0).
☺
#include using namespace std; bool ResoutEquationDegre2(const double &a, const double &b, const double &c, double &x1, double &x2) /*données a, b, c hypothèse a ≠ 0 Tache : résout dans " ax2 + bx + c = 0 Résultats : x1 et x2 et un booléen Convention : si renvoie vrai alors x1 et x2 ont été calculé Si renvoie faux alors x1 et x2 sont indéterminés*/ { if (a == 0) return false; double delta; delta = b * b – 4 * a * c; if (delta < 0) return false; else /*delta >= 0*/ { double r; r = sqrt(delta); x1 = (-b – r) / (2 * a); x2 = (-b + r) / (2 * a); return true; } } main() { double p, q, r, x, y; cout << ’’Résolution dans " de ax2 + bx + c = 0, tapez les valeurs de a, b, c avec a différent de 0\n’’; cin >> p >> q >> r; if (ResoutEquationDegre2(p, q, r, x, y)) cout << ’’Les solutions sont ’’ << x << ’’ et ’’ << y << endl; else cout << "Pas de solution réelle\n"; } Une racine carrée se calcule sous la forme d’une suite :
lim Un =
n→+∞
x avec x ≥ 0
U0 = 1 Un =
1⎛ x ⎞ ⎜ Un−1 + ⎟ 2⎝ Un−1 ⎠
Conception et programmation orientée objet avec C++
1 1
LES FONCTIONS EN C++
Ecrire un programme qui résout le système suivant (avec a, b, c, a’, b’, c’ dans " ) : ⎧ax + by = c
⎨ ⎩a ' x + b ' y = c '
Méthode de Kramer Calculer det =
a
b
a' b' c
si det ≠ 0 alors x=
= ab '− a ' b b
c' b'
=
cb '− c 'b
et y=
ac'-a'c
det det det si det = 0 alors ∞ de sol ou pas de solution Les données sont a1, a2, b1, b2, c1, c2 et le résultat x, y et un booléen. ☺
#include using namespace std; double det(const double &a11, const double &a12, const double &a21, const double &a22) a11 a12 /* calculer */ a21 a22 { return a11 * a22 - a21 * a12; } bool ResoutSystem2x2(const double &a1, const double &a2, const double &b1, const double &b2, const double &c1, const double &c2, double &x, double &y) /* données : a1, a2, b1, b2, c1, c2 Résultat : x, y et un booléen Taches : résout a1x + b1y = c1, a2x + b2y = c2 Convention : si renvoie vrai x et y ont été calculé Si renvoie faux x et y sont indéterminé*/ { double d; d = det(a1,b1,a2,b2); // d = a1 * b2 – a2 * b1; if (d == 0) return false; else /*d ≠ 0*/ { x = (c1 * b2 – c2 * b1) / d; //x = det(c1,b1,c2,b2) / d; y = (a1 * c2 – c1 * a2) / d; //y = det(a1,c1,a2,c2) / d; return true; } } main() { double a1, a2, b1, b2, c1, c2, x, y; cout << "Entrez les coefficients de la premiere equation : "; cin >> a1 >> b1 >> c2; cout << "Entrez les coefficients de la seconde equation : "; cin >> a2 >> b2 >> c2; if (ResoutSystem2x2(a1, a 2, b 1, b 2, c 1, c 2, x, y)) cout << "x= " << x << " y= " << y << "\n"; else cout << "pas de solution\n"; } Solution pour le test sur d == 0 : FLT_EPSILON. C’est la limite des plus petits nombres flottants.
☺
#include #include using namespace std; bool ResoutSystem2x2(const double &a1, const double &a2, const double &b1, const double &b2, const double &c1, const double &c2, double &x, double &y, const double &EPS = FLT_EPSILON) /* données : a1, a2, b1, b2, c1, c2 Résultat : x, y et un booléen
Conception et programmation orientée objet avec C++
1 2
LES FONCTIONS EN C++
Taches : résout a1x + b1y = c1, a2x + b2y = c2 Convention : si renvoie vrai x et y ont été calculé Si renvoie faux x et y sont indéterminé*/ { double d; d = det(a1,b1,a2,b2); if (fabs(d) < EPS) return false; else /*d ≠ 0*/ { x = det(c1,b1,c2,b2) / d; y = det(a1,c1,a2,c2) / d; return true; } }
Conception et programmation orientée objet avec C++
1 3
LES CLASSES
L E S C L A S S E S 1.
DEFINITION
La classe est une entité qui regroupe des structures de données (comme un type record du Pascal, ou struct du C) et les opérations associées à ces données. C’est un concept très algébrique, les instances de la classe (aussi appelées exemplaires ou objet) sont vu comme les éléments d’un ensemble mathématique. La classe est donc une entité ou sont décrites simultanément la structure, les propriétés et opérations possibles sur les instances.
2.
COMPOSANTS D’UNE CLASSE
Les composants d’une classe sont appelés aussi membres (ou encore champs = fields). Les membres peuvent être indifféremment des données ou des traitements. Les traitements sont encore appelés fonctions membres ou méthodes.
3.
PROTECTION DES MEMBRES
Les membres (indifféremment données ou traitements) peuvent être publiques (public), privés (private) ou protégés (protected). La notion de membre protégé est liée à l’héritage. Si un membre est déclaré public toutes fonctions extérieures à la classe et toute fonction de la classe y ont accès. Si un membre est déclaré privé seules les fonctions membres de la classe y ont accès (en consultation et modification). En résumé : une classe comporte généralement une partie privée dans laquelle sont décrits les membres que seule la classe gère et une partie public servant d’interface avec les autres classes et où sont décrits les membres accessibles à l’ensemble des classes. Cette organisation est la même que celle de celle de la compilation séparée. C’est la même notion également que la notion de module modèle/primitives d’accès.
4.
INITIALISATION
Il est possible, et par suite recommandée, de définir pour une classe une fonction membre particulière. Le constructeur servant a initialiser tout exemplaire dès sa création (il est appelé automatiquement). Il garantit que tout objet est correctement initialisé. Il sert également de fonction de création, d'un exemplaire de la classe et il peut également servir à réaliser des conversions.
5.
DESTRUCTEUR
Voir plus loin.
6.
FONCTIONS MEMBRES
Pour les fonctions membres il y a existence d'un argument explicite : c'est l'instance pour laquelle la fonction membre est appelée. Cette instance constitue toujours un argument de la fonction. Elle est toujours accessible et un pointeur est toujours associé à cet instance : le pointeur this. L'argument implicite est toujours passé par référence.
7.
EXEMPLE COMPLET DE CLASSE: LA CLASSE FRACTIONS
Supposons que l’on désire un logiciel qui utilise la notion de fraction. Module d’une fraction : a a ∈ # ( entier relatif signé ) , b ∈ $* ( entier ≥ 1) b
7.1. DEFINITION Voir en annexe le fichier complet testfractions.cpp. ☺
#include using namespace std; typedef unsigned int Naturel; class Fraction { /*par défaut privé*/ int num; // numérateur Naturel den; // dénominateur >= 1 public: /*déclaration de ce qui est public jusqu’à
Conception et programmation orientée objet avec C++
main() { Fraction x1(2,3); Fraction x2(-1,4); /*appel du constructeur passage des paramètres*/ }
Mémoire x1
2 3
num den
x2
-1 4
num den
1 4
LES CLASSES
nouvel ordre*/ Fraction(const int &a, const Naturel &b); // constructeur crée a/b on suppose b ≠ 0 }; // ne pas oublier le; ☺
/*le constructeur porte le même nom que la classe*/ /*référencement de la fonction à la classe*/ Fraction :: Fraction(const int &a, const Naturel &b) /* constructeur crée a/b on suppose b ≠ 0*/ { if (b == 0) Erreur("Fraction :: Fraction(a,b) : b == 0"); if (b > 0) { num =a; den = b; } else /*b < 0*/ { num = - a; den = (Naturel)(-b); } } Le constructeur tel que défini est sur d’emploi.
☺
/*définition de la fonction erreur*/ void Erreur(char* message) { cerr << ’’\n\nErreur ! ’’ << message << endl; exit (0); }
7.2. FONCTION SOMME Par convention on rajoute la fonction étudiée dans une version simplifiée de la classe. ☺
class Fraction { public: Fraction Somme(const Fraction &a) const; // le dernier const signifie que la fonction membre ne modifie pas l’argument implicite };
main() { Fraction x1(2,3); Fraction x2(-1,4); Fraction x3(0,1); x3 = x1.Somme(x2); // x1 est l’argument implicite }
Mémoire x1
2 3
x2
-1 4
x3
7 1
*this
this r ☺
Fraction Fraction :: Somme(const Fraction &a) const { Fraction r(0,1); r.num = (*this).num * a.den + (*this).den * a.num; // simplification r.num = num * a.den + den * a.num; r.den = (*this).den * a.den; // simplification r.den = den * a.den; return r; } ère
1 ☺
optimisation de la fonction membre Somme :
Fraction Fraction :: Somme(const Fraction &a) const { Fraction r(num * a.den + den * a.num, den * a.den); return r; } ème
2 ☺
optimisation de la fonction membre Somme :
Fraction Fraction :: Somme(const Fraction &a) const { return Fraction(num * a.den + den * a.num, den * a.den); } Conception et programmation orientée objet avec C++
0 1
5 12
1 5
LES CLASSES
Equivalence d’écriture :
x4 = x3.Somme(x1.Somme(x2)); <=> x4 = x3.Somme(x1).Somme(x2); x4 = Fraction(2,3).Somme(x1);
7.3. CONSTRUCTEUR FRACTION() ☺
class Fraction { public: Fraction(); // crée 0 / 1 }; Fraction :: Fraction() // crée 0 / 1 { num = 0; den = 1; }
7.4. CONSTRUCTEUR FRACTION(CONST INT & A ) ☺
class Fraction { public: Fraction(const int &a); // crée a / 1 }; Fraction :: Fraction(const int &a) // crée a / 1 { num = a; den = 1; }
7.5. FONCTION OPPOSE ☺
class Fraction { public: Fraction Oppose() const; // renvoie l’opposé de l’argument implicite }; ☺
main() { Fraction x1(2,3); Fraction x2(-1,4); x1 = x2.Oppose(); }
Fraction Fraction :: Oppose() const /*renvoie –(*this)*/ { Fraction r; r.num = -(*this).num r.den = (*this).den; return r; } Optimisation de la fonction Oppose() :
☺
Fraction Fraction :: Oppose() const /*renvoie –(*this)*/ { return Fraction(-num, den); }
7.6. FONCTION EST _ EGAL ☺
class Fraction { public: bool Est_egal(const Fraction &a) const; }; ☺
bool Fraction :: Est_egal(const Fraction &a) const { Return num * a.den == den * a.num; }
Conception et programmation orientée objet avec C++
main() { Fraction x1(2,3); Fraction x2(-1,4); bool ok; ok = x1.Est_egal(x2); }
1 6
LES CLASSES
7.7. FONCTION PRODUIT ☺
class Fraction { public: Fraction Produit(const Fraction &a) const; };
☺
main() { Fraction x1(2,3); Fraction x2(-1,4); Fraction x3; x3 = x1.Produit(x2); }
Fraction Fraction :: Produit(const Fraction &a) const { return Fraction(num * a.num, den * a.den); }
7.8. FONCTION DIFFERENCE ☺
class Fraction { }; Fraction Difference(const Fraction &a, const Fraction &b); ☺
main() { Fraction x1(2,3); Fraction x2(-1,4); Fraction x3; x3 = Difference(x1,x2); }
Fraction Difference(const Fraction &a, const Fraction &b) { return a.Somme(b.Oppose()); } Faire une fonction ordinaire (non membre) permet d’alléger la maintenance en cas de changement dans la classe, celleçi n’est pas dans la classe et sera mise à jour automatiquement. Elle utili se des fonctions membres.
7.9. FONCTION INVERSE ☺
class Fraction { public: Fraction Inverse() const; };
☺
main() { Fraction x1(2,3); Fraction x2(-1,4); Fraction x3; x3 = x1.Inverse(); }
Fraction Fraction :: Inverse() const { return Fraction(den,num); }
7.10. FONCTION QUOTIENT ☺
class Fraction { }; Fraction Quotient(const Fraction &a, const Fraction &b);
☺
main() { Fraction x1(2,3); Fraction x2(-1,4); Fraction x3; x3 = Quotient(x1,x2); }
Fraction Quotient(const Fraction &a, const Fraction &b) { return a.Produit(b.Inverse()); }
7.11. FONCTION ECRIRE ☺
class Fraction { public: void Ecrire(ostream &os) const; };
Conception et programmation orientée objet avec C++
main(){ Fraction x1(2,3); Fraction x2(-1,4); Fraction x3; x1.Ecrire(cout); }
1 7 ☺
LES CLASSES
void Fraction :: Ecrire(ostream &os) const { os << num << ’’/’’ << den; } Le paramètre de la fonction n’est pas considéré comme un const car l’action ‘’écrire’’ modifie le fichier. L’écran est considéré comme un fichier par le système.
7.12. FONCTION LIRE ☺
class Fraction { }; Fraction Lire(istream &is); ☺
main() { Fraction x1(2,3); Fraction x2(-1,4); Fraction x3; x3 = Lire(cin); }
Fraction Lire(istream &is) { int a,b; is >> a >> b; return Fraction(a,b); }
8.
EXERCICE
3 1 ⎧2 ⎪⎪ 5 x + 4 y = − 3 Résoudre le système d’équation tel que : ⎨ ⎪7 x − 7 y = 1 ⎪⎩ 4 3 2 Les coefficients, connus sous forme de fractions, sont saisis au clavier. Les solutions, si elles existent, sont calculées sous forme de fraction.
8.1. FONCTION DET ☺
Fraction Det(const Fraction &a11, const Fraction &a12, a a const Fraction &a21, const Fraction &a22); /*calcule 11 12 */ a21 a22 Fraction Det(const Fraction &a11, const Fraction &a12, const Fraction &a21, const Fraction &a22) a a /*calcule 11 12 */ a21 a22 { return Difference(a11.Produit(a22), a21.Produit(a12)); }
8.2. FONCTIONS PGCD ☺
Naturel Pgcd1(const Naturel &a, const Naturel &b); /*calcule le pgcd de a et b, hypothèse a >= 1 b >= 1*/ Naturel Pgcd(const int &a, const int &b); /*on suppose (a,b) ≠ (0,0)*/ #include Naturel Pgcd(const int &a, const int &b) /*on suppose (a,b) ≠ (0,0)*/ { if (a == 0) return abs(b); else /*a ≠ 0*/ if (b == 0) return abs(a); else /* a ≠ 0 et b ≠ 0*/ return Pgcd1(abs(a),abs(b)); }
Conception et programmation orientée objet avec C++
1 8
LES CLASSES
Version itérative de la fonction Pgcd1 : ☺
Naturel Pgcd1(const Naturel &a, const Naturel &b) /*calcule le pgcd de a et b, hypothèse a >= 1 b >= 1*/ { Naturel x, y; x = a; y = b; while (x != y) { if (x > y) x -= y; else /*x < y*/ y -= x; } return x; }
☺
Naturel Pgcd1(const Naturel &a, const Naturel &b) /*calcule le pgcd de a et b, hypothèse a >= 1 b >= 1*/ { if (a == b) return a; // cas trivial else if (a > b) return Pgcd1(a – b,b); // cas général else /*a < b*/ return Pgcd1(a,b - a); // avec un sous problème }
Version récursive de la fonction Pgcd1 :
8.3. FONCTION R ESOUTS YSTEM2X2 ☺
bool ResoutSystem2x2(const Fraction &a1, const Fraction &b1, const Fraction &a2, const Fraction &b2, const Fraction &c1, const Fraction &c2, Fraction &x, Fraction &y); /* données : a1, a2, b1, b2, c1, c2 Résultat : x, y et un booléen Taches : résout a1x + b1y = c1, a2x + b2y = c2 Convention : si renvoie vrai x et y ont été calculé Si renvoie faux x et y sont indéterminé*/ bool ResoutSystem2x2(const Fraction &a1, const Fraction &b1, const Fraction &a2, const Fraction &b2, const Fraction &c1, const Fraction &c2, Fraction &x, Fraction &y) /* données : a1, a2, b1, b2, c1, c2 Résultat : x, y et un booléen Taches : résout a1x + b1y = c1, a2x + b2y = c2 Convention : si renvoie vrai x et y ont été calculé Si renvoie faux x et y sont indéterminé*/ { Fraction d = Det(a1,b1,a2,b2), zero; if (d.Est_egal(zero)) return false; // pas d’arrondi car ce sont des entiers else { x = Quotient(Det(c1,b1,c2,b2),d); y = Quotient(Det(a1,c1,a2,c2),d); return true; } }
8.4. FONCTION SIMPLIFIE ☺
class Fraction { Public: Fraction Simplifie() const; }; Fraction Fraction :: Simplifie() const { Naturel p; p = Pgcd(num,den); return Fraction(num / p,den / p); }
Conception et programmation orientée objet avec C++
1 9
9.
LES CLASSES
COMPILATION SEPAREE
Il s’agit, en premier lieu, de répartir le code en module ou unité. Fractions classe Fraction Lire Quotient Difference
Erreurs Erreur
Systèmes Det ResoutSystem2x2
Entiers Pgcd Pgcd1 Naturel
Programme main
Chaque module se décompose en deux fichiers : • Le *.h correspond aux entêtes. Il contient les définitions utiles dans le module : o Les types. o Les constantes. o Les classes o Les entêtes de méthodes. o Les commentaires. • Le *.cpp contient les corps des méthodes définies dans le fichier *.h accompagnées des fonctions ordinaires. On compile ensuite chacun des fichiers avec la commande ‘’g++ -c’’ du compilateur g++ sous linux.
9.1. EDITIONS DES LIENS Elle fournit un seul fichier exécutable indépendant du langage de programmation.
9.2. INCLUSION DANS LES FICHIERS Un premier type d’inclusion concerne les fichiers où se trouvent des fonctions non définies dans le fichier courant. Il faut inclure le fichier contenant les définitions des fonctions appelées dans ce fi chier. L’ordonnancement des include suit celui des appels de fonctions !! Si le fichier à inclure est dans le même répertoire :
#include ’’fichier.h’’ Dans un répertoire différent :
#include Chaque fichier *.cpp fait appel au fichier *.h correspondant et nécessite donc une inclusion de celui-ci. Exemple pour fractions.cpp :
#include #include #include #include
’’erreurs.h’’ ’’entiers.h’’ ’’fractions.h’’
Une fonction pouvant être appelée dans différents fichiers, il en serait de même pour le fichier *.h concerné entraînant une erreur. Pour y palier, on teste l’existance d’une constante : • Si oui, on quitte le fichier. • Si non, on la définit et le fichier est lu entièrement. Exemple pour fraction.h :
#ifndef FRACTIONSH #define FRACTIONSH using namespace std; typedef unsigned int Naturel; class Fraction { }; #endif
Conception et programmation orientée objet avec C++
2 0
LA SURCHAGE D’OPERATEURS
L A S U R C H A G E D’O P E R A T E U R S 1.
DEFINITION
La surcharge d’opérateur consiste à faire signifier à un opérateur une autre opération que celle pour laquelle il était initialement prévu. Pour surcharger un opérateur il faut écrire sous forme de fonction, l’opérateur que l’on désire associé à cette opération. Cela signifie qu’un opérateur n’est rien d’autre qu’une fonction que l’on appelle de façon particulière. Le traitement associé à cet opérateur surchargé est défini par le concepteur et n’a donc aucun lien obligatoire avec l’opérateur initial. Exemple : Un opérateur + redéfini peur très bi en signifié autre chose qu’une addition. Les seules propriétés des opérateurs qui sont conservés lors de la redéfinition sont : • L’arité (le nombre d’opérande. • La priorité. • L’associativité (de gauche à droite, de droite à gauche). Conseil : il est vivement recommandé d’utiliser le mécanisme seulement pour étendre les opérations existantes à des classes plus complexes. Il est dangereux de faire signifier à des opérateurs des opérations qui peuvent surprendre le programmeur. Exemple : l’opérateur – pour une addition ou une lecture. Donc lors de la redéfinition d’un opérateur arithmétique pour une classe imitez la signification du même opérateur pour un type simple du C. Avant de redéfinir les opérateurs du C il est utile de rappeler certaines propriétés et exigences de ces opérateurs. Certains des ces opérateurs sont binaires, d’autres unaires, certains exigent qu’un opérande soit un objet. On peut classer les opérateurs en plusieurs catégories et il convient lors de la redéfinition de ne pas changer de catégories un opérateur. Remarque : différence entre objet et expression. Un objet représente un emplacement mémoire : • On peut lui affecter quelque chose : il peut être placé à gauche d’un opérateur d’affectation. • On peut parler de son adresse. Une expression ne représente pas une case mémoire : • On ne peut rien affecter à une expression, ne peut être à gauche d’un opérateur d’affection. • On ne peut en prendre l’adresse. Remarque : il est toujours possible à partir d’un objet d’obtenir une expression : en effet il suffit d’évaluer l’objet (récupérer la valeur inscrite dans la case) pour obtenir une valeur. Le contraire n’est pas vrai.
2.
CATEGORIE D’OPERATEUR 2.1. OPERATEUR CLASSIQUE : EXPRESSION X EXPRESSION
→
EXPRESSION
+ - (binaire) * / == > < => <= % && || != << >> ^ & | 2.2. OPERATEUR : OBJET X EXPRESION
→
EXPRESSION
= op= op ∈ {+, -, -, *, *, /, /, %, %, &&, &&, ||, ||, ^, ^, &, &, |, |, << <<, >> >>} 2.3. OPERATEUR
UNAIRE : EXPRESION → EXPRESSION
- (unaire) ! ~ 2.4. OPERATEUR
UNAIRE : OBJET → EXPRESSION
++ -- & (adresse de) Conception et programmation orientée objet avec C++
2 1
LA SURCHAGE D’OPERATEURS
2.5. OPERATEUR BINAIRE : EXPRESSION X EXPRESSION
[] 2.6. OPERATEUR BINAIRE : EXPRESSION
→
→
OBJET
. →
OBJET
* 2.7. OPERATEUR PARTICULIER
? : , Les catégories 1 à 3 peuvent être surchargée, la 4 sauf le & et dans la 5 uniquement les [].
3.
SYNTAXE DE REDEFINITION
Un opérateur surchargé peut être une fonction ordinaire ou une fonction membre.
3.1. S YNTAXE DE DEFINITION POUR UNE FONCTION ORDINAIRE 3.1.1. OPERATEUR BINAIRE Soit @ un opérateur binaire et trois classes X, Y, Z. On définit :
Z operator @(Xx,Yy) { // corps de la fonction } A l’appel :
Zz; Yy; Xx; z = x @ y; //<=> z = operator @(x,y);
3.1.2. OPERATEUR UNAIRE Soit @ un opérateur unaire. On définit :
Y operator @(X x); { // corps de la fonction } A l'appel
z = @x; // <=> z = operator @(x);
3.2. S YNTAXE DE DEFINITION POUR UNE FONCTION MEMBRE 3.2.1. OPERATEUR BINAIRE Soit @ un opérateur binaire de la classe X. On définit :
class X { Z operator @(Yy); }; Corps de la fonction membre :
Z X :: operator @(Yy) { // corps de la fonction }
Conception et programmation orientée objet avec C++
2 2
LA SURCHAGE D’OPERATEURS
A l'appel :
Z = x @ y; //<=> z = x.operator @(y);
3.2.2. OPERATEUR UNAIRE Soit @ un opérateur unaire de la classe X. On définit :
class X { Z operator @(); }; Corps de la fonction membre :
Z X :: operator @() { // corps de la fonction } A l'appel :
Z = @x; // <=> z = x.operator @();
4. EXERCICE Pour la classe fraction définir les opérateurs : + * - (binaire) - (unaire) / == > < => <= ++ -- += *= -= /= << >>
4.1. OPERATEUR + ☺
class Fraction { Fraction operator +(const Fraction &a) const; }; Fraction Fraction :: operator +(const Fraction &a) const { return Fraction(num * a.den + den * a.num, den * a.den).Simplifie(); }
4.2. OPERATEUR - UNAIRE ☺
class Fraction { Fraction operator -() const; }; Fraction Fraction :: operator -() const { return Fraction(-num, den); }
4.3. OPERATEUR * ☺
class Fraction { Fraction operator *(const Fraction &a) const; }; Fraction Fraction :: operator *(const Fraction &a) const { return Fraction(num * a.num, den * a.den).Simplifie(); }
4.4. OPERATEUR == ☺
class Fraction { bool operator ==(const Fraction &a) const; };
Conception et programmation orientée objet avec C++
2 3
LA SURCHAGE D’OPERATEURS
bool Fraction :: operator ==(const Fraction &a) const { return (num * a.den == den * a.num); }
4.5. OPERATEUR < ☺
class Fraction { bool operator <(const Fraction &a) const; }; bool Fraction :: operator <(const Fraction &a) const { return num * a.den < den * a.num; }
4.6. OPERATEUR ++ ☺
class Fraction { const Fraction & operator ++(); }; const Fraction &Fraction :: operator ++() { num += den; return *this; /*il faut renvoyer l'argument implicite sinon impossible de faire par exemple x2 = ++x1; car ++x1 ne renverrait rien */ }
4.7. OPERATEUR += ☺
class Fraction { Fraction operator +=(const Fraction &a); }; Fraction Fraction :: operator +=(const Fraction &a) { num = num * a.den + den * a.num; den *= a.den; (*this) = (*this).Simplifie(); return *this; // sinon x3 = x2 += x1; impossible }
4.8. OPERATEUR << On réalise une fonction ordinaire. ☺
ostream &operator <<(ostream &os, const Fraction &a); ostream &operator <<(ostream &os, const Fraction &a) { a.Ecrire(os); return os; } On renvoie os pour pouvoir écrire cout << x1 << x2;. On passe le résultat par référence pour permettre d’écrire dedans. Si on l’omets on renvoie une expression donc pas d’écriture possible.
4.9. OPERATEUR >> ☺
istream &operator >>(istream &is, Fraction &a); istream &operator >>(istream &is, Fraction &a) { a = Lire(istream); return is; }
Conception et programmation orientée objet avec C++
2 4
LA SURCHAGE D’OPERATEURS
4.10. A UTRES OPERATEURS ☺
Fraction operator -(const Fraction &a, const Fraction &b); Fraction operator /(const Fraction &a, const Fraction &b); bool operator !=(const Fraction &a, const Fraction &b); bool operator >(const Fraction &a, const Fraction &b); bool operator <=(const Fraction &a, const Fraction &b); bool operator >=(const Fraction &a, const Fraction &b); Fraction operator -(const Fraction &a, const Fraction &b) { return a+(-b); } Fraction operator /(const Fraction &a, const Fraction &b) { return a*(b.Inverse()); } bool operator !=(const Fraction &a, const Fraction &b) { return !(a==b); } bool operator >(const Fraction &a, const Fraction &b) { return bb); }
Conception et programmation orientée objet avec C++
2 5
LES CONVERSIONS
L E S C O N V E R S I O N S Outre les conversions entre types simples du C, il existe en C++ deux mécanismes pour réaliser les conversions entre deux classes différentes. Il est possible de réaliser des conversions à l'aide soit : • d'un constructeur. • d'un opérateur de conversion.
1.
CONVERSION A L'AIDE D'UN CONSTRUCTEUR
Le constructeur peut servir à réaliser des conversions d'une classe vers une classe et plus particulièrement d'un type simple vers une autre classe. Le constructeur définit une conversion dès qu'il existe un constructeur à un paramètre. La conversion est réalisée depuis le type de l'argument unique (à convertir) vers la classe du constructeur. Exemple : La classe fraction a été muni du constructeur suivant :
Fraction::Fraction(const int &a); // constructeur à un paramètre Conséquence : quelque soit n entier, n est converti en fraction si le contexte l'exige. n est converti en Fraction(n). Exemple :
Fraction x1=3; // Fraction x1 = Fraction(3); x1 = x1 + 9; // x1 = x1 + Fraction(9); Le compilateur prend l’initiative d’appeler le constructeur adapté à la situation de façon à rendre possible l'instruction.
1.1. A PPLICATION ⎧a1x + b1y = c1 Soit le problème suivant : résoudre ⎨ avec a1, a2, b1, b2 des entiers, c1, c2 des fractions. On obtient la ⎩a2 x + b2 y = c 2 fonction suivante :
bool ResoutSystem2x2(const int &a1, const int &b1, const Fraction &c1, const int &a2, const int &b2, const Fraction &c2, Fraction &x, Fraction &y { int d; d = a1 * b2 - a2 * b1; if (d == 0) return false; else { x = (c1 * b2 - c2 * b1) / d; y = (c2 * a1 - c1 * a2) / d; return true; } } Conversions réalisées Avantages des conversions : elles nous évitent d'avoir à écrire les fonctions qui réalisent :
#+%→ % %+Z → % %*Z → % Z*% → % %/Z → % += *= /= ⎧a1x + b1y = c1
Les conversions permettent également d'améliorer la solution d’un problème : ⎨
⎩ a 2 x + b2 y = c 2
des fractions. On obtient les fonctions suivantes :
Fraction Det(const Fraction &a11, const Fraction &a12, const Fraction &a21, const Fraction &a22) { return a11 * a22 - a21 * a12; }
Conception et programmation orientée objet avec C++
avec a1, a 2, b 1, b 2, c 1, c 2
2 6
LES CONVERSIONS
bool ResoutSystem2x2(const Fraction &a1, const Fraction &b1, const Fraction &c1, const Fraction &a2, const Fraction &b2, const Fraction &c2, Fraction &x, Fraction &y) { Fraction d = Det(a1,b1,a2,b2); if (d == 0) return false; else { x = Det(c1,b1,c2,b2)/d; y = Det(a1,c1,a2,c2)/d; return true; } }
1.2. GENERALISATION Tout constructeur d'une classe X définie avec un unique argument de classe Y, définit implicitement une conversion de la classe Y vers la classe X. Dès que le contexte l'exige, un Y est converti en X. La nature de la conversion est définie par le programmeur car elle est réalisée dans le corps du constructeur de X.
2.
CONVERSION A L'AIDE D'UN OPERATEUR DE CONVERSION
Soit une classe X et une classe Y. Il est possible de définir une conversion de la classe X vers la classe Y, en définissant dans la classe X une fonction membre particulière : un opérateur de conversion vers Y. La syntaxe de définition est la suivante:
class X { public: operator Y() const; }; Corps de l’operateur de conversion :
X :: operator Y() const { return ’’expression dont l’évaluation produit une instance de la classe Y’’; } A présent, dès que le contexte l'exige, un X est converti en Y à l'aide de l'opérateur de conversion. 2
Exemple : on dispose de trois fractions p, q et r et l’on voudrait bien résoudre l'équation px + qx + r = 0. On a déjà écrit la fonction suivante :
bool ResoutEquationDegre2 (const double &a, const double &b, const double &c, double &x, double &y, const double &Eps=FLT_EPSILON); Problème: on dispose de fractions p, q et r et la fonction exige des données sous format double. La solution est d’écrire une conversion de Fraction vers double. Il faut donc modifier la classe Fraction :
class Fraction { public : operator double() const; }; Le corps de l’opérateur :
Fraction :: operator double() const { return (double)num / den;// force le transtypage de num car en C entier / entier = entier } A présent on peut résoudre l’équation, on obtient le programme suivant :
main() { Fraction p(8,3), q(11,5), r(-3,4); double x,y; if (ResoutEquationDegre2(p,q,r,x,y)) cout << "les solutions sont " << x << " et " << y << "\n"; else cout << "Pas de solution"; }
Conception et programmation orientée objet avec C++
2 7
LES CONVERSIONS
Remarque sur les conversions : soient deux classes X et Y, on désire réaliser une conversion de la classe X vers la classe Y. La conversion va être réalisée à l’aide d’un constructeur ou d’un opérateur ? • Si la conversion est réalisée à l’aide d’un constructeur, ce constructeur est écrit dans la classe cible Y. La conversion est réalisée sous forme de constructeur dans Y lorsque X n’est pas accessible ou bien il est dangereux de le modifier ou bien si X est un type simple. • Si la conversion est réalisée à l’aide d’un opérateur, cet opérateur est membre de la classe source X. La conversion est réalisée sous forme d’opérateur dans la classe X lorsque Y n’est pas accessible ou bien il est dangereux de le modifier ou bien si Y est un type simple. Conversion X Source opérateur de conversion
3.
→
Y Destination constructeur à un argument de X
CONVERSION EXPLICITE
Comme pour toutes les conversions définies en C, il est possible de réaliser des conversions forcées grâce à l’opérateur cast pour les conversions définies à l’aide d’opérateurs ou de constructeurs. Exemple :
Fraction x(8,3); cout << x; cout << (double)x; cout << (Fraction)7;
4.
INCONVENIENTS DES CONVERSIONS
Les conversions implicites peuvent provoquer certaines ambiguïtés, car laissées à l’initiative du compilateur, aussi elles obéissent à certaines règles dont en voici deux : • L'argument implicite d’une fonction membre ne peut pas être obtenu par une conversion implicite. Exemple :
Fraction x(2,3), y; y = 3 + x; // pas possible y = (Fraction)3 + x; // conversion forcée y = x + 3; // ok •
Deux conversions implicites successives ne sont pas réalisées.
Soit trois classes X, Y, Z et deux conversions : X→Y et Y→Z. Soit x, y, z des instances de X, Y, Z alors :
z z z z
= = = =
x; // nécessite deux conversions implicites : pas réalisées (Y)x; // ok (Z)x; // ok (Z)(Y)x; // ok
Malgré ces règles restreignant les conversions implicites il subsiste des problèmes graves. Exemple :
Fraction Fraction :: operator Somme(const Fraction &x) const { return (num * x.den + den * x.num, den * x.den); // il manque le terme Fraction pour indiquer qu'il s’agit d'un constructeur } A la compilation, le compilateur ne retourne pas de message d'erreurs. Il y a conversion implicite du fait de la virgule (opérateur d’enchaînement) et l’appel de Fraction (cons int &a); Le langage C++ permet maintenant de faire les conversions avec constructeur de manière explicite uniquement.
class Fraction { public: explicit Fraction(const int &a); operator int() const; }; Fraction :: operator int() const { return num / den; } main() { Fraction x1(8,3), x2; int a, b; a=2;
Conception et programmation orientée objet avec C++
2 8
LES CONVERSIONS
x2 x2 x2 x2
= x1 + 7; // refusé = x1 + (Fraction)7; // ok = x1 + a; = a+ x1; + + x 2 = x1 a; ≠ x 2 = a x1; ↓ ↓ ↓ ↓ % % # # &' &' +% +# ↓ ↓ " %
}
Conception et programmation orientée objet avec C++
2 9
EXEMPLE COMPLET DE CLASSE
E X E M P L E C O M P L E T D L A S S E D E C 1.
RAPPEL SUR LES CHAINES DE CARACTERES EN C
Elles sont réalisées sous formes de tableaux. Les tableaux contenant des caractères comportent un caractère spécial de terminaison ‘\0’ (octet dont tous les bits sont à 0).
main() Mémoire { char t[6]; 0 1 2 3 4 5 t[0]=’S’; t[1]=’a’; t ‘S’ ‘a’ ‘l’ ‘u’ ‘t’ \0 t[2]=’l’; t[3]=’u’; n 5 t[4]=’t’; t[5]=’\0’; cout << t; s int n = strlen(t); char *s; // case mémoire habilité à contenir une adresse physique d’une case contenant un caractère s = t; // t expression littérale de l’adresse physique qui pointe sur t cout << s[0]; //renvoie la première lettre cout << *s; // désigne le contenu de la case pointée par s : *s == s[0] t = s; // refusé Mémoire
s = ’’pif’’; n = strlen(s); // ok
s
0
1
2
3
‘p’
‘i’
‘f’
\0
char c; c = s; // refuse char *s1, *s2, *r; s1 = ’’pif’’; s2 = ’’ le chien’’; int u1, u2; u1 = strlen(s1); u2 = strlen(s2); r = new char[1 + u1 + u2]; strcpy(r, s1); // r est la destination, s1 est la source strcat(r, s2); // r doit contenir au minimum ‘\0’, concatène une chaîne dan une chaîne ayant déjà des caractères } Mémoire
s1
s1
r
2.
0
1
2
3
‘p’
‘i’
‘f’
\0
0
1
2
3
4
5
6
7
8
9
‘_’
‘l’
‘e’ ‘_’
‘c’
‘h’
‘i’
‘e’
‘n’
\0
0
1
2
3
4
5
6
7
8
9
10
11
12
‘p’
‘i’
‘f’
‘_’
‘l’
‘e’ ‘_’
‘c’
‘h’
‘i’
‘e’
‘n’
\0
EXERCICE
Créer une classe Chaine permettant de gérer les chaines de caracteres en C++. Cette classe doit: • Etre compatible avec char, char*. • Etre sure d'emploi. • Etre simple d'utilisation. • Utiliser l'existant sur le type char*.
Conception et programmation orientée objet avec C++
3 0
EXEMPLE COMPLET DE CLASSE
La classe comportera: • Un ou plusieurs constructeurs • Une fonction longueur • Faire la surcharge d'operateur : o == o < o <= o + (concatenation) o << o >> L'allocation dynamique s'effectue avec l'opérateur new.
3. ☺
LA CLASSE CHAINE
#define DEBUG class Chaine { char *t; // pour l’instant il n’y a pas de tableau (pas de caractères), le tableau sera alloué en cours d’exécution void Alloue(const Naturel &n); //alloue n caractères public : ¤ Chaine (); // crée la chaîne vide ¤ Chaine (const char *s); ¤ Chaine (const char &c); ¤ Chaine (const Chaine &ch); // constructeur de copie ¤ Chaine (const Naturel &n, const char &c); // crée une chaîne contenant n fois le caractère c ¤ ~ Chaine(); // ~ est le symbole du destructeur Naturel Longueur() const; Chaine operator +(const Chaine & ch) const; ¤ const Chaine &operator =(const Chaine &ch); // le résultat est passé par référence char operator [](const Naturel &i) const; // renvoie une valeur, sert à consulter /* 0 ≤ i ≤ (*this).Longueur() -1 */ char &operator [](const Naturel &i); // renvoie un objet, sert à modifier bool operator <=(const Chaine &ch) const; void Ecrire(ostream &os) const; }; ostream &operator <<(ostream &os,const Chaine &ch); istream &operator >>(istream &is, Chaine &ch); En bleu sont les fonctions membres nécessaires pour réaliser une classe avec allocation dynamique. Les fonctions précédées du signe ¤ effectue une allocation dynamiquement.
main { char c; char p[10]; char *s; //plus souple d’emploi que le tableau s = ’’salut’’; Chaine ch1(s); cout << sizeof(Chaine); // taille en octets de n’importe quelle chaîne n = ch1.Longueur(); c = ‘M’; Chaine ch2(c), ch3; Chaine ch4(’’pif’’), ch5(’’ le chien’’), ch6; ch6 = ch4 + ch5; // ch6 = ch4.operator+(ch5); }
Mémoire 0
1
2
3
5
6
7
8
p
s
0
1
2
3
4
5
‘s’
‘a’
‘l’
‘u’
‘t’
\0
0
1
‘M’
\0
ch1 t c
‘M’
ch2 t
0 ch3 t
Conception et programmation orientée objet avec C++
4
\0
9
3 1
4.
EXEMPLE COMPLET DE CLASSE
FONCTIONS MEMBRES 4.1. FONCTION PRIVEE A LLOUE
☺
void Chaine :: Alloue(const Naturel &n) { t = new char[n]; if (t == NULL) Erreur(’’Chaine:: Alloue(n): mémoire saturée’’); }
4.2. CONSTRUCTEUR CHAINE(CONST CHAR *S) ☺
Chaine :: Chaine(const char *s) { t = new char[1 + strlen(s)]; strcpy(t, s); } Optimisation 1 : il est possible qu’il n’y a plus de place dans le tas mémoire d’où le test (allocation dynamique de mémoire).
☺
Chaine :: Chaine(const char *s) { t = new char[1 + strlen(s)]; if (t == Null) Erreur(’’Chaine°:: Chaine(s) : mémoire saturée’’); strcpy(t, s); } Optimisation 2 : utilisation de la fonction privée Alloue().
☺
Chaine :: Chaine(const char *s) { Alloue(1 + strlen(s)); strcpy(t, s); }
4.3. CONSTRUCTEUR CHAINE(CONST CHAR &CH) ☺
Chaine :: Chaine(const Chaine &ch) { t = new char[1 + strlen(ch.t)]; strcpy(t, ch.t); cout << ’’Appel au constructeur de copie’’; } Chaine :: Chaine(const Chaine &ch) { Alloue(1 + strlen(ch.t)); strcpy(t, ch.t); cout << ’’Appel au constructeur de copie’’; }
4.4. CONSTRUCTEUR CHAINE(CONST CHAR &C) ☺
Chaine :: Chaine (const char &c) { t = new char[2]; *t = c; // équivaut à t[0] = c; *(t + 1) = ’\0’; // équivaut à t[1] = ’\0’; } Chaine :: Chaine (const char &c) { Alloue(2); *t = c; // équivaut à t[0] = c; *(t + 1) = ’\0’; // équivaut à t[1] = ’\0’; }
4.5. CONSTRUCTEUR CHAINE() ☺
Chaine :: Chaine () // crée la chaîne vide
Conception et programmation orientée objet avec C++
3 2
EXEMPLE COMPLET DE CLASSE
{ t = new char [1]; t[0] = ’\0’; } Chaine :: Chaine () // crée la chaîne vide { Alloue(1); t[0] = ’\0’; }
4.6. CONSTRUCTEUR CHAINE (CONST N ATUREL &N, CONST CHAR &C) Chaine (const Naturel &n, const char &c) // crée une chaîne contenant n fois le caractère c {}
4.7. DESTRUCTEUR ☺
Chaine :: ~ Chaine() { delete []t; // cout << ’’appel au destructeur’’; } Le système vérifie qu’il y a un destructeur dans la classe à chaque fois qu’une instance est restituée et y fait appel
4.8. OPÉRATEUR + ☺
Chaine :: Chaine operator +(const Chaine & ch) const { Chaine r; r.t = new char[1 + strln(t) + strlen(ch.t)]; strcpy(r.t, t); strcat(r.t, ch.t); return r; Mémoire } main() { Chaine ch4(’’pif’’); Chaine ch5(’’ le chien’’); Chaine ch6; ch6 = ch4 + ch5; // ch6 = ch4.operator+(ch5); }
0
1
2
3
ch4
‘p’
‘i’
‘f’
\0
0
1
2
3
4
5
6
7
8
9
ch5
‘_’
‘l’
‘e’ ‘_’
‘c’
‘h’
‘i’
‘e’
‘n’
\0
ch6
\0
0
0 r
0
1
2
3
4
5
6
7
8
9
10
11
12
‘p’
‘i’
‘f’
‘_’
‘l’
‘e’ ‘_’
‘c’
‘h’
‘i’
‘e’
‘n’
\0
\0
Pour ne pas laisser traîner en mémoire le tableau vide d’initialisation de r on réécrit la fonction de la manière suivante : ☺
Chaine Chaine :: operator +(const Chaine & ch) const { Chaine r(strlen(t) + strlen(ch.t),’ ’); strcpy(r.t, t); strcat(r.t, ch.t); return r; }
4.9. OPÉRATEUR = ☺
const Chaine &Chaine :: operator =(const Chaine &ch) // le résultat est passé par référence { if (this != &ch) // evite ch = ch { delete []t; Alloue(1 + strlen(ch.t)); // t = new char[1 + strlen(ch.t)]; strcpy(t, ch.t); } return *this;
Conception et programmation orientée objet avec C++
3 3
EXEMPLE COMPLET DE CLASSE
} main() { Chaine ch1(’’bonjour’’), ch2(’’ciao’’); ch2 = ch1; // équivaut à ch2.operator = (ch1); }
4.10. OPÉRATEUR [] ☺
Mémoire ch1 t
‘b’
‘o’
‘n’
‘j’
‘o’
ch2 t
‘c’
‘i’
‘a’
‘o’
\0
ch2 t
‘b’
‘o’
‘n’
‘j’
‘o’
‘u’
‘r’
\0
‘u’
‘r’
\0
main() { Chaine ch1(’’pif’’), ch2(’’pouf’’), ch3; char c; c = ch1[0]; // équivaut à c= ch1.operator[](0); c = ch1[18];; // équivaut à c= ch1.operator[](18); } char Chaine :: operator [](const Naturel &i) const /* renvoie une valeur, sert à consulter, 0 ≤ i ≤ (*this).Longueur() -1 */ { #ifdef DEBUG if (i >= strlen(t)) Erreur(’’Chaine :: operator[](i) : l’élément défini’’); #endif return t[i]; }
i
n’est
pas
Test coûteux à l’exécution à cause de la boucle de parcours sur la chaîne de caractères : phases de débugage ???????? certains tests, supprimés après finalisation du programme. Le test sera ignoré à la compilation quand l’instruction #define DEBUG sera mise en commentaire.
main() { Chaine ch1(’’pif’’), ch2(’’pouf’’), ch3; char c; ch1[1] = c; // équivaut à ch1.operator[](1) = c; } Il n’y a pas de const à la fin du prototype de la fonction, on peut modifier l’argument implicite. C’est un objet et on va le modifier. On renvoie un objet : on a le droit de le renvoyer par référence. ☺
char &Chaine :: operator [](const Naturel &i) /* renvoie un objet, sert à modifier */ { #ifdef DEBUG if (i >= strlen(t)) Erreur(’’Chaine :: défini’’); #endif return t[i]; }
operator[](i) :
4.11. OPÉRATEUR <= ☺
main() { Chaine ch1(’’pif’’), ch2(’’pouf’’), ch3; char c; bool ok; ok = (ch1 <= ch2); // équivaut à ok = ch1.operator[(1) = c;] } bool Chaine :: operator <=(const Chaine &ch) const { return strcmp(t, ch.t) <= 0; }
4.12. FONCTION ECRIRE() ☺
void Chaine :: Ecrire(ostream &os) const {
Conception et programmation orientée objet avec C++
l’élément
i
n’est
pas
3 4
EXEMPLE COMPLET DE CLASSE
os << t; }
4.13. OPÉRATEUR << main() { Chaine ch1(’’pif’’); cout << ch1; // équivaut à cout.operator<<(ch1); } On réalise une fonction ordinaire car on ne modifie pas la classe ostream. ☺
ostream &operator <<(ostream &os,const Chaine &ch) { ch.Ecrire(os); return os; }
4.14. OPÉRATEUR >> ☺
main() { Chaine ch3; cin << ch1; // équivaut à cin.operator<<(ch3); } istream &operator >>(istream &is, Chaine &ch) { static char s[1024]; is.getline(s, 1023, ’ ’); ch = Chaine(s); return is; } istream &is permet d’écrire cin >> ch1 >> ch2.
4.15. FONCTION LONGUEUR () ☺
Naturel Chaine :: Longueur() const { return (Naturel) strlen(t); }
Conception et programmation orientée objet avec C++
3 5
L’HERITAGE ET LES CLASSES DERIVEES
L’ H E R I T L A S S E S D E R I V H T A G E E T L L E S C V E E S 1.
INTRODUCTION
Soit une classe X définie comme suit :
classe X { int a; double b; public : X(int n,double x); void Ecrire() const; }; Et soit la classe Y définie comme suit :
class Y : public X // la classe Y dérive de la classe X { char c[10]; public : Y(); void Ecrire() const; }; Cette définition fait de Y une classe dérivée de la classe X. On dit que : • Y hérite de X. • Y est une sous classe de X. • X est la classe mère de Y ). • X est la superclasse de Y. • X est la classe de base de Y. Cette relation est souvent notée par le diagramme:
L’intêret de cette défintion est que toute instance de la classe Y est une instance de la classe X, autrement dit : {ensemble des instances de Y} ⊂ { ensemble des instances de X}
Ensemble des instances de Y
Ensemble des instances de X
Soit y une instance de la classe Y, selon le contexte, y est considérée comme instance de Y ou de X. Par contre si x est une instance de X, x n’est jamais considérée comme instance de Y car Y ⊂ X et X⊄ Y . On peut considérer que les instances de T sont des instances particulières, spécialisées de la classe X. On peut donc considérer que la relation d’hériyage est une relation de spécialisation, du général au particulier.
2.
HERITAGE ET CONVERSION
Lorsque y est vu comme une instance de X seuls sont considérées ses membres (données et fonctions) relatives à la classe X, c'est-à-dire :
Conception et programmation orientée objet avec C++
3 6
L’HERITAGE ET LES CLASSES DERIVEES y a b X() Ecrire()
Membres de y lorsque y est vu comme instance de X
y a b
c
X()
Y()
Ecrire()
Ecrire()
Membres de y lorsque y est vu comme instance de X. Concrètement : taille(Y) = taille(X) + 10 (octets du tableau c)
Lorsqu’un membre a le même nom dans la classe dérivée et la classe de base, comme c’est le cas ici pour la fonction Ecrire(), la définition de la classe dérivée cache celle de la classe de base. Résumé : lorsque y est considéré comme instance de la classe X, toutes les définitions relatives à Y sont ignorées (mais toujours présentes en mémoires en ce qui concerne les membres données).
3.
PROTECTION DES MEMBRES ET HERITAGE
Les fonctions membres de la classe Y n’ont pas accès à la partie privée de la classe X sinon pour contourner le mécanisme de protection il suffirait de réaliser un héritage. Si une classe décide d’autoriser ses classes dérivées d’accéder à certains de ses membres, elle doit déclarer ceux-ci protégés (mot clé protected). Résumé des mécanismes de protection : • Si un membre de X est déclaré privé (private) seules les fonctions membres de X y ont accès. • Si un membre de X est déclaré protégé (protected) les fonctions membres de X et toutes classes dérivées de X y ont accès. • Si un membre de X est déclaré public (public) toutes fonctions y ont accès. Exemple 1 : gestion du personnel d’une entreprise. On définit une classe Employé : • Nom. • Echelon. • Nombre d’heures travaillées dans le service. Les opérations souhaitées pour un employé : • Constructeur. • Calcul de salaire.
class Employe { Chaine nom; int echelon; double nbheures; public : Employe(const Chaine &nomEmploye, const int &nbheuresMois); double Salaire(const double &tarihoraire) const; void Ecrire(ostream &os) const; void EcrireNom( ostream &os) const;
&echelonEmploye,
const
double
}; Un chef de service est un employé responsable d’un service, on lui associe le chiffre d’affaires de ce service. Il est clair qu’un chef de service est un employé particulier, spécialisé : {ensemble des chefs} ⊂ {ensemble des employés} d’où la classe Chef héritant de la classe Employe. Informations supplémentaires : • Nom du service. • CA du service. • Consulter le Ca.
Conception et programmation orientée objet avec C++
Employe
Hérite de
Chef
3 7
L’HERITAGE ET LES CLASSES DERIVEES
On obtient la classe suivante :
class Chef : public Employe { Chaine nomService; public : Chef(const Chaine &nomChef, const int &echeleonChef, const double &nbheuresMois, const Chaine &nomserviceChef, const double &chiffreAffaires); Chef(const Employe &employe, const Chaine &nomserviceChef, const double &chiffreAffaires); double Chiffre() const; double Salaire(const double &tarifhoraire) const; void Ecrire(ostream &os) const; }; Chef :: Chef(const Chaine &nomChef, const int &echeleonChef, const double &nbheuresMois, const Chaine &nomserviceChef, const double &chiffreAffaires) : Employe(nomChef, echelonChef, nbheuresMois) { nomservice = nomserviceChef; CA = chiffreAffaires; } Chef :: Chef(const Employe &employe, &chiffreAffaires) : Employe(employe) { nomservice = nomserviceChef; CA = chiffreAffaires; }
const
Chaine
&nomserviceChef,
const
double
double Chef :: Chiffre() const { return CA; } double Chef :: Salaire(const double &tarifhoraire) const { return 0.01 * CA + Employe :: Salaire(tarifhoraire); } void Chef:: Ecrire(ostream &os) const { Employe:: Ecrire(os); os << ’’service’’ << nomservice << ’’chiffre d’affaires = ’’ << CA; } main() { Employe e1(_,_,_); Chef f1(_,_,_); e1.Ecrire(cout); f1.Ecrire(cout); f1.EcrireNom(cout); cout << e1.Chiffre(); // pas de fonction chiffre dans la classe Employe : refusé cout << f1.Chiffre(); e1 = f1; // converti le chef en employé et zappe les informations supplémentaires f1 = e1; // refusé car manque d’informations ((Employe)f1).Ecrice(cout); // pour forcer l’utilisation de la fonction de Employe f1.Employe :: Ecrire(cout); // comme ci-dessus }
Conception et programmation orientée objet avec C++
3 8
L’HERITAGE ET LES CLASSES DERIVEES
La relation d’héritage peut être enrichie par l’ajout de nouvelles classes. On obtient une hiérarchie de classe ou graphe d’héritage. Plus on remonte dans cette hiérarchie, plus les classes deviennent générales. Plus on descend dans cette hiérarchie plus les classes deviennent spécialisées. Les fonctions membres, données membres communes à plusieurs classes migrent vers la classe de base. L’intêret est d’éliminer les redondances d’informations, de code. La classe de base permet de factoriser les notions communes à plusieurs classes.
Conception et programmation orientée objet avec C++
3 9
LES FONCTIONS VIRTUELLES
L E S F O N C T I O N S V I R T U U E L L E S
Conception et programmation orientée objet avec C++
4 0
LES CLASSES ABSTRAITES
L E S C L A S S E S A B S T R A I T T E S
Conception et programmation orientée objet avec C++
4 1
LES PATRONS (TEMPLATES) LES CLASSES ABSTRAITES
L E S P A T R O N S (T E M P L A T E S ) 1.
INTRODUCTION
On veut calculer le maximum de deux entiers. Fichier entier.h
int Max(const int &a, const int &b); Fichier entiers.cpp
int Max(const int &a, const int &b) { if (a >b) return a; else return b; } Maintenant on veut réaliser la même fonction avec des réels. Fichier reels.h
double Max(const double &a, const double &b); Fichier reels.cpp
double Max(const double &a, const double &b) { if (a > b) return a; else return b; } On a besoin ensuite de manipuler des fractions avec le même type de fonction. Fichier fractions.h
Fraction Max(const Fraction &a, const Fraction &b); Fichier fractions.cpp
Fraction Max(const Fraction &a, const Fraction &b) { if (a > b) return a; else return b; } #include ’’entiers.h’’ #include ’’reels.h’’ #include ’’fractions.h’’ main() { int x, y, z; x = 3; y = 5; z = Max(x,y); double r1, r2, r3; r1 = 3.5; r2 = 7.8; r3 = Max(r1,r2); Fraction x1(2,3), x2(1,4), x3; x3 = Max(x1,x2); } On remplace le type int pour les entiers par un type quelconque pour faire générique. On fait abstraction des fichiers réels et fractions. Fichier entier.h
Typedef int T; T Max(const T &a, const T &b); Fichier entiers.cpp
T Max(const T &a, const T &b)
Conception et programmation orientée objet avec C++
4 2
LES PATRONS (TEMPLATES) LES CLASSES ABSTRAITES
{ if (a >b) return a; else return b; } #include ’’entiers.h’’ /*#include ’’reels.h’’ #include ’’fractions.h’’*/ main() { int x, y, z; x = 3; y = 5; z = Max(x,y); /*double r1, r2, r3; r1 = 3.5; r2 = 7.8; r3 = Max(r1,r2); Fraction x1(2,3), x2(1,4), x3; x3 = Max(x1,x2);*/ } Le programme est testé et validé, ensuite pour les réels on garde le tout en remplacant le typedef int T par un double T. On déplace dans le programme principal les commentaires. On refait de même pour les fractions. On n’a alors qu’une seule possibillité d’utilisation de la fonction à la fois. Le but est le paramétrage de la fonction par le type : c’est le mécanisme du modèle de fonctions
2.
MODELE DE FONCTION
Fichier modeles.h (déclaration du modèle)
template T Max(const T &a, const T &b); Fichier modeles.cpp (définition du modèle)
template // à mettre devant chaque modèle de fonction T Max (cont T &a, const T &b) { if (a > b) return a; else return b; } Le modèle ne se compile pas. Suivant les types de compilateur, la compilation et l’édition des liens sera différente. Fichier modeles.h
#ifndef MODELES.H #define MODELES.H template T Max(const T &a, const T &b); #include ’’modeles.cpp’’ #endif Fichier modeles.cpp
template // à mettre devant chaque modèle de fonction T Max (cont T &a, const T &b) { if (a > b) return a; else return b; } Avec C++ Builder le corps de la méthode est à mettre dans le fichier d’entêtes. On compile le programme principal.
3.
MODELE DE CLASSE
Supposons que dans un certain contexte de programmation, on ait besoin de la notion de pile de nombres entiers. La classe Pile obtenue, doit être munie des fonctions suivantes : • Insérer dans la pile.
Conception et programmation orientée objet avec C++
4 3 • • •
LES PATRONS (TEMPLATES) LES CLASSES ABSTRAITES
Retirer de la pile. La pile est-elle vide. Créer une pile vide.
#include ’’pile.h’’ main() { Pile p; int m; Pile p1; p.Inserer(-2); p.Inserer(7); p.Inserer(3); p.Inserer(4); m.Retirer(); p1 = p.Insertion(8); }
Mémoire p
-2
7
3
m
4
p
-2
7
4
p1
-2
7
4
4
8
Fichier pile.h
#define MAXPILE 100 // arbitriare #define DEBUG class Pile // définit une pile d’entiers (LIFO) { int t[MAXPILE]; // t[0] à t[p] sont les cases occupées int s; // -1 ≤ s ≤ MAXPILE-1 indice de la dernière case occupée public : Pile(); // crée une pile vide boo Estvide()const; void Inserer(const int &a); // insère l’élément x en tête, modifie la pile int Retirer(); // retire l’élément de tête et le renvoie, modifie laa pile Pile Insertion(const int &a) const; // insertion en tête, ne modife pas la pile argument implicite };
Fichier pile.cpp
Pile :: Pile() { s = -1; } void Pile :: Inserer(const int &a) { #ifndef DEBUG if (s = MAXPILE – 1) Erreur(’’Pile :: Inserer(a) : pile pleine’’); #endif t[++s] = a; // incrémente d’abord s et utilise la valeur de s //équivalent à ++s; t[s] = a; } bool Pile :: Estvide() const { return (s == -1); // teste la valeur de s égale à -1 } int Pile :: Retirer() { #ifndef DEBUG If (s == -1) Erreur(’’Pile:: Retirer(): pile vide’’); #endif return t[s--]; // renvoie t[s]et décrémente ensuite }
Conception et programmation orientée objet avec C++
4 4
LES PATRONS (TEMPLATES) LES CLASSES ABSTRAITES
Pile Pile :: Insertion(const int &a) const { Pile r(*this); // copie physique r.Inserer(a); return r; } Pour faire un template pour utiliser avec des doubles, fractions et autres on remplace les int par un type générique T associé au typedef correspondant. Comme précédemment la limitation de cette technique est l’utilisation d’un seul type de pile dans le programme principal. D’où la nécessité de créer un modèle de classe. Pile.h
template #define DEBUG class Pile // définit une pile d’entiers (LIFO) { T t[n]; // t[0] à t[p] sont les cases occupées int s; // -1 ≤ s ≤ n-1 indice de la dernière case occupée public : Pile(); // crée une pile vide boo Estvide()const; void Inserer(const T &a); // insère l’élément x en tête, modifie la pile T Retirer(); // retire l’élément de tête et le renvoie, modifie laa pile Pile Insertion(const T &a) const; // insertion en tête, ne modife pas la pile argument implicite }; Fichier pile.cpp
template Pile :: Pile() { s = -1; } template void Pile :: Inserer(const T &a) { #ifndef DEBUG if (s = n – 1) Erreur(’’Pile :: Inserer(a) : pile pleine’’); #endif t[++s] = a; // incrémente d’abord s et utilise la valeur de s //équivalent à ++s; t[s] = a; } template bool Pile :: Estvide() const { return (s == -1); // teste la valeur de s égale à -1 } template T Pile :: Retirer() { #ifndef DEBUG If (s == -1) Erreur(’’Pile:: Retirer(): pile vide’’); #endif return t[s--]; // renvoie t[s]et décrémente ensuite } template Pile Pile :: Insertion(const T &a) const { Pile r(*this); // copie physique r.Inserer(a); return r; }
Conception et programmation orientée objet avec C++
4 5
LES EXCEPTIONS
L E S E X C E PT I O N S
Conception et programmation orientée objet avec C++
P R O J E T AN N U E L
Conception et programmation orientée objet avec C++
4 7
PROJET ANNUEL DE PROGRAMMATION ORIENTEE OBJET EN LANGAGE C++
P R O J E T A N U E L D E P R O G R A M M A T I O N A N O R I E N T E E O B J E T E N L A N G A G E C ++ E 1.
ORGANISATION DU TRAVAIL
Les auditeurs peuvent se grouper par binômes pour réaliser le travail. Un binôme sera le même pour les t rois problèmes.
2. TRAVAIL DEMANDE Réaliser en C++, toutes les classes impliquées dans la résolution des problèmes demandés. Date de remise : au plus tard le lundi 13 juin 2005.
3.
MODALITES DE LA SOUTENANCE
Les soutenances seront organisées dans la semaine qui suit la semaine de remise des projets. Elles auront lieu dans mon bureau (E 113. 1er étage à l’UFR MIM) ou en salle de TP à l’IUT. Une soutenance dure environ 40 minutes. Si vous avez réalisé le projet sur un ordinateur portable, celui-ci sera bienvenu. La note de projet tiendra compte du bon fonctionnement des programmes demandés et de la qualité de la programmation (la qualité de l’analyse et la rigueur de programmation seront examinées).
4.
SUJET 4.1. PROBLEME 1. GESTION DE FICHIERS GENERIQUES
Le but est de mettre en œuvre une classe fichier de X où X peut être n’importe quel t ype ou n’importe quelle classe. Par l’intermédiaire de cette classe il doit donc être possible de sauvegarder sur disque n’importe quelle suite finie (liste, tableau, etc) de données quelque soit leur type. Vous ne pouvez donc faire aucune hypothèse sur X. La nature de X sera précisée dans le programme principal. Bien entendu, par l’i ntermédiaire de cette même classe, il doit être possible de restaurer les données sauvegardées sans perte d’information. Vous pouvez supposer que la classe (ou le type) X fournit les deux fonctions non membres suivantes : char * ConversionChaine(const X& x); void ChaineConversion(char * c, X& x);
//Donnée : c, résultat : x
La première fonction permet de convertir une instance de la classe X au format chaîne de caractère. La seconde permet de réaliser la conversion réciproque et donc de restaurer intacte toute donnée x traduite en chaîne par la première.
4.1.1. C AHIER DES CHARGES DE LA CLASSE FICHIER DE X La classe fichier de X doit permettre les opérations suivantes : • Ouverture en lecture uniquement a partir d’un fichier physique disque. • Ouverture en écriture uniquement vers un fichier physique disque. • Opérateur d’écriture << en fin de fichier d’un nouvel élément sur le fichier. Uniquement disponible si le fichier a été ouvert en écriture. • Opérateur de lecture >> du prochain élément sur le fichier. Uniquement disponible si le fichier a été ouvert en lecture. • Opérateur [] d’accès direct en lecture a l’élément n° i. • Fonction indiquant si le fichier est vide. • Fonction indiquant la taille du fichier (unité : le nombre d’éléments). N’oubliez pas, en outre, de munir cette classe de toute opération nécessaire à son bon fonctionnement.
4.1.2. GESTION DES ERREURS Les erreurs d’entrée-sortie ou autres doivent toutes être gérées : le programmeur utilisant la classe de fichiers de X doit pouvoir le faire de façon sûre. Le minimum que doivent donc faire vos fonctions, en matière de gestion d’erreur, est de signaler au programme appelant le problème rencontré si elles ne peuvent pas traiter elles même ce dernier. Ce cahier des charges n’est pas exhaustif, vous pouvez l’étendre, il en sera tenu compte le cas échéant.
4.2. PROBLEME 2 : GESTION D’ENSEMBLES GENERIQUES Le but est de fournir au programmeur une classe générique d’ensemble mathématique munie des opérations classiques de l’algèbre de Boole. Notons cette classe Ensemble de X. Cette classe permet de représenter des ensembles
Conception et programmation orientée objet avec C++
4 8
PROJET ANNUEL DE PROGRAMMATION ORIENTEE OBJET EN LANGAGE C++
d’instances de X où X peut être n’importe quel type ou n’importe quelle classe. La nature de X sera précisée dans le programme principal. Rappel : propriété d’un ensemble : Un ensemble ne contient pas de doublon; c’est à dire qu’un élément n’est jamais présent en plus d’un exemplaire.
4.2.1. C AHIER DES CHARGES DE LA CLASSE ENSEMBLE DE X : La classe Ensemble de X doit permettre de réaliser les opérations suivantes : 1) 2) 3) 4) 5) 6) 7) 8) 9) 10) 11) 12) 13) 14)
Création à vide. Insertion d’un nouvel élément (sans créer de doublons). Calcul de la taille. Indiquer si une valeur quelconque appartient à l’ensemble. Retrait d’un élément (le premier par exemple). Accès en lecture à l’élément n° i (à l’aide de l’opérateur []). Opération réunion ensembliste (opérateur + pour réaliser des instructions de la forme e1 = e2+e3;). Opération intersection ensembliste (opérateur *). Opérateur +=. Opérateur d’affichage <<. Indiquer si un ensemble est inclus (au sens large) dans un autre (opérateur <=). Indiquer si deux ensembles sont égaux (opérateur ==). Rappel : (e1 == e2)⇔ ((e1⊂ e2) et (e2 ⊂ e1)). Opérateur de différence ensembliste (opérateur -). Construire l’ensemble des sous ensembles d’un ensemble.
Les opérations 7 à 14 doivent être non membres. N’oubliez pas de munir la classe de toute opération nécessaire à son bon fonctionnement.
4.3. PROBLEME 3. DERIVATION FORMELLE Le but est de représenter en machine une expression arithmétique représentant une fonction mathématique et de procéder sur cette fonction à quelques opérations. La fonction est une fonction d’une variable réelle et à valeurs réelles. Elle peut contenir les opérateurs + - * / et ^ (exposant) et les fonctions sin, cos et ln et l’opérateur – unaire. Les opérandes peuvent être soit des constantes numériques, soit la variable (unique).
Les opérations souhaitées sur une telle expression sont :
• • • • • •
Evaluation pour une valeur donnée à la variable. Obtention de l’expression de la dérivée formelle. Simplification de l’expression (opération essentielle pour la dérivation). Affichage sur la sortie standard. Réalisation d’un duplicata. Toute opération nécessaire au bon fonctionnement d’une expression.
Conception et programmation orientée objet avec C++
AN N E X E S
Conception et programmation orientée objet avec C++
5 0
PROGRAMMES
P R O G R A M M E S Note de l’auteur : les différents programmes ont été testés et validés en utilisant l’IDE Dev-C++ 5.0 beta 9.2 (4.9.9.2) avec Mingw/GCC 3.4.2 que vous trouverez sur http://www.bloodshed.net/dev/devcpp.html , voir également http://www.bloodshed.net/.
1.
ENTIERS.H
#ifndef ENTIERSH #define ENTIERSH /*definit le type naturel a partir d'entiers non signes*/ typedef unsigned int Naturel; Naturel Pgcd1(const Naturel &a, const Naturel &b); /*calcule le pgcd de a et b, hypothèse a >= 1 b >= 1*/ Naturel Pgcd(const int &a, const int &b); /*on suppose (a,b) <> (0,0)*/ #endif
2.
ENTIERS.CPP
#include #include "entiers.h" Naturel Pgcd1(const Naturel &a, const Naturel &b) /*calcule le pgcd de a et b, hypothèse a >= 1 b >= 1, Version récursive */ { if (a == b) return a; // cas trivial else if (a > b) return Pgcd1(a - b,b); // cas général else /*a < b*/ return Pgcd1(a,b - a); // avec un sous problème } Naturel Pgcd(const int &a, const int &b) /*on suppose (a,b) <> (0,0)*/ { if (a == 0) return abs(b); else /*a <> 0*/ { if (b == 0) return abs(a); else /* a <> 0 et b <> 0*/ return Pgcd1(abs(a),abs(b)); } }
3.
ERREURS.H
#ifndef ERREURSH #define ERREURSH /*definition de la fonction d'erreur*/ /*envoie le message sur le fichier d'erreur standard puis arrete le programme*/ void Erreur(char * message); #endif
4.
ERREURS.CPP
#include #include "erreurs.h" using namespace std; /*definition de la fonction d'erreur*/ /*envoie le message sur le fichier d'erreur standard puis arrete le programme*/ void Erreur(char * message) { cerr << "\n\n Erreur!" << message << endl;
Conception et programmation orientée objet avec C++
5 1
PROGRAMMES
exit(0); }
5.
FRACTIONS.H
#ifndef FRACTIONSH #define FRACTIONSH using namespace std; //------------------------- classe Fraction --------------------------------------class Fraction { /*par défaut privé*/ int num; // numérateur Naturel den; // dénominateur >= 1 public: /*déclaration de ce qui est public jusqu'à nouvel ordre*/ /*------------Fonctions membres---------------------------*/ Fraction(); // constructeur crée 0 / 1 Fraction(const int &a); // constructeur crée a / 1 Fraction(const int &a, const Naturel &b); // constructeur crée a/b on suppose b <> 0 Fraction Somme(const Fraction &a) const; /*additionne la fraction passee en reference a la fraction implicite*/ //le dernier const signifie que la fonction membre ne modifie pas l'argument implicite Fraction Produit(const Fraction &a) const; /*fait le produit de la fraction passee en reference a la fraction implicite*/ Fraction Oppose() const; // renvoie l'opposé de l'argument implicite Fraction Inverse() const; // renvoie l'inverse de l'argument implicite Fraction Simplifie() const; /*simplifie une fraction*/ bool Est_egal(const Fraction &a) const; /*teste l'egalite de la fraction passee en reference avec la fraction implicite*/ void Ecrire(ostream &os) const; // affiche sur la sortie standard la fraction implicite Fraction operator +(const Fraction &a) const; /*surcharge d'opérateur additionne la fraction passee en reference a la fraction implicite*/ Fraction operator -() const; /*surcharge d'opérateur renvoie la fraction opposee de la fraction implicite*/ Fraction operator *(const Fraction &a) const; /*surcharge d'opérateur fait le produit de la fraction passee en reference a la fraction implicite*/ bool operator ==(const Fraction &a) const; /*surcharge d'opérateur teste l'egalite de la fraction passee en reference avec la fraction implicite*/ bool operator <(const Fraction &a) const; /*surcharge d'opérateur teste l'inferiorite de la fraction passee en reference a la fraction implicite*/ const Fraction & operator ++(); /*surcharge d'operateur incrémentation la fonction est constante pour empecher par exemple ++x1 = x2*/ Fraction operator +=(const Fraction &a); /*surcharge d'operateur effectue a = a+b*/ }; // ne pas oublier le; /*-----------------Declaration des fonctions ordinaires---------------------*/ Fraction Difference(const Fraction &a, const Fraction &b); /*fait la difference de deux fractions passees en parametre*/ Fraction Quotient(const Fraction &a, const Fraction &b); /*fait la division de deux fractions passees en parametre*/ Fraction Lire(istream &is); /*saisie d'une fraction depuis l'entrée standard*/ ostream &operator <<(ostream &os, const Fraction &a);
Conception et programmation orientée objet avec C++
5 2
PROGRAMMES
/*surcharge d'opérateur écrit sur le flux de sortie la fraction*/ istream &operator >>(istream &is, Fraction &a); /*surcharge d'opérateur saisie sur le flux d'entrée la fraction*/ Fraction operator -(const Fraction &a, const Fraction &b); /*surcharge d'opérateur fait la difference de deux fractions passees en parametre*/ Fraction operator /(const Fraction &a, const Fraction &b); /*surcharge d'opérateur fait la division de deux fractions passees en parametre*/ bool operator !=(const Fraction &a, const Fraction &b); /*surcharge d'opérateur teste la non egalite de deux fractions passees en parametre*/ bool operator >(const Fraction &a, const Fraction &b); /*surcharge d'opérateur teste la superiorite de deux fractions passees en parametre*/ bool operator <=(const Fraction &a, const Fraction &b); /*teste l'inferiorite ou egalite de deux fractions passees en parametre*/ bool operator >=(const Fraction &a, const Fraction &b); /*teste la superiorite ou egalite de deux fractions passees en parametre*/ #endif
6.
FRACTIONS.CPP
#include #include #include #include
"entiers.h" "erreurs.h" "fractions.h"
//--------------- implementation de la classe Fraction -----------------------/*referencement des fonctions membres a sa classe d'origine*/ /*les constructeurs portent le même nom que la classe*/ Fraction :: Fraction() // constructeur crée 0 / 1 { num = 0 ; den = 1 ; } Fraction :: Fraction(const int &a) // constructeur crée a / 1 { num = a ; den = 1 ; } Fraction :: Fraction(const int &a, const Naturel &b) /* constructeur crée a/b on suppose b <> 0*/ { if (b == 0) Erreur("Fraction :: Fraction(a,b) : b == 0") ; if (b > 0) { num =a; den = b; } else /*b < 0*/ { num = - a; den = (Naturel)(-b); } } Fraction Fraction :: Somme(const Fraction &a) const /*additionne la fraction passee en reference a la fraction implicite*/ { return Fraction(num * a.den + den * a.num, den * a.den).Simplifie(); } Fraction Fraction :: Produit(const Fraction &a) const /*fait le produit de la fraction passee en reference a la fraction implicite*/ { return Fraction(num * a.num, den * a.den).Simplifie(); } Fraction Fraction :: Oppose() const // renvoie l'opposé de l'argument implicite
Conception et programmation orientée objet avec C++
5 3
PROGRAMMES
//renvoie -(*this) { return Fraction(-num, den) ; } Fraction Fraction :: Inverse() const // renvoie l'inverse de l'argument implicite { return Fraction(den,num); } Fraction Fraction :: Simplifie() const /*simplifie une fraction*/ { Naturel p ; p = Pgcd(num,den) ; return Fraction(num / p,den / p); } bool Fraction :: Est_egal(const Fraction &a) const /*teste l'egalite de la fraction passee en reference avec la fraction implicite*/ { return num * a.den == den * a.num ; } void Fraction :: Ecrire(ostream &os) const // affiche sur la sortie standard la fraction implicite { os << num << "/" << den; } Fraction Fraction :: operator +(const Fraction &a) const /*surcharge d'opérateur additionne la fraction passee en reference implicite*/ { return Fraction(num * a.den + den * a.num, den * a.den).Simplifie(); }
a
la
fraction
Fraction Fraction :: operator -() const /*surcharge d'opérateur renvoie la fraction opposee de la fraction implicite*/ { return Fraction(-num, den); } Fraction Fraction :: operator *(const Fraction &a) const /*surcharge d'opérateur fait le produit de la fraction passee en reference a la fraction implicite*/ { return Fraction(num * a.num, den * a.den).Simplifie(); } bool Fraction :: operator ==(const Fraction &a) const /*surcharge d'opérateur teste l'egalite de la fraction passee en reference avec la fraction implicite*/ { return (num * a.den == den * a.num); } bool Fraction :: operator <(const Fraction &a) const /*surcharge d'opérateur teste l'inferiorite de la fraction passee en reference a la fraction implicite*/ { return num * a.den < den * a.num; } const Fraction &Fraction :: operator ++() /*surcharge d'operateur incrémentation la fonction est const pour empecher par exemple ++x1 = x2*/ { num += den;
Conception et programmation orientée objet avec C++
5 4
PROGRAMMES
return *this; /*il faut renvoyer l'argument implicite sinon impossible de faire par exemple x2 = ++x1; car ++x1 ne renverrait rien */ } Fraction Fraction :: operator +=(const Fraction &a) /*surcharge d'operateur effectue a=a+b*/ { num = num * a.den + den * a.num; den *= a.den; (*this) = (*this).Simplifie(); return *this; // sinon x3 = x2 += x1; impossible } //---------------------------Implementation des fonctions ordinaires----------------Fraction Difference(const Fraction &a, const Fraction &b) /*fait la difference de deux fractions passees en parametre*/ { return a.Somme(b.Oppose()); } Fraction Quotient(const Fraction &a, const Fraction &b) /*fait la division de deux fractions passees en parametre*/ { return a.Produit(b.Inverse()); } Fraction Lire(istream &is) /*saisie d'une fraction depuis l'entrée standard*/ { int a,b; is >> a >> b ; return Fraction(a,b) ; } ostream &operator <<(ostream &os, const Fraction &a) /*surcharge d'opérateur écrit sur le flux de sortie la fraction*/ { a.Ecrire(os); return os; } istream &operator >>(istream &is, Fraction &a) /*surcharge d'opérateur saisie sur le flux d'entrée la fraction*/ { a = Lire(is); return is; } Fraction operator -(const Fraction &a, const Fraction &b) /*surcharge d'opérateur fait la difference de deux fractions passees en parametre*/ { return a + (-b); } Fraction operator /(const Fraction &a, const Fraction &b) /*surcharge d'opérateur fait la division de deux fractions passees en parametre*/ { return a * (b.Inverse()); } bool operator !=(const Fraction &a, const Fraction &b) /*surcharge d'opérateur teste la non egalite de deux fractions passees en parametre*/ { return !(a == b); } bool operator >(const Fraction &a, const Fraction &b) /*surcharge d'opérateur teste la superiorite de deux fractions passees en parametre*/ {
Conception et programmation orientée objet avec C++
5 5
PROGRAMMES
return b < a; } bool operator <=(const Fraction &a, const Fraction &b) /*teste l'inferiorite ou egalite de deux fractions passees en parametre*/ { return !(a > b); } bool operator >=(const Fraction &a, const Fraction &b) /*teste la superiorite ou egalite de deux fractions passees en parametre*/ { return b <= a; }
7.
SYSTEMES.H
#ifndef SYSTEMESH #define SYSTEMESH Fraction Det(const Fraction Fraction &a22); /*calcule le déterminant |a11 a12| |a21 a22|*/
&a11,
const
Fraction
&a12,
const
Fraction
&a21,
const
bool ResoutSystem2x2(const Fraction &a1, const Fraction &b1, const Fraction &a2, const Fraction &b2, const Fraction &c1, const Fraction &c2, Fraction &x, Fraction &y); /* données : a1, a2, b1, b2, c1, c2 Résultat : x, y et un booléen Taches : résout a1x + b1y = c1, a2x + b2y = c2 Convention : si renvoie vrai x et y ont été calculé Si renvoie faux x et y sont indéterminé*/ #endif
8.
SYSTEMES.CPP
#include #include #include #include
"entiers.h" "fractions.h" "systemes.h"
Fraction Det(const Fraction &a11, const Fraction &a12,const Fraction &a21, const Fraction &a22) /*calcule le déterminant |a11 a12| |a21 a22|*/ { return Difference(a11.Produit(a22), a21.Produit(a12)); } bool ResoutSystem2x2(const Fraction &a1, const Fraction &b1, const Fraction &a2, const Fraction &b2, const Fraction &c1, const Fraction &c2, Fraction &x, Fraction &y) /* données : a1, a2, b1, b2, c1, c2 Résultat : x, y et un booléen Taches : résout a1x + b1y = c1, a2x + b2y = c2 Convention : si renvoie vrai x et y ont été calculé Si renvoie faux x et y sont indéterminé*/ { Fraction d = Det(a1,b1,a2,b2), zero; if (d.Est_egal(zero)) return false; // pas d'arrondi car ce sont des entiers else { x = Quotient(Det(c1,b1,c2,b2),d); y = Quotient(Det(a1,c1,a2,c2),d); return true; Conception et programmation orientée objet avec C++
5 6
PROGRAMMES
} }
9.
CHAINES.H
#ifndef CHAINESH #define CHAINESH #define DEBUG using namespace std; //------------------------- classe Chaine -------------------------------------class Chaine { char *t ; // pour l'instant il n'y a pas de tableau (pas de caractères), le tableau sera alloué en cours d'exécution void Alloue(const Naturel &n) ; // alloue n caractères public : Chaine () ; /* constructeur sans parametres cree un pointeur sur chaines de caracteres vide */ Chaine (const char *s) ; /* constructeur avec un pointeur sur chaine de caractere */ Chaine (const char &c) ; /*constructeur avec une variable de type chaine de caractere*/ Chaine (const Chaine &ch) ; // constructeur de copie ~ Chaine() ; // ~ est le symbole du destructeur Chaine (const Naturel &n, const char &c); // crée une chaîne contenant n fois le caractère c Naturel Longueur() const ; Chaine operator +(const Chaine & ch) const ; /*surcharge d'opérateur concatène la chaine passée en référence à la chaine implicite*/ const Chaine &operator =(const Chaine &ch) ; /*surcharge d'opérateur affecte à la chaine implicite la chaine passée en référence le résultat est passé par référence*/ char operator [](const Naturel &i) const ; /*renvoie une valeur, sert à consulter, 0 <= i <= (*this).Longueur() -1 */ char &operator [](const Naturel &i); // renvoie un objet, sert à modifier bool operator <=(const Chaine &ch) const ; void Ecrire(ostream &os) const; // affiche sur la sortie standard la chaine implicite } ; ostream &operator <<(ostream &os,const Chaine &ch); /*surcharge d'opérateur écrit sur le flux de sortie la chaine*/ istream &operator >>(istream &is, Chaine &ch); /*surcharge d'opérateur saisie sur le flux d'entrée la chaine*/ #endif
10. CHAINES.CPP #include #include #include #include
"erreurs.h" "entiers.h" "chaines.h"
//--------------- implementation de la classe Fraction ------------------------/*referencement des fonctions membres a sa classe d'origine*/ /*corps des fonctions*/ void Chaine :: Alloue(const Naturel &n) { t = new char[n] ; if (t == NULL) Erreur("Chaine:: Alloue(n): mémoire saturée") ; Conception et programmation orientée objet avec C++
5 7
PROGRAMMES
} Chaine :: Chaine () /*constructeur sans parametres crée un pointeur sur chaines de caracteres vide*/ { Alloue(1) ; t[0] = '\0' ; } Chaine :: Chaine(const char *s) /*constructeur avec un pointeur sur chaine de caractere*/ { Alloue(1 + strlen(s)); strcpy(t, s); } Chaine :: Chaine (const char &c) /*constructeur avec une variable de type chaine de caractere*/ { Alloue(2) ; *t = c ; // équivaut à t[0] = c ; *(t + 1) = '\0' ; // équivaut à t[1] = '\0' ; } Chaine :: Chaine(const Chaine &ch) /* constructeur de copie */ { Alloue(1 + strlen(ch.t)); strcpy(t, ch.t); cout << "Appel au constructeur de copie" ; } Chaine :: ~ Chaine() // ~ est le symbole du destructeur { delete []t ; // cout <<''appel au destructeur'' ; } Chaine :: Chaine (const Naturel &n, const char &c) // crée une chaîne contenant n fois le caractère c { } Naturel Chaine :: Longueur() const { return (Naturel) strlen(t); } Chaine Chaine :: operator +(const Chaine & ch) const /*surcharge d'opérateur concatene la chaine passée en référence à la chaine implicite*/ { Chaine r(strlen(t) + strlen(ch.t),' ') ; strcpy(r.t, t) ; strcat(r.t, ch.t) ; return r; } const Chaine &Chaine :: operator =(const Chaine &ch) /*surcharge d'opérateur affecte à la chaine implicite la chaine passée en référence le résultat est passé par référence*/ { if (this != &ch) // evite ch = ch { delete []t; Alloue(1 + strlen(ch.t)); // t = new char[1 + strlen(ch.t)]; strcpy(t, ch.t); } return *this; }
Conception et programmation orientée objet avec C++
5 8
PROGRAMMES
char Chaine :: operator [](const Naturel &i) const /*surcharge d'opérateur renvoie une valeur, sert à consulter, 0 <= i (*this).Longueur() -1 */ { #ifdef DEBUG if (i >= strlen(t)) Erreur("Chaine :: operator[](i) : l'élément i n'est défini"); #endif return t[i]; } char &Chaine :: operator [](const Naturel &i) /*surcharge d'opérateur renvoie un objet, sert à modifier */ { #ifdef DEBUG if (i >= strlen(t)) Erreur("Chaine :: operator[](i) : défini"); #endif return t[i]; }
l'élément
i
n'est
bool Chaine :: operator <=(const Chaine &ch) const /*surcharge d'opérateur*/ { return strcmp(t, ch.t) <= 0; } void Chaine :: Ecrire(ostream &os) const // affiche sur la sortie standard la chaine implicite { os << t; } //---------------------------Implementation des fonctions ordinaires-----------ostream &operator <<(ostream &os,const Chaine &ch) /*surcharge d'opérateur écrit sur le flux de sortie la chaine*/ { ch.Ecrire(os); return os; } istream &operator >>(istream &is, Chaine &ch) /*surcharge d'opérateur saisie sur le flux d'entrée la chaine*/ { static char s[1024]; is.getline(s, 1023, ' '); ch = Chaine(s); return is; }
Conception et programmation orientée objet avec C++
<=
pas
pas
5 9
LES LIBRAIRIES
L E S L I B R A I R I E S Source : http://www.tcom.ch/Tcom/Cours/C++/C++.html. Voir également : http://www.opengroup.org/onlinepubs/007908799/idx/index.html.
1.
L’INTERET DES LIBRAIRIES
La librairie standard de UNIX est actuellement encore largement utilisée. Mais il ne faut pas oublier qu'elle a été prévue pour une utilisation dans un cadre de programmation système. Elle recèle nombre de fonctions de très bas niveau, ce qui, incidemment, en fait un outil très performant dans le cadre de la programmation système ou la programmation de microcontrôleurs. Ainsi des routines couramment utilisées sous UNIX, comme signal(), setjmp(), longjmp(), provoquent une rupture du déroulement normal du programme qui peut poser de sérieux problèmes de récupération de mémoire, en particulier pour des objets crées automatiquement, de manière pas toujours transparente par le compilateur. En principe, lorsqu'un groupe de travail se met à développer en C, il se met sur pied une librairie de procédures qui va croître au cours du temps et au cours des projets. Cette démarche nécessite une organisation, des règles à respecter absolument. Ces règles sont fonction de la dimension du groupe de travail. Pour de petits groupes largement autonomes (10 personnes), on peut se contenter d'encourager les gens à parler entre eux. Si cette communication ne va pas de soi, souvent en raison de problèmes d’incompatibilité entre les divers développeurs, on peut "organiser" cet échange de vues, sans que cela soit forcément une directive émise par un chef de groupe donné. En fait, une directive fonctionne généralement mieux lorsqu'elle est spontanément appliquée. Une bonne méthode (et de surcroît facile à rendre populaire dans nos contrées) est l'organisation fréquente de verrées ou chacun parle de manière libre de ce qu'il fait. Pour des groupes de développement plus importants, ou des projets regroupant plusieurs groupes (parfois distribués géographiquement), il n'est plus possible de se contenter d'une communication orale, et il faut recourir à des documents pour échanger les informations concernant les classes produites. La production de documents étant une entreprise longue et fastidieuse, on peut raisonnablement craindre que l'échange d'informations ne se fasse pas aussi bien que l'on pourrait le souhaiter. C'est pourquoi là aussi, il est important de trouver des moyens permettant de favoriser des communications informelles entre les groupes de développement. Ces communications ont de tous temps été précieuses, elles le sont d'autant plus lorsque l'on dispose d'un outil potentiellement efficace pour échanger du code. La création de modules de librairies, dans ce contexte, doit faire l'objet de soins particuliers par les auteurs de code. Exporter des classes boguées n'apporte que des ennuis, tant à l'auteur qu'aux utilisateurs. Dans le meilleur des cas, on n'utilisera plus les classes développées par un auteur particulier, parce que trop souvent boguées. Dans le pire des cas, l'exportation d'une erreur dans plusieurs projets n'ayant en commun que la réutilisation d'un module particulier peut causer de graves problèmes financiers à une société. Lors du développement d'une classe, il est prudent de partir du principe que l'utilisateur est un ennemi potentiel, contre lequel il convient de se protéger. Si il existe une condition qui peut entraîner une erreur dans le code d'une classe, il est plus que vraisemblable qu'un utilisateur de cette classe déclenchera un jour ou l'autre cette condition. Une classe que l'on désire exporter doit donc impérativement être à l'épreuve de toute manipulation dont on sait qu'elle est dangereuse. Il vaut mieux avorter brutalement l'exécution d'un programme avec un message d'erreur sur la console que de laisser se poursuivre une exécution dans une situation d'erreur.
2.
LIBRAIRIE C STANDARD
La librairie C standard est certainement la plus utilisée des librairies C++, bien qu’elle ne soit pas “orientée objets”. Le principal avantage de cette librairie est qu’elle est en partie normalisée par l’ANSI, et de ce fait se retrouve sur toutes les implémentations ANSI du langage. La liste présentée dans le cadre de ce chapitre ne prétend pas être exhaustive, mais présente les principales fonctions de la librairie C telle qu’on la retrouve sur quasiment toutes les implémentations de C et de C++ (à l’exception de certains compilateurs C de bas niveau, utilisés pour la programmation de chips spécialisés, comme des microcontrôleurs et des processeurs de signaux, ou DSP). Il est important de se souvenir que cette librairie a été conçue indépendamment de C++. Elle peut donc présenter de sérieuses incompatibilités avec certaines constructions plus modernes de C++. En pratique, ces incompatibilités ne sont pas gênantes, dans la mesure où l’on ne mélange pas les fonctions de la librairie C avec les fonctions correspondantes d’une libraire C++, ou pire, avec des instructions réservées de C++. Ainsi, le code suivant produira à coup sûr de sérieuses difficultés à l’utilisation :
const int nbPoint = 200; struct Point { int x, y; }; Point *pptr; pptr = calloc(nbPoint, sizeof(Point)); ... Conception et programmation orientée objet avec C++
6 0
LES LIBRAIRIES
delete pptr; // suicidaire !
2.1.
GENERALITES (#INCLUDE )
void va_start(va_list varList, parameter); type va_arg(va_list varList, type); void va_end(va_list varList); Ces macros permettent de définir des fonctions avec des listes d’arguments de longueur variable. Elles sont spécialement utilisées pour des fonctions telles que printf() et scanf() entre autres, mais il est également possible de définir des fonctions utilisant un nombre variable d’arguments à des fins personnelles. La fonction suivante effectue la moyenne d’un nombre arbitraire de valeurs réelles positives:
#include double mean(float firstArg, ...) { va_list argList; double nextArg; double mean; int count; // Indiquer quel est le premier paramètre de la liste, // et initialiser les macros : va_start(argList, firstArg); mean = firstArg; next = 0; // Une valeur négative sert de terminateur de la liste while ((next = va_arg(argList, double)) >= 0) { mean += next; count++; } // Ne pas oublier va_end !!! va_end(argList); return (mean / count); } void main() { printf(“Resultat = %f\n“, mean(1.5, 2.3, 4.2, 5, 6, 2.1)); } Ces macros sont implémentées dans pratiquement toutes les implémentations de C ( en tout cas, toutes les implémentations compatibles avec le standard ANSI). Par l’utilisation de classes en C++, il existe toutefois des méthodes autrement puissantes - et autrement plus élégantes- de résoudre ce genre de problèmes, si bien que nous n’insisterons pas plus longtemps sur cette possibilité.
2.2.
STDIO (#INCLUDE )
On connaît déjà stdio par le biais de printf() et scanf(). En principe, on conseille plutôt l’utilisation de iostream pour l’accès à des fichiers en C++. En pratique, stdio reste largement utilisé, souvent pour les entrées-sorties sur terminal. De plus, l’utilisation de stdio par diverses librairies existantes est intensive, si bien qu’il vaut la peine de s’intéresser quand même à cette librairie. Dans le cadre de stdio, les conventions suivantes sont utilisées pour définir le modes d’accès aux fichiers : • “r” : (Read) Le fichier est utilisable en lecture seulement. Pour l’ouvrir, le fichier doit exister au préalable. • “w” : (Write) Fichier utilisable en écriture seulement. Ouvrir un fichier existant en mode. • “w” surécrit le contenu précédent de ce fichier. Si le fichier n’existait pas lors de l’ouverture, il sera crée. • “a” : (Append) Ouverture du fichier en mode ajout. Les données sont appondues à la fin du fichier, mais ce dernier doit exister au moment de l’ouverture. • “r+” : (Read and Update) Ouverture d’un fichier existant en mode lecture / écriture. • “w+”, “rw” : (Read / write) Ouverture d’un fichier en mode lecture / écriture. Si ce fichier n’existe pas, alors il est crée. • “a+” : (Append / Create) Comme “a”, mais si le fichier n’existe pas, alors il est crée. La lettre “b”, ajoutée à une autre indication (comme par exemple “wb+”) indique qu’il s’agit d’un fichier binaire. Utilisée dans le cadre de systèmes comme MVS, VMS, etc.., cette indication est rarement utilisée dans le cadre des systèmes de gestion de fichiers modernes. Toutes les routines retournent un entier indiquant le résultat -couronné de succès ou non- de l’appel à la fonction concernée. On peut choisir de négliger ce résultat ou non. En règle générale, on testera le résultat de l’ouverture de fichier, et le programmeur choisira fréquemment de négliger les résultats livrés par les autres opérations. Cette manière de faire est courante dans tous les langages, et dangereuse dans tous les langages. Dans le
Conception et programmation orientée objet avec C++
6 1
LES LIBRAIRIES
cadre des petits exemples donnés avec les procédures, nous négligerons fréquemment de tester le résultat de l’opération, ceci afin de ne pas surcharger l’exemple. En cas d’erreur, la cause de l’erreur peut être déduite de la variable errno. Les valeurs possibles de errno sont explicitées dans le fichier errno.h.
FILE *fopen(const char* fileName,const char* mode); Ouverture d’un fichier du nom de fileName en mode mode. Le résultat de l’appel est un pointeur sur une variable d’un type en principe opaque, qui sera passé ultérieurement aux autres fonctions de manipulation de fichiers. Si l’ouverture échoue, c’est une valeur de pointeur NULL qui sera retournée.
FILE* fichier; fichier = fopen(“porno.gif”, “r”); if (fichier != NULL) { ... // Considérations anatomiques ? ... } FILE *freopen(const char* fileName, const char* mode, FILE* stream); Ferme le fichier stream et ouvre en lieu et place le fichier nommé par fileName dans le mode mode. En cas d’échec de l’ouverture, un pointeur NULL est retourné, mais l’état d u fichier ouvert précedemment est indeterminé (généralement, il a été fermé). La valeur de la variable errno peut donner une indication quant à ce qu’il s’est effectivement passé.
FILE* fichier; fichier = freopen(“anOtherStdout”, “w”, stdout); // redirection de stdout sur un fichier... if (fichier == NULL) { printf(“Redirection de stdout manquée pour cause de %d\n”, strerror(errno)); exit(1); } // else continuer allègrement le travail... int fclose(FILE* stream); Fermeture du fichier associé au pointeur stream. Rappelons qu’en C, un fichier n’est pas fermé implicitement lors de la destruction du pointeur fichier associé. Retourne 0 si succès, EOF dans le cas contraire. fclose() provoque un appel implicite à fflush() pour vider le tampon associé à stream.
FILE* f = fopen(“tmp.tmp”, “w”); fprintf(f, “Contenu de fichier bidon\n”); fclose(f); int fflush(FILE *stream) Provoque l’écriture forcée du tampon associé au fichier stream. En cas de succès, retourne 0, EOF en cas d’échec. Si stream vaut NULL, tous les fichiers ouverts en écriture seront vidés.
printf(“Ecriture dans le tampon...”); if ((err = fflush(stdout))) { printf(“fflush n’a pas fonctionné pour cause %d\n”, errno); exit(0); } // Provoque l’écriture forcée, malgré l’absence de \n int remove(const char* name); Destruction du fichier portant le nom name. Retourne 0 en cas de succès. Si le fichier portant le nom name était ouvert au moment de l’appel de remove(), le résultat dépend de l’implémentation (en particulier du système d’exploitation), mais généralement, cette situation est signalée comme une erreur.
int rename(const char* oldName,const char* newName); Permet de renommer le fichier appelé oldName en newName.Cette opération échoue si le fichier désigné par newName existe déjà (pas vrai pour toutes les implémentations). On trouve parfois également rename(const char* oldName,
Conception et programmation orientée objet avec C++
6 2
LES LIBRAIRIES
int ref, const char* newName) comme définition. ref indique alors le volume sur lequel doit ê tre effectuée cette opération.
FILE* f = fopen(“tmp.tmp”, “w”); fprintf(f, “Contenu de fichier bidon\n”); fclose(f); rename(“tmp.tmp”, “bidon.txt”); f = fopen(“bidon.txt”, “r”); char buffer[200]; fscanf(f, “%s”, buffer); printf(“%s”, buffer); fclose(f); remove(“bidon.txt”); int setvbuf(FILE* stream, char *buf, int mode, size_t size); setvbuf() permet de définir comment les entrées-sorties seront mises en tampon, et quelle sera la taille du tampon. Cet appel doit faire suite immédiatement à l’appel de fopen(), donc avant toute autre utilisation du fichier, sans quoi les résultats peuvent devenir imprévisibles. stream pointe sur le descripteur de fichier pour lequel on définit le t ampon,buf, pointe sur un tampon mémoire de taille size, et mode peut prendre l’une des trois valeurs suivantes : • _IOFBF (Full BuFfering) • _IOLBF (Line BuFfering) • _IONBF (No BuFfering) A noter que beaucoup d’implémentations de librairies offrent la possibilité d’effectuer cet appel implicitement, de manière combinée à l’appel à fopen(), si bien que l’ appel explicite n’est plus nécessaire. Cette fonction tend de ce fait à ne plus être utilisée, sauf dans des cas particuliers où l’on désire forcer une dimension de tampon très grande pour des fichiers de très grande taille. Cette fonction retourne 0 en cas de succès. Un appel à cette fonction après une opération sur le fichier (lecture ou écriture) a un résultat indéfini.
void setbuf(FILE* stream, char *buf); L’appel de cette fonction correspond à appeler setvbuf en mode _IONBF, avec size à 0 si buf est NULL. Si buf n’est pas NULL, il doit avoir au préalable été défini à la librairie par un appel dépendant de l’implémentation. La longueur du buffer, en particulier correspondra à la longueur par défaut du tampon fichier (souvent un multiple d’un bloc disque).
FILE* tmpfile(void); Crée un fichier temporaire qui sera automatiquement détruit lors de la fermeture de ce fichier, ou à la fin du programme (dans la mesure où le programme peut se terminer normalement, bien sûr). Le fichier sera ouvert en mode “wb+”.
char* tmpnam(char* name); Génère un nom de fichier correct par rapport au système d’exploitation, et unique dans l’environnement d’exécution. Si name est différent de NULL, le nom généré sera écrit dans la chaîne fournie en paramètre. La longueur minimale de cette chaîne dépend de l’implémentation (en particulier du système d’exploitation considéré). Le nombre maximum de noms différents que peut générer tmpnam() dépend de l’implémentation, mais devrait dans tous les cas être supérieur à 9999. Si name est NULL, il faut se référer à l’implémentation pour savoir comment gérer le pointeur retourné par tmpnam(). Certaines implémentations (la plupart, en fait) utilisent une chaîne de caractères statique qui sera réutilisée à chaque appel de tmpnam(), l’ancien contenu se trouvant dés lors surécrit. Plus rarement, une nouvelle chaîne sera générée en mémoire, par un appel à malloc(); dans ce cas, il faudra que le programme appelant prenne en charge la destruction de cette chaîne par un appel à free().
int printf(const char* controlString, ...); printf écrit une chaîne de caractères formattée sur la sortie standard (stdout). La formattage est défini par la chaîne de caractères controlString, alors que les arguments sont contenus dans une liste de longueur indéfinie. Un descripteur de format est indiqué par le caractère % suivi d’un caractère indiquant la conversion à effectuer. Ainsi, la séquence %d indique qu’il faut interpréter l’argument correspondant (défini par sa position dans controlString et dans la liste d’arguments) comme un entier décimal. En cas de succès, printf retourne le nombre de caractères imprimés sur stdout, sinon, EOF. printf(“La valeur de pi est %7.5f\n”, 3.1415926); imprime sur la sortie standard :
La valeur de pi est 3.14159 Conception et programmation orientée objet avec C++
6 3
LES LIBRAIRIES
Autres références : Voir Descripteurs de formats de printf().
int scanf(const char* controlString, ...); scanf convertit des entrées en provenance de l’entrée standard stdin en une série de valeurs, en se servant de controlString comme modèle de conversion. Les valeurs sont stockées dans les variables sur lesquelles pointe la liste d’arguments indiquée par ... dans le prototype. La chaîne controlString contient des descriptions de format qui indiquent à scanf comment il convient de convertir les entrées de l’utilisateur. Un descripteur de format est indiqué par le caractère % suivi d’un caractère indiquant la conversion à effectuer. Ainsi, la séquence %d indique qu’il faut interpréter les entrées sur stdion comme un entier décimal. scanf retourne le nombre d’arguments auxquels il a été possible d’assigner une valeur. Ce nombre peut être différent du nombre d’arguments passé à scanf ! Si il n’est pas possible de lire sur stdin, EOF sera retourné.
int x; printf(“Entrer une valeur entière: “); scanf(“%d”, &x); printf(“Vous avez entré %d\n”, x); Autres références : Voir Descripteurs de formats de printf().
int fprintf(FILE* f, const char* controlString, ...); Fonctionnalité équivalente à printf(). De fait, printf() peut également s’écrire fprintf(stdout, “Valeur de
pi = %f\n”, 3.1415926); int fscanf(FILE *f, const char* controlString, ...) Fonctionnalité équivalente à scanf(). De fait, scanf() peut également s’écrire :
float unReel; print(“Entrer un nombre reel “); fscanf(stdout, “%f”, unReel); int sprintf(char *buffer, const char* controlString, ...); Fonctionnalité équivalente à fprintf(), mais la sortie se fait dans une chaîne de caractères en lieu et place d’un fichier. L’utilisateur devra veiller lui-même à réserver une place suffisante dans la chaîne référencée par *buffer.
int fgetc(FILE *stream); Lecture du prochain caractère dans le fichier associé au pointeur stream. Le fait que le résultat soit entier (au lieu de char) est dû à la possibilité de livrer EOF (de type int) en cas d’erreur. Pratiquement, du fait de la conversion implicite int <-> char, ceci n’a pas vraiment d’importance. Valeurs de retour : • le prochain caractère si l’opération est couronnée de succès. EOF (négatif, en général) si il y a une erreur. •
char* fgets(char* str, int nbChars, FILE *stream); Lecture d’une chaîne de caractères du fichier associé au pointeur stream dans la zone mémoire pointée par str. str doit pointer sur une zone mémoire de dimension au moins égale à nbChars + 1 (+ 1 en raison du caractère \0 qui termine une chaîne de caractères en C). C’est au programmeur de s’assurer que la place qu’il a reservée est suffisante. La fonction fgets() lit des caractères jusqu’à un caractère de fin de ligne, ou jusqu’à la lecture de nbChars caractères. Valeurs de retour : str si l’opération est couronnée de succès. • • NULL si il y a une erreur.
int fputc(int c, FILE* stream); Ecriture du caractère c (donné sous forme d’entier) dans le fichier associé au pointeur stream. Retourne la valeur entière de c en cas de succès, EOF autrement. On ne peut donc pas sans autre écrire EOF au moyen de fputc().
// Copie d’un fichier dans un autre : FILE* source; FILE* copy; int x;
Conception et programmation orientée objet avec C++
6 4
LES LIBRAIRIES
if ((source = fopen(“source.dat”, “r”)) == NULL) { printf(“Erreur à l’ouverture de source.dat, %s\n”, strerror(errno)); exit(1); } if ((source = fopen(“copy.dat”, “w”)) == NULL) { printf(“Erreur à l’ouverture de copy.dat, %s\n”, strerror(errno)); exit(1); } while ((x = fgetc(source)) != EOF) fputc(x); fclose(source); fclose(copy); int fputs(const char* str, FILE* stream); Ecrit la chaîne de caractères pointée par str dans le fichier pointé par stream. Retourne 0 en cas de succès, EOF en cas d’échec.
int getc(FILE* stream); int putc(int c, FILE* stream); Implémentations sous forme de macros de fgetc() et fputc(). Comportement en principe similaire.
int puts(const char* str); Identique à fputs(), mais écriture sur stdout.
char* gets(char* str); Identique à fgets(), mais lecture à partir de stdin.
int getchar(void) Retourne la valeur du prochain caractère, sous forme d’un entier, lu sur stdin. Retourne EOF en cas d’erreur.
int putchar(int car); Ecrit le caractère car (donné sous forme d’entier) sur stdout. Retourne car en cas de succès, EOF en cas d’erreur.
int ungetc(int c, FILE* stream); Réinsère le caractère c dans le fichier associé au pointeur stream. Retourne c en cas de succès, EOF en cas d’échec.
size_t fread(void* buffer, size_t sizeOfItem, size_t count, FILE* stream); Lecture de count éléments de taille sizeOfItem du fichier associé au pointeur stream dans le tampon buffer. La fonction retourne le nombre d’éléments lus en cas de succès, ou EOF en cas d’erreur.
size_t fwrite(const void *buffer, size_t sizeOfItem, size_t count, FILE* stream); Ecriture de count éléments de taille sizeOfItem du tampon buffer dans le fichier associé au pointeur stream. La fonction retourne le nombre d’éléments écrits en cas de succès, ou EOF en cas d’erreur.
#include typedef struct { int a; char b; } Foo; void main () { FILE *fp; int i; Foo inbuf[3]; Foo outbuf[3] = { { 1, 'a' }, { 2, 'b' }, { 3, 'c' } };
Conception et programmation orientée objet avec C++
6 5
LES LIBRAIRIES
fp = fopen( "MyFooFile", "wb+"); fwrite( outbuf, sizeof(Foo), 3, fp ); rewind( fp ); fread( inbuf, sizeof(Foo), 3, fp ); for ( i=0; i<3; i++ ) printf( "foo[%d] = { %d %c }\n", i, inbuf[i].a, inbuf[i].b ); fclose( fp ); } int fgetpos(FILE* stream, fpos_t* position); Cette fonction livre dans la variable pointée par position la position dans le fichier associé au pointeur stream. Le type fpos_t est un type opaque, qui ne devrait être utilisé qu’en conjonction avec fsetpos(). Cette fonction retourne 0 en cas d’exécution correcte.
int fsetpos(FILE* stream, fpos_t* position); Permet de se repositionner dans le fichier associé au pointeur stream à la position position. position doit impérativement avoir été livré par fgetpos(), sinon le programme peut ne plus être portable. Retourne 0 en cas de succès.
#include void main() { FILE *fp; fpos_t pos; char s[80]; fp = fopen( "MyFile", "w+" ); fgetpos( fp, &pos ); fputs( "Hello world.", fp ); fsetpos( fp, &pos ); fgets( s, 80, fp ); printf( "You read: %s\n", s ); fclose( fp ); } long ftell(FILE* stream); Retourne la position courante dans le fichier associé au pointeur stream. Contrairement à fgetpos, ftell est limité à la taille d’un long pour exprimer la position courante du fichier, et ne convient de ce fait pas aux très grands fichiers. ftell retourne -1L en cas d’échec, et ajuste errno à la valeur représentant la cause de l’erreur.
int fseek(FILE* stream, long offset, int how); Permet de se positionner dans le fichier associé au pointeur stream. La position est exprimée par la valeur offset, qui sera interprétée en fonction du paramètre how. how peut prendre les trois valeurs suivantes : • SEEK_SET : position doit être interprété par rapport au début du fichier. • SEEK_CUR : position doit être interprété comme un déplacement relatif à la position courante dans le fichier. • SEEK_END : position doit être interprété comme un déplacement relatif à la fin du fichier. Le système d’exploitation sous-jacent peut imposer des restrictions à fseek, en particulier lorsqu’on l’applique à des fichiers texte. Il est recommandé de jeter un coup d’oeil à la documentation livrée avec l’environnement de développement en cas de doute.
#include void main() { FILE *fp; long int pos; char s[80]; fp = fopen( "MyFile", "w+" ); pos = ftell( fp ); fputs( "Hello world.", fp ); fseek( fp, pos, SEEK_SET ); fgets( s, 80, fp );
Conception et programmation orientée objet avec C++
6 6
LES LIBRAIRIES
printf( "You read: %s\n", s ); fclose( fp ); } void rewind(FILE* fstream); Equivalent à fseek(stream, 0L, SEEK_SET);
void clearerr(FILE* stream); Efface une condition d’erreur préalablement mise sur le fichier associé au pointeur stream.
int feof(FILE *stream); Teste si le fichier associé au pointeur stream est positionné en fin de fichier, c’est-à-dire si une opération d’entrée-sortie précédente a causé une erreur. feof() retourne 0 si la position actuelle dans le fichier n’est pas la fin du fichier.
#include void main() { FILE *fp; char c; fp = fopen( "MyFile", "w+" ); c = fgetc( fp ); if ( feof(fp) ) printf( "At the end of the file.\n" ); else printf( "You read %c.\n", c ); fclose( fp ); } int ferror(FILE* stream); Retourne le code d’erreur pour le fichier associé au pointeur stream, si une erreur a eu lieu. Le code d’erreur vaut 0 si aucune erreur n’a eu lieu. Le code d’erreur est remis automatiquement à zéro par un appel à rewind(), ou explicitement par un appel à clearerr().
void perror(const char* str); Permet d’afficher sur stderr le message d’erreur en clair associé à la valeur actuelle de errno. On peut ajouter la chaîne de caractères pointée par str à ce message. errno n’est pas utilisé que pour les erreurs d’entrée-sorties, comme le montre l’exemple suivant :
#include #include #include void main() { double i, result; i = -4.0; result = sqrt(i); if ( (result==0) & (errno!=0) ) perror( "Invalid argument for sqrt()" ); else printf( "The square root of %f is %f\n", i, result ); }
2.3.
STDLIB (#INCLUDE )
La plus utilisée (implicitement ou explicitement) des librairies C. Attention! Certaines des entrées de cette librairie peuvent entrer en conflit avec des instructions spécifiques à C++. Il s’agit en particulier des instructions permettant de réserver de la mémoire, comme malloc(), calloc(), realloc(), free().
Conception et programmation orientée objet avec C++
6 7
LES LIBRAIRIES
void* malloc(size_t size); malloc() permet l’allocation dynamique de mémoire en cours d’exécution du programme. En cas de succès de l’allocation, il retourne un pointeur sur le bloc mémoire reservé, et en cas d’échec, il retourne la valeur NULL. La dimension du bloc à réserver est donnée en paramètre. Le type size_t dépend de l’implémentation: le plus souvent, size_t correspond à long. struct Complex { double real, imag; }; Complex* cptr; // Réserver de la place pour un tableau de complexes int size; printf(“Dimension du tableau ? “); scanf(“%d”, &size); cptr = malloc(size*sizeof(Complex)); if (cptr == NULL) { perror(“Allocation mémoire refusée“); exit(1); } malloc() n’effectue aucune initialisation de la zone mémoire reservée. En C++, il faut absolument préférer le mot réservé new. malloc() n’utilise pas les constructeurs de classes. void* calloc(size_t num, size_t size); calloc() permet de réserver de la place mémoire pour un tableau de num éléments ayant chacun une taille size. En cas de succès de l’allocation, il retourne un pointeur sur le bloc mémoire reservé, et en cas d’échec, il retourne la valeur NULL. L’exemple précédent peut se récrire, avec calloc(), de la manière suivante :
struct Complex { double real, imag; }; Complex* cptr; // Réserver de la place pour un tableau de complexes int size; printf(“Dimension du tableau ? “); scanf(“%d”, &size); cptr = calloc(size, sizeof(Complex)); if (cptr == NULL) { perror(“Allocation mémoire refusée“); exit(1); } Contrairement à malloc(), calloc() effectue une initialisation de la zone mémoire reservée. Cette initialisation consiste à mettre tous les bits de la zone mémoire considérée à zéro. En C++, il faut absolument préférer le mot réservé new. malloc() n’utilise pas les constructeurs de classes.
void* realloc(void *ptr, size_t size); realloc() permet de modifier la taille d’un bloc mémoire réservé préalablement au moyen de calloc() ou malloc(). Pour ce faire, le bloc considéré, pointé par ptr, est recopié en un autre endroit de la mémoire, dont la taille correspond à size. En cas de succès de l’allocation, il retourne un pointeur sur le bloc mémoire reservé, et en cas d’échec, il retourne la valeur NULL. Si ptr vaut NULL lors de l’appel de realloc(), cet appel a le même effet que malloc(). Si ptr n’est pas NULL, et que size vaut 0, alors cet appel est équivalent à free(). Lors de l’augmentation de la taille d’une zone mémoire, les bits nouvellement alloués ne sont pas initialisés. Lors de la diminution de taille mémoire, les bits excédentaires sont perdus. Le changement de taille du tableau reservé dans l’exemple précédent pourrait s’écrire:
int newSize; printf(“Nouvelle dimension du tableau ? “); scanf(“%d”, &newSize); cptr = realloc(cptr, sizeof(Complex) * newSize); if (cptr == NULL)
Conception et programmation orientée objet avec C++
6 8
LES LIBRAIRIES
{ perror(“Allocation mémoire refusée“); exit(1); } Attention ! il faut se souvenir que, contrairement à calloc(), realloc() n’effectue aucune initialisation ! En C++, il faut absolument préférer le mot réservé new. realloc() n’utilise pas les constructeurs de classes, ni les opérateurs de copie.
void free(void* ptr); free() permet de libérer la place mémoire réservée par malloc(), calloc() ou realloc(). Le pointeur passé à free() comme argument doit impérativement avoir été réservé par l’une de ces instructions, sans quoi les résultats de l’appel sont imprévisibles (le plus probablement, un “crash” du programme). Passer un pointeur NULL à free() n’a pas d’effet.
char* str = malloc(200 * sizeof(char)); ... free(str); int abs(int i); Valeur absolue de i.
div_t div(int numerateur, int denominateur); Livre la partie entière et le reste de la division de numerateur/denominateur.
#include #include void main () { int n = 32452, d = 787; div_t r; r = div(n,d); printf( "%d / %d vaut %d\n", n, d, r.quot ); printf( "et le reste vaut %d.\n", r.rem); } long labs(long j); Valeur absolue pour des long.
int rand(void); Retourne une valeur aléatoire uniformément distribuée entre 0 et RAND_MAX. Ne convient pas pour des applications nécessitant des nombres à caractéristiques statistiques de bonne qualité.
void srand(unsigned int seed); Permet d’initialiser le générateur de rand à une valeur prédeterminée.
void* bsearch(const void *key, const void* array, (*comparison)(const void *key, const void *data));
size_t
count,
size_t
size,
int
Recherche dichotomique ( binary search) de l’élément key dans le tableau array. Le tableau comporte count éléments, chacun de taille size. L’utilisateur fournit la fonction permettant de comparer deux éléments; cette fonction doit retourner un résultat < 0 si key est plus petit que l’élément considéré, 0 si key est égal à l’élément, et > 0 si key est plus grand que l’élément comparé. Le tableau array doit avoir préalablement été trié par ordre ascendant, de manière à permettre la recherche dichotomique. On pourra utiliser qsort() pour ce faire.
#include #include #include
Conception et programmation orientée objet avec C++
6 9
LES LIBRAIRIES
int compare( const void *s1, const void *s2 ) { return strcmp( s1, s2 ); } int Findname( char *name ) { char *result; static char infoProfs[][15] = { "Breguet", "Evequoz", "Guerid", "Molliet", "Pesenti", "Roethlisberger" }; result = bsearch( name, infoProfs, 6, sizeof(char[15]), compare ); return ( result != NULL ); } void main() { char name[80]; printf( "Votre nom SVP ? " ); scanf( "%s", name ); printf(“%s “, name); if ( Findname(name) ) printf( "est"); else printf( "n’est pas"); printf(“ un prof d’informatique à l’EINEV\n”); } void qsort(void* array, size_t count, size_t size, int (*comparison)(const void *key, const void *data)); Réalise le tri en place d’un tableau array comportant count éléments de taille size. L’utilisateur fournit lui-même la fonction de comparaison; cette fonction doit retourner un résultat < 0 si key est plus petit que l’élément considéré, 0 si key est égal à l’élément, et > 0 si key est plus grand que l’élément comparé. L’algorithme de tri utilisé est celui dû à R. Sedgewick, appelé quicksort. Il consiste à partitionner le tableau à trier en tableaux partiels que l’on triera individuellement.
#include #include const int ASIZE = 10; const int AMAX = 100; int compare( const void *n1, const void *n2 ) { return ( *((int *) n1) - *((int *) n2) ); } void main() { int a[ASIZE], i; srand( (unsigned int) clock() ); // Initialisation d’un tableau d’entiers // avec des nombres aléatoires for ( i=0; i
Conception et programmation orientée objet avec C++
7 0
LES LIBRAIRIES
double atof(void char* str); Conversion d’une chaîne de caractères en une valeur de type double.
#include #include void main() { char *s1 = "3.141593"; char *s2 = "123.45E+676COUCOU!"; double result; result = atof( s1 ); printf( "s1 vaut %G.\n", result ); result = atof( s2 ); printf( "s2 vaut %G.\n", result ); } Résultat : s1 vaut 3.141593 s2 vaut 1.2345E+676
int atoi(const char* str); long int atol(const char* str); Conversions de chaînes de caractères en un entier ou en un long entier, respectivement.
double strtod(const char *str, char **end); Convertit str en une valeur en double précision. La conversion saute des blancs initiaux, débute dès qu’un nombre est rencontré, et s’arrête dès qu’un caractère non traduisible est trouvé. C’est l’adresse de ce caractère qui sera retournée dans end. end peut être réutilisé pour des appels successifs dans le cas de conversions en chaîne. atof(str) est équivalent à strtod(str, NULL). Une conversion impossible livre 0 comme valeur de retour, et errno est mis à E_RANGE. Si une conversion dépasse les limites de la machine, la valeur HUGE_VAL est retournée (voir ).
long int strtol(const char* str, char** end, int base); unsigned long strtoul(const char* str, char** end, int base); Conversion d’une chaîne de caractères en une valeur entière longue, respectivement longue non signée. Obéit au mêmes principes que strtod. En plus, il est possible de spécifier une base de conversion comprise entre 2 et 36. Si la base vaut 0, la fonction va chercher à déterminer la base du nombre au moyen du préfixe.
#include #include main() { char *s1 = "3723682357boo!"; char *s2 = "0xFACES"; unsigned long int result; char *end; result = strtoul( s1, &end, 10 ); printf( "s1 vaut %lu, avec '%s' laissé non converti.\n", result, end ); result = strtoul( s2, &end, 0 ); printf( "s2 vaut %lu, avec '%s' laissé non converti.\n", result, end ); }
2.4.
MATH (#INCLUDE )
Cette librairie ne pose pas de problèmes particuliers à l’utilisation. Il faut toutefois se rappeler que la librairie math travaille en double précision, et que l’utilisation en conjonction avec des variables en virgule flottante peut amener certains compilateurs à générer des avertissements, dans le cas où la conversion automatique n’est pas “sûre”.
Conception et programmation orientée objet avec C++
7 1
LES LIBRAIRIES
double sin(double x); double tan(double x); double cos(double x); Fonctions trigonométriques sinus, cosinus et tangente.
double acos(double x); double asin(double x); double atan(double x); Arc cosinus, arc sinus et arc tangente.
double atan2(double x, double y); Arc tangente de y/x.
#include #include void main() { double x, y, result; printf( "Enter x: " ); scanf( "%lf", &x ); printf( "Enter y: " ); scanf( "%lf", &y ); result = atan2(x,y); printf( "atan2( %G/%G ) = %G\n", x, y, result); } double sinh(double x); double cosh(double x); double tanh(double x); Sinus hyperbolique, cosinus hyperbolique et tangente hyperbolique.
double double double double
exp(double x); log(double x); log10(double x); sqrt(double x);
Exponentielle, logarithme naturel, logarithme en base 10, et racine carrée. Des erreurs eventuelles (log(-4)) seront signalées par le biais de errno.
double fabs(double x); Valeur absolue.
double floor(double x); double ceil(double x); Arrondi entier par troncature, arrondi entier par excès. Noter que l’entier résultant est retourné sous forme d’un double.
double fmod(double x, double y); Livre le reste de la division de x par y. errno est mis à EDOM si y == 0.
#include #include void main() { double x, y, result; printf( "Enter x: " ); scanf( "%lf", &x ); printf( "Enter y: " ); scanf( "%lf", &y );
Conception et programmation orientée objet avec C++
7 2
LES LIBRAIRIES
result = fmod( x, y ); printf( "The remainder of %G/%G is %G.\n",x, y, result ); } double ldexp(double x, int exposant); double frexp(double x, int *exposant); ldexp() retourne xexposant, alors que frexp() décompose un nombre en ses deux parties x et exposant. Ces deux fonctions sont inverses l’une de l’autre.
double modf(double x, double* intpart); Retourne la partie décimale de x, et dans la variable pointés par intpart, la partie entière.
double pow(double x, double y); Retourne xy.
2.5.
CTYPE (#INCLUDE )
Dans de nombreuses implémentations de librairies, les fonctions de ctype, permettant la manipulation de caractères, sont implémentées sous forme de macro. Il faut souligner que ces fonctions doivent être préférées à un test direct basé sur un code ASCII, qui pourrait ne pas être utilisé sur une machine utilisant un système d’exploitation différent. Les caractères sont généralement traités comme des entiers par cette librairie.
int isalnum(int c); Retourne TRUE (vrai) si c est un caractère alphanumérique (lettre ou chiffre).
int isalpha(int c); Retourne TRUE (vrai) si c est un caractère alphabétique (lettre majuscule ou minuscule).
int iscntrl(int c); Retourne TRUE (vrai) si c est un caractère de contrôle (exemple, ESCAPE).
int isdigit(int c); Retourne TRUE (vrai) si c est un chiffre.
int isgraph(int c); Retourne TRUE (vrai) si c est affichable, et différent de SPACE.
int islower(int c); Retourne TRUE (vrai) si c est une lettre minuscule.
int isprint(int c); Retourne TRUE (vrai) si c est un caractère affichable ou un espace (SPACE).
int ispunct(int c); Retourne TRUE (vrai) si c est un caractère différent d’une lettre, d’un chiffre, ou d’un caractère de contrôle.
int isspace(int c); Retourne TRUE (vrai) si c est un espace (SPACE), un tabulateur, une fin de ligne, un tabulateur vertical (interligne), un saut de page.
Conception et programmation orientée objet avec C++
7 3
LES LIBRAIRIES
int isupper(int c); Retourne TRUE (vrai) si c est une lettre majuscule.
int isxdigit(int c); Retourne TRUE (vrai) si c est un chiffre héxadécimal.
int tolower(int c); Retourne le résultat de la conversion de c en minuscule. Si c n’est pas une lettre majuscule, il sera retourné inchangé.
#include #include void main() { char uc, lc; printf("Entrer une majuscule: "); uc = getchar(); lc = tolower( uc ); if ( uc != lc ) printf( "Minuscule de '%c' est '%c'.\n", uc, lc ); else printf("Pas de conversion en minuscule possible pour '%c'.\n",uc ); } int toupper(int c); Retourne le résultat de la conversion de c en majuscule. Si c n’est pas une lettre minuscule, il sera retourné inchangé.
2.6.
STRING (#INCLUDE )
string est un module très apprécié des programmeurs en C et C++. Il faut toutefois se méfier de l’utili sation incontrôlée de string en C++, puisque ce module fait usage, dans certains cas, de malloc() et free(). En pratique, tant que le programmeur n’utilise que les primitives de string, et ne les mélange pas avec les instructions correspondantes de C++, il n’y a pas de problèmes particuliers à utiliser string, sinon que certaines librairies C++ implémentent les mêmes fonctionnalités de manière plus confortable.
void *memcpy(void* dest, const void* source, size_t size); Copie de size bytes de la zone pointée par source vers la zone pointée par dest. Attention, si les deux zones mémoires se chevauchent, il peut y avoir destruction de données.
#include #include typedef struct { int a; int b; } Foo; void main() { Foo foo1 = { 123, 456 }; Foo foo2 = { 987, 321 }; printf( "Before memcpy():\n" " foo1 = %d, %d foo2 = %d, %d\n", foo1.a, foo1.b, foo2.a, foo2.b ); memcpy( &foo1, &foo2, sizeof(Foo) ); printf( "After memcpy():\n" " foo1 = %d, %d foo2 = %d, %d\n", foo1.a, foo1.b, foo2.a, foo2.b ); } void* memmove(void* dest, const void* source, size_t size); Comme memcpy(), mais la fonction est executée correctement même si les zones mémoirese chevauchent.
Conception et programmation orientée objet avec C++
7 4
LES LIBRAIRIES
char* strcpy(char* dest, const char* source); Comme memcpy(), mais pour le cas particulier où les zones mémoires sont occupées par des chaînes de caractères. Attention à s’assurer que la place reservée dans dest est bien suffisante pour stocker la longueur de source!
#include #include void main() { char *source = "Au revoir"; char dest[25] = "Bonjour"; printf( "Avant strcpy(): dest = \"%s\"\n", dest ); strcpy( dest, source ); printf( "Apres strcpy(): dest = \"%s\"\n", dest ); } char* strncpy(char* dest, const char* source, size_t size); Comme memcpy(), mais pour le cas particulier où les zones mémoires sont occupées par des chaînes de caractères. La copie s’arrête soit après copie du caractère de fin de chaîne (‘\0’), soit après copie de size caractères.
char* strcat(char* dest, const char* source); Concaténation de dest et de source, et dépose du résultat dans dest. Attention à s’assurer que la place reservée dans dest est bien suffisante pour stocker la longueur de dest + source!
#include #include void main() { char s1[25] = "hello "; char *s2 = "world"; printf( "Avant strcat(): s1 = \"%s\"\n", s1 ); strcat( s1, s2 ); printf( "Apres strcat(): s1 = \"%s\"\n", s1 ); } char* strncat(char* dest, const char* source, size_t size); Concaténation de dest et de source, et dépose du résultat dans dest. La concaténation s’arrête au plus tard après la dépose de size caractères dans dest.
int memcmp(const void* m1, const void* m2, size_t size); Compare size éléments entre les deux zones mémoire indiquées. Retourne 0 si elles sont égales, > 0 si m1 est plus grand que m2, < 0 si m1 est inférieur à m2. L’opération s’arrête aussi tôt que le résultat est connu.
#include #include typedef struct { int a; int b; } Foo; void main() { Foo foo1, foo2; printf( "For each structure, enter two integers \n" "separated by a space, like: 123 987\n\n" ); printf( "Enter two numbers for foo1: "); scanf( "%d %d", &foo1.a, &foo1.b ); printf( "Enter two numbers for foo2: "); scanf( "%d %d", &foo2.a, &foo2.b ); if ( memcmp(&foo1, &foo2, sizeof(Foo)) == 0 )
Conception et programmation orientée objet avec C++
7 5
LES LIBRAIRIES
printf( "foo1 and foo2 are the same.\n" ); else printf( "foo1 and foo2 are different.\n" ); } int strcmp(const char* s1, const char* s2); int strcoll(const char* s1, const char* s2); Comme memcmp(), mais pour des chaînes de caractères. strcoll() respecte en plus les conditions locales d’utilisation, qui peuvent inclure certaines règles particulières à un langage donné.
#include #include main() { char s1[80], s2[80]; int result; printf( "Première chaîne (s1): "); scanf( "%s", s1 ); printf( "Seconde chaîne (s2): "); scanf( "%s", s2 ); result = strcmp( s1, s2 ); if ( result > 0 ) printf( "s1 est plus grand que s2.\n" ); else if ( result < 0 ) printf( "s1 est inférieur à s2.\n" ); else printf( "s1 et s2 sont identiques.\n" ); } int strncmp(const char* s1, const char* s2, size_t size); Comme memcmp(), mais pour des chaînes de caractères. La comparaison s’arrête après un maximum de size caractères, et au plus tôt lorsque le résultat est connu.
void* memchr(const void* str, int c, size_t size); Retourne un pointeur sur le premier caractère identique à c trouvé dans les size premiers caractères de la chaîne str. Si c n’st pas trouvé, memchr() retourne NULL.
char* strchr(const char* str, int c); Retourne un pointeur sur le premier caractère identique à c trouvé dans la chaîne str. Si c n’st pas trouvé, strchr() retourne NULL. Identique à memchr() pour des chaînes terminées par ‘\0’.
2.7.
SIGNAL (#INCLUDE )
Ces groupes de fonctions permettent de gérer des évènements asynchrones au programme. Il ne faut pas confondre ce mécanisme avec un traitement d’exceptions tel qu’on le trouve en C++ ou en ADA : il s’agit plutôt de communications élémentaires entre processus asynchrones, voire de traitement d’interruptions ou d’erreurs de la librairie. Dans les implémentations minimales de C, les signaux disponibles reflètent les limitations du système d’exploitation, et se limitent à des évènements internes au programme (erreurs mathématiques, time-outs, etc...). Dans le cadre d’implémentations UNIX, par contre, les signaux disponibles sont beaucoup plus riches; en revanche, l’utilisation de cette richesse tend à rendre les programmes difficiles à porter.
void (*signal(int sig, void(*func)(int)))(int); Détermine la fonction à appeler lorsque le signal sig sera émis.
int raise(int sig); Provoque l’émission du signal sig.
Conception et programmation orientée objet avec C++
7 6
3.
LES LIBRAIRIES
IOSTREAM (#INCLUDE )
iostream est une librairie «standard» de C++. Cette librairie définit les flots d’entréesortie, et sert à remplacer la librairie stdio de C. Un flot est un canal (recevant, d’entrée ou fournissant, de sortie) que l’on peut associer à un périphérique, à un fichier, ou à une zone mémoire. Un flot recevant ou d’entrée est unistream, (ifstream dans le cas d’un fichier) alors qu’un flot de sortie est un ostream (ofstream dans le cas d’un fichier). Logiquement, un flot d’entrée-sortie se déduit par héritage multiple d’un istream et d’un ostream pour former un iostream. Il existe des flots prédéfinis : • cout est le flot de sortie standard, connecté à la sortie standard stdout. • cin est le flot d’entrée standard, qui est connecté à l’entrée standard stdin.
3.1.
OSTREAM
ostream redéfinit l’opérateur << sous la forme d’une fonction membre : ostream& operator << (expression) ; L’expression correspondant au deuxième opérande peut être d’un type standard de base quelconque, y compris char ou char*. Si on veut utiliser cet opérateur pour des types définis par l’utilisateur, il faudra le redéfinir dans le cadre de la déclaration du type concerné, généralement sous forme d’une fonction friend. Il n’est pas possible de redéfinir cet opérateur comme fonction membre, à cause de l’ordre d’interprétation des opérandes.
class X { friend ostream& operator << (ostream&, const X&); // opérandes dans l’ordre correct ... }; L’exemple çi-dessous n’est pas faux, mais est quasi inutilisable, car l es opérandes vont se trouver inversés :
class X { public : ostream& operator << (ostream&); // les opérandes sont dans le mauvais ordre !!! }; Parmi les nombreuses fonctions membres, citons :
ostream& put(char c); ostream& write(void* adressOfBuffer, long bufferSize);
3.2.
ISTREAM
istream redéfinit l’opérateur >> de la manière suivante : istream& operator >> (& base_type); base_type peut être un type de base quelconque, sauf un pointeur (char* est accepté, néanmoins, pour la lecture de chaînes de caractères). >> est compatible avec scanf, en ce sens que les «espaces» (espace, tabulateur, fin de ligne, saut de page, etc...) sont interprétés comme des délimiteurs. Pour redéfinir l’opérateur dans le cas d’un type utilisateur, utiliser la même technique que pour ostream :
class X { friend istream& operator >> (istream&, const X&); // opérandes dans l’ordre correct ... }; istream& get(char& c); int get(); // returns value of char at input, or EOF istream& read(void* bufferAdress, int bufferSize); // si moins de bufferSize caractères ont pu être lus, la valeur // effectivement lue du nombre de caractères peut être obtenue par : int gcount();
3.3.
IOSTREAM
Dérivée publiquement de istream et ostream, cette classe permet les entrées-sorties conversationnelles, par exemple pour la lecture/écriture sur un fichier.
Conception et programmation orientée objet avec C++
7 7
LES LIBRAIRIES
3.4. E TAT D’UN FLOT istream et ostream dérivent de la classe ios, qui définit les constantes suivantes : • eofbit : fin de fichier. • failbit : la prochaine opération sur le flot ne pourra pas aboutir. • badbit : le flot est dans un état irrécupérable. • goodbit : aucune des erreurs précédentes. L’accès aux nits d’erreur peut se faire par l’une des fonctions membres suivantes : • int eof() : valeur de eofbit. int bad() : valeur de badbit. • int fail() : valeur de failbit. • • int good() : vrai (== 1) si aucun bit d’erreur n’est activé. int rdstate() : valeur du mot d’état complet. • Le mot d’état peut être modifié par la méthode suivante :
void clear(int newState = 0); Le mot d’état prend la valeur newState. Pour activer un bit (par exemple badbit sur le flot fl) on utilisera quelque chose comme :
fl.clear(ios::badbit | fl.rdstate()); Lorsque l’on lit un fichier, il arrive fréquemment que l’on écrive une boucle du type :
char buffer[1001]; int sze; while (!f.eof()) { // read the next chunk of data and process it f.read(buffer, 1000); for (int i = 0; i < f.gcount(); i++) { .... } } Il faut se souvenir qu’à la fin de cette boucle, fl.rdstate() est différent de zéro. Aucune opération sur ce flot n’est plus tolérée. En particulier, si il s’agit d’un fichier à accès aléatoire, où l’on peut librement positionner le pointeur de lecture dans le fichier, il ne sera plus possible de le positionner à la sortie de la boucle, à moins de remettre à zéro le mot d’état à l’aide de :
f.clear(); A chaque flot est associé un état de formattage, constitué d’un mot d’état et de 3 valeurs numériques associées, correspondant au gabarit, à la précision et au caractère de remplissage. Tableau 2 : signification du mot d’état de formatage Nom du champ Nom du bit Signification ios::dec conversion décimale ios::oct conversion octale ios::basefield ios::hex conversion hexadécimale ios::showbase affichage de l’indication de base ios::showpoint affichage du point décimal ios::scientific notation dite scientifique ios::floatfield ios::fixed notation en virgule fixe Pour influencer le formatage, on peut utiliser soit des «manipulateurs», soit des fonctions membres. Les manipulateurs peuvent être «paramétriques» ou «simples». Les manipulateurs simples s’utilisent sous la forme :
flot << manipulateur flot >> manipulateur Les principaux manipulateurs simples sont représentés Tableau3. Tableau 3 : principaux manipulateurs simples Manipulateur Utilisation Action dec Entrée/Sortie Active le bit de conversion décimale hex Entrée/Sortie Active le bit de conversion hexadécimale oct Entrée/Sortie Active le bit de conversion octale endl Sortie Insère un saut de ligne et vide le tampon de sortie ends Sortie Insère un caractère de fin de chaîne (\0) Les manipulateurs paramétriques s’utilisent sous la forme :
istream& manipulateur(argument);
Conception et programmation orientée objet avec C++
7 8
LES LIBRAIRIES
ostream& manipulateur(argument); Les principaux manipulateurs paramétriques sont représentés Tableau4. Tableau 4 : principaux manipulateurs paramétriques Manipulateur Utilisation Action setbase(int) Entrée/Sortie Définit la base de conversion setprecision(int) Entrée/Sortie Définit la précision de sortie des nombres en virgule flottante setw(int) Entrée/Sortie Définit le gabarit. Attention! Il retombe à zéro après chaque conversion !
3.5. A SSOCIATION D’UN FLOT A UN FICHIER Dérivés de ostream, respectivement de istream, les classes ofstream et ifstream permettent la création de flôts de sortie ou d’entrée associés à des fichiers :
ofstream of(char *fileName, char openMode=ios:out); ifstream if(char *fileName, char openMode=ios:in); Le pointeur d’écriture (respectivement de lecture) peut être manipulé au moyen de :
seekp(int displacement, int relativeTo); respectivement :
seekg(int displacement, int relativeTo); Par défaut, relativeTo prend la valeur ios::begin, c’est-à-dire que le déplacement se fera par rapport au début du fichier. Les autres valeurs possibles sont ios::current (déplacement relatif à la position actuelle) et ios::end (déplacement par rapport à la fin du fichier. Fermer un ofstream ou un ifstream requiert dans tous les cas l’utilisation de close(); noter que détruire une instance de flot d’entrée-sortie (delete) ne détruit pas le fichier associé, mais généralement le ferme (au cas où il était resté ouvert). Dans tous les cas, il vaut mieux fermer explicitement un flot associé à un fichier lorsque ce dernier n’est plus utilisé. L’utilisation des classes ifstream et ofstream requiert l’inclusion de fstream.h; fstream. h incluant iostream.h, il n’est en principe pas nécessaire de l’inclure à nouveau explicitement. Le fichier peut être ouvert dans différents modes, selon la valeur du paramètre openMode, comme décrit dans le Tableau5 : Tableau 5. Modes d’ouverture d’un fichier Bit de mode d’ouverture Action ios::in Ouverture en lecture (obligatoire pour ifstream) ios::out Ouverture en écriture (obligatoire pour ofstream) ios::app Ouverture en mode ajout (ofstream seulement) ios::ate Place le pointeur fichier en fin de fichier après ouverture ios::trunc Si le fichier existe, son contenu est détruit après ouverture ios::nocreate Le fichier doit exister à l’ouverture (ne doit pas être crée) ios::noreplace Le fichier ne doit pas exister à l’ouverture (sauf si ios::ate ou ios::app est spécifié) Dérivant de ifstream et ofstream, un flot de type fstream peut être associé à un fichier utilisable simultanément en lecture et en écriture.
4.
L++ (STANDARD COMPONENTS)
L++ est une tentative de correction de l’erreur commise par les concepteurs de C++, qui n’ont pas défini de librairie standard C++ (à l’exception de iostream). L++ contient des fonctions très générales, universellement applicables. L++ est livré en standard avec les compilateurs conformes à la spécification Cfront de USL, mais n’est actuellement pas prévu pour la standardisation ANSI. L++ se trouve sur la grande majorité des compilateurs UNIX, mais n’est généralement pas inclus dans les versions pour ordinateurs personnels (Symantec, Microsoft ou Borland). C’est fort regrettable, car L++ propose un ensemble de fonctions très puissant et universellement applicable, à l’inverse des librairies proposées sur PC, qui ne concernent souvent que des portione limitées de l’interface utilisateur spécifique à la machine utilisée. Parmi les classes proposées par L++, citons : • Bit : Permet la définition et la manipulation de chaînes de bits de longueur arbitraire • Block : Gestion de zones mémoires • Graph : Structure de graphes (éléments reliés par des réseaux arbitraires) • List : Listes généralisées. • Map : Gestion de tableaux à adressage multiple, en particulier avec adressage relatif au • contenu. • Pool : Gestion de mémoire à usage général, avec réallocation dynamique de bloca devenus
Conception et programmation orientée objet avec C++
7 9 • • • • • • • • • •
5.
LES LIBRAIRIES
libres. Regexp : Manipulation d’expressions régulières. Set : Manipulation d’ensembles. Stopwatch : Chronomètre. String : Gestion de chaînes de caractères dynamiques. Utilise Pool. Symbol : Gestion de tables de symboles. Utilise Pool. Time : Manipulation et contrôle de données associées aux temps. bag : Container d’information généralisé. Il est possible de faire des opérations d’entréesortie sur des bags. g2++ : Routines d’encodage et décodage de données. g++ permet la transmission d’information entre ordinateurs différents sans précautions relativement à la manière d’encodage propre à chaque machine. Ainsi, pour transférer un entier dans sa représentation binaire entre un PowerPC et un CPU Intel, il est normalement nécessaire de tenir compte de la représentation différente des entiers dans le stack du PowerPC et du chip Intel. L’utilisation de g2++ permet d’éviter cet inconvénient en introduisant la notion de syntaxe de transfert, un peu à la manière de ASN.1 dans le monde OSI, ou IDL / XDR dans le monde RPC.
QUELQUES AUTRES LIBRAIRIES
Il existe de nombreuses autres librairies, certaines dépendantes du système d’exploitation, d’autres indépendantes. Certaines ne sont que des librairies C avec un interface C++, d’autres sont des librairies natives. Il serait inutile et vain de chercher à faire un listing exhaustif. Nous nous bornerons à citer quelques-unes des pl us intéressantes : Motif++ : Version C++ de Motif 1.2. Domaine public. Devrait être bientôt remplacée par Motif2.0, qui devrait comporter un interface C++ en mode natif. Interviews : Librairie originellement développée à Stanford, est actuellement implémentée sur plusieurs plate-formes, y compris Motif et Macintosh. Emule Motif sans faire explicitement appel à Motif. Microsoft Foundation Classes : Librairie d’accès à Windows. Dans sa dernière mouture, avec Visual C++ 4.0, est devenue multi-plateformes. Il est possible de générer du code pour Alpha, Intel, PowerPC et Macintosh, avec une librairie native. Think C Library : librairie fournie avec le compilateur Think C (Symantec C++) pour le Macintosh. La librairie n’est pas écrite en C++, mais dispose d’un i nterface C++. DCE++ : Actuellement en domaine semi-public, DCE++ pourrait être standardisée dans un proche avenir. DCE++ facilite l’utilisation de DCE (Distributed Computing Environment ) en C++. L’accès à des services distribués comme DTS (Distributed Time Service), RPC (Remote Procedure Call), NIS (Network Information Service) ou DNS (Domain Name Service) est encapsulé dans des classes facilement utilisables. DCE++ est distribué par HaL computers (http://hal.com). GCOOL : Domaine public. Implémente des classes universellement utilisables, un peu à la manière de L++. Pour les intéressés, il existe sur Internet une rubrique (en fait, il en existe plusieurs qui se référencent les unes les autres) rassemblant les librairies C++, tant du domaine public que commerciales. Cette rubrique se nomme FAQ : available C++ libraries et peut être consultée par ftp ou par http (World Wide Web). La mise à jo ur se fait environ une fois par mois, et est raisonnablement exhaustive. Comme beaucoup de rubriques sur Internet, celle-ci dépend entièrement de la disponibilité de son auteur bénévole. Actuellement (octobre 1995) la rubrique comprend 50 pages et va de librairies mathématiques (LINPACK++, FFT++) à des classes couvrant la biologie et l’astrophysique, en passant par de très intéressantes librairies de classes génériques et de classes utilisables pour la génération d’interfaces utilisateur. La plupart de ces classes sont développées pour UNIX, mais la disponibilité du code source pour beaucoup d’entre elles les rend utilisables aussi dans d’autres environnements (Windows 95 et Windows NT entre autres). Il est également possible d’utiliser des sites Internet spécialisés (www.apple.com, www.microsoft.com, www.hal.com, www.att.com, www.borland.com, etc...) pour se renseigner sur la disponibilité de librairies. Il est de toutes façons recommnadé, avant d’entreprendre un nouveau projet, de se renseigner sur les librairies disponibles. Même si, pour des raisons de licences ou de copyright, ou simplement d’erreurs, elles se révèlent inutilisables, il est très utile de connaître d’autres solutions à un même problème : dans l’optique de la réutilisation de logiciels, même les erreurs (?) des autres peuvent être mises à profit.
Conception et programmation orientée objet avec C++
AN N AL E S
Conception et programmation orientée objet avec C++