118
Cours de Programmation Introduction à C++ Laurent Henocque Maître de Conférences Ecole Supérieure d’Ingénieurs de Luminy Université de la Méditerranée Aix-Marseille II

Cours de Programmation - s3.e-monsite.coms3.e-monsite.com/2010/12/26/93740867cpp-pdf.pdf · 4.2. héritage : classes dérivées 38 4.3. classes abstraites 38 ... 8.8. un tableau associatif83

  • 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