Upload
vanmien
View
212
Download
0
Embed Size (px)
Citation preview
Cours de Programmation
Introduction à
C++
Laurent HenocqueMaître de Conférences
Ecole Supérieure d’Ingénieurs de Luminy
Université de la Méditerranée Aix-Marseille II
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 1
Cours de programmationIntroduction à C++Utilisation avancée du langage
Manuel de référence sommaire
ESIL / ES2I
Université de la Méditerranée
par Laurent Henocque
([email protected])(http://www.esil.univ-mrs.fr/Staff/Prof/henocque/)
version 1.2 en date du 8 octobre 1996
Ce document peut être librement reproduit dans son intégralité pourvu que la présente mention de copyright ainsi quecelles présentes en tête et en pied de page y restent attachées. Toute autre forme de copie est interdite.
Auteur Laurent Henocque, maître de conférences.Ce document est un cours de l’
Ecole Supérieure d’Ingénieurs de Luminy,département Etudes Supérieures en Ingénierie Informatique,
163 Avenue de Luminy, case 925, 13288 Marseille Cedex 9(http://www.esil.univ-mrs.fr)
0. Introduction et plan
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 2
Ce cours évoque les notions fondamentales du langages C++ telles qu’exposées dans l’ouvrage “C++ Seconde Edition”,par l’auteur du langage C++ : Bjarne Stroustrup. Ce livre constitue un document de référence irremplaçable. Nous enreprenons ici de nombreux exemples, et une partie de la structure.
L’objectif du cours est de servir de référence pour l’étudiant soucieux d’exploiter le plus possible les qualités du langage,et envisage donc les différentes constructions dans un assez grand détail.
Voici le plan de ce cours :1. A LA DECOUVERTE DE C++ 1
1.1. introduction11.2. paradigmes de programmation 11.3. "Un meilleur C" 41.4. C++ apporte un support pour les abstractions de données 61.5. C++ apporte un support pour la programmation par objets 9
2. FONCTIONS ET FICHIERS 122.1. introduction122.2. portée d'édition 122.3. … portée d'édition 122.4. fichiers d' en tête “.h” 132.5. éditions de liens avec du code non C++ 142.6. comment construire les bibliothèques152.7. les fonctions 152.8. macros 212.9. … macros 212.10. exercices 24
3. CLASSES 263.1. classes et membres 263.2. interfaces et mise en oeuvre 293.3. autres caractéristiques des classes 303.4. construction et destruction 343.5. exercices 363.6. 37
4. HERITAGE 384.1. introduction et tour d'horizon 384.2. héritage : classes dérivées 384.3. classes abstraites 384.4. héritage multiple 394.5. héritage virtuel 394.6. contrôle d'accès 394.7. mémoire dynamique39
5. DECLARATIONS ET CONSTANTES 415.1. déclarations 415.2. les noms 435.3. les types 435.4. les littéraux 50
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 3
5.5. les constantes nommées 515.6. les champs de bits et unions 535.7. exercices 54
6. EXPRESSIONS ET INSTRUCTIONS 566.1. sommaire des opérateurs 566.2. les instructions 656.3. commentaires et indentation676.4. exercices 67
7. SURCHARGE D'OPERATEUR 707.1. introduction707.2. fonctions opérateurs 707.3. conversions de types définis par le programmeur 717.4. littéraux 737.5. gros objets 737.6. affectation et initialisation 737.7. indexation 737.8. opérateur d’appel de fonction 737.9. indirections 737.10. incrémentation et décrémentation 747.11. une classe chaîne de caractères 747.12. amies et membres 757.13. avertissement 76
8. TEMPLATES 778.1. introduction778.2. un patron simple : patron de classe 778.3. listes génériques 778.4. fonctions génériques globales818.5. résolution de la surcharge des fonctions génériques 828.6. arguments de template 828.7. dérivation et templates 838.8. un tableau associatif 83
9. GESTION DES EXCEPTIONS 859.1. gestion des erreurs 859.2. discrimination et nommage des exceptions 859.3. acquisition de ressources 879.4. exceptions qui ne sont pas des erreurs899.5. spécification d'interface 899.6. exceptions non interceptées 909.7. alternatives à la gestion des erreurs 909.8. 90
10. FLOTS 9110.1. introduction9110.2. sorties 91
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 4
10.3. entrées 9110.4. mise en forme 9310.5. fichiers et flots 9810.6. entrées/sorties C 99
11. NOTES RAPIDES AU PROGRAMMEUR C 10011.1. règles empiriques de base pour la conception d'objets10011.2. notes aux programmeurs C 100
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 1
1. A la découverte de C++
1.1. introduction
C++ est une tentative d'améliorer C C++ supporte l’abstraction de données C++ permet la programmation objet
1.2. paradigmes de programmation
1.2.1. la programmation procédurale : algorithmie
choisir les procédures, implanter les meilleurs algorithmes
double sqrt(double) {
// calcule result avec le meilleur algorithme possible
return result;
}
1.2.2. la programmation modulaire
choisir les modules, masquer les données (exemple de la pile)
#include "stack.h"
static char v[stack_size]
static char * p=v;
void push(char c){/*vérification de débordement et empilement*/}
char pop(){/*vérification de taille 0 et pop*/}
avantages : l'utilisateur ne connaît que les fonctions, et la représentation interne peut changer C++ supporte ce type de programmation par compatibilité avec C, et l'étend grâce aux classes
1.2.3. l'abstraction de données dans un langage classique (C)
pour chaque type de donnée, on donne un ensemble complet d'opérations : notamment des fonctions de construction etde destruction et de copie, comme il en existe pour les types fondamentaux, mais aussi toutes les fonctions correspondant auxservices offerts par la classe
c'est nécessaire par exemple si l'on doit pouvoir utiliser deux piles simultanément, ce qui n'est pas permis par laprogrammation modulaire (à cause de l'utilisation de variables globales)
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 2
/*class*/ struct stack_id {…};
stack_id create_stack();
void delete_stack(stack_id);
void push (stack_id, char);
char pop (stack_id);
les types ainsi conçus par des classes sont différents des types pré définis dans leur utilisation le compilateur (C) reste incapable de détecter certaines erreurs. Dans l'exemple ci dessous, s2 est utilisé et détruit sans
avoir été initialisé.
main(){
stack_id s1,s2;
s1 = create_stack();
push (s1,'a');
char c = pop(s1);
push (s2,'b');
delete_stack(s2);
}
1.2.4. ... abstraction de données
C++ améliore cette situation. On dispose du mot clef "class". La classe est une unité d'encapsulation pour les fonctionsqui implémentent les services rendus, et pour les données nécessaires. Le langage interdit d'utiliser un objet non initialisé, etle compilateur devient capable d'effectuer certains contrôles.
class complex {
double re,im;
public:
complex (double r, double im){re = r; im = i;}
complex (double r){re = r; im = 0;}// conversion
friend complex operator+(complex,complex);
friend complex operator-(complex,complex);
friend complex operator-(complex);
friend complex operator*(complex,complex);
friend complex operator/(complex,complex);
…
};
complex operator+(complex c1, complex c2){
return complex(a1.re+a2.re,a1.im+a2.im);
}
void main(){
complex a = 2.3;
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 3
complex b = 1/a;
complex c = a+b*complex(5,7.3);
c = c-a*b+2;
}
Dans l'exemple ci dessus, les fonctions de construction de la classe "complex" sont appelées automatiquement, et ladéfinition des opérateurs pour ce nouveau type permet de l'utiliser "comme" un type prédéfini. C'est du C++.
1.2.5. difficultés liées à l'abstraction (quand on n'a pas de notion d'objet!)
il peut être impossible d'adapter un type pour de nouvelles utilisations sauf en modifiant sa définition. Enprogrammtion classique, on doit souvent recourir à des tests (au moyen de "switch" par exemple) que C++ rend inutiles,facilitant ainsi considérablement la réutilisabilité des programmes.
class point {…};
enum shapeType {circle, triangle, square};
class shape{
point center;
shapeType type;
public :
point where (){return center;}
void draw();
void rotate(float);
…
};
void shape::draw(){
switch (type){
case circle : … break;
case triangle : … break;
case square : … break;
}
}
dans l'exemple ci dessus, chaque nouvelle forme ajoutée impose de toucher le code de toutes les fonctions importantesdéfinies pour le type "forme"
1.2.6. la programmation par objets
le problème rencontré auparavant vient de ce que le langage ne permet pas de distinguer entre les propriétés généralesdes "shape" et celles qui sont spécifiques de chaque sorte de "shape"
l'héritage permet de résoudre ce problème
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 4
class shape {
point center;
public:
point where () { return center;}
void move (point to) {undraw(); center = point; draw();}
virtual void draw();
virtual void rotate(float);
};
les fonctions dont l'interface d'appel peut être déclarée, mais ne peut pas être définie, ont été déclarées virtual de telles fonctions sont dites constituer le protocole de la classe abstraite shape. une fonction virtuelle est liée dynamiquement : la bonne fonction est appelée automatiquement sur un objet, même
quand son type exact est inconnu
shape *tab[50];
void draw_all(float){
for (int i=0; i<50;i++){ tab[i]->rotate();}
}
1.2.7. … la programmation par objets
chaque forme particulière est alors déclarée comme une forme (via l'héritage, déclaré par la syntaxe ":" ci dessous),dont les caractéristiques spécifiques peuvent être précisées
class circle : public shape {
float radius;
public:
void draw(){…};
void rotate(float){/*rien a faire*/}
};
le processus de développement consiste alors à• choisir les classes,• partager par héritage ce qui est commun,• implanter les fonctions spÈcifiques ‡ chacun des types
1.3. Comment C++ améliore le langage C
C++ apporte fonctions, arithmétique, instructions de branchement et constructions itératives comme en C les entrées sorties sont accessibles au travers d'une bibliothèque, différente de celle connue en C les constructions élémentaires et les pointeurs sont issus de C
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 5
la compilation séparée apporte une notion minimale de module, comme en C on ne dispose toujours pas d'une notion de package qui fournirait un espace de nommage isolé
1.3.1. le plus petit programme C++ du monde
int main(){return 0;}
1.3.2. le programme "hello world"
#include <iostream.h>
int main(){
cout << "bonjour tout le monde\n";
return 0;
}
1.3.3. les entrées sorties
#include <iostream.h>
int a;
cin >> a; // charge dans la variable a l'entier qui est frappé au clavier
cout << 2*a << endl; // affiche sur la console le double de a, et fin de ligne
1.3.4. variables et arithmétique
types fondamentaux : char, short, int, long, float, double, long double opérateurs arithmétiques : + - * / % opérateurs de comparaison : == != < > <= >= C++ permet la conversion automatique entre les types de base, garantie sans perte d'information si possible (c'est
assuré dèl lors que le type origine est converti en un type dont la représentation est au moins aussi grande en nombred'octets).
double d;
int i = 2.0;//float vers int
short s;
d = d+i;//int vers double
i=s*i;//short vers int
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 6
1.3.5. pointeurs et tableaux
les types X tab[] et X* tab sont comparables de même qu'en C (un tableau n'est jamais copié). Un tableau eststrictement identique à un pointeur vers son premier élément.
char v[20]; // un pointeur vers 20 caractères consécutifs
char *p, *q; // deux pointeurs vers un caractère
q=v; // ok
p=&v[3]; // l'adresse du quatrième char est un pointeur
1.3.6. tests et itérations
if, switch, while, for comme en C on peut déclarer une variable au moment de sa première utilisation, après des instructions, même dans une instruction
"for" : for (int i=8; i>= 0; i--){} la norme C++ prévoitmaintenant que la variable ainsi déclarée soit locale à la boucle, mais attention, de nombreux
compilateurs ne sont pas encore à la norme.
1.3.7. fonctions
les types des arguments sont contrôlés lors de l'appel et éventuellement convertis dans le type attendu la signature d'une fonction (on dit aussi son "prototype") est constituée de :
• son nom• les types de ses arguments dans l'ordre ou ils se présentent
le type retourné par la fonction n'est pas indifférent, mais n'est pas traditionnellement en C++ considéré dans sasignature.
deux fonctions différentes peuvent porter le même nom : int f(int); int f(float). On parle alors de surcharge, ou depolymorphisme. Le compilateur a l'intelligence de savoir quelle fonction appeler en fonction des types des arguments quisont proposés.
le passage d'argument par référence est possible comme en Pascal (passage VAR). En C, le même mécanisme requiertl'utilisation explicite de pointeurs.
void swap (int& i, int&j){ // permute les valeurs de deux variables
int aux = j;
j = i;
i = aux;
}
int main (){
int a = 1;
int b = 2;
cout << a << " " << b << endl;
swap(a,b);
cout << a << " " << b << endl;
return 0;
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 7
}
1.3.8. la réalisation de modules et de bibliothèques
C++ permet l'édition de liens avec des fonctions écrites en d'autres langages , à condition d'informer lecompilateur des conventions de passage des arguments
extern "C" double sqrt(double);
extern ostream cout;
extern permet de spécifier le type de convention de passage des arguments (fortran et pascal sont comme "C") on peut répartir la programmation dans des fichiers séparés avec références externes
// header.h
extern char *chaine;
// chainedef.cc
#include "header.h"
char *chaine = "hello world\n";
//chainedisp.cc
#include "header.h"
#include <iostream.h>
main(){
cout << chaine;
return 0;
}
//compilation
les deux fichiers .cc sont alors compilés par la commande :
CC chainedef.cc chainedisp.cc -o prog
ou encore:
CC -c chainedef.cc #genere chainedef.o
CC -c chainedisp.cc #genere chainedisp.o
CC chainedef.o chanedisp.o -o prog
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 8
1.4. C++ apporte un support pour les abstractions de données
1.4.1. initialisation et destruction
les classes comportent des méthodes de construction d'objet, et de destruction on veut éviter de séparer l'allocation de l'espace requis pour un objet et son initialisation (source d'oublis facheux) un constructeur est une fonction qui porte le meme nom que la classe. Il y en a toujours plusieurs le destructeur porte le nom de la classe précédé du caractère "~" (tilde)
class vector{
int _s;
int *_t;
public:
vector (int s){ // cette fonction est un constructeur de vector
assert(s>0);
_s=s;
_t=new int [s];
}
vector ():_s(0),_t(0){} // un autre constructeur, qui initialise tout à 0
~vector () {delete []t;} // cette fonction est LE destructeur de vector
int operator[](int i){return _t[i];}
};
Le compilateur appelle automatiquement les constructeurs correspondant aux arguments fournis en même temps que ladéclaration, et les destructeurs des variables locales
main(){
vector v(5); // appelle le constructeur à un argument
vector w = 8; // appelle egalement ce constructeur
vector x; // appelle le constructeur sans argument.
...
} // appelle le destructeur de vector pour x, puis pour w puis pour v
1.4.2. affectation et initialisation
on doit également contrôler les opérations de copie, dans deux cas : l'initialisation d'un objet avec un autre du mêmetype pour argument (on parle alors de construction par copie), ou l'affectation d'un objet sur un autre.
par défaut, la copie C++ est a copie champ à champ de la structure C, en général non satisfaisante:
void main(){
vector v1(100); //initialisation classique
vector v2 = v1; //initialisation par copie
v1 = v2; //copie simple lors d'affectation
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 9
}
la classe vector peut être complétée comme suit :
class vector{
…
void operator=(const vector&); // affectation
vector (const vector&); // initialisation par copie
};
question : pourqoui la copie par défaut des champs des objets n'est elle pas satisfaisante? question : comment programmer ces deux fonctions afin de remédier à ce problème?
1.4.3. patrons
en C++, on peut également définir des types génériques, comme un "vecteur "de n'importe quoi". Dans l'exemple cidessous, le symbole T est un type paramètre pour le template VectorOf :
template<class T> class VectorOf {
T* v;
int sz;
public:
VectorOf (int s) { v = new T[sz = s]; }
};
void f() {
VectorOf<int> v1(100);
VectorOf<complex> v2(100);
}
1.4.4. gestion des exceptions
en C, les exceptions ne sont pas prises en charge par le langage. Habituellement on utilise setjump et longjump (desfonctions de libc) pour transférer rapidement le contrôle d'execution d'un programme lors de situations exceptionnelles sansavoir à prévoir de codes d'erreur en valeurs de retour des fonctions.
C++ apporte des constructions spécifiques pour le traitement des exceptions :• on déclare un type quelconque (notre sempiternel vector par exemple), et une (ou plusieurs) classe d'exception pour ce
type : ici "range" qui sera levée lors de dépassement des bornes du tableau :
class vector {
class range {};
...};
• on lance le contrôle à tout programme susceptible de traiter l' exception par l'intruction "throw"
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 10
int vector::operator[](int i) {
if (i<0 sz<=i) throw range();return v[i];
}
• on peut alors intercepter l'erreur dans un bloc spécialisé de type "try {} catch{}"
void f(int i) {
try {
vector v[i];
v[i+1000] = 12; // exception assurée
… // programmes pouvant aussi provoquer une exception
} catch (vector::range){
error ("attention : bornes de tableau dépassées");
}
}
1.4.5. conversions de types
C++ est un langage très fortement typé statiquement. Le défaut d'un tel langage est que le compilateur refuse deprendre des vessies pour des lanternes : on doit parfois convertir (on dit "caster") des pointeurs d'un type vers un autre. EnC++ le programmeur peut définir des convertisseurs pour ses objets dans d'autres types, ce qui peut éviter ces fameux cast, etmême programmer des conversions d'un type vers un autre par forcément très proche (un objet converti en entier qui indiqueson état par exemple)
les convertisseurs peuvent être appelés explicitement ou laissés aux bons soins du compilateur quand il en est capable(lors d'un appel de fonction notamment)
complex a = complex(1); // explicite
complex b = 1; // implicite
a = b + complex(2); // explicite
a = b + 2; // implicite
les convertisseurs permettent aussi de réduire la complexité des interfaces de programmation : une seule fonctioncomplex operator+ (complex,complex) traite trois situations dès lors qu'il existe une convertion de int verscomplex
1.4.6. C++ permet de multiplier les implantations
on peut définir une superclasse abstraite de plusieurs implantations distinctes du même type par exemple : une pile déclarée de façon abstraite puis implantée sous les deux formes de tableau et de liste
template <class type> class stack {
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 11
public:
virtual void push(T) = 0; // pure virtuelle
virtual T pop(void) = 0; // pure virtuelle
};
on ne peut pas utiliser d'instances d'une classe abstraite
stack<vtt> S; // erreur
void f(stack<car>& sc, car mercedes){ //ok
sc.push(mercedes);
car aux = sc.pop();
}
1.4.7. … C++ permet de multiplier les implantations
on peut définir plusieurs sous types de piles, l'interface ne change pas ex : avec un tableau
template <class T> class astack : public stack<T>{
// un tableau
public:
astack(int s);
~astack();
void push(T);
T pop(void);
}
ex : avec une liste :
template <class T> class lstack : public stack<T>{
// une liste
public:
lstack(void);
~lstack();
void push(T);
T pop(void);
}
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 12
1.5. C++ apporte un support pour la programmation par objets
1.5.1. mécanismes d'appel
appel de fonction membre par les opérateurs -> et .
class shape{
virtual void rotate(int);
};
void main(){
shape *p;
…
p->rotate();
}
détermination statique de la fonction membre• c'est le cas lorsqu'elle n'est pas déclarée virtuelle : le compilateur choisit celle qui est définie pour la classe connue de
l'objet détermination du membre dynamique
• lorsqu'elle est virtuelle : le compilateur appelle la fonction indirectement, au travers d'un champ appelé vtbl• cet appel est aussi efficace qu'un appel normal de fonction
Shape
center color … &?::draw()
&?::rotate()
Circle
vtbl&Circle::draw() &Circle::rotate()
vtbl
1.5.2. vérification de type
les types des arguments des fonctions sont contrôlés par le compilateur, ce qui permet une détection précoce des erreurs la vérification statique associée au mécanisme de fonctions virtuelles est très performante comparée à la vérification de
type dynamique de certains langages (Smalltalk) les erreurs sont par ailleurs détectées avant l'exécution
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 13
ex smalltalk:
Stack s; // pile fourre tout
void main(){
s.push (new Peugeot(309));
s.push (new Boeing(727));
s.pop()->takeoff();
s.pop()->takeoff(); // erreur exécution : une 309 ne vole pas
ex C++:
Stack s<plane>; // pile d'avions
void main(){
s.push (new Boeing(727)); // ok
s.push (new Peugeot(309)); // erreur compilation
s.pop()->takeoff();
Smalltalk permet de définir pour une classe un ensemble minimal d'opérations, l'utilisateur étant libre d'(essayer d')appeler une fonction non définie à la base
C++ permet de définir une interface exacte, toute erreur étant détectée dès la compilation
1.5.3. héritage multiple
C++ permet de définir des classes qui héritent de plusieurs super classes ce mécanisme est précieux car il offre une flexibilité qui ne peut être atteinte sinon
class task {…};
class printableObj {…};
class taskPrintable : public task, public printableObj{};
class mytask : public task {};
class myprintable : public printable {};
class mytaskPrintable : public taskPrintable {};
sans l'héritage multiple, deux seulement des trois choix ci dessus sont accessibles C++ ne tient aucun compte de l'ordre de l'héritage pour résoudre les ambiguïtés, et demande une désambiguation
explicite
1.5.4. un exemple d'héritage multiple
class A {void f();};
class B {void f();};
class C : public A, public B {/*f non redéfini*/};
class D : public A, public B {
void f() {
A::f(); // appel explicite de f pour la classe A;
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 14
B::f(); // appel explicite de f pour la classe B
}
};
main(){
C c;
c.f();// erreur : ambigu
c.A::f(); // OK
D d;
D.f();// ok
}
1.5.5. l'encapsulation
les fonctions opérant sur un objet sont encapsulées dans la classe. l'unité d'encapsulation est la classe, et non l'objet : un objet d'une classe possède tous les droits sur un autre objet de la
même classe en particulier, une classe C++ n'est pas un objet C++ : la classe n'est pas une entité palpable, mais une abstraction utile
seulement lors de la compilation C++ permet de restreindre l'accès aux membres des classes, données ou fonctions, sans distinguer entre les
autorisations de lecture et d'écriture pour les données membres• public donne l'accès à tout programme• private interdit l'accès à tout programme, y compris un membre d'une classe qui en hérite• protected donne l'accès aux seuls membres des (futures) sous classes• le mot clef friend permet de donner l'accès de façon explicite à (toutes les fonctions d') une classe, où à une fonction
désignée explicitement
l'accès une fois possible l'est en lecture et en écriture pour les champs
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 15
2. Fonctions et fichiers
2.1. introduction
le fichier a un rôle unique : définir la portée de fichier pour :• les fonctions static,• les fonctions inline,• les variables globales static et• les variables globales const
c’est aussi habituellement une unité de stockage et de compilation
2.2. La portée d'édition
un nom qui n’est pas local à une fonction ou à une classe doit désigner un objet du même type dans tous les fichiersd’un programme compilé en plusieurs unités
donc un nom de fonction ou de variable n'apparaît qu'une seule fois dans un programme (on entend ici programmeexécutable)
// fic1.cc
int a = 1;
int f() {/* du code ici */}
//fic2.cc
extern int a; // la variable a de fic1.cc
int f(); // la fonction f de fic1.cc
void g() { a = f();} // g définie ici appelle f défini dans fic1.cc
un objet doit être défini une seule fois dans un programme il peut être déclaré plusieurs fois, de façon identique, notamment dans différents fichiers combinés pour produire le
programme exécutable
// fic1.cc
int a = 1; // déclaration ET définition
int b = 1; // id
extern int c; // déclaration seule
//fic2.cc
int a; // erreur, signifie int a = 0; les globales sontinitialisées
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 16
extern double b; // erreur type différent
extern int c; // erreur, déclaration mais pas de définition
2.3. … portée d'édition
on peut faire empêcher la publication d'un symbole dans tout le programme par une déclaration interne expliciteutilisant static (portée de fichier)
la portée static facilite la compréhension des programmes les noms de classe doivent être uniques à tout le programme, et référer à des descriptions identiques
// fic1.cc
struct S {int a; char b;};
extern int f(S*);
//fic2.cc
struct S {char a; int b;}; // diffère de S dans fic1
int f(S*p){/*your code here*/}
// (certains compilateurs ne verront pas l’erreur)
habituellement, les définitions de classes sont décrites dans des fichiers d'en tête inclus par tous les fichiers qui lesutilisent
les fonctions inline ont une portée d’édition interne les constantes (const) aussi les typedef sont locaux à leur fichier également. Attention, en C++ typedef ne sert pas à définir un type mais à définir
un nouveau nom pour un type, ou un nom de type composite. on a la possibilité de déclarer des constantes externes explicites
extern const int a;
const int a = 1;
2.4. fichiers d' en tête “.h”
leur suffixe est habituellement .h ils sont inclus dans les fichiers sources .cc
#include “monfic.h” // cherche d'abord dans le répertoire courant puis comme <>
#include <libfic.h> // dans les répertoires d’include standard et utilisateur
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 17
2.4.1. contenu normal d'un fichier .h
définitions de types struct list{…};
patrons template <class T> class A {T f();};
déclarations de fonctions extern void g();
déclarations de fonctions inline inline max(int a,int b) {…}
déclarations de variables extern char calu;
déclarations de constantes const int MAXINT = 10000;
déclarations d'énumérations enum codes {NUL, BCD, WKL}
déclarations de noms struct list;
inclusions #include “…”
définitions de macros #define ONDEBUG(prog) {prog}
commentaires // et /* */
2.4.2. ce qu’il ne faut pas y mettre
définition de fonctions normales, void g(){…};
définition de données int maglobale; //initialisée
définition d'agrégats de constantes const tab[] = {1,3,5…};
2.4.3. le projet à fichier en tête unique
on définit un unique fichier .h pour un projet, qui permet la déclaration des éléments communs à tous les fichiers àcompiler
utile sur un petit projet que l'on veut compiler par parties
2.4.4. le projet à fichiers en tête multiples
utile lorsque des parties peuvent être utilisées séparément nécessaire sur les grands projets pour éviter que tous les programmes soient compilés avec toutes les déclarations, ce
qui ralentit la compilation on décrit souvent l’interface publique de programmation d’un fichier .c dans un .h de même nom
2.5. éditions de liens avec du code non C++
2.5.1. extern "C" {}
sert à dire au compilateur que les conventions d'édition de liens sont celle de "C" pour un ensemble de fonctions ou devariables
n'affecte pas la portée, la sémantique, ni les ordres d'appels
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 18
fortran et assembleur ont les mêmes conventions que C possibilité d'utiliser le symbole __cplusplus défini par le préprocesseur du compilateur
extern “C” char * strcpy(char *, const chat *);
ou encore
extern “C” {
char * strcpy(char *, const char *);
char * strcmp(const char *, const char *);
…
}
2.5.2. … extern "C" {}
pour utiliser un fichier de déclarations d’une bibliothèque standard C :
extern “C” {
#include <string.h>
}
pour écrire un fichier .h compatible entre C et C++ :
#ifdef __cplusplus
extern “C” {
#endif
char * strcpy(char *, const char *);
char * strcmp(const char *, const char *);
…
#ifdef __cplusplus
}
#endif
Note : la directive extern “C” spécifie la convention d’édition de liens, mais le programme compilé doit rester du C++,qui est plus rigoureux que C, notamment sur l’utilisation effective des arguments des fonctions
2.6. comment construire les bibliothèques
2.6.1. génération d'objets
CC -c prog.cc
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 19
2.6.2. construction de bibliothèques
ar rv prog1.o, prog2.o malib.a
ranlib malib.a
2.6.3. compilation d'exécutables
CC main.cc -o main malib.a
2.7. les fonctions
pas d'appel possible avant la déclaration (en C, une fonction non déclarée était sensée retourner un int).
2.7.1. déclarations
une déclaration de fonction mentionne les types de la valeur de retour et des arguments et le nom de la fonction une fonction est soit globale (comme une fonction C) soit membre d'une classe donnée. On y accËde alors avec les
opÈrateurs . et -> comme dans le cas des donnÈes membres.
class StackOfInt {
// ...
void push (int data);
int pop ();
};
// ...
main(){
StackOfInt s;
s.push(5);
(&s)->pop();
}
le passage d'argument est comparable à l’initialisation : les types sont vérifiés et les conversions nécessaires qui sontpossibles sont réalisées
les arguments peuvent être nommés lors de la déclaration : cela rend les fichiers .h plus lisibles, car le nom d'unargument éclaire souvent plus que son type sur son rôle.
on peut fournir des valeurs par défaut aux derniers arguments fournis dans la déclaration. Ces arguments sont alorsoptionnels.
int fonction (int a, char* s = "", float f=2.0);
fonction(12); // ok : fonction(12,"",2.0);
fonction(12,"aze"); // ok : fonction(12,"aze",2.0);
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 20
fonction(12,3.4); // non : les arguments doivent être renseignés dans l'ordre
fonction(12,"ert",5.6); // ok
on peut donner le type d'une fonction qui attend un nombre variable d'arguments.
int printf (char* format ...); // un char * puis autant d'arguments que voulu
2.7.2. définition de fonction
toute fonction appelée doit être définie quelque part dans le programme avant son appel une définition de fonction est une déclaration plus un corps entre accolades
int double (int a) {return 2*a;}
des arguments peuvent rester inutilisés : il suffit de ne pas les nommer pour obtenir le calme du compilateur une fonction peut être déclarée inline
inline int double (int a) {return 2*a;}
dans ce cas, le compilateur insère le code (assembleur si on veut) de la fonction à chaque appel, et sait parfois mêmefaire mieux :
inline int fact (int n) {return n<2 ? 1 : n*fact(n-1);}
int i=fact(5); // génère i=120;
un très bon compilateur doit savoir calculer la valeur de i ci dessus à la compilation
2.7.3. passage d'arguments
pour le passage d'arguments, de la mémoire est réservée dans la pile d'exécution, et les arguments réels sont initialiséspar copie
C++ permet le passage par référence, utile pour le passage de gros objets. Un argument passé par référence estcomparable à un argument VAR en Pascal. La référence s'emploie comme un paramètre copié, mais se comporte comme unpointeur.
void f(const large& arg); // const permet que l'original ne soit pas modifié
il faut utiliser const autant que possible, même pour les passages d'arguments par pointeur, lorsque la fonction ne doitpas modifier l'objet pointé
int strlen (const char *); // strlen ne modifie pas le contenu de la chaîne
un littéral, une constante, ou un argument qui demande une conversion peuvent être passés comme argument d'unefonction qui attend const& (une référence à un objet const), mais pas d'une fonction qui attend une référence non const
float fortran_sqrt(const float&);
void f(double d){
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 21
float r;
fortran_sqrt(2.0f); // réf à tmp= 2.0f (erreur si non const)
fortran_sqrt(r); // réf à r, ok dans tous les cas
fortran_sqrt(d); // référence à tmp= float(d) (erreur si non const)
}
c'est normal car l'original passé en argument référence non const est susceptible d'être modifié par la fonction appelée.Ce n'est pas possible pour la constante 2.0f ci dessus, ni pour la temporaire de conversion de "d" en float : la conversioninverse n'existe pas.
2.7.4. valeur de retour
une fonction non void doit retourner une valeur, indiquée par return une instruction de retour est considérée comme initialisant une variable du type retourné toutes les conversions licites peuvent avoir lieu dans ce cadre
double f(){
…
return 1; // converti en double(1)
}
on ne peut retourner ni une référence, ni un pointeur vers une variable locale ou une constante locale :
int * f(){
int aux;
return &aux;//erreur
}
int & f(){
int aux;
return aux;//erreur
}
int & f() {return 1;} // erreur
2.7.5. arguments de type tableaux
un tableau est passé comme pointeur vers son premier élément un argument de type T[] est converti en T* un tableau ne peut pas être passé par valeur (c'est le seul cas en C++) la taille d'un tableau est inconnue pour la fonction appelée dans le cas des tableaux multidimensionnels, le compilateur a besoin de connaître les dimensions du tableau pour
générer le code d'indexation. Une seule dimension du tableau peut être ignorée, sauf à implanter lsoi même l'indexation (sansperte de performance cela dit, voir exemple ci dessous).
void print_m34(int m[3][4]){ //cas idéal
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 22
for (int i = 0 ; i<3 ; i++)
for (int j = 0 ; j<4 ; j++)
cout <<"élément i,j :" << m[i][j]<< endl;
}
void print_mi4(int m[][4], int dim1){ //cas moins idéal mais OK
for (int i = 0 ; i<dim1 ; i++)
for (int j = 0 ; j<4 ; j++)
cout <<"élément i,j :" << m[i][j]<< endl;
}
void print_mij(int m[][], int dim1, int dim2); //impossible
void print_mij(int **m, int dim1, int dim2){ // la solution OK
for (int i = 0 ; i<dim1 ; i++)
for (int j = 0 ; j<dim2 ; j++)
cout << "elt i,j :" << ((int*)m)[i*dim2+j] << endl;
}
le code précédent est le même que celui généré par le compilateur
2.7.6. surcharge de noms de fonctions
C++ permet de donner le même nom à des fonctions qui diffèrent par leurs seuls arguments la signature d'une fonction ne prend pas en compte le type de la valeur de retour un appel de fonction donné choisit la fonction qui est dans la portée pour laquelle un ensemble de conversions existe
qui permettrait de l'appeler, est qui correspond le mieux possible aux arguments réels. la fonction choisie est celle qui décrit une correspondance meilleure que tout autre pour au moins un argument. Dans
tout autre cas, il y a ambiguïté, et une erreur de compilation
void print(double);
void print(long);
f(){
print (1L); //ok 1L est un int de type long (L)
print (1.0); //ok 1.0 est une constante de type flottant
print (1); //erreur, ambigu car les deux conversion existent
}
ou encore, de façon plus vicieuse :
void print(char *);
void print(long);
f(){
print (1L); //ok
print ("toto"); //ok
print (0); //erreur, ambigu : zéro ou pointeur nul?
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 23
}
dans ce dernier cas, noter que zéro est un int : l’ambiguïté naît ici de ce qu'il existe deux conversions possibles
2.7.7. … surcharge de noms de fonctions
les fonctions à n arguments optionnels sont considérées comme n+1 fonctions si un membre d'une classe X est explicitement appelé sur un pointeur en utilisant l'opérateur ->, cet argument
supplémentaire est supposé avoir le type const X* pour les membres const, volatile X* pour les membres volatile et X* pourles autres.
si un membre d'une classe X est explicitement appelé sur un objet en utilisant l'opérateur ., ou si la fonction estinvoquée sur le premier opérande d'un opérateur surchargé, cet argument supplémentaire est réputé avoir le type const X&pour les membres const, volatile X& pour les membres volatile et X& pour les autres.
aucune séquence de conversions contenant plus de une conversion définie par l'utilisateur, ou pouvant être raccourcien'est examinée.
la séquence de correspondance la plus courte pour construire d'un type à un autre s'appelle séquence de meilleurecorrespondance
les points de suspension correspondent à tout argument réel de n'importe quel type. Dans ce cas le compilateurn'effectue pas de conversion des arguments. Toutefois les arguments fournis dont la taille est inférieure à celle d'un int sontconvertis en int.
2.7.8. conversions triviales
Les conversions triviales ci dessous n'affectent pas laquelle de deux conversions est la meilleure.
T T&
T& T
T[] T*
T(args) T(*)(args)
T const T
T volatile T
T* const T*
T* volatile T*
2.7.9. les conversions standard
promotions d'entiers (automatique des petits entiers vers int ou unsigned suivant l'implantation) conversions d'entiers (vers unsigned, ou vers un type signé plus petit) float et double (l'un vers l'autre, avec des pertes) float et entier (perte de la partie décimale) conversions arithmétiques (pour harmoniser les types de opérandes arithmétiques, vers le type du plus grand) conversions de pointeurs : de 0 vers ptr, de prt non const ni volatile vers void*, de fonction vers void* si assez grand,
de classe vers classe de base, de tableau vers ptr, de fonction vers adresse de fonction (sauf adresse explicite ou appel) conversions de références : d'une classe vers classe de base accessible
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 24
pointeurs sur membres : d'une classe vers une classe dérivée Note : dans le cas des pointeurs sur membre, la conversion est inversée par rapport aux pointeurs normaux. Ce ne sont
pas de vrais pointeurs, et ils ne peuvent d'ailleurs pas être convertis vers void *
2.7.10. règles de priorité pour les correspondances
1 : correspondance exacteles séquences de zéro ou plus conversions triviales sont meilleures que toute autre séquence.celles qui ne convertissent pas vers const ou volatile sont meilleures que toutes les autres.
2 : correspondance avec les promotionsles séquences qui ne contiennent que des promotions d'entiers, des conversions de float en double, et des triviales sont
meilleures que toutes les autres 3 : correspondance avec les conversions standard
les séquences qui ne contiennent que des conversions standard et triviales sont meilleures que toutes les autres 4 : correspondance avec les conversions définies par l'utilisateur 5 : correspondance avec les points de suspension dans la déclaration
si deux correspondances existent au même niveau de priorité, l'appel est ambigu et rejeté
2.7.11. arguments par défaut
on peut spécifier des valeurs par défaut aux arguments d'une fonction. L'argument absent lors d'un appel effectif seraremplacé par cette valeur
ce n'est possible que pour les derniers arguments de la fonction cela doit être fait à la définition de la fonction
void print (int d , int base=10);
f(){
print (3);// print(3,10);
print (4,16);
}
on peut simuler ce mécanisme par surcharge (et l'étendre aux autres arguments)
void error (char *text, int code);
inline void error (int code) { error ("no text here", code);}
2.7.12. arguments de nombre et type inconnus à la déclaration
on utilise dans ce cas les points de suspension, qui permettent au compilateur d'accepter des argumentssupplémentaires, utilisés ou non par la fonction lors de l'appel
extern "C" int printf(const char * ...);
extern "C" int fprintf(FILE*, const char * ...);
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 25
extern "C" int execl(const char * ...);
le fichier stdarg.h décrit des macros permettant de concevoir de telles fonctions
node * buildnode(node *first ...){
va_list ap; //ap pour Argument Pointer
va_start(ap,first); //initialise
node *child; //auxiliaire
// on cree un noeud ici
for (child = first ; child != 0 ; child = va_arg(ap,node*)){
// bravo un fils on l'ajoute a son père
// on s’arrête à zéro
}
va_end(ap); //nécessaire
}
f(){
node *n=buildnode(new node(), new node(), (node*)0);
}
noter que 0 est un int, et que sizeof int est potentiellement différent de sizeof (node *), d’où le cast
2.7.13. pointeur de fonction
on peut soit appeler une fonction soit prendre son adresse
void error(char*);
void (*errpt) (char*);//une variable
typedef void (*errpttype) (char*);//un type pour ces variables
f(){
errpt = &error;
errpttype aux = &error;
(*errpt)("test");
(*aux)("test");
}
lors d'une affectation à un pointeur de fonction, le type de l'argument doit correspondre exactement, y compris pour savaleur de retour
les règles de l'appel direct s'appliquent aux pointeurs de fonction sans changement on peut définir un type pointeur de fonction pour rendre plus aisée la manipulation (cf. signal.h) ces pointeurs sont utiles pour :
• paramètrer la dynamique d'un système (actions correspondant à des menus par exemple)
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 26
• réaliser des procédures polymorphiques (comme la fonction "map" de lisp) : on passe l'adresse d'une fonction enparamètre à une autre pour effectuer un traitement global (tri d'un tableau d'objets inconnus pour lequel on mentionne unfonction de comparaison exemple de qsort dans libC)
on peut prendre l'adresse d'une fonction inline, ou d'une fonction surchargée
2.8. macros
elles sont d'un usage plus limité en C++ qu'en C, à cause de "template", "inline", et "const" elles sont parfois utiles les compilateurs et débuggers sont largués, parce que les macros génèrent du code sur une seule ligne, et le
préprocesseur est invoqué avant la compilation proprement dite
#define ma_macro du texte a qui mieux mieux
#define le texte p enrobé
donnent à l'utilisation :
ma_macro fada
-> du texte a qui mieux mieux fada
avec_param(qui suit est)
-> le texte qui suit est enrobé
partout ou C++ offre une construction équivalente il faut l'utiliser
const int MAXINT = 32768;
template <class type>
inline type max(type a, type b){return (a<b) ? b : a;}
les macros récursives sont interdites (tentation de factorielle)
2.9. … macros
lorsqu'un argument d'une macro intervient dans une expression, la macro doit le parenthèser lorsqu'un argument d'une macro est une séquence d'instructions, la macro doit le mettre entre accolades dans une macro, utiliser les commentaires /* */ à la C les mentions de noms globaux dans une macro doivent être préfixées de "::", afin de ne pas subir de masquage local
intempestif lorsque le résultat de la substitution est une séquence d'instructions, la macro doit la mettre entre accolades. Par exemple :
#define carre(a) a*a
#define fonc(prog) if (…) prog else exit(); validate();
devraient être écrites :
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 27
#define carre(a) (a)*(a)
#define fonc(prog) {if (…) {prog} else exit(); validate();}
2.9.1. étapes
le pré processeur réalise les opérations suivantes :• des caractères de passage à la ligne sont introduits pour remplacer les indicateurs de fin de ligne dépendant du système
(ainsi que toute autre traduction dépendant du système). les séquences trigraphes sont remplacées (voir ci dessous)• la paire du caractère \ suivi d'un passage à la ligne est effacée• le source est décomposé en symboles de pré traitement et séquences d'espaces. Chaque commentaire est remplacé par un
unique espace• les directives sont exécutées et les macros sont générées• les séquences d'échappement dans les chaînes sont remplacées par leur équivalent• les littéraux chaine adjacents sont concaténés• la compilation peut enfin avoir lieu
2.9.2. séquence trigraphe
les séquences suivantes sont substituées avant toute chose
??= # ??/ \ ??' ^
??( [ ??) ] ??! |
??< { ??> } ??- ~
2.9.3. opérateur #
suivi d'un paramètre de macro, les deux sont remplacés par la chaine de caractères fournie en argument réel de lamacro
2.9.4. opérateur ##
il permet de concatèner un symbole et un paramètre de macro, en supprimant les espaces cela permet de créer de nouveaux symboles
2.9.5. analyses successives
une chaine produite par une macro est réanalysée jusqu'à ce que plus aucune substitution ne soit possible une macro en cours de génération ne peut pas être générée récursivement (factorielle)
2.9.6. portée des noms de macros et #undef
une macro est active dès sa définition jusqu'à la fin du fichier obtenu par tous les #include ou jusqu'à la rencontre de#undef
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 28
un #undef abusif est ignoré
2.9.7. inclusion de fichiers
#include <nom> provoque une recherche de nom dans les répertoires d'include standard et ceux spécifiés parl’utilisateur
#include "nom" provoque la recherche de nom dans le répertoire courant, et en cas d'échec continue comme pour#include <nom>
2.9.8. compilation conditionnelle
#if expression-constante-entière
defined évalue à 1 si le symbole est défini, ou à zéro
#ifdef symb équivalent à #if defined (symb)
#ifndef symb équivalent à #if ! defined (symb)
#else
#elsif
2.9.9. contrôle des lignes
#line constante "nom_fichier"opt
cette directive modifie la valeur des macros pré définies __LINE__ et __FILE__
2.9.10. directive d'erreur
#error chaine-symbole
provoque la génération d'un message d'erreur
2.9.11. pragmas
#pragma chaine-symbole
provoque un comportement dépendant de la mise en oeuvre (voir la doc du compilateur) #pragma once fait enregistrer le nom du fichier de sorte qu'il ne sera plus réinclu dans le même programme
2.9.12. directive nulle
# n'a aucun effet
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 29
2.9.13. noms pré définis
__LINE__ __FILE__ __DATE__ __TIME__ __cplusplus __STDC__ est défini par certains compilateurs les compilateurs définissent normalement de nombreux symboles qui permettent de rendre du code dÈpendant du
systËme, de la machine, du niveau de respect du standard C++
2.10. exercices
2.10.1. exercice
écrire des déclarations de fonctions prenant des pointeurs de fonctions en arguments et retournant de tels pointeurs
2.10.2. exercice
que signifie typedef int (&rifii)(int,int);
2.10.3. exercice
écrire un programme qui affiche Hello suivi de tous les arguments de la ligne de commande
2.10.4. exercice
écrire le programme cat, qui lit les fichiers nommés sur la ligne de commande et les écrit sur sa sortie standard
2.10.5. exercice
programmer une fonction de tri "sort" prenant une fonction de comparaison en argument
2.10.6. exercice
décrire une structure de terme acceptant plusieurs fils écrire une fonction qui construise un tel terme dont les fils sont passés en arguments, de sorte que l'arbre puisse être
exprimé presque comme s'il était interprété
2.10.7. exercice
programmer itoa
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 30
2.10.8. exercice
lire les fichiers .h présents dans /usr/include et /usr/include/CC
2.10.9. exercice
écrire une fonction qui inverse u tableau à deux dimensions
2.10.10. exercice
écrire un programme de cryptage qui utilise une clef passée en paramètre dont les caractères sont combinés de façonitérative aux caractères du fichier traduit
2.10.11. exercice
écrire la fonction printf
2.10.12. exercice
définir des règles typographiques pour marquer la différence entre les différentes entités nommées par vos programmes(fonctions, pointeurs vers, variables locales, globales, etc)
2.10.13. exercice
écrire un processeur de macros qui traite des macros simples
2.10.14. exercice
écrire un programme qui appelle une fonction (sqrt par exemple) de la bibliothèque C standard (sans include <math.h>)
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 31
3. Classes
3.1. classes et membres
les classes sont des types définis par l'utilisateur aucun type de base de C++ n'est une classe
3.1.1. fonctions membres
une classe est une structure à laquelle s'appliquent un certain nombre de fonctions ces fonctions sont encapsulées dans la classe, et ne peuvent s'appliquer qu'à des objets instances de la classe des structures distinctes peuvent avoir des fonctions membres de même nom
struct individu {
char *nom;
int age;
void set (char *nom,int age);//une fonction membre
void print();//une autre
};
3.1.2. classes
le type "class" permet la restriction des appels : seules des fonctions "autorisées" pourront invoquer une fonctionmembre, ou bien agir sur un membre (lire où modifier)
cela permet une localisation plus facile des erreurs : tout état erroné est le fait des seules fonctions membres
class individu {
char *nom;
int age;
public:
void set (char *nom,int age);
void print();
};
3.1.3. auto référence
une fonction membre est invoquée sur une instance d'une classe un pointeur vers l'objet qui porte l'appel est passé implicitement en paramètre lors de l'appel on peut obtenir ce pointeur avec la variable fictive this
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 32
on ne peut pas assigner à this, mais on peut modifier l'objet pointé. Pour une classe X, le type de this dans les fonctionsmembres est :
X *const this;
utiliser this pour désigner l'objet n'est pas nécessaire : les membres d'une classe sont accessibles directement par lesfonctions membres d'un objet
class X {
int _a;
void f(int a) {_a = a;} // signifie this->_a = a;
};
3.1.4. … auto référence
quand on veut un membre ne pouvant pas modifier l'objet on mentionne const après les arguments
class X {
int _a;
public :
int get_a() const {return _a;} //ne modifie pas l'objet pointé
};
un membre const peut être invoqué sur un objet const, une fonction ordinaire ne peut pas noter que le compilateur ne détecte pas seul les tentatives de modification d'un objet const : il est essentiel de faire
figurer ce type dans la déclaration des fonctions le type de this dans un membre const de la classe X est
const X *const
en cas de besoin, il y a possibilité de coercition explicite pour passer outre ce const :
void X::f() const { ((X*)this)->compteur++;}
C'est utile pour modifier physiquement un objet dont l'état logique n'est cependant pas affecté par l'opération : parexemple pour implanter un compteur d'accès à l'objet (cas ci dessus). On distingue la constance logique de la constancephysique.
3.1.5. initialisation
dans une classe X, on peut définir des fonctions membres d'initialisation qui portent le nom X
class individu {
char * _nom;
int _age;
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 33
individu();//pour initialiser un individu
};
individu::individu(char *nom, int age):_nom(nom),_age(age){}
lors de la création d'objets d'une classe donnée, on doit fournir les argument attendus par le constructeur utilisé :
main(){
individu bob("bob",34);
individu *paulptr = new individu("paul",23);
}
un constructeur est invoqué dans les condition suivantes :• lors de la création d'une variable automatique (le cas de bob ci dessus)• lors de la création d'un objet alloué sur le tas• pour passer un paramètre réel• pour passer une valeur retournée• lors d'une conversion dans le type correspondant
3.1.6. … initialisation
on définit en général de multiples constructeurs, avec des arguments par défaut
individu::individu(char *nom="", int age=0):_nom(nom),_age(age){}
un constructeur particulier est le constructeur de copie, de type :
X::X(const X&);
la copie par défaut est une copie champ à champ (chaque champ est copié avec son propre constructeur de copie s'ilexiste
si aucun constructeur de copie n'existe, pour la classe comme pour les données membres, la copie par défaut est cellede la structure : recopie bit à bit
on peut créer un tableau d'instances d'un classe à condition que cette classe définisse un constructeur sans paramètre.On ne peut pas passer de paramètre au constructeur pour initialiser tous les objets d'un tableau
Attention : un constructeur à un seul argument peut être appelé automatiquement par le compilateur pour convertir unargument lors d'un appel de fonction. Pour l'empêcher, on utilise le mot clef explicit dans la déclaration du constructeur.
3.1.7. destruction
en général, les objets qui ont été créés doivent faire un peu de ménage quand on les jette cela se produit automatiquement à la sortie du bloc de portée pour les variables automatiques, et explicitement par
appel de delete pour les objet alloués C++ garantit l'appel automatique des destructeurs des objets créés dans ces deux situations, dans l'ordre inverse des
appels de constructeurs correspondants le destructeur pour une classe X porte le nom ~X
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 34
individu::individu(char *nom):_age(0){
_nom = new char [1+strlen(nom)];//acquisition de mémoire
strcpy(_nom,nom);
}
individu::~individu(){
delete[] _nom;//libération dans le destructeur
}
il ne prend aucun paramètre il est unique, et peut être virtuel (il doit souvent l’être) le rôle d'un destructeur est de libérer les ressources acquises lors de la construction ou de la vie de l'objet pour détecter un éventuel accès à un objet déjà détruit, un constructeur peut (mais ne doit pas) mettre les données
membres de l'objet dans un état incorrect mais reconnaissable
3.1.8. fonctions inline
les classes implantent souvent de nombreuses petites fonctions, qu'il est utile de programmer "en ligne" notamment, l'accès au données est souvent interfacé par de petites fonctions d'accès, qui permettent notamment le
mode "lecture seule" l'appel d'un fonction est normalement plus coûteux que l'accès à une donnée membre : ce n'est pas le cas des fonctions
d'accès "inline"
// file individu.h
class individu {
char * _nom;//private
int _age;//private
public:
const char *getnom(){return _nom;} //inline car dans la classe
int getage();
};
inline int individu::getage(){return _age;} //inline explicite hors classe
cela sert un objectif de performance : pas de surcoût à la définition d'une classe "propre" les fonctions membres définies au sein de la définition de la classe sont automatiquement inline elles peuvent être déclarées hors de la classe
3.2. interfaces et mise en oeuvre
brièvement ici, on peut dire qu'une bonne classe est une boite noire, prestataire de services, dont les modalitésd'utilisation ne sont pas sensibles aux modalités d'implantation. Le cours de conception orientée objet complète et raffine cesnotions.
ex : objets containers
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 35
3.2.1. modification de mise en oeuvre
modifier l'implantation d'une classe est sans effet pour ses utilisateurs tant que la partie publique reste inchangée, ainsique les déclarations des fonctions membres
les utilisateurs doivent recompiler après modification de la partie privée, notamment à cause du changement possiblede la taille de la structure, ou la transformation de certaines fonctions en virtuelles
on doit permettre que des fonctions inline puissent atteindre les champs privés d'un objet un bon compilateur peut déterminer quels fichiers doivent absolument être recompilés lors du changement d'une
interface on doit généralement garantir la compatibilité ascendante d'une bibliothèque : les programmes écrits avec une
ancienne version doivent compiler et tourner sans changement avec la nouvelle si la nouvelle implantation de fonctions (constructeurs) requiert plus de paramètres, des valeurs par défaut permettent
de garder l'ancienne version une fonction qui attend un paramètre de type X peut être transformée pour accepter un paramètre de type const X&
sans changement pour ses utilisateurs
3.2.2. un exemple de classe
programmer avec des classes demande plus de réflexion à priori qu'avant, notamment car chaque classe décrit un typede données dont il faut prévoir toutes les utilisations réalistes, et développer en permettant au minimum la réusabilité et lasécurité
class intset {
int _cursize;
int _maxsize;
int *_array;
public:
intset (int m, int n);//au moins m entiers dans 1..n
~intset();
int hasmember(int t)const;// t est il un membre
void add(int t);//ajouter t
//itération
void start(int&v)const{v=0;}
int isnotfinished(int&v)const{return v<_cursize;}
void next(int&v)const{v++;}
int getvalue(int&v)const{return _array[v];}
};
int intset::intset(int m, int n){/*alloue et initialise*/}
int intset::hasmember(int t)const{/*dichotomique ds tab trié*/
void intset::add (int t){/*insertion ds l'ordre avec décalage*/}
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 36
3.2.3. … un exemple de classe
les fonctionnalités d'itération permettent à un programme d'utiliser la classe malgré un changement de représentationinterne : on n'accède jamais aux données membres
main(){
intset is(15,15);
is.add(1);
is.add(7);
int num;
for (is.start(num) ; is.isnotfinished(num) ; is.next()){
cout << "num : "<< num <<" val : "<< is.getvalue(num);
cout << endl;
}
3.3. autres caractéristiques des classes
3.3.1. fonctions amies
dans une classe, la déclaration "friend" permet d'accorder l'accès au membres privés ou protected à des fonctionsisolées, ou à toutes les fonctions d'une classe
cela sert notamment quand une opération requiert des donnée membres de deux ou plusieurs classes : par exemplepour multiplier un vecteur par une matrice
class vector {
friend vector mult(const vector&,const matrix&);
};
class matrix {
friend vector mult(const vector&,const matrix&);
};
une fonction amie peut être elle même une méthode
class Y{
friend void X::f();
};
ou bien être n'importe quelle fonction d'une classe donnée
class X{
friend class Y;
};
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 37
une fonction amie fait partie de l'interface de programmation d'une classe
3.3.2. qualification des noms de membre
dans certaines situations on doit désigner explicitement le nom de la classe d'un membre on utilise l'opérateur de résolution de portée "::"
class X{
int m;
public:
int getm()const{return m;}
void setm (int m) {X::m=m;} //le paramètre masque le nom du membre
};
on peut aussi accéder à des symboles globaux masqués par un symbole local
class MyFile {
void open(…){
…
::open(…);//appelle la fonction globale "open"
…
}
};
3.3.3. classes emboîtées
une classe peut être déclarée dans une autre, ce qui réduit les symboles globaux en circulation
class X{
struct Y{
int i;
Y():_i(0){}
};
Y* head;
public:
X():head(0){}
};
cela sert en cas de besoin reconnu, pour des classes emboîtées simples. Le mécanisme utile (accès par la seule classeenglobante) peut être obtenu par l'utilisation de classes amies :
class Y{ friend class X;
int i;
Y():_i(0){}
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 38
};
class X{
Y* head;
public:
X():head(new Y()){}
};
Un type emboîté peut être atteint directement par qualification de portée : X::Y. Une classe imbriquée possêde le mêmestatut que tout autre classe. Un membre qui retourne une valeur d'un type emboîté se déclare ainsi :
T1::T2 T1::f();
3.3.4. membres statiques
il est parfois nécessaire de stocker des informations relatives à un classe (compter les instances, maintenir une liste desinstances créées …)
exemple : la bibliothèque iostream compte le nombre d'instances de cout créées puis détruites afin de savoir quandfermer le fichier de sortie standard
cela se fait en déclarant des membres statiques dans la classe
class obj {
static obj * listeDesObj; // liste des obj est une variable globale
};
un membre statique est une donnée globale, dont la visibilité et restreinte à la classe, sauf s'il est public une fonction membre statique n'est visible que dans le fichier qui la définit Attention : il y a nécessité de définir et initialiser les membres statiques dans le fichier .cc, car leur déclaration "static"
dans la classe n'est qu'une déclaration
3.3.5. pointeurs sur fonctions membres
on peut prendre l'adresse de membres, que ce soient des fonctions ou des données ce mécanisme nécessaire en C est moins souvent utile en C++ grâce aux fonctions virtuelles
struct cl {
char *v;
void print (int x){cout << v <<< x << endl;}
cl(char *_v):v(_v){}
};
typedef void (cl::*PMFI)(int);
int main() {
cl z1("z1");
cl z2("z2");
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 39
cl * p = &z2;
PMFI pf = &cl::print;
z1.print(1)
(z1.*pf)(2);//parenthèses obligatoires
z2->print (3);
(z2->*pf)(4);//parenthèses obligatoires
}
3.3.6. structures et unions
une structure est une classe publique par défaut (et une classe est une struct, privée par défaut)
struct S {…}; équivaut à class S {public:…};
une union nommée est une struct dont chaque membre possède la même adresse, utile dans le cas rarissime où l'on saitqu'un seul membre d'une structure est utisÈ à un moment donn. Une programme C++ avec des unions est en gÈnÈral unmauvais programme C++
union token {
char *p;
char v[8];
long i;
double d;
token(const char*);//pb : p ou v ? : décidé par programme par ex
token (long _i):i(_i){}
token (double _d):d(_d){}
};
le compilateur ne sait pas quelle est la donnée valide à un moment donné : pas de vérification on peut utiliser des constructeurs surchargés pour initialiser une union on a un problème de vérification de conformité au type : on encapsule souvent l'union dans une classe qui mémorise le
type, ne serait-ce que pendant les phases de développement et de tests, afin d'éviter de douloureuses mésaventures.
struct Token {
enum Type {Double, Long, StringPtr, StringArray};
private:
Type type;
union token {
char *p;
char v[8];
long i;
double d;
};
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 40
public:
Token(char *pp);
Token(long ii):i(ii),type(Long){}
Token(double dd):d(dd),type(Double){}
long& ival(){assert(type==Long);return i;}
long& dval(){assert(type==Double);return d;}
long& sval(){assert(type==StringPtr);return s;}
long& aval(){assert(type==StringArray);return v;}
};
de tel classes unions à type maintenu fournissent une solution élégante pour décrire des fonctions à argumentsvariables sécurisées
extern Token NOTHING;
void message(const char * pattern, Token a1=NOTHING, Token a2=NOTHING, Token
a3=NOTHING);
les constructeurs permettent alors la conversion lors de l'appel en passant des arguments normaux, qui sontréceptionnés en toute sécurité après l'appel
3.4. construction et destruction
quand une classe possède un constructeur (toujours!) il est appelé à chaque création d'objet :• automatique (variable locale, conversion dans une expression, paramètre formel)• statique (variable globale)• dynamique (alloué par new)• membre (d'une classe ou d'un tableau)• retourné par une fonction
et également lors de tout appel explicite du constructeur (locale muette)
3.4.1. variables locales
le constructeur est appelé à chaque passage du flux de contrôle sur la déclaration les destructeurs sont appelés en ordre inverse des constructeurs on a généralement un problème avec la copie
table t1(100);
table t2 = t1; //constructeur par copie
table t3(200);
t3 = t2; //opérateur d'affectation
le destructeur est appelé pour t2, mais pas le constructeur, et le t3 original pas désalloué pour éviter ces problèmes il faut définir le constructeur par copie et l'affectation
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 41
3.4.2. mémoire statique
les objets statiques et globaux sont alloués dans l'ordre de leur définitions et désalloués dans l'ordre inverse ils permettent de faire exécuter du code avant et après main un appel de exit() appelle les destructeurs de globales avant de sortir (et peut donc boucler), ce qui n'est pas le cas de
abort() parfois, il est pratique dans une librairie de définir un type ayant pour seul but d'initialiser la librairie par son
constructeur la bibliothèque iostream utilise ces mécanismes pour ouvrir et fermer les fichiers standard (cout, cin et cerr sont des
variables statiques)
3.4.3. mémoire dynamique
un objet dynamique est alloué avec l'opérateur new doit être libéré par delete. Si c'est un tableau, il doit être libéré pardelete[].
class A{};
A* ptr = new A; // pointeur vers un seul A
A* tab = new A[25]; // pointeur vers 25 A consécutifs
delete ptr; // ok
delete tab; // erreur
delete[] tab; // ok
on ne peut allouer un tableau d'instances d'une classe que si elle possède un constructeur sans argument oublier d' appeler delete perd définitivement de la mémoire appeler delete deux fois sur un même pointeur est probablement fatal
3.4.4. objets membres d'autres objets
les membres d'une classe doivent être initialisés par tous les constructeurs les arguments du constructeur de membre sont placés dans la définition, entre la partie déclaration et les accolades les membres sont initialisés dans l'ordre de leur déclaration dans la classe, et seront détruits dans l'ordre inverse
class A {
int i;
A(int ii):i(ii){}
};
class B {
int j;
A a;
B(int i, int jj);
};
B::B(int i, int jj) : a(i),j(jj){/*des acquisitions de ressources/}
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 42
3.4.5. tableaux d'objets
la déclaration d'un tableau d'objets d'une classe ayant un constructeur nécessite que la classe ait un constructeur neprenant pas de paramètre
C++ ne permet pas de passer de paramètres pour la construction de tous les objets d'un tableau on contourne cette difficulté en stockant des valeurs par défaut d'initialisation dans des variables statiques de la classe le destructeur doit être appelé sur chaque objet du tableau : il faut utiliser delete[] et non delete delete[] utilise une information de taille stockée à côté de la zone allouée
3.4.6. allocation de petits objets
l'allocateur standard peut être inefficace si l'on doit allouer et désallouer de très nombreux petits objets on peut avoir de meilleures stratégies d'allocation quand on traite des objets d'un seul type il est possible de définir des opérateurs new et delete pour une classe, appelés en priorité s'ils existent
class X{
void *operator new (size_t);
void operator delete(void*, size_t);
};
l'opérateur new global reste accessible si besoin est par ::new, sauf s'il a été lui même masqué, ce qui est trèsdésagréable
si l'on implante un allocateur pour un besoin spécifique, il est bon de pas masquer pour la classe l'opérateur newhabituel : on surcharge donc new en mentionnant un argument supplémentaire
typedef HEAP …;
class X{
void *operator new (size_t,HEAP);//spécifique
void *operator new (size_t){return ::new char[size_t];};//standard
void operator delete(void*, size_t);
};
noter que si l'on a plusieurs "new" on a un seul delete, ce qui peut poser quelques problèmes enfin, si l'on alloue des blocs de taille constante, on peut ignorer l'argument taille de new, et utiliser une version de
delete sans argument taille on peut jusqu'à doubler la vitesse d'un programme en allouant bien les petit blocs beaucoup utilisés (maillons de liste
chainée par exemple)
3.5. exercices
3.5.1. exercice
concevoir une structure Noeud d'arbre binaire avec des constructeurs et destructeurs
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 43
3.5.2. exercice
modifier la classe intset pour en faire un ensemble de Noeuds, un ensemble de chaînes
3.5.3. exercice
définir une classe pour l'analyse, la mémorisation et l'évaluation d'expressions arithmétiques entières simples (leconstructeur reçoit la chaine de caractères qui représente l'expression)
3.5.4. exercice
concevoir une classe table des symboles, et une classe "entrée de table des symboles"
3.5.5. exercice
utiliser la construction d'un objet global pour afficher du texte avant le main et après
3.6.
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 44
4. Héritage
4.1. introduction et tour d'horizon
l'héritage permet de décrire une hiérarchie entre les différents types de données manipulés par un programme. Larelation fondatrice de cette hiérarchie est la relation "est une sorte de". Cette relation logique est celle qui doit guider ladécision d'untiliser l'héritage autant que possible.
les classes héritées permettent de décrire des points communs entre différentes classes l'héritage multiple permet de combiner des caractéristiques issues de plusieurs super classes soit par ajout simple (on
parle alors de mixin - pour mixing in -) soit pour hériter d'une part d'une implémentation, d'autre part d'une interface deprogrammation. Cess notions sont détaillées dans le cours de programmation objet.
4.2. héritage : classes dérivées
Exemple si nous disposons d'une classe employé et d'une classe manager, il est probable que manager hérite deemployé. Un manager est une sorte de employé, avec certaines prérogatives.
class employé {};
class manager : public employé {};
C++ permet la conversion implicite de la classe dérivée vers la classe de base, et la conversion explicite (cast) de la classe de base vers la classe dérivée sans qu'aucune vérification ne soit possible
par le compilateur dans ce deuxième cas
4.2.1. fonctions membres
un membre privé de la classe de base ne peut pas être accédé par un membre de la classe dérivée. si on désire permettre cette possibilité, on utilise le spécificateur d'accès protected au lieu de private
4.2.2. constructeurs et destructeurs
la classe de base est vue exactement comme un membre de la classe dérivée. Le constructeur de chaque classe héritéedoit être appelé comme les constructeurs des membres
class B : public A {
int i;
B():A(),i(0){} // la construction de B requiert celle de A et de i
};
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 45
4.2.3. hiérarchies de classes
Pour savoir à quel type réel appartient un objet dont on connaît la classe de base, il y a trois solutions : garantir que l'on ne peut pointer que sur des objets d'un seul type utiliser un champ de type utiliser des fonction virtuelles
4.2.4. champs de type
une information de type peut être fournie par une classe au moyen d'une variable statique (globale) déclarée dans laclasse, et dont la valeur (ou simplement l'adresse) permet de savoir à quel classe appartient un objet.
on peut même construire automatiquement l'arbre des classes d'un programme ce qui permet de savoir si un objetappartient à une "sous classe" d'une classe donnée. (Exercice !)
4.2.5. fonctions virtuelles
une fonction virtuelle est liée dynamiquement par le compilateur : la fonction appelée sur un objet dont le type n'estpas connu avec précision (car son pointeur est manipulé au travers d'un pointeur vers une super classe) est celle quicorrespond à sa classe de création. En C++ le liage par défaut est statique : la fonction appelée est celle définie pour la classeidentifiée par le type du pointeur utilisé (elle peut bien sûr être héritée), même s'il y a eu un cast. Dans le doute, une fonctionmemebre est virtuelle.
une fonction virtuelle est marquée par le qualificateur virtual. elle peut être définie dans la classe ou elle est déclarée : la classe de base est capable de fournir une implantation par
défaut, masquée par les futures sous classes éventuellement ou alors elle est virtuelle pure, définie par 0
class Z {
virtual f()=0; // virtuelle pure
};
l'ensemble des fonctions virtuelles d'une classe définissent son protocole.
4.3. classes abstraites
il s'agit de classes qui groupent des fonctionnalités et des données utilisées par plusieurs classes dérivées, mais dontaucun objet réel ne peut être issu. Toute classe qui comporte au moins une virtuelle pure est abstraite d'office : le compilateurrefuse d'en créer des instances, même si l'on n'utilise pas les fonctions non définies.
exemple : la classe forme dans un système graphique est abstraite. On en dérive les classes concrètes carré, rond,triangle, qui pour lesquelles les fonctions dessine, efface, pivote du protocole de forme peuvent recevoir une implantation.
les classes abstraites permettent de fournir une interface sans exposer les détails de réalisation. une classe abstraite ne peut être utilisée comme type d'argument, de valeur de retour, ou comme type d'une conversion
explicite. Par contre, dans tous ces cas, on peut utiliser un pointeur vers la classe abstraite.
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 46
4.4. héritage multiple
C++ permet l'héritage multiple. Le problème de conflit potentiel lors de l'héritage de plusieurs classes qui possèdentdes membres de même signature doit être réglé par le programmeur en spécifiant explicitement quelle est la classe de lafonction appelée. On écrit A::f() ou B::f() si on parle du f de A ou du f de B
on peut définir une nouvelle fonction f() dans la classe dérivée qui appelle celle qui convient ou les deux si nÈcessaire. il reste un problème lié à la mention explicite du type de la classe de base : si on insère une classe dans la hiérarchie de
classes, on doit modifier les programmes des classes dérivées (le nom de la super classe change). Une solution est de définirun type local appelé inherited, ou superclass pour identifier la classe d'héritage principal par exemple :
class manager : public worker, public A, public Z {
typedef manager inherited;
void f(){ inherited::f()}
}
4.5. héritage virtuel
Il arrive que des classes soeurs aient besoin de partager de l'information. Cela peut être obtenu par l'héritage commund'une classe de base héritée virtuellement. Un tel exemple de partage est le nom de l'objet dans un système à objets nommés.
dans toute instance d'une classe donnée, on trouve une seule instance de chaque classe héritée virtuellement
class B : public virtual A {}
class C : public virtual A {}
class D : public C, public B {} // un seul A dans D
quand une classe hérite virtuellement, une fonction virtuelle de la classe de base peut être effectivement implantée parune classe soeur.
l'héritage virtuel est implanté au travers de pointeurs internes à l'objet et présente un cout non nul (compensé par lanon répétition)
4.6. contrôle d'accès
private : la classe seulement et les amies protected : la classe et toutes ses filles à venir et les amies public : tout le monde
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 47
4.7. mémoire dynamique
4.7.1. surcharge de new et delete
une classe de base peut redéfinir new et delete
void * operator new (size_t);
void operator delete (void *, size_t);
tant que le type réel est le type de l'objet spécifié, le compilateur connaît la taille de l'objet à détruire. dans le cas contraire, on doit utiliser un destructeur virtuel. Attention, il est toujours préférable d'utiliser des
destructeurs virtuels !!!
4.7.2. constructeurs virtuels
on a parfois besoin de fonctions de clonage de structures, pouvant être apparentées à des constructeurs virtuels (pardéfinition un constructeur ne peut pas être virtuel car on désigne le nom de la classe à instancier). On définit un système declonage ainsi :
virtual base * new_base () { return new base()};
et dans une classe dérivée :
base * new_base () {return new dérivée ()};
4.7.3. opérateur de placement
On peut vouloir allouer des objets dans un espace prédéterminé (placement explicite):
void * operator new(size_t, void *p);
char buffer[sizeof(X)];
X*p = new(buffer) X(i);
On peut vouloir placer dans un espace de stockage particulier, ce qui est utile pour faire du backtracking, ou stockerdes données en différents espaces (rémanents, partagés, etc.) :
class storage {
virtual void * alloc(size_t);
virtual void free(void *, size_t);
};
void *operator new(size_t s, storage & loc) {
return loc.alloc ( s );
}
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 48
storage maPileDobjets (1024);
X* p = new(maPileDobjets) (args);
Lors de la destruction, il faut appeler explicitement le destructeur (qui doit donc être virtuel si l'opération est faite parfree soi même), et ensuite prévenir le système de stockage:
p->~X(); maPileDobjets.free(p);
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 49
5. Déclarations et constantes
5.1. déclarations
en C++ un nom doit être déclaré la déclaration stipule le type de l'objet syntaxe : nom de type + nom de variable [ + "=" + valeur]; quand il y a valeur, c'est une déclaration et une définition. Exemples : déterminer les définitions :
char ch;
int count =1;
char * name;
struct complex {float re, im};
complex cvar;
extern complex sqrt(complex);
extern int error_number;
typedef complex point;
float real(complex *p) { return p->re;};
const double pi = 3.1415926...;
struct user;
template<class T> abs(T a) {return a<0 ? -a : a; }
enum car {peugeot, citroen, volvo};
certains types ont une valeur initiale permanente : structures, fonctions toute déclaration précisant une valeur est une définition
5.1.1. portée des symboles
une déclaration introduit un nom (symbole) dans une portée deux déclarations dans le même bloc sont impossibles une déclaration dans un bloc englobé peut masquer une déclaration plus globale cette facilité doit être utilisée avec précautions : un bon moyen est de donner des noms longs aux variables (éviter i, z,
t, x etc…) espaces de portée en C++:
bloc { … }
classe class { … } ;
fonction T f(…) { … }
fichier static
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 50
programme extern
5.1.2. … portée des symboles
opérateur de résolution de portée ::
int x;
main(){
int x= 1;
x = x + ::x;
}
il n'y a aucun moyen d'utiliser un nom local masqué la portée commence dès que le nom est connu:
int x = x;
la portée commence dès la déclaration
int x;
main(){
int y = x+1; // x global
int x = 2;
y = x+1; // x local
}
5.1.3. objets et l_values
un objet est une région de la mémoire une l_value est un objet ou une fonction en C++ les l_values const ne peuvent être affectées (placées à gauche du signe =) une l_value qui n'est pas déclarée const est dite modifiable
5.1.4. durée de vie
un objet est normalement détruit quand le flux de contrôle sort de la zone de portée sauf s'il est static dans ce cas, il est initialisé la première fois que le flux de contrôle passe par lui
int a = 1;
void f() {
int b = 1;
static int c = a;
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 51
a++, b++, c++;
cout << a << " " << b << " " << c << " " << endl;
}
main(){
while (a<4) {f();}
}
5.2. les noms
le premier caractère doit être une lettre souligné "_" est une lettre l'analyse considère le nom le plus long possible certains caractères ($) rendent les programmes non portables les mots clef ne sont pas des noms majuscules et minuscules sont distinctes
5.3. les types
tout nom est associé à un type le nom du type détermine les opérations possibles sur le symbole associé un nom de type est normalement utilisé pour spécifier le type d'un autre nom deux opérations particulières s'appliquent aux seuls noms de type :
• sizeof - pour connaître la taille d'une structure en multiples de la taille d'un char• new - pour allouer dynamiquement une instance du type
int main() {
int * p = new int;
cout << sizeof(int) << endl; // taille d'un int
cout << sizeof(p) << endl; // taille d'un pointeur
cout <<sizeof(*p) << endl; // taille d'un int
un nom de type peut aussi être utilisé pour spécifier une conversion explicite d'un type vers un autre
char *p;
long l = long(p); // convertit de pointeur vers long
5.3.1. types fondamentaux
char, short int, int, long int (signed, unsigned)
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 52
float, double, long double
tout type manquant est considéré comme int tout type est signé par défaut (sauf char) char, unsigned char et signed char sont trois types distincts, mais ont la même taille char est suffisamment grand pour contenir un caractère du jeu standard, et est signé ou non signé suivant les
caractéristiques de la machine (critère de performance) signed char est donc un entier de la même taille unsigned char est plus portable, mais peut être considérablement moins efficace int est optionnel dans les combinaisons ("unsigned" au lieu de "unsigned int")
5.3.2. tailles des types
les tailles des types respectent les règles suivantes :
sizeof(char) == 1
char < short < int < long
float < long < double
T == signed T == unsigned T
char >= 8, short >= 16, long >= 32
on est garanti qu'un char contiendra les entiers de 0 à 127 ; supposer plus est non portable donner le type unsigned n'empêche pas de copier une valeur négative : cela n'agit que sur les règles de l'arithmétique
unsigned int bitmask = -1; // warning peut être
unsigned int bitmask2 = ~0; // meilleur
5.3.3. la conversion implicite
lors de la conversion d'un type en un autre, si possible, l'information de départ est conservée dès que l'on perd des bits, on a des problèmes :
int i = 256+255;
char c = i; //c = 255
int j = c;
// j vaut 255 sur une machine ou le char est non signé (68XXX)
// j vaut -1 sur une machine où le char est signé (VAX)
5.3.4. les types dérivés
à partir de type(s) de base, on peut définir des types dérivés :
* pointeur,
& référence,
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 53
[] tableau,
() fonction,
struct structure
exemples :
int* a;
float v[10];
chat-r *p[20];
void f(int);
struct str{int a; char *s;};
les parenthèses peuvent être utilisées dans les cas où la précédence des opérateurs n'est pas celle souhaitée
int *v[10]; // tableau de 10 pointeurs vers des int
int (*p) [10]; // pointeur vers un tableau de 10 int
on peut déclarer plusieurs noms ensemble séparés par une virgule (mais il faut éviter de le faire)
int *p, q; // q n'est pas un pointeur
5.3.5. les types void et void*
type fondamental, ne peut servir que comme élément d'un type dérivé un pointeur de n'importe quel type peut être affecté à une variable de type void * utile pour les fonctions qui ne sont pas autorisées à faire d'hypothèse sur le type de la donnée qu'elles manipulent exemple de malloc :
void * malloc(size_t);
void free (void *);
main(){
int * s = (int *) malloc(10*sizeof(int));
…
free (s);
}
5.3.6. pointeurs
pour la plupart des types T, la déclaration d'un pointeur sur T est notéeT*
int * pi;
char **ps; //pointeur vers pointeur
int (*vp)[10]; //pointeur sur un tableau de 10 int
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 54
int (*f) (char, float); // pointeur sur fonction
la déréférence (ou encore indirection) est notée *V l'arithmétique des pointeurs est possible exemple : strlen version 1
int strlen(char *p){
int i = 0;
while (*p++) i++;
return i;
}
ou encore
int strlen(char *p){
char * q = p;
while (*q++);
return q-p-1;
}
5.3.7. tableaux
déclaration T[taille]; les éléments sont indexés de 0 à taille - 1; pas de virgule pour les dimensions supérieures a un
int tab[2][5]; //un tableau de deux tableaux de cinq entiers
initialisation statique
char t[]="abcdefghijkl";
char v[2][5] = {{1,2,3,4,5},{'a','b','c','d','e'}};
utilisation
main (){
for (int i=0; i<2 ; i++)
for (int j = 0; j<5 ; j++)
cout << v[i][j] << endl;
}
l'affectation à un tableau est impossible, il faut faire une copie élément par élément
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 55
5.3.8. pointeurs et tableaux
un tableau est toujours assimilable à son premier élément, et réciproquement les arguments de fonctions de type tableau sont toujours passés sous la forme d'un pointeur vers leur premier élément il n'y a aucun moyen de provoquer une copie de tableau lors d'un appel de fonction dans une structure, un tableau diffère d'un pointeur car il est intégré dans la structure (le tableau intégré dans la
structure sera donc copié lors d'une copie bit à bit de la structure) arithmétique :
• on peut soustraire deux pointeurs: le résultat est entier• on peut ajouter ou soustraire un entier à un pointeur : le résultat est un pointeur• le seul type entier qui est garanti de contenir un pointeur converti est "long"• le compilateur ne fait aucun contrôle sur la validité d'un pointeur obtenu par calcul
5.3.9. structures
on appelle structure un agrégat arbitraire d'objets de types quelconques
struct adresse {
char * nom;
int numéro;
char * rue;
int code;
char * pays;
}; // noter le point virgule final
l'accès aux membres des structures se fait par les opérateurs "•" et "->"
adresse add;
add.nom = "James Bond";
add.number = 007;
adresse *padd = &add;
cout << padd->nom << " " << padd->number << endl;
l'initialisation statique est possible comme pour les tableaux
adresse A={"Pernod",45,"rue des vagues", 13004, "Marseille"};
les comparaisons == et != sont non définies (mais définissables par le programmeur) l'affectation d'une structure à une autre fait une copie bit à bit
5.3.10. … structures
le nom d'un type est utilisable dès qu'il a été vu, pourvu que son emploi ne nécessite pas de connaître la taille de lastructure
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 56
struct maillon {
maillon *suivant;
…
};
et encore :
class S;
extern S a;
S f();
void g(S);
par contre :
void h(){
S a; //erreur S inconnu
f(); //id
g(a);//id
}
on ne peut déclarer une instance d'un type structure avant que la définition du type n'ait été vue entièrement
struct mauvaise {
mauvaise elem; // refusé
};
5.3.11. structures
la gestion des références croisées (list de links qui pointent sur la liste) se fait par des déclarations préalables :
struct link; //déclaration préalable
struct list {
link *l;
};
struct link {
link *next;
list *pere
};
5.3.12. équivalences de types
deux types sont différents mêmes s'ils ont les mêmes membres et ne sont pas interchangeables
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 57
struct s1 {int a;};
struct s2 {int a;};
s1 x;
s2 y = x; //impossible
int i =x; //impossible
on utilise typedef pour définir de nouveaux (noms de) types à partir d'existants
typedef char * String;
String p1, p2;
char * q1,q2; //attention ici q2 est char !
5.3.13. références
une référence est un nom alternatif à un objet : les opérateurs agissent sur l'objet référencé au travers de la référence
int i = 0;
int& j = i;
j++; //i vaut maintenant 1
l'initialiseur d'une référence doit donc être une l_value (sauf cas particulier ci après) les références servent essentiellement pour
• le passage d'arguments par adresse• les valeurs de retour de fonction• les définitions d'opérateurs
une référence est un alias : on ne peut prendre une double référence
int i = 0;
int& j = i;
int&& k = j; // impossible
5.3.14. … références
une référence est un pointeur masqué (constant) le type de la référence et de l'objet référencé doivent être strictement compatibles on peut prendre une référence à une constante ; dans ce cas, l’initialiseur peut ne pas être une l_value, ni même être du
type demandé : des convertisseurs sont appelés automatiquement pour initialiser une variable temporaire
double& cd1=1; //impossible
const double& cdr = 1; //ok
les références permettent l'utilisation d'un appel de fonction en l_value (à gauche du signe =)
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 58
struct assoc {
char *nom;
int value;
};
int&value(char*nom){
assoc *courant;
… //recherche de nom
return courant->value;
}
main(){
char buf[80];
while (cin>>buf) {
value(buf)++;
}
}
5.3.15. … références
les références permettent de faire évoluer une interface fonctionnelle qui copie des structures (petites) vers uneinterface qui copie des pointeurs sans modifier les programmes qui utilisent cette interface
struct data {…};
//version 1
data f(){}
main(){
data res=f();
}
//version 2
const data& f(){}
main(){
data res=f(); // cet appel ne change pas
}
5.4. les littéraux
5.4.1. constantes entières
décimales :0, 123, 213546887778984
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 59
octales (commencent par un zéro) : 077, 0732, hexadécimales (commencent par un zéro et un x) : 0x1a6f les suffixes U et L identifient des constantes non signées ou long : 3U, 3L
5.4.2. constantes virgule flottante
type double par défaut 1.3, .23, 1.2e-15, le suffixe f identifie les constantes de type float : 1.56e4f
5.4.3. constantes caractères
'a', '\n', '\a', '\0' ce sont des constantes symboliques pour les valeurs entières du jeu de caractère de la machine caractères spéciaux:
passage à la ligne NL(LF) \n
tabulation horizontale HT \t
tabulation verticale VT \v
effacement BS \b
retour chariot CR \r
saut de page FF \f
alerte BEL \a
anti slash \ \\
caractère ? \?
caractère ' \'
caractère " \"
caractère valant 0 NUL \0
caractère en octal ooo \ooo (1,2,3 chiffres)
caractère en hexa hhh \xhhh (autant que l'on veut)
utiliser des constantes explicites (octales ou hexa) rend les programmes non portables entre machines ayant des jeux decaractères différents
une constante octale ou hexa se termine au premier caractère non octal ou non hexa
char *s="voici un caractère idiot \x8E345AB inséré dans une chaîne";
5.4.4. littéraux chaînes
texte entre guillemets, caractère nul terminal sizeof donne la longueur de la chaîne + 1 strlen donne la longueur de la chaîne le passage a la ligne dans une chaîne est interdit
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 60
la concaténation est automatique il est conseillé de toujours utiliser trois chiffres pour les caractères en octal
s= "cette chaine provoque
une erreur";
char s[]= "ca fait " "bien plaisir "
"par rapport a C";
char s[]= "parlez après le bip\007\n";
char s[]= "la fin disparaît\000imbecile";
char s[]= "a\xOfah\0129"; //'a' '\xfa' 'h' '\12' '9'
char s[]= "a\xfah\129"; //'a' '\xfa' 'h' '\12' '9'
char s[]= "a\xfad\127"; //'a' '\xfad' '\127'
5.4.5. zéro
c'est un int utilisable comme constante de tout type entier, pointeur, ou flottant.
void f(int);
void f(char *);
main(){
f(0); // le compilateur refuse cet appel : ambigu
f(int(0)); //ok
f((int)0); //ok, coercition a la C
f((char *)0);//ok
}
généralement, quand c'est possible, il correspond à un mot dont les bits sont tous a zéro
unsigned bitmask = ~0; // bits tous a un
C++ recommande d'utiliser 0 pour désigner le pointeur nul plutôt que la macro NULL habituellement définie dans desfichiers include de l'utilisateur ou des bibliothèques
5.5. les constantes nommées
5.5.1. le mot clef const
le mot clef const ajouté a un type permet de transformer l'objet correspondant en constante plutôt qu'en variable une donnée const ne peut pas être modifiée dans tout l'espace de sa portée une constante doit être initialisée dès sa déclaration
const int tailleTab =4;
const int tab[]={1,2,3,4};
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 61
const est un modifieur de type : il décrit comment une donnée doit être utilisée
const char * peek(int i) {
return tab[i]; //l'appelant ne peut pas changer tab[i]
}
void f(const char *);
void f(char *); //une autre fonction f
5.5.2. … le mot clef const
utilisations de const :
const char *s1="123456789";
s1++; //ok
s1[3]='e'; //interdit
char *const s2="123456789";
s2++; //interdit
s2[3]='e'; //ok
const char *const s3="123456789";
s3++; //interdit
s3[3]='e'; //interdit
void f1(const char *); //appel possible sur s1, s2, s3
void f2(char *); //appel possible sur s2
void f3(const char *&); //appel possible sur s1 et s2
void f4(char *&); //appel impossible dans tous les cas
5.5.3. énumérations
pour déclarer des constantes, on peut également utiliser enum
enum {ASM, AUTO, BREAK}; // déclare trois valeurs
forme équivalente :
const int ASM =0;
const int AUTO =1;
const int BREAK =2;
une énumération peut être nommée. Attention, il ne faut pas utiliser la syntaxe "typedef enum" comme en C.
enum motclef {ASM, AUTO, BREAK}; // motclef devient un type
on a la possibilité de valeurs explicites
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 62
enum data {a=123,b=-78,c=0};
la conversion d'enum vers int est automatique mais la conversion inverse doit être rendue explicite, ce qui rend souventles énumérés assez désagréables à utiliser
int i = ASM; // ok
motclef var1 = 2; // rejeté
int j = 1;
motclef var = motclef(i); ok
les enumérations sont parfois meilleures que les int correspondants : elles permettent au compilateur de générer desmessages si l'un des cas est oublié dans un switch.
5.6. les champs de bits et unions
objectif : gain d'espace mémoire. C'est du HACK rarement utilisé sauf dans des programmes de communication.L'accès à un bit est d'ailleurs plus cher que l'accès à un int.
on peut obtenir de la place en mettant plusieurs objets de petite taille dans un octet, ou en utilisant le même espace àdes fins différentes suivant les cas
c'est rarement immédiatement portable
5.6.1. champs de bits
struct registre {
unsigned int enable :1; //un bit
unsigned int error : 2; //deux bits
unsigned int :2; //deux bits inutilisés
unsigned int mode : 3;
…
};
gain hypothétique (la taille du programme et le coût des accès augmente) utilisés surtout pour accéder de façon nommée à des parties d' octet
5.6.2. unions
on veut utiliser la même structure pour stocker des données différentes suivant les moments exemple : table des symboles, dont une entrée contient nom et valeur, de type entier ou chaîne.
struct entrée {
char type;
char *nom;
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 63
union {char* valeurChaine; int valeurInt;};
};
sur certaines machines, int et pointeur n'auront pas la même taille : ce n'est pas une simple conversion certains programmeurs les utilisent pour convertir sans que le compilateur ne rechigne (c'est vilain : mauvaise
utilisation du concept)
struct cast {
union baduse{ //on peut la nommer
int i;
int *p;
};
};
main(){
cast c;
c.i = 12;
int *p=c.p; //a vomir
}
5.7. exercices
5.7.1. exercice
écrire un programme qui affiche les tailles de tous les types fondamentaux, et de types pointeurs vers, tableaux etstructures qui en dérivent.
5.7.2. exercice
écrire un programme qui affiche sous forme d'entiers les adresses relatives de champs de structures
5.7.3. exercice
écrire une fonction qui permute deux entiers
5.7.4. exercice
afficher la configuration de bits du pointeur 0 sur votre machine
5.7.5. exercice
afficher tous les caractères utiles et leurs valeurs comme entiers
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 64
5.7.6. exercice
définir un fichier de définitions pour des types de base de taille connue avec exactitude. (pour la portabilité).
5.7.7. exercice
écrire des déclarations pour• un pointeur sur un caractère• un tableau de 10 entiers• un pointeur sur un tableau de chaînes de caractères• un pointeur sur un pointeur de caractère• une constante entière• un pointeur sur une constante entière• un pointeur constant sur une constante de type entierécrire les initialisations et compiler
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 65
6. Expressions et instructions
6.1. sommaire des opérateurs
6.1.1. opérateurs
voici un tableau synoptique des opérateurs du langage C++, avec les conventions suivantes :• les opérateurs sont présentés par ordre de précédence décroissant• dans la même boite, la précédence est la même• objet est une expression donnant une instance d'une classe et l_value est une expression représentant un objet non
constant
:: résolution de portée classe::membre
:: global ::nom
. sélection de membre objet.membre
-> sélection de membre objet->membre
[] indexation pointeur[expression]
() appel de fonction expression(liste
d’expression)
() construction de valeur type(liste d’expression)
sizeof taille d'un objet sizeof expression
sizeof taille d'un type sizeof (type)
6.1.2. opérateurs
++ pré et post incrémentation ++lvalue et lvalue++
-- pré et post décrémentation --l_value et l_value--
~ complément à 2 ~expr
! non !expression
-,+ moins et plus unaires -expression, +expr
& adresse de &lvalue
* déréférence *pointeur
new créer new type
delete détruire delete pointeur
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 66
delete[] détruire tableau delete[] pointeur
() coercition (conversion) (type) expression
.* section de membre objet.ptr_de_membre
->* section de membre ptr->ptr_de_membre
*, /, % fois, divise, modulo expression op expression
+, - plus, moins expression op expression
<<, >> décalage expression op expression
<, <=, >, >= comparaisons expression op expression
==, != égal différent expression op expression
& et bit à bit expression op expression
6.1.3. opérateurs
^ ou exclusif bit à bit expression op expression
| ou bit à bit expression op expression
&& et logique expression op expression
|| ou logique expression op expression
? : expression conditionnelle expression ? expression :
expression
= affectation simple l_value = expression
op= op et affectation l_value op= expression
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 67
(op peut valoir +,-,*,/,%,<<,>>,&,|,^)
, virgule (séquence) expr,expr
6.1.4. associativité
les opérateurs unaires et d'affectation sont associatifs à droite tous les autres associent à gauche, y compris les opérateurs postfixés
a=b=c signifie (a=(b=c))
*p++ signifie (*(p++))
a<b<c signifie ((a<b)<c) et non (a<b && b<c)
6.1.5. parenthèses
les parenthèses permettent de contrôler l'associativité, et l'ordre d'évaluation en partie du fait de la précédence desopérateurs
6.1.6. ordre d'évaluation
il n'est pas garanti sauf pour les opérateurs &&, ||, "," qui évaluent d'abord la gauche
int i=1;
v[i]=i++; //v[1]=1 ou v[2]=1;
i=v[i++]; //valeur de i indéfinie
les parenthèses peuvent imposer un ordre d'évaluation
x*y/z évalue en (x*y)/z
(x*y)/z diffère en général de x*(y/z) (virgule flottante)
6.1.7. expressions primaires
primaire :
littéral
this
:: identificateur
:: operator operateur
:: nom_classe_qualifié::nom (permet A::B::C::size)
( expression )
nom
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 68
nom:
identificateur
operator operateur
nom_fonction_conversion
~nom_classe
nom_classe_qualifié::nom
6.1.8. indexation
a[b] est identique par définition à *((a)+(b)) ce n'est plus vrai pour l'opérateur [] quand il est défini pour un type utilisateur évidement
6.1.9. appel de fonction
avant l'appel, chaque argument formel est initialisé avec l'argument réel la fonction peut changer la valeur de ses arguments formels non constants les arguments réels sont intouchés sauf références non const l'ordre d'évaluation des arguments est non défini les appels récursifs sont permis un appel de fonction dont le type retourné est une référence non const est une l_value une fonction peut être déclarée pour accepter moins d'arguments réels (valeurs par défaut) ou plus (notation •••) dans le cas ••• (pas d'argument formel disponible)
• un argument float est converti en double• un char, short, champ de bit ou enum est converti en int ou unsigned int par promotion d'entiers (int de préférence si la
valeur passée peut être décrite par un int)• un objet de classe est passé comme une struct (sinon un appel au constructeur de copie est réalisé)
6.1.10. conversion de type explicite type(liste_expr)
construit une valeur du type à partir des arguments s'il y a plusieurs paramètres, le type doit être une classe avec un constructeur disponible et non ambigu pour ces
arguments réels s'il n'y a aucun argument, soit un constructeur existe, soit le résultat est une donnée de valeur indéfinie pour le type
void f(int){…}
main(){
f(int());//passe un int arbitraire
}
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 69
6.1.11. accès à un membre • et ->
l'expression est une l_value ssi le membre en est une
a->nom équivaut strictement à (*a).nom
6.1.12. opérateurs d'incrémentation et de décrémentation
leur effet sur les pointeurs est celui de l'arithmétique des pointeurs (qu'ils soient préfixes ou postfixes)
char * strcpy (char *p, const char *q) {
while(*p++ = *q++);
return p;
}
6.1.13. sizeof
sizeof(char) == 1
une taille d'instance est toujours strictement plus grande que 0 sizeof ne peut pas être appliqué à une fonction, un champ de bits, une classe indéfinie, à void, ou à un tableau ayant
une dimension non spécifiée si l'opérande est une expression, elle n'est pas évaluée si c'est un type, il doit être entre parenthèses appliqué à une référence, c'est la taille de l'objet référencé appliqué à un tableau, c'est la taille du tableau (nombre total d'octets)
6.1.14. l'opérateur new
new essaye de créer un objet du type demandé, et retourne un pointeur sur cet objet si l'objet est un tableau, un pointeur sur le premier élément est retourné
new int; //type int *
new int[20]; //type int *
new int[i][50]; //type int(*)[10]
dans ce cas la première dimension peut être variable, les autres constantes on peut définir des allocateurs supplémentaires, ou surcharger l'opérateur new par défaut
new (a,b,c) T; //appel de operator new(sizeof(T),a,b,c)
void *operator new(size_t) peut être appelé avec l'argument 0 : un pointeur vers un objet toujours différent est retourné chaque classe peut définir un opérateur new pour ses instances
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 70
class T;
new T; //appel de void *T::operator new(size_t) s'il existe
new U; //appel de void *operator new(size_t) si U non classe
::new V; //appel de void *operator new(size_t) global
6.1.15. … l'opérateur new
un appel à new combine allocation et appel de constructeur
new T(a,b,c); //appel de T::T(a,b,c) sur l'espace alloué
la même syntaxe est utilisable pour les types de base:
int * ipt=new int(200);
on ne peut pas passer de paramètre au constructeur pour les tableaux un tableau ne peut être alloué que si la classe décrit un constructeur par défaut (sans paramètre) qui sera alors appelé
pour chaque instance du tableau
new T[10](a,b);//interdit
on peut utiliser explicitement la version parenthèsée de new si on a un problème à cause de parenthèses dans le type :
new(int (*[15])(char)); //tableau de 15 pointeurs vers fonctions
6.1.16. l'opérateur delete
delete doit recevoir un pointeur sur un objet construit par new delete(0) ne fait rien pour une classe T, void T::operator delete (void *) est appelé prioritairement s'il a été défini la valeur de l'objet pointé est indéfinie après la destruction delete appelle le destructeur de l'objet (qui doit être virtuel) delete[] d'un pointeur simple est terrifiant delete simple d'un pointeur vers un tableau est abominable
6.1.17. conversion de type explicite (coercition)
toute conversion standard est possible de façon explicite la notation coercition (cast C) est nécessaire pour convertir vers un type dont le nom est complexe un pointeur peut être converti en entier assez grand, et réciproquement un pointeur sur un type peut être converti en un pointeur sur un autre type : problèmes si incompatibilités d'alignement si l'autre type est de taille supérieure ou égale, l'aller retour de conversions ne produit pas de changement
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 71
un pointeur vers B peut être converti en pointeur vers D qui hérite de B si B n'est pas classe de base virtuelle (supposeque B apparaît comme sous objet dans D : les valeurs de ces deux pointeurs sont normalement différentes)
le pointeur nul est toujours converti en lui même un objet peut être converti en référence X& si un pointeur sur cet objet peut être converti en X* la conversion vers une référence ne provoque pas d'appel de fonction de coercition, ni de constructeur le résultat d'une coercition vers une référence est une l_value (le seul cas)
6.1.18. … conversion de type explicite (coercition)
on peut convertir des pointeurs vers fonctions en pointeurs vers objets et réciproquement à condition que les taillessoient compatibles (danger à l'utilisation)
un pointeur vers fonction peut être converti en pointeur vers une autre fonction : l'appel à travers ce nouveau pointeur ades effets imprévisibles
un objet ou une valeur peut être converti en objet d'une classe seulement si un constructeur ou un opérateur a étédéclaré
un pointeur sur membre peut être converti en un pointeur sur membre différent quand :• les deux pointent sur des membres de la même classe ou• les deux pointent sur des membres de classes dont l'une dérive de l'autre sans ambiguïté
on peut convertir un type constant (objet, référence d'une part ou pointeur) vers un type non constant (référence oupointeur seulement) : on obtient le même pointeur ou objet. Toutefois son utilisation pour modifier l'objet peut provoquer uneexception d'adressage (dépend de la mise en oeuvre)
on peut convertir de volatile vers non volatile
6.1.19. opérateurs pointeurs sur membres
•* lie son second argument, de type pointeur sur un membre de T, à son premier argument, de type T ou d'une classedont T est classe de base accessible
id pour ->* avec un pointeur le résultat est un objet ou une fonction si c'est une fonction, il peut être utilisé pour un appel
class A{
int f1(void *);
int f2(void *);
};
typedef int (A::*Afptr)(void *);
main(){
Afptr pt = A::f1;
A a;
(a.*pt)(0);
}
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 72
6.1.20. opérateurs de multiplication
opérandes de * et / de type arithmétique opérandes de % (reste) de type entier a / 0 et a % 0 ont une valeur indéfinie (a/b)*b + a%b vaut a
6.1.21. opérateurs d'addition
arithmétique classique et arithmétique des pointeurs :• addition pointeur + entier et entier + pointeur• soustraction pointeur - entier• soustraction pointeur - pointeur (valable au sein d'un tableau, jusqu'à la première position au bout du tableau, sinon
indéfini
6.1.22. opérateurs de décalage
le décalage vers la gauche s'accompagne d'un remplissage par des 0 à droite le décalage vers la droite est garanti remplir à gauche par des 0 si le nombre à décaler est de type non signé, ou bien
s'il est positif. Sinon danger le résultat est indéfini si l'opérande droit est plus grand que le nombre de bits de l'opérande gauche une fois promu
unsigned int bit1mask = 1U<<1;
unsigned int bit2mask = 1U<<2;
6.1.23. opérateurs relationnels < > <= >=, et ==, !=
les arguments doivent être de type arithmétique ou pointeur le résultat est de type int, valant 0 ou 1 tout pointeur peut être comparé à (une expression constante évaluant à) 0 tout pointeur peut être comparé à un void * des pointeurs sur des objets ou des fonctions de même type peuvent être comparés si deux pointeurs sur des membres non statiques d'un même objet sont comparés, et s'ils ne sont pas séparés par un
label spécificateur d'accès, le pointeur sur celui déclaré en dernier est plus grand que l'autre deux pointeurs sur des membres données d'une même union sont égaux deux pointeurs sur un même objet sont égaux dans un tableau, le pointeur sur l'objet de plus grand index est le plus grand les opérateurs == et != sont identiques mais de précédence plus faible des pointeurs sur membres de même type peuvent être comparés
6.1.24. opérateurs logiques bit à bit
ces opérateurs ne s'appliquent que sur des arguments de type entier le résultat est la fonction logique bit à bit des arguments
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 73
ils servent notamment pour réaliser des masques 2 4, 3 & 2
6.1.25. opérateurs logiques && et ||
ils associent et ils évaluent de gauche à droite ils n'évaluent l'argument droit que si c'est nécessaire les opérandes ne sont pas nécessairement de même type mais doivent être de type arithmétique ou pointeur
6.1.26. opérateur conditionnel a ? b : c
il associe et il évalue de gauche à droite (seulement l'un des deux derniers) le type de l'expression (et du résultat) est un type commun aux opérandes 2 et 3, s'il existe, après conversions possibles si les arguments 2 et 3 sont des l_values, le résultat aussi
6.1.27. affectation =, += etc…
l'opérande gauche doit être une l_value modifiable, qui donne son type au résultat et à l'expression l'opérande droit doit pouvoir être converti dans le type attendu à gauche avant affectation (pas les énumérations)
T* = T*const //OK
T*const = T* // impossible
ptr [sur membre] = ptr [sur membre] //OK
ptr [sur membre] = 0; //OK
T, volatile T = const T, volatile T; //OK
class X = … ; // défini par X& X::operator=(const X&);
class X = … ; // sinon affectation membre à membre
class X = class Y; // OK ssi Y hérite de X de façon non ambiguë
T& = …; // affecte à l'objet référé
X op= Y est identique à X = X op (Y), sauf que X n'est évalué qu'une seule fois
6.1.28. opérateur d'enchaînement (virgule)
virgule associe et évalue de gauche à droite, et la valeur de l'expression gauche est abandonnée le type est celui de l'expression de droite
6.1.29. expressions constantes
elles sont nécessaires dans certaines situations : case, bornes de tableaux, longueurs de champs de bits, initialiseurs detypes énumérés
elles sont connues et évaluées à la compilation, remplacées par leur valeur elles consistent en :
• des littéraux• des expressions sizeof• des énumérés
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 74
• des valeurs const de types entiers initialisées par des expressions constantes
6.1.30. à propos de conversion de types
la notation C habituelle pour la coercition est possible : int i = (int) 3.2; on utilise la notation fonctionnelle pour les types ayant un nom simple la sélection de membre a une précédence plus forte que la coercition, ce qui rend la notation fonctionnelle plus
agréable :
((complex)n).re; //cast
complex(n).re; //fonction
quand un cast explicite n'est pas nécessaire, il faut l'enlever la conversion explicite de pointeur vers int, ou de pointeur vers un autre pointeur est non portable (char * peut différer
de int *, et de int) seules sont garanties les conversions de pointeur vers void * et retour (on peut donc passer par void *)
6.1.31. à propos de mémoire dynamique
new, delete, delete [] pour les tableaux l'opérateur delete connaît la taille de l'objet devant être désalloué par le compilateur, sauf dans le cas des tableaux.
#include <stddef.h>
void * operator new(size_t);
void operator delete (void *);
void out_of_memory(void){
cerr << "plus de memoire" << endl;
exit (1);
}
int main(){
set_new_handler(&out_of_memory);
char *p=new char[10000000000000000000000];
cout << "cette trace n'a aucune chance" << endl;
}
pas d'initialisation automatique à zéro new retourne 0 en cas d'impossibilité d'allocation, mais termine sans erreur
6.2. Syntaxe des instructioNS
instruction :
déclaration
{ liste d'instructions }
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 75
expression ;
if ( expression ) instruction
if ( expression ) instruction else instruction
switch (expression) instruction
while (expression) instruction
do instruction while (expression)
for (instruction_for expression ; expression ) instruction
case expression-constante : instruction
default : instruction
break;
continue;
return expression ;
goto identificateur ;
indentificateur : instruction
instruction for :
déclaration
expression
6.2.1. sélection et contrôle
C++ n’enrichit pas les instructions C : if, switch, case, while, do while, for, goto, label il n’y a pas de type booléen pour les tests : la valeur 0 signifie false, et toute autre valeur signifie true les opérateurs <=, < etc. renvoient 0 si faux, et 1 sinon toute valeur entière ou pointeur peut donc être utilisée dans un test
6.2.2. if
l'utilisation de if se fait comme en C. Certains tests peuvent être supprimés en utilisant l'opÈrateur de sÈlectionconditionnelle (?:), même en l_value, ce qu'ignorent beaucoup de programmeurs :
if (a<b) {
max = b ;
b_used = 1;
} else {
max = a;
a_used = 1;
}
devient :
max = (a<b) ? b : a;
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 76
((a<b) ? b_used : a_used) = 1;
6.2.3. switch
switch est plus performant que if lorsqu'une expression est comparée à des constantes le compilateur peut informer sur des oublis lorsque l’on utilise des types énumérés
6.2.4. goto
goto est utile dans des programmes générés automatiquement (analyseurs syntaxiques) utile dans des cas particuliers de besoin en performance dans une boucle temps réel par exemple également et surtout pour sortir d’une boucle de façon brutale lors en économisant des tests
void f(int a){
int i, j;
for (i=0 ; i<n ; i++)
for (j=0 ; j<n ; j++)
if (nm[i][j] == a) goto found;
// not found
// ... return
found:
// found
}
6.3. commentaires et indentation
un commentaire décrit le programme : sensé, correct, complet, (à jour) chaque fois que possible, un commentaire doit être décrit par le langage notamment, un assert est préférable à tout commentaire l’indentation peut être prise en charge automatiquement par un indenteur
6.4. exercices
6.4.1. exercice
écrire l’instruction while équivalente à l’instruction for suivante :
for (i=0 ; i<maxlen ; i++)
if (input[i] == ‘?’) quest++;
utiliser un pointeur comme variable de contrôle
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 77
6.4.2. exercice
parenthèser les expressions suivantes :
a = b + c * d << 2 & 8
a & 077 != 3
a == b || a == c && c < 5
c = x != 0
0 <= i < 7
f(1,2) + 3
a = -1 + + b -- - 5
a = b == c++
a = b= c =0
a[4][2] *= * b ? c : * d * 2
a-b,c=d
6.4.3. exercice
trouvez 5 constructions C++ différentes dont la signification est indéfinietrouvez dix exemples de code C++ non portableque se passe t’il lors d’une division par zéro sur votre systèmeque se passe t’il lors de dépassement de capacité par ajout excessif, retrait excessif
6.4.4. exercice
parenthèser correctement les expressions suivantes :
*p++
*--p
++a--
(int*)p->m
*p.m
*a[i]
6.4.5. exercice
écrire strlen, strcpy, strcmp
6.4.6. exercice
observer les réactions du compilateur aux erreurs suivantes
void f(int a, int b) {
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 78
if (a = 3) // ...
if (a&077 == 0) //...
a := b+1;
imaginez des erreurs plus simples et voyez comment le compilateur réagit
6.4.7. exercice
écrire la fonction cat qui concatène deux chaînesécrire reverse, une fonction qui renverse une chaine
6.4.8. exercice
écrire un programme qui élimine les commentaires C++ dans un programmeattention aux chaînes de caractères
6.4.9. exercice
que fait le programme suivant , et pourquoi l’écrire :
void send (register*to, register *from, register count){
register n=(count+7)/8;
switch (count%8){
case 0 : do { *to++ = *from++;
case 7: *to++ = *from++;
case 6: *to++ = *from++;
case 5: *to++ = *from++;
case 4: *to++ = *from++;
case 3: *to++ = *from++;
case 2: *to++ = *from++;
case 1: *to++ = *from++;
} while (--n>0);
}
}
6.4.10. exercice
écrire la fonction atoi qui prend une chaîne de caractères et retourne un int• octal et hexadécimal• constantes caractèresécrire la fonction inverse itoa
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 79
7. Surcharge d'opérateur
7.1. introduction
C++ permet de définir des opérateurs pour toutes les classes définies par un programmeur Exemples : le type complexe, pour lequel on définit a + b, le type vecteur, pour lequel on définit l’opérateur []
7.2. fonctions opérateurs
+ - * / % ^^& | ˜ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- ->* , -> [] () new delete Attention : il est impossible de changer la précédence des opérateurs, ni leur sens d'appariement. "=" associe de droite
à gauche, alors que "<<" associe de gauche à droite. Ce comportement est figé. Attention : il est impossible de changer la syntaxe des expressions (nouveaux opérateurs, ou bien modification de
binaire en unaire par exemple) le nom d’une fonction opérateur est operator suivi du symbole adéquat, avec ou sans espace
operator ==
7.2.1. opérateurs binaires et unaires
binaire : définissable comme fonction membre à un argument, ou comme fonction globale à deux arguments unaire : définissable comme fonction membre sans argument, ou comme fonction globale à un argument a@b s’interprète soit comme a.operator@(b), soit comme operator@(a,b) @a s’interprète soit comme a.operator@(), soit comme operator@(a) Lorsque les deux sont définis, le choix est fait avec les règles habituelles a@ (postfixe) s’interprète par a.operator(int) ou par operator@(a,int)
7.2.2. spécification pré définie des opérateurs
=, [], () et -> doivent être des fonctions membres non statiques. += n’est pas déduit automatiquement de + et de = =, &, et , (enchaînement) ont une valeur pré définie. Ils peuvent être rendus inaccessibles en les déclarant privés dans
la classe.
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 80
7.2.3. opérateurs et types utilisateur
Une fonction opérateur doit soit être membre d’une classe utilisateur, soit prendre une classe utilisateur en paramètre. C++ est donc extensible, mais pas modifiable, sauf pour les opérateurs pré définis par défaut : =, & et ,. La commutativité d’un opérateur n’est pas connue du compilateur. On doit déclarer deux fonctions pour l’obtenir.
7.3. conversions de types définis par le programmeur
On ne doit normalement pas définir autant d’opérateurs amis que le nécessitent des conversions automatiques voulues,gr‡ce aux opÈrateurs de conversions de types
class complex {
double re, im;
public :
complex(double r, double i) re(r), im(i) {};
friend complex operator+(complex,complex);
friend complex operator+(double,complex); // inutile
friend complex operator+(complex,double); // inutile itou
...
}
7.3.1. conversion par constructeurs
class complex {
double re, im;
public :
complex(double r, double i = 0) re(r), im(i) {};
friend complex operator+(complex,complex); // suffit maintenant
...
}
7.3.2. un principe de mise en oeuvre
On définit en général les opérateurs de type *= en premier et on en dérive *:
matrix& matrix::operator*=(const matrix& a)
{
//..
return *this;
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 81
}
matrix matrix::operator*(const matrix& a, const matrix& b)
{
matrix prod = a;
prod *=b;
return prod;
}
Une conversion utilisateur est automatiquement appliquée si elle est unique. Tout objet construit par l’appel explicite d’un constructeur est automatique et sera détruit dès que nécessaire.
7.3.3. opérateurs de conversion
La conversion par constructeur est limitée : pas de conversion vers les types de base pas possible de convertir un type nouveau en un type ancien sans modifier l’ancien pas possible d’avoir un constructeur à un seul argument sans avoir aussi de facto une conversion (toutefois le mot clef
explicit avant le constructeur permet de bloquer ce type de conversion automatique)
class tiny {
char v;
void assign (int i) { assert (! (i & ˜63)); v = i;}
public :
tiny(int i) { assign (i);}
tiny (const tiny &t) { i = t.v;} // ou utilisation de assign
tiny& operator=(const tiny& t) {i=t.v; return *this;}
tiny& operator=(const int& i) {assign (i); return *this;}
operator int() {return v;} // conversion en int
};
main(){
tiny c1 = 2;
tiny c2 = 62;
tiny c3 = c2 - c1;
tiny c4 = c3; // pas de vérif
int i = c1 + c2;
c1 = c2 + 2*c1; // erreur
c2 = c1 - i; // erreur
c3 = c2; // pas de vérif
}
7.3.4. opérateurs de conversion (2)
exemple officiel (mais il n’est pas bon en général de perdre de l’information lors d’une conversion)
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 82
while (cin >> x) cout << x; // conversion implicite de cin en int (son état)
Il est conseillé de ne pas abuser des conversions, qui rendent les programmes difficiles à lire.
7.3.5. ambiguïté
Le compilateur ne réalise qu’un niveau de conversion automatique. Pour en avoir plusieurs, il faut les expliciter. Les conversions utilisateur ne sont utilisées que s’il est impossible de faire autrement. Si une conversion standard
permet de traiter l’appel, elle est utilisée.
7.4. littéraux
On ne peut définir des littéraux de classes. Mais on peut invoquer les constructeurs pour des types de base. Ainsi : complex(1,2) peut être considéré comme une constante littérale de la classe complex. Si le constructeur est “inline”, une expression l’incluant ne comportera pas d’appel de fonction supplémentaire.
7.5. gros objets
Pour éviter des copies de gros objets, les opérateurs prennent en général des arguments référence. Voir l’exempleprécédent de la classe matrix.
On ne peut retourner une référence à cause d’un problème d’allocation mémoire. Il sera donc nécessaire de procéder àune copie de l’objet retourné. (On ne peut pas retourner une référence à une variable locale static, car un opérateur peut êtreutilisé plusieurs fois dans une même expression).
7.6. affectation et initialisation
Lorsqu’un objet alloue de l’espace lors de son initialisation, il faut veiller à redéfinir un constructeur par copie, et unopérateur d’affectation, sous peine d’erreurs aléatoires lors de la destruction (plusieurs appels de delete sur le même pointeur)
exemple de string
7.7. indexation
L’argument supplémentaire (index) de l’opérateur d’indexation peut être de n’importe quel type. On peut l’utiliserpour des dictionnaires, dont l’index est directement une chaîne de caractères.
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 83
7.8. opérateur d’appel de fonction
Cet opérateur peut servir pour appeler une fonction de l’objet qui prime sur toutes les autres. operator()() doit être une fonction membre. expression (liste d’expression) est interprété comme un opérateur binaire ayant expression comme premier argument,
et liste d’expression comme deuxième argument.
X obj;
obj.fn_toujours_utilisee(); // remplacé par :
obj();
7.9. indirections
l'opérateur -> est défini comme un opérateur postfixe unaire.
class Ptr {
//…
X* operator->();
}
void f (Ptr p){
p->m = 7; // (p.operator->())->m = 7;
Cet opérateur sert à calculer un pointeur sur un objet. Lorsqu'on utilise la version habituelle de la notation ->, un nomde membre est obligatoire après.
X* x = p->; // erreur
X* x = p.operator->(); // ok
On peut utiliser -> pour créer des pointeurs intelligents.
class X {
Y *p;
int compteur;
public :
X(Y* _p):p(_p),compteur(0){};
Y* operator->() {return p; compteur++;}
Y& operator*() {return *p; compteur++;}
Y& operator[](int i) {return p[i];compteur++;}
};
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 84
7.10. incrémentation et décrémentation
On peut surcharger les opérateurs ++ et -- pour avoir un contrôle de bornes automatique.
class CheckedPtrToT {
T* p;
int position;
int taille;
//
T *operator++(); //préfixe
T *operator++(int); //postfixe
T *operator--(); //préfixe
T *operator--(int); //postfixe
T &operator*(); //préfixe
}
void f(T t) {
T v[200];
CheckedPtrToT p(v,200);
p--; // p.operator--(1);
*p = t; // p.operator*() = t; // erreur
++p; // p.operator++();
*p = t; // p.operator*() = t; // ok
}
7.11. une classe chaîne de caractères
class string {
struct srep {
char *s;
int n;
srep() {n=1;}
}
srep *p;
public :
string (const char *s){
p = new srep;
p->s= new char[strlen(s)+1];
strcpy(p->s,s);
}
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 85
string ();{
p = new srep;
}
string (const string &from){
from.p->n++;
p = from.p;
}
// et les opérateurs ==, !=, =
}
7.12. amies et membres
On a souvent le choix entre définir des fonctions membres ou des fonctions globales prenant des arguments référence.Ex m.inv(), ou bien inv(m) pour inverser une matrice m.
Toutes choses égales par ailleurs, on choisit toujours un membre. Les références montrent de façon moins lisible quel'objet peut être modifié.
Une fonction modifiant l'état d'un objet doit être un membre, ou bien une fonction globale prenant un argumentréférence non const.
Par contre, une fonction nécessitant une conversion de type pour tous ses arguments ne peut être que globale. En effetune fonction membre ne convertit jamais le type de l'objet auquel elle s'applique. Cette considération guide aussi lors de ladéfinition des opérateurs qui peuvent aussi bien être des globales que des membres. On utilise une fonction membre si l'onveut protéger le paramètre "principal" contre une coercition automatique non voulue.
class X {
//…
X(int);
int m1();
int m2()const;
friend int f1(X&);
friend int f2(const X&);
friend int f3(X);
}
1.m1(); // erreur : X(1).m1() non essayé
1.m2(); // erreur : X(1).m2() non essayé
f1(1); // erreur : impossible
f2(1); // ok f2(X(1))
f3(1); // ok f3(X(1))
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 86
7.13. avertissement
L'utilisation abusive des opérateurs peut conduire à des programmes illisibles. L'utilisation d'opérateurs à contre emploi aussi :
int operator+(int a, int b) {return a - b;}
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 87
8. Templates
8.1. introduction
Les patrons servent à définir des types, ou des opérations, utilisant ou s'appliquant à des types fournis commeparamètres
Ce mécanisme s'appelle la généricité On peut ainsi définir des listes de "n'importe quoi", et plus généralement des classes "conteneurs de n'importe quoi". L'objectif est de garder la vérification de type, et les performances Le principe consiste à décrire des schémas de programmes, qui peuvent ensuite être complétés par les fragments
manquants, à la demande. La simple utilisation d'un patron provoque la création des programmes nécessaires.
8.2. un patron simple : patron de classe
template<class T>
class stackOf {
T* v;
T* p;
int sz;
public:
stack(int s) {v = p = new T[sz = s];}
˜stack() {delete[] v;}
void push(T a) {*p++ = a;}
T pop() {return *--p;}
int size() const {return p-v;}
8.2.1. utilisation
Ce template s'utilise ensuite ainsi :
stackOf<char> sc(100); // pile de 100 caractères max
stackOf<int> si(30); // pile de 30 entiers max
On peut également avoir des classes génériques dont les fonctions ne sont pas inline, mais les compilateurs balbutientencore un peu dans ce domaine
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 88
template<class T> void stack<T>::push(T a) { *p++ = a;)
template<class T> void stack<T>::stack(int s) {v=p=new T[sz=s];}
Noter dans le second cas que la répétition du paramètre de type est optionnelle
8.3. listes génériques
8.3.1. liste intrusive
struct slink {// classe lien
slink* next;
slink() : next(0){}
slink(slink* n):next(n){}
};
struct list {// classe de gestion de liens
//…
public:
void insert (slink *); //ajout en tête
void append (slink *); //ajout en fin
slink *get(); //détruit et retourne le début
};
class name : public slink {
char *n;
//…
};
template<class T>
class listOf : private list {
public:
void insert(T* a) {list::insert(a);}
T * get() {return (T*) list::get();}
}
void f(char *s){
listOf<name> ln;
ln.insert(new name(s));
name *n = ln.get(); //pas de coercition nécessaire
avantages :
typage sûr
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 89
pas de surcoût pas de réplication de code possibilité de cacher le code source de list à l'utilisateur une liste intrusive est optimale en temps et en mémoire
inconvénients :
pas possible de réaliser des listes croisées
8.3.2. une liste non intrusive
template<class T>
struct Tlink : public slink {
T info;
Tlink(const T& d):info(d){}
};
template<class T>
class slist : private list {
void insert(const T& a) {list::insert(new Tlink<T>(a));}
void append(const T& a) {list::append(new Tlink<T>(a));}
T get();
};
template<class T>
slist<T>:get() {
Tlink<T> *l = (Tlink<T> *)list::get();
T d=l->info;
delete l;
return d;
}
void f(int i) { //pas de sous classe de slink
slist<int>l1;
slist<int>l2;
l1.insert(i);
l2.insert(i); //le même i sur deux listes
}
8.3.3. avantages / inconvénients
la liste intrusive est avantageuse en performance (pas d'allocation ni de copie)
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 90
8.3.4. principe
Ne pas utiliser de références comme arguments de templates : ex : slist<int&> Cela peut provoquer des erreurs à l'expansion du patron
8.3.5. listes de pointeurs
//listes de pointeurs
template<class T>
class Splist : private Slist<void *> {
public :
void insert (T* p) {Slist<void *>::insert(p);}
T* get() {return (T *) Slist<void *>::get();}
8.3.6. instance de template comme argument de template
Une instance de template est un type "normal". On peut donc utiliser un tel type comme paramètre d'un template. typedef Slist< Slist<int> > ints; // remarquer les espaces
8.3.7. une implantation effective des listes
class list {
slink *last; //liste finie codée de façon circulaire
public:
void insert(slink *a);
void append(slink *a);
slink *get();
void clear(){last = 0;}
list()last(0){}
list(slink *a)last(a->next=a){}
friend class listIter;
}
typedef void (*PFV) (const char *);
PFV setListErrorHandler(PFV f) {
PFV old = listErrorHandler;
listErrorHandler = f;
return old;
}
void defaultListErrorHandler(const char *s) {
assert(0);
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 91
}
PFV listErrorHandler = &defaultListErrorHandler;
8.3.8. itération
class listIter {
slink *e; //élément courant
list *l; //liste courante
public:
listIter(list&_l):l(&_l),e(l->last){}
inline slink* operator()();
}
slink* listIter::operator()(){
slink *ret = e ? e->next : 0;
if (ret == l->last) e = 0;
return ret;
}
template<class T> class IslistIter; //déclaration préalable
template<class T> class Islist { //intrusive
friend class IslistIter;
//…
}
template<class T> class SlistIter; //déclaration préalable
template<class T> class Slist { //non intrusive
friend class SlistIter;
//…
}
8.3.9. itérateurs (2)
template<class T>
class IslistIter : private listIter { //hérite de classe de base
public:
IslistIter(Islist<T>& l):listIter(l){}
T* operator()(){return (T*) listIter::operator()();}
}
template<class T>
class SlistIter : private listIter { //hérite de classe de base
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 92
public:
IslistIter(Islist<T>& l):listIter(l){}
T* operator()(){
return ((Tlink<T>*)listIter::operator()())->info;
}
}
void f(name *p){
Islist<name> l1;
Slist<name> l2;
l1.insert(p);
l2.insert(p);
IslistIter<name> it1(l1);
const name *p;
while (p = it1()) { // do something }
}
8.4. fonctions génériques globales
template<class T> void sort(vect<T>&) {
/* trie en ordre croissant */
unsigned n = v.size();
for (int i = 0 ; i<n-1 ; i++)
for (int j = n-1 ; i<j ; j--)
if (v[j]<v[j-1]) swap (v[j],v[j-1]);
}
//version particulière pour char * car < ne fonctionne pas
void sort(vect<char*>&) {
/* trie en ordre croissant */
unsigned n = v.size();
for (int i = 0 ; i<n-1 ; i++)
for (int j = n-1 ; i<j ; j--)
if (strcmp(v[j],v[j-1])<0) swap (v[j],v[j-1]);
}
void f(vect<int>&vi, vect<char*>&vc, vect<string>vs) {
vi.sort();
vc.sort();
vs.sort();
}
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 93
8.4.1. exemple
On peut aussi implanter une classe de vecteurs triables
template<class T> void sort(sortableVect<T>&) {
/* trie en ordre croissant */
unsigned n = v.size();
for (int i = 0 ; i<n-1 ; i++)
for (int j = n-1 ; i<j ; j--)
if (v.lessThan(v[j],v[j-1])) swap (v[j],v[j-1]);
}
template<class T> sortableVect:public vect, public comp<T> {
public:
sortableVect(int s):vect(s){}
}
template<class T> class comp {
static int lessthan(T&a,T&b) {return a<b;}
}
class comp<char *> {
static int lessthan(T&a,T&b) {return strcmp(a,b)<0;}
}
8.5. résolution de la surcharge des fonctions génériques
création d'une nouvelle fonction pour chaque type argument pas de conversion des types
8.5.1. surcharge explicite possible :
template<class T> T max (Ta, Tb) { return a<b ? b : a;} int max(int a, int b) {…}
8.5.2. templates à plusieurs arguments
template<class T, class U> T max(T a, U b) {…}
8.6. arguments de template
pas besoin d'être un argument de type :
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 94
template<class T, int sz> class buffer {
T v[sz];
}
c'est utile dans le cas où la taille est connue à la compilation chaque argument de template de fonction doit figurer dans le type d'un des arguments de la fonction. Cela permet la
sélection et la génération de la bonne fonction lors d'un appel. Deux classes générées par template sont égales si leurs arguments de création sont identiques modulo un typedef,
l'évaluation de constantes, etc..
8.7. dérivation et templates
Une classe exprime ce qui est commun dans la représentation et les interfaces d'appel, et un template exprime ce quiest commun aux types utilisés comme arguments
Deux classes générées par template ne sont pas dans une relation d'héritage particulière
8.7.1. mise en oeuvre via les templates
certaines classes nécessitent d'allouer de la mémoire on peut contrôler cette allocation par l'utilisation d'un template
template<class T, classA> class controlled_container :
public container<T>, private A {
void f() {
T *p = new(A::operator new(sizeof(T))) T;
}
8.8. un tableau associatif
template<class V, class K> class map {
public :
V& operator[](const K &k) { // trouver le V à partir du K
// grâce à <, et ==
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 95
9. Gestion des exceptions
9.1. gestion des erreurs
En cas de situation exceptionnelle (i.e. sortant du cadre prévu par l'algorithme), le programmeur se trouve devantplusieurs alternatives, non équivalentes en coût de conception et de codage :
1 : terminer le programme en indiquant le lieu de l'erreur (assert())2: retourner une valeur représentant l'erreur, qui doit être propagée, et traitée, par toutes les fonctions appelantes3: continuer dans un état invalide (avec ou non affichage de message)4: provoquer un "longjump" jusqu'à un point où l'erreur peut être traitée (et un état valide reconstruit)
C++ offre une solution élégante et performante au problème de la prise en compte des exceptions
9.2. discrimination et nommage des exceptions
déclaration d'intercepteurs :
class vector {
int sz;
int *v;
//
class range{
int index;
range(int i) : index(i){}
};
class size{};
vector(int i) : sz(i) { v = new int[i]; if (!v) throw size();}
int& operator[](int i) { if (i>=sz) throw range(i); return v[i];
}
9.2.1. utilisation
void f()
{
try {
g();
} catch (vector::range r) {
cout << "bad index" << r.index << endl;
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 96
} catch (vector::size) { // une autre erreur par exemple
}
}
9.2.2. Les interceptions peuvent être imbriquées
9.2.3. On peut utiliser des templates de classes d'exceptions
Chaque classe générée aura son propre gestionnaire d'exceptions
template<class T> class allocator {
class exhausted{};
};
void f() {
try {
// allocate for some room
} catch (allocator<int>::exhausted) {
} catch (allocator<char>::exhausted) {
}
}
La classe exception peut aussi être globale, i.e. non intégrée dans la classe qui provoque l'exception
9.2.4. regroupement d'exceptions
On peut grouper le traitement des exceptions
enum matherr {overflow, underflow, zerodivide, etc...}
try { //... }
catch (matherr m) {
switch (m) case overflow: … break; case …
}
On peut utiliser l'héritage dans des classes d'exceptions et profiter des fonctions virtuelles C'est utile car cela permet de traiter toutes les exceptions, y compris celles à venir
class matherr {};
class overflow : public matherr {};
class underflow : public matherr {};
class zerodivide : public matherr {};
try { … }
catch (overflow) {
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 97
// tout overflow ou sous classe de overflow
} catch (matherr) {
// tout matherr qui n'est pas overflow
}
9.2.5. Exceptions dérivées
Une exception est généralement interceptée par un gestionnaire d'une classe de base Pour disposer de toute l'information relative à l'exception exacte, il faut utiliser une référence
try {}
catch (errtype &e){}
On peut relancer une exception
try {}
catch (errtype &e){
if (cantdoit) throw; //relance l'exception exacte d'origine
}
Les gestionnaires sont essayés dans l'ordre, et cet ordre est donc opportun Le compilateur connaît la hiérarchie des classes et peut donc signaler des erreurs : exception masquée et donc jamais
gérée par exemple. catch(...)signifie : intercepter n'importe quelle exception
try { // qqchose
} catch (...){
// autre chose
throw; //relance l'exception exacte d'origine
}
9.3. acquisition de ressources
Le mécanisme de throw garantit que l'appel des destructeurs des variables automatiques est correctement effectué On peut tirer parti de ce mécanisme pour acquérir des ressources dans des constructeurs, et libérer ces ressources dans les destructeurs. Exemple de la trace
9.3.1. constructeurs et destructeurs
Un objet n'est pas considéré construit tant que son constructeur n'est pas terminé. Un constructeur n'est terminé que pourvu que tous les sous objets ont été construits.
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 98
Le destructeur ne sera appelé que si l'objet est construit.
class X {
filePtr aa;
lockPtr bb;
X(const char *x, const char *y) : aa(x), bb(y) {}
};
Si le constructeur de bb lance une exception, celui de aa ayant été terminé, le destructeur de aa sera appelé, mais pascelui de bb.
9.3.2. acquisition de ressources = initialisation
Un constructeur bien pensé obéit au principe : l'acquisition de ressources est une initialisation. Si l'acquisition échoue, le constructeur échoue.
struct X{
int *p;
X(int s) {p = new int[s]; init()};
˜X() {delete[] p;}
}; // si init lance une exception, p n'est jamais libéré
variante sûre :
template<class T>class arrayOf{
T *p;
arrayOf(int s){p = new T[s];}
˜arrayOf(){delete[] p;}
operator T* (){return p;}
}
struct X{
arrayOf<int> p;
X(int s) : p(s){init()};
˜X() {}
}; // si init lance une exception, p est libéré
Le code d'un constructeur n'est pas appelé si new ne parvient pas à allouer assez d'espace pour l'objet.
9.3.3. épuisement de ressources
Lors de l'incapacité à obtenir une ressource, on peut prévenir le programme appelant (en appelant une fonction del'appelant), ou encore lancer une exception.
On combine en général les deux approches de la façon suivante (exemple de l'opérateur new):
#include <stdlib.h> //pour size_t
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 99
extern void * _last_allocation;
extern void * operator new(size_t size) {
void * p;
while (0 == (p = malloc(size))) {
if (_new_handler) (*_new_handler)();
else return 0;
}
return _last_allocation = p;
}
9.3.4. … épuisement de ressources
// pour l'utiliser :
void my_new_handler() {
if (can_get_some_mem()) return;
throw MemoryExhausted;
}
// et dans une fonction :
void (*oldnh)() = set_new_handler (&my_new_handler);
try {…}
catch (MemoryExhausted) {…}
catch (...){
set_new_handler (oldnh); //remet
throw; //relance
}
set_new_handler (oldnh); //remet
9.3.5. règle lors de l'utilisation de callbacks
Il est bon d'échanger aussi peut de paramètres que possible avec les callbacks, pour éviter de rendre interdépendantsdes programmes qui n'ont pas de rapport.
9.4. exceptions qui ne sont pas des erreurs
Terminer élégamment une recherche hautement récursive (dans un arbre par exemple) Sortir d'une boucle infinie parcourue un grand nombre de fois Attention à la lisibilité des programmes résultants, car les exceptions sont un mécanisme de contrôle moins bien
structuré que les autres.
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 100
9.5. spécification d'interface
On peut déclarer les exceptions pouvant être lancées par une fonction :
void f() throw (x2,x3,x4);
void f() throw (x2,x3,x4)
void f() {
try { /* code */ }
catch (x1) {throw;}
catch (x2) {throw;}
catch (x3) {throw;}
catch (...) {unexpected ();} // terminate() puis abort()
}
int g() throw();//ne peut lancer aucune exception
int h(); //peut lancer toutes les exceptions
9.5.1. utilisation de set_unexpected
On peut utiliser set_unexpected pour redéfinir le comportement par défaut, et appeler throw quand même pour relancerune erreur qui ne figure pas dans l'interface de la fonction
typedef void (*PFV)();
PFV set_unexpected (PFV);
class STC {
PFV old;
STC(PFV f) : old(set_unexpected(f)) {}
˜STC(){set_unexpected(old);}
};
void rethrow () { throw;}
f_qui_laisse_passer()
{
STC(&rethrow);
f();
}
9.6. exceptions non interceptées
Une exception non interceptée provoque un appel à terminate PFV set_terminate (PFV); Un appel à terminate est supposé ne pas retourner à son appelant.
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 101
9.7. alternatives à la gestion des erreurs
int f(args) {
try {
g(args);
} catch (x1) {// corrige, et réessaye
g(args);
} catch (x2) {// calcule et retourne un résultat
return 2;
} catch (x3) {//relance
throw;
} catch (x4) {//change en une autre exception
throw y4;
} catch (x5) {corrige et continue la fonction
// du code
} catch (...) {traduit en une autre forme d'erreur
errno = EBZCLDF;
terminate();
}
9.8.
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 102
10. Flots
10.1. introduction
Les flots sont accessibles par l'interface "iostream.h" Les noms de fonction sont surchargés pour tous les types souhaités Des opérateurs sont définis (<< et >>)
10.2. sorties
put(cerr, "x = ");
put(cerr, x);
put(cerr, "\n");
// notation directe :
cerr << "x = " << x << "\n";
cout << a * b + c << "\n"; //précédence assez basse de <<
class ostream : public virtual ios {
// …
public :
ostream& operator<<(const char *);
ostream& operator<<(char);
ostream& operator<<(short i){return *this<<int(i);;
ostream& operator<<(int);
ostream& operator<<(long);
ostream& operator<<(float);
ostream& operator<<(double);
ostream& operator<<(const void *);//tous les pointeurs
};
10.2.1. extension pour une classe utilisateur
class complex{
//
friend ostream& operator<<(ostream& o, complex&c);
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 103
}
ostream& operator<<(ostream& o, complex&c) {
return o << '(' << c.re << ',' << c.im << ')';
}
//utilisation :
main(){
complex c(1,2);
cout << "le complexe c vaut " << c << endl;
}
10.3. entrées
class istream : public virtual ios {
// …
public :
istream& operator<<(char *);
istream& operator<<(char&);
istream& operator<<(short&);
istream& operator<<(int&);
istream& operator<<(long&);
istream& operator<<(float&);
istream& operator<<(double&);
};
// idée du code :
istream& operator<<(T& tvar) {
//sauter les espaces
//lire un "T" dans tvar
return *this
}
int readints (Vector<int>& v){
for (int i = 0;i<v.size ; i++) {
if (cin>>v[i++]) continue; //converteur implicite en int
return i;
}
//autres traitements
}
//autres méthodes de la classe istream :
istream& get (char &c);
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 104
istream& get (char *s,int n, char fin = '\n');
10.3.1. fonctions définies dans <ctype.h> :
int isalpha(char);
int isupper(char);
int islower(char);
int isxdigit(char);
int isspace(char);
int iscntrl(char);//0->31, 127
int ispunct(char);//aucun des précédents
int isalnum(char);//alpha ou digit
int isprint(char);//affichable ascii ' ' .. '˜'
int isgraph(char);//alnum ou ispunct
int isascii(char);//compris entre 0 et 127
//toutes sauf isascii sont évaluées par accès direct ds tableau
//donc plus efficaces que des comparaisons
10.3.2. états du flot
int eof() const, //fin de fichier fail() const, //prochaine opération échouera, mais état du flot correct (rien de perdu) bad() const, //ouups good() const //c'est dans ce seul état que les opérations font quelque chose
class ios {
// …
public :
enum io_state {goodbit=0, eofbit=1, failbit=2, badbit=4};
rdstate (); //retourne un ensemble de bits d'io_state
};
10.3.3. entrée de types utilisateur
Le second argument doit être une référence. Exemple (simplifié)
istream& operator>>(istream& s, complex&c) {
//trois formats : re , (re) , (re,im)
{
double re = 0, im = 0;
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 105
char c = 0;
s >> c;
if ( c == '(' ) {
s >> re >> c;
if ( c == ',' ) {
s >> im >> c;
} // pas de else
if (c != ')') {
s.clear (ios::badbit); //
} else {
s.putback(c);
s >> re;
}
if (s) a = complex (re,im);
return s;
}
10.4. mise en forme
La classe ios de base permet de gérer le lien entre un flot de sortie et un flot d'entrée, ainsi que des informations liéesaux formats.
class ios {
//
public :
ostream *tie(ostream *s); //attache l'entrée à la sortie
ostream *tie()const; //retourne la sortie liée
int width(int w); //positionne le champ longueur
int width()const; //retourne la longueur
char fill(char); //init le caractère de remplissage
char fill()const; //retourne ce caractère
long flags(long);
long flags()const;
long setf(long setbits, long field);
long setf(long);
long unsetf(long);
int precision (int);
int precision ()const;
void clear (int i = 0);
int rdstate()const, eof()const, bad()const, fail()const, good()const;
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 106
10.4.1. liaison de flots
main () {
char s[50];
cout << "passwd :";
// grâce à tie, on peut omettre ici cout.flush();
cin >> s;
}
//
cin.tie(0); // permet de supprimer le lien
10.4.2. champs de sortie
Le paramètre width (0 par défaut) spécifie le nombre de caractères à utiliser pour la prochaine (seulement) sortienumérique ou de chaîne de caractères. La valeur 0 signifie : autant que nécessaire.
Le paramètre fill (espace par défaut) est le caractère utilisé pour combler
cout.width(4);
cout.fill(*);
cout << '(' << 12 << ')';
// produit :
(**12)
Width ne provoque pas de troncation
10.4.3. état du format
La classe ios maintient un paramètre (flags) décrivant les différents paramètres de format.
class ios {
public :
enum {
skipws=01, //sauter les séparateurs en entrée
//remplissage :
left=02, //avant la valeur
right=04, //après la valeur
internal=08, //entre le signe et la valeur
//base entière :
dec = 020,
oct = 040,
hex = 0100,
showbase = 0200, //montrer la base
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 107
showpoint = 0400, //affiche les zéros après la virgule
uppercase = 01000, //majuscules
showpos = 02000, //+ avant les positifs
scientific = 04000, //.dddddd Edd
fixed = 010000, //ddd.dd
unitbuf = 020000, // après chaque sortie
stdio = 040000, //après chaque caractère
};
};
//utilisation :
mystream.flags(mystream.flags() | ios::showbase);
//ou son équivalent :
mystream.setf(ios::showbase);
10.4.4. gestion des bits exclusifs : base, remplissage, et flottants
cout.setf(ios::oct, ios::basefield); // met ce qu'il faut à zéro
cout.setf(ios::left, ios::adjustfield); // id
cout.setf(ios::fixed, ios::floatfield); // id
cout.setf(0, ios::floatfield); // remet la valeur par défaut
cout.precision(4); //chiffres après la virgule (6 par défaut)
10.4.5. manipulateurs
Il est parfois nécessaire de procéder à des opérations sur les flots autres que des entrées sorties, au milieu de cesdernières. Ex :
cout << x; cout.flush(); cout<<y;
On peut procéder ainsi :
typedef ostream& (*ostreamOp)(ostream&);
ostream& flush(ostream&);
ostream& operator<<(ostream&o, ostreamOp f) {
return f(o);
}
//ensuite :
cout << x << flush << y flush;
//un tel opérateur est déjà défini dans la classe ostream :
class ostream : public ios {
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 108
//
public :
ostream& operator<<(ostream& (*)(ostream&));
10.4.6. manipulateurs de istream
istream &ws(istream& is) {return is.eatwhite();}
//
cin >> ws >> x;
//on peut étendre le mécanisme : cout << setprecision (4) << x;
Il faut construire un objet affichable
typedef ostream & (*Fostint)(ostream&, int);
class omanip_int {
int param;
Fostint fonct;
omanip_int (Fostint f, int p) : fonct(f),param(p) {}
friend ostream& operator<<(ostream&, omanip_int&)
};
omanip_int setprecision (int i) {
return omanip_int (&_set_precision, i);
}
ostream& operator<<(ostream& o, omanip_int& m) {
return m.fonct(o, m.param);
};
cout << x << setprecision(4) << y;
10.4.7. utilisation de patrons
On peut définir un patron pour prévoir des structures de manipulateurs pour tous les types, autres qu'entiers :
template<class T> class OMANIP {
T i;
ostream& (*f)(ostream&,T);
public:
OMANIP(ostream& (*_f)(ostream&,T),T _i) : f(_f), i(_i){}
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 109
friend ostream& operator<<(ostream& o, OMANIP& m) {
return m.f(o,m.i);
}
};
ostream& precision (ostream& o, int i) {
o.precision (i);
return o;
}
OMANIP<int> setprecision (int i) {
return OMANIP<int>(&precision, i);
}
cout << precision(5) << x;
Les templates OMANIP, IMANIP et SMANIP (pour ios) sont définis dans <iomanip.h>
10.4.8. intérêt des structures de manipulateurs
Passer un objet plutôt qu'appeler une fonction est une technique intéressante, même dans d'autres domaines que lesentrées sorties.
Un objet est créé, puis passé n'importe où, et utilisé comme une fonction. Cela permet de partager les détails d'exécution entre l'appelé et l'appelant.
10.4.9. Manipulateurs d'entrées sorties standard
ios& oct (ios&); // dec, hex
ostream& endl(ostream&); // ends, flush
istream& ws(istream&); //saute les blancs
SMANIP<int> setbase(int); // et setfill, setprecision, setw
SMANIP<long> resetiosflags(long); // et setiosflags
10.4.10. membres de ostream
On peut avoir un accès direct à une position d'un fichier, dès qu'il est lié à un ostream :
class ostream : public virtual ios {
ostream& flush();
ostream& seekp(streampos); //aller à une position
ostream& seekp(streamoff,seek_dir);
streampos tellp();
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 110
};
seekp permet d'aller à une position donnée pour écriture :
fout.seekp[10]; //lié à un file (char file[n]);
fout<<"@"; // écrit @ dans file[10]
10.4.11. membres de istream
On peut avoir un accès direct à une position d'un fichier, dès qu'il est lié à un istream :
class istream : public virtual ios {
int peek(); // consulte le prochain caractère sans lire
istream& putback(char c);
istream& seekg(streampos); //aller à une position
istream& seekg(streamoff,seek_dir);
streampos tellg();
};
seekg permet d'aller à une position donnée pour lecture les lettres p et g suffixes sont nécessaires car on peut créer une classe héritant à la fois de ostream et de istream
10.5. fichiers et flots
#include <fstream.h> les classes fstream, ofstream et ifstream dérivent de iostream, qui est elle même dérivée de istream et ostream. Toutes les fonctionnalités de ces classes sont donc accessibles par fstream
class ios {
public :
enum open_mode {
in = 1<<0;//ouvert en entrée
out = 1<<1;//ouvert en sortie
ate = 1<<2;//ouvre et va a la fin
app = 1<<3;//ajoute
trunc = 1<<4;//tronque le fichier à longueur 0
nocreate = 1<<5;//échoue si le fichier n’existe pas
noreplace = 1<<6;//échoue si le fichier existe
};
};
// exemple : copie d'un fichier dans un autre
main(int argc, char **argv){
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 111
assert(argc == 3);
ifstream inp(argv[1]); assert(inp);
ofstream out(argv[2]); assert(out);
char c;
while (inp.get(c) && out.put(c));
assert(inp.eof() && !out.bad());
}
10.5.1. paramètrage
Un ofstream est ouvert en écriture par défaut, un ifstream en lecture. On peut aussi paramètrer cette ouverture :
ofstream out(name, ios::out|ios::nocreate);
fstream dictionary("dict.text", ios::in|ios::out);
10.5.2. fermeture des flots
dictionary.close(); //pour fermer
A l'arrêt du programme, les flots cin, cout et cerr sont automatiquement fermés
10.5.3. flots de chaînes de caractères
la classe strstream est déclarée dans "strsream.h" le caractère zéro terminal est interprété comme la fin du fichier
char *p = new char[size];
ostrstream out(p,size);
out << 3 << "abcd";
10.5.4. mise en tampon
Les entrées sorties utilisent des mécanismes de tampons distincts suivant que le type de flot est un fichier, ou untableau de caractères. Le mécanisme de leur prise en compte tire profit de la possibilité d'avoir des fonctions de débordement(overflow et underflow) virtuelles.
10.6. entrées/sorties C
On peut entremêler des appels aux fonctions d'entrée sortie C sur une base ligne à ligne. (au niveau des caractères onpeut avoir des problèmes de portabilité).
Certaines implantations de C++ demandent d'appeler une fonction membre static de la classe ios pour travailler avecdes fonctions C:
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 112
ios::sync_with_stdio ();
Cours de programmation Laurent Henocque Le langage C++
Ecole Supérieure d’Ingénieurs de Luminy / département ES2I, Marseille 113
11. Notes rapides au programmeur C
11.1. règles empiriques de base pour la conception d'objets
si il y a structure : instance de classe si visible comme objet : instance de classe si deux classes partagent un concept : classe de base, et héritage si la classe contient un objet d'un type donné : patron
11.2. notes aux programmeurs C
moins de préprocesseur : const, enum, inline, patrons pas de malloc : new moins d'unions : héritage éviter void *, +pointeur, tableaux C, cast explicite sauf au coeur des classes