348
DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++ Méthodologie, grammaire, sémantique, syntaxe Jacques PHILIPP 9 janvier 2009

DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

Embed Size (px)

Citation preview

Page 1: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++

Méthodologie, grammaire, sémantique, syntaxe

Jacques PHILIPP

9 janvier 2009

Page 2: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les
Page 3: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

AVANT PROPOS

L'informatique à considérablement évolué depuis son origine dans son utilisation. Bien évidemment, les méthodes de programmation ont du être adaptées pour intégrer les nouveaux usages : internet, téléphonie mobile, chat, etc., basés tous essentiellement sur la technologie objet, apparue à la fin des années 1970…

Or, cette dernière est essentiellement basée sur une évolution progressive et naturelle de la programmation procédurale structurée. Ainsi, le langage C traditionnel de Kernighan et Ritchie (1972), considéré alors comme un langage « d'assemblage de haut niveau » est normalisé (1986), ce qui a apporté à ce langage à la fois :

• plus de rigueur tout en préservant ses acquits à savoir la syntaxe d'un langage de haut niveau (Pascal, Fortran, PL/1) avec la puissance d'un langage de bas niveau (assembleur),

• une évolution sémantique importante avec l'intégration des concepts de type et de prototype d'objet structurée doté de ses traitements. Ainsi, le système d'exploitation Unix, écrit en C considère un fichier comme un objet sur lequel des traitements peuvent être réalisés (lecture, écriture) au travers d'interfaces adéquates selon son type (fichier usuel, clavier, écran, réseau, etc.).

L'utilisation du langage C comme langage à objet est donc possible, même si sa syntaxe rend ce type de programmes très complexe, d'où les méthodes de programmation actuelles (langages de navigation et de description d'objets tels HTML et XML), gestion des systèmes client-serveur (Javascript, CLI,…) à la fois orientée objet (Java, C++, etc.) mais également utilisant des langages procéduraux (C, PHP, etc.).

Des rappels des notions fondamentales d'algorithmique (complexité, preuves de programmes, méthodes récursive et itératives) sont présentés dans la première partie de l'ouvrage.

La sémantique des concepts de programmation procédurale et structurée (programme, instruction, structures de contrôle, fonctions, procédures, structuration des données…), développés à partir des années 70, est présentée dans la deuxième partie de l'ouvrage, complétée par la présentation normalisée de la grammaire du langage C. La structuration des données est devenue une des bases de la technologie objet car une donnée est devenue un objet typé. Cette évolution sémantique permet de décrire le langage C dans l'esprit de la programmation objet. Sont également mis en évidence la puissance de ce langage et ses limites.

Le langage C++, conçu comme un sur ensemble du C par intégration de compléments fonctionnels (références) et objets (classe, prototype d'objet, surdéfinition, modèle d'objet, gestion des exceptions, héritage, objets virtuels, espace de nommage, bibliothèques de classes), est présenté dans la troisième partie de l'ouvrage.

Public : étudiants des 1er et 2nd cycles universitaires, élèves ingénieurs, ingénieurs informaticiens, BTS, IUT…

Page 4: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les
Page 5: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

TABLE DES MATIERES

CHAPITRE I : LE GENIE LOGICIEL 11

1. De l'art de la programmation au génie logiciel 11

2. Développement d'un logiciel 15

CHAPITRE II : ALGORITHMIQUE ELEMENTAIRE 19

1. Définitions 19

2. Mise au point et preuve d'un algorithme 19

3. Complexité, problèmes NP-complets 20

4. Comportement asymptotique 20

5. Enoncés de problèmes 21

6. Méthodes de conception d'algorithmes 22

7. Structures de contrôle et méthodes itératives 26

8. Récursivité 32

CHAPITRE III : BASES DE PROGRAMMATION EN C ET C++ 39

1. Quelques règles d'or en programmation 39

2. Grammaire, syntaxe, sémantique 40

3. Mots clés 41

4. Instruction, action 41

5. Structures de contrôle : boucles, tests, branchements 45

6. Fonctions, procédures, bibliothèques 51

7. Représentation interne des objets de base des langages C et C++ 53

8. Accès à un objet 60

9. Structures de données abstraites 63

10. Exercices 68

CHAPITRE IV : LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS 69

1. Popularité du langage C et des langages dérivés 69

2. Esprit du langage C 70

3. Principes généraux du langage 71

4. Variables 73

5. Types 75

6. Variables qualifiées constantes 82

7. Opérateurs 84

8. Fonctions et procédures 91

9. Pointeurs 96

10. Arguments de fonctions et pointeurs 101

Page 6: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

6 DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++ ───────────────────────────────────────────────────

11. Tableaux et pointeurs 101

12. Chaînes de caractères et pointeurs 106

13. Applications des pointeurs 108

14. Fonction à nombre variable d'arguments 110

15. Structures de données abstaites et objets structurés 113

16. Unions 123

17. Type énuméré 125

18. L'instruction typedef 126

19. Exercices 127

CHAPITRE V : BASES DE LA PROGRAMMATION ORIENTEE OBJET 139

1. Classes 139

2. Approche objet, donnée abstraite, encapsulation 142

3. Initialisation des objets 143

4. Polymorphisme, surdéfinition et généricité 144

5. Collections d'objets. 145

6. Principes généraux de protection des données 145

7. Abstraction et encapsulation en C et C++ 146

CHAPITRE VI : LE C++, LANGAGE PROCEDURAL ET FONCTIONNEL 151

1. Introduction 151

2. Objets de base 152

3. Entrées/sorties élémentaires en C++ 153

4. Instruction et expression 156

5. Fonctions et procédures 159

6. Référence 163

7. Exercices 166

CHAPITRE VII : CLASSES EN LANGAGE C++ 169

1. Rappels 169

2. Définitions 169

3. Qualification d'accès aux membres d'une classe 172

4. Méthode 174

5. Le pointeur this 175

6. Méthode spécifiée constante 175

7. Pointeur sur les membres d'une classe 177

8. Exercice 177

Page 7: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

TABLE DES MATIERES ───────────────────────────────────────────────────

7

CHAPITRE VIII : INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQUES 179

1. L'initialisation des variables en langage C 179

2. L'initialisation des instances en langage C++ 184

3. Constructeur 185

4. Destructeur 190

5. Gestion dynamique de la mémoire en langage C 191

6. Gestion dynamique de la mémoire en C++ 193

7. Objets statiques en langage C++ 197

8. Exercices 201

CHAPITRE IX : SURDEFINITION DES OPERATEURS 211

1. Généralités et syntaxe 211

2. Surdéfiniton d’un opérateur non membre d'une classe 212

3. Surdéfinition d’un opérateur membre d'une classe 213

4. Amitie et levée partielle de l'encapsulation 214

5. Opérateurs relationnels 215

6. Opérateur de transtypage 216

7. Opérateur [] 219

8. Constructeur copie 221

9. Affectation 223

10. Incrémentation et décrémentation 225

11. Gestion dynamique de la mémoire 226

12. Classe encapsulee ou imbriquée 227

13. Déréférenciation, référence, sélection de membre 229

14. Opérateur fonctionnel 230

15. Exercices 231

CHAPITRE X : L'HERITAGE EN LANGAGE C++ 239

1. Définitions 239

2. Qualifications d'accès aux objets d'une classe 241

3. Qualification d'héritage 241

4. Constructeur dans les classes dérivées 248

5. Héritages multiples 249

CHAPITRE XI : METHODES VIRTUELLES, LIAISON DYNAMIQUE, POLYMORPHISME 253

1. Méthodes virtuelles et liaison dynamique 253

2. Méthode virtuelle pure - classe abstraite 255

3. Récapitulation des règles de dérivation 259

Page 8: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

8 DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++ ───────────────────────────────────────────────────

CHAPITRE XII : LES MODELES GENERATEURS D'OBJETS (TEMPLATE) 261

1. Le préprocesseur du langage C 261

2. Les modèles générateurs d'objet 264

3. Modèle d'argument 265

4. Modèle de fonction, de classe, de méthode 266

5. Instanciation d'un modèle 270

6. Modèle spécialisé 273

7. Métaclasse modèle (template template) 277

8. Modèles de constantes 278

9. Généricité et méthode virtuelle 279

10. Mot clé typename 281

11. La bibliothèque template 281

12. Fonctions exportées 284

13. Exercices 285

CHAPITRE XIII : ESPACES DE NOMMAGE 291

1. Espace nommé et anonyme 291

2. Déclaration using 294

3. Directive using 296

CHAPITRE XIV : REPRISE DES ERREURS D'EXECUTION EN LANGAGE C++ 299

1. Principes sémantiques 299

2. Génération et traitement d'une exception 300

3. Gestionnaire d'exceptions élémentaires 303

4. Exception typée 303

5. Exception et constructeur 305

6. Exception et allocation mémoire 307

7. Hiérarchie des exceptions 307

CHAPITRE XV : TYPES DYNAMIQUES 309

1. Identification dynamique d'un type 309

2. Transtypages en langage C++ 311

CHAPITRE XVI : ANNEXES 315

1. Programmation objet et appel système 315

2. La gestion objet des entrées/sorties 321

3. Quelques appels système 330

4. Génération d'applications et commande make 331

5. Règles de priorité des opérateurs 337

Page 9: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

TABLE DES MATIERES ───────────────────────────────────────────────────

9

BIBLIOGRAPHIE 339

INDEX 340

Page 10: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les
Page 11: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LE GENIE LOGICIEL

1. DE L'ART DE LA PROGRAMMATION AU GENIE LOGICIEL

1.1 De la programmation empirique à Internet Les méthodes de développement ont évolués avec les générations d'ordinateurs.

Génération : 1ère 2ème 3ème 4ème 5ème langage machine Assembleur langage évolué SGBD Internet

■ Programmation, rendement, coûts du développement

En 1972, le rendement moyen d'un programmeur professionnel est de 8 lignes par heure (source Pentagone : The high cost of software). L'écriture de "bons" programmes est très longue et difficile. Les mêmes statistiques chiffrent l'écriture d'une instruction à 75 $, celui de sa mise au point à 4000 $.

■ Evolution historique du profil du programmeur

1950 Il est un initié qui utilise exclusivement le langage machine. 1968 Il est considéré comme un artiste (Knuth). 1975 Il respecte une discipline de programmation non empirique (Dijkstra). 1981 La programmation est devenue une science (D.Gries). 1990 L'ingénierie du logiciel est devenue la règle.

■ Les causes

Les tests et branchements conditionnel conduisant à la rupture du déroulement séquentiel du programme (Von Neumann) ont permis l'écriture de la de la première génération des programmes procéduraux.

La complexification des applications conduisit :

• à supprimer les ruptures de séquence devenues l'ennemie du programmeur car source d'erreurs dues aux branchements erronés,

• à organiser les programmes par une programmation structurée, décrite par cette citation de Boileau "Ce qui se conçoit bien s'énonce clairement et les mots pour le dire arrivent aisément", base des langages structurés (Pascal, C) ou à objet (Smalltalk, Java, C++).

■ Erreurs et preuves de programmes

Tout programmeur doit tenir compte de ce constat d'E. Dijkstra : "Essayer un programme peut seulement indiquer des erreurs mais ne prouve jamais qu'il est juste".

CHAPITRE I

Page 12: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

12 CHAPITRE I ───────────────────────────────────────────────────

1.2 Le génie logiciel Le développement du logiciel est une discipline d'ingénierie solidement établie dans ses méthodes et principes.

■ Difficultés de conception des logiciels

Plusieurs facteurs rendent difficile la conception et le développement des logiciels :

• Ils peuvent être d'une très grande complexité, à la limite de l'ingéniosité humaine.

• Aucune loi mathématique ou physique ne délimite l'univers des solutions possibles.

• Les causes de certaines erreurs d'exécution peuvent être aléatoires.

• Une erreur d'exécution d'un programme peut provoquer l'interruption du service fournit par ce dernier.

■ Chaque détail compte

Des erreurs d'apparence anodine peuvent se glisser dans des programmes ainsi que l'illustre le programme en langage C suivant :

if (i = 0) printf("la valeur de i est zéro\n"); else printf('la valeur de i n'est pas zéro\n");

Contrairement à l'intention du programmeur, ce programme imprime "la valeur de i est zéro" quel que soit i car dans la syntaxe du langage C, le symbole = représente l'affectation, pas la comparaison logique (qui s'écrit == ). L'expression i = 0 étant toujours nulle, le test d'arrêt n'est jamais satisfait ce qui provoque un cycle perpétuel indétectable.

■ Une simple question d'argent ?

Le logiciel embarqué dans la navette spatiale américaine, développé par la NASA et dont le prix de revient est estimé à 3300 € par ligne de code, contient encore des erreurs dont la suivante : lors de l'utilisation simultanée de deux claviers, tout caractère saisi est modifié par un "ou logique". La solution retenue a été de n'utiliser qu'un clavier à la fois…

■ Le développeur de logiciels

On pense souvent que le développement de logiciel est une tâche simple, à confier à des ingénieurs débutants. Pourtant, la conception d'ouvrages d'art importants est toujours confiée à des ingénieurs confirmés. Et on comprend mal pourquoi il faudrait opérer différemment pour la conception des logiciels car c'est une tâche qui requiert une grande expertise pour être menée à bien.

■ Valeur marchande d'un logiciel

La valeur marchande d'un logiciel tient autant à son introduction rapide sur le marché qu'à ses qualités propres, le client étant rarement prêt à attendre patiemment qu'un programmeur exceptionnellement doué ait trouvé une solution élégante et gratuite à son problème. Il a tort à long terme, mais il achète le produit du moment.

Page 13: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LE GENIE LOGICIEL ───────────────────────────────────────────────────

13

1.3 Productivité du développement logiciel Les aspects économiques du développement du logiciel sont encore mal maîtrisés.

Le coût du logiciel de fabrication de l'Airbus A320 fut estimé, à partir de celui de l'A310, connu, et de son cahier des charges spécifiques. Les différentes évaluations variaient entre 3 et 12.

Une des difficultés de ce type d'exercice prévisionnel est le manque de précision de la mesure de productivité du logiciel que plusieurs techniques permettent, imparfaitement, de mesurer.

■ Le nombre de lignes de code par programmeur et par jour

La mesure la plus couramment utilisée de la productivité d'une équipe de développement est le nombre de lignes de programme produites par jour. Elle est mauvaise pour au moins deux raisons :

• Un programmeur compétent consacre un temps parfaitement productif à réduire la taille de ses programmes ce qui en diminue la complexité et en augmente la valeur.

• Cette mesure ne fait pas la différence entre la productivité d'une équipe qui réalise un logiciel de 10 000 lignes de code en un an, et celle qui réalise en deux ans un logiciel similaire de 20 000 lignes de code.

Une mesure empirique de la productivité quotidienne d'un programmeur en fonction de la complexité du logiciel est donnée par la formule :

productivité = constante * (taille du programme)1.5

■ Le coût du développement

Une meilleure mesure de la productivité d'une équipe est basée sur le coût du développement d'un logiciel.

Ce dernier prend en compte simultanément :

• sa valeur marchande,

• son évolution probable,

• la complexité intrinsèque du problème résolu,

• le surcoût du au retard éventuel de sa commercialisation, celui de son support et de sa maintenance pendant sa durée de vie,

• la possibilité de le réutiliser.

Le coût est aussi une indication de la difficulté qu'aura la concurrence à reproduire un effort de développement similaire.

Page 14: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

14 CHAPITRE I ───────────────────────────────────────────────────

1.4 Qualité du logiciel

■ Critères de qualité

Un logiciel doit être doté des qualités suivantes :

• Validité : aptitude du logiciel à réaliser exactement les tâches de sa spécification.

• Robustesse : aptitude du logiciel à fonctionner dans des conditions anormales.

• Extensibilité : facilité de modification des spécifications du logiciel.

• Ré-utilisabilité : aptitude du logiciel à être réutilisé partiellement ou en totalité.

• Compatibilité : aptitude du logiciel à pouvoir être combiné avec d'autres.

• Efficacité : bonne utilisation des ressources matérielles et logicielles

• Portabilité : facilité d'adaptation du logiciel à différents environnements.

• Vérifiabilité : présence de procédures de test, de déboguage, de recette et de certification.

• Intégrité : aptitude du logiciel à protéger ses différentes composantes (programmes, données) contre des accès ou des modifications non autorisées.

• Ergonomie du logiciel (fonctionnement, utilisation, interprétation des résultats, fiabilité).

■ L'ergonomie

Des défauts peuvent s'avérer fatals comme l'illustre la catastrophe de l'Airbus A320 du Mont Sainte Odile. Cet avion est doté d'un moniteur sur lequel s'affichait alors, selon un mode choisi par le pilote, la vitesse verticale de l'avion en pieds par seconde ou l'inclinaison de sa trajectoire en degrés. Il semble que le pilote ait mal interprété le contenu de l'écran le copilote ayant modifié le mode d'affichage, ou l'inverse. L'avion a ainsi, à leur insu, perdu rapidement de l'altitude ce qui a conduit au crash. Ainsi, un défaut de conception dans l'ergonomie de l'interface homme machine s'est révélé être aussi dangereux qu'un bogue.

■ Le long terme

Le manque de vision à long terme des développements de logiciels est aussi la source de nombreux déboires. Le problème le plus fréquent est causé par l'introduction de limites arbitraires d'adressage ou de précision dont voici deux illustrations.

• Dans les années cinquante, pour économiser l'espace, seuls, les deux derniers chiffres de l'année calendaire étaient stockées. De nombreux programmes développés à cette époque étant toujours utilisés en l'an 2000, il a fallu dépenser des milliards d'euros pour corriger ce fameux "bogue de l'an 2000".

• Le système de défense anti-aérienne Patriot n'a pas intercepté un missile SCUD qui a fait 28 victimes au nord de l'Arabie Saoudite durant la première guerre du Golfe à cause d'un décalage de son horloge, le système ayant été initialement conçu pour intercepter des avions, et non des missiles balistiques plus rapides.

Page 15: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LE GENIE LOGICIEL ───────────────────────────────────────────────────

15

2. DEVELOPPEMENT D'UN LOGICIEL

Les étapes de développement d'un logiciel sont présentées en séquence.

■ Analyse

L'analyse consiste à déterminer les contraintes d'utilisation et d'exploitation du logiciel et les besoins de l'utilisateur. Voici quelques questions typiquement abordées lors de cette phase.

• Type de programme (transactionnel, temps réel, bureautique, etc.).

• Entrées et sorties.

• Matériel et système d'exploitation utilisé.

• Temps de développement et personnel disponible pour développer l'application.

• Extensions futures les plus probables.

• Destinataires (grand public ou professionnels).

■ Spécification

La spécification, réponse formelle aux besoins identifiés à l'analyse, comprend des données chiffrées résultant des spécifications (temps de développement, configurations matérielles et logicielles requises, nombre d'utilisateurs, temps de réponse, entrées et sorties, version préliminaire de la documentation, etc.).

■ Documentation

La documentation fait partie intégrante du développement du logiciel sous la forme d'un document technique normatif de référence formalisant sa conception :

• Architecture générale.

• Découpage en modules avec leurs fonctions respectives, leur interface d'accès et les protocoles de communication utilisés.

• Description des composants logiciels utilisés (bibliothèques, générateurs d'interface ou de requêtes, composants réutilisables, etc.).

2.1 Méthodologie de programmation

■ La méthode des approximations successives

La programmation met en évidence des erreurs de conception nécessitant une modification des modules donc des documents rédigés à l'étape précédente.

■ Programmation et chirurgie

Programmer est long et n'est pas une tâche d'exécutant. Ainsi, les éditeurs les plus performants utilisent le modèle de l'équipe chirurgicale, le chirurgien responsable des parties les plus délicates étant supporté par l'équipe chargée des parties plus simples et de la documentation.

Page 16: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

16 CHAPITRE I ───────────────────────────────────────────────────

■ Preuves de programmes

La garantie qu'un programme correspond à ses spécifications et qu'il ne comporte pas d'erreurs en est la preuve mathématique par analyse formelle, réalisée uniquement à partir de spécifications non ambiguës et parfaites. Or, la complexité d'une telle preuve peut être très grande et les logiciels d'analyse formelle limités. Ainsi, il n'existe aucun algorithme pouvant identifier une boucle infinie.

L'analyse formelle n'apporte donc que des aides partielles à la preuve, d'application limitée car imposant une méthodologie de développement très rigide.

■ Jeux d'essais

On se rabat sur des simulations ou tests de validation d'un logiciel dont il faut vérifier l'intégralité des modules. Deux approches sont utilisées :

• des tests ciblés sur les conditions limites comme le fonctionnement d'une procédure de tri sur un tableau de taille nulle ou contenant des entrées identiques.

• Des tests en utilisation réelle, généralement faits par un client enthousiaste ou impatient, réalisés sur des versions préliminaires du logiciel, appelées alpha ou bêta releases. C'est ce qu'a fait à 500 000 exemplaires Microsoft avant la sortie de Windows 95 ce qui n'a pas suffit à éliminer ses bogues.

■ Maintenance et support

La maintenance d'un logiciel consiste à en corriger les bogues, à en étendre les fonctionnalités, et à en assurer le portage sur différentes plateformes matérielles et systèmes d'exploitation. Elle est coûteuse (70% du coût total de développement) et son importance souvent sous-estimée. Ainsi, le logiciel Word, développé par une équipe de quatre personnes, est maintenu par une équipe bien plus nombreuse.

Le support d'un logiciel consiste à assurer un service de formation et d'assistance auprès de la clientèle. Son coût n'est pas non plus négligeable.

2.2 La chaîne de développement

■ Cycle du développement

La conception d'une application doit respecter le cycle suivant :

• Ecriture du programme dans un langage évolué : c'est le code source.

• Transformation du code source en langage machine : c'est la compilation.

• Recherche par l'éditeur de liens dans les bibliothèques des références non satisfaites à la compilation pour la génération du code exécutable.

Divers outils permettent de créer des applications : éditeur de texte, générateur de programmes, analyseur syntaxique et sémantique, outils de génie logiciel et utilitaires.

■ Editeur de textes

Un éditeur de texte permet de modifier des mots d'une ligne ou d'un programme, d'insérer, de modifier, de déplacer, de dupliquer des lignes dans un texte, etc.

Page 17: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LE GENIE LOGICIEL ───────────────────────────────────────────────────

17

■ Mise en forme et analyse syntaxique

Un enjoliveur de programmes met en forme un fichier source.

La vérification de la grammaire d'un programme (source) est effectuée par le compilateur ou préalablement à la compilation par un analyseur syntaxique qui retourne les messages d'erreurs et/ou d'avertissement lorsqu'il détecte des incohérences de syntaxe ou des ambiguïtés. L'utilisation d'un tel outil est recommandée pour éliminer tous les messages d'avertissement (warning) ou d'erreur.

■ Compilation et édition de lien

• L'adressage symbolique associe une adresse en mémoire ou une valeur à un symbole alphanumérique, référencé dans une instruction. Cette référence est interne si le symbole est défini dans le programme, externe sinon.

• Un symbole peut être référencé avant sa définition par une référence en avant.

• Une référence est satisfaite par la définition dans la table des symboles d'une correspondance entre un symbole et son adresse.

• La plupart des compilateurs procèdent en deux phases :

◊ phase 1 : construction de la table des symboles rencontré dans le programme,

◊ phase 2 : achèvement de la table des références en avant.

• L'éditeur de lien cherche dans les bibliothèques les références non satisfaites à ce stade pour construire le fichier exécutable.

■ Langage machine

Le langage machine est spécifique au calculateur. Toutes les instructions en langage machine sont représentées par une suite de 0 et 1 sous la forme :

CO CA AD code condition zone opération d'adressage adresse

� Exemple

Soit l'instruction (codée ici en hexadécimal et en binaire) sur 32 bits

Hexadécimal 10 25 0208 Binaire 0001 0000 0010 1001 0000 0010 0000 1000

■ Outils de génie logiciel

Un débogueur symbolique (debugger) contrôle l'exécution d'un programme et permet l'examen de ses données pour identifier l'origine d'erreurs d'exécution.

La génération de fichiers exécutables peut être réalisée à partir :

• de fichiers source, éventuellement de différents langages,

• de fichiers binaires compilés,

• de fichiers édités de telle sorte que l'on puisse modifier un fichier (source, bibliothèque,...) sans recompiler tout l'ensemble, surtout s'il est important.

Page 18: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

18 CHAPITRE I ───────────────────────────────────────────────────

■ Profileur

Un profileur génère des statistiques d'exécution pour identifier d'éventuelles séquences inutilisées (code mort).

■ Outils de test

Les tests sont essentiels au développement et la maintenance d'un logiciel.

• Un test de régression permet de vérifier qu'une modification n'a pas introduit d'erreur donc n'a pas fait régresser le logiciel.

• Un test de couverture contrôle l'intégralité d'un logiciel.

■ Automatiseur et gestionnaire de versions

Deux outils complètent la chaîne : le générateur de fichier exécutable à partir des ses dépendances (fichiers sources, objets, bibliothèques, etc.) présenté en annexe et les gestionnaire des différentes versions d'un logiciel comme SCCS sous Unix/Linux.

Analyse

Editeur de texte

Fichier source

Enjoliveur Vérificateur

Compilateur

Fichier (source) à inclure

Fichier objet Bibliothèques système Bibliothèques utilisateur

Fichier exécutable Débogueur Profileur

Exploitation

Editeur de liens

Automatisation

Archiveur

Page 19: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ALGORITHMIQUE ELEMENTAIRE

1. DEFINITIONS

Le mot algorithme dérive du nom du mathématicien Mohammed Al-Khoarizmi, auteur du traité d'arithmétique : "Règles de restauration et de réduction".

Un problème calculatoire est une relation binaire entre un ensemble d'entrées (données) et un ensemble de sorties (résultats) qu'il faut identifier.

Un algorithme est une méthode mathématique de résolution d'un problème calculatoire donné.

Un calcul effectif est une suite d'opérations élémentaires effectuées à la main.

2. MISE AU POINT ET PREUVE D'UN ALGORITHME

La mise au point d'un algorithme nécessite d'analyser le problème pour en déduire un énoncé mathématique permettant d'obtenir les résultats recherchés, préalablement à sa programmation

L'algorithme retenu doit avoir les qualités suivantes :

■ Robustesse

Il doit être robuste pour fournir un résultat exact quelles que soient les données.

Tous les cas doivent avoir été prévus de telle sorte qu'il existe toujours un traitement correspondant à la situation du problème (toujours particulier) à résoudre.

■ Preuve de l'algorithme

Il faut au moins vérifier, à défaut de pouvoir le prouver théoriquement, que l'algorithme retenu fournit la solution théorique et définir, préalablement au passage sur ordinateur, un jeu complet d'essais devant représenter l'univers des cas possibles.

■ Programmation d'un algorithme

L'algorithme retenu doit être transcrit dans un langage de programmation, choisi en principe en fonction du problème à résoudre. Cette dernière étape est à priori la plus rapide l'écriture du programme devant être ramenée à une simple traduction de l'algorithme dans le langage de programmation retenu.

Le compilateur ou l'interprète de commandes permet de traduire le problème en langage machine, selon le type du langage choisi, interprété ou compilé.

CHAPITRE II

Page 20: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

20 CHAPITRE II ───────────────────────────────────────────────────

3. COMPLEXITE, PROBLEMES NP-COMPLETS

■ Complexité

La complexité d'un problème est le nombre d'opérations nécessaires à sa résolution. Elle doit être évaluée pour déterminer à priori le temps de calcul.

La solution théorique de certains problèmes est inexploitable, comme par exemple celle qui implémente simplement les règles du jeu d'échecs : un ordinateur peut parfaitement battre le champion du monde dans la mesure où il peut faire, de façon itérative, l'inventaire de toutes les solutions, en ...1 siècle de calcul pour les ordinateurs les plus performants actuellement.

L'algorithme retenu doit donc, sans être aberrant, réaliser un compromis entre sa complexité et sa difficulté de mise en œuvre. Souvent, les algorithmes les plus simples sont les plus efficaces.

� Exemple

La résolution d'un système d'équations linéaires par la méthode de Cramer a un temps de calcul très important le calcul d'un déterminant d'une matrice carrée d'ordre n par cette méthode nécessitant n! opérations.

L'inversion d'un système linéaire par cette méthode nécessite donc O((n+1)!) opérations ce qui devient rédhibitoire dès que n est "suffisamment" grand. En comparaison, la méthode de Gauss de résolution de systèmes linéaires par

triangulation ne nécessite que O(n2) opérations.

■ Problèmes NP-complets

Les problèmes dont la complexité n'est pas polynomiale et dont il est impossible de calculer le nombre d'opérations nécessaires à leur résolution sont dits NP-complets.

4. COMPORTEMENT ASYMPTOTIQUE

L'analyse de la performance d'un algorithme permet de déterminer les ressources matérielles nécessaires à son exécution : temps de calcul, mémoire, nombre d'accès disque pour un algorithme de gestion de bases de données opérant sur une grande quantité d'informations, etc.

■ Comportement asymptotique

Le comportement asymptotique d'un algorithme est une borne supérieure caractéristique de ses entrées en O(f(n)), où f est un polynôme en n ou une fonction logarithmique en log(n).

� Exemple

Le nombre d'éléments à trier soumis à un algorithme de tri, les nombres de sommets et d'arêtes pour le parcourt d'un graphe.

Page 21: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ALGORITHMIQUE ELEMENTAIRE ───────────────────────────────────────────────────

21

L'analyse du comportement asymptotique d'un algorithme distingue trois situations : l'analyse du cas pire, l'analyse en moyenne, l'analyse amortie.

• L'analyse du cas pire détermine une borne supérieure du temps d'exécution.

• L'analyse en moyenne détermine une borne supérieure du temps d'exécution moyen en définissant un espace de probabilités sur l'ensemble des entrées selon leur taille.

• L'analyse amortie fournit des résultats en moyenne sans hypothèse probabiliste. Elle détermine une borne supérieure du temps d'exécution d'une séquence d'opérations algorithmiques dont le temps d'exécution moyen est obtenu par division du temps total par le nombre d'opérations.

■ Algorithmes presque toujours corrects

Un algorithme probabiliste tire des nombres au hasard pour prendre des décisions et fournit un résultat correct avec une probabilité arbitrairement proche mais non égale à 1. Ce concept a permit de déterminer de très grands nombres premiers.

5. ENONCES DE PROBLEMES

■ Recherche d'une valeur dans un tableau

Entrées A, un tableau d'entiers, contenant n éléments, notés (A[0],...,A[n-1]); X, un entier;

Sorties Recherche, s'il existe, du plus petit entier positif i tel que A[i] = X; sinon n.

Algorithme Tous les éléments du tableau sont examinés jusqu'à ce qu'à la première valeur de i telle que A[i] = X ou n si aucun élément de A n'est égal à X.

Cet algorithme est optimal si aucune information sur le contenu de A n'est connue. Si les éléments de A sont triés par ordre de croissant, la recherche est beaucoup plus efficace, avec un temps proportionnel à log(n). En voici une réalisation en langage C :

int recherche_element_tableau(const int a[], int n, int x) { int i; // Tableau a[], dimension n, élément recherché x

for (i = 0; i < n; i++) if (x = = a[i]) return i; return n; }

■ Problème du tri

Entrées Un tableau d'entiers A, contenant n éléments, notés (A[0],...,A[n-1])

Sorties Une permutation σ de {0,...,n-l} telle que la suite (A[σ

0],..., A[σ

n-1]) soit ordonnée par

ordre croissant.

Page 22: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

22 CHAPITRE II ───────────────────────────────────────────────────

■ Problème du représentant de commerce

Entrées Un ensemble fini de villes {V

1,...,V

n},

dij la distance entre Vi et Vj pour tout couple (Vi,Vj), i≠j

B >0 la distance effectivement parcourue par le représentant de commerce.

Sorties On cherche une permutation σ de {1,..., n} telle que la visite des villes dans l'ordre indiqué par σ conduit à une distance totale minimale :

d dn i n i iσ σ σ σ1 1 1 1+ ∑≤ ≤ − + ≤ B

Ce problème NP-difficile n'a pas aujourd'hui de solution polynomiale connue.

■ Problème de l'arrêt

Entrées Deux chaînes de caractères, FN et DATA, FN étant l'appellation d'une fonction.

Sorties La réponse à la question: l'exécution de la fonction FN sur les données DATA est-elle terminée ?

Il n'existe aucun algorithme de résolution de ce problème indécidable.

6. METHODES DE CONCEPTION D'ALGORITHMES

6.1 Relativité du choix d'un algorithme • Les conditions et limites d'utilisation d'un algorithme sont relatives à certaines

situations qui doivent être clairement identifiées avant son utilisation.

• Des algorithme différents sont souvent possibles.

• Un algorithme efficace sur un problème dont la complexité (spatiale ou temporelle) est faible peut s'avérer inefficace quand cette dernière croît.

• Les gains apportés par l'emploi d'un algorithme adapté sont beaucoup plus importants que ceux apportés par l'usage d'un ordinateur plus puissant.

• L'augmentation de la puissance des processeurs permet d'obtenir des résultats plus rapidement. Elle ne garantit aucunement qu'ils sont meilleurs.

Page 23: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ALGORITHMIQUE ELEMENTAIRE ───────────────────────────────────────────────────

23

6.2 L'abstraction procédurale, ancêtre de l'objet L'abstraction procédurale a pour objectif le développement d'applications complexes en minimisant les dépendances entre les modules et la communication entre les équipes de développement.

■ Mécanismes d'abstraction

Les mécanismes d'abstraction simplifient la conception et la réalisation de logiciels en distinguant l'information nécessaire à son utilisation de celle nécessaire à sa réalisation.

L'abstraction représente le fait de considérer à part un élément (qualité ou relation) d'une représentation ou d'une notion en portant spécialement l'attention sur lui et en négligeant les autres.

Une bonne abstraction dissimule une action, quelquefois complexe, derrière une interface simple à utiliser. La difficulté de la conception réside dans son choix.

On distingue l'abstraction procédurale de l'abstraction des données.

■ L'abstraction procédurale

L'abstraction procédurale implémente une action (calcul, action de contrôle, manipulation de données, etc.) dans un programme utilisable sans nécessité de connaître les détails de sa réalisation. Ainsi, l'utilisation d'une procédure de tri n'impose pas la connaissance de l'algorithme de tri utilisé par celle-ci.

■ L'abstraction des données et les structures de données abstraites

Une structure de données abstraite est une organisation de l'information qui en rend l'accès efficace pour certains traitements tel l'index d'un annuaire téléphonique pour une recherche. Ainsi, les applications sont organisées autour de la manipulation de certains types de données : fichiers, processus, fenêtres, transactions, relations, etc.

L'abstraction des données masque leur représentation interne et n'autorise leur manipulation qu'au travers de procédures de traitement spécifiques appelées méthodes, dérivant de leur définition mathématique.

Un type abstrait est constitué d'un ensemble de valeurs et procédures nécessaires à la manipulation des données abstraites (accès, modification).

� Exemple

Dans le système d'exploitation UNIX, un fichier est la représentation abstraite d'un objet sur lequel est effectuée une opération d'entrée/sortie à partir d'une sémantique d'utilisation uniforme (ouverture, lecture, écriture, fermeture), quelle que soit sa nature, données abstraites ou périphériques (Cf. annexe).

■ La Programmation Orientée Objet

L'abstraction des données est une idée très forte issue du génie logiciel de ces vingt dernières années dont l'usage simplifie considérablement l'architecture des applications. Possible et complexe avec un langage procédural comme le C développé historiquement pour implémenter le système Unix, elle a été introduite dans la grammaire des langages orientés objet tels C++ et Java.

Page 24: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

24 CHAPITRE II ───────────────────────────────────────────────────

6.3 Partages et invariants L'abstraction procédurale comme l'abstraction des données permettent de respecter un des principes les plus importants du développement logiciel : le principe du partage.

• Aucune partie (programme ou donnée) d'un logiciel ne doit être dupliquée.

• Toute violation de ce principe est une source d'erreurs donc de surcoûts pour sa maintenance car il est fréquent d'oublier de modifier une des multiples occurences d'une donnée ou d'une fonction, créant ainsi des inconsistances.

• Le principe du partage est un excellent guide pour déterminer les bonnes abstractions bases de l'architecture d'un logiciel.

Un invariant est une propriété d'un programme vérifiée à un point donné de son exécution. Ce concept essentiel est à la base des méthodes itératives ou récursives.

6.4 Méthodes par décomposition Dans ce qui suit, le mot problème désigne son sens abstrait de problème calculatoire ou son sens concret de problème calculatoire avec un jeu d'entrées donné.

Un algorithme fait référence au sens abstrait.

La taille d'un problème fait référence au sens concret.

Un sous problème P' d'un problème P est un problème concret correspondant au même problème abstrait que P dont le jeu d'entrées forme une sous partie du jeu d'entrées de P.

On suppose ici que la taille d'un problème peut être caractérisée par un entier n, par exemple le nombre d'élément à trier induit la taille du tableau utilisé par la procédure.

■ Diviser pour régner

La résolution d'un problème complexe se ramène, par décompositions successives, à une suite de sous problèmes dont la résolution est immédiate. Ainsi, un problème P de taille n peut souvent être décomposé en sous problèmes (P

1,...,P

k) de taille plus petite,

de telle sorte que les solutions des sous problèmes (P1,...,P

k) puissent être combinées

efficacement pour fournir une solution du problème P.

Selon ce principe, les sous problèmes (P1,...,P

k) peuvent être à leur tour décomposés,

en sous problèmes suffisamment simples pouvant être résolus directement.

Un algorithme conçu selon ce principe résout un problème par application successive de la même méthode de calcul sur des sous problèmes de taille successivement plus petite. La programmation de tels algorithmes est effectuée généralement par récursion, c'est-à-dire en utilisant des procédures qui s'appellent elles-mêmes. La correction et l'analyse de leur performance s'établissent généralement par induction. La méthode "diviser pour régner" (du latin divide et impera) est donc l'équivalent algorithmique de l'induction mathématique.

■ Programmation dynamique

La programmation dynamique est une méthode informatique de recherche de la solution optimale d'un problème à partir de solutions optimales de sous problèmes.

Page 25: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ALGORITHMIQUE ELEMENTAIRE ───────────────────────────────────────────────────

25

6.5 Programmation modulaire La décomposition d'un problème en problèmes d'une complexité moindre conduit à la programmation modulaire.

Les critères de modularité suivants ont été définis :

■ Décomposabilité modulaire

Méthode de conception permettant de décomposer un problème en plusieurs sous problèmes dont la solution peut être recherchée séparément.

■ Composabilité modulaire

Méthode favorisant la production d'éléments de logiciels pouvant être combinés librement les uns avec les autres pour produire de nouveaux systèmes, comme par exemple les bibliothèques de programmes.

■ Compréhension modulaire

Méthode de production d'éléments dont chacun peut être compris isolément.

■ Continuité modulaire

Méthode de conception telle qu'une petite modification de la spécification du problème n'amène à modifier qu'un seul (ou peu de) module(s) de l'application.

■ Protection modulaire

Méthode de conception telle que l'effet d'une condition anormale se produisant lors de l'exécution d'un module y reste localisée ou tout au moins ne se propage qu'à quelques modules voisins.

■ Unités modulaires linguistiques

Chaque module abstrait doit correspondre à une unité syntaxique du langage.

■ Peu d'interfaces

Tout module doit communiquer avec un minimum de modules (appel, partage de données) et la quantité d'informations échangées doit être minimale.

■ Interfaces explicites

Les informations échangées entre deux modules doivent être connues et accessibles par chacun.

Page 26: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

26 CHAPITRE II ───────────────────────────────────────────────────

7. STRUCTURES DE CONTROLE ET METHODES ITERATIVES

7.1 Test et branchement sur condition

■ Test et sémantique

Un test sélectionne une partie du programme selon un critère logique. En programmation structurée, un test a la forme générale est la suivante :

si <condition logique> alors action1 sinon action2 is

■ Branchement conditionnel

L'instruction correspondant à l'action à exécuter peut être étiquetée.

Forme générale aller_à si < condition logique > alors aller_à instruction_étiquetée

� Exemple 1

lire x si x > 0 alors aller_à impression sinon x = -x is impression : imprimer x

� Exemple 2

Résolution d'une équation du second degré à coefficients réels : soient a,b,c réels. On

cherche x solution de l'équation ax2 + bx + c = 0. Soit delta = b

2 - 4ac.

si delta ≥ 0 alors x=a2

deltab ±− is sinon pas de solution réelle is

Algorithme Lire a,b,c calculer delta = b2 - 4ac si delta < 0 aller_à Delta négatif

x1=a2

deltab +− x2=

a2

deltab −−

aller à Fin du calcul Delta négatif Imprimer "pas de solution". Fin du calcul

Cet algorithme, écrit dans un langage voisin de BASIC, ne tient pas compte des cas particuliers (a = 0, b ≠ 0, c quelconque), (a = b = 0, c ≠ 0 ou c = 0).

Page 27: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ALGORITHMIQUE ELEMENTAIRE ───────────────────────────────────────────────────

27

10 lire a,b,c 20 si a = 0 aller_à 200 30 ' cas a 0 40 delta = b*b - 4*a*c 50 si delta > 0 aller_à 100 60 si delta négatif aller_à 400 70 ' delta = 0' 80 x = -b/(2*a) 90 aller_à 500 100 ' delta = positif 110 x1 = (-b + racine(delta))/(2*a) 120 x2 = (-b - racine(delta))/(2*a) 130 aller_à 500 200 ' a = 0' 210 si b = 0 aller_à 300 220 x = -c/b 230 aller_à 500 300 si c = 0 aller_à 330 310 imprimer 'impossible' 320 aller_à 500 330 imprimer 'impossible' 340 aller_à 500 400 imprimer "pas de racine réelle" 500 FIN

Ce programme peu lisible ne fonctionne pas car tous les cas ne sont pas traités. L'algorithme suivant, sans branchement est lisible, clair et efficace.

Début Lire a,b,c. si a = 0 alors

si b≠ 0 alors xc

b= −

sinon si c = 0 alors indétermination sinon impossibilité is is sinon

delta = b ac2 4−

si delta = 0 alors x = − b

a2; imprimer "racine double "

sinon si delta < 0 alors imprimer "pas de racine réelle" sinon

a2

deltabx,

a2

deltabx 21

−−=+−=

imprimer x1, x2 is is is

Fin du calcul

Les différentes parties du programme, effectuant chacune une action différente, apparaissent nettement. La structure de bloc des langages de programmation structurés permet de les implémenter.

Page 28: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

28 CHAPITRE II ───────────────────────────────────────────────────

7.2 Généralités sur les méthodes itératives On souhaite éditer le bulletin de salaire des employés d'une entreprise.

■ Méthode 1

Calcul du salaire du 1er employé; édition ... Calcul du salaire du dernier employé; édition. C'est long surtout s'il y a 10.000 employés.

■ Méthode 2

Du 1er jusqu'au dernier employé de la firme Faire Calcul du salaire; édition Recommencer

Ce qui est équivalent à la séquence :

Nombre total d'employés : nombre Pour n = 1 jusqu'à nombre faire // Marque de début de boucle Calcul du salaire de l'employé n; édition // Corps de la boucle FinFaire // Marque de fin de boucle

■ Exécution répétitive d'une action

Une séquence d'instructions représente une action, susceptible d'être répétée pendant l'exécution du programme. C'est la structure de bloc des langages Pascal, C, Java, etc.

Une itération exécute une action un certain nombre de fois, fixé ou non.

Toutes les actions définies entre les marques de début et de fin de boucle constituent le corps de la boucle dont il est nécessaire de prévoir l'arrêt. C'est le test de fin de boucle dont l'importance de la position dans la boucle apparaît ci-dessous.

� Exemple 1

n = 1 calcul : calculer le salaire de l'employé n Edition n = n+1; si n < nombre alors aller en calcul sinon fin is

Le test de fin de boucle étant effectué à la fin de la boucle, celle-ci est exécutée au moins une fois, même s'il n'est pas vérifié.

� Exemple 2

n = 1 test : si n > nombre alors Fin du travail

sinon calcul du salaire de l'employé n et édition; n = n+1; aller à test

is Le test est effectué en début de boucle qui ne s'exécute pas si le test n'est pas vérifié.

Page 29: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ALGORITHMIQUE ELEMENTAIRE ───────────────────────────────────────────────────

29

7.3 Boucles

■ Indice de boucle

Initialisé préalablement à l'exécution de la boucle, l'indice de boucle ne doit pas être modifié pendant une itération. Incrémenté en fin d'itération d'une valeur appelée pas de boucle, il est comparé à une variable définissant la condition d'arrêt de la boucle. Cette action préalable à toute itération peut éviter une action inutile et parfois néfaste.

■ Boucle à nombre d'itérations fixé et borné

Le nombre d'itérations connu avant l'exécution de la boucle est contrôlé avec l'indice de boucle.

Sémantique Pour <indice variant de valeur initiale à valeur finale > Faire instruction(s) ou action FinFaire

� Application au cas précédent

Pour n = 1 jusqu'à nombre faire Calculer la paie de l'employé n et édition FinFaire

■ Boucles à condition d'arrêt

Le nombre d'itérations étant inconnu avant l'exécution de la boucle, son arrêt est contrôlé par une condition logique d'arrêt (sinon, la boucle devient "perpétuelle").

• Effectué avant l'itération, cette structure de contrôle est la clause Tant que.

• Effectué en fin d'itération, la boucle s'exécute au moins une fois (clause Répéter).

Sémantique des deux boucles Tant que < condition d'arrêt non vérifiée > Faire Répéter Action Action FinFaire Jusqu'à < condition d'arrêt vérifiée >

� Exemple

n = 1 Répéter imprimer n, n*n; n = n+1; Jusqu'à n > 100

■ Equivalence des clauses Tant que et Répéter

Tant que < condition > faire si condition alors Répéter Instruction(s) Instruction(s) FinFaire jusqu'à is

Page 30: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

30 CHAPITRE II ───────────────────────────────────────────────────

7.4 Méthodes itératives et approximations successiv es Un programme itératif est basé sur le raisonnement par récurrence :

• On vérifie que l'algorithme est correct pour n petit.

• On suppose qu'il fonctionne à l'ordre n-1 (hypothèse de récurrence) pour démontrer qu'il fonctionne à l'ordre n.

Le résultat recherché est alors obtenu par approximations successives, chaque instruction exécutée s'en rapprochant.

Une méthode générale de conception d'un algorithme itératif est la donc suivante :

• Recherche d'un invariant de boucle (hypothèse de récurrence).

• Si c'est fini, alors sortir de la boucle.

• Se rapprocher de la solution et rétablir l'hypothèse de récurrence si nécessaire.

• Des valeurs satisfaisant l'hypothèse de récurrence doivent initialiser le processus.

� Exemple 1

n! = fac(n)

Hypothèse de récurrence

A l'étape i, fac(i) est supposé connu.

Boucle si i = n alors fini is

// A l'étape i+1 fac(i+1) = (i+1)*fac(i) i = i+1 // Rétablissement de l'hypothèse de récurrence aller_à Boucle

Initialisation i = 1 ; fac(i) = 1

� Exemple 2

xn = p(x,n)

Hypothèse de récurrence

On construit par récurrence la suite (x1, ..., x

i) = (p(x,1) ..., p(x,i)) ∀ i = 1,2,…n

Boucle si i > n alors fini

sinon p(x,i+1) = x*p(x,i) ; i = i+1 aller_à Boucle

is

Initialisation p(x,1) = x

Page 31: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ALGORITHMIQUE ELEMENTAIRE ───────────────────────────────────────────────────

31

7.5 Le problème du drapeau Hollandais Soit (a

1,...,a

n) une suite non ordonnée d'entiers à ordonner de la façon suivante :

les entiers de la liste congrus à 0 modulo 3 sont classés à gauche, les entiers de la liste congrus à 2 modulo 3 sont classés à droite, les entiers de la liste congrus à 1 modulo 3 sont classés "au milieu".

■ Méthode de calcul : hypothèse de récurrence et notations

Soient : i l'indice du 1er élément congru à 1 modulo 3, j l'indice de l'élément courant, que l'on va trier, k l'indice du 1er élément congru à 2 modulo 3.

Avant le tri, à l'indice j, on est dans la situation suivante : i-1 nombres congrus à 0 modulo 3 sont triés, en place, j-i nombres congrus à 1 modulo 3 sont triés, en place, n-k+1 nombres congrus à 2 modulo 3 sont triés, en place.

tableau (≡ 0, ≡ 1, x, ,≡ 2 ...) indice 1 i j k …. n états triés triés non triés triés

Soit r = mod(aj, 3) le reste de la division de a

j modulo(3). Alors

si r = 1 alors aj est à sa place is

si r = 2 alors permuter (aj, a

k-1) is

si r = 0 alors permuter (ai, a

j) is

� Recherche d'une hypothèse de récurrence et d'un invariant de boucle.

Test d'arrêt si j = k alors FINI is

Se rapprocher de la solution en préservant l'hypothèse de récurrence Soit a

j, nouvel élément de la liste à trier. Il y a 3 situations à évaluer :

r = 1 L'élément étant placé, l'hypothèse de récurrence reste vérifiée pour le suivant.

si r = 1 alors j = j+1; SUIVANT is

r = 0 On permute (a

i, a

j) ce qui classe aj sans perturbation du tri antérieur.

(≡ 0 ≡ 1 1 ≡ 2) 1 i j k n

L'hypothèse de récurrence est ensuite rétablie par incrémentation de i et j. si r = 0 alors permuter(ai , aj); // Modifie l'hypothèse de récurrence

i = i+1; j = j+1; SUIVANT // Rétablit l'hypothèse de récurrence is

r = 2 On procède également par échange de a

k-1 inconnu avec a

j connu.

permuter(ai, a

k-1); k = k-1 ; SUIVANT

Page 32: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

32 CHAPITRE II ───────────────────────────────────────────────────

Initialisation du programme Les valeurs initiales doivent respecter l'hypothèse de récurrence et les conditions initiales. Or, le nombre d'éléments en place au départ est tel que :

i-1 = 0 ⇒ i = 1 ; j-i = 0 ⇒ j = i = 1; n-k+1 = 0 ⇒ k = n+1

� Remarques

• A chaque pas de boucle, d(j,k) diminue de 1.

• Le nombre d'échanges est inférieur à n par cette méthode.

• Il existe une autre hypothèse de récurrence, pas forcément meilleure (ici, boucle Tant Que).

Le programme final obtenu est le suivant :

Initialisation k = n+1; i = 1; j = 1

Tri Tant que j ≠ k Faire r = mod(aj,3) si r = 1 alors j = j+1; is // Aj est à sa place donc au suivant si r = 2 alors permuter (aj, ak-1); k = k-1; is // Permuter ak-1, aj si r = 0 alors permuter (a

j, a

i); i = i+1; j = j+1 is

FinFaire

8. RECURSIVITE

8.1 Fonction récursive Une fonction récursive s'appelle elle même.

� Exemple

La fonction n! (factorielle n) notée fac(n) est définie par :

fac(n)=

>=

0n si 1)-fac(n*n

0 n si 1

Ainsi :

fac(4) = 4*fac(3) // fac(3) inconnu, à calculer = 4*3*fac(2) // fac(2) inconnu, à calculer = 4*3*2*fac(1) // fac(1) fin de la phase descendante = 4*3*2*1*fac(0) // jusqu'à l'obtention du résultat

Il faut donc empiler la suite des valeurs inconnues (ici 4!, puis 3!, puis 2!) jusqu'à une valeur connue (1!) ce qui permet alors de calculer les valeurs inconnues, empilées.

Page 33: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ALGORITHMIQUE ELEMENTAIRE ───────────────────────────────────────────────────

33

■ Remarques

• Un algorithme récursif peut souvent être formulé d'une façon itérative. La définition récursive est donc un axiome mathématique, pas toujours une stratégie de calcul.

• Il faut impérativement définir une condition d'arrêt pour éviter une descente infinie.

■ Théorème

Un appel récursif provoque la création d'une pile et l'exécution de deux boucles : une boucle descendante jusqu'à l'arrêt, une boucle ascendante pour la remontée.

La complexité d'un algorithme récursif peut donc être très importante.

Démonstration Une définition générale d'une fonction récursive est la suivante :

f(x)=

(1)sinon d(x) o f(b(x))

est vraie c(x)arrêt d'condition la si a(x) ,

L'opérateur o désigne une loi de composition interne quelconque.

Soit (uk) le terme général de la suite générée par les appels récursifs successifs et soit i la valeur de k tel que la condition d'arrêt c(ui) soit vérifiée.

On pose :

uo = x u1 = b(x) = b(uo) … ui = b(ui-1)

L'application de la formule (1) jusqu'à la vérification de la condition d'arrêt conduit à la relation :

f(x) = f(uo) = f(b(uo)) o d(uo) = f(u1) o d(uo) = f(b(u1)) o d(u1) o d(uo) =...= = f(ui) o d(ui-1) o d(ui-2)o...o d(u1) o d(uo) (2)

De plus, le terme de la suite f(uk) peut être calculé avec la formule (1) d'où la relation :

f(uk) = f(b(uk)) o d(uk) = f(uk+1) o d(uk) ∀k = i-1,...,0 (3)

Le calcule de f(x) nécessite d'empiler préalablement la suite (uk)k=1,i, ce qui permet de calculer proche en proche la suite (f(uk))k=i,0 (phase de descente) puis la suite (uk) (phase de remontée).

Page 34: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

34 CHAPITRE II ───────────────────────────────────────────────────

Algorithme de calcul // Génération et empilement de la suite (uk) k : = 0 ; u : = x ; empiler(u); Tant que non c(u) faire k : = k+1 ; u : = b(u) ; empiler(u) FinFaire

// Génération et dépilement de la suite f(uk) y : = a(u) ; i = k ; Tant que i est non nul faire dépiler ; y : = y o d(u) ; i : = i-1 ; FinFaire

� Exemple

Calcul récursif de xn

On pose, pour x donné :

xn =

==

sinon1)-(np*x(n)p

1 n si x

xx

On a ici, avec les notations de la formule (1) :

a(y) = d(y) = x ; bx(n) = xn-1

et la loi de composition o est la multiplication usuelle.

Alors :

uo = xn inconnu, empilé u1 = bx(n) = xn-1 inconnu, empilé u2 = bx(n-1) = xn-2 inconnu, empilé ... un-1 = bx(2) = x connu, défini par la condition d'arrêt.

On peut maintenant calculer la suite (yk), ∀k = n-1,n-2,---,0 :

yn-1 = a(un-1) = x yn-2 = yn-1 o d(un-2) = x * x = x2 ... yo = y1 o d(uo) = xn-1 * x = xn

■ Remarques

L'algorithme se simplifie dans les cas suivants : l'applications b est inversible ou d est constante et il n'est pas nécessaire d'empiler la suite (uk), l'une des applications a ou c est constante, la loi de composition * est associative, commutative...

Malheureusement, le compilateur ne le détecte pas toujours.

Page 35: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ALGORITHMIQUE ELEMENTAIRE ───────────────────────────────────────────────────

35

8.2 Récursivité et complexité

■ Première formulation récursive de la fonction puissance

De la relation :

xn = x * xn-1

On obtient :

==

sinon1)-np(x,*x1 n si x

)n,x(p

Soit g(x,n) le nombre d'appels récursifs et de multiplications de l'algorithme. On a :

+==

sinon11)-ng(x,1 n si 0

)n,x(g

⇒ g(x,n) = g(x,n-1) + 1 ∀(x,n)

Or g(x,1) = 0 ⇒ g(x,n) = g(n) = n-1 ∀ (x, n)

L'algorithme est de complexité linéaire.

■ Deuxième formulation récursive de la fonction puissance

Cette méthode est la procédure récursive logarithmique. On a :

n)x(x 2

n2n ∀= d'où :

=

=

n de entière partie la ent(n) avec impair,est n si))2

n ent(x,*p(x

pairest nsi)2

n x,*p(x

1 n si x

)n,x(p

Soit g(n) le nombre d'appels récursifs et d'opérations de l'algorithme. On a :

+

+

=

=

impairest n si))2

ng(ent(2

pairest nsi)2

n g(1

1 n si 0

)n(g

Lorsque n est pair, xn est calculé à partir de 2n

x diminuant de moitié la complexité du problème à chaque pas du calcul et le nombre d'appels récursifs à la fonction puissance tend vers log2 (n).

Le cas n impair étant similaire, l'algorithme est de complexité logarithmique.

� Exemple

2 2 2 2 4 2 4 2 16 2 16 2 256 2 256 25625 2 12 12 2 6 6 2 3 3 2= × = × = × = × = × = × = × ×( ) ( ) ( )

Page 36: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

36 CHAPITRE II ───────────────────────────────────────────────────

■ Troisième formulation récursive de la fonction puissance

On a la relation triviale :

qpn x*xx = avec p+q = n

Si n est pair, on a :

2

n

2

nn x*xx = ∀n

Si n est impair, alors

2

1n

2

1nn x*x*xx

−−

= ∀n

D'où la définition de la fonction puissance :

=

=

impairest n si ) 2

1-n(x, p * )

2

1-n(x, p*x

pairest nsi) 2

n(x, p * )

2

n(x, p

1 n si x

)n,x(p

Une double évaluation à chaque pas du calcul est évitée en posant :

z = )) 2

nent((x, p avec ent(n) la partie entière de n.

On obtient alors :

==

impairest n si z*z *xpairest nsiz * z

1 n si x )n,x(p

Comme dans le cas précédent, l'algorithme est de complexité logarithmique.

8.3 Suite de Fibonacci

■ Définition

u1 = u2 = 1 un = un-1 + un-2 ∀n >2

■ Méthode récursive

On applique simplement la relation :

u1 = u2 = 1 un = f(n) = f(n-1) + f(n-2) ∀n >2

Le calcul de f(n-1) nécessite de calculer f(n-2) et f(n-3), celui de f(n-2) nécessite de calculer f(n-3) et f(n-4), etc. Le temps de calcul croît donc exponentiellement avec l'exposant puisque le nombre d'appels récursifs g (n) vaut :

g(n) = 2n-1

+ 2n-2

= 3*2n-2

∀n

Page 37: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ALGORITHMIQUE ELEMENTAIRE ───────────────────────────────────────────────────

37

■ Méthode itérative classique

i = 1; p = u1 = p = 1; q = u2 = 1; Faire si i = n + 1 alors Fini is i = i+1; r = p p = p+q ; // Calcul de un q = r FinFaire

Le temps de calcul croît linéairement avec l'exposant.

■ Méthode itérative de Gries

Cet algorithme, d'une complexité en O(log(n)), s'écrit sous la forme matricielle suivante :

==

−−

=

− 1

10111

...)2n(f)1n(f

0111

)1n(f)n(f

n

8.4 Tours de Hanoi Ce problème amusant illustre à la fois l'élégance d'une formulation récursive tout en mettant en évidence :

• sa complexité exponentielle.

• la formulation itérative induite, moins intuitive, finalement beaucoup plus simple et de complexité linéaire.

■ Position du problème

Soient trois tours numérotées T1, T2, T3. On suppose que sur une des tours (T1 par exemple) se trouvent n disques de diamètre respectif Di tels que

Di > Dj, ∀i , j pour 1 ≤ i < j ≤ n .

On a donc la condition :

Dn < Dn-1 < Dn-2 < ... < D1 ∀n

Le problème est de transférer n disques de T1 à T3 avec T2 comme tour intermédiaire, l'ordre initial des disques devant être conservé pendant le transfert et à l'arrivée.

■ Notations

T1 = TO (tour origine) T2 = TI (tour intermédiaire) T3 = TD (tour de destination)

Page 38: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

38 CHAPITRE II ───────────────────────────────────────────────────

■ Hypothèse de récurrence sur le nombre de disques transférés.

On suppose que n-1 disques peuvent être transférés de TO à TD en passant par TI ce qui est trivialement vérifié pour n = 2 ou 3.

Il faut démontrer que n disques peuvent être transférés de TO à TD en passant par TI.

� Démonstration

Le transfert de n disques se décompose toujours en deux phases : le transfert des n-1 disques supérieurs puis celui du disque restant.

• L'hypothèse de récurrence justifie le transfert de n-1 disques. Deux cas se présentent : les disques peuvent être transférés sur TD par TI ou l'inverse. Or ce dernier cas est impossible car il imposerait de mettre le disque Dn sur le dessus de

la pile. Les disques sont donc transférés sur TI.

• Le disque restant Dn est transféré sur TD puis les n-1 disques de TI sont à leur tour

transférés sur TD en utilisant TO (justifié par l'hypothèse de récurrence).

Chaque pas de calcul est donc composé des trois étapes :

• Transfert des n-1 disques ordonnés de TO à TI en passant par TD.

• Transfert du disque Dn de TO à TD.

• Transfert des n-1 disques ordonnés de TI à TD en passant par TO.

■ Nombre d'appels récursifs

Cet algorithme effectue deux appels de procédures récursives à chaque pas de calcul. Soit g(n) le nombre d'appels récursifs pour transférer n disques. Alors

g(1) = 1

g(n) = g(n-1) + g(1) + g(n-1) = 2g(n-1) + 1

⇒ g(n) + 1 = 2(g(n-1)+1) = ... = 2n g(1) ⇒ g(n) = 2n - 1

Le nombre d'appels récursif croît exponentiellement avec le nombre de disques.

■ Conclusion sur les traitements récursifs

La formulation récursive n'apporte pas la solution à tous les problèmes mais est néanmoins très utile.

Dans le présent exemple, la méthode récursive de résolution du problème apparaît naturellement dans la démonstration, est élégante et … se révèle être d'une complexité exponentielle. Elle permet surtout de justifier théoriquement la méthode itérative, de complexité linéaire, dont la formulation (simple) et la démonstration (difficile) sont laissées en exercice au lecteur.

Page 39: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE PROGRAMMATION EN C ET C++

1. QUELQUES REGLES D'OR EN PROGRAMMATION

■ Choix du langage

Le langage n'est que le support de l'algorithme programmé qui en est indépendant. Son choix peut être imposé (disponibilité, stratégie d'entreprise, connaissance préalable du programmeur, etc.) ou être fait selon la nature du problème (calcul scientifique, gestion, application système, programmation Web, etc.). Ainsi, la plupart des logiciels sont développés dans des langages d'usage général tels le C ou le C++. Le langage Fortran est surtout utilisé en calcul scientifique, et Cobol en informatique de gestion, mais les langages C, C++ et Java peuvent aussi être utilisés dans ce type d'applications. Certains développements imposent le langage, par exemple XML pour la programmation de site Web.

■ Maximes

E. Dijkstra a énoncé la maxime suivante : "Essayer un programme peut seulement montrer qu'il contient des erreurs mais ne prouve jamais qu'il est juste". D'où la maxime "Tout programme doit être totalement testé et validé, à chaque modification. Cette action est nécessaire mais non suffisante pour garantir son exactitude".

■ Commentaires et lisibilité

Un programme est destiné à être lu ultérieurement, en général par d'autres. Sa lisibilité est donc essentielle pour le maintenir et le développer.

Il est aussi indispensable de le commenter.

� Exemple

/* Un commentaire en C et C++ */ // Un commentaire en C++

■ Identification des objets

Les noms des objets (variables, fonctions, procédures) doivent être appropriés.

� Exemple

float r; // Que représente le nombre réel r ? float rayon; // C'est mieux

CHAPITRE III

Page 40: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

40 CHAPITRE III ───────────────────────────────────────────────────

Chaque traitement effectué par une procédure ou fonction doit être identifié par une entête qui spécifie son action, ses arguments et leur domaine de validité, ses effets de bord éventuels et ses limitations et qui doit respecter un format permettant l'extraction d'un manuel de référence.

Les variables doivent être regroupées par type, thèmes, etc.

Des commentaires doivent indiquer la méthode de résolution choisie, la description des conventions utilisées pour nommer les variables, les fonctions et les procédures utilisées, les bibliothèques appelées, les éventuelles astuces (à éviter par principe).

� Exemple

// Résolution d'une équation du 2° degré // Coefficients de l'équation a, b ,c à valeurs réelles, solution réelles et complexes // Programmeur : tartempion le 10 Janvier 2018 // Variables utilisées : float a, b ,c // etc.

2. GRAMMAIRE, SYNTAXE, SEMANTIQUE

■ Langage et grammaire

Tout langage de programmation est défini par l'utilisation de règles très précises (grammaire) que le programmeur se doit de respecter de façon impérative. Certains langages sont très rigoureux de telle sorte que le programmeur n'ait aucun choix possible à part le bon (langage Pascal). D'autres sont plus permissifs (le C).

Chaque langage a ses propres règles de syntaxe donc de grammaire.

■ Méta langage

Un métalangage est un langage syntaxique de description des différentes opérations (affectation, boucle, etc.) d'un algorithme.

■ Syntaxe

La syntaxe d'une instruction est son mode d'emploi. Elle est vérifiée par le compilateur ou l'interprète de commandes par une analyse syntaxique préalable.

■ Sémantique

La sémantique d'une instruction correspond à la description de l'action qu'elle exécute.

Une instruction doit être correcte à la fois sur les plans syntaxique et sémantique. Ces deux conditions sont nécessaires et non suffisantes pour qu'elle fournisse un résultat exact, une instruction pouvant être syntaxiquement correcte et sémantiquement fausse comme l'instruction en langage C ci-dessous :

if (i = 1)

dont le résultat est l'affectation de la valeur 1 à i et non sa comparaison à la valeur 1.

Page 41: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE PROGRAMMATION EN C ET C++ ───────────────────────────────────────────────────

41

3. MOTS CLES

Un langage est constitué par un ensemble de mots clés qui représentent les instructions autorisées. Ils ont une syntaxe d'utilisation définie par sa grammaire.

� Exemple

La liste ci-dessous récapitule pratiquement la totalité des mots clés du langage C.

■ Déclarations de type

char double enum float int long short signed sizeof struct typedef union unsigned void

■ Déclarations de la classe de mémorisation

auto extern register static const volatile

■ Structures de contrôle

break case continue default do else for goto if return switch while

4. INSTRUCTION, ACTION

4.1 Objets de base des langages C et C++

■ Objets de base du langage

Tout langage de programmation permet d'accéder à des objets dits "de base". Ainsi, le langage C++ permet, entre autres, à partir des objets scalaires prédéfinis de base (entiers, réels, caractères) de construire des tableaux typés de vecteurs. A chacun de ces objets de base est associé un ou plusieurs types :

Objet déclaration de type Entier naturel int, short, unsigned Entier relatif int, short, signed Rationnel float, double Réel float, double Caractère char Vecteur tableau

■ Opérations sur les objets de base

Les opérateurs du langage permettent de réaliser des opérations sur les objets de base. On distingue :

• les opérateurs arithmétiques +, -, *, /, % (opérateur modulo)

• les opérateurs logiques et, ou , non,

• les opérateurs relationnels, permettant de faire des comparaisons entres objets (relation d'ordre).

Page 42: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

42 CHAPITRE III ───────────────────────────────────────────────────

■ Construction d'objet

Un objet complexe peut être construit à partir d'objets de base. Ainsi, en C, un tableau est un ensemble d'objets de même type stockés consécutivement en mémoire dont chaque élément est accessible par un indice relatif au premier élément.

� Exemple

int a[100]; // Un tableau de 100 entiers

Des objets de type composite (tableau, variables structurées, classe, etc.) peuvent être construits.

� Exemples

L'objet structuré individu est décrit par son nom, prenom, adresse.

struct individu {char nom[20] ; char prenom[20] ; char adresse[50] ;}; struct complexe {float x ; float y}; // Une structure complexe

■ Surdéfinition (surcharge)

Certains langages objet permettent de redéfinir le comportement des opérateurs. Ainsi, il est possible en C++ de (re)définir l'opérateur + pour qu'il permette d'additionner des matrices. Ce principe est appelé la surdéfinition des opérateurs.

4.2 Instruction simple Un programme est une suite finie d'instructions (élémentaires ou complexes).

■ Instruction simple

Une instruction opère sur un ou plusieurs opérandes, des variables ou des expressions en utilisant des opérateurs.

Dans les langages C, C++, Java, toute instruction doit se finir par le terminateur ;

On distingue les instructions exécutables et non exécutables.

� Exemple

int a, b; // Non exécutable en C , exécutable en C++ float c = 45.67; a = 12; // Exécutable b = a+c; if ((a > 1) && (b < 5)) ...; else ...;

Une instruction peut appeler des procédures ou des fonctions, prédéfinies ou non.

� Exemple

x = f(a); // Appel de la fonction f g(y); // Appel de la procédure g

Page 43: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE PROGRAMMATION EN C ET C++ ───────────────────────────────────────────────────

43

■ Alphabet

Les caractères autorisés dans un langage sont définis par un alphabet. En C, les lettres minuscules, majuscules, les caractères alphanumériques, et certains caractères spéciaux utilisés pour constituer les variables symboliques sont autorisées. L'utilisation des mots clés pour définir les variables symbolique est interdite (en PL/1, c'est autorisé). Enfin, les lettres minuscules et majuscules sont considérées comme différentes.

� Exemple

int a = 1; float A = 6.78; char chaine[128] = "bonjour"; char ceci_est_une_longue_chaine[128] = "anticonstitutionnellement";

■ Définition

La définition d'une variable lui donne naissance.

Selon la grammaire utilisée, il est obligatoire (Pascal ou C) ou facultatif (Fortran) de définir préalablement les variables utilisées.

A chaque objet est associée une variable symbolique du programme, accessible par un identificateur qui doit suivre les règles de grammaire du langage. C'est en général un ensemble de caractères alphanumériques.

� Exemple

// Définition de variables int i; // L'entier i float a,b; // Deux réels a et b char chaine[10] = "bonjour"; // Une chaîne de caractères

■ Déclaration

Une déclaration est indicative. Elle peut être obligatoire (C++). Ainsi, la déclaration :

float somme(int, int);

indique que somme est une fonction à valeur réelle avec deux variables entières. Une déclaration est aussi appelée prototype, maquette, signature.

■ Affectation

Une instruction d'affectation comporte deux membres : celui de gauche et celui de droite, séparé par l'opérateur d'affectation souvent représenté par le signe = (C, C++, Java, Fortran). Le membre de droite appelé Rvalue précise la valeur à affecter à la variable de gauche appelé Lvalue. L'opération d'affectation n'est pas commutative.

� Exemple

b = 3; pi = 3.14; c = b + pi;

Page 44: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

44 CHAPITRE III ───────────────────────────────────────────────────

■ Clarté

L'affectation provoque une modification de la situation initiale des variables et de l'état du programme en en réduisant la clarté. Considérons les instructions suivantes

// Situation initiale x = b; // Situation 1 : porte sur x x = x+1; // Situation 2 : porte sur x-1 … x = a; y = b; x = x-y; y = y-x; x = y-x; y = x-y; Que valent (x,y) après exécution de ces instructions ?

■ Entrées/sorties élémentaires en C++

Les opérations d'entrée/sortie permettent de gérer les échanges d'informations entre la mémoire centrale et les périphériques. Ainsi, il existe des instructions permettant de faire la saisie, l'affichage, l'écriture ou la lecture de fichiers.

� Exemple

int main(void) { int a ; // Définition de a cin >> a; // Saisie de l'entier a cout << "a =" << a << endl; // Impression de l'entier a }

4.3 Action simple et structure de bloc Une instruction peut être simple ou complexe, selon les variables et les opérateurs manipulés. Certaines instructions deviennent illisibles vu leur complexité dont on distingue plusieurs niveaux.

La formule simple Soit h le salaire brut horaire, t le nombre d'heures de travail. Alors

b = h × t;

Le résultat intermédiaire On souhaite ici calculer le salaire net b. Soit p le pourcentage de retenue.

b = h × t - h × t × p;

On évite de calculer deux fois l'expression h× t en procédant avec deux instructions :

aux = h× t; b = aux× (1-p);

La structure de bloc Une action est exécutée par un ensemble d'instructions appelé bloc, représenté par une accolade ouvrante suivie d'un traitement (une suite d'instructions) terminée par une accolade fermante {...} dans les langages C, C++, Java.

� Exemple

if (a > 0) {traitement} else {autre traitement}

Page 45: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE PROGRAMMATION EN C ET C++ ───────────────────────────────────────────────────

45

5. STRUCTURES DE CONTROLE : BOUCLES, TESTS, BRANCHEMENTS

Les tests, boucles et rupture de séquence contrôlent l'exécution du programme.

5.1 Boucle à nombre borné d'itérations L'exécution de cette boucle est conditionnée par l'évaluation d'expressions entières.

Synopsis for (expression1; expression2; expression3) action expression1 // Initialisation de la boucle expression2 // Test de fin de boucle expression3 // Expression de fin de boucle

■ Sémantique

Cette syntaxe très puissante se traduit sous la forme suivante :

expression1 // Initialisation de la boucle tant que (expression2) action // Corps de la boucle expression3; // Action exécutée à la fin de boucle

� Exemples

for (i=0; i<n; i++) printf("%d",i); for (i=0; i<n; i+=2) printf("%d",i);

Interprétation i=0 i = 0 Tant que i<n faire tant que i < n faire imprimer i imprimer i i=i+1 i = i+2 FinFaire FinFaire

■ Remarques

• Le test de fin de boucle est effectué en début de boucle.

• Chacune des expressions peut être simple, complexe, vide. Dans ce dernier cas, expression

2 est toujours positive et la boucle for(;;) perpétuelle.

� Exemple

for(i=0,j=1; i+j<10; i++,j+=2) printf(" i = %d j = %d \n",i,j); printf(" Boucle terminée \n"); printf(" i = %d j = %d \n",i,j);

// Résultat i = 0 j = 1 // Initialisation de la boucle i = 1 j = 3 // Incrémentation de i et j en fin de boucle i = 2 j = 5 // Incrémentation de i et j en fin de boucle Boucle terminée i = 3 j = 7

Page 46: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

46 CHAPITRE III ───────────────────────────────────────────────────

5.2 La clause if… else

Synopsis

if (expression) actionv else action

f

Description La clause if...else évalue numériquement expression qui peut prendre la valeur numérique 0 (faux) ou 1 (vrai). L'action

v est exécutée si expression est non nulle,

l'actionf sinon. La séquence else est optionnelle.

5.3 La clause else if

Synopsis if (expression

1) action

1 else if (expression

2) action

2 else action

3

Description La clause else...if évalue les expressions en séquence. Si expression

1 est vraie,

l'action1 est exécutée. Le dernier else s'applique si aucune condition n'est satisfaite.

■ Principe de localisation

Si il y a moins de else que de if, le dernier else est associé au dernier if sans else qui le précède.

� Exemple

Les séquences suivantes ne sont pas équivalentes :

if ( n > 0 ) if (a > b) z = a; else z = b; if (n>0) { if (a>b) z = a; } else z = b;

Description si n>0 alors si n>0 alors si a>b alors z = a is

si a>b alors z = a sinon z = b is sinon z = b is is

5.4 Branchement multiple : la clause switch

■ Sémantique des tests multiples

La clause switch généralise les tests multiples sous la forme :

switch(valeur_de_a) { cas 1 : action

1

cas 2 : action2

... cas par défaut : action par défaut }

Page 47: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE PROGRAMMATION EN C ET C++ ───────────────────────────────────────────────────

47

La valeur de l'expression (entière ou caractère) détermine l'action à exécuter décrite suite à l'une des clauses case ou default.

Synopsis switch (expression) { case constante

1 : instruction(s)

1

... case constante

n : instruction(s)

n

default : instruction(s) par défaut // Optionnel fortement conseillé }

■ Règles de syntaxe

• La clause switch évalue l'expression entière et compare sa valeur à celle qui suit chacune des clauses case, libellée par une constante de type entier ou caractère.

• La clause optionnelle default ne s'exécute que si aucune clause case n'est satisfaite.

• Les clauses case et default peuvent être utilisées dans n'importe quel ordre.

• Tous les cas doivent être différents.

■ Rupture de séquence

• L'instruction break permet la sortie immédiate d'un bloc interne de la clause switch. Si elle n'est pas utilisée, l'exécution se poursuit même après l'exécution d'un cas favorable. Ainsi, chaque instruction suivant un case doit se finir par l'instruction break si on veut éviter l'exécution (inutile et quelquefois malencontreuse) en séquence des autres cas.

• Il peut y avoir des cas multiples pour une action unique.

� Exemple

switch(c) { case 'a': printf(" a"); break; // Affichage de a

case 'b': printf(" b"); break; // Affichage de b default : printf(" r"); break; // Affichage de r

}

5.5 Boucle sur condition d'arrêt : la clause while

Synopsis while(expression ≠ 0) instruction ou bloc

� Exemple

// Comptage des espaces (' '), tabulation (\t), passage à la ligne (\n) int c, nb = 0 , ntab = 0, nl = 0; while (( c = getchar()) != EOF ) { if (c == ' ' ) ++ nb; else if (c == '\t') ++ntab; else if (c == '\n') ++nl; }

Page 48: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

48 CHAPITRE III ───────────────────────────────────────────────────

5.6 Clause do while

Synopsis do instruction(s) while (expression)

� Exemple

Calcul de e = ∑∞

=0n !n

1

■ Condition d'arrêt du calcul

Soit ∑=

=n

0in !i

1S

Le calcul s'arrête dès que D = ε≤+

−+ 1)!(n

1 =

nS

1nS

avec ε (le seuil de précision) fixé par l'utilisateur.

■ Algorithme

Initialisation

D = 1 n = 0 somme= 0

Tant que D > ε Faire somme=somme+D n = n+1

D = n

D

FinFaire

■ Calcul de D et arrêt du calcul

Deux modes de calcul possibles pour D à chaque pas, l'un à partir de la valeur précédemment calculée de D (le plus économique et le plus simple, indiqué ci-dessus), l'autre à partir de la fonction factorielle (le plus naturel mais le plus "cher", surtout si le calcul est récursif, programmé ci-après).

Si l'algorithme diverge, le calcul ne peut s'arrêter. Il faut donc toujours prévoir cette éventualité et ajouter une condition d'arrêt pour éviter une boucle perpétuelle (ici un compteur du nombre maximum d'itérations autorisé).

■ Convergence

La convergence de cet algorithme est très rapide (7 itérations pour une seuil de précision de 10-6) ce qui est normal l'écart de deux termes consécutifs étant inversement proportionnel à n!.

Page 49: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE PROGRAMMATION EN C ET C++ ───────────────────────────────────────────────────

49

� Programme

#include <iostream.h> #include <stdio.h> int main() // Calcul itératif de la valeur de e = 2.71828... { double epselon ; cout << "indiquez la valeur de epselon : "; cin >> epselon; // Précision du calcul int factorielle(int); // Prototype (maquette), retour entier, 1 argument entier double somme=0, valeur;

// Initialisation de la boucle int n=0; valeur=1./factorielle(n); // Appel while ((valeur > epselon) && (n <20)) {somme+=valeur; n=n+1; valeur=1./factorielle(n); }

cout << "nombre d'itérations : " << n << " valeur de e approchée : "<< somme << endl ; printf("e = %16.13g\n",somme); }

int factorielle(int p) // Fonction factorielle définie récursivement {if (p==0) return 1; else return factorielle(p-1)*p; }

5.7 Branchements inconditionnels - ruptures de séqu ence Les instructions break et continue permettent de programmer des ruptures de séquence sans faire référence à une instruction étiquetée.

■ L'instruction break

Description Cette instruction force la sortie d'une boucle.

Son effet étant limité à un unique niveau d'imbrication, elle permet de réaliser la sortie du bloc englobant.

■ Diagramme

while (...) ou for(...){.... if(...) break; ...}

� Exemple 1

for (i=0; i<10; i++) if (i== 3) break;

Description Sortie de la boucle quand i = 3

Page 50: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

50 CHAPITRE III ───────────────────────────────────────────────────

� Exemple 2

for(i = 0; i < 10; i++) for (j=0; j<10; j++) if (j== 3) break;

Description Nous avons deux boucles imbriquées. Or, la clause

for (j=1; j<10; j++) if (j== 3) break;

étant l'affectation invariante j=3, elles se réduisent aux affectations i=10 et j=3.

■ L'instruction continue

Description Arrêt de l'exécution de la boucle à l'endroit spécifié en omettant le reste du bloc jusqu'au début de l'itération suivante.

Diagramme

while(...) do{.... {... .... ... continue; continue; .... ....} } while(...);

� Exemple

for (i = 0; i < n; i++) { if ( i== 3 ) continue; printf("%d",i); }

Description Impression sauf si i = 3

� Contre exemple

Voici un exemple vicieux de ce qu'il faut éviter d'écrire :

for(;;) { if(cas_particulier()) break; else continue;

traitement_général(); /* Ne sera jamais exécuté */ }

■ L'instruction goto

L'instruction goto, dont il faut rappeler qu'elle n'est pas dans l'esprit de la programmation structurée, est utilisée pour sortir de boucles imbriquées.

Synopsis goto étiquette;

...

étiquette :

Page 51: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE PROGRAMMATION EN C ET C++ ───────────────────────────────────────────────────

51

6. FONCTIONS, PROCEDURES, BIBLIOTHEQUES

6.1 Fonction

■ Définitions

Une fonction opère sur des arguments scalaires. C'est une application

(E1 X E2 X ... X En) → D

où (Ei)i=1,n sont les domaines de définition des arguments de la fonction et D est le

domaine de valeur du résultat scalaire.

Les arguments sont d'un type prédéfini (entier, réel, caractère, pointeur, etc.) ou non.

■ Transmission du résultat

Le résultat d'exécution une fonction est transmis à l'expression appelante par l'instruction return .

� Exemple

int main(void) { int x = 2, y = 3; printf("somme = %d\n", somme(x,y)); // Appel de la fonction somme }

// Corps de la fonction somme int somme (int a; int b) { return (a+b);}

6.2 Procédure et action

■ Définition

Une procédure représente une action, par exemple inverser un système linéaire.

■ Syntaxe en langages C et C++

Une procédure est une fonction qui ne retourne rien (mot clé void).

� Exemple

Le programme ci-dessous est constitué d'un programme principal qui appelle les procédures nécessaires à la solution du problème.

// Début du programme float a[100][100], b[100], x[100]; // Appels des procédures d'initialisation initialiser_tableau(b); initialiser_tableau(x); initialiser_matrice(a); inverser(a,x,b); affichage(x); // Inversion du système puis affichage // Fin du programme principal // Définition des procédures void initialiser_tableau(float x[]) {/* Code de la procédure */ } void affichage(float x[]) {/* Code de la procédure d'affichage */ } void inverser(float a[][], float x[], float b[]){/* Code de l'inversion */}

Page 52: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

52 CHAPITRE III ───────────────────────────────────────────────────

6.3 Bibliothèques de programmes Il existe des bibliothèques (fonctions, procédures, classes, boites à outils, etc.), appelables depuis la plupart des langages de programmation (C, C++, Java, Fortran, etc.).

� Exemple

La fonction de la bibliothèque standard C qsort effectue le tri d'un tableau selon l'algorithme de Quick sort. Son prototype est le suivant :

#include < stdlib.h > void qsort(void *tableau, size_t n, size_t t,int(*comp)(const void *arg1, const void *arg2));

■ Description

La variable tableau représente l'adresse de la première composante d'un tableau de n éléments chacun de t octets, à trier par ordre croissant.

La variable comp est l'adresse d'une fonction de comparaison de deux composantes du tableau écrite par le programmeur, qui retourne 0 si les éléments comparés sont identiques, un nombre positif si l'élément pointé par arg1 est supérieur à arg2, un nombre négatif sinon.

Le type void * indique que le type effectif du tableau et des arguments de la fonction de comparaison est déterminé dynamiquement à l'exécution.

6.4 Structure d'un programme C/C++

■ Programme principal

Tout programme est constitué d'un programme principal qui en est le point d'entrée et contenant les définitions, déclarations, blocs, et éventuellement des appels de fonctions et des procédures. En C ou C++, c'est la fonction main.

� Exemple

int main(void) { cout << "coucou!! c'est moi" << endl;} // Instruction d'impression

■ Structure élémentaire d'un programme en langage C ou C++

Tout programme en langage C/C+ doit respecter les règles suivantes :

int main(void) /* Point d'entrée de la fonction main */ {// Début de la fonction main()

définition(s) et déclaration(s) obligatoires de toutes les variables utilisées initialisation (recommandée) des variables instructions du programme fin de la fonction main()

}

Définition des éventuelles fonctions et/ou procédures appelées.

L'ordre de la définition des différentes fonctions est arbitraire.

Page 53: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE PROGRAMMATION EN C ET C++ ───────────────────────────────────────────────────

53

7. REPRESENTATION INTERNE DES OBJETS DE BASE DES LANGAGES C ET C++

Le système binaire est un système de représentation naturel de l'information par un ordinateur l'information digitale représentant l'état d'un signal électrique.

Un ensemble de n bits permettant la représentation de 2n états, le codage consiste à établir une loi de correspondance (appelée code) entre les informations à représenter et les configurations binaires possibles de telle sorte qu'à chaque opération corresponde une et une seule configuration binaire.

La conversion d'un système de codage en un autre est appelée transcodage.

■ Interprétation selon le type de l'objet

Les différents types de codage utilisés dans un ordinateur sont tous basés sur le système binaire et dépendent de l'entité à représenter. Une même suite de bits a de multiples interprétations ce qui explique que l'opération de lecture du contenu de la mémoire d'un ordinateur ne puisse être faite sans la connaissance de l'adresse et de la nature de l'information à laquelle on désire accéder.

7.1 Notation binaire et hexadécimale En système binaire, les informations sont formées de suites "assez longues" de 0 et de 1. Si l'utilisateur dialogue sous cette forme avec l'ordinateur, la longueur de la chaîne de bits du codage freine considérablement la compréhension du dialogue. Les risques d'erreurs sont nombreux. Le système de base 16 (hexadécimal), permet une représentation condensée et claire de 4 bits. Comme il est nécessaire de disposer de 16 symboles, on utilise les chiffres 0 à 9 et les lettres A, B, C, D, E F. La correspondance s'effectue suivant le tableau suivant :

Hexadécimal binaire hexadécimal binaire 0 0000 8 1000 1 0001 9 1001 2 0010 A 1010 3 0011 B 1011 4 0100 C 1100 5 0101 D 1101 6 0110 E 1110 7 0111 F 1111 Un octet est alors représenté par deux caractères hexadécimaux. Ainsi l'octet

1011 0010 s'écrit B2.

Il existe aussi des représentations en système octal. Dans ce cas, seuls les chiffres de 0 à 7 sont utilisés et 3 bits suffisent à leur représentation. Les deux premières colonnes du tableau au précédent fournissent la correspondance décimal/octal.

Nous présentons ci-après divers codages en distinguant données et instructions.

Page 54: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

54 CHAPITRE III ───────────────────────────────────────────────────

7.2 Données alphanumériques On appelle donnée alphanumérique, (par opposition à donnée numérique) toute donnée qui ne peut donner lieu à un calcul arithmétique. Dans la pratique une chaîne de caractères est constituée d'une suite de caractères.

• lettres majuscules et minuscules,

• chiffres 0, 1,... 9,

• symboles ! ? ; : , . < > = etc.,

• caractères spéciaux : saut de ligne, saut de page, etc.

Tout caractère est codé sur un octet à partir d'un système de codage.

On en distingue deux : les codes EBCDIC et ASCII. Le code EBCDIC (Extended Binary Coded Decimal Interchange Code), d'origine IBM, utilisant un octet par caractère permet d'en coder 256.

Le code ASCII (American Standard Code for Information Interchange), utilisé sur les micro et mini-ordinateurs, utilise à l'origine 7 bits, le 8ème bit étant utilisé comme bit de contrôle de la validité des transferts de données. Le code ASCII étendu, sur 8 bits, étend le jeu de caractères de base.

■ Remarque

Comme il existe de nombreux alphabets (russe, grec, etc.), il n'y a pas unicité de la représentation. C'est pourquoi il existe un code ascii anglais, allemand, français, etc. ce qui pose de nombreux problèmes pour l'édition. Une norme "universelle " est à l'étude.

Le code ASCII international s'interprète de la façon suivante : les caractères de contrôle ont un code compris entre 0 et 32, les caractères usuels ont un code compris entre 33 et 127, les autres caractères ont un code compris entre 128 et 256.

Le lecteur pourra vérifier qu'avec ce système de codage, la chaîne JEAN est codée en hexadécimal 4A45414E et que la chaîne 12 est codée 3132. Un nombre peut donc être codé sous la forme d'une chaîne de caractères. Il est alors impossible d'effectuer sur celui-ci des opérations arithmétiques sans l'avoir converti en donnée numérique.

■ Police de caractères

Une police de caractères est une représentation particulière des caractères d'un système de codage. Ainsi, les traitements de textes usuels utilisent une grande variété de polices tels le Times, Arial, etc. Des polices particulières permettent de représenter divers symboles, par exemple mathématiques.

7.3 Nombres entiers Les nombres relatifs sont représentés à partir de deux systèmes de codage : les nombres entiers non signés et signés.

Page 55: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE PROGRAMMATION EN C ET C++ ───────────────────────────────────────────────────

55

■ Nombres entiers non signés

On appelle nombre entier non signé tout entier naturel codé sans signe. Pour un codage sur n bits numérotés de droite à gauche et de 0 à n-1, le nombre entier non signé p s'écrit, selon l'algorithme d'Euclide, sous la forme binaire :

p = ∑−

=

δ1n

0ii

i2

où δi représente sur le bit i le reste des divisions successives par 2.

� Exemple

13 s'écrit sur un octet 0000 1101.

Les bits de droite sont appelés bits de poids faible et ceux de gauche bits de poids fort.

■ Nombres entiers signés

On appelle nombre entier signé tout entier relatif. Le bit le plus à gauche, dit bit le plus significatif (most significant bit), code le signe. Quand ce bit est nul, le nombre est positif; quand il est égal à 1, le nombre est négatif. Sa valeur absolue utilise les n-1 bits restants (entier non signé). Ainsi sur un octet

+ 13 s'écrit 00001101 0 s'écrit 00000000 + 127 s'écrit 01111111

Quand le nombre est négatif, on utilise les n-1 bits restants non pour écrire sa valeur absolue mais son complément dont il existe deux définitions.

Le complément à 1 d'un nombre A codé sur n bits, noté C1(A) vérifie :

C1(A) + A = 2n - 1

Le complément à 2 d'un nombre A codé sur n bits, noté C2(A) vérifie :

C2(A) + A = 2n

On démontre que le complément à 1 d'un nombre est sa négation bit à bit et que

C2(A) = C1(A) + 1 = C1(A-1)

Ainsi, pour écrire -3 sur un octet :

Codage de 2 sur 7 bits 0000010 Complément à 1 1111101 Ajout du bit de signe 11111101

De la même façon :

- 1 s'écrit 11111111 - 128 s'écrit 10000000 - 11 s'écrit 11110101

Page 56: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

56 CHAPITRE III ───────────────────────────────────────────────────

� Exemple

Représentation sur 32 bits du nombre + 1973

En numération :

binaire 1973 = 0…0 0111 1011 0101 hexadécimale 1973 = 0…0 7 B 5

■ Remarque

Le lecteur vérifiera que n bits permettent de représenter des nombres entiers signés m tels que :

− ≤ ≤ −− −2 2 11 1n nm

7.4 Nombres réels Les nombres réels sont composés des nombres rationnels et des nombres irrationnels.

• Seuls sont représentables exactement les nombres rationnels.

• Les nombres irrationnels sont approximés et l'erreur de représentation est appelée erreur de troncature.

On distingue trois représentations des nombres réels : les réels DCB, les réels avec une virgule flottante ou réels flottants, les réels avec une virgule fixe. Ces représentations peuvent être liées à une machine ou un logiciel ce qui peut provoquer des problèmes de portabilité.

■ Nombres réels flottants

Dans cette représentation, le nombre est décomposé en deux parties : l'exposant e qui est un entier signé, la mantisse M.

La valeur d'un nombre x ainsi représenté est par construction

x = S M bc

avec S le signe du nombre, M la mantisse, b la base de numération (en général 2 ou 16), c la caractéristique, sur p bits, liée à l'exposant e par la relation

c = e + 2p-1

Il faut représenter le signe, la mantisse, l'exposant.

signe exposant mantisse

Chaque constructeur pouvant utiliser des longueurs de mot, de caractéristique, ou d'exposant différentes, il n'y a pas unicité de la représentation ce qui peut conduire à des résultats différents pour un calcul donné. Nous choisissons ici des mots de 32 bits, une caractéristique sur 7 bits et une mantisse sur 24 bits. Nous présentons en outre la norme IEEE 754 des nombres flottants qui utilise une caractéristique sur 8 bits et une mantisse sur 23 bits pour les nombres en simple précision, une caractéristique sur 11 bits et une mantisse sur 53 bits pour les nombres en double précision.

Page 57: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE PROGRAMMATION EN C ET C++ ───────────────────────────────────────────────────

57

■ Signe

Le bit de gauche est le bit de signe. Par convention, il vaut 0 si le nombre est positif 1 si le nombre est négatif

■ Caractéristique

Elle est représentée sur les p bits suivants. Puisque

0 ≤ c ≤ 2p

on a :

-2p-1 ≤ e ≤2p-1 - 1

Quand p = 7 ou 8 (norme IEEE 754), on peut représenter des exposants positifs et négatifs d'une valeur absolue "raisonnable".

■ Mantisse

La mantisse indique le nombre de chiffres de la représentation. Avec des mots de 32 bits, elle est représentée sur (32 - p-1) bits soit 24 ou 23 bits (norme IEEE 754). Le premier problème est le choix de la représentation de la mantisse. En effet, on peut écrire le nombre suivant de différentes manières :

1973 = 197,3 * 10 = 19,73 * 102 = 0,1973 *104

Dans la dernière égalité, la mantisse vaut 0,1973 et l'exposant 4.

La forme normalisée de la mantisse est définie par la relation :

1/b ≤ M < 1 (1)

Elle permet d'obtenir le maximum de chiffres significatifs.

� Exemple

En base 10, le nombre 1,973 a pour mantisse normalisée 0,1973 et pour caractéristique 65 (1+64).

Dans la pratique, seule la partie fractionnaire de la mantisse soit ici 0,1973 est représentée.

Calcul de la mantisse Chacun des bits de la mantisse s'il vaut 1 représente 2-i, i étant sa position. D'où :

M ii

i

n

= −

=∑δ 2

1

où δi est la valeur 0 ou 1 de son ième bit.

Si tous les δi valent 1, alors

M nn= + + + = − −1

2

1

4

1

21 2...

Page 58: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

58 CHAPITRE III ───────────────────────────────────────────────────

� Exemple

Le nombre réel +1973 s'écrit :

1973 = 7B5 = 0,7B5 * 163

donc :

S = 0 ; M = 7B5 c = 2p-1 + 3 = 26 + 3 = 67 soit 43 en base 16.

En hexadécimal : 1973 = 4 3 7 B 5 0...0 Codé en binaire : 1973 = 0100 0011 0111 1011 0101 0...0

Avec la norme IEEE 754, on obtient :

S=0; M = 7B5

c = 2p-1 + 3 = 27 + 3 = 131 soit 83 en base 16.

En hexadécimal : 1973 = 4 1 B D A 8...0...0

Le nombre réel -1973 s'écrit à partir du complément à 2 du nombre positif soit BC84B000. Avec la norme IEEE 754, on obtient BD425800

■ Choix de la base de représentation

Le choix est effectué selon la précision de la représentation obtenue.

� Remarque 1

Pour additionner deux nombres réels flottants, il faut les réduire au même exposant.

Si b vaut 2, la valeur du nombre n'est pas modifiée en décalant la mantisse d'une position vers la droite et en augmentant l'exposant de 1.

Si b vaut 16, il faut décaler la mantisse de quatre positions et augmenter l'exposant de 1 pour ne pas changer la valeur du nombre. Mais lors du décalage d'une position vers la droite, le bit n de la mantisse est perdu ce qui provoque une perte de précision.

� Remarque 2

Si b vaut 2, l'inéquation (1) devient :

1/2 ≤ M < 1

Le premier bit de la mantisse M est toujours égal à 1 pour tout x non nul et cette représentation assure le nombre maximum de bits significatifs.

Si b vaut 16, l'inéquation (1) devient :

1/16 ≤ M < 1

4 bits sont nécessaires pour représenter les différentes valeurs possibles 1/16, 2/16,...15/16 du premier chiffre de la mantisse. Le lecteur vérifiera que tout nombre compris entre 1/16 et 1/8 a une mantisse dont les 3 premiers bits sont nuls et que dans ce cas, trois bits significatifs sont perdus.

Page 59: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE PROGRAMMATION EN C ET C++ ───────────────────────────────────────────────────

59

� Remarque 3

Pour un mot en base b, avec une mantisse sur n bits et une caractéristique sur p bits, le plus grand nombre positif représentable mb est :

mb

SMb bp p

= ≤− −1

2

1

2

Ce nombre indépendant du nombre de bits de la mantisse dépend de la base choisie.

En base 16 et 2 et avec p = 7, on obtient :

m16 = 1663 ≈ 1075 m2 = 263 ≈ 1019

En base 16 et 2 et avec p = 8, (norme IEEE 754), on obtient :

m16 = 16127 ≈ 10151 m2 = 2127 ≈ 1038

Le choix de la base 16 est donc un compromis entre la grandeur de la valeur absolue et la précision de la représentation.

■ Recherche du nombre de bits de la mantisse

Soient :

x = sbn M et y = sbn M'

alors, y devient négligeable devant x dès que l'équation :

x + y = x

est vérifiée. Or, la mantisse M représente la suite des puissances négatives de 2 ce qui permet d'en calculer le nombre n de bits par l'algorithme suivant :

Début n = 0; y = 2-n ; x = 1; Faire si x+y = x alors imprimer n-1; exit ; sinon n = n+1; y = 2-n ; is FinFaire Fin

■ Calcul du nombre de chiffres significatifs

Soit y = 2-n le plus petit nombre représentable sur la machine considérée. On cherche p tel que :

2-n = 10-p

d'où on tire la relation :

p = n log2 ≈ 0.3 n

Une mantisse de n bits représente p chiffres significatifs d'où une mantisse sur 23 bits (norme IEEE 754 en simple précision) donne 6 chiffres significatifs et une mantisse sur 53 bits (norme IEEE 754 en double précision) donne 15 chiffres significatifs.

Page 60: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

60 CHAPITRE III ───────────────────────────────────────────────────

7.5 Images Une image est un ensemble de lignes constituées par des point élémentaires appelés pixels (picture element) caractérisé par leur teinte appelée niveau de gris pour les images monochromes et couleur pour les images en couleur. Chaque pixel d'un moniteur graphique courant est souvent représenté sur un octet et peut avoir 256 niveaux de gris. Dans le cas d'images en couleur, chaque pixel est coloré à partir des couleurs de base rouge, vert, bleu, chacune pouvant être représentée sur un octet. Dans ce cas, on a une panoplie d'environ seize millions de couleurs. Les définitions les plus courantes varient entre 340*200 et 1600*1200 pixels.

7.6 Instructions Le codage d'une instruction machine est lié à l'architecture interne de la machine et varie selon la longueur des mots et le nombre d'adresses. Le code opération dépend du nombre d'instructions possibles.

A chacun des éléments d'information composant l'instruction est associée une zone de plusieurs bits pour coder les différents états possibles de cette information. Par exemple, 6 bits pour le code instruction permettent de coder 26 instructions, 4 bits pour l'adresse du premier opérande permettent un choix de 16 registres, et 16 bits d'adresse mémoire peuvent adresser 216 mots.

8. ACCES A UN OBJET

■ Définitions

L'adressage est une transformation entre l'adresse de l'objet dans le programme et son adresse effective dans la mémoire.

L'adresse absolue est définie par rapport à l'adresse 0 de la mémoire.

L'adresse effective est calculée selon un mode d'adressage, technique utilisée pour accéder aux différents objets stockés en mémoire.

Les différentes modes d'adressage sont les suivants :

■ Adressage direct

Appelé également adressage normal, absolu ou réel, l'adresse effective de l'objet est contenue dans l'instruction. Le temps de recherche est d'un cycle mémoire.

■ Adressage immédiat

La partie adresse contient la valeur de l'objet et ne nécessite aucune recherche en mémoire.

� Exemple

i=6;

Page 61: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE PROGRAMMATION EN C ET C++ ───────────────────────────────────────────────────

61

■ Adressage indirect

La partie adresse est un pointeur sur l'adresse effective de l'objet dont le chargement nécessite de deux cycles mémoire : un pour l'adresse de l'objet, l'autre pour l'objet.

Le nombre total de cycles est le nombre d'indirections de l'instruction dont le maximum est le niveau d'indirection du calculateur.

� Exemple

Dans les compilateurs Fortran, cette technique est utilisée pour gérer les adresses de retour des sous-programmes. Pour ce faire, l'adresse de retour est stockée, lors de l'appel, dans la première instruction du sous-programme appelé. Au moment du retour dans le programme appelant, le compteur ordinal est chargé avec la première adresse contenue dans le sous programme. Ainsi, le programme Fortran :

CALL SP1 20 instruction SUBROUTINE SP1 RETURN END

se déroulera comme suit : // Appel de SP1 et stockage de l'adresse de retour (ici 20) Ranger l'adresse de retour à l'adresse SP1 aller_à SP1 exécuter SP1 aller_à *SP1 // Notation usuelle pour l'adressage indirect

■ Adressage relatif

L'adresse effective est obtenue par addition ou soustraction d'un déplacement à l'adresse de l'instruction en cours, l'instruction relative, ce qui permet de d'accéder simplement à n'importe instruction du programme.

■ Adressage par base et déplacement

La transformation des adresses symboliques d'un processus en adresses réelles utilise une adresse relative (déplacement) à son adresse initiale d'implantation, choisie par le système d'exploitation, appelée l'adresse de base.

Soient Aa l'adresse absolue, Ab l'adresse de base, D le déplacement.

L'adresse effective est la somme du contenu du registre de base et du déplacement.

Aa = Ab+D

Pour déplacer un processus en mémoire, il suffit de modifier l'adresse de base (adresse de référence), contenue dans le registre de base. On peut ainsi translater des processus dans la mémoire par simple modification du contenu du registre de base, sans avoir à réaffecter toutes les adresses réelles des instructions. Les compilateurs génèrent des codes objets avec des adresses relatives à une adresse initiale nulle. Le déplacement D représente les adresses des instructions, repérées à partir de l'adresse initiale. De tels programmes sont dits translatables et ce mode d'adressage s'appelle aussi la translation d'adresse.

Page 62: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

62 CHAPITRE III ───────────────────────────────────────────────────

� Exemple

Considérons le processus P, implémenté à l'instant T à l'adresse Aa comme suit :

Instant Aa Aa+Dmax Ab D Dmax T 3200 3599 3200 0 399 T+δt 1100 1499 1100 0 399

A l'instant T+δt, le processus P est déplacé ce qui libère l'espace mémoire comprit entre les adresses 3200 et 3599. Seule l'adresse de base Ab a été modifiée.

Un autre intérêt de ce procédé est la protection mémoire : il garantit que le processus ne puisse lire et écrire dans la région mémoire située avant l'adresse de base. De plus, il suffit de stocker dans un registre la valeur maximale autorisée pour les déplacements pour garantir que le processus ne puisse lire ou écrire dans la région mémoire située après l'adresse de base augmentée du déplacement maximum.

■ Adressage par rapport à l'adresse courante

Le contenu du compteur ordinal sert d'adresse de référence.

� Exemple

Déplacement par rapport à la ligne courante de n lignes dans un éditeur de texte.

■ Adressage indexé

L'indexation permet d'atteindre chaque composante d'un tableau d'objets dont l'adresse initiale est connue.

L'adresse effective est obtenue par ajout à l'adresse de base de l'objet (celle de la première composante du tableau) d'un index préalablement initialisé puis incrémenté d'un pas à l'exécution de l'instruction de fin de boucle.

On appelle pré-indexation l'adressage par addition du déplacement de base, et post indexation l'adressage par addition de l'index.

� Exemple : soit le programme C :

int main(void) { float a[100]; int i ; for (i=0;i < 100; i++) a[i] = 0;} }

Le déroulement de ce programme est le suivant :

• réservation d'une zone de 100 mots en mémoire commençant à l'adresse du symbole A.

• boucle de remplissage de la zone à partir de son adresse initiale indexée par I (registre d'index).

On obtient alors le programme assembleur suivant (les notations sont décrites dans les commentaires) :

Page 63: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE PROGRAMMATION EN C ET C++ ───────────────────────────────────────────────────

63

DEBUT A RES 100 // Réservation du tableau A CHA RB 0 // Chargement du registre d'index (valeur 0) COMPTEUR EQU 100 BOUCLE CP RB COMPTEUR // Comparaison de RB avec COMPTEUR BG FIN // Branchement si (COMPTEUR)>(RB) à FIN CHA RB // Chargement avec le contenu de RB RNG A,RB // Rangement du contenu de l'accumulateur à // L'adresse A indexée par le contenu de RB INC RB // Incrémenter de 1 le contenu de RB B BOUCLE FIN STOP // Fin du programme

■ Application : calcul de la longueur maximale d'un tableau

La longueur maximale d'un tableau est calculée à partir du nombre de bits du registre d'index puisque l'accès à une de ses composantes nécessite d'y accéder.

■ Récapitulation des modes d'adressage

On note (A) le contenu de l'adresse A.

Accès immédiat à l'objet opérande=(AD) Adressage normal (direct, absolu) adresse effective=(AD) Adressage indirect adresse effective=((AD)) Adressage relatif base et déplacement (pré-indexation) adresse effective=(B)+(AD) relatif à l'adresse courante adresse effective=(P)±(AD) dans la page courante adresse effective= adresse de page+(AD) Adressage indexé (post indexation) adresse effective=(AD)+(X)

9. STRUCTURES DE DONNEES ABSTRAITES

9.1 Tableaux, pointeurs, tables

■ Construction d'objets

La programmation procédurale définit les objets de base suivants : variable, tableau, pointeur, table, liste, file, construits à partir des objets de base. Des objets de type composite (tableau, enregistrement, variable structurée, classe, etc.) peuvent être construits.

■ Tableau, pointeur

L'organisation d'un tableau en mémoire est séquentielle. Cette structure d'objet, simple est bien adaptée au calcul scientifique. L'accès en séquence à l'information est réalisé à partir d'un pointeur (index) qui représente son adresse (typée).

info i-1 info i info i+1

Page 64: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

64 CHAPITRE III ───────────────────────────────────────────────────

■ Table

Une structure de données abstraite très utilisée en informatique de gestion est la table : l'accès aux informations d'un fichier est réalisé par l'intermédiaire d'une clé d'accès contenue dans un autre fichier appelé table. Cette clé est un pointeur contenant l'adresse de l'information dans le fichier.

clé_info i info j

clé_info j info i

clé_info k info k

9.2 Piles Une pile est une structure de données permettant la gestion de l'historique d'une séquence d'événements. Deux cas se présentent :

• la gestion des interruptions ou des branchements durant lesquels il faut sauvegarder le numéro d'une interruption masquée ou l'adresse de l'instruction suivante pour reprendre ultérieurement l'exécution du programme à cette adresse,

• la transmission des arguments à l'appel d'une fonction ou d'une procédure.

Les objets d'une pile donnée sont de même nature même si les types de ces derniers peut être de nature diverse (variable, pointeur, tableau, instances de classes, etc.).

L'objet le plus récent est situé au sommet de la pile, le plus ancien à la base, la position du sommet étant stockée dans le registre pointeur de pile (stack pointer).

sommet info i info i-1 info i-2

base info 1

Une pile matérielle est un circuit qui gère automatiquement le pointeur de pile. L'avantage est une grande rapidité dans l'utilisation, l'inconvénient sa taille limitée.

Une pile logicielle est une zone réservée de la mémoire vive dont l'adresse en mémoire est gérée par le programme.

Page 65: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE PROGRAMMATION EN C ET C++ ───────────────────────────────────────────────────

65

■ Opérations sur les piles

Les opérations de manipulation d'une pile sont l'empilement dont l'instruction est PUSH (pousse ou dépose), le dépilement supérieur dont l'instruction est POP (prends ou retire), le dépilement inférieur dont l'instruction est POPD pour déposer ou retirer un des éléments sur le sommet ou sur la base de la pile permettant l'accès à la dernière ou la première information de la pile.

sommet........base

PUSH POP

PUSH

sommet........base

POP

� Exemple : empilement des adresses de retour

Voici un algorithme de gestion des adresses de retour lors de l'appel de sous-programmes en Fortran où un sous-programme est écrit sans faire aucune hypothèse, ni sur le programme appelant, ni sur son implantation en mémoire. Lors de son appel, il est nécessaire de stocker l'adresse de l'instruction du programme qui suit l'instruction d'appel, naturellement nommée adresse de retour. Voici une méthode, basée sur l'utilisation des piles. Les commentaires sont après les délimiteurs //.

Etiquettes Instructions 1 // Début du programme principal 10 call SP1 11 Instruction END (PP) // Fin du programme principal 20 SUBROUTINE SP1 call SP2 25 Instruction END (SP1) 50 SUBROUTINE SP2 60 call SP3 61 Instruction END (SP2) 100 SUBROUTINE SP3 110 call SP4 111 call SP5 112 Instruction END (SP3) 130 SUBROUTINE SP4 140 END (SP4) 150 SUBROUTINE SP5 160 END (SP5)

La pile des adresses de retour sera gérée selon l'algorithme suivant :

Page 66: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

66 CHAPITRE III ───────────────────────────────────────────────────

// Début programme principal tableau pile(n); pile = vide // Appel de SP1 PUSH (11) // 11 : 1-ère adresse de retour; aller_à SP1; // Appel de SP2 PUSH (25) // 25 : 2-ème adresse de retour; aller_à SP2; // Appel de SP3 PUSH (61) // 61 : 3-ème adresse de retour; aller_à SP3; // Appel de SP4 PUSH (111) // 111 : 4-ème adresse de retour; aller_à SP4; // Fin de SP4, retour dans SP3 POP ; aller_à adresse_retour (111); // Appel de SP5 PUSH (112) // 112 : 5-ème adresse de retour; aller_à SP5; // Fin de SP5, retour dans SP3 POP ; aller_à adresse_retour (112) ; // Fin de SP3, retour dans SP2 POP ; aller_à adresse_retour (61) ; // Fin de SP2, retour dans SP1 POP ; aller_à adresse_retour(25) ; // Fin de SP1, retour au programme principal POP ; aller_à adresse_retour(11) ; // Déroulement du programme principal fin;

9.3 File et liste

■ File

Une file contient une liste de travaux en attente que le système gère soit :

• dans l'ordre d'arrivée par une pile FIFO (First In, First Out) ou premier entré, premier sorti,

• dans l'ordre inverse d'arrivée par une pile LIFO (Last In, First Out) ou dernier entré, premier sorti.

■ Liste chaînée

Une liste chaînée est un ensemble d'informations composées de deux parties: l'information proprement dite, complétée par un pointeur contenant l'adresse de l'information suivante de la liste.

Les opérations sur une liste chaînée sont l'initialisation d'un élément de la liste, l'insertion ou la suppression d'un des éléments de la liste, le parcourt de la liste.

Page 67: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE PROGRAMMATION EN C ET C++ ───────────────────────────────────────────────────

67

� Exemple

Les blocs constituant un fichier ne sont pas toujours consécutifs sur le disque; chacun contient, en dernière information, l'adresse sur le disque du bloc qui lui est (logiquement) consécutif constituant ainsi une liste chaînée par des pointeurs.

■ Liste chaînée parcourue selon un algorithme FIFO

Une liste est chaînée avec un algorithme FIFO quand toute information de la liste adresse l'information suivante. Son parcourt est séquentiel à partir de son premier élément.

info (i-1) pointeur(i) info(i) pointeur(i+1) info(i+1) pointeur(i+2)

■ Liste chaînée parcourue selon un algorithme LIFO

Quand les pointeurs adressent l'information précédente, ils constituent une liste chaînée dont les éléments sont accessibles par un algorithme LIFO dont le balayage est séquentiel à partir de son dernier élément.

info (i-1) pointeur(i-2) info(i) pointeur(i-1) info(i+1) pointeur(i)

■ Liste doublement chaînée

Une liste doublement chaînée est simultanément FIFO et LIFO et nécessite deux pointeurs de chaînage avant et arrière (exercice laissé au lecteur).

info(i) pt(i-1) pt(i+1) info(i+1) pt(i) pt(i+2) info(i+2) pt(i+1) pt(i+3)

Page 68: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

68 CHAPITRE III ───────────────────────────────────────────────────

10. EXERCICES

� Exercice 1

Ecrire un programme qui affiche horizontalement un histogramme des fréquences des caractères d'un fichier texte. On pourra distinguer les lettres majuscules des lettres minuscules, les chiffres et les autres caractères. Le principe est le suivant :

• constitution d'un tableau entier des effectifs, à partir du caractère saisi,

• calcul des fréquences (attention au type),

• affichage (attention au type).

� Exercice 2

Histogramme horizontal des fréquences des longueurs des mots d'au plus 25 caractères d'un texte. Les pourcentages correspondants sont ensuite calculés.

� Exercice 3

Calcul de π par la formule 6n

1 2

12

π=∑∞

// Complexité importante pour un résultat minable : 46000 itérations pour 5 décimales #include <iostream.h> #include <math.h> int main() { int carre(int); // La fonction carre (retourne un int, 1 argument int double epselon ; cout << "indiquez la valeur de epselon : "; cin >> epselon; // Précision du calcul

double somme=0, valeur; // Boucle de calcul int n=1; valeur=1./carre(n);

cout << "nombre maximum d'itérations : "; int iter; cin >> iter ; while ((valeur > epselon) && (n < iter )) // Boucle de calcul // Nombre d'itérations inconnu à priori donc borné par un maximum autorisé

{somme+=valeur; n=n+1; valeur=1./carre(n); }

double pi = sqrt(6*somme); cout << " nombre d'itérations : " << n << " pi = " << pi << endl; }

int carre(int p) // Fonction de calcul du carré d'un nombre fourni en argument (ici p) { return (p*p);}

Page 69: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS

1. POPULARITE DU LANGAGE C ET DES LANGAGES DERIVES

Le langage C, très populaire, n'est toutefois pas sans défaut.

• Sa grammaire, très permissive, peut conduire à de graves erreurs de sémantique.

• Il n'est pas fortement typé, contrairement aux langages à objet.

• Certains mécanismes d'abstraction (objets) et de contrôle (exceptions) font défaut.

• Il oblige le programmeur à gérer lui-même son espace mémoire dynamiquement.

Ces défauts sont corrigés avec les langages C++ ou Java, dont une des raisons du succès est leur compatibilité ascendante avec le langage C.

La popularité du langage C a plusieurs causes :

■ UNIX et C

Le langage C a été défini dans les laboratoires Bell d'AT&T pour implémenter les premières versions d'UNIX. Il a ensuite bénéficié de la diffusion de ce système.

■ Langage de bas niveau

Le langage C offre des services système de bas niveau (de type langage machine) avec une syntaxe évoluée aujourd'hui normalisée.

■ Universalité, normalisation, portabilité

Le langage C a une vocation universelle car utilisé dans des applications diverses (système, client/serveur, graphique, etc.).

Le langage C étant normalisé depuis 1988, une application développée avec sur une machine peut être compilée et exécutée sans modification sur la plupart des autres systèmes informatiques ce qui la rend portable, avantage économique considérable pour le développement de logiciels.

■ Interface homme machine

La plupart des systèmes offrent une interface avec le langage C : système d'exploitation, système de gestion de bases de données, système de fenêtrage, tableurs, pour ne citer que les plus importants. En outre, de nombreux outils de développement existent.

CHAPITRE IV

Page 70: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

70 CHAPITRE IV ───────────────────────────────────────────────────

2. ESPRIT DU LANGAGE C

Le terme "Esprit du langage C" fait référence aux principes de programmation sous-jacents de ce dernier. Ils ne sont pas formellement définis mais la norme permet d'en dégager les principaux :

• Faire confiance au programmeur en lui permettant de faire ce qu'il estime nécessaire (langage permissif).

• Conserver un langage concis, puissant, et simple.

• N'autoriser qu'une unique façon de réaliser une opération.

• La réaliser rapidement, même si la garantie de portabilité n'est pas assurée. Ce dernier principe signifie que le standard n'empêche pas le programmeur d'écrire un programme adapté à une architecture de machine particulière.

Traditionnellement, le langage C est considéré comme un assembleur de haut niveau. La norme tente de conserver au langage une flexibilité suffisante pour donner aux utilisateurs une chance très sérieuse d'écrire des codes portables.

Le langage C ayant été conçu pour écrire un système d'exploitation, sa philosophie générale est la suivante :

• Le code réalisé doit être le plus compact possible.

• Il faut réaliser des fonctions complexes de façon simple.

• Les codes doivent être modulaires le langage C étant structuré.

• La grammaire de base du langage est simple : nombre de mots clés réduit (32), opérateurs classiques, fonctions traditionnelles (mathématiques, traitement de chaînes de caractères, appels systèmes) compilées et éditées dans des bibliothèques standards, fichiers de description des prototypes et des variables qualifiées constantes prédéfinies suffixés par.h à inclure dans le programme pour les utiliser.

2.1 Règles générales de programmation et de portabi lité Un programme portable s'exécute de façon identique sur des systèmes différents. Quoique la portabilité absolue soit difficile à réaliser, voici quelques règles de base qui permettent d'assurer qu'un programme sera à peu près portable :

■ Attention aux règles de grammaire

• En C, certaines déclarations sont facultatives, comme celle du type de retour d'une fonction, par défaut entier, ce qui ne correspond pas toujours à la réalité. Ce problème disparaît en C++ car tout objet utilisé doit être déclaré.

• Il ne faut pas écrire d'instruction dont le résultat de l'exécution dépend d'un ordre d'évaluation des arguments différent de celui du standard.

■ Utilisation du préprocesseur

• Toujours utiliser les variables qualifiées constantes prédéfinies (EOF).

• Utiliser systématiquement des fichiers en-tête pour spécifier les déclarations et les définitions dépendantes du site d'exécution local.

Page 71: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

71

■ Prototypage

Déclarer explicitement tous les objets utilisés, même quand la grammaire ne l'impose pas. En particulier, utiliser le type void pour les fonctions qui ne retournent aucune valeur.

■ Bonne utilisation des pointeurs

• Ne pas oublier que les pointeurs sont typés et qu'ils ne peuvent pointer n'importe où et sur n'importe quel objet.

• Ne pas supposer qu'un entier et un pointeur ont la même taille.

• Ne pas utiliser de fonctionnalités dépendantes du compilateur C (ordre d'évaluation des arguments d'une fonction, ordre d'évaluation de certains opérateurs), dépendantes de la machine, ou du système local.

• Toujours utiliser les bibliothèques standards.

• Dans la mesure du possible, rendre l'application indépendante de la machine, par exemple en écrivant des programmes indépendants de la taille du mot machine. Ainsi, il faut éviter d'utiliser le type int.

• Utiliser systématiquement l'opérateur sizeof pour déterminer la taille des objets utilisés et l'opérateur de transtypage pour les conversions de type.

2.2 Esprit, règles de bonne programmation et maxime s • Le dicton "Small is beautiful" rappelle la nécessité de développer des programmes

modulaires dont chaque module ne réalise qu'une seule action garantie correcte.

• Le résultat d'exécution d'une application (sortie standard) doit pouvoir être utilisé comme donnée d'une autre donc redirigée sur l'entrée standard de cette dernière (principe du tube (pipe)).

• Utiliser les outils disponibles pour ne pas réinventer la roue.

3. PRINCIPES GENERAUX DU LANGAGE

Pour des raisons historiques, il existe deux variantes du langage C : le C traditionnel de Kernighan et Ritchie, obsolescent, et le C normalisé, beaucoup plus rigoureux.

La grammaire de ce langage est très puissante. Les expressions du langage sont constituées à partir d'instructions simples ou composées (structure de bloc), de délimiteurs, d'identificateurs.

3.1 Expressions et instructions

■ Expressions

Une expression est une variable, une "variable qualifiée constante", une expression arithmétique, logique, relationnelle, ou une fonction.

Page 72: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

72 CHAPITRE IV ───────────────────────────────────────────────────

■ Instructions

Une instruction est une expression suivie d'un ;

expression;

� Exemple : l'instruction vide

; /* Instruction vide : ne fait rien ! */

■ Opérateur d'affectation

variable = valeur

L'affectation n'est pas une instruction mais est une opération non commutative qui retourne la valeur affectée ce qui permet les affectations multiples.

� Exemple

i=j=k=m=0; /* Annule les variables i, j, k et m. */

■ Délimiteurs

Un délimiteur est un caractère spécial permettant au compilateur de discriminer les unités syntaxiques (token) du langage. Les principaux délimiteurs sont les suivants :

; Terminateur de toute instruction et déclaration , Séparateur de deux éléments d'une liste () Délimiteurs des arguments ou des paramètres formels d'une fonction [] Délimiteur de la dimension ou d'indices dans les tableaux {} Délimiteur de début et de fin de bloc /* Délimiteur de début de commentaire */ Délimiteur de fin de commentaire

■ Structure de bloc

La structure de bloc implémente le concept d'instruction généralisée. Délimité par les caractères { et }, il a la structure suivante :

{déclaration des variables locales au bloc (optionnelles) instruction(s)

}

La portée des variables est limitée au bloc dans lequel elles sont définies.

On peut créer des blocs imbriqués, avec leurs variables locales, qui masquent les variables du bloc englobant du même nom.

� Exemple

int main(void) { int a = 1, b= 2; float x = 10.45;

printf(" a = %d b = %d x = %6.2f\n", a,b,x); { float a = 38.45, x = -32.46; /* Redéfinition des variables dans le deuxième bloc */

printf(" a = %6.2f b = %d x = %6.2f\n", a,b,x); } printf(" a = %d b = %d x = %6.2f\n", a,b,x); /* Retour aux variables initiales */

}

Page 73: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

73

// Résultat a = 1 b = 2 x = 10.45 a = 38.45 b = 2 x = -32.46 a = 1 b = 2 x = 10.45 Dans le cas de blocs imbriqués, toutes les variables du bloc englobant sont des variables globales du bloc englobé, sauf celles qui y sont redéfinies.

■ Objets du langage

On distingue six types d'objets en C : les mots clés, les identificateurs, les variables qualifiées constantes, les chaînes de caractères, les opérateurs, les délimiteurs.

3.2 Mots clés du langage C La liste ci-dessous récapitule pratiquement la totalité des mots clés du langage.

■ Déclarations de type

char double enum float int long short signed sizeof struct typedef union unsigned void

■ Déclarations de la classe de mémorisation

auto extern register static const volatile

■ Structures de contrôle

break case continue default do else for goto if return switch while

4. VARIABLES

4.1 Identificateur, type et classe de mémorisation En C, une variable est caractérisée par trois attributs : son identificateur, son type, sa classe de mémorisation.

■ Identificateur

L'identificateur est un symbole permettant de référencer les différents objets utilisés (variables, fonctions, procédure, etc.).

■ Alphabet

Tout symbole interne est formé d'une suite quelconque de caractères alphanumériques dont 31 au plus sont significatifs. Le caractère "_" est autorisé. Les mots clés du langage sont réservés et inutilisables comme nom de variable.

Tout identificateur de variable doit commencer par une lettre autorisée.

Le compilateur différencie les lettres minuscules et majuscules. Usuellement, les lettres minuscules sont utilisées pour les noms de variables et les lettres majuscules pour les constantes symboliques accessibles par le préprocesseur.

Page 74: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

74 CHAPITRE IV ───────────────────────────────────────────────────

■ Type

C'est le type prédéfini (entier, réel, ...) ou non de la variable utilisée.

� Exemple

int toto; char caractere;

■ Classe de mémorisation

La classe de mémorisation des variables ou des fonctions détermine leur mode d'allocation mémoire qui peut être statique (static), dynamique (auto, type par défaut), ou dans les registres (register).

4.2 Définition et déclaration On distingue en C les notions de définition et de déclaration des identificateurs.

■ Définition

La définition d'un objet (variable, fonction, procédure) en spécifie les attributs et provoque l'allocation de l'espace mémoire nécessaire.

■ Déclaration

Une déclaration

• peut avoir différentes significations selon le contexte,

• ne provoque pas d'allocation d'espace mémoire,

• déclare les objets définis dans d'autres fichiers (déclaration extern).

• peut être située :

◊ à l'extérieur des fonctions (déclaration de variable globale),

◊ au début d'un bloc (déclaration de variable locale).

◊ entre la définition d'une fonction et son corps (C Kernighan & Ritchie).

■ Remarque

Définition et déclaration ont le même aspect et une syntaxe voisine mais représentent deux notions différentes : une déclaration permet au compilateur de se référer à une variable et de la décrire alors que la définition lui donne naissance.

4.3 Règles d'utilisation des variables Toute variable doit être définie de façon unique dans un bloc et être déclarée avant son utilisation. On peut définir une nouvelle variable avec le même nom dans un bloc englobé, qui masque la variable correspondante du bloc englobant comme ci-dessous.

int main(void) { int a = 2, b = 3; { float a = 0.; } }

Page 75: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

75

4.4 Accès aux variables En langage C, les divers modes d'accès aux variables sont les suivants : accès direct d'un objet statique en mémoire, accès indirect à l'aide de pointeurs, accès indexé pour les tableaux, accès aux registres de la machine, accès du sommet de la pile de travail.

On peut ainsi réaliser toutes les opérations d'adressage des langages d'assemblage.

4.5 Lvaleur, Rvaleur et affectation Les expressions à gauche et à droite du symbole d'affectation n'obéissent pas aux mêmes règles syntaxiques. C'est pourquoi le standard définit respectivement les termes Lvaleur et Rvaleur pour désigner une expression située à gauche et à droite de l'opérateur d'affectation.

■ Lvaleur

• Une Lvaleur (Lvalue) désigne un objet en mémoire de n'importe quel type à l'exception du type void.

• Une Lvaleur modifiable ne peut être d'un des types suivants : tableau, type incomplet, type qualifié const, variable structurée ou union dont un des champs est qualifié par const.

• Seule, une Lvaleur modifiable est autorisée à la gauche de l'opérateur d'affectation.

• Une Lvaleur est convertie à la valeur définie à la droite du symbole d'affectation conformément aux règles de conversion de la grammaire du langage.

• L'origine du terme est l'abréviation américaine de l'expression "Left value".

■ Rvaleur

• Une Rvaleur est la valeur d'une expression. Le terme Rvaleur vient de l'instruction d'assignation E1=E2; dans laquelle le membre de droite est une Rvaleur.

• Contrairement à une Lvaleur, une Rvaleur peut être une variable ou une constante.

• L'origine du terme est l'abréviation américaine de l'expression "Right value", quelquefois remplacé par "la valeur d'une expression".

5. TYPES

5.1 Types de base Le type d'un objet en décrit la nature (nombre, image, caractère, traitement, etc.) permettant sa représentation interne. Ainsi, les nombres entiers sont représentés sur 16 ou 32 bits, les caractères sur un octet, etc.

Il existe un grand nombre de types prédéfinis présentés ci-dessous, à partir desquels il est possible de construire des objets structurées (variables structurées, union) ou composites.

Page 76: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

76 CHAPITRE IV ───────────────────────────────────────────────────

La norme du langage C définit exhaustivement les types suivants.

■ Type arithmétique

L'ensemble des types entiers et flottants.

■ Type de base

Les types (signé ou non signé) caractère et entier. Le type énuméré n'y figure pas.

■ Type scalaire

L'ensemble des types arithmétiques et pointeurs

■ Type entier signé

Tous les types signed char, int, long int, short int.

■ Type entier non signé

Tous les types unsigned char, unsigned int, unsigned long int, unsigned short int.

■ Type qualifié

Type précisé par un qualificatif.

■ Type flottant

Les types float, double ou long double.

■ Type fonction

Une fonction, ses arguments typés et leur nombre, le type de l'argument retourné.

■ Type pointeur

Descripteur de l'objet pointé.

■ Type énuméré

Une énumération est constituée d'une liste finie et ordonnée de variables qualifiées constantes entières (re)nommées.

■ Type tableau

Ensemble d'objets de même type dans une zone contiguë en mémoire.

■ Type structuré

Type décrivant un groupe d'objets nommés contigus, chacun d'un type spécifique.

■ Type union

Type décrivant des objets nommés de type spécifique se recouvrant en mémoire.

■ Type agrégat

Les types tableaux et structurés.

Page 77: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

77

■ Type incomplet

Type ne fournissant pas au compilateur l'ensemble des informations nécessaires à la définition d'un objet. Par exemple un tableau dont la taille est inconnue, une variable structurée de contenu indéterminé. Un type incomplet est complété à l'exécution.

� Exemple

int tableau[];

■ Type intégral

Les types caractères, entier signé et non signé, et les types énumérés.

■ Type composite

Type dérivé des types de base tableaux, fonctions, pointeurs, variables structures, unions.

■ Type objet

L'ensemble des types décrivant des objets plutôt que des fonctions.

■ Type majeur

Le type majeur d'un type de base est le type lui-même.

Le type majeur d'un type dérivé est le premier type (avec les priorités) utilisé pour décrire l'objet. Ainsi, le type int * est le type pointeur sur un entier dont le type majeur est le type pointeur.

■ Type non qualifié

Tout type non qualifié par les qualificatifs const, noalias, et volatile.

5.2 Types scalaires

■ char

Caractère codé sur huit bits (ASCII étendu ou EBCDIC). Dans le cas particulier de l'ASCII, le bit de poids fort est habituellement forcé à zéro.

La taille des types n'est spécifiée dans aucune norme, sauf pour le type char (un octet). En revanche, les inégalités suivantes sont toujours vérifiées :

char ≤ short int ≤ int ≤ long int float ≤ double ≤ long double

L'opérateur ≤ signifie "a une plage de valeur plus petite ou égale que".

■ int

Entier signé dont la représentation dépend de la machine (16 ou 32 bits).

■ float

Réel flottant sur 32 bits représenté selon la norme IEEE 744.

Page 78: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

78 CHAPITRE IV ───────────────────────────────────────────────────

5.3 Qualificatif Un qualificatif est optionnel. Il précise les déclarations de type précédentes.

■ short

Nombre entier signé court sur 16 bits dont le bit de signe.

■ long

Nombre entier représenté avec 32 bits dont le bit de signe.

■ long double

Nombre réel flottant, représenté au moins en double précision, quelquefois plus, dont la structure est définie dans le fichier en-tête limits.h.

■ unsigned

Utilisable avec les caractères, les nombre entiers court, les nombres entiers standards, les nombre entiers longs sans signe.

■ signed

Utilisable avec les caractères, les nombres entiers courts, les nombres entiers standards, les nombres entiers longs avec signe.

■ double

Nombre réel flottant en double précision, représenté au moins en simple précision selon l'implémentation, dont la structure est définie dans le fichier en-tête limits.h.

■ const

Qualificatif constant : l'objet est dans une zone de mémoire en lecture seule.

■ noalias

Identificateur d'un objet sans alias qu'il n'est possible de modifier qu'avec des pointeurs sur ce dernier.

■ volatile

Ce type interdit certaines optimisations. Ainsi, une variable invariante dans une boucle est déclarée volatile pour empêcher le compilateur de la sortir de la boucle.

� Exemple

extern int heure; /* La variable heure indique l'heure courante */ int main(void) { int i; for(i=0;i<10000;i++) printf("%d:%d\n",i,heure);}

La variable heure étant un invariant de boucle, le compilateur optimise la boucle :

tmp =heure; for(i=0;i<10000;i++) printf("%d:%d\n",i,tmp); ce qui est évité par :

extern volatile int heure; ...

Page 79: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

79

■ Récapitulation des déclarations de type

char double, double float enum float int, signed, signed int long double long , long int, signed long, signed long int short, short int, signed short int, signed short struct typedef union unsigned char unsigned int, unsigned unsigned long int, unsigned long unsigned short int, unsigned short void

Sur une même ligne figurent les déclarations de types synonymes.

5.4 Tableau Un tableau est constitué d'objets d'un même type stockés consécutivement en mémoire, accessibles à partir de l'adresse du premier. Nous verrons que son nom est l 'adresse symbolique de son premier élément en mémoire.

Tout identificateur suivi de [...] représente un tableau dont les éléments sont des variables, éventuellement structurées, de n'importe quel type précédemment défini.

■ Règles d'utilisation des tableaux

Le premier indice est toujours initialisé à la valeur 0. Ainsi, la déclaration :

int tableau[10] ;

crée un tableau de 10 entiers indicés de 0 à 9.

La définition d'un tableau est récursive permettant ainsi un nombre théoriquement illimité d'indices. Les règles de priorité des opérateurs (évaluation de la gauche vers la droite des opérateurs []) indiquent que la déclaration a[m][n] crée un tableau de m tableaux de n éléments : c'est la définition de m vecteurs de n composantes.

Une chaîne de caractères est un tableau de type char.

� Exemple

int tableau[10]; /* Un tableau de 10 entiers*/ float d[10][20]; /* 10 vecteurs de 20 composantes réelles*/ char chaine[30]; /* Une chaîne de 30 caractères */ float e[10,20] /* Interdit */

Page 80: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

80 CHAPITRE IV ───────────────────────────────────────────────────

5.5 Conversions de type implicites Toute opération impose la conversion des opérandes d'une expression arithmétique dans un type commun à partir des règles implicites suivantes :

• Une variable char ou short est convertie en objet de type int. Les types char et int sont compatibles un caractère étant remplacé par sa valeur ASCII.

• Un opérande double provoque la conversion de l'autre en long double avec un résultat long double.

• Un opérande double provoque la conversion de l'autre en double avec un résultat double.

• Un opérande float provoque la conversion de l'autre en float avec un résultat float.

• Un opérande long provoque la conversion de l'autre en long avec un résultat long.

• Un opérande unsigned provoque la conversion de l'autre en unsigned avec un résultat unsigned.

• Un opérande unsigned long int provoque la conversion de l'autre en unsigned long int avec un résultat unsigned long int.

• Un opérande long int provoque la conversion de l'autre en unsigned int si l'opérande de type long int peut représenter toutes les valeurs de l'autre; sinon les deux sont convertis en unsigned long int. Dans les autres cas, un opérande unsigned int provoque la conversion de l'autre en unsigned int avec un résultat unsigned int.

• Un opérande long int provoque la conversion de l'autre en long int avec un résultat long int.

• Les nombres entiers sont convertis en réels.

• Les calculs sur des variables réelles sont effectués en simple ou double précision.

• La plupart des conversions sont résumées dans le tableau suivant :

opérande2 char int short unsigned long float

opérande1 char int short int unsigned long float unsigned unsigned unsigned long float long long long long float float float float float float

Les instructions d'assignation suivent les règles suivantes :

• Le membre de droite est converti, après calcul, dans le type du membre de gauche, qui est donc le type par défaut. Ainsi, un caractère est converti en entier, avec ou sans extension de signe. L'opération inverse est bien sûr possible. La conversion float en int est faite par troncature. Le type double devient le type float, arrondi.

• Le mélange de tous les types entiers (short, long, unsigned, char, int) dans les expressions arithmétiques est autorisé. Ainsi, l'expression 'c'+3 a pour résultat la valeur caractère 'f' (code ASCII du caractère 'c' auquel est ajouté 3).

Page 81: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

81

5.6 Conversion de type explicite On peut spécifier les conversions explicites d'opérandes en utilisant l'opérateur de transtypage ou encore opérateur de transtypage noté () encore appelé cast. Le transtypage ou la coercition est la conversion temporaire du type d'une expression.

Synopsis (type_désiré) expression_dont_on_force_le_type

ou type est un type de base prédéfini (char, int, short, long, float, double, char *, etc.) ou non. Tout se passe, au moment d'un transtypage, comme si une variable du type spécifiée par (type), était utilisée avec la valeur initiale de expression, convertie dans le type spécifié. Le membre de droite n'est alors en aucun cas modifié.

� Exemple

#include <math.h> /* Contient les prototypes des fonctions mathématiques */ int main(void) { int i , j = 10;

double x = 3.1235678, y = 3.987; double sqrt(double); /* Prototype de sqrt, calcul de la racine carrée d'un nombre */ i = x; printf(" conversion implicite : i = %d \n x = %13.8f \n",i,x); i = y; printf(" conversion implicite : i = %d \n y = %13.8f \n",i,y); i = (int) y; printf(" conversion explicite : i = %d \n y = %13.8f \n",i,y); y = i / j; printf(" conversion implicite : y = i/j = %13.8f \n ",y); y = (float) i / j; printf(" conversion explicite : y = (float)i/j=%13.8f \n ",y); printf(" conversion implicite : sqrt(i) = % 13.8f \n ",sqrt(i)); printf(" conversion explicite : sqrt(i) = %13.8f\n ", sqrt((double) i));

}

// Résultats conversion implicite : i = 3 x = 3.12356780 conversion implicite : i = 3 y = 3.98700000 conversion explicite : i = 3 y = 3.98700000 conversion implicite : y = i/j = 0.00000000 conversion explicite : y = (float) i/j = 0.30000000 conversion implicite : sqrt(i) = 1.73205081 conversion explicite : sqrt(i) = 1.73205081

5.7 Diagramme de définition des variables Les variables et leurs attributs sont définies selon le diagramme suivant :

Classe Qualificatif Type Identificateur Initialisation ;

,

Page 82: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

82 CHAPITRE IV ───────────────────────────────────────────────────

La classe de mémorisation est de type auto par défaut et le qualificatif optionnel. Les listes de variables sont autorisées.

La déclaration du type de la variable est obligatoire comme son identificateur.

Les règles d'initialisation sont par défaut fantaisistes.

� Exemple

int main(void) { int k, w; /* Deux entiers */

short int a,b,l = 0; /* Entiers courts*/ long int longueur, i = 780987; /* Entiers longs*/ unsigned i1 = 10; /* Un entier non signé */ unsigned int i2 = i1; /* Un entier non signé*/ char c; /* Un caractère */ float rayon; /* Un flottant simple précision */ double ray; /* Un flottant double précision */ long double ld = 18.456732; /* Un flottant avec la précision maximale */ const double e = 2.71828182845905; /* Variable qualifiée constante double */ printf(" i = %ld \n e = %16.14f \n ",i,e); /* Attention au format d'impression pour les entiers longs */ printf(" i1 = %d \n ",i1); printf(" i2 = %d \n ",i2); printf(" ld = %13.6f \n ",ld);

}

// Résultat i = 780987 e = 2.71828182845905 i1 = 10 i2 = 10 ld = 18.456732

6. VARIABLES QUALIFIEES CONSTANTES

On peut qualifier, avec le qualificatif const, une variable ou un tableau pour lui imposer de rester constant après son initialisation. Les quatre types d'objets caractère, entier, flottant, énumérées sont autorisés.

Il est également possible de définir à la compilation des constantes symboliques d'un type entier, flottant, ou caractère avec la directive #define du préprocesseur.

■ Variables qualifiées constantes entières

Les variables qualifiées constantes entières sont d'un des types entiers prédéfinis. Toute variable qualifiée constante dont la valeur excède les capacités par défaut de la machine devient automatiquement de type long.

■ Variables qualifiées constantes réelles

Les variables qualifiées constantes en virgule flottante sont de type float ou double.

Page 83: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

83

� Exemples

const int i = 1; const long int longueur = 56789543; const float x = 3.55; const double y = 6.789876543432;

■ Variables qualifiées constantes de type caractère

Une variable qualifiée constante de type caractère est un caractère unique, entouré par des ' comme 'a' (définition littérale). Deux types de définitions :

const char car = 'c'; // Définition directe const char car = '\014'; // Code ASCII 12 format octal sous la forme'\0dd' const char car = '\0x14'; // Code ASCII 20 format hexadécimal sous la forme'\0xdd'

Des caractères spéciaux peuvent être utilisés par des instructions d'entrées/sorties. Le caractère d'échappement (escape sequence), permettant d'"échapper" à la signification primitive d'un caractère pour lui donner une signification différente, est nécessaire. Ce sont les caractères suivants :

Caractère Code ASCII Abréviation Description \\ 92 Escape Caractère d'échappement, \a 07 BELL Alerte (bell) \b 08 BS Espace en arrière (Backspace) \f 12 FF Alimentation du papier (Form Feed) \n 10 NL /LF Fin de ligne (New Line/Line Feed) \r 13 CR Retour chariot (Carriage Return) \t 09 HT Tabulation Horizontale \v 11 VT Tabulation Verticale \0 00 NULL Délimiteur de fin de chaîne.

La constante symbolique EOF est définie dans le fichier standard stdio.h. Selon les machines, elle est définie par la valeur 0 ou la valeur -1, valeur qui n'est interprétée comme marque de fin de fichier que si elle suit un caractère de fin de ligne (\n).

■ Variables qualifiées constantes de type chaîne de caractères

Une chaîne de caractères est une suite de caractères délimitée par des guillemets, représentée sous la forme d'un tableau de caractères dont le dernier caractère est obligatoirement le délimiteur de fin de chaîne '\0' (NULL), utilisé par les fonctions d'entrée/sortie pour y accéder caractère par caractère, jusqu'à sa rencontre.

� Exemple 1

const char chaine[4] = "toto";

La constante symbolique NULL, définie dans le fichier stdio.h, est également utilisée pour initialiser des pointeurs ou des objets d'un type quelconque.

Page 84: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

84 CHAPITRE IV ───────────────────────────────────────────────────

� Exemple 2

#include <stdio.h> int main(void) // Déclarations diverses { int i; // Variables qualifiées constantes entières

const short court = 4356; // Variable qualifiée constante entière format court const unsigned int entier = 8; // Variable qualifiée constante entière non signée const int maxline = 10; // Variable qualifiée constante entière const long l1 = 4567797L; // Variable qualifiée constante entière de type long const long l2 = 5656897l; // Variable qualifiée constante entière de type long const int tableau[5] = {0,1,2,3,4}; // Tableau de variables qualifiées constantes const int *pt = &i; // Pointeur constant sur l'adresse de i

// Variables qualifiées constantes virgule flottante et divers formats exponentiels const float pi = 3.1415, p1 = 1E-6; const float p2 = 1e-6, p3 = 0.31416e+2, const float alpha = 31.45E-3; const double beta = 2.67543288990087;

// Variables qualifiées constantes de type caractère const char car1 = 'c', car2 = '\104', chaine[5]="toto";

// Impression des résultats printf(" court = %d entier = %d\n",court, entier); printf(" maxline = %d l1 = %ld l2 = %ld\n",maxline,l1,l2); printf(" pi = % f p1 = %e p2 = %g p3 =%g\n",pi, p1, p2, p3); printf(" car1 = %c car2 = %c\n",car1,car2); printf(" alpha = %e béta = %16.15f\n", alpha,beta); printf(" i= %d &i = %x\n",i,&i); printf(" pt = %x\n",pt); printf(" car1 = %c car2 = %c chaine= %s\n",car1, car2, chaine); for(i=0;i<5;i++) printf(" tab[%1d] = %1d ",i, tableau[i]); return(1);}

// Résultat court = 4356 entier = 8 maxline = 10 l1 = 4567797 l2 = 5656897 pi = 3.141500 p1 = 1.000000e-06 p2 = 1e-06 p3 =31.416 car1 = c car2 = D alpha = 3.145000e-02 béta = 2.675432889900870 i= 5716 &i = fff4 pt = fff4 car1 = c car2 = D chaine= toto tab[0] = 0 tab[1] = 1 tab[2] = 2 tab[3] = 3 tab[4] = 4

7. OPERATEURS

Trois classes d'opérateurs sont définis en langage C : les opérateurs unaires, qui précèdent (ou suivent) un identificateur, une expression, ou une variable qualifiée, les opérateurs binaires (resp. ternaires) mettant en relation deux (resp. trois) expressions, identificateurs ou constantes.

Page 85: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

85

7.1 Opérateurs binaires

■ Affectation

Lvaleur = Rvaleur;

■ Opérateurs arithmétiques sur les objets de type int, float, double, long

+ - * / % opérateur modulo défini par la relation : x % y est le reste de la division entière de l'entier x par l'entier y

■ Opérateurs relationnels

> >= < <=

■ Opérateurs sur les champs de bits

& ET, | OU, ^ OU exclusif, << décalage à gauche, >> décalage à droite, ~ complément à un.

■ Opérateurs logiques

Ces opérateurs sont utilisés pour effectuer les tests booléens.

&& et logique, || ou logique, ! non logique, == identité logique, != inégalité logique.

Les opérateurs = (affectation) et == (test d'identité logique) ne sont pas équivalents.

� Exemple

for( i = 0; i < 5; i++) if ( i == 1 ) printf("%d",i);

Description Impression de i si i = 1

Soit maintenant la séquence :

for( i = 0; i < 5; i++) if ( i = 1 ) printf("%d",i);

La boucle est infinie puisque chaque test affecte la valeur 1 à la variable i.

Page 86: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

86 CHAPITRE IV ───────────────────────────────────────────────────

7.2 L'opérateur ternaire La clause if (condition) expression

v ; else expression

f ;

s'écrit avec l'opérateur ternaire () ? : (condition) ? expressionv : expression

f

� Exemple

#include <stdio.h> int main(void) // impression des composantes d'un tableau d'entier par ligne de 10 { const n = 25; int i, a[25];

for ( i = 0; i < n; i++ ) a[i] = i; for ( i = 0; i < n; i++ ) printf( "%3d %c" , a[i], ( i % 10 == 9 || i== n-1) ? '\n' : ' ');

}

// Résultat 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

Description Dix nombres sont imprimés par lignes.

La condition d'impression porte sur le caractère d'impression qui suit a[i].

Si le reste de la division de i modulo 10 est égal à 9 ou N-1, le caractère spécial \n (passage à la ligne) est imprimé. Sinon, c'est le caractère d'espacement. On obtient ainsi l'impression des coefficients du tableau a par groupe de dix en laissant un espace après chaque colonne.

� Remarque

Le test est possible dans une expression à l'intérieur des arguments de la fonction printf.

7.3 Opérateurs d'incrémentation et décrémentation Les opérateurs unaires ++ et -- sont utilisables avant ou après l'opérande :

++n incrémentation avant l'affectation ou pré-incrémentation, n++ incrémentation après l'affectation ou post-incrémentation, --n décrémentation avant l'affectation ou pré-décrémentation, n-- décrémentation après l'affectation ou post-décrémentation.

� Exemple 1 séquence équivalente effet n = 5; x = n++; x = n; n = n+1; x = 5 n = 6 n = 5; x = ++n; n = n+1; x = n; x = 6 n = 6 s[j++] = s[i]; s[j] = s[i]; j = j+1; s[j++] = s[i++]; s[j] = s[i]; j = j+1; i = i+1;

Page 87: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

87

� Exemple 2

#include <stdio.h> int main(void) {int n,x,i,j, s[5]; n = 5; x=n++; /* Post incrémentation */ printf(" x = %d n = %d\n",x,n); n=5; x=++n; /* Pré incrémentation */ printf(" x = %d n = %d\n",x,n); j = 0; for(i=0;i<5;i++) s[i]=i; /* Initialisation du tableau s */ printf("incrémentation de j après affectation \n"); for(i=0;i<3;i++) {s[j++]=s[i]; printf("i = %d j = %d s[i] = %d s[j] = %d\n",i, j, s[i], s[j]); } printf("*************************\n"); for(i=0;i<3;i++) printf(" s[%d]=%d",i,s[i]); printf("\n"); j = 0; printf("incrémentation de j avant affectation \n"); for(i=0;i<3;i++) {s[++j] = s[i]; printf("i = %d j = %d s[i] = %d s[j] = %d\n",i,j,s[i],s[j]); } printf("*************************\n"); for(i=0;i<3;i++) printf(" s[%d]=%d",i,s[i]); printf("\n"); }

// Résultats x = 5 n = 6 x = 6 n = 6 incrémentation de j après affectation i = 0 j = 1 s[i] = 0 s[j] = 1 i = 1 j = 2 s[i] = 1 s[j] = 2 i = 2 j = 3 s[i] = 2 s[j] = 3 ************************* s[0]=0 s[1]=1 s[2]=2 incrémentation de j avant affectation i = 0 j = 1 s[i] = 0 s[j] = 0 i = 1 j = 2 s[i] = 0 s[j] = 0 i = 2 j = 3 s[i] = 0 s[j] = 0 ************************* s[0]=0 s[1]=0 s[2]=0

7.4 Opérateurs composés L'opérateur d'affectation peut être composé avec les opérateurs binaires + - * / % << >> &.

Soient e1 et e2 deux expressions, l'instruction :

e1 opérateur = e2;

est équivalente à l'instruction :

e1 = (e1) opérateur (e2);

� Exemples

a += i; s'écrit également a = a+i; a *= i; s'écrit également a = a*i; a *= y-2; s'écrit également a = a*(y-2);

Page 88: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

88 CHAPITRE IV ───────────────────────────────────────────────────

7.5 Opérateurs sur les champs de bits Les opérations sur les bits peuvent être effectuées sur la représentation interne des opérandes de type entier signé ou non. Leur principe est d'opérer simultanément sur l'ensemble des bits de la représentation du nombre. Plus précisément, soient a,b et c trois nombres entiers non signés et soit n le nombre total de bits de leur représentation. On a alors :

a = ana

n-1...a

1

b = bnb

n-1...b

1

c = cnc

n-1...c

1

Soit T un opérateur sur les bits de a,b,c. Le résultat de l'opération :

c = a T b

est défini bit à bit par les opérations :

ci = a

i T B

i , ∀ i = 1,n

Les opérateurs sur les champs de bits sont :

■ L'opérateur &

Forme : opérande1 & opérande2

Description : résultat de type entier de l'opération et logique sur chacun des bits des deux opérandes.

■ L'opérateur |

Forme : opérande1 | opérande2

Description : résultat de type entier de l'opération ou logique sur chacun des bits des deux opérandes.

■ L'opérateur ^

Forme : opérande1 ^ opérande2

Description : résultat de type entier de l'opération ou exclusif sur chacun des bits des deux opérandes.

& ET | OU ^ OU exclusif << décalage à gauche >> décalage à droite ~ complément à un

Page 89: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

89

■ L'opérateur <<

Forme : opérande1 << opérande2

Description : décalage logique vers la gauche de opérande2 bits de opérande1. Les

bits de droite de la représentation binaire sont remplacés par des zéros.

■ L'opérateur >>

Forme : opérande1 >> opérande2

Description : décalage logique vers la droite de opérande2 bits de opérande1 avec

extension du signe. Les bits de gauche sont remplacés par des zéros () quand le bit de signe est nul, par des uns quand il vaut 1.

■ L'opérateur ~

Forme : ~ opérande

Description : résultat de type entier de l'opération complément à un sur chacun des bits de opérande.

� Exemple

L'algorithme de la multiplication égyptienne permet d'effectuer la multiplication de deux nombres entiers positifs en utilisant les opérateurs logiques. Soit z le résultat cherché du produit x*y.

Début lire x,y z = 0 Tant que y est non nul faire si y pair alors y = y/2 x = 2*x sinon y = y-1 z = z+x is Finfaire

Fin

Cet algorithme est programmé uniquement avec des instructions logiques et des instructions de décalage :

• pour réaliser le test de parité, on définit un "masque" avec la valeur 1. On effectue l'opération logique et sur la variable y et sur le masque. La variable y est impaire si le résultat est 1, paire sinon.

• pour multiplier un nombre par deux, il suffit de décaler d'un bit vers la gauche sa représentation binaire; de même, il suffit de décaler d'un bit vers la droite sa représentation binaire pour le diviser par deux.

Page 90: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

90 CHAPITRE IV ───────────────────────────────────────────────────

7.6 Associativité des opérateurs binaires Les opérateurs binaires sont les suivants :

Affectation =

Opérateurs arithmétiques + -* / %

Opérateurs relationnels > >= < <=

Opérateurs logiques && et logique || ou logique ! non logique == identité logique != inégalité logique

Opérateurs sur les champs de bits & ET | OU ^ OU exclusif << décalage à gauche >> décalage à droite ~ complément à un

Opérateurs unaires ++ pré ou post incrémentation -- pré ou post décrémentation

■ Priorité et règles d'associativité des opérateurs binaires

Le tableau ci-dessous présente les règles d'associativité ou ordre d'évaluation des opérateurs (y compris ceux qui sont présentés plus loin). Les lignes sont présentées par ordre de priorité décroissante et les opérateurs de même priorité sont sur la même ligne.

Opérateurs Ordre d'évaluation () [] ->. gauche à droite ! ~ ++ -- + - * & (type) sizeof droite à gauche * / % gauche à droite + - gauche à droite << >> gauche à droite << = >= > < gauche à droite == != gauche à droite & gauche à droite ^ gauche à droite | gauche à droite && gauche à droite || gauche à droite ? : gauche à droite = += -= etc. droite à gauche , gauche à droite

Page 91: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

91

8. FONCTIONS ET PROCEDURES

8.1 Définitions et règles d'utilisation

■ Définition normalisée

En C, une fonction est une application

(E1 X E2 X... X En) → ℜ

où (Ei)i=1,n sont les domaines de définition des arguments de la fonction et ℜ est le domaine de valeur du résultat.

• Des arguments de type scalaire prédéfini (entier, réel, caractère, pointeur, void) ou non (objet structuré,etc.) sont autorisés.

• Le type par défaut du résultat d'une fonction est int.

• La déclaration et la définition des fonctions permettent le contrôle du nombre et du type des arguments à la compilation par la génération de messages d'erreur ou d'avertissement (warning).

• La prudence impose au programmeur d'éliminer de son programme tous les message d'avertissement, sauf peut-être ceux dont il connaît précisément la raison.

La définition d'un objet, unique dans un programme, provoque l'allocation de la mémoire nécessaire à son utilisation.

La syntaxe est la suivante :

type_de_ℜ identificateur_de_la_fonction( type du paramètre_formel identificateur_paramètre_formel, ... type du paramètre_formel identificateur_paramètre_formel) {liste de variables locales corps de la fonction }

Type Identificateur ( Type Argument formel ) Type Argument_formel ,

Diagramme de la déclaration des arguments formels

� Exemple 1

// Définition de la fonction addition et déclaration de type des arguments int addition(int a, int b) // Corps de la fonction { int aux; // Variables locales à la fonction aux = a+b; return(aux); // Transmission du résultat à la fonction appelante }

Page 92: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

92 CHAPITRE IV ───────────────────────────────────────────────────

� Exemple 2

int addition(int a, int b) // Définition de la fonction addition { return(a+b); } // Transmission du résultat à la fonction appelante

■ Identificateur

Un identificateur non préalablement déclaré utilisé dans une expression et suivi d'une parenthèse gauche sera déclaré, par contexte, comme celui d'une fonction.

■ Fonctions imbriquées

La norme interdit de définir une fonction dans une autre. Toutes les fonctions sont externes.

■ Fonction d'appel

Une fonction est d'accès public sauf si elle est qualifiée static.

■ Ordre des définitions

L'ordre des définitions des différentes fonctions dans un fichier est sans importance.

8.2 Déclaration et prototypes La syntaxe du C étant très permissive sur ce plan, un prototype est une déclaration facultative dans la fonction appelante, qui peut éventuellement décrire le nombre et le type des arguments et du résultat de la fonction appelée, permettant ainsi à l'analyseur syntaxique de contrôler les appels de fonction à la compilation et de détecter divers types d'erreurs :

• implémentation d'une fonction non conforme à la norme,

• erreur dans le nombre ou le type des arguments d'appel,

• erreur dans le type du résultat.

Les noms d'arguments utilisés dans le prototype ne sont pas visibles à l'extérieur et n'affectent en aucune manière les autres variables de la fonction. Il est même possible de les omettre.

Un prototype peut apparaître plusieurs fois dans le corps d'un programme.

Il faut les regrouper avec les autres déclarations et définitions (fichiers en tête...).

� Exemple

// Prototypes normalisés int addition (int, int); // Addition à valeur entière avec deux arguments de type entier double simpson(double); // Simpson à valeur double avec un argument de type double float *f(int, double); // f retourne un pointeur sur un flottant, un argument entier, l'autre de type double.

Page 93: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

93

8.3 Le mot clé void On distingue plusieurs utilisations du mot clé void :

• Définition et prototype de fonctions sans argument,

• Type conventionnel d'une procédure (fonction ne retournant aucun résultat),

• Pointeurs ou expressions.

Nous étudions ici les deux premiers cas.

■ Fonction sans argument

Le type void utilisé dans la définition et le prototype de la fonction indique que la liste des paramètres formels est vide. Son omission signifie que rien n'est précisé sur les arguments donc que tout est possible et autorisé.

Synopsis : identificateur_de_fonction(void ) { ...}

� Exemple

int f(void ); // Prototype : aucun argument autorisé int g(); // Prototype imprécis : tout est possible f(); // Seul appel autorisé g(a,b); // Appel autorisé

■ Procédure

Rien n'imposant d'utiliser le résultat d'exécution d'une fonction, une instruction se limitant à son appel est valide. Sur le plan syntaxique, une procédure est une fonction ne retournant aucun résultat ce qui s'indique par la déclaration void.

Synopsis : void identificateur_de_procédure(...);

� Exemple

int main(void ) { void f(void ); // Prototype de la procédure f f(); // Appel de f return(1); }

void f(void ) // Définition de la procédure f { printf ("procédure f\n");}

8.4 Résultat : transmission et type

■ Transmission du résultat

Le résultat d'exécution d'une fonction est transmis à l'instruction appelante par l'instruction return.

Synopsis return(expression) ou return expression

Dans le cas où expression est vide, il n'y a pas de résultat retourné par la fonction. Si le type retourné est non entier (type par défaut), il est nécessaire de le spécifier dans la définition de la fonction pour éviter une erreur d'exécution

Page 94: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

94 CHAPITRE IV ───────────────────────────────────────────────────

� Exemple

int main(void ) { int i = 1, j = 2; printf(" addition (i,j) = %d\n",addition (i,j)); return(1); }

addition (int a,int b) // Définition de la fonction addition { int c; // Variable locale c = a + b; return(c); // Transmission du résultat au programme appelant } // Résultat addition (i,j) = 3

■ Type du résultat

En langage C, la déclaration du type du résultat d'une fonction est facultative le type par défaut étant int. Il est donc fortement recommandé de toujours le spécifier par un prototype pour faciliter la détection d'éventuelles erreurs de compilation de fonctions définies dans différents fichiers, avec des risques d'incompatibilité (nombre ou type de ses arguments).

En langage C++, le maquettage des fonctions et méthodes est impératif.

� Exemple

int main(void ) { double sphere(double); // Prototype double f = 1.; printf( " volume de la sphère = % e ", sphère(f)); return(1); }

double sphere(double rayon) // Définition du type du résultat de la fonction et du type de l'argument { double pi = 3.1416, v; v = 4./3*pi* rayon* rayon* rayon; return(v); }

// Résultat volume de la sphère = 4.188800e+000 Supposons le cas suivant :

int main(void ) { float f (...);… // Prototype de f

f(...);… // Appel de f }

Dans une compilation simultanée de la fonction main et de la fonction f (fonctions main et f dans le même fichier), le type du résultat est vérifié. Dans une compilation séparée, la fonction f est par défaut à valeur entière dans la fonction main. En l'absence de prototypes, cette erreur est indétectable.

Page 95: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

95

8.5 Appel Une fonction est toujours appelée depuis une autre car toutes sont définies dans des blocs externes.

Un appel de fonction invoque une fonction à un point particulier du programme. Sa forme syntaxique est l'identificateur de la fonction suivi d'une paire de parenthèses contenant la liste des arguments effectifs qui peuvent être des constantes, des variables, des expressions, ou des appels de fonction.

� Exemple

int main(void ) { int a = 1 ,b = 2, y; int add(int, int); /* Prototype de add */ y = add(a,b); /* Appel de add */ return(1); } int add (int a, int b) /* Définition de la fonction add et de ses arguments formels */ { /* Corps de la fonction add */ }

8.6 Récursivité Une fonction récursive s'appelle elle même et ne nécessite aucune autre déclaration.

� Exemples

La suite de Fibonacci est définie par :

>∀+===

1 n u u u

1 u u

2-n1-nn

10

Cette suite est définie récursivement (arborescence binaire) et on démontre aisément que la profondeur de la récursivité est en O(2n).

#include <stdio.h> int main(void ) /* Suite de Fibonacci */ { long int n = 10, i;

long fib(long ); /* Prototype */ printf("\n nombre n : "); for(i = 0;i<n;i++) printf("\n i = %3d fib(i) = %ld",i,fib(i)); return(1);

}

long fib(long n) { if (n < 2) return(1);

else return(fib(n-1) + fib(n-2)); }

Page 96: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

96 CHAPITRE IV ───────────────────────────────────────────────────

9. POINTEURS

Un pointeur opère sur une variable dont on veut accéder au contenu par utilisation d'une référence à son emplacement en mémoire.

Ils permettent en particulier d'accéder aux composantes d'un tableau. C'est une fonctionnalité essentielle du langage C qui en fait toute la puissance.

Des pointeurs sur n'importe quel objet peuvent être définis (variables, tableaux, fonctions, etc.).

La définition mathématique de ces notions est explicitée par de nombreux exemples.

9.1 Définitions Un pointeur est une variable contenant l'adresse d'une autre variable permettant son accès indirect.

Soient x une variable typée et px une variable pointeur sur x. On définit deux

opérateurs mathématiques A et C de la façon suivante :

A : x → A(x) = px

C : px → C(px) = x

L'opérateur A fournit l'adresse de x et l'opérateur C le contenu de px. On démontre

aisément que les fonctions A et C sont des fonctions réciproques. D'où les identités :

C(px) = C(A(x)) = x A(x) = A(C(px)) = px

■ Opérateurs de référence et de déréférenciation

En langage C, les notations suivantes sont utilisées :

• l'opérateur A, dit de référence ou d'adresse et noté & , fournit l'adresse de l'objet concerné. Il n'est employé qu'avec des variables ou des tableaux.

• l'opérateur C, dit de déréférenciation ou d'indirection et noté *, accède au contenu d'un pointeur.

La syntaxe du langage omet l'écriture des parenthèses ce qui conduit aux définitions usuelles des opérateurs & et * suivantes :

&x = adresse de la variable x *px = contenu de l'adresse contenue dans px

� Exemple

int i = 7; int *pi,** ppi; /* pi est un pointeur sur un entier */ /* ppi est un pointeur sur un pointeur sur un entier */ pi = & i; /* Initialisation de pi */ ppi = &pi; /* Initialisation de ppi */

Page 97: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

97

ppi pi i

&pi &i 7

et on a

*ppi = pi *pi = i =7 et donc ** ppi = * (*ppi) = * (*& pi) = *pi = i = 7

On a par définition l'équivalence fondamentale :

px = &x et y = *px ⇔ y = x

D'où on déduit :

*px = x (1) ⇔ px = &x (2)

■ Corollaire 1

Au sens mathématique, les opérateurs * et & sont des fonctions réciproques. On a donc l'identité :

*& x = x

Démonstration : par application des formules (1) et (2)

*px = x = *& x

■ Corollaire 2

Tout pointeur est typé car *px est une variable dont le type est celui de l'objet pointé

ce qui justifie les déclarations de la forme :

int *pa; double *py;

■ Corollaire 3

Dans les instructions d'affectation, les variables peuvent être remplacées par des pointeurs. On suppose que *px = x

Compte tenu de la hiérarchie des opérateurs, on a :

x = 0; ⇔ *px = 0; y = x+1; ⇔ y = *px+1; x = x+1; ⇔ *px += 1; x++ ; ⇔ (*px)++ ;

■ Corollaire 4

Soient deux pointeurs px et py sur des objets de même type. Alors l'instruction

px = py;

est licite et recopie le contenu du pointeur py dans le pointeur px.

Page 98: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

98 CHAPITRE IV ───────────────────────────────────────────────────

9.2 Transmission par adresse et par valeur Deux modes de transmission des arguments sont implémentés en C :

■ Mode de transmission par adresse

L'argument d'appel est directement transmis par son adresse ce qui permet à la fonction appelée d'utiliser la variable originelle. La grammaire du langage C utilise explicitement les pointeurs pour effectuer ce mode de transmission dont un des inconvénients est le suivant : la pérennité des variables transmises n'est pas garantie puisque elles sont modifiables par la fonction appelée. Or, une erreur d'indice dans un tableau transmis risque de provoquer l'écrasement des variables stockées avant ou après l'espace alloué au tableau.

Un tableau est transmis par son adresse son nom étant un pointeur constant déréférencé.

� Exemple

La fonction strcat de concaténation des chaînes de caractères chaine1 et chaine2.

#include <stdio.h> #define LON 500 int main(void ) { char chaine1[LON], chaine2[LON];

int strcat(char [] , char []); printf("\t programme de concaténation de chaîne\n\t saisir dans l'ordre chaine1, chaine2\n"); scanf("\n %s",chaine1); scanf("\n %s",chaine2); strcat(chaine1,chaine2); return(1);

}

strcat(char s[],char t[]) { int i,j;

i = j = 0; while (s[i] != '\0') i++ ; // Lecture de la première chaîne while((s[i++ ]= t[j++ ]) != '\0'); // Remplissage de la première chaîne avec la deuxième printf("\n chaîne totale %s ",s);

}

■ Mode de transmission par valeur

L'argument d'appel est sauvegardé dans une pile d'exécution et une variable auxiliaire, initialisée avec cette valeur de l'argument d'appel, est utilisée lors de l'exécution de la fonction ce qui rend la variable initiale accessible par la fonction appelée. Ce mode, indispensable pour la gestion interne de la récursivité, garantit en outre l'intégrité dans la fonction appelante des variables transmises par valeur.

L'exemple suivant met en évidence la nécessité de pouvoir utiliser ces deux modes.

Page 99: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

99

� Exemple

Procédure de permutation de deux variables avec une transmission par valeur.

int main(void ) { int a = 1 ,b = 2; void swap (int, int); // Prototype de swap printf( " a= %d b= %d\n", a,b); swap(a,b); // Appel de la procédure swap printf( " a= %d b= %d\n", a,b); return(1); }

void swap (int x, int y) // Définition de la procédure swap { int aux; // Corps de la procédure swap aux = x; x = y; y = aux; }

// Résultats a= 1 b= 2 a= 1 b= 2

■ L'art du compromis

La transmission par adresse est délicate à programmer les objets étant des variables dans la fonction appelante et des pointeurs déréférencés dans la fonction appelée. La transmission par référence, similaire, en simplifie l'écriture.

Toutes variable transmise par valeur est sauvegardée dans la pile d'exécution. La fonction appelée est exécutée avec une copie ce qui peut être pénalisant dans le cas ou l'espace mémoire occupé par l'argument effectif est important.

L'utilisation du mot clé const permet de faire une transmission par adresse ou par référence en garantissant que la variable transmise n'est pas modifiée ce qui permet un gain d'espace mémoire en assurant la pérennité des arguments transmis.

9.3 Objets composites

■ Pointeur sur un objet

Classe Type * Identificateur ;

,

DIAGRAMME DE DEFINITION DES POINTEURS

identificateur est une variable scalaire (int, float, etc.), structurée, un tableau.

■ Pointeur sur une fonction

Type_résultat ( * identificateur_pointeur) ( type_argument,...)

DIAGRAMME DE DEFINITION DES POINTEURS SUR DES FONCTIONS

Page 100: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

100 CHAPITRE IV ───────────────────────────────────────────────────

9.4 Construction d'objets composites Les règles de priorités et d'associativité des opérateurs permettent les définitions d'objets suivantes :

Pointeur sur une variable scalaire int *p;

Tableau de pointeurs int *tab[3]; // tab est un tableau de trois pointeurs sur des entiers.

Pointeur sur une variable flottante float *pt; // pt est un pointeur sur un flottant.

Pointeur sur un tableau int (* pt)[3]; // pt est un pointeur sur un tableau de 3 entiers.

Fonction retournant un pointeur int *phi();

phi, fonction avec des arguments indéterminés retournant un pointeur sur un entier.

Pointeur sur une fonction float(*phi)();

phi, pointeur sur une fonction avec des arguments indéterminés, à valeur flottante (évaluation de gauche à droite).

Pointeur sur un tableau float (*phi)[];

phi est un pointeur sur un tableau de flottants (associativité des opérateurs () et []).

Fonction et variables structurés struct *(*f( float, char *))[];

f est une fonction avec un argument de type float et un argument de type char *, retournant un pointeur sur un tableau de pointeurs sur des structures.

N'importe quoi float (*(*(*f())[])())[];

f, fonction avec des arguments indéterminés retournant un pointeur sur un tableau de pointeurs sur une fonction retournant un pointeur sur un tableau de flottants.

Fonctions et pointeurs int *comp(void * , int *);

comp est une fonction retournant un pointeur sur un entier avec deux arguments de type respectif void* et int*.

int (*comp)(float, void *);

comp est un pointeur sur une fonction retournant un entier avec deux arguments de type respectif float et void *.

Page 101: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

101

10. ARGUMENTS DE FONCTIONS ET POINTEURS

La transmission des arguments par adresse est réalisée de la façon suivante : le programme appelant transmet les adresses des variables concernées; les déclarations des variables formelles sont modifiées en conséquence.

� Exemple

#include <stdio.h> // Permutation de deux entiers : transmission par adresse int main(void) { int a = 2 ,b = 3; void swap(int *, int *); // Prototype swap (&a, &b); // Appel printf("\n a= %d b= %d",a,b); return(1); }

void swap (int *px, int *py) // Définition { int temp = * px; *px = * py; *py = temp; }

// Résultat a = 2 b = 3 a = 3 b = 2

■ Application aux fonctions de la bibliothèque C scanf et printf

Une saisie modifiant une variable, scanf transmet ses arguments par adresse.

Toute variable à imprimer est transmise par valeur à la fonction printf.

11. TABLEAUX ET POINTEURS

Nous présentons les relations fondamentales entres les pointeurs et les tableaux qui justifient la gestion des suites d'objets d'un même type en mémoire.

11.1 Calcul d'adresse Soient un tableau a[N] d'objets d'un type quelconque et pa0 un pointeur du même type.

Si pa0 = &a

0 alors :

pa0++ pointe en type sur l'objet qui suit x

0 et

pa0 += i pointe sur le i-ème objet suivant de même type.

a0 a

1 ... a

i ⇑ ⇑ ⇑ ⇑ pa0

pa0+1 ... pa0+i

Vu les règles de priorité et d'associativité des opérateurs, l'expression *p++ est équivalente à *(p++), l'incrémentation du pointeur étant exécutée avant l'indirection.

Page 102: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

102 CHAPITRE IV ───────────────────────────────────────────────────

11.2 Correspondance tableau pointeur

■ Théorème 1

Soient a[N] un tableau de variables d'un type donné, pa un pointeur sur une variable du même type et x une variable du même type définis par les instructions :

type a[N], *pa, x; pa = &a[0]; (1)

L'instruction :

x = * pa; (2)

est équivalente à l'instruction

x = a[0];

Démonstration La déclaration

type a[N];

définit un tableau de N objets consécutifs référencés par a[0],..., a[N-1]. Par application de l'opérateur * à la relation (1), on obtient :

*pa = *& a[0] = a[0] = x (relation 2)

■ Théorème 2

En C, l'identificateur d'un tableau est un pointeur constant déréférencé contenant l'adresse de son premier élément. De plus, quelque soit le type des variables du tableau, on a les identités mathématiques :

pa ≡ a *(pa+ i) ≡ a[i] ∀ i ∈ Z

Démonstration L'identificateur du tableau est un synonyme de l'adresse du premier élément. D'où

pa = &a[0] = a

Un identificateur de tableau étant un pointeur constant déréférencé et un pointeur une variable, il est interdit d'écrire l'instruction :

a = pa // Reviendrait à affecter pa à la constante a.

La deuxième relation découle directement de la définition du paragraphe précédent.

■ Théorème 3

On a, pour tout tableau d'objets d'un type donné les identités fondamentales :

a[i] = *(a+i) et *(pa+i) = pa[i]

Démonstration Compte tenu du théorème 2, on a :

*(a+ i) = * (pa+ i) = a[i] ce qui démontre la première relation.

La deuxième relation en découle directement les objets a et pa étant identiques.

Page 103: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

103

■ Conclusions fondamentales

• En C, toute opération sur les tableaux peut être réalisée par des pointeurs.

• Un tableau n'est pas un pointeur. Seul, son identificateur en est un.

• La définition d'un tableau provoque la réservation de l'espace mémoire nécessaire à toutes ses composantes.

• La définition d'un pointeur ne réserve que l'espace nécessaire à son stockage.

• Un programme utilisant des pointeurs est plus rapide.

■ Corollaire

Utilisées comme paramètres formels d'une fonction, les expressions

type a[] et type *a

sont équivalentes, quelque soit le type de base du tableau a.

Ainsi, les paramètres formels suivants sont équivalents.

char a[] et char *a int b[] et int * b

� Exemple

#define TAILLE 2 int main(void ) // Les tableaux ci-dessous sont imprimés en utilisant des pointeurs. {long tab1[TAILLE] , *pt1 = tab1; short i, tab2[TAILLE] , *pt2 = tab2; float reel[TAILLE] , *preel = reel; void * v1, *v2;

for ( i = 0; i < TAILLE; i++) {tab1[i]= (long) (2*i); tab2[i] = 2*i+ 10; reel[i] = (float) (2*i + 20); }

for (i = 0 ; i < TAILLE; i++) { printf(" tab1[%d] = %d\n", i , tab1[i]); printf(" tab2[%d] = %d\n", i , tab2[i]); printf(" reel[%d] = %6.3f\n", i , reel[i]); }

printf( " impression de *(tab1+i), *(tab2+i), *(reel+i)\n");

for (i = 0 ; i < TAILLE; i++) { printf(" tab1[%d] = %d\n", i , *(tab1+i)); printf(" tab2[%d] = %d\n", i , *(tab2+i)); printf(" reel[%d] = %6.3f\n", i , *(reel+i)); }

printf(" impression de *tab1+i, *tab2+i, *reel+i\n"); for (i = 0 ; i < TAILLE; i++) { printf(" tab1[%d] = %d\n", i , *tab1+i); printf(" tab2[%d] = %d\n ", i , *tab2+i); printf(" reel[%d] = %6.3f\n", i , *reel+i); } printf( " impression de *pt1+i, *pt2+i, *preel+i\n"); for (i = 0 ; i < TAILLE; i++) { printf(" tab1[%d] = %d\n", i , *pt1+i); printf(" tab2À%d] = %d\n", i , *pt2+i); printf(" reel[%d] = %6.3f\n", i , *preel+i); }

Page 104: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

104 CHAPITRE IV ───────────────────────────────────────────────────

v1 = pt1; v2 = (void *) pt1 ; /* Manipulation sur les pointeurs génériques */ printf(" &tab1 = %x &tab2 = %x\n", tab1 , tab2 ) ; printf(" v1 = %x v2 = %x pt1 = %x\n", v1 , v2, pt1); printf(" pt2 = %x void * pt2 = %x\n", pt2, (void *) pt2); printf(" impression de pt1[i], pt2[i] , preel[i] \n"); for (i = 0 ; i < TAILLE; i++) { printf(" tab1[%d] = %d\n", i , pt1[i]); printf(" tab2[%d] = %d\n", i , pt2[i]); printf(" reel[%d] = %6.3f\n", i , preel[i]); } return(1); }

// Résultat tab1[0] = 0 tab2[0] = 10 reel[0] = 20.000 tab1[1] = 2 tab2[1] = 12 reel[1] = 22.000 impression de *(tab1+i), *(tab2+i), *(reel+i) tab1[0] = 0 tab2[0] = 10 reel[0] = 20.000 tab1[1] = 2 tab2[1] = 12 reel[1] = 22.000 impression de *tab1+i, *tab2+i, *reel+i tab1[0] = 0 tab2[0] = 10 reel[0] = 20.000 tab1[1] = 1 tab2[1] = 11 reel[1] = 21.000 impression de *pt1+i, *pt2+i, *preel+i tab1[0] = 0 tab2[0] = 10 reel[0] = 20.000 tab1[1] = 1 tab2[1] = 11 reel[1] = 21.000 &tab1 = ffec &tab2 = ffe8 v1 = ffec v2 = ffec pt1 = ffec pt2 = ffe8 void * pt2 = ffe8 impression de pt1[i], pt2[i] , preel[i] tab1[0] = 0 tab2[0] = 10 reel[0] = 20.000 tab1[1] = 2 tab2[1] = 12 reel[1] = 22.000

Page 105: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

105

11.3 Opérations algébriques sur les pointeurs D'un point de vue logique, il est seulement possible de comparer des pointeurs pointant sur des objets d'un même tableau. Seules sont autorisées les opérations suivantes : addition ou soustraction de constante entière à un pointeur, soustraction de pointeurs de même type, comparaison de pointeurs.

■ Addition et soustraction

L'addition (resp. la soustraction) d'une constante à un pointeur signifie que l'on pointe sur n objets après (resp. avant) le pointeur. Les opérateurs correspondants sont respectivement notés + et -.

■ Comparaison

Les comparaisons sont à effectuer au sens d'une relation d'ordre (avant ou après). Les opérateurs relationnels sont < <= > >=.

■ Initialisation

Pour initialiser un pointeur, il faut procéder comme suit :

int i= 5; // Définition et initialisation de i int *pi = & i; // Définition et initialisation du pointeur pi

On peut aussi initialiser un pointeur à la valeur NULL en écrivant :

int *pi; pi = (int *) NULL;

Si on souhaite que pi pointe sur le caractère i, il faut utiliser l'opérateur de transtypage (cast) pour définir le type de l'objet pointé de la façon suivante :

int i = 5; char *pi = (char*) &i;

L'initialisation d'un pointeur sur une fonction s'écrit :

int (*pfonc)(void) = fonc;

La variable pfonc est un pointeur sur l'adresse de la fonction entière sans argument fonc.

■ Conversion du type de l'objet pointé

Un pointeur sur un objet d'un type donné peut être converti en un pointeur sur un objet d'un type différent. Le pointeur résultant peut être incohérent si le pointeur d'origine ne fait pas référence à un objet correctement aligné en mémoire.

La norme ANSI garantit qu'un pointeur sur un objet d'un type donné peut être converti en un pointeur sur un objet d'un type différent si ce dernier a un alignement strictement identique ou inférieur.

Page 106: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

106 CHAPITRE IV ───────────────────────────────────────────────────

12. CHAINES DE CARACTERES ET POINTEURS

Une chaîne de caractères est un tableau de caractères, accessible par un pointeur sur son premier élément et toutes les fonctions de manipulation de chaînes de caractères peuvent être développées avec des pointeurs dont voici une application étonnante.

■ Initialisation

Considérons le programme suivant :

int main(void ) { char *chaine = "bonjour", *pchaine = chaine;

// Création d'une chaîne de caractères accessible par le pointeur pchaine printf( " chaîne initiale : %s\n",chaine); // Impression caractère par caractère while (*chaine!= '\0') { printf("%c",*chaine);chaine++ ;} // Le pointeur a été modifié d'où aucune impression printf ("\n aucune impression %s\n",chaine); // Réinitialisation du pointeur chaine= pchaine; printf(" chaîne totale %s \n",chaine); return(1);

}

// Résultat chaîne initiale : bonjour bonjour aucune impression chaîne totale bonjour

■ Interprétation

• La variable chaine est de type pointeur. Sa définition provoque la définition d'une chaîne de huit caractères que l'on peut imprimer (premier ordre d'impression).

• L'impression caractère par caractère prouve que la variable chaine est bien de type pointeur (deuxième impression).

• Le pointeur chaine ayant été incrémenté jusqu'à la fin de la chaîne, il n'y a plus rien à imprimer (troisième impression).

• Une réinitialisation du pointeur donne la dernière impression.

� Remarque 1

La chaîne de caractères crée est sans nom explicite car l'instruction

char *chaine = "bonjour";

définit un pointeur sur une chaîne de caractères et l'initialisation provoque l'allocation de mémoire nécessaire à la gestion de la chaîne de caractères comme le confirme le programme suivant.

Page 107: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

107

#include <stdio.h> int main(void ) { char *chaine ="bonjour";

printf("chaine = %s sizeof(chaine) = % d\n", chaine, sizeof(chaine)); printf("&chaine = %x *chaine = %c \n",&chaine, *chaine); printf("sizeof(*chaine) = % d\n", sizeof(*chaine)); return(1);

}

// Résultats chaine = bonjour sizeof(chaine) = 2 &chaine = ffde *chaine = b sizeof(*chaine) = 1

� Remarque 2

Soient les deux instructions :

char message[] = "Bonjour Monsieur "; char *pmessage = "Bonjour Monsieur ";

message est un tableau dont la taille est fixée à la définition, pmessage est un pointeur initialisé sur le premier caractère d'une chaîne non nommée.

� Exemples

char string[30] = "bonjour"; char chaine[30],*pchaine = "bonjour"; while(*pchaine != '\0') chaine[i++ ]=* pchaine++ ; chaine[i++ ]= '\0'; for (i=0;*pchaine != '\0';chaine[i++ ] = * pchaine++ ); chaine[i] = ' \0';

■ Taille

Le calcul de la taille d'une chaîne de caractère illustre la puissance et la concision de l'utilisation des pointeurs dont voici des versions de plus en plus compactes.

int str(char *s) { int n;

for (n = 0; *s != '\0'; s++ ) n++ ; return(n);

}

char *str(char *s) { char *n = s; // Initialisation de n, pas de *n

while (*n != '\0') n++ ; // clause identique à while (*n) n++ ; return(n-s); // Soustraction de pointeurs

}

Les deux dernières instructions peuvent aussi s'écrire :

while (*n++ ) ; return(n-1-s);

Page 108: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

108 CHAPITRE IV ───────────────────────────────────────────────────

■ Comparaison

La fonction strcmp compare, caractère par caractère et dans l'ordre lexicographique, le nombre et la valeur des occurrences de deux chaînes de caractères. Suivant la valeur de l'argument de retour, la longueur de s est inférieure à celle de t s'il est négatif, égale à celle de t s'il est nul, supérieure à celle de t s'il est positif. Voici deux versions : une avec tableau, l'autre avec pointeur.

int strcmp(char s[], char t[]) // Comparaison de chaînes de caractères { int i = 0; while (s[i] == t[i]) if (s[i++ ] == ' \0') return(0); return(s[i] - t[i]); }

char *strcmp (char *s, char *t) { for (; *s = * t; *s++, * t++ ) if (*s == ' \0') return (0); return(*s - *t); }

13. APPLICATIONS DES POINTEURS

■ Transmission d'arguments à la fonction main

Une application fondamentale de la notion de tableaux de dimension incomplète est d'écrire des fonctions dont le nombre d'arguments d'appel peut être variable. Cette propriété permet de transmettre un nombre quelconque d'arguments à une commande sous forme de chaînes de caractères à son appel.

Le principe de base est le suivant : deux arguments sont transmis lors de l'appel de la fonction main. Ce sont :

int argc; char *argv[]; // Nombre et liste des arguments d'appel.

� Exemple

int main(int argc, char *argv []) { int i;

for (i = 0; i < argc; i++ ) printf("%s%c",argv[i], (i<argc-1) ? ' ' : '\n'); return(1);

}

La boucle peut avoir la forme

while(--argc >=0) printf("%s%c",*argv++, (argc >0)?' ' : '\n');

Page 109: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

109

■ Pointeur sur des tableaux et tableaux de pointeurs

Des pointeurs sur des tableaux permettent, pour des tableaux à plusieurs indices, d'accéder directement à une ligne du tableau, sans en modifier les composantes.

� Exemple

// Permutation des composantes d'un tableau de chaînes de caractères char * tab[3] = {"Bonjour","Demain","Aujourd'hui"} ; char * aux; aux = tab[0]; tab[0] = tab[2]; tab[2] = aux ;

■ Transtypage et pointeurs

Il est dangereux d'utiliser sans précaution l'opérateur de transtypage sur des pointeurs. Supposons par exemple le programme suivant :

char *c; int *a; c = (char *) a;

Le compilateur choisit un des octets pointés par a et retourne son adresse. Le choix est toujours le même sur une machine donnée (l'octet de poids faible ou de poids fort) mais il varie d'une implémentation à une autre.

■ Pointeurs sur des fonctions

L'identificateur d'une fonction est l'adresse de sa première instruction. C'est donc un pointeur constant déréférencé, comme celui d'un tableau.

� Exemple 1

int plus(int x, int y) // Mini calculatrice (addition et soustraction) { return(x+y);}

int moins(int x, int y) { return (x-y);}

int main(void ) {int (*f[])(int, int) = {plus, moins};

/* f est un tableau de pointeurs initialisé à l'adresse des deux fonctions précédentes */ int m = 4, n = 5 ,i; for(i=0; i<2 ;i++) printf("m = %d n = %d res %d\n",m,n,(*f[i])(m,n)); return(1);

}

// Résultat m = 4 n = 5 res = 9 m = 4 n = 5 res = -1

Page 110: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

110 CHAPITRE IV ───────────────────────────────────────────────────

� Exemple 2

Voici un programme (certes un peu compliqué), de calcul de fonction de multiplication et de fonction puissance.

int plus (int x, int y) { return(x+y);}

int prod (int x, int y) { return(f(x,y,plus,0));}

int puis (int x, int y) { return(f(x,y,prod,1));}

int f(int x, int y, int (*g)(), int a) { return( (y==0) ?a : (*g)(f(x,y-1,g,a),x));}

int main(void ) { int i,j; scanf("%d %d",&i,&j); printf("%d * %d = %d\n %d ** %d = %d\n", i,j,prod(i,j), i,j , puis(i,j)); return(1); }

// Résultat 2 * 3 = 6 2 ** 3 = 8

14. FONCTION A NOMBRE VARIABLE D'ARGUMENTS

Le programmeur peut définir des fonctions avec un nombre variable d'arguments, chacun d'un type aléatoire. C'est par exemple le cas de la fonction printf.

14.1 Principe général Le principe est le suivant :

• la fonction est définie en indiquant l'argument d'appel à partir duquel le nombre des arguments suivants varie,

• le nombre d'arguments ainsi que leur type respectif est indiqué à chaque appel,

• chaque type d'argument est décrit préalablement par un entier, utilisé comme indicateur de son type.

� Exemple

void f_var_arg(int nb_arg,...)

14.2 Gestion de la pile des arguments La pile d'exécution contient les arguments d'appel. Elle est gérée automatiquement, pour des fonctions à nombre d'arguments fixe et dont le type est déterminé à la définition de la fonction. Quand le nombre d'arguments est variable et leur type aléatoire, le programmeur va devoir décrire cette gestion. Il dispose pour cela d'un pointeur et de primitives permettant de réaliser des opérations sur ce dernier.

Page 111: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

111

■ Pointeur de pile

Dans le corps de la fonction, une variable pointeur de pile, de type prédéfini va_list, est utilisée pour gérer la pile des différents arguments.

■ Opérations sur le pointeur de pile

Les opérations définies sur le pointeur de pile sont l'initialisation, la mise à jour selon le type de l'argument, la fermeture.

Les primitives associées sont respectivement va_start, va_arg, va_end.

■ Le type va_list

Le type prédéfini va_list est défini par une instruction typedef dans le fichier en-tête stdarg.h.

Ce type représente, selon les implémentations un pointeur ou un tableau de pointeurs sur des objets de type très divers. Il est utilisé pour les implémentations des fonctions printfou scanf.

■ La fonction va_start

Synopsis #include <stdarg.h> void va_start(va_list pointeur, paramètre_le_plus_à_droite);

Description La macro va_start pointe sur la base de la liste des arguments. Son utilisation est nécessaire pour initialiser le pointeur, de type va_list.

Le paramètre_le_plus_droite est le dernier paramètre avant l'unité syntaxique "..." . C'est en général un entier définissant le nombre de couples d'arguments variables de la fonction.

� Exemple

va_list p_liste; int nb_arg; va_start(p_liste, nb_arg);

■ La fonction va_arg

Synopsis #include <stdarg.h> nom_de_type * va_arg(va_list pointeur, nom_de_type);

Description La fonction va_arg met à jour le pointeur de la pile en fonction du type de l'argument courant. Elle retourne un pointeur sur l'argument suivant de la liste. Elle est toujours utilisé en conjonction avec les fonctions va_start et va_end.

va_arg(p_liste, int); ... va_arg(p_liste, char *);

Page 112: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

112 CHAPITRE IV ───────────────────────────────────────────────────

■ La fonction va_end

Synopsis #include <stdarg.h> void va_end(va_list pointeur);

Description La fonction va_end, dont l'usage est seulement recommandé, met à jour le pointeur de pile en fin d'utilisation.

� Exemple

#include <stdarg.h> #define NMAX 10 // Nombre maximum d'arguments variables accepté void f_var_arg(int nb_arg,...) // Forme générale : f_var_arg(int nb_arg,int n1,arg1, int n2, arg2, int n3, arg3,...) // Le premier arguments d'appel indique le nombre d'arguments d'une liste // Constituée de couples d'arguments (indicateur_du_type, argument) // Le premier paramètre du couple est toujours de type int, descripteur du type de l'argument // suivant et ainsi de suite. Par exemple 0 : char *, 1 : int , 2 : double { va_list p_liste; // Initialisation du pointeur p_liste

int i; if(nb_arg>NMAX) nb_arg=NMAX; // Le nombre d'arguments est borné par NMAX va_start(p_liste, nb_arg); // Initialisation obligatoire du pointeur de pile p_liste

// Boucle sur le nombre d'arguments for(i=0; i < nb_arg; i++) { printf("paramètre %d --> ",i);

switch(va_arg(p_liste,int)) // Test sur le type de l'argument suivant et mise à jour de la pile { case 0 : // Type char *

// Mise à jour du pointeur p_liste printf("type : char * valeur : %s\n", va_arg(p_liste, char *)); break; case 1 : printf("type : int valeur : %d\n",va_arg(p_liste, int)); break; case 2 : printf("type : double valeur : %lf\n",va_arg(p_liste, double)); break; case 3 : printf("type : float valeur : %lf\n",va_arg(p_liste, double)); break; // L'instruction : printf("type : float valeur : %lf\n",va_arg(p_liste, float)); // ne fonctionne pas

} }

va_end(p_liste); // Recommandé pour une ultime mise à jour du pointeur de pile }

int main(void ) { printf("\t\nPremier appel\n"); // 3 arguments : char *, int, double

f_var_arg(3,0,"abcdefg",1,255,2,3.5657); printf("\n\tDeuxième appel\n"); // 4 arguments : entier, char *, int, double f_var_arg(4,1,-2345,0,"DFGHbhj",1,456,2,-67.67); printf("\n\tTroisième appel\n"); // 5 arguments : double, char *, int, double, char * f_var_arg(5,2,-23.45,0,"Bonjour",1,-56,3,-7.67,0,"Au revoir"); return(1);

}

Page 113: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

113

// Résultat Premier appel paramètre 0 --> type : char * valeur : abcdefg paramètre 1 --> type : int valeur : 255 paramètre 2 --> type : double valeur : 3.565700 Deuxième appel paramètre 0 --> type : int valeur : -2345 paramètre 1 --> type : char * valeur : DFGHbhj paramètre 2 --> type : int valeur : 456 paramètre 3 --> type : double valeur : -67.670000 Troisième appel paramètre 0 --> type : double valeur : -23.450000 paramètre 1 --> type : char * valeur : Bonjour paramètre 2 --> type : int valeur : -56 paramètre 3 --> type : float valeur : -7.670000 paramètre 4 --> type : char * valeur : Au revoir

15. STRUCTURES DE DONNEES ABSTAITES ET OBJETS STRUCTURES

Le langage C permet de définir des objets structurées conformément à une structure de donnée abstraite (variable structurée et union). Ainsi, un agenda est constitué du nom, du prénom, de l'adresse, du numéro de téléphone de différentes personnes.

Un objet abstrait est appelé enregistrement logique lors de requêtes de lecture ou d'écriture

En programmation objet, l'objet abstrait devient une classe dotée de données structurées (ses attributs) et des traitements associés appelés méthodes.

■ Définitions

La déclaration struct décrit une modèle de données abstraites constituée d'un agrégat d'objets typés, et définit des variables structurées (appelées structures) conformes aux attributs (champs, zones) de ce modèle, stockés séquentiellement.

La taille nécessaire au stockage d'une variable structurée est le total des tailles des différents attributs qui la constituent, complété si nécessaire de bits d'alignement.

En programmation objet, les attributs sont appelés données membres.

Page 114: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

114 CHAPITRE IV ───────────────────────────────────────────────────

15.1 Déclaration et définition La déclaration struct décrit les attributs d'une variable structurée sans allocation mémoire. Elle devient une définition quand une variable structurée lui est associée.

struct { type déclarateur ; } ;

nom_structure identificateur

Diagramme d'utilisation du mot clé struct

■ Interprétation

struct mot clé nom_structure définition (optionnelle) du nom du type de la variable

structuré (tag) ce qui permet de créer un nouveau type de variable

{ ...} marque de début/fin de la description de la structure abstraite type type prédéfini ou non de l'attribut déclarateur identificateur de l'attribut identificateur définition optionnelle des identificateurs des variables

structurées.

� Exemples

struct date {int jour; int mois; int année;}; // Déclaration de la structure abstraite date

// Deux définitions similaires des variables structurées de type date aujourd_hui, demain struct date aujourd_hui,demain; struct date {int jour; int mois; int année;} aujourd_hui,demain;

// Définitions des variables structurées aujourd_hui, demain struct { int jour; int mois; int année;} aujourd_hui, demain;

Dans ce cas, le type de structure abstrait date n'a été déclaré.

15.2 Règles d'utilisations Une variable structurée peut être définie à partir d'une autre variable structurée. Ainsi, la déclaration

#define L 50 struct personne {char nom[L]; char prenom[L]; struct date date_mois;} individu;

est licite. C'est la définition d'une variable structurée personne et la déclaration d'un identificateur individu de type personne. Le symbole date_mois est de type prédéfini date.

■ Initialisation d'une variable structurée

Une variable structurée peut être initialisée de la façon suivante :

struct personne individu = {"Dupont", "Albert", 20, 3, 53};

Page 115: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

115

■ Récursivité

Une définition récursive est autorisée.

� Exemple

Soit la variable structurée noeud :

struct noeud {int code; struct noeud *gauche; struct noeud *droite;};

Elle a la représentation suivante en mémoire :

entier pointeur_sur_une_structure_noeud pointeur_sur_une_structure_noeud

� Exemples

struct noeud arbre[10 ]; // Déclaration d'un tableau de 10 noeuds struct noeud *p; // p pointeur sur un noeud struct noeud * f (void); // f fonction sans argument retournant un pointeur vers un noeud

15.3 L'opérateur de sélection de membre L'opérateur de sélection de membre accède aux champs d'une variable structurée par une construction (arborescente) de la forme :

nom_variable.nom_du_champ[.nom_du_champ]

� Exemple

demain.jour = 20; individu.date_mois.jour = 20;

15.4 Pointeurs sur des variables structurées : l'op érateur -> La définition :

struct date *pt;

définit la variable pt de type pointeur sur la variable structurée date.

Pour accéder au champ mois, on écrit :

(*pt).mois = 11 ; (1)

Les parenthèses sont nécessaires compte tenu de la hiérarchie des opérateurs * et ().

L'opérateur binaire -> permet l'accès à un attribut de la variable structurée. Il est défini de la façon suivante :

L'instruction

p -> x;

est équivalente à l'instruction :

(*p).x;

Ainsi, l'instruction (1) s'écrit également :

pt ->mois = 11;

Page 116: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

116 CHAPITRE IV ───────────────────────────────────────────────────

� Exemple

#include <stdio.h> #define TAILLE 30 int main(void) {// Déclaration des variables structurées date et personne

struct date {int jour; int mois; int annee;}; struct personne {char nom[TAILLE]; char prenom[TAILLE]; struct date date_mois;}; struct date demain; // Définition des variables structurées demain et individu

// Initialisation de la variable individu de type personne struct personne individu = {"Dupond","Jean - Albert",20,3,1953 }; printf("Caractéristiques initiales de l'individu :\n"); printf("Nom : %s\t Prénom : %s \n",individu.nom, individu.prenom); printf("Date de naissance : %d - %d - %d \n", individu.date_mois.jour, individu.date_mois.mois, individu.date_mois.annee); printf("fin du premier acte\n ");

// Autres formes d'initialisation demain.jour = 20; printf("La date de demain est : %d\n ",demain.jour); individu.nom = "Dupont"; individu.prenom = "Albert "; individu.date_mois.jour = 20; individu.date_mois.mois = 03; individu.date_mois.annee = 1920;

// Impression des résultats printf("Caractéristique de individu :\n"); printf("Nom : %s\t Prénom : %s \n",individu.nom, individu.prenom); printf("Date de naissance : %d - %d - %d \n", individu.date_mois.jour, individu.date_mois.mois, individu.date_mois.annee);

}

// Résultat Caractéristiques initiales de l'individu : Nom : Dupond Prénom : Jean - Albert Date de naissance : 20 - 3 - 1953 fin du premier acte La date de demain est : 20 Caractéristique de individu : Nom : Dupont Prénom : Albert Date de naissance : 20 - 3 - 1920

15.5 Associativité des opérateurs * ->++ -- () [ ] Nous avons vu précédemment que la hiérarchie des opérateurs de manipulation des pointeurs est la suivante :

Opérateurs Ordre d'évaluation () [ ] ->. gauche à droite ! ~ ++ -- - * & sizeof droite à gauche

La bonne utilisation de ces règles nécessite certaines précautions.

Page 117: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

117

� Exemple

#include <stdio.h> int main(void) { int i = 16, j = 32;

int x = 0; int *pt =&i; printf(" valeur d'initialisation\n"); printf(" i = %d j = %d",i,j); printf(" &i = %x &j = %x\n", &i,&j); printf(" \n\névaluation de pt\n"); printf(" i = %d *pt = %d pt = %x\n", i, *pt, pt); *pt++; // Cette expression équivalente à *(pt++) doit être une Lvalue. printf("\n\n évaluation de *pt++\n"); printf(" *pt = %d pt = %x\n", *pt, pt); (*pt)++; // pt est inchangé car cette expression n'a de sens que pour une Lvalue printf("\n\n évaluation de (*pt)++\n"); printf(" *pt = %d pt = %x\n", *pt, pt); x=*(pt++); /* x = *pt; pt++ ; */ printf("\n\n évaluation de x = *(pt++)\n"); printf(" x =%d *pt = %d pt = %x\n", x, *pt, pt); x=*pt++; /* x = *pt; pt++ */ printf("\n\n évaluation de x = *pt++\n"); printf(" x =%d *pt = %d pt = %x\n", x, *pt, pt); return(1);

}

■ Applications

1°) Vu les règles d'associativité, l'instruction :

++p -> x est équivalente à l'instruction ++ (*p).x

D'où incrémentation du champ x, pas du pointeur p.

Démonstration : ++p ->x = ++(p->x) = ++((*p).x)) = ++(*p).x

La première égalité résulte de la hiérarchie des opérateurs -> et ++.

La deuxième est une conséquence de la définition de l'opérateur ->.

La troisième résulte de la hiérarchie des opérateurs (),.,++.

2°) L'instruction

++p -> x;

n'est pas équivalente à l'instruction (L-valeur ou R-valeur)

(++p ) -> x;

En effet, la deuxième instruction s'écrit :

(++p ) -> x = (* (++p )).x

Page 118: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

118 CHAPITRE IV ───────────────────────────────────────────────────

L'instruction exécute la post-incrémentation du pointeur après un accès au champ x, l'expression entre parenthèses étant évaluée en premier.

3°) Soit l'instruction :

(p++ ) ->x =...;

Compte tenu des règles de priorité, on a :

(p++ ) ->x =...⇔ (* (p++ )).x =... ;

L'opérateur de post-incrémentation ++ étant utilisé, l'instruction précédente s'écrit :

((*p)).x =...; p++ ;

4°) L'instruction (*p)-> y accède à l'objet pointé par y puisque :

(*p)-> y = (* (*p)).y = (** p).y

5°) L'instruction (*p)->y++ est une Rvaleur ou une Lvaleur. Son effet est de post-incrémenter le champ y, puisque :

(*p)-> y++ = ((**p).y)++

6°) Compte tenu des hiérarchies respectives des opérateurs ++ et ->, la post-incrémentation du pointeur pt s'écrit pt++->y .

7°) La post-incrémentation du pointeur *p s'écrit (*p)++ -> y . Il suffit de poser q = *p dans l'égalité précédente.

8°) L'instruction (*p++ )->y post-incrémente le pointeur p puisque :

(*p++ )->y = (* (p++ ))->y

15.6 Pointeurs, variables structurées et fonctions Les opérations sur les variables structurées sont les suivantes :

• accès à son adresse avec l'opérateur & ,

• accès, puis modification d'un champ avec l'opérateur ->,

• transmission d'une structure comme argument d'une fonction (valeur ou adresse),

• retour d'une variable structurée par une fonction.

� Exemple

• Ce programme définit les types structurés date et personne.

• La fonction lit effectue la saisie des champs d'un individu de type personne.

• Les arguments sont transmis par adresse.

• Les résultats sont écrits de deux façons différentes (fonctions ecrit et ecrit2).

La première transmet la structure par valeur, ce qui est naturel puisque aucune modification ne doit être faite à l'écriture, la seconde par adresse.

Page 119: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

119

#include <stdio.h> #define TAILLE 30 struct date {int jour; int mois; int annee;}; // Déclaration des variables globales struct personne { char nom[TAILLE]; char prenom[TAILLE]; struct date date_mois;}; void main(void) { struct date demain; // Définitions

struct personne individu; // Prototypes void ecrit(struct personne); // Transmission par valeur (la meilleure) void ecrit2(struct personne *); // Transmission par adresse void lit (struct personne *); // Transmission par adresse // Initialisation demain.jour = 20; printf(" La date de demain est : %d\n ", demain.jour); // Lecture d'une structure lit(&individu); // Transmission par adresse // écriture d'une structure ecrit(individu); // Transmission par valeur ecrit2(&individu); // Transmission par adresse

}

void lit (struct personne * pindividu) {(*pindividu).nom = "Dupont"; // Remplissage du champ nom

pindividu->prenom = "Albert "; // Remplissage du champ prénom // Remplissage du champ date pindividu->date_mois.jour = 20; (*pindividu).date_mois.mois = 03; pindividu->date_mois.annee = 1920;

}

void ecrit( struct personne individu) // Transmission par valeur { printf(" Caractéristique de individu :\n");

printf(" Nom : %s \t Prénom : %s \n", individu.nom, individu.prenom); printf("Date de naissance : %d - %d - %d \n", individu.date_mois.jour, individu.date_mois.mois, individu.date_mois.annee);

}

void ecrit2( struct personne *individu) // Transmission par adresse { printf(" Caractéristique de individu :\n"); printf(" Nom : %s \t Prénom : %s \n", individu->nom, individu->prenom); printf(" Date de naissance : %d - %d - %d \n", individu->date_mois.jour, individu->date_mois.mois, individu->date_mois.annee); }

// Résultat La date de demain est : 20 Caractéristique de individu : Nom : Dupont Prénom : Albert Date de naissance : 20 - 3 - 1920 Caractéristique de individu : Nom : Dupont Prénom : Albert Date de naissance : 20 - 3 - 1920

Page 120: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

120 CHAPITRE IV ───────────────────────────────────────────────────

15.7 Variables structurées et tableau La définition de tableaux de variables structurées ou de variables structurées contenant un tableau est autorisée. Ainsi, l'instruction

struct t tabs [8 ] [3 ];

définit huit tableaux de trois variables structurées de type t.

� Exemple

// Structure constituée d'un tableau #include <stdio.h> #define NLIGNE 2 #define NCOL 3 #define NTAB 4 struct Super_tableau {int tab[NLIGNE][NCOL][NTAB]; }; int main (void) { struct Super_tableau super_tableau, *pt = &super_tableau, super_tableau2;

void remplissage(struct Super_tableau *); void affichage(struct Super_tableau); remplissage(pt); printf("\nAffichage de super_tableau\n"); affichage(super_tableau); super_tableau2 = super_tableau; printf("\nAffichage de super_tableau2\n"); affichage(super_tableau2); return(0);

}

void remplissage(struct Super_tableau * tableau) { int i , j, k ;

for (i = 0 ; i < NLIGNE ; i++) for (j = 0 ; j < NCOL; j++) for (k =0 ; k < NTAB; k++) tableau->tab[i][j][k] = i+j+k; }

void affichage (struct Super_tableau tableau) { int i , j, k , compteur = 0;

for (i = 0 ; i < NLIGNE ; i++) for (j = 0 ; j < NCOL; j++) for (k =0 ; k < NTAB; k++) { if(compteur %4 !=0)

printf(" tab[%d][%d][%d] = %d ",i,j,k,tableau.tab[i][j][k]); else printf("\n tab[%d][%d][%d] = %d ", i, j, k, tableau.tab[i][j][k]); compteur++;

} printf("\n");

}

// Résultat Affichage de super_tableau tab[0][0][0] = 0 tab[0][0][1] = 1 tab[0][0][2] = 2 tab[0][0][3] = 3 tab[0][1][0] = 1 tab[0][1][1] = 2 tab[0][1][2] = 3 tab[0][1][3] = 4 tab[0][2][0] = 2 tab[0][2][1] = 3 tab[0][2][2] = 4 tab[0][2][3] = 5 tab[1][0][0] = 1 tab[1][0][1] = 2 tab[1][0][2] = 3 tab[1][0][3] = 4 tab[1][1][0] = 2 tab[1][1][1] = 3 tab[1][1][2] = 4 tab[1][1][3] = 5 tab[1][2][0] = 3 tab[1][2][1] = 4 tab[1][2][2] = 5 tab[1][2][3] = 6 Affichage de super_tableau2 tab[0][0][0] = 0 tab[0][0][1] = 1 tab[0][0][2] = 2 tab[0][0][3] = 3 …

Page 121: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

121

15.8 Affectation de variable structurée Une variable structurée peut être affectée à une autre variable structurée à condition d'être constituée des mêmes champs. C'est une première forme de surdéfinition de l'opérateur d'affectation, généralisée en C++.

� Exemple

#include <stdio.h> void main(void) { typedef struct{ int jour; int mois; int annee;} date;

date jour1 = {20,12,1988}, jour2 = jour1; printf("jour = %d mois = %d annee = %d\n",jour1.jour, jour1.mois, jour1.annee); printf("jour = %d mois = %d annee = %d\n", jour2.jour, jour2.mois, jour2.annee);

}

// Résultat jour = 20 mois = 12 annee = 1988 jour = 20 mois = 12 annee = 1988

■ Application : assignation globale d'un tableau

Un tableau d'une variable structurée peut être assigné globalement. C'est d'ailleurs le seul moyen en C.

int main(void) { struct GrossTab {int tableau[5];} tab1, tab2 = {1, 2, 3, 4, 5}; int i; printf("sizeof (tab1) = %d",sizeof(tab1)); for (i=0; i <5; i++) tab1.tableau[i]=i; tab2= tab1; return 1; }

15.9 Champs de bits Des variables structurées dont les champs sont de type int, ou unsigned int constituent des champs de bits.

Synopsis struct { unsigned int champ_optionnel: nombre_de_bits_du_champ; ...}

Il peut être nécessaire d'intercaler des bits de remplissage.

� Exemple 1

struct { unsigned int sexe : 1 ; // 2 états unsigned int mois : 4 ; // 12 états utiles sur 16 possibles unsigned int : 3 // Bits de remplissage (padding)

}

Page 122: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

122 CHAPITRE IV ───────────────────────────────────────────────────

� Exemple 2

Programme de création, de saisie, d'écriture d'un numéro de sécurité sociale.

#include <stdio.h> struct SecSoc { unsigned int sexe : 1 ; // 2 états

unsigned int annee : 7 ; // 100 états unsigned int :4; // 4 bits de remplissage unsigned int mois : 4 ; // 12 états unsigned int :1; // 1 bit de remplissage unsigned int departement : 7; // 98 états unsigned int :8; // 8 bit de remplissage unsigned int :6; // 6 bit de remplissage unsigned int canton : 10 ; // 1000 états unsigned int :6; // 6 bit de remplissage unsigned int quantieme : 10 ; // 1000 états

};

int saisie(struct SecSoc *secu) { unsigned int sexe=0, annee =0, mois=0, departement=0, canton=0, quantieme =0;

printf("\n sexe : 1 (garçon) ou 2 (fille) "); scanf("%u", &sexe); switch(sexe)

{ case 1 : secu->sexe = 0; break; case 2 : secu->sexe = 1; break; default : printf("erreur sexe\n") ; return(0);

} printf("année de naissance (2 chiffres) "); scanf("%u",&annee); if(annee > 99) { printf("erreur année\n") ; return(1);} secu->annee=annee;

printf("\nmois (1-12) "); scanf("%u", &mois); if(mois > 12) { printf("erreur mois \n"); return(2);} secu->mois=mois;

printf(" \nnuméro de département (1-98) "); scanf("%u",&departement); if(departement > 98) {printf("erreur departement\n"); return(3);} secu->departement=departement;

printf(" \nnuméro de canton (0-999) "); scanf("%u",&canton); if(canton > 999) {printf("erreur canton\n"); return(4);} secu->canton=canton;

printf(" \nnuméro de quantième (0-999) "); scanf("%u",&quantieme); if(quantieme > 999) {printf("erreur quantième\n"); return(5);} secu->quantieme=quantieme;

}

void ecriture(struct SecSoc secu) { char * Mois[12] = {"Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet",

"Août" ,"Septembre", "Octobre", "Novembre" , "Décembre" } ; printf("\nsexe (0-masculin, 1-féminin) : %u\n",secu.sexe); printf("année de naissance 19%u\n", secu.annee); printf("mois de naissance : %s \n",Mois[secu.mois-1] ); printf("département de naissance : %u \n canton : %u \n ",secu.departement, secu.canton); printf("quantième : %u \n",secu.quantieme);

}

Page 123: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

123

int main(void) // Gestion des champs de bits { struct SecSoc num_sec, *pt = & num_sec;

unsigned int *pt2 = (unsigned int *) pt ; int i ; // Prototypes int saisie(struct SecSoc *); void ecriture (struct SecSoc);

// Appels saisie(pt); ecriture(num_sec); // Impression en hexadécimal for (i = 0; i <4; i++) {printf("%x ", *pt2); pt2++;} printf("\n%x\n",*pt); printf("sizeof(unsigned int) = %d\n",sizeof(unsigned int)); printf("sizeof(struct (SecSoc)) = %d\n",sizeof(struct SecSoc)); return(1);

}

// Résultat sexe : 1 (garçon) ou 2 (fille) 1 année de naissance (2 chiffres) 53 mois (1-12) 03 numéro de département (1-98) 75 numéro de canton (0-999) 012 numéro de quantième (0-999) 130 sexe (0-masculin, 1-féminin) : 0 année de naissance 1953 mois de naissance : Mars département de naissance : 75 canton : 12 quantième : 130 306a e97 316 2090 306a sizeof(unsigned int) = 2 sizeof(struct (SecSoc)) = 8

16. UNIONS

Les variables structurées et les unions ont une syntaxe d'utilisation très voisine. Par contre, la sémantique est différente : les variables structurées sont constitués de champs consécutifs, les unions sont constitués de champs qui se recouvrent.

■ Déclaration et définition

La déclaration union permet de définir des structures de données permettant de manipuler alternativement des objets de différents types localisés à une même adresse dans la mémoire. Une union est une variable structurée dont tous les attributs sont superposés sur la même adresse ce qui permet de l'interpréter de plusieurs façons différentes.

La syntaxe du mot clé union est similaire à celui du mot clé struct.

Page 124: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

124 CHAPITRE IV ───────────────────────────────────────────────────

� Exemple 1

union typunion {char car;int entier;long longueur;double x;}var;

Le type courant de var est défini par la variable typunion. Ainsi peut-on écrire :

int i; double y; typeunion var1, var2; var1.entier = i; var2.x = y;

� Exemple 2

int main(void) { union bizarre{ char car; int entier; long longueur; double x; } var1, var2;

int i = 5; double y = 38.6785; var1.entier = i; var2.x = y; var1.car = 'c'; printf(" var1.car = %c var1.entier = %d\n var1.longueur = %d var1.x = %e\n",

var1.car, var1.entier, var1.longueur,var1.x); printf(" var2.car = %c var2.entier = %d\n var2.longueur = %d var2.x = %e\n",

var2.car, var2.entier,var2.longueur, var2.x); }

// Résultats var1.car = c var1.entier = 99 var1.longueur = 99 var1.x = 2.199951e-034 var2.car = _ var2.entier = 11011 var2.longueur = 11011 var2.x = 2.356809e+110

Le seul résultat exact est var1.car les variables imprimées n'ayant pas été initialisées.

� Exemple 3

void main(void) { union bizarre { char car; int entier; long longueur; double x; } var1, var2;

int i = 5; double y = 38.6785; printf("y = %e\n",y); var1.entier = i; printf("var1.entier = %d\n",var1.entier); var2.x = y; printf("var2.x = %e\n",var2.x); var1.car = 'c'; /* Modifie l'interprétation de var1.entier */ printf(" var1.car = %c var1.entier = %d var1.longueur = %d var1.x = %e \n",var1.car,

var1.entier,var1.longueur,var1.x); printf(" var2.car = %c var2.entier = %dvar2.longueur = %d var2.x = %e\n",

var2.car, var2.entier,var2.longueur,var2.x); }

// Résultats y = 3.867850e+001 var1.entier = 5 var2.x = 3.867850e+001 var1.car = c var1.entier = 99 var1.longueur = 99 var1.x = 1.390369e+093 var2.car = _ var2.entier = 11011 var2.longueur = 11011 var2.x = 2.356809e+110

■ Variable structurée et union

Une union peut être imbriquée dans une variable structurée.

Page 125: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

125

17. TYPE ENUMERE

Le mot clé enum définit un ensemble ordonné fini de constantes symboliques de type entier.

Forme enum nom_de_type {liste de constantes symboliques} [liste_de_variables] ;

� Exemple

enum jours { lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche}; jours jour; // La variable jour est de type jours

Les instances de la liste d'énumération sont considérées comme des constantes entières dont les valeurs sont numérotées par défaut à partir de zéro, par pas de 1. Ainsi, lundi = 0; mardi = 1;...

L'instruction

jour = mardi;

est équivalente à l'instruction

jour = 1;

L'initialisation explicite du premier élément de la liste avec une valeur entière non nulle est autorisée.

enum mois { Janvier = 1, Février, Mars, ..., Décembre };

� Exemple

int main(void) { enum jours {lundi, mardi, mercredi, jeudi,vendredi, samedi, dimanche};

enum mois {Janvier = 1, Février, Mars, Avril, Mai, Juin, Juillet, Août, Septembre, Octobre, Novembre, Décembre} month; enum jours jour; jour = lundi; printf ("jour = %d\n",jour); jour = dimanche; printf ("jour = %d\n",jour); month = Décembre; printf("month = %d\n",month);

}

// Résultats jour = 0 jour = 6 month = 12

Page 126: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

126 CHAPITRE IV ───────────────────────────────────────────────────

18. L'INSTRUCTION TYPEDEF

■ Type synonyme et interface objet

L'instruction typedef permet de définir un nouveau nom de type ou type synonyme à partir d'un type existant.

Ce nouvel identificateur de type, défini sans création d'un type nouveau, est utilisé pour accéder simplement à tous types d'objets tels les tableaux, les variables structurées, les unions et les énumérations ce qui améliore la lisibilité des interfaces d'accès. C'est un concept de programmation orienté objet.

Syntaxe : typedef <type(s)_existant(s)> type_synonyme;

� Exemple

#include <stdio.h> int main(void) { typedef int entier; // entier synonyme de int

typedef char octet; // octet synonyme de char typedef char *texte []; typedef struct { float reel; float imaginaire;} complexe; entier i = 1; texte chaine = "bonjour"; octet byte = '\063'; complexe z; ...

}

L'instruction typedef permet de s’affranchir des problèmes de portabilité la taille des objets de base (int, short, long...) pouvant différer d'un système à un autre.

Il est aussi possible de définir un synonyme de différents types.

� Exemple

typedef int short char long geant;

Le type geant est synonyme de tous les types précédents.

■ Définition d'un nom de type

La grammaire du langage C impose, à l'utilisation d'un type structuré, la répétition du mot clé struct contrairement aux types prédéfinis. L'instruction typedef permet de l'éviter de la façon suivante :

typedef struct [nom_ structure] {type1 champ1;..., typep champ; } nom_structure;

� Exemple

#define TAILLE 5 int main(void) { typedef struct { char nom[TAILLE]; char prenom[TAILLE];} chaine;

chaine personne; …

}

Page 127: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

127

19. EXERCICES

� Exercice 1

Programmer la multiplication égyptienne en utilisant exclusivement des opérateurs binaires.

� Exercice 2

Calculer le nombre de bits à 1 de la représentation binaire d'un nombre entier non signé. Pour tester la valeur du dernier bit du nombre n, il suffit de tester le résultat de l'opération logique n et 1 puis de faire un décalage à droite de 1 bit et d'itérer tant que n est différent de 0.

� Exercice 3

Ecrire un programme d'extraction à droite de n bits à partir du bit p du nombre entier x, le bit d'indice 0 est celui qui est plus à droite.

� Exercice 4

L'objectif est de calculer le volume d'une sphère par utilisation d'appels successifs de fonctions. Ecrire une fonction vol(r) qui appelle la fonction surf(r) qui appelle la fonction circonf(r). La fonction vol est appelée par la fonction main où le rayon est défini.

� Exercice 5

La fonction 91 est définie de la façon suivante :

f(n) = n - 10 si n > 100 f(n) = f(f(n+11)), 0 < n ≤100

Quand n est supérieur à 100, il n'y a pas de problème pour calculer f.

Dans le cas où n est inférieur ou égal à 100, l'appel de fonction est doublement récursif.

1°) Programmer cette fonction. Conclusion ?

2°) Démontrer mathématiquement le résultat obtenu.

� Exercice 6

Ecrire un programme de suppression d'une occurrence c dans une chaîne de caractère s. La fonction de suppression est la fonction squezze.

� Exercice 7

Ecrire un programme d'écriture des caractères d'une chaîne dans l'ordre inverse.

� Exercice 8

Ecrire une fonction atoi qui assure la conversion d'une chaîne numérique en un nombre entier. Cette fonction de la bibliothèque C retourne zéro si la chaîne est non numérique.

Page 128: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

128 CHAPITRE IV ───────────────────────────────────────────────────

� Exercice 9

Rechercher la ligne la plus longue d'un fichier à partir des fonctions getline (saisie d'une ligne de texte), copy (recopie une chaîne de caractères dans une autre).

� Exercice 10

Interpréter le programme suivant :

int main(void) { void saisie(float *);

float r; saisie(&r);

}

void saisie(float * rayon) { printf("indiquer le rayon : "); scanf("%f",rayon); printf(" r = %f \n",*rayon); printf("indiquer le rayon : "); scanf("%f",&(*rayon)); printf(" r = %f \n",*&(*rayon)); printf("indiquer le rayon : "); scanf("%f",&*rayon); printf(" r = %f \n", *&*rayon); }

#include <stdio.h> int main(void) { void saisie(float *); /* Prototypes */

void saisie2(float *); void saisie3(float *); void saisie4(float *); void affiche (char *, float); float r; /* Appel de saisie */

saisie(&r); affiche("main",r); saisie2(&r); affiche("main",r); saisie3(&r); affiche("main",r); saisie4(&r); affiche("main",r); }

void affiche(char *chaine, float valeur) { printf("Depuis la fonction %s, la variable saisie est : %f\n\n",chaine, valeur);}

void saisie(float * rayon) { printf("saisie\nindiquer le rayon : ");

scanf("%f",rayon); printf("rayon = %f\n",rayon);

}

Page 129: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

129

void saisie2(float * rayon) { printf("saisie2\nindiquer le rayon : ");

scanf("%f",&(*rayon)); printf("&(*rayon) = %f\n",rayon);

}

void saisie3(float * rayon) { printf("saisie3\nindiquer le rayon : ");

scanf("%f",&*rayon); printf("&*rayon = %f\n",rayon);

}

void saisie4(float * rayon) { printf("saisie4\nindiquer le rayon : ");

scanf("%f",&rayon); printf("rayon = %f\n",rayon);

}

// Résultats saisie indiquer le rayon : 4.568 rayon = 0.000000 Depuis la fonction main, la variable saisie est : 4.568000 saisie2 indiquer le rayon : 89.56 &(*rayon) = 0.000000 Depuis la fonction main, la variable saisie est : 89.559998 saisie3 indiquer le rayon : -86.93 &*rayon = 0.000000 Depuis la fonction main, la variable saisie est : -86.930000 saisie4 indiquer le rayon : 965.325 rayon = 3.172861892505597550000000000000000000000e+100 Depuis la fonction main, la variable saisie est : -86.633675

� Exercice 11

Transmettre par valeur un tableau de variables entières à une fonction et modifier ce tableau dans le corps de la fonction.

� Exercice 12

Soit un pointeur p sur un objet d'un type donné. Evaluer, quand c'est possible, les expressions *p++-- , * (p++ )--, (* )(p++ )--, (*p++ )—

� Exercice 13

Ecrire une fonction qui permette d'additionner p composantes d'un tableau de type entier ou réel, à partir d'un indice quelconque. On utilisera successivement trois méthodes :

• la fonction somme transmet directement le nom du tableau,

• la fonction sommeb transmet un pointeur sur une variable du type du tableau.

• la fonction sommec transmet un pointeur sur un tableau.

Page 130: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

130 CHAPITRE IV ───────────────────────────────────────────────────

� Exercice 14

Ecrire trois versions différentes d'un programme de recopie d'une chaîne de caractères. Le premier programme utilise des tableaux. Les deux autres utilisent des pointeurs.

� Exercice 15

Ecrire deux programmes de concaténation de chaînes de caractères : une version tableau et une version pointeur.

� Exercice 16

Ecrire un programme de permutation des composantes d'un tableau d'entier à deux indices en utilisant un tableau de pointeurs.

� Exercice 17

Créer un tableau à trois indices et imprimer, à l'aide de pointeurs ou de tableaux de pointeurs toutes ses composantes.

� Exercice 18

Rechercher une chaîne de caractères donnée dans un fichier et imprimer de la ligne correspondante.

� Exercice 19

Ce programme est une calculatrice. L'utilisateur saisit dans l'ordre un nombre, un des opérateurs + ,-,*,/,%, un nombre. Le résultat de l'opération est affiché. L'algorithme est le suivant :

• saisie des données,

• comparaison de l'opérateur saisi avec la liste des opérateurs mathématiques autorisés stockés dans un chaîne de caractères (fonction ind),

• branchement vers la fonction correspondante de calcul,

• calcul du résultat.

� Exercice 20

Ecrire un programme qui teste la hiérarchie et l'associativité des opérateurs (),*,->,. utilisés avec l'opérateur d'affectation, sur des expressions situées à gauche de cet opérateur (L-valeur) ou à droite (R-valeur).

� Exercice 21

Ecrire un programme de :

• saisie d'un nombre complexe,

• calcul de la somme et du produit de nombres complexes,

• affichage des résultats.

Page 131: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

131

� Exercice 22

Ecrire un module de gestion d'une liste chaînée de tableaux de flottants.

#define SIZE 10 #define N 5 #include <stdio.h>

int main(void) { int i, nombre;

typedef struct chaine {float tab[SIZE]; struct chaine *suivant;} chaine; chaine * debut, *next, *pred; /* Initialisation du premier élément de la liste chainée */ debut = (chaine * ) calloc(1,sizeof(chaine)); printf("adresse initiale de la chaîne: %x\n",debut); printf("valeur initiale de la chaîne:\n"); for(i=0;i<SIZE;i++ ) printf("%6.2f ",debut->tab[i]); printf("\n"); printf("debut->suivant = %x\n",debut->suivant);

/* Remplissage du premier élément de la liste chaînée */ for(i=0;i<SIZE;i++ ) debut-> tab[i]= (float)i;

/* Allocation et initialisation de la mémoire pour la nouvelle structure */ next = (chaine * ) calloc(1, sizeof(chaine)); /* Initialisation de la structure */ for(i=0; i< SIZE; i++ ) next->tab[i] = (float) 2* i; next->suivant=(chaine *) NULL; debut->suivant = (chaine * ) next; /* Chaînage des deux structures */ /* Création et remplissage de plusieurs listes chaînées */ for(nombre=3;nombre<N+3;nombre++ ) {pred =next;

/* Création du nouvel élément de la liste */ next = (chaine * ) calloc(1,sizeof(chaine));

/* Remplissage de la nouvelle structure crée */ for(i=0; i< SIZE; i++ ) next->tab[i] = (float) nombre * i; next->suivant=(chaine *) NULL; pred->suivant=next; /* Chaînage entre le prédécesseur et le successeur */

}

/* Affichage des résultats */ printf("affichage des résultats\n"); next=debut; while (next->suivant !=(chaine *) NULL) { for(i=0; i< SIZE; i++ ) printf("%6.2f ",next->tab[i]);

printf("\n next->suivant = %x\n", next->suivant); next = (chaine * ) next->suivant;

}

for(i=0; i< SIZE; i++ ) printf("%6.2f ",next->tab[i]); printf("\n next->suivant = %x", next->suivant); exit(0); }

Page 132: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

132 CHAPITRE IV ───────────────────────────────────────────────────

// Résultat adresse initiale de la chaîne: a7c valeur initiale de la chaîne: 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 debut->suivant = 0 affichage des résultats 0.00 1.00 2.00 3.00 4.00 5.00 6.00 7.00 8.00 9.00 next->suivant = aaa 0.00 2.00 4.00 6.00 8.00 10.00 12.00 14.00 16.00 18.00 next->suivant = ad8 0.00 3.00 6.00 9.00 12.00 15.00 18.00 21.00 24.00 27.00 next->suivant = b06 0.00 4.00 8.00 12.00 16.00 20.00 24.00 28.00 32.00 36.00 next->suivant = b34 0.00 5.00 10.00 15.00 20.00 25.00 30.00 35.00 40.00 45.00 next->suivant = b62 0.00 6.00 12.00 18.00 24.00 30.00 36.00 42.00 48.00 54.00 next->suivant = b90 0.00 7.00 14.00 21.00 28.00 35.00 42.00 49.00 56.00 63.00 next->suivant = 0

� Exercice 23 : les listes

Un tri par chaînage constitue, à partir d'une liste de noms saisis dans un ordre quelconque, une liste chaînée d'indices permettant son parcourt par ordre alphabétique.

Rappelons qu'une liste chaînée a la structure suivante :

info (i-1) pointeur(i) info(i) pointeur(i+1) info(i+1) pointeur(i+2)

Soit le tableau liste, de N structures, chacune composée de deux champs :

• la chaîne de caractères nom contiendra le nom saisi,

• l'entier tsuc, contiendra l'indice, dans le tableau liste, du successeur (dans l'ordre alphabétique) du nom précédemment saisi, avec les conventions suivantes :

liste [0 ].tsuc pointe sur le premier nom, dans l'ordre alphabétique, de la liste, liste [i ].tsuc=k, indice du successeur de liste [i].nom, dans l'ordre alphabétique, si

k≠0, liste [i ].tsuc=0, si le i-ème nom saisi n'a pas de successeur dans l'ordre

alphabétique.

Page 133: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

133

Toute nouvelle saisie va modifier deux enregistrements du tableau liste. Soit les tableaux :

i-ème nom saisi liste [i ].nom liste [i].tsuc

3

2

1

0

MARTIN

ALBERT

DUPOND

4

successeur de pas an' 0,

Albertd' successeur 1,

DUPOND de successeur 3,

liste la de nompremier

L'introduction du nom ALAIN va modifier le tableau liste[].tsuc selon le chaînage suivant :

i-ème nom saisi liste [i ].nom liste [i ].tsuc

4

3

2

1

0

ALAIN

MARTIN

ALBERT

DUPOND

4

2

0

1

3

L'algorithme de création de la liste chaînée, pour déterminer la valeur du prédécesseur et du successeur du nom saisi, est le suivant.

■ Notations

Soient j et pred les indices respectifs du candidat successeur et du candidat prédécesseur du ième nom saisi.

Au départ :

pred = 0; j= liste [0 ].tsuc

Le premier candidat prédécesseur est initialisé à 0. Il est comparé au prédécesseur du premier nom classé dans l'ordre alphabétique. Il y a deux possibilités :

liste [i ].nom s'intercale dans la liste

Dans ce cas :

∃ j0 tel que liste [pred ].nom < liste [i ].nom ≤ liste [j0 ].nom

et

liste [pred ].tsuc = i; liste [i ].tsuc = j0;

liste [i ].nom ne s'intercale pas dans la liste.

C'est donc le (nouveau) dernier élément de la liste, dans l'ordre alphabétique. Il n'aura pas de successeur. Soit j0 l'indice du dernier nom par ordre alphabétique de la liste avant l'insertion du nouveau nom dans la liste.

Page 134: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

134 CHAPITRE IV ───────────────────────────────────────────────────

■ Représentation graphique

Soit Li la liste de noms à l'étape i, alors Li = Li-1 ∪ liste [i ].nom

L i-1 liste [i ].tsuc Li

1-i

1

n

...

...

...

n

1-i

1

p

...

...

...

p

=

i

1-i

1

n

n

...

...

...

n

■ Evolution du tableau liste.tsuc

Voyons l'évolution du tableau liste [].tsuc dans les deux cas.

Premier cas : le nom s'intercale dans la liste. En utilisant les relations ci-dessus, on obtient :

L i-1 liste [i ].tsuc Li liste[].tsuc

1-i

k

1

n

...

n

...

n

1-i

0

1

p

...

j

...

p

0

=

i

1-i

k

1

n

n

...

n

...

n

0

1-i

1

j

p

...

j

...

p

0

Avant Après

Deuxième cas : le nom ne s'intercale pas dans la liste : On a la représentation suivante :

L i-1 liste [i ].tsuc Li liste[].tsuc

1-i

j0

1

n

...

n

...

n

1-i

1

p

...

0

...

p

0

=

i

1-i

j0

1

n

n

...

n

...

n

0

p

...

i

...

p

0

1-i

1

Avant Après

Page 135: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

135

■ Analyse

Avant l'insertion, on a les relations :

liste [j0 ].tsuc = 0

liste [j ].nom < liste [j0 ].nom pour tout j = 1,... , i-1

Après l'insertion, on a les relations :

liste [i ].tsuc = 0 = liste [j0 ].tsuc liste [j0 ].tsuc = i

■ Arrêt du calcul

Deux possibilités :

∃ j0 tel que liste [i ].nom > liste [j ].nom

j0= 0

■ Algorithme

L'algorithme se décompose en deux phases :

Phase 1 Recherche de la position

• Initialisation de la recherche : pred = 0; j = liste[0].tsuc

• Recherche effective

• Arrêt de la recherche

◊ dès qu'est déterminé j0 tel que liste [j0 ].nom ≥ liste [j ].nom > liste [pred ].nom

◊ ou dès que j = 0 ce qui implique que ni n'a pas de successeur.

La négation de ces deux conditions s'écrit :

j ≠ 0 ou liste[j].nom < liste[i].nom

Phase 2 Modification des deux champs à modifier de liste[].tsuc par les formules : liste[i].tsuc = j liste[pred].tsuc = i

Ces formules valables dans les deux cas simplifient l'écriture de l'algorithme.

Page 136: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

136 CHAPITRE IV ───────────────────────────────────────────────────

■ Programme

Initialisation lire N, dimension du tableau liste liste[i].tsuc = 0, pour tout i= 0,N liste[i].NOM = "" pour tout i = 1,N i = 1 Tant que(i < N et liste [i].nom ≠"") faire saisir liste [i ].nom; pred = 0; j = liste [0 ].tsuc; // Initialisation Recherche Tant que (j ≠ 0 et (liste [j ].nom < liste [i ].nom)) faire pred = j; // j : nouveau candidat prédécesseur j = liste [j ].tsuc; // Nouveau candidat successeur fin_faire liste [i ].tsuc = j; liste [pred ].tsuc = i; & modification de liste [ ].tsuc fin_faire

Le tri alphabétique est trivial.

j = liste [0 ].tsuc; Tant que (j ≠ 0) faire imprimer liste [j ].nom; j = liste [j ].tsuc; fin_faire

■ Programmation

La saisie au clavier de la liste des individus et son stockage dans un fichier est réalisée à partir d la fonction saisie.

La fonction lire permet, à partir du fichier des individus, de créer le tableau des successeurs. La comparaison de deux chaînes de caractères est réalisée par la fonction st qui retourne, à partir de la fonction de la bibliothèque C strcmp (prototype dans string.h), un nombre entier, utilisable dans le test de fin de recherche.

La fonction tri réalise le tri alphabétique.

Page 137: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LANGAGE C : TYPES, OPERATEURS, FONCTIONS, POINTEURS ───────────────────────────────────────────────────

137

#include <stdio.h> #include <string.h> #define MAX 20 #define ever (;;) typedef struct { char nom [20 ]; int tsuc;} pers;

void saisie(void) /* Saisie des données */ { FILE *fp;

pers accueil={"" ,0} , vide= {"" ,0}; fp= fopen("personnel","a"); for ever { printf("Donnez le nom : ");

gets (accueil.nom); if (!strlen(accueil.nom)) break; /* Fin de saisie */ fwrite( (char * ) & accueil, sizeof(accueil),1,fp); fflush(fp); /* Vidage du tampon après chaque saisie */ accueil=vide; /* Réinitialisation entre deux saisies*/

} fclose(fp); /* Fermeture du fichier */

}

int lire(pers liste [ ]) /* Remplissage de la liste et constitution du tableau tsuc */ { FILE * fp;

pers accueil; /* Initialisation */ int i , pred , j; int st(char *, char *); /* Fonction de comparaison de deux chaînes*/ liste [0 ].tsuc = 0; i = 1; fp=fopen("personnel","r"); while (fread ((char*) &accueil, sizeof(accueil),1,fp)) /* Chargement d'un enregistrement de longueur sizeof(accueil) du fichier */ /* Dont l'étiquette logique est fp dans le champ mémoire accueil*/ {liste [i ] = accueil; /* Affectation de variable structurée */

pred = 0; j = liste [0 ].tsuc; /* Initialisation de la recherche */ while (j!=0 && st(liste [j ].nom, liste [i ].nom)) { pred = j; j = liste [j ].tsuc; } liste [i ].tsuc = j; /* Modification du champ tsuc */ liste [pred ].tsuc = i; i++ ; /* Enregistrement suivant */ }

fclose(fp); return(i); }

int st(char *s, char *t) /* Comparaison de deux chaîne de caractères */ { int ret;

ret = strcmp(s,t); if (ret == 0) return(-1); if (ret<0) return(1); else return(0);

}

void tri(pers liste [ ]) { int j = liste [0 ].tsuc;

printf("\n\ntri par ordre alphabétique\n"); while(j) { printf(" %s\n",liste [j ].nom); j = liste [j ].tsuc;}

}

Page 138: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

138 CHAPITRE IV ───────────────────────────────────────────────────

int main(void) { int i,k; pers liste [MAX ];

/* Prototypes */ void saisie(void), tri(pers [ ]); int lire(pers [ ]); saisie(); /* Saisie de la liste dans un fichier */ k = lire(liste); /* Constitution du tableau des successeurs*/ printf ("liste non triée et indice du successeur\n"); for(i=0;i<k;i++ ) printf("%d%-30.30s%d\n",i,liste [i ].nom, liste [i ].tsuc); tri(liste);

}

// Résultat Donnez le nom : liste non triée et indice du successeur 0 12 1 PHILIPPE 4 2 LUIS 1 3 ALBERTY 10 4 PHILIPPE 11 5 ALBERT 8 6 JULES 2 7 JEAN-LOUIS 6 8 ALBERTA 3 9 HUBERT 7 10 ALBERTINE 9 11 ADELE 5

tri par ordre alphabétique ADELE ALBERT ALBERTA ALBERTY ALBERTINE HUBERT JEAN-LOUIS JULES LUIS PHILIPPE PHILIPPE

On pourra réécrire ce programme avec le champ tsuc de type pointeur sur une variable structurée pers.

Page 139: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE LA PROGRAMMATION ORIENTEE OBJET

■ Philosophie objet

L'interface d'accès à un objet est implémentée conformément à un modèle abstrait et constitue la couche objet.

Dans la formulation abstraite des problèmes, les données, représentées par des variables structurées, et les propriétés des objets, représentées par les opérations qu'on peut leur appliquer, sont pris en compte simultanément.

L'encapsulation des données et traitements leurs garantit une meilleure protection donc une plus grande fiabilité des programmes.

Avec ce point de vue, les données et le code sont logiquement inséparables, même s'ils sont gérés dans différentes régions de la mémoire.

Ces considérations conduisent à la définition d'un objet : ensemble de données sur lesquelles des procédures, appelées méthodes, peuvent opérer. La programmation d'un objet est réalisée à partir de ses données (séparées) et méthodes ces dernières pouvant être partagées.

1. CLASSES

1.1 Définitions

■ Objet (Object)

Un objet est implémenté sous la forme d'un "paquet" (ou paquetage) logiciel constitué d'un ensemble de procédures appelées méthodes et des données associées.

C'est une abstraction informatique d'une entité du monde réel caractérisée par une identité, un état, un comportement.

Les objets peuvent être définis et gérés indépendamment les uns des autres.

■ Identificateur d'objet (Object Identifier)

Un identificateur d'objet est une référence unique et invariante attribuée à un objet lors de sa création permettant de l'identifier et de le référencer pendant son existence.

■ Attribut (Attribute)

Un attribut représente une donnée caractéristique d'un objet désignée par un identificateur.

CHAPITRE V

Page 140: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

140 CHAPITRE V ───────────────────────────────────────────────────

■ Opération (Operation)

Une opération est la représentation d'une action applicable sur un objet, caractérisée par un en-tête appelé signature définissant son nom, ses arguments d'appel (identificateur et type) et le type de l'objet retourné.

■ Méthode - fonction membre

Autres appellations des opérations sur les objets, utilisées dans les langages de programmation à objet tels les langages C++, Java, ou SmallTalk.

■ Messages

Les objets peuvent échanger des informations sous forme de message que le langage Simula définit comme le résultat de l'application d'une méthode opérant sur un objet. L'objet à l'origine du message en est l'émetteur et l'objet destinataire le récepteur.

Un message est donc un ensemble composé d'un identificateur d'objet récepteur, d'un identificateur d'une méthode et de ses arguments permettant, suite à son émission, l'invocation externe de la méthode (publique) de l'objet récepteur.

Dans les environnements objets, ces derniers communiquent donc par des messages comportant le nom d'une méthode et ses arguments.

Un objet peut réceptionner un message et réagir à ce dernier.

L'émission d'un message est une implémentation flexible et contrôlée du traditionnel appel de procédure par valeur des langages de programmation.

■ Classe

Une classe est l'implémentation d'une interface d'utilisation d'un objet décrit selon un modèle abstrait permettant de spécifier ses propriétés (attributs et opérations) et de créer (instancier) des instances de cet objet dotées de ces propriétés communes.

Interface d'accès aux objets publics.

Données Traitements

Attributs Méthodes

Accès public (Utilisateur)

Accès privé (Développeur)

Page 141: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE LA PROGRAMMATION ORIENTEE OBJET ───────────────────────────────────────────────────

141

Une classe spécifie donc la structure abstraite et le comportement commun des objets qu'elle permet de créer (instanciation). L'instanciation est ainsi une relation d'appartenance d'un objet à une classe donnée. Chaque instanciation provoque une allocation de mémoire dont l'initialisation est réalisée par une méthode appelée constructeur. Lorsqu'elle est détruite, une méthode conjuguée est exécutée : le destructeur. Le programmeur de la classe peut les redéfinir.

La classe définit la structure des données appelée champ, variable d'instance, ou donnée membres de la classe (aspect statique) dont seront constitués ses objets.

Les opérations sur les instances sont réalisées par les méthodes ou fonctions membres de la classe (aspect dynamique).

Une méthode multi-classes est définie dans une classe et peut s'appliquer à des instances de classes différentes, ces dernières étant référencées comme des paramètres de la classe de définition de la méthode.

1.2 Relations d'héritage

■ Héritage (Inheritance)

L'héritage permet de transmettre les propriétés d'une classe de niveau supérieur (classe mère, classe de base, super classe) à une classe de niveau inférieur (classe fille, classe dérivée) ce qui permet la réutilisation par la classe fille de tous les attributs et traitements accessibles de la classe mère.

■ Généralisation (Generalization)

L'héritage représente un lien hiérarchique entre deux classes spécifiant que les objets de la classe de niveau supérieur sont plus généraux que ceux de la classe de niveau inférieur.

■ Hiérarchie de classes

Les classes peuvent être organisées selon un modèle arborescent pour intégrer des cas de plus en plus particuliers au modèle de base.

■ Classes virtuelles (ou abstraites)

Une classe virtuelle (abstraite) est une classe sans possibilité d'instanciation d'objet, créée pour définir les données membres et les méthodes associées qui s'appliqueront à ses classes de plus bas niveau dans la hiérarchie de classes.

■ Héritage multiple (Multiple Inheritance)

Une classe dérivée peut hériter de plusieurs classes de base.

Page 142: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

142 CHAPITRE V ───────────────────────────────────────────────────

2. APPROCHE OBJET, DONNEE ABSTRAITE, ENCAPSULATION

2.1 Types abstraits

■ Approche orientée objet

Les logiciels orientés objet modélisent un système qui est une représentation du monde réel.

■ Types abstraits

• La plupart des langages de programmation permettent de définir des types de données abstraites, en complément de ses types prédéfinis.

• Les langages orientés objets permettent en outre d'utiliser les opérateurs traditionnels sur ces données abstraites par surdéfinition. De nouveaux types abstraits y sont définis par la création de nouvelles classes.

■ Extension de la notion de type du langage C en langage C++

La couche objet constitue l'apport essentiel du langage C++ au langage C dont le typage traditionnel a été transformé en classe.

� Exemple

Les types prédéfinis char, int, double, etc. représentent l'ensemble des propriétés des variables de ce type et en constituent la classe avec les opérateurs arithmétiques comme méthodes tel l'addition.

■ Utilisateur et programmeur de la classe

Les règles d'accès aux objets sont différentes pour l'utilisateur et le programmeur pour distinguer leur utilisation de leur implémentation.

• L'utilisateur d'un objet n'a pas à connaître sa représentation interne; pour lui, seul compte son comportement (principe de la boîte noire).

• Le programmeur ne peut ignorer la façon dont une instance d'un type donné est représentée en binaire car la plage des valeurs possibles est essentielle. Ainsi, un bug d'exécution résultant d'un choix erroné de la représentation d'un nombre a provoqué une modification incontrôlable de la trajectoire du lanceur Ariane 5 conduisant au crash de son premier vol de qualification, d'un coût estimé à plusieurs milliards d'euros.

2.2 Agrégat et encapsulation

■ Agrégation (Aggregation)

Une agrégation est une association entre deux classes exprimant que les objets de l'une sont des composants de l'autre. L'agrégation traduit la relation "fait partie de" (is a part off).

Les objets contenus dans un autre constituent un agrégat.

Page 143: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE LA PROGRAMMATION ORIENTEE OBJET ───────────────────────────────────────────────────

143

■ Encapsulation, objet encapsulé, interface d'objet

• Un objet d'une classe, accessible par une interface d'objet y est encapsulé.

• Les logiciels orientés objet sont conçus par assemblage de modules dans lesquels des données et des comportements peuvent être encapsulés.

• Le fait de rendre un objet privé est l'encapsulation. Cette dernière permet la dissimulation d'informations, l'accès à une instance n'étant possible qu'au travers des méthodes autorisées ce qui la protège et améliore la fiabilité. L'encapsulation permet donc de contrôler les modifications de l'objet concerné.

• Les types prédéfinis garantissent l'abstraction et l'encapsulation des données prédéfinies (comme le type float en C). Par contre, la complexité de la grammaire de ce langage ne permet pas ou n'incite pas le programmeur à définir des types utilisateur garantissant ces mêmes propriétés.

• Les méthodes autorisées constituent l'interface d'accès de l'utilisateur des instances en masquant leur implémentation.

Les avantages sont immédiats :

• l'utilisateur ne peut provoquer d'erreurs d'exécution par modification intempestives de certaines données.

• D'interface d'accès standard, l'objet est utilisable par d'autres applications.

• Le programmeur de la classe peut modifier l'implémentation interne de l'objet sans réécrire tout le programme, les méthodes utilisées devant conserver les mêmes identificateurs et arguments.

• L'interface d'objet (Object Interface) est décrite par l'ensemble des signatures des méthodes et des attributs publics d'un objet.

3. INITIALISATION DES OBJETS

• En langage C, les règles d'initialisation des variables sont complexes voire confuses. Ainsi, les variables de classe de mémorisation statique sont par défaut toujours initialisées, les variables automatiques non.

• Ces règles sont appliquées sur des tableaux de façon fantaisistes par les éditeurs de logiciel ce qui nuit à la portabilité et à la robustesse des programmes.

■ Constructeur et destructeur

• Une méthode d'initialisation appelée constructeur est définie par défaut ou peut être redéfinie par le programmeur. Elle est toujours appelée implicitement ou explicitement lors de l'instanciation d'une variable de la classe.

• La mémoire allouée par un constructeur est toujours récupérable par une méthode appelée destructeur ou déconstructeur (principe du ramasseur d'ordures (garbage collector)).

Page 144: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

144 CHAPITRE V ───────────────────────────────────────────────────

4. POLYMORPHISME, SURDEFINITION ET GENERICITE

■ Polymorphisme (Polymorphism)

On appelle polymorphisme la faculté de dissimulation des détails de l'implémentation à travers une interface d'utilisation ce qui simplifie la communication entre objets. Ainsi, en langage C++, une méthode peut avoir différentes signatures.

■ Surdéfinition (Overloading)

• La surdéfinition (surcharge) d'une fonction (resp. procédure ou méthode) consiste à donner un même identificateur à plusieurs fonctions (resp. procédure ou méthode), la fonction (resp. procédure ou méthode) appropriée étant déterminée par le nombre et le type de ses arguments d'appel.

• La surdéfinition (surcharge) d'un opérateur permet d'étendre ses caractéristiques d'origine à des opérandes d'un type différent ce qui est une forme de polymorphisme.

• La surdéfinition peut s'exécuter en mode :

◊ statique (early binding) : l'objet surdéfini est déterminé à la compilation.

◊ dynamique (dynamic binding ou late binding) : l'objet surdéfini est déterminé à l'exécution (méthodes virtuelles du langage C++).

� Exemple de fonction surdéfinie

Soit la classe des figures géométriques (cercles, carrés, etc.). La méthode surdéfinie dessiner opère sur les instances de la classe… et un cercle n'est pas un carré.

� Exemple d'opérateur surdéfini

Les opérateurs arithmétiques traditionnels du langage C sont surdéfinis. Ainsi, l'opérateur arithmétique + est utilisé sur des objets de type int, float, double, etc..

En C++, cet opérateur surdéfini peut opérer sur des tableaux, des instances de classe, etc.

■ Généricité

Un traitement générique (unique) opère sur des objets de type différent contrairement à une fonction surdéfinie qui a une implémentation spécifique pour chaque type d'objet.

Le langage C permet de définir des variables et des fonctions génériques sous une forme symbolique à partir du préprocesseur.

Le langage C++ implémente les objets génériques sous forme d'objets modèles paramétrés (fonctions, classes, méthodes et constantes) appelés template.

■ Redéfinition (Overriding)

La redéfinition d'une méthode est la spécification dans une classe dérivée d'une méthode définie dans sa classe de base.

Page 145: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE LA PROGRAMMATION ORIENTEE OBJET ───────────────────────────────────────────────────

145

5. COLLECTIONS D'OBJETS.

■ Conteneur (Container)

Un conteneur d'objets (Container) typés est désigné par un identificateur.

Il contient une collection d'instances organisées conformément une structure particulière et accessibles par des opérations spécifiques au conteneur.

� Exemples

• L'ensemble (Set) définit une collection non ordonnée sans doublon.

• Le sac (Bag) définit une collection non ordonnée avec doublon.

• La liste (List) définit une collection ordonnée avec doublon.

• Le tableau (Array) définit une collection ordonnée, indexée.

■ Objet polymorphique

Un conteneur d'objets polymorphiques contient des objets dont le type peut être paramétré.

■ Classe modèle (classe Template)

Modèle d'objet paramétré.

6. PRINCIPES GENERAUX DE PROTECTION DES DONNEES

Le langage C++ n'offre pas de moyens de contrôle permettant de réaliser n'importe quelle protection. Les principes de base sont les suivants :

• La protection des données dépend de l'application.

• La protection contre les accidents de programmation est assurée par le compilateur sans garantie contre une violation explicite des règles.

• L'unité de protection et d'accès est la classe.

• Le contrôle d'accès est réalisé à partir de l'identificateur de l'objet et non à partir de son type.

• La visibilité n'est pas contrôlée.

Page 146: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

146 CHAPITRE V ───────────────────────────────────────────────────

7. ABSTRACTION ET ENCAPSULATION EN C ET C++

Dans le présent paragraphe, les concepts de programmation objet sont utilisés en C puis en C++ pour définir une structure abstraite de pile et les méthodes associées.

■ Représentation interne et méthodes

• L'objet de la classe est une pile représentée par un tableau.

• La base et le sommet de la pile sont représentés par les pointeurs top et pile.

• Les traitements sont les suivants : la fonction pile_vide initialise la pile, la fonction push empile un objet, la fonction pop dépile un objet.

• L'ensemble est décrit dans le fichier pile.c pour être intégré dans une bibliothèque.

■ Interface utilisateur

L'interface utilisateur est décrite dans le fichier pile.h.

7.1 Première implémentation d'un modèle abstrait en C

■ Interface utilisateur

/* fichier pile.h : opérations autorisée */ int pile vide(void), push(int), pop(void); void reinit_pile(void);

■ Fichier pile.c

#include "pile.h" #define TAILLE 1024 static int pile[TAILLE]; static int* top=pile;

int pile_vide (void){ return top==pile;}

int push(int e) { if (top-pile == TAILLE) return 0; *top++=e; return 1; } int pop(void) {if (top!=pile) return *--top;}

void reinit_pile(void) {top=pile;}

■ Avantages

• Abstraction et encapsulation des données : l'implémentation peut être modifiée (structure de données, corps des méthodes) sans modifier l'interface.

• Simplicité de l'écriture.

• Efficacité à l'exécution.

■ Inconvénients

• Mémoire gérée statiquement.

• Absence de définition de l'objet abstrait pile ce qui ne permet pas d'instancier d'autres objets du même type. D'où l'implémentation suivante.

Page 147: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE LA PROGRAMMATION ORIENTEE OBJET ───────────────────────────────────────────────────

147

7.2 Deuxième implémentation d'un modèle abstrait en C

■ Interface utilisateur (deuxième version)

/* fichier pile.h */ typedef struct { int taille; int* base; int* top;} PILE; void init_pile(PILE *, int); /* Opérations autorisées */ int pile_vide(PILE *) , push(PILE*, int) , pop(PILE*); void effacer_pile(PILE*);

■ Fichier pile.c (deuxième version)

#include <malloc.h> #include "pile.h"

void init_pile(PILE * pile, int taille) {pile->top = pile->base = (int* ) malloc((pile->taille=taille) *sizeof(int));}

int pile_vide(PILE *pile) {return (pile->top == pile->base);} int push(PILE* pile, int entier) { if ((pile->top - pile->base) == pile->taille) return 0;

*pile->top++ = entier; return 1; }

int pop(PILE* pile) {if (pile->top != pile->base) return *--pile->top;} void effacer_pile(PILE* pile) { free(pile->base); pile->top=pile->base=0; pile->taille = 0; }

� Utilisation

#include "pile.c" #include <stdio.h>

int main(void) { int i=0; PILE pile; init_pile(&pile, 10); /* Initialisation non automatique */ while (push(&pile, i++)) /* Utilisation de la pile */ while (!pile_vide(&pile)) printf("%d\n", pop(&pile)); effacer_pile(&pile); /* Destruction de la pile après utilisation */ }

■ Avantages

• Efficacité à l'exécution.

• Gestion dynamique de la mémoire.

• Le type abstrait pile permet à l'utilisateur d'instancier des piles.

■ Inconvénients

• Rien n'empêche de modifier la pile sans utiliser l'interface.

• Complexité du programme due à l'utilisation des pointeurs.

• Initialisation et vidage de la pile non sécurisés car à la charge de l'utilisateur.

Page 148: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

148 CHAPITRE V ───────────────────────────────────────────────────

7.3 Troisième implémentation d'un modèle abstrait e n C Ces remarques conduisent à l'implémentation suivante :

■ Interface utilisateur (troisième version)

/* fichier pile.h */ typedef void* PILE ; void init_pile(PILE, int), effacer_pile(PILE*); int pile_vide(PILE), push(PILE, int), pop(PILE);

■ Fichier pile.c (troisième version)

#include <malloc.h> #include "pile.h" typedef struct{ int taille; int *base; int *top;}pile;

void init_pile(void*a, int t) {a = malloc(sizeof(pile)); // Gestion de l'allocation dynamique

((pile*)a)->top = ((pile*)a)->base = (int* )malloc((((pile*)a)->taille=t)*sizeof(int)); }

int pile_vide(void* a) { if (((pile*)a)->top == ((pile*)a)->base) return 1; else return 0;}

int push(void* a, int e) { if ((((pile*)a)->top - ((pile*)a)->base) == ((pile*)a)->taille) return 0;

*((pile*)a)->top++=e; return 1; }

int pop(void* a) { if (((pile*)a)->top !=((pile*)a)->base) return *--((pile*)a)->top;}

void effacer_pile(void** a) {free(((pile*)*a)->base); free(*a); *a=0; }

■ Avantages

• Abstraction et encapsulation des données.

• Gestion dynamique de la mémoire.

• Le type abstrait pile permet à l'utilisateur d'instancier des piles.

■ Inconvénients

• Complexité de la syntaxe.

• Inefficacité à l'exécution due aux trop nombreuses indirections.

• Initialisation et vidage de la pile à la charge de l'utilisateur donc non sécurisés.

■ Conclusions sur les types abstraits en C

• Le langage C permet d'encapsuler des données et de définir des types abstraits.

• Il ne facilite pas la tâche du programmeur d'applications l'encapsulation nécessitant d'utiliser l'indirection d'où une grande complexité d'écriture.

• Tout effort d'abstraction et d'encapsulation en C se traduit également par une écriture plus complexe et un ralentissement possible à l'exécution.

Page 149: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BASES DE LA PROGRAMMATION ORIENTEE OBJET ───────────────────────────────────────────────────

149

7.4 Implémentation d'un modèle abstrait en C++ En C++, l'implémentation du type abstrait PILE est simplifiée par l'utilisation de constructeurs, destructeurs et de l'opérateur d'allocation dynamique de mémoire new.

■ Interface utilisateur (quatrième version)

class PILE {/* fichier pile.h */ private : int taille; int* base; int* top;

public: void init(int), effacer(void); int push(int) , pop(void), vide(void); };

■ Fichier pile.C

#include <iostream.h> #include "pile.h"

void PILE::init(int t){top=base=new int[taille=t]; }

int PILE::vide(void){ return top==base;}

int PILE::push(int e) // Référence implicite à l'objet { if ((top-base)==taille) return 0; *top++ =e; return 1; }

int PILE::pop(void){if (top!=base) return *--top;}

void PILE::effacer(void) { delete base; this->top = this->base = this->taille = 0;}

� Utilisation

int main(void) { int i=0; PILE pile;

pile.init(10); /* Initialisation */

while (pile.push(i++)); while (!pile.vide()) cout << pile.pop() << endl ; pile.effacer(); /* Destruction */ return 1;

}

■ Avantages

• Encapsulation des données : l'utilisateur ne peut accéder à une pile qu'en utilisant l'interface, la partie privée étant réservée au développeur.

• Abstraction : l'utilisateur accède à la représentation des données sans pouvoir la modifier.

• Simplicité de l'écriture : pas de pointeurs explicites.

• Exécution optimisée par le langage.

• Gestion dynamique de l'allocation mémoire.

• L'objet abstrait pile est défini pour instanciations ultérieures.

Page 150: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

150 CHAPITRE V ───────────────────────────────────────────────────

7.5 Deuxième implémentation d'un modèle abstrait en C++ La classe pile est encapsulée dans la classe PILE.

■ Interface utilisateur (cinquième version)

class PILE {/* fichier pile.h */ void* adr; public: PILE(int); ~PILE(void); int vide(void), push(int), int pop(void); void effacer(void); };

■ Fichier pile.C (deuxième version)

class pile {int taille; int* base; int * top;

public: // Constructeur par défaut d'une pile de taille 1024 pile(int t=1024) {top=base=new int[taille=t]; } int vide(void){ return top==base; } int push(int e) {if((top-base)==taille) return 0; *top++= e; return 1; } int pop(void) { if(top!=base) return *--top;} ~pile(void) {delete base;}

};

#include "pile.h" typedef pile* adr_pile; PILE::PILE(int t) {adr=new pile(t);} int PILE::vide(void) { return adr_pile(adr)->vide(); } int PILE::push(int e) {return adr_pile(adr)->push(e);} int PILE::pop(void) { return adr_pile(adr)->pop(); } PILE::~PILE(void) {delete adr_pile(adr); adr=0;}

� Utilisation

#include <iostream.h> #include "pile.h" int main() { int i=0; PILE pile(10);

while (pile.push(i++)); while (!pile.vide()) cout << pile.pop() <<endl ; return 1;

}

■ Avantages

• Encapsulation des données abstraites: les données sont protégées.

• Simplicité de l'écriture : pas de pointeurs explicites.

• Gestion dynamique de l'allocation mémoire.

• L'objet pile est défini pour instanciation.

• Implémentation des données abstraites modifiable sans que l'interface ne le soit.

Page 151: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LE C++, LANGAGE PROCEDURAL ET FONCTIONNEL

1. INTRODUCTION

■ Le C++, langage de programmation orienté objet

Le langage C++, a été développé par Bjarne Stroustrup pour intégrer les concepts de la programmation orientée objet au langage C à savoir :

• définition d'objet typé basé sur les concepts de classes, d'instances et de méthodes,

• contrôle de la syntaxe basé sur un typage fort,

• initialisation et suppression d'objet à partir de constructeur et destructeur,

• prise en compte de l'héritage (simple ou multiple),

• définition d'objets modèles (objets paramétrés),

• contrôle à priori par le programmeur des erreurs d'exécution à partir de la gestion des exceptions.

Le langage C++ est efficace, performant, et complexe donc quelquefois difficile à interpréter. Avec le C, il est idéal pour réaliser des programmes dont le code source est portable.

■ Le C++, langage procédural et fonctionnel

Les caractéristiques du C++, langage procédural et fonctionnel sont les suivantes :

• syntaxe compatible avec celle du langage C en évitant d'utiliser certaines de ses fonctionnalités grammaticalement douteuses ce qui permet de considérer le langage C++ comme une extension du langage C, certains programmes C devant être adaptés avec des modifications minimes.

• performances similaires à celles du langage C et extension de ses fonctionnalités :

◊ transmission par référence,

◊ généralisation du prototypage,

◊ définition autorisée de valeurs par défaut des arguments d'appel des fonctions,

◊ surdéfinition des fonctions et des opérateurs.

CHAPITRE VI

Page 152: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

152 CHAPITRE VI ───────────────────────────────────────────────────

2. OBJETS DE BASE

■ Commentaire

Le délimiteur // permet d'insérer des commentaires, en particulier à la droite d'une instruction.

� Exemple

int valeur; // Ceci est un entier

■ Types de base

On retrouve les types de base traditionnels du langage C, avec quelques évolutions :

void; void *; unsigned char , signed char ; short int, int; long int (unsigned ), unsigned short int, unsigned int; unsigned long int, float, double, long double (non implémenté); enum;

■ Nommage des agrégats

Le nommage des agrégats évite de réutiliser les mots clés struct, class, union à l'instanciation.

� Exemple

struct complexe {float reel; float imaginaire; }; complexe z; // Et non struct complexe z;

■ Type booléen et énumération

Type booléen prédéfini. Définition de constante symbolique par énumération avec contrôle de valeur.

� Exemple

enum Booleen {FAUX, VRAI }; // Différent de {VRAI, FAUX}; enum COULEUR {VERT=5, ROUGE, JAUNE=9 }; // ROUGE équivaut à 6 par défaut. COULEUR couleur = ROUGE; // couleur initialisé à 6. couleur = -7; // Incorrect

■ Type intégral

char , wchar_t; // caractère, caractère long short, int, long, enum;

■ Type flottant

float, double ;

■ Types arithmétiques

Les deux types précédents.

Page 153: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LE C++, LANGAGE PROCEDURAL ET FONCTIONNEL ───────────────────────────────────────────────────

153

■ Types dérivés

& opérateur de référence, * opérateur de déréférenciation, [] tableau, () fonction, const qualification d'un type, d'une variable ou du comportement

d'une méthode, class, struct, union types agrégat, .* sélecteur sur objet membre (donnée ou méthode), -> sélecteur sur pointeur.

3. ENTREES/SORTIES ELEMENTAIRES EN C++

3.1 Opérateurs associés Le langage C++ utilise les flux (streams) de préférence aux fonctions de la bibliothèque C scanf, printf, fscanf, fprintf, sscanf, sprints, gets, puts, fgets, fputs, getchar , putchar , etc.

Quatre opérateurs gestionnaires des flux sont définis :

cin et le délimiteur associé >> cout et le délimiteur associé << cerr et le délimiteur associé << clog et le délimiteur associé << L'utilisation de ces opérateurs nécessite l'utilisation du fichier en tête iostream.h.

■ Saisie

• L'utilisation de l'opérateur cin permet d'éviter les erreurs de saisie dues à une mauvaise utilisation de la fonction scanf comme l'illustre l'exemple ci-dessous :

int i ; scanf("%d", i); // Sous Windows98, le plantage total du PC est possible. scanf("%d",&i); // Correct mais les pointeurs arrivent.

• L'opérateur cin saisit en format libre un flux de données d'un quelconque type prédéfini sans le spécifier, retourne la valeur saisie si cette dernière s'est déroulée correctement, 0 sinon.

• Le symbole >> délimite les objets à saisir.

• Le séparateur des données saisies est représenté par un ou (ou non exclusif) plusieurs caractères d'espacement, de tabulation ou les deux. Le caractère CR (retour chariot) est également autorisé.

Page 154: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

154 CHAPITRE VI ───────────────────────────────────────────────────

■ Affichage

• L'opérateur cout affiche des données d'un type prédéfini en format libre.

• L'éventuel texte d'accompagnement est délimité par deux doubles quotes ".

• Le délimiteur associé << précède les objets à imprimer.

• L'impression s'effectue de la gauche vers la droite.

• La fonction endl permet de gérer le passage à la ligne.

� Exemple 1

#include <iostream.h> int main() { int a,b ; cout << "Saisir a et b : "; // Affichage du message de saisie (format libre) cin >> a >> b; // Saisie des variables a et b cout << "a= " << a << "\tb= " << b << endl ; // Affichage et passage à la ligne }

// Résultat Saisir a et b : 2 3 a= 2 b= 3

� Exemple 2

// Fonction fact #include <iostream.h> unsigned long fact(unsigned n) { return ((n>1) ? n*fact(n-1) : 1); }

int main() { int i =1 ;

while (i) { cout << "Entrez un nombre : ";

if (! (cin >> i) ) break; cout << "fact(" << n << ") = << fact(n);

} }

■ Affichage de données en format libre

char c='a'; int i=17; float f=4.2; char *s="coucou"; char *v=s; cout << c << ' ' << i << ' '<< f << '\t' << v << ': ' << *v << endl;

// Résultat a 17 4.2 0x4ab2: coucou

Page 155: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LE C++, LANGAGE PROCEDURAL ET FONCTIONNEL ───────────────────────────────────────────────────

155

3.2 Interprétation objet des entrées/sorties

■ Objet et traitement associé

• En programmation orientée objet, les objets contrôlent les traitements par refus ou acceptation d'un message. Dans cette dernière situation, l'objet destinataire déclenche le traitement correspondant au message accepté.

• En C++, un message résulte de l'usage d'un opérateur ou d'un appel de fonction.

• Les gestionnaires des flux cin et cout peuvent être considérées comme des objets opérant respectivement sur les flux de données (messages) stdin et stdout.

� Exemple

#include <iostream.h> // Interface d'accès à la gestion des flux standards int main() { float PrixLu; cout << "Prix hors taxes : "; // L'opérateur cout opère sur le flux standard stdout cin >> PrixLu; // L'objet (l'opérateur) cin traite un message cout << "Prix TTC : " << PrixLu*1.196 << endl ; // L'opérateur cout traite trois messages consécutifs cout.width(16); // Un message est un appel de méthode width() cout << PrixLu*1.196 << endl ; cout.width(16); // Cadrage sur 16 caractères d'espacement cout.fill('*'); // Remplissage (limite du facteur de cadrage) cout << PrixLu*1.196 << endl ; return (1); }

// Résultat Prix hors taxes : 100 Prix TTC : 119.60 119.60 ***********119.60

3.3 Fichiers Les classes istream et ostream et les flux cin, cout et cerr gèrent les données des entrées/sorties standard. La gestion des flux sur des fichiers nécessite d'inclure le fichier <fstream.h> qui permet l'accès aux classes ifstream et ofstream.

� Exemple 1

#include <iostream.h> // Ecriture dans un fichier (classe ofstream) #include <fstream.h> int main(void) { ofstream dest("essai.txt");

dest << "Ceci est un test d'écriture" << 3 << endl; dest << "fin de fichier" << endl; dest.close(); return 0;

}

Page 156: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

156 CHAPITRE VI ───────────────────────────────────────────────────

� Exemple 2

#include <iostream.h> // Lecture d'un fichier (classe ifstream) #include <fstream.h> int main(void) { ifstream source("essai.txt");

char *ch = new char[50]; source >> ch; cout << ch; return 0;

} Il existe d'autres méthodes dans ces classes : open, seek, tell, flush, eof, etc.

4. INSTRUCTION ET EXPRESSION

■ Instruction exécutable

En C++, les définitions des objets sont des instructions exécutables ce qui permet de définir une variable dans le corps du programme contrairement au C.

� Exemple

#include <iostream.h> int main() { int t[] = {7, 4, 2, 9, 3, 6, 1, 4}; // Tableau d'entiers de dimension incomplète

for (int i=0; i<8; i++) cout<<t[i]; // Définition de la variable entière i cout <<endl ;

}

// Résultats 7 4 2 9 3 6 1 4

■ Déclaration

En langage C++, l'ambiguïté entre définition et déclaration est interdite toute déclaration de variable globale devant comporter obligatoirement le mot-clef extern, contrairement au langage C.

■ Définition

Toute variable globale doit être définie une et une seule fois dans le programme et déclarée explicitement au moins une fois dans chaque fichier où elle est utilisée.

■ Opérateurs spécifiques au langage C++

• L'opérateur d'allocation dynamique d'instances new.

• L'opérateur conjugué de libération de mémoire delete.

• La conversion de type par appel de fonction ou transtypage fonctionnel.

• L'opérateur de résolution de visibilité :: démasque un nom global masqué.

Page 157: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LE C++, LANGAGE PROCEDURAL ET FONCTIONNEL ───────────────────────────────────────────────────

157

� Exemple

#include <iostream.h> int x =1; // X variable globale int main() { void f(); // Prototype

cout << " x global " << x << endl ; f(); cout << " x global depuis la fonction main : " << x << endl ;

}

void f() { int x = 3; // Masque le x global

cout << " x local avant affectation x global : " << x << endl ; ::x= 2; // Affectation du x global cout << " x local après affectation du x global : " << x << endl ; cout << " x global depuis f : " << ::x << endl ;

}

// Résultats x global 1 x local avant affectation x global : 3 x local après affectation x global : 3 x global depuis f : 2 x global depuis la fonction main : 2

■ Fichier en tête

En langage C++, un fichier en tête (header) peut contenir :

• des commentaires,

• des déclarations de type, de constantes, de méthodes en ligne (inline), de données et de fonctions externes, de types énumérés,

• des directives d'inclusion et de substitution du préprocesseur C.

Il ne doit jamais contenir de définition de donnée, de fonction, d'agrégat de constantes.

■ Le spécificateur const et les variables

Le spécificateur const peut qualifier tout variable typée pour indiquer qu'elle reste constante après son initialisation.

� Exemple

const char new_line = endl ; const char * voyelles = "aeiouy"; const char * format1 = "%4d\t%d << endl" ; const float pi = 3.14159265;

L'utilisation du mot clé const est une bonne alternative à celle de la directive #define pour définir des constantes symboliques, la vérification de type étant alors effectuée dès la compilation.

En langage C++, la portée du qualificatif const est limitée au fichier courant.

Page 158: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

158 CHAPITRE VI ───────────────────────────────────────────────────

■ Nommage des agrégats

• En langage C++, une instance d'une classe est définie à partir de l'identificateur de cette dernière, en omettant le mot clé struct, union ou class sans qu'il ne soit nécessaire d'utiliser le mot clé typedef (langage C).

• Des variables structurées construites selon des modèles abstraits similaires sont différentes.

� Exemple

struct type1 {int a; int b;}; struct type2 {int a; int b;}; type1 s10, s11 type2 s20; s11 = s10; // OK s11 = s20; // Erreur de type

■ Le qualificatif mutable

Cette classe de mémorisation spécifique au langage C++ est utilisée pour redéfinir la qualification d'accès d'attributs en passant outre leur éventuel caractère constant.

■ Types énumérés

Le contrôle de type est plus rigoureux en langage C++. Ainsi, les types énumérés ne sont plus un sous ensemble des types entiers. La conversion implicite d'un élément d'une énumération en un type entier est autorisée. La réciproque impose d'utiliser une conversion de type explicite.

� Exemple

enum Feu {vert, orange, rouge}; int main(void) { Feu croisement = vert;

Feu intersection = rouge; int Valeur = intersection; cout << Valeur << endl; croisement = (Feu) 2; cout << croisement << endl;

}

Page 159: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LE C++, LANGAGE PROCEDURAL ET FONCTIONNEL ───────────────────────────────────────────────────

159

5. FONCTIONS ET PROCEDURES

5.1 Prototypage

■ Obligation

Le prototype est obligatoire si la fonction n'est pas définie préalablement à la fonction appelante dans le fichier courant contrairement au langage C il est facultatif.

Synopsis définition : = en_tête corps_de_la_fonction déclaration : = extern en_tête; en_tête: = classe_memorisation type_résultat identificateur_fonction (déclar_params) corps : = instruction_composée

� Exemple

// Définition int f(int a, float b, int c) // En-tête {/* Corps de la fonction */ }

// Déclarations (prototypes, signature) extern int f(int, float, int); extern int printf(const char *, ...); // Nombre et type d'arguments variables.

5.2 Procédure Une procédure est une fonction qui ne retourne rien ce qui se traduit sur le plan syntaxique par le retour d'un objet de type void. L'utilisation du mot clé return est donc interdite dans cette situation.

Une fonction ou procédure avec une liste vide est déclarée sans argument, contrairement au langage C où cette déclaration en indique un nombre indéterminé.

� Exemple

void f() {...} // Procédure sans argument

5.3 Surdéfinition d'une fonction

• Dans les langages procéduraux, plusieurs fonctions effectuant la même action, avec la même sémantique, opérant sur des objets de types différents doivent être implémentées avec des identificateurs et des types d'arguments différents.

• En langage C, le préprocesseur (directive #define) permet d'implémenter des fonctions génériques dont l'inconvénient est l'absence de vérification syntaxique des instructions symboliques et des types symboliques.

• Le langage C++ autorise une définition multiple d'une fonction dont le nombre et les types d'arguments peuvent différer (surdéfinition).

• Ce concept est généralisé à la plupart des (fonctions) opérateurs prédéfinies.

Page 160: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

160 CHAPITRE VI ───────────────────────────────────────────────────

■ Signature et surdéfinition d'une fonction

• La signature d'une fonction est définie par sa portée, le nombre et le type de ses arguments ainsi que par le type de l'objet retourné. Elle est représentée par le prototype de la fonction.

• Une fonction surdéfinie a plusieurs signatures.

■ Choix de la fonction à l'exécution

Le choix est effectué à l'exécution selon la signature de la fonction en trois étapes :

• recherche d'une correspondance exacte entre les paramètres formels et les arguments d'appel,

• recherche d'une correspondance en utilisant les conversions de type prédéfinis,

• recherche d'une correspondance en utilisant les conversions définies par l'utilisateur et s'il n'en n'existe qu'une l'appliquer, sinon arrêt du programme du à un problème d'ambiguïté.

� Exemple

#include <iostream.h> int main() { // Prototypes divers de la fonction test surdéfinie

float test(float, float), test(float, int); double test( double, double );

float x, x2; double y, z; int n; cout << "Test (float, int) " << endl ; cout <<"saisir x et n : " ; cin >> x >> n; cout << "test("<< x << "," << n << ")=" << test(x,n) << endl ;

cout << "Test (float, float) " << endl ; cout <<"saisir x et x2 : " ; cin >> x >> x2; cout << "test("<< x << "," << x2 << ")=" << test(x,x2) << endl ;

cout << "Test ( double, double ) " << endl ; cout <<"saisir y et z : " ; cin >> y >> z; cout << "test("<< y << "," << z << ")=" << test(y, z) << endl ; return 1;

}

float test(float x, int n) { if (n <0) {cout << "erreur exposant négatif" << endl ; return -1;}

switch(n) { case 0 : return 1;

case 1 : return x; default : return x*test(x,n-1);

} }

float test (float x, float n) { cout << "float test (float x, float n)" << endl ;

cout << "x= " << x << "\tn = " << n << endl ; return (x*n);

}

Page 161: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LE C++, LANGAGE PROCEDURAL ET FONCTIONNEL ───────────────────────────────────────────────────

161

double test (double x, double n) { cout << "double test (double x, double n)" << endl ; cout << "x= " << x << "\tn = " << n << endl ; return (x*n); }

// Résultat Test (float, int) saisir x et n : 3 2 // Flottant puis réel test(3,2)=9

Test (float, float) saisir x et x2 : 2 3 // Deux flottants float test (float x, float n) x= 2 n = 3 test(2,3)=6

Test ( double, double ) saisir y et z : 2 3 // Deux doubles double test (double x, double n) x= 2 n = 3 test(2,3)=6

5.4 Valeur par défaut des arguments d'appel

• En langage C++, les prototypes permettent de définir des valeurs par défaut des arguments depuis la droite vers la gauche de la liste.

• Une nouvelle surdéfinition ne peut pas modifier la valeur par défaut d'un argument mais peut en augmenter le nombre.

• Les appels ambigus sont interdits.

Soit le prototype :

int calcul( int, int =5, int =0);

Tout appel de la forme calcul(x,y,z), calcul(x,y), calcul(x) est licite et les appels calcul(x,y) et calcul(x) se traduisent respectivement par calcul(x,y,0) et calcul(x, 5, 0).

� Exemple 1

#include <iosteam.h> // Fonctions avec 3 valeurs par défaut int main() { int somme(int =0, int=2, int =-3);

int a,b,c; cout << "saisir a, b ,c" << endl; cin >> a >> b >> c; cout <<" a = "<< a << " b = " << b << " c = " << c << endl; cout<<"somme(a,b,c) ="<< somme(a,b,c) << endl << "somme(a,b) ="<<somme(a,b)<<endl; cout << "somme(a) =" << somme(a) << endl << "somme() =" << somme()<< endl ;

}

int somme(int x, int y ,int z) {return x+y+z;}

Page 162: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

162 CHAPITRE VI ───────────────────────────────────────────────────

// Résultat saisir a, b ,c 5 -5 -10 a = 5 b = -5 c = -10 somme(a,b,c) = -10 // Somme des 3 valeurs saisies somme(a,b) = -3 // 5 + (-5) + -3 somme(a)=4 // 5 + 2 + -3 somme() = -1 // 0 + 2 + -3

� Exemple 2

#include <iostream.h> int main() { int somme(int =0, int=2, int =-3);

float somme(int, int, int, float); // Surcharge int a,b,c; float t; cout << "saisir a, b ,c" << endl; cin >> a >> b >> c; cout <<" a = "<< a << " b = " << b << " c = " << c << endl; cout << "somme(a,b,c) =" << somme(a,b,c) << endl ; cout << "somme(a,b) =" << somme(a,b) << endl; cout << "somme(a) =" << somme(a) << endl; cout << "somme() =" << somme()<< endl ; cout << "saisir a, b ,c, t" << endl; cin >> a >> b >> c >> t ; cout <<" a = "<< a << " b = " << b << " c = " << c << "t = " << t << endl; cout << "somme(a,b,c,t) =" << somme(a,b,c,t) << endl ; cout << "somme(a,b) =" << somme(a,b) << endl; cout << "somme(a,t) =" << somme(a,t) << endl; cout << "somme() =" << somme()<< endl ;

}

int somme(int x, int y ,int z) { return x+y+z; }

float somme (int x, int y, int z, float t) { return x + y + z +t; }

// Résultat saisir a, b ,c 5 -5 -10 a = 5 b = -5 c = -10 somme(a,b,c) = -10 // Somme des 3 valeurs saisies somme(a,b) = -3 // 5 + (-5) + -3 somme(a)=4 // 5 + 2 + -3 somme() = -1 // 0 + 2 + -3 saisir a, b ,c, t 5 -5 -10 3.14 a = 5 b = -5 c = -10 t = 3.14 somme(a,b,c, t) = -6.86 // Somme des 4 valeurs saisies somme(a,b) = -3 // 5 + (-5) + -3 somme(a,t)=5 // Erreur sur certains compilateurs somme() = -1 // 0 + 2 + -3

Page 163: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LE C++, LANGAGE PROCEDURAL ET FONCTIONNEL ───────────────────────────────────────────────────

163

6. REFERENCE

6.1 Définitions • Une référence est un identificateur synonyme d'une variable existante dont

l'utilisation simplifie l'écriture de la transmission des arguments entre les fonctions appelantes et appelées en évitant l'utilisation des pointeurs.

• L'opérateur & est l'opérateur de référence ou d'adresse.

• L'opérateur * est l'opérateur de déréférenciation ou d'indirection.

• Une référence est initialisée en ce sens qu'il doit toujours exister un objet de référence sur lequel les opérateurs opèrent par l'intermédiaire de ladite référence, implémentée sous la forme d'un pointeur constant déréférencé.

� Exemple

#include <iostream.h> int main() { int i = 1;

int & r1 = i, & r2 = i; // r1, r2 sont des références à i int x = r1; // x = i r1 += 2; // i=3 cout << " i = " << i << " r1 = " << r1 << " r2 =" << r2 << " x = " << x << endl;

}

// Résultat i = 3 r1 = 3 r2 = 3 x = 1

6.2 Transmission d'argument par référence • En langage C, les deux modes de transmission des arguments par valeur et par

adresse sont implémentés. Ce dernier mode est délicat à programmer les objets concernés étant représentés sous la forme de variables référencées dans la fonction appelante et de pointeurs déréférencés dans la fonction appelée.

• Le langage C++ définit la transmission des arguments par référence qui évite l'utilisation explicite de pointeur déréférencé dans la fonction appelée tout en permettant l'accès et la modification de l'objet depuis la fonction appelante.

Syntaxe

• Dans la fonction appelante :

◊ l'appel est identique à celui de la transmission par valeur le rendant alors impossible,

◊ seul, le prototype indique le mode de transmission des variables par référence.

• Dans la fonction appelée :

◊ le corps de la fonction est identique à celui de la transmission par valeur,

◊ seul, l'opérateur de référence définit le type des paramètres formels transmis.

Page 164: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

164 CHAPITRE VI ───────────────────────────────────────────────────

■ Surdéfinition et ambiguïté

La fonction swap peut être surdéfinie pour une transmission par référence et par adresse car la syntaxe d'appel n'est pas ambiguë, contrairement à la transmission par référence et par valeur.

� Exemple

#include <iostream.h> int main() { void swap(int &, int &); // Prototype de l'appel par référence void swap(int *, int*); // Prototype de l'appel par adresse

int a =2, b=3; swap(a,b); cout << "a= " << a << "\tb = " << b << endl ; swap(&a,&b); cout << "a= " << a << "\tb = " << b << endl ; }

void swap(int &x, int &y) // Transmission par référence { int aux; aux =x; x = y; y = aux; }

void swap (int *a, int *b) // Transmission par adresse { int aux; aux=*a; *a=*b; *b=aux;}

// Résultat a= 3 b = 2 a= 2 b = 3

6.3 Le spécificateur const et la transmission d'argument par référence

■ Inconvénient de la transmission par valeur

Toutes variable transmise par valeur est sauvegardée dans la pile d'exécution. La fonction appelée s'exécute avec une copie des arguments effectifs transmis ce qui est pénalisant quand la mémoire nécessaire pour l'argument effectif est importante.

■ Inconvénient de la transmission par référence ou par adresse

La transmission par référence permet à la fonction où la procédure appelée de modifier les arguments transmis, même quand le programmeur ne le souhaite pas.

■ L'art du compromis

L'utilisation du spécificateur const permet une transmission par adresse ou référence en garantissant l'intégrité de la variable transmise par la fonction ou la procédure appelée avec un gain de mémoire.

extern char * strcpy(const char *, char *);

Page 165: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LE C++, LANGAGE PROCEDURAL ET FONCTIONNEL ───────────────────────────────────────────────────

165

6.4 Fonction retournant une référence Une fonction peut retourner une référence (pointeur déréférencé) donc une Lvaleur. D'où la conséquence : un appel de fonction peut être une Lvaleur quand la fonction retourne une référence et figurer à gauche de l'opérateur d'affectation.

Syntaxe type_résultat & identificateur_de_fonction(...){corps_de_la_fonction}

� Exemple

Nous anticipons ici sur l'utilisation des notions de classe et de constructeur.

#include <iostream.h> #include <stdlib.h> #define TAILLE 80 class Ligne // Une instance de cette classe est une ligne de TAILLE caractères { private : char t[TAILLE + 1]; public: // Appel explicite du constructeur Ligne(char C = ' '); // Construction d'une ligne vide par défaut // Fonction (membre inline) retournant le caractère de la position d'un caractère donné char & Pos(int Position) { if (Position < 1 || Position > TAILLE){ cerr << "Position inacceptable" << endl ; exit(1); } return t[Position-1]; } };

Ligne::Ligne(char C) // Constructeur d'une ligne de C caractères identiques {for (int k=0; k<TAILLE; k++) t[k]=C; t[TAILLE]='\0'; }

int main() { Ligne L, La('A');

cout << "La.Pos(3) = " << La.Pos(3) << endl ; // La fonction peut modifier une position si le résultat est transmis par référence La.Pos(3) = 'Z'; cout << "La.Pos(3) = " << La.Pos(3) << endl ; La.Pos(2) = La.Pos(3); // Erreur de compilation en C: Lvalue required cout << "La.Pos(2) = " << La.Pos(2) << endl ; return 1;

}

// Résultat La.Pos(3) = A La.Pos(3) = Z La.Pos(2) = Z

Page 166: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

166 CHAPITRE VI ───────────────────────────────────────────────────

7. EXERCICES

� Exercice 1

Analyser le programme suivant #include <iostream.h> int saisir(char * texte) { cout << texte ;

int a; cin >> a; return a;

}

int additionner(int a, int b) { return(a+b);}

void afficher(char *texte , int a) { cout << texte << a << endl;}

int main() { int a, b;

int saisir (char *); void afficher(char * , int) , afficher(char *, float), afficher(char *, double ) ; int additionner(int, int) ; float additionner(float, float) ; double additionner( double, double ) ; a = saisir("a = "); b = saisir("b = "); afficher("a = ",a); afficher("b = ", b); afficher("c =", additionner(a,b));

}

� Exercice 2

Ecrire une fonction surdéfinie de multiplication de 3 nombres entiers et/ou flottants.

� Exercice 3

Analyser le programme suivant.

#include <iostream.h> int main(void) { int i =1;

int &r =i; // R est une référence à i r++; cout << "\nr = " << r << "\ti = " << i; i++; cout << "\nr = " << r << "\ti = " << i; // I est r cout << "\nLe résultat qui suit est étrange. Pourquoi ? "; cout << "\nr = " << r << "\ti = " << i++; cout << "\nr = " << r << "\ti = " << i; cout << "\nLe résultat qui suit est attendu. Pourquoi ? "; cout << "\nr = " << r++ << "\ti = " << i; cout << "\nr = " << r << "\ti = " << i;

}

Page 167: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LE C++, LANGAGE PROCEDURAL ET FONCTIONNEL ───────────────────────────────────────────────────

167

// Résultat r = 2 i = 2 r = 3 i = 3 Le résultat qui suit est étrange. Pourquoi ? r = 4 i = 3 r = 4 i = 4 Le résultat qui suit est attendu. Pourquoi ? r = 4 i = 4 r = 5 i = 5

� Exercice 4

Comment une variable, transmise par référence et qualifiée constante est-elle (re)transmise à une fonction appelée encapsulée ?

#include <iostream.h> void f1(const int &a) { void f2( int &); cout << "a =" << a << endl; int b =a ; f2(b); }

void f2( int & a) {a++; cout << "f2 : a =" << a << endl; }

int main() { void f1(const int &); int a =1; f1(a); a++; f1(a); }

� Exercice 5

On souhaite définir des opérations sur des nombres telles la saisie et l'affichage d'un nombre, la somme et le produit, la permutation de deux nombres, entiers ou flottants.

1°) Ecrire les fonctions ou procédures correspondantes (surdéfinies) en utilisant quand c'est nécessaire la transmission par valeur ou par référence.

2°) Ecrire une fonction main() qui se décompose uniquement en appel des fonctions précédentes pour effectuer la saisie, l'affichage de nombres que l'on additionnera, multipliera ou permutera selon les besoins.

2°) Compléter ce programme de telle sorte qu'il effectue les mêmes opérations surdéfinies avec des nombres complexes.

Page 168: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les
Page 169: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

CLASSES EN LANGAGE C++

1. RAPPELS

La classe permet d'implémenter un type d'objet abstrait dont la définition comprend :

• une partie privée de représentation des données, réservée au développeur,

• une partie publique constituant l'interface d'appel pour les utilisateurs des objets de la classe et contenant la description de l'ensemble des opérations (méthodes) autorisées sur ces derniers.

Le développeur se réserve l'usage exclusif d'un objet (attribut, méthode) en le déclarant privé ou permet à l'utilisateur d'y accéder en le déclarant public.

Le contrôle des accès aux objets est garanti par le compilateur.

2. DEFINITIONS

En langage C++, la classe est une généralisation aux langages orientés objets des variables structurées du langage C complétée à la fois :

• par la définition de fonctions internes (méthodes) opérants sur les objets de la classe,

• par des mécanismes de protection des données.

CHAPITRE VII

Interface d'accès aux objets publics.

Données Traitements

Attributs Méthodes

Accès public (Utilisateur)

Accès privé (Développeur)

Page 170: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

170 CHAPITRE VII ───────────────────────────────────────────────────

2.1 Classes et instances • Une classe est la représentation abstraite d'un ensemble d'objets dotés de propriétés

identiques.

• Chaque objet créé de la classe est appelé instance.

• L'opération de création est appelée instanciation. Elle nécessite bien évidemment de gérer la mémoire nécessaire à l'objet créé.

■ Propriétés

Les propriétés des instances d'une classe sont caractérisées par :

• la définition des informations caractérisant l'objet, appelées données membres, attributs de classe, ou champ,

• la définition des traitements autorisés sur ces objets appelés fonctions membres ou méthodes qui peuvent être définies dans la classe (méthodes en ligne) ou à l'extérieur.

• Dans ce dernier cas, le spécificateur inline peut précéder la définition, externe à la classe, de la méthode.

■ Mots clés

Les spécificateurs struct et class permettent de définir des classes d'objets.

■ Opérateur de résolution de visibilité

L'opérateur de résolution de visibilité (opérateur scope) :: permet de définir ou d'accéder à des méthodes d'une classe à l'extérieur de celle-ci selon la syntaxe :

Type_résultat identificateur_de_la_classe::identificateur_méthode(liste_des_paramètres_formels_typés) {corps_de_la_méthode}

■ Accès à une donnée membre

L'accès à une donnée membre est réalisé à partir de l'opérateur de sélection de membre ., comme en langage C pour une variable structurée.

■ Opération d'une méthode sur une instance

Le traitement d'une instance sur laquelle opère une méthode s'écrit, d'une façon analogue :

instance.methode(argument(s));

Page 171: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

CLASSES EN LANGAGE C++ ───────────────────────────────────────────────────

171

� Exemple

#include <string.h> #include <iostream.h> const int NbMaxCarac = 25; struct Produit { // Définition de la classe Produit // Définition des données-membres pour chaque instance char Nom[NbMaxCarac+1]; // Nom du produit float PrixHT; // Prix HT float TauxTVA; // Taux de TVA

// Définition et prototypes des méthodes float PrixTTC() {return PrixHT * (1+TauxTVA);} // Définition d'une méthode inline int FixeNom (const char *); // prototype de la méthode FixeNom }; // Fin de la définition de la classe Produit

int Produit::FixeNom (const char * Texte) // La fonction membre FixeNom {// Recopie au plus les NbMaxCarac premiers caractères de l'argument Texte

// Dans la donnée membre Nom. strncpy (Nom, Texte, NbMaxCarac); Nom[NbMaxCarac] = '\0'; // Chaîne à copier trop longue return strlen(Nom);

}

int main() {Produit P1; // Une instance de la classe Produit

Produit TabProduits[50]; // Un tableau de 50 instances P1.FixeNom("Chocolat Meunier 500g"); // Appel de la méthode FixeNom TabProduits[3] = P1; // Affectation (surdéfinie) entre instances TabProduits[5].FixeNom("Baril 5kg Lessive économique"); TabProduits[5].PrixHT = 45; TabProduits[5].TauxTVA = 0.196; cout << TabProduits[5].Nom <<": " << TabProduits[5].PrixTTC(); // Problème de débordement de chaîne de caractères avec // Strcpy (TabProduits[2].Nom,"Baril 5kg Lessive économique"); return (1);

}

// Résultat Baril 5kg Lessive économi: 53.82

■ Remarque

Le résultat semble satisfaisant sauf pour les débordements de chaînes de caractères (à priori limité par NbMaxCarac).

Page 172: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

172 CHAPITRE VII ───────────────────────────────────────────────────

2.2 Propriétés des méthodes en ligne • Une méthode définie dans le corps de la classe est dite inline.

• La sémantique d'utilisation d'une méthode inline est similaire à celle de la classe de mémorisation register pour une variable car c'est une directive de compilation qui spécifie le remplacement de la rupture de séquence de l'appel traditionnel par l'intégration immédiate du corps de la fonction dans le programme dont le code utilise davantage de mémoire mais est d'exécution plus rapide.

• Son utilisation est une bonne alternative à l'emploi de la directive #define :

◊ elle en présente les mêmes avantages liés à la substitution du corps de la fonction à l'appel.

◊ L'utilisation correcte des parenthèses est garantie par le compilateur ce qui limite les effets de bord possibles en langage C et offre une meilleure clarté.

• Une méthode inline n'a pas d'adresse, ne peut être ni récursive ni exportée.

• Une méthode inline étant insérée à son appel doit être définie préalablement à ce dernier (référence en avant).

• Sur le plan syntaxique, il est interdit de déclarer une méthode inline dans les fonctions appelantes ou de les définir dans un fichier séparé, contrairement aux fonctions traditionnelles.

• Une méthode, définie à l'extérieur de la classe peut être spécifiée inline.

� Exemple

inline void bonjour() { cout << "bonjour" << endl;}

3. QUALIFICATION D'ACCES AUX MEMBRES D'UNE CLASSE

L'accès à un membre d'une classe est qualifié privé, protégé (classe dérivée), ou public.

■ Accès privé

L'accès à une donnée membre privée n'est autorisé que pour les fonctions membres et amies de la classe où elle est déclarée. Il doit être réalisé au travers d'un assesseur (permettant d'en connaître la valeur) et d'un modificateur (permettant de la changer). Ces deux méthodes sont usuellement appelées setAttr, getAttr, où Attr représente l'attribut.

Un objet d'accès privé contenu dans un autre y est encapsulé. C'est la définition sémantique de l'encapsulation.

■ Accès public

L'accès à une donnée membre publique est autorisé pour n'importe quelle méthode, fonction, ou opérateur.

Page 173: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

CLASSES EN LANGAGE C++ ───────────────────────────────────────────────────

173

■ Qualification d'accès par défaut

Par défaut, les données et fonctions membres des variables structurées (struct) sont d'accès public. Celles des classes (class) sont d'accès privés.

Les mode d'accès par défaut des données et fonctions membres peuvent être requalifiés à partir des spécificateurs d'accès public, private, protected.

■ Accès protégé pour les classes dérivées

L'accès à un attribut protégé n'est autorisé que pour les fonctions membres et amies de la classe où il est déclaré ainsi que celles des classes dérivées par héritage.

■ Portée d'un spécificateur

Un spécificateur d'accès définit les règles d'accès pour les membres de la classe qui le suivent jusqu'à la fin de la classe ou jusqu'à un autre spécificateur d'accès.

Synopsis spécificateur_d'accès : liste_des_membres

■ Retour sur les variables structurées (struct et union)

En langage C++, les variables structurées et les unions sont des classes où tous les accès aux objets membres sont par défaut publics et où il est possible de définir une partie privée. Ainsi, la séquence :

struct pile {int taille, int *base, int *top, int pile(int); }

équivaut à

class pile {public : int taille, int *base, int *top, int pile(int); };

Cette définition est compatible avec le langage C normalisé.

� Exemple

// Deuxième version de la classe Produit #include <iostream.h> #include <string.h> const int NbMaxCarac = 25; // Par défaut, les accès des données membres d'une classe sont privées class Produit { char nom[NbMaxCarac+1]; // Données membre qualifiée d'accès privé

float prixHT, tauxTVA; public: // Méthodes inline float PrixTTC() {return prixHT * (1+tauxTVA);} const char * Nom() {return nom;} // La donnée membre nom reste constante int FixeNom (const char *); // Méthode définie à l'extérieur de la classe

}; // Fin de la définition de la classe Produit

Page 174: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

174 CHAPITRE VII ───────────────────────────────────────────────────

int Produit::FixeNom (const char * Texte) // La méthode FixeNom {// Copie au plus les NbMaxCarac premiers caractères de Texte dans le membre nom

strncpy (nom, Texte, NbMaxCarac); nom[NbMaxCarac] = '\0'; // Chaîne à copier trop longue return strlen(nom);

}

int main() { Produit P1;

P1.FixeNom("Chocolat économique Meunier par emballage de 100g"); cout << "P1 : " << P1.Nom() << endl ; P1.FixeNom("Baril 5kg Lessive Paic économique"); cout << "P1 : " << P1.Nom() << endl ; // Cout << "P1 : " << P1.nom << endl ; // Erreur de compilation : Produit::nom is not accessible // strcpy (P1.Nom(),"Baril 5kg Lessive Paic économique"); // Erreur de compilation : cannot convert 'const char *' to 'char *' return (1);

}

// Résultat P1 : Chocolat économique Meuni P1 : Baril 5kg Lessive Paic éc

4. METHODE

■ Règles d'utilisation

Une méthode peut :

• accéder et opérer sur toutes les données membres de la classe leur accès étant contrôlé à l'exécution à l'appel de la méthode.

• appeler toute méthode de la classe y compris elle même par récursion si elle n'est pas qualifiée inline,

• être d'accès public, privé ou protégé.

■ Méthode en ligne (inline)

Une méthode définie dans une classe est toujours inline.

■ Méthode définie à l'extérieur de la classe

• Une méthode, définie à l'extérieur d'une classe doit :

◊ comporter la définition explicite de sa portée,

◊ être déclarée dans la classe.

• Elle peut être définie en ligne si sa définition comporte le spécificateur inline.

Page 175: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

CLASSES EN LANGAGE C++ ───────────────────────────────────────────────────

175

5. LE POINTEUR THIS

■ Définition

Dans une méthode non statique, le mot clé this représente un pointeur constant contenant l'adresse de l'instance par l'intermédiaire duquel elle a été invoquée, accessible par déréférenciation à partir de l'expression *this.

Le pointeur this est inaccessible à l'extérieur de la méthode.

� Exemple

#include <iostream.h> class Entiers { public: int i; void affiche(char * chaine)

{ cout << chaine << " this=" << this << " i=" <<this->i << endl;}; };

int main() {Entiers k,l; k.affiche("k : "); l.affiche("l : "); Entiers p; p.affiche("p : "); }

// Résultats k : this =0x50771c24 i=0 l : this =0x50771c22 i=0 p : this =0x50771c20 i=9615 // Initialisation fantaisiste

6. METHODE SPECIFIEE CONSTANTE

Une méthode spécifiée constante ne peut modifier l'instance qui l'invoque.

Seules les méthodes spécifiées constantes peuvent opérer sur des instances qualifiées constantes.

Tout argument qualifié const perd cette qualification dans l'interprétation de sa signature sauf l'instance courante (accessible par le pointeur this). Deux méthodes avec des paramètres identiques dont une qualifiée const peuvent donc être définies.

Synopsis type_résultat identificateur_de_méthode(...) const {corps de la méthode}

Page 176: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

176 CHAPITRE VII ───────────────────────────────────────────────────

� Exemple

#include <iostream.h> // Troisième version de la classe Produit #include <string.h> const int NbMaxCarac = 25; enum {Taux1, Taux2}; // Entiers caractérisant les taux de TVA applicables class Produit { char nom[NbMaxCarac+1];

float tauxTVA; public: // Données membres publiques float PrixHT; float PrixTTC() const { return PrixHT * (1+tauxTVA);}

void MemeTVAque (Produit P) {tauxTVA = P.tauxTVA; } // Le taux de P est affecté à l'instance courante

// Prototypes const char * Nom() const; int FixeNom(const char *); void TVA( int); };

// La méthode Nom() qualifiée inline est définie à l'extérieur de la classe inline const char * Produit::Nom() const { return nom;}

// Validation du taux et affectation au membre de tauxTVA void Produit::TVA(int Taux) { switch (Taux)

{ case Taux1 : tauxTVA = 0.055; break; case Taux2 : tauxTVA = 0.196; break; default : cerr << "TVA inacceptable" << endl ; exit (1);

} }

// Copie au plus les NbMaxCarac premiers caractères de Texte dans le membre nom int Produit::FixeNom (const char * Texte) {strncpy (nom, Texte, NbMaxCarac); nom[NbMaxCarac] = '\0'; // Chaîne copiée trop longue

return strlen(nom); }

int main() { Produit P1 , P2;

P1.FixeNom("Chocolat Meunier 100g"); P1.PrixHT = 9; cout << "Prix HT de l'instance P1 : " << P1.PrixHT <<endl ; P1.TVA(Taux2); cout << "Prix TTC de l'instance P1 : " << P1.PrixTTC() <<endl ; P1.TVA(0); cout << "Prix TTC de l'instance P1 : " << P1.PrixTTC() <<endl ; P2.MemeTVAque (P1); P2.PrixHT = 18; cout << "Prix HT de l'instance P2 : " << P2.PrixHT <<endl ; cout << "Prix TTC de l'instance P2 : " << P2.PrixTTC() <<endl ; P2.TVA(4); // Message d'erreur à l'exécution (contrôle du taux) return (1);

}

// Résultat Prix HT de l'instance P1 : 9 Prix TTC de l'instance P1 : 10.764 Prix TTC de l'instance P1 : 9.495 Prix HT de l'instance P2 : 18 Prix TTC de l'instance P2 : 18.99 TVA inacceptable

Page 177: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

CLASSES EN LANGAGE C++ ───────────────────────────────────────────────────

177

7. POINTEUR SUR LES MEMBRES D'UNE CLASSE

Les objets membres d'une classe sont accessibles par leur adresse.

� Exemple

#include <iostream.h> class Classe { public: void methode(int a){cout << a << endl ;}; };

void f() {Classe c, *pc=&c; void (Classe::*Pointeur_Methode)(int) =&Classe::methode;

// Quatre écritures pour un même résultat int i =3; c.methode(i); pc->methode(++i); (c.*Pointeur_Methode)(++i) ; (pc->*Pointeur_Methode)(++i);

}

int main() { void f(); f(); }

// Résultats 3 4 5 6

8. EXERCICE

On souhaite créer une bibliothèque de classes de nombres (entiers, réels, complexes, etc.) permettant de réaliser des opérations élémentaires sur ces derniers (saisie, affichage, addition, multiplication, division, soustraction, division modulo, permutation, etc.).

1°) Définir les objets membres d'une classe de nombres entiers (attributs, méthodes) publics ou privés correspondants. Instancier 2 nombres entiers dont on affichera la valeur initiale, la somme, le produit, la division entière, la division modulo et les permuter.

2°) Idem avec une classe de nombres réels et une classe de nombres complexes.

Page 178: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

178 CHAPITRE VII ───────────────────────────────────────────────────

// Corrigé partiel #include <iostream.h> class Entiers { int valeur; public : void saisie() , affichage(char *) , addition(Entiers, Entiers) ; friend void permut(Entiers &, Entiers &); };

void Entiers::saisie() { cout << "valeur entière à saisir : " ; cin >> valeur; }

void Entiers::affichage(char * chaine) { cout << chaine << " : " << valeur << endl;}

void Entiers::addition(Entiers A, Entiers B) {valeur=A.valeur+B.valeur;}

void permut(Entiers & i, Entiers & j) { Entiers aux; aux.valeur=i.valeur; i.valeur=j.valeur; j.valeur=aux.valeur; }

int main() { Entiers A,B,C; A.saisie(); A.affichage("A"); B.saisie(); C.addition(A,B); C.affichage("somme :"); permut(B,C); B.affichage("B"); C.affichage("C"); }

Page 179: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQUES

Les règles d'allocation mémoire des variables en langage C sont très permissives comme nous allons le voir. Elles permettent bien évidemment d'écrire des bons programmes mais il y a de nombreux pièges d'utilisation, corrigés dans les langages à objets (C++, Java, etc.) par les constructeurs et les destructeurs.

Sont ensuite présentées les évolutions sémantiques des règles d'allocation dynamique de mémoire dans les langages C et C++.

La gestion des objets statiques (variables, fonctions, méthodes) est ensuite étudiée.

1. L'INITIALISATION DES VARIABLES EN LANGAGE C

1.1 Classes de mémorisation Les différentes classes de mémorisation des variables sont définies à partir des qualificatifs correspondants :

auto pour les variables automatiques, static pour les variables statiques, register pour les variables registres, extern pour les variables externes.

La portée (visibilité) d'une variable est la partie du programme où elle est accessible, celle-ci pouvant être globale ou de locale.

1.2 Variables locales et globales • Une variable est interne à un bloc, à une fonction ou à un fichier si elle y est

définie. Elle y est externe sinon.

• Un programme en langage C est constitué d'objets externes (variables, fonctions).

• La portée d'une variable locale est limitée à un fichier, une fonction, ou à un bloc.

• Les variables globales externes, définies une seule fois à l'extérieur de toute fonction, sont accessibles par différentes fonctions externes, leurs valeurs étant conservées entre les différents appels.

• Une variable externe peut être déclarée dans chaque fonction l'utilisant par la déclaration facultative extern. Cette dernière n'est obligatoire que si les variables concernées n'ont pas encore été définies (référence en avant). Des variables externes peuvent être définies relativement à certaines fonctions.

CHAPITRE VIII

Page 180: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

180 CHAPITRE VIII ───────────────────────────────────────────────────

� Exemple 1

On considère les différentes déclarations d'une variable externe i. Soient les fonctions f1, f2, f3, f4, définies avant la fonction main().

int i = 100; /* Variable globale pour toutes les fonctions */ void f1( void) /* Définition de f1 */ { printf("fonction f1 : ");

i++ ; /* Variable globale définie au début du fichier */ printf(" i = %d\n",i);

}

void f2(void) /* Définition de f2 */ { extern int i; /* Déclaration équivalente à la précédente */

printf("fonction f2 : "); i++ ; /* Variable globale définie au début du fichier */ printf(" i = %d\n",i);

}

void f3(void) /* Définition de f3 */ { extern i; /* Déclaration équivalente à la précédente */

printf("fonction f3 : "); i++ ; /* Variable globale définie au début du fichier */ printf(" i = %d\n",i);

}

int main(void) { /* Déclarations (prototype) de f1, f2, f3 */

void f1(void), f2(void), f3(void); printf("fonction main : i = %d\n",i); f1(); f2(); f3();

}

// Résultat fonction main : i = 100 fonction f1 : i = 101 fonction f2 : i = 102 fonction f3 : i = 103

f 1 f 2 f 3 m a i n

i = 1 0 0

Page 181: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQ UES ───────────────────────────────────────────────────

181

� Exemple 2

i est globale à l'ensemble du fichier, j accessible dans les fonctions f2 et main.

int i = 100; /* Variable globale pour les fonctions f1, f2, main */ void f1(void) { printf("fonction f1 : ");

i++ ; /* Variable globale définie au début du fichier */ printf(" i = %d\n",i);

}

int j = 20; /* Variable globale pour f2, main, invisible dans f1 sans déclaration extern */ void f2(void) { extern int i,j;

printf("fonction f2 : "); i++ ; printf(" i = %d j = %d\n",i,j);

}

int main(void) { void f1(void), f2(void);

printf("fonction main : i = %d\n",i); f1(); f2();

}

// Résultats fonction main : i = 100 fonction f1 : i = 101 fonction f2 : i = 102 j = 20

� Exemple 3

j étant définie après la fonction f1 y est inaccessible (référence en avant).

int i = 100; /* Variable globale */ void f1(void) { printf("fonction f1 : ");

printf(" j = %d\n",j); /* Erreur car j est définie en avant */ }

int j = 20; void f2(void) { extern int i,j; /* Déclaration équivalente à la précédente*/

printf("fonction f2 : "); i++ ; /* Variable globale définie au début du fichier */ printf(" i = %d j = %d\n",i,j);

}

int main(void) { void f1(void), f2(void); printf("fonction main : i = %d\n",i); f1(); f2(); }

f 1 f 2 m a i n

i = 1 0 0 j = 2 0

Page 182: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

182 CHAPITRE VIII ───────────────────────────────────────────────────

� Exemple 4

j défini comme variable externe dans la fonction f1 y est accessible.

int i = 100; /* Variable globale */ void f1(void) { extern j; /* Déclaration complémentaire */

printf("fonction f1 : "); i++ ; /* Variable globale définie au début du fichier */ printf(" j = %d\n",j);

}

int j = 20; void f2(void) { extern int i,j; /* Déclaration optionnelle */

printf("fonction f2 : "); i++ ; printf(" i = %d j = %d\n",i,j);

}

int main(void) { void f1(void), f2(void); printf("fonction main : i = %d\n",i); f1(); f2(); }

// Résultat fonction main : i = 100 fonction f1 : j = 20 fonction f2 : i = 102 j = 20

1.3 Initialisation des variables automatiques La portée d'une variable automatique est le bloc où elle est définie.

Toute variable automatique a une existence dynamique car créée à l'exécution du bloc et disparaissant à la fin de son exécution. Il faut donc impérativement l'initialiser pour éviter un comportement aléatoire d'un programme, la variable étant initialisée à chaque exécution à une valeur résiduelle laissée par le programme précédent.

C'est la classe de mémorisation par défaut.

1.4 Variables statiques Une variable statique peut être interne ou externe (donc locale ou globale).

■ Variable statique interne

Allouée à la compilation, locale à la fonction où elle a été définie, non réinitialisée entre chaque appel de cette dernière ce qui permet de conserver sa valeur entre deux appels son stockage étant permanent pendant l'exécution. C'est donc un point mémoire adressable, confidentiel et permanent.

Page 183: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQ UES ───────────────────────────────────────────────────

183

� Exemple

Les chaînes de caractères apparaissant dans les spécifications de format de la fonction printf sont ainsi définies, leur identificateur pouvant être utilisé ailleurs et définissant alors une variable différente.

■ Variable statique externe

La portée d'une variable statique externe est le fichier où elle a été définie.

C'est une méthode de camouflage d'une variable, à la fois accessible des fonctions du fichier source et invisible de l'extérieur.

■ Fonction statique

Une fonction statique est invisible hors de son fichier de définition.

� Exemple

i est une variable globale statique, une variable locale statique (f1, f2), une variable externe globale statique (f3, f4, f5), une variable de type automatique.

static i=1

f4 f5f3

static i = 100 i=1

f1 main

static i=3

f2

static i = 100; /* Variable globale statique, de type int par défaut, visible dans f3, f4, f5*/ /* Aucune variable de même nom n'y est définie. */ void f1(void) { static i = 1; /* Variable statique locale définie dans f1 */

printf("fonction f1 : "); i++ ; printf(" i = %d\n",i); }

void f2(void) { static i = 3; /* Variable statique locale définie dans f2 */

printf("fonction f2 : "); i++ ; printf(" i = %d\n",i); }

void f3(void) { printf("fonction f3 : ");

i++ ; printf(" i = %d\n",i); /* Variable statique globale définie au début du fichier */ }

void f4(void) { extern int i; /* Déclaration équivalente à la précédente*/

printf("fonction f4 : "); i++ ; printf(" i = %d\n",i); /* Variable statique globale définie au début du fichier */

}

void f5(void) { extern i; /* Déclaration équivalente à la précédente*/

printf("fonction f5 : "); i++ ;printf(" i = %d\n",i); /* Variable statique globale définie au début du fichier */

}

Page 184: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

184 CHAPITRE VIII ───────────────────────────────────────────────────

int main(void) { void f1(void), f2(void), f3(void), f4(void), f5(void);

int i; /* Variable automatique locale à main */ for (i = 0; i < 3; i++ ) { f1();f2();} printf(" résultats pour f3, f4, f5\n "); f3(); f4(); f5();

return(1); }

// Résultat fonction f1 : i = 2 fonction f2 : i = 4 fonction f1 : i = 3 fonction f2 : i = 5 fonction f1 : i = 4 fonction f2 : i = 6 résultats pour f3, f4, f5 fonction f3 : i = 101 fonction f4 : i = 102 fonction f5 : i = 103

1.5 Variables de type register Le type register est utilisé pour charger des variables dans des registres du processeur selon leur disponibilité ce qui optimise les temps d'exécution.

Rappelons que leur taille et leur nombre, spécifique pour tout processeur, détermine les types d'objets autorisés à y être chargés.

Cet objet est donc non portable en ce sens qu'il n'existe aucune fonction du langage permettant de connaître cette information, spécifique à chaque processeur.

2. L'INITIALISATION DES INSTANCES EN LANGAGE C++

En langage C++, deux méthodes permettent respectivement d'allouer et d'initialiser la mémoire puis d'en récupérer l'espace : le constructeur et le destructeur.

• Le constructeur est appelé pour définir et initialiser (implicitement ou explicitement) les données membres lors de toute instanciation.

• Le destructeur définit les opérations à effectuer lors de la restitution de la mémoire utilisée par un objet alloué par un constructeur.

Contrairement au langage C, la fonction main n'est pas la première à s'exécuter, les constructeurs devant l'être préalablement.

L'utilisation d'un constructeur permet une initialisation correcte des instances d'une classe contrairement à la classe de mémorisation par défaut (automatique) du langage C.

Page 185: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQ UES ───────────────────────────────────────────────────

185

3. CONSTRUCTEUR

3.1 Constructeur implicite Le langage C++ fournit un constructeur par défaut appelé constructeur implicite.

Les données membres sont initialisées à une valeur indéterminée.

� Exemple

// Quatrième version de la classe Produit : utilisation du constructeur par défaut #include <iostream.h> enum {Taux1, Taux2}; // Taux de TVA utilisables class Produit { private : float tauxTVA; public: float PrixHT; // Seul le prix HT d'un produit peut varier float PrixTTC() const { return PrixHT * (1+tauxTVA);} // Méthode inline };

int main() { Produit P1; // L'instance P1 est initialisée avec le constructeur par défaut

P1.PrixHT = 10.5; // PrixHT est le seul attribut public initialisé explicitement. cout << "PrixHT = " << P1.PrixHT << "\tPrix TTC = " << P1.PrixTTC() << endl ;

}

// Résultat PrixHT = 10.5 Prix TTC = 10.5 // Taux=0 !!

3.2 Constructeur explicite

■ Définition

Un constructeur explicite est une méthode dont l'identificateur est celui de la classe et qui se substitue au constructeur implicite.

Il ne retourne rien donc ne comporte aucune type de retour.

■ Synopsis

Soit la classe C. Alors:

[C::]C(...){corps_de_la_méthode}

est une méthode constructeur de la classe.

■ Redéfinition du constructeur par défaut

Tout constructeur sans argument surcharge le constructeur par défaut. Il doit être cohérent avec l'objet à construire sur le plan sémantique.

■ Remarque

L'utilisation d’un constructeur explicite impose d'instancier les objets avec le nombre d'arguments défini dans ce dernier ce qui oblige les utilisateurs de la classe à instancier les objets d'une manière convenable.

Page 186: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

186 CHAPITRE VIII ───────────────────────────────────────────────────

� Exemple et exercice

On considère la classe Produit. On souhaite exécuter la fonction main() suivante :

int main() { Produit P1("SAVON", 10, Taux2);

Produit P2("LIVRE DE POCHE", 25); cout << "Article P1 : Nom = " << P1.Nom() << " Prix HT = " << P1.PrixHT() ; cout << " Taux = " << P1.Taux() << " Prix TTC = " << P1.PrixTTC() ; cout << "\nArticle P2 : Nom = " << P2.Nom() << " Prix HT = " << P2.PrixHT() ; cout << " Taux = " << P2.Taux() << " Prix TTC = " << P2.PrixTTC() << endl;

// Produit P2; // Erreur de compilation : could not find a match for 'Produit::Produit() }

// Résultat Article P1 : Nom = SAVON Prix HT = 10 Taux = 0.196 Prix TTC = 11.96 Article P2 : Nom = LIVRE DE POCHE Prix HT = 25 Taux = 0.055 Prix TTC = 26.375 1°) Ecrire les méthodes void fixeNom(float), void prix(float), void tva(int)

ainsi que les méthodes inline

float PrixTTC() const const char * Nom() const const float PrixHT() const float Taux()

2°) Ecrire un constructeur de la classe qui fixe le taux de TVA par défaut à 5.5%. // Cinquième version de la classe Produit : constructeur explicite #include <iostream.h> #include <string.h> #include <stdlib.h> const int NbMaxCarac = 25; enum {Taux1, Taux2}; class Produit { private :

char nom[NbMaxCarac+1]; // Nom de l'instance float tauxTVA; // Taux de TVA float prixHT; // Prix HT // Prototypes des méthodes définies à l'extérieur à la classe void fixeNom(const char *); void prix(float); // Affecte une valeur valide à la donnée membre prixHT void tva(int); // Affecte une valeur valide à la donnée membre tauxTVA

public: // Méthodes inline float PrixTTC() const { return prixHT * (1+tauxTVA); } const char * Nom() const { return nom;} const float PrixHT() const { return prixHT;} const float Taux() const { return tauxTVA;} // Constructeur inline Produit(const char * Nom, float Prix, int TVA = Taux1) {fixeNom(Nom); prix(Prix); tva(TVA);}

}; // Fin de la classe Produit

Page 187: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQ UES ───────────────────────────────────────────────────

187

void Produit::fixeNom (const char * Texte) { strncpy (nom, Texte, NbMaxCarac);

nom[NbMaxCarac] = '\0'; }

void Produit::prix(float Prix) // Affectation d'une valeur valide au membre prixHT {if (Prix < 1 || Prix > 1000) {cerr << "prix HT inacceptable" << endl ; exit(1);}

prixHT = Prix; }

void Produit::tva(int Taux) // Validation du taux de TVA {switch (Taux)

{ case Taux1 : tauxTVA = 0.055; break; case Taux2 : tauxTVA = 0.196; break; default : cerr << "\nTVA inacceptable" << endl ; exit(1); }

}

3.3 Constructeurs multiples Plusieurs constructeurs peuvent être définis dans une classe à la condition que leurs signatures respectives soient distinctes, le constructeur utilisé à l'exécution étant celui dont la signature est (la plus) adaptée.

� Exemple

Soit la classe Produit. On souhaite exécuter la fonction main() suivante :

int main() { Produit P1("SAVON", 7.5, Taux2); // 1er constructeur Produit P2("LIVRE DE POCHE 1 VOL",25); // 1er constructeur (taux par défaut) Produit Base; // 2ième constructeur Produit P3 ("LESSIVE PROMO"); // 3ième constructeur Produit P4; // 2ième constructeur P4 = "NOUVEAUTE"; // 3ième constructeur avec copie membre à membre cout << "\nArticle\tNom\t\t\tPrix HT\tTaux\tPrix TTC" ; cout << "\nP1\t" << P1.Nom() << "\t" << P1.PrixHT() << "\t" << P1.Taux(); cout << "\t" << P1.PrixTTC() ; cout << "\nP2\t" << P2.Nom() << "\t" << P2.PrixHT() << "\t" << P2.Taux() ; cout << "\t" << P2.PrixTTC() ; cout << "\nP3\t" << P3.Nom() << "\t" << P3.PrixHT() << "\t" << P3.Taux(); cout << "\t" << P3.PrixTTC() ; cout << "\nP4\t" << P4.Nom() << "\t" << P4.PrixHT() << "\t" << P4.Taux(); cout << "\t" << P4.PrixTTC() ; cout<< "\nBase\t" << Base.Nom() << "\t" << Base.PrixHT() << "\t" << Base.Taux(); cout << "\t" << Base.PrixTTC() ; }

// Résultat Article Nom Prix HT Taux Prix TTC P1 SAVON 7.5 0.196 8.97 P2 LIVRE DE POCHE 1 VOL 25 0.055 26.375 P3 LESSIVE PROMO 8.36 0.196 10 P4 NOUVEAUTE 8.36 0.196 10 Base TEMOIN 83.60 0.196 100

Page 188: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

188 CHAPITRE VIII ───────────────────────────────────────────────────

1°) Ecrire les méthodes

void fixeNom(float), void prix(float), void tva(int)

ainsi que les méthodes inline

float PrixTTC() const , const char * Nom() const const float PrixHT() , const float Taux()

2°) Ecrire un constructeur (défaut) de la classe Produit fixant le taux de TVA à 5.5%.

3°) Ecrire un constructeur de la classe Produit qui fixe le nom d'un produit témoin dont le prix TTC est à 100 € avec un taux de TVA à 19.6%.

4°) Ecrire un constructeur de la classe Produit qui fixe par défaut le prix TTC de tout produit à 10 € avec un taux de TVA à 19.6%. #include <iostream.h> #include <string.h> #include <stdlib.h> const int NbMaxCarac = 25; enum {Taux1, Taux2};

class Produit { private : // Données membres char nom[NbMaxCarac+1]; float tauxTVA, prixHT; void fixeNom(const char *), prix(float), tva(int); // Prototypes des méthodes

public: // Méthodes inline float PrixTTC() const { return prixHT * (1+tauxTVA); } const char * Nom() const { return nom;} const float PrixHT() const { return prixHT;} const float Taux() const { return tauxTVA;}

Produit(const char * Nom, float Prix, int TVA = Taux1) // 1 : premier constructeur {fixeNom(Nom); prix(Prix); tva(TVA);}

Produit() // 2 : constructeur par défaut {fixeNom ("TEMOIN"); prixHT = 100/1.196; tva(Taux2);}

Produit(const char * Nom) // 3 : produit à 10 € {fixeNom(Nom); prixHT = 10/1.196; tva(Taux2); } };

// Méthodes void Produit::fixeNom (const char * Texte) { strncpy (nom, Texte, NbMaxCarac); nom[NbMaxCarac] = '\0'; }

void Produit::prix(float Prix) { if (Prix < 1 || Prix > 1000) {cerr << "prix HT inacceptable" << endl ; exit(1);} prixHT = Prix; }

void Produit::tva(int Taux) { switch (Taux)

{ case Taux1 : tauxTVA = 0.055; break; case Taux2 : tauxTVA = 0.196; break; default : cerr << "\nTVA inacceptable" << endl ; exit (1);

} }

Page 189: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQ UES ───────────────────────────────────────────────────

189

3.4 Transtypage par appel d'un constructeur En langage C++, deux opérations de transtypage explicite sont définies : la forme traditionnelle du langage C et le transtypage par appel d'un constructeur, encore appelé transtypage fonctionnel du langage C++.

■ Synopsis du transtypage fonctionnel

Un nom de type suivi d'une expression entre parenthèses convertit l'expression conformément au type de retour spécifié par l'appel du constructeur implicite.

Quand expression est une liste, le transtypage s'effectue par l'appel d'un constructeur, la classe de l'objet devant être dotée du constructeur adéquat.

� Exemple 1

float f ; long i = (long) f ; // Forme traditionnelle i = long(f) ; // Forme fonctionnelle : appel du constructeur implicite

� Exemple 2

// Transtypage fonctionnel par appel du constructeur par défaut de la classe int #include <iostream.h> int main() { int i = int(1.2); cout << " i = " << i << endl ; i = int(); cout << " i = " << i << endl ; int j = int(); cout << " j = " << j << endl ; return (1); }

// Résultat i = 1 i = 0 j = 0

■ Conversions implicites

Les conversions implicites sont exécutées dès qu'existe un constructeur dont le premier argument est du même type que l'objet source. Dans l'exemple ci-dessous, le nombre situé à la droite de l'affectation est converti en un objet de la classe Entier.

� Exemple

#include <iostream.h> class Entier { public : int i; Entier(int j) {i=j;} // Transtypage de nombres entiers par appel du constructeur };

int main() { int j=2;

Entier e1(j), e2=j, e3(5) ; cout << " e1 = " << e1.i << " e2 = " << e2.i << " e3 = " << e3.i << endl;

}

Page 190: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

190 CHAPITRE VIII ───────────────────────────────────────────────────

■ Le mot clé explicit

Le mot-clé explicit utilisé avant la déclaration du constructeur force la conversion explicite à partir d'un transtypage fonctionnel.

� Exemple

#include <iostream.h> class Entier {public : int i; explicit Entier(int j) {i=j; return ;} };

int main() {int j=6; // Entier e1; // Erreur Entier e1=(Entier) j; // e1.i=6 Entier e2=Entier(j); // e2.i=6 Entier e3=Entier(5); // e3.i=5 }

■ Remarque

Le type ne peut être composé dans la forme fonctionnelle (restriction syntaxique).

� Exemple

#include <iostream.h> int main() { char c = 'a', * p_c = &c; typedef int* p_int; p_int p_i; p_i = p_int(p_c); // OK // P_i = int* (p_c); // KO cout << " c = " << c << " *p_c = " << *p_c << " *p_i = " << *p_i << endl; }

// Résultat c = a *p_c = a *p_i = 97

4. DESTRUCTEUR

■ Destructeurs implicite et explicite

• Le destructeur est appelé quand une instance sort de la portée de la classe.

• Le destructeur par défaut libère la mémoire occupé par l'instance.

• Un destructeur ne peut être surdéfini son contexte d'utilisation étant inconnu.

• L'unique destructeur explicite, sans argument, d'une classe C est défini à partir de l'opérateur ~ selon la syntaxe :

[C::]~C(){...}

Page 191: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQ UES ───────────────────────────────────────────────────

191

5. GESTION DYNAMIQUE DE LA MEMOIRE EN LANGAGE C

La gestion dynamique de la mémoire permet d'allouer dynamiquement une zone mémoire et de la restituer au système d'exploitation après usage.

■ Taille des objets : l'opérateur sizeof

L'opérateur sizeof retourne le nombre d'octets de la représentation interne de son opérande qui peut être un nom de type, l'identificateur d'un tableau, d'une fonction, d'une variable structurée, ou un objet typé.

Le type retourné par l'opérateur sizeof est défini par la constante size_t (type non signé intégral) dans le fichier stddef.h.

Synopsis : size_t sizeof(objet);

On peut ainsi déterminer la taille des types des objets de base d'un ordinateur donné.

� Exemple 1

#include <stdio.h> int main(void) {/* Utilisation de l'opérateur sizeof avec un processeur 16 bits */

char tab1[10]; int tab2[10]; float tab3[10]; short tab4[10]; unsigned tab5[10]; double tab6[10]; printf("sizeof(char) = %d\t",sizeof(char)); printf("sizeof(int) = %d\t",sizeof(int)); printf("sizeof(short)= %d\n",sizeof(short)); printf("sizeof(long) = %d\t",sizeof(long)); printf("sizeof(float)= %d\t",sizeof(float)); printf("sizeof(double)= %d\n",sizeof(double)); printf("sizeof(long double)= %d \t ",sizeof(long double)); printf("sizeof(tab1) = %d\t",sizeof(tab1)); printf("sizeof(tab2) = %d\n",sizeof(tab2)); printf("sizeof(tab3) = %d\t",sizeof(tab3)); printf("sizeof(tab4) = %d\t",sizeof(tab4)); printf("sizeof(tab5) = %d\n",sizeof(tab5)); printf("sizeof(tab6) = %d\t",sizeof(tab6));

}

// Résultat en octets sizeof(char) = 1 sizeof(int) = 2 sizeof(short) = 2 sizeof(long) = 4 sizeof(float) = 4 sizeof(double)= 8 sizeof(long double) = 8 sizeof(tab1) = 10 sizeof(tab2) = 20 sizeof(tab3) = 40 sizeof(tab4) = 20 sizeof(tab5) = 20 sizeof(tab6) = 80

Sur une processeur 32 bits, on aurait obtenu

sizeof(int) = 4

Page 192: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

192 CHAPITRE VIII ───────────────────────────────────────────────────

� Exemple 2

#include <stdio.h> int main(void) { struct date { int jour; int mois; int années;} demain,*ptr;

printf("sizeof(struct date) = %d\n", sizeof(struct date)); printf("sizeof(demain) = %d\n",sizeof(demain)); printf("sizeof(*ptr) = %d\n", sizeof(*ptr)); printf("sizeof(ptr) = %d\n", sizeof(ptr));

}

// Résultat sizeof(struct date) = 6 sizeof(demain) = 6 sizeof(*ptr) = 6 sizeof(ptr) = 2

■ Mémoire occupée par un tableau

Soient le tableau mono-indice tab, * l'opérateur d'indirection, type(tab) le type des composantes du tableau. L'adresse du i-ème élément est obtenue par la formule :

tab[i] = *(tab[0] + i*sizeof(type(tab)))= *(tab + i*sizeof(type(tab)))

La définition char tab[4][2]; donne :

tab[i][j] = *(tab + i*sizeof(type(tab)) + j*sizeof(char))

■ Le pointeur générique

Les fonctions d'allocation dynamique de mémoire opèrent sur des objets dont le type effectif est déterminé à l'exécution du programme.

L'adresse de ces objets, de type prédéfini size_t, est décrite par le pointeur générique void * qui permet :

• au programmeur d'applications de définir des traitements qui s'appliqueront dynamiquement à l'exécution sur des objets dont le type sera déterminé par l'utilisateur.

• de comparer des pointeurs sur des objets de type différent tout pointeur pouvant être comparé au pointeur générique. Il est alors fondamental d'utiliser l'opérateur de transtypage pour convertir une adresse générique en un pointeur sur un objet d'un type déterminé.

■ Allocation dynamique et libération

La gestion des requêtes d'allocation dynamique de mémoire est intégrée à la bibliothèque standard du langage C.

• La fonction calloc fait une requête d'allocation dynamique d'une zone de mémoire pouvant contenir n items, chacun d'une taille t, initialisée à une valeur nulle.

• La fonction malloc retourne un pointeur sur le premier octet d'une zone allouée d'au moins n octets

• La fonction free libère l'espace en mémoire alloué par une des fonctions malloc, calloc ou realloc accessible à partir de l'adresse du bloc mémoire à libérer.

Page 193: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQ UES ───────────────────────────────────────────────────

193

Synopsis #include <stdlib.h> void *calloc(size_t n, size_t t);

#include <malloc.h> void *malloc(size_t n);

#include <stdlib.h> void free(void *ptr);

� Exemple

int main(void) // Allocation dynamique d'un tableau {int i; float *p; p = (float *) malloc(100*sizeof(float)); // Allocation d'un tableau de 100 flottants for(i=0;i < 100; i++) *(p+i)=i; // Remplissage du tableau … free(p); // Libération }

6. GESTION DYNAMIQUE DE LA MEMOIRE EN C++

• Le langage C++ définit l'opérateur new de gestion de l'allocation dynamique qu'il est préférable d'utiliser à la place des fonctions traditionnelles de la bibliothèque C (malloc, calloc, realloc, etc...), en particulier sur des instances de classes.

• La fonction free (C) ou l'opérateur delete (C++) restitue l'espace alloué .

• Ces opérateurs sont appelés implicitement ou explicitement par les constructeurs et destructeurs.

■ L'opérateur d'allocation dynamique new

• L'opérateur new effectue l'allocation mémoire sans initialisation et appelle si nécessaire un constructeur d'objet. Il renvoie la constante NULL en cas d'échec.

• La définition d'un modèle d'objet se distingue d'une instanciation qui provoque toujours l'appel d'un constructeur.

• La définition du constructeur par défaut est nécessaire pour initialiser chaque composante d'un tableau d'instances.

• L'opérateur new retourne un pointeur typé sur la composante du tableau alloué.

• La définition de la taille du tableau n'est pas obligatoire pour l'utilisation standard mais peut le devenir pour une surdéfinition.

Synopsis new déclaration_de_type; // Un objet new classe; // Une instance de la classe new classe[Taille_du_Tableau]; // Un tableau d'instances de la classe new classe(valeur); // Initialisation par transtypage fonctionnel

Page 194: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

194 CHAPITRE VIII ───────────────────────────────────────────────────

■ L'opérateur de libération delete

L'opérateur delete libère la mémoire alloué par l'opérateur new selon la syntaxe :

delete 0; // Autorisé et sans effet. delete P // Libération de l'espace alloué à l'instance pointée par P delete [] P // Libération de l'espace alloué au tableau d'instances P

■ Règles d'utilisation

• Les opérateurs new et delete d'allocation et de libération de la mémoire doivent être utilisés de préférence aux fonctions de la bibliothèque C malloc et free car ils garantissent un contrôle du type et une initialisation correcte.

• Il ne faut pas mélanger les fonctions et opérateurs d'allocation mémoire des langages C et C++, la gestion de la mémoire étant différente.

• Les opérateurs delete et delete [] ne doivent pas être utilisés sur une zone mémoire accessible avec le pointeur générique.

• Il faut utiliser l'opérateur delete avec les pointeurs retournés par l'opérateur new et l'opérateur delete [] avec ceux retournés par l'opérateur new [].

• L'opérateur new [] alloue la mémoire et crée les objets dans l'ordre croissant des adresses. L'opérateur delete [] les détruit dans l'ordre décroissant inverse.

� Exemple 1

// Allocation dynamique pour la donnée membre Nom avec l'opérateur new #include <iostream.h> #include <string.h> #include <stdlib.h> // Pour exit()

class Produit { char * nom; float prix;

public: Produit (const char * Nom, float Valeur) // Constructeur {nom = new char [strlen(Nom)+1];

if (nom == NULL) {cerr << "allocation impossible" << endl ; exit (1);} strcpy (nom, Nom); prix = Valeur;

}

void AfficheToi() const { cout << "Produit " << nom << " de prix " << prix << endl ;}

};

int main() {Produit P1("SAVON",7.5);

Produit * Ptr = &P1; Ptr->AfficheToi(); Ptr = new Produit("FARINE 1KG", 15.5); Ptr->AfficheToi(); // L'instruction Produit * Ptr2 = new Produit[100]; provoque l'erreur de compilation : // Cannot find default constructor to initialize array element of type 'Produit' return 1;

}

// Résultat Produit SAVON de prix 7.5 Produit FARINE 1KG de prix 15.5

Page 195: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQ UES ───────────────────────────────────────────────────

195

� Exemple 2

Soit le constructeur de la classe Entier qui initialise tout Entier à 0. Définir

1°) un pointeur initialisé sur un Entier (int) indéfini,

2°) un pointeur initialisé sur un Entier (int) défini,

3°) un pointeur sur un tableau de la classe Entier initialisé à 0,

4°) un pointeur sur un tableau d'instances de la classe Entier non initialisé.

#include <iostream.h> #define Taille 10 class Entier { public: int nombre , *ptnombre; Entier(){nombre=0; ptnombre=(int *) NULL; } // Constructeur par défaut ~Entier(){cout << "destructeur" << endl; delete [] ptnombre ; } };

int main() { int *pi = new int; cout << "*pi=" << *pi << endl ; delete pi; int a = 25, *pl = &a; cout << "\t*pl=" << *pl << endl ; int *pj = new int(543); // Initialisation d'un pointeur sur la constante entière 543 cout << "\t*pj=" << *pj << endl ; delete pj;

int *tableau = new int[Taille]; // Constructeur implicite de la classe int et valeurs résiduelles int i; cout << "tableau : " << endl; for(i=0;i<Taille;i++) cout << tableau[i] << " "; cout << endl; delete [] tableau;

Entier *ptab= new Entier [Taille]; // Appel du constructeur explicite cout << "tableau ptab : constructeur explicite" << endl; for(i=0; i<Taille; i++) cout << (*ptab++).nombre << " ";

Entier Tab[Taille]; // Appel du constructeur explicite cout << "\ntableau Tab : constructeur explicite" << endl; for(i=0; i<Taille; i++) cout << Tab[i].nombre << " "; cout << endl ; cout << "tableau pk : constructeur int implicite" << endl; int *pk = new int [Taille] ; // Un tableau d'entiers non initialisés for(i=0; i< Taille;i++) cout << *pk++ << " "; cout <<endl ; }

// Résultat *pi=0 *pl=25 *pj=543 tableau : 543 0 0 3369 0 0 0 0 0 0 // Valeurs résiduelles tableau ptab : constructeur explicite 0 0 0 0 0 0 0 0 0 0 tableau Tab : constructeur explicite 0 0 0 0 0 0 0 0 0 0 tableau pk : constructeur int implicite 0 0 0 0 0 0 0 0 0 0 destructeur ... destructeur

Page 196: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

196 CHAPITRE VIII ───────────────────────────────────────────────────

� Exemple 3

La chaîne de caractères doit être initialisée avec un caractère au moins.

// Classe Produit : version avec les opérateurs new et delete #include <iostream.h> #include <string.h> #include <stdlib.h> class Produit {private : char * nom; float prix; char * alloue(int LgrMem) {// Méthode privée d'allocation pour la donnée membre nom

char * Ptr = new char [LgrMem]; if (Ptr == NULL) {cerr << "plus de place en mémoire" << endl ; exit (1); } return Ptr;

}

public: Produit( const char * Nom, float Valeur) // Un constructeur {nom = alloue(strlen(Nom)+1);

strcpy (nom, Nom); prix = Valeur;

}

Produit() // Le constructeur par défaut {nom = alloue(1); nom[0] = '\0';

prix = 0; cout << "constructeur par défaut" << endl ;

}

void AfficheToi() const { cout << "Produit " << nom << " de prix " << prix << endl ; }

};

int main() { Produit P1("SAVON",7.5); // Appel du constructeur

P1.AfficheToi(); Produit *Ptr = new Produit[2]; // 2 instances et appel du constructeur par défaut for (int k=0; k<2; k++) Ptr[k].AfficheToi(); Produit TabProd[2]; // Idem cas précédent

}

// Résultat Produit SAVON de prix 7.5 constructeur par défaut constructeur par défaut Produit de prix 0 Produit de prix 0 constructeur par défaut constructeur par défaut

Page 197: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQ UES ───────────────────────────────────────────────────

197

7. OBJETS STATIQUES EN LANGAGE C++

7.1 Variables statiques Une variable statique a, comme en C, des propriétés de rémanence et confidentialité.

• Accessible uniquement dans le fichier où elle est définie.

• Non détruite en sortie de bloc comme les variables automatique.

• Accessible dans une fonction, même après un retour d'appel.

� Exemple : analyser les résultats du programme suivant :

#include <iostream.h> int main() { int f(int); // Prototype for(int j=0; j<3; j++) {cout << "fonction main()" << endl; cout << " j = " << j << " f(j) = " << f(j) << endl; cout << "**********************" << endl; } }

int f(int i) { static int s=0 ; s++; cout << " fonction f " << endl; cout << " i = " << i << " s = " << s << endl ; return i+1; }

// Résultats fonction main() fonction f i = 0 s = 1 j = 0 f(j) = 1 ********************** fonction main() fonction f i = 1 s = 2 j = 1 f(j) = 2 ********************** fonction main() fonction f i = 2 s = 3 j = 2 f(j) = 3 **********************

7.2 Fonctions statiques

■ Fonction externe

Par défaut, une fonction définie dans un fichier peut être utilisée dans un autre à condition d'y être préalablement déclarée. La fonction est alors dite externe.

Page 198: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

198 CHAPITRE VIII ───────────────────────────────────────────────────

■ Fonction statique

• Il peut être nécessaire de définir des fonctions locales à un fichier pour éviter des conflits d'identificateur (deux fonctions de même nom, même signature, dans deux fichiers différents) ou parce que la fonction est exclusivement d'intérêt local.

• Les langages C et C++ utilisent le qualificatif static, qui, en précédant la définition et les déclarations d'une fonction, la rend visible exclusivement dans ce fichier.

• La syntaxe d'utilisation des fonctions qualifiées static est identique à celle des fonctions traditionnelles.

� Exemple

static int locale1(void); // Déclaration d'une fonction statique /* Définition d'une autre fonction statique : */ static int locale2(int i, float j) { return i*i+j;}

7.3 Objets statiques d'une classe

■ Propriété des objets statiques

• Les objets statiques d'une classe peuvent être des données membres, des variables qualifiées statiques définies dans une méthode, des méthodes statiques.

• Les attributs statiques d'une classe sont communs à toutes ses instances.

7.4 Donnée membre statique

■ Propriétés

Une donnée membre qualifiée static :

• caractérise la classe et non ses instances,

• est accessible par tous ses objets,

• a des propriétés de rémanence et de protection identiques à celles d'une variable qualifiée statique en langage C.

■ Constructeur et données statiques

Les données statiques d'une classe n'étant pas spécifiques à une instance donnée, la norme interdit de les initialiser par un constructeur dont le rôle est l'initialisation dynamique des nouvelles instances.

■ Initialisation

L'initialisation des données statiques est réalisée à leur définition, toujours externe à la classe et spécifiée avec l'opérateur de résolution de portée.

Page 199: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQ UES ───────────────────────────────────────────────────

199

� Exemple

#include <iostream.h> class test { public : static int i; // compteur d'instances test(){i++;}; }; int test::i=0; // Initialisation externe à la classe. int main(void) { test a; cout << test::i << endl; test b; cout << test::i << endl; } // Résultat 1 2

7.5 Variable statique d'une méthode

• Les variables statiques des méthodes doivent y être initialisées.

• Leur portée est réduite à celle du bloc où elles ont été définies.

• Elles caractérisent la classe, pas ses instances.

� Exemple

#include <iostream.h> class Test {public: int compte(void); }; int Test::compte(void) { static int nombre=0; nombre++; return nombre; }

int main(void) {Test objet1, objet2; cout << objet1.compte() << endl; // Affiche 1 cout << objet2.compte() << endl; // Affiche 2 return 0; }

� Exercice : analyser le programme suivant

#include <iostream.h> class Entiers { public: int i; Entiers() {i=0; cout << "this =" << this << endl;}; // Un constructeur void incremente() {i++; cout << "this =" << this << endl;}; void affiche(char * chaine ){cout << chaine << i << endl;}; };

int main() { void f(); f(); f(); }

Page 200: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

200 CHAPITRE VIII ───────────────────────────────────────────────────

void f() { static Entiers k,l; k.affiche("k = "); k.incremente(); k.affiche("k = "); l.affiche("l = "); Entiers p; p.affiche("p = "); }

// Résultat this =0x6db70642 this =0x6db70644 k = 0 this =0x6db70642 k = 1 l = 0 this =0x6db71c30 p = 0 k = 1 this =0x6db70642 k = 2 l = 0 this =0x6db71c30 p = 0

7.6 Méthode statique Certaines méthodes peuvent n'opérer que sur des attributs statiques. Leur appel ne nécessite que le nom de leur classe.

■ Définition

• Une méthode statique, définie et qualifiée static, ne caractérise pas la classe. Son appel est identique à celui d'une méthode non statique.

• Une méthode qualifiée static peut être invoquée avec ou sans référence à un objet. Dans le premier cas, la partie gauche de l'expression objet.methode() n'est pas évaluée le pointeur this n'ayant alors pas de sens.

� Exemple 1

#include <iostream.h> class Entier { static int j;

public: static int set_value(void); };

int Entier::j=10; int Entier::set_value(void) {j++; // Légal. return j; }

int main() { cout << Entier::set_value(); cout << " " << Entier::set_value() << endl; }

// Résultat 11 12

Page 201: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQ UES ───────────────────────────────────────────────────

201

� Exemple 2

#include <iostream.h> class Entier { static int i; public: static int set_value(void); };

int Entier::i=3; // Initialisation externe à la classe int Entier::set_value(void) { return i;}

int main(void) { int resultat=Entier::set_value(); cout << resultat << endl; // Résultat = 3 return 0; }

8. EXERCICES

� Exercice 1

1°) Créer une classe de nombres complexes avec un constructeur devant prendre en compte toutes les situations possibles d'initialisation de ses instances.

2°) Instancier quatre objets dans des situations d'initialisation différentes par un transtypage fonctionnel. On constatera que la situation peut être ambiguë.

#include <iostream.h> class complexe { public:

float reel; float imaginaire; complexe(double x=0, double y=0){reel = x; imaginaire =y;}// Constructeur inline

};

int main() // Initialisation par appel du constructeur et transtypage fonctionnel {// complexe z0 = {1,10}; initialisation traditionnelle interdite si constructeur défini

complexe z1(1.5, -10.78); // 2 flottants complexe z2(-1,3); // 2 entiers et transtypage fonctionnel, identique à // complexe z2= complexe(-1,3); complexe z3 = complexe(); // Valeur par défaut, mais // complexe z3(); // Interdit complexe z4 = complexe(-5); // Identique à complexe z4(-5, 0); complexe z5(0,-5); cout << "z1.reel = " << z1.reel << "\tz1.imaginaire = " << z1.imaginaire << endl ; cout << "z1.reel = " << z1.reel << "\tz1.imaginaire = " << z1.imaginaire << endl ; cout << "z2.reel = " << z2.reel << "\tz2.imaginaire = " << z2.imaginaire << endl ; cout << "z3.reel = " << z3.reel << "\tz3.imaginaire = " << z3.imaginaire << endl ; cout << "z4.reel = " << z4.reel << "\tz4.imaginaire = " << z4.imaginaire << endl ; cout << "z5.reel = " << z5.reel << "\tz5.imaginaire = " << z5.imaginaire << endl ;

}

// Résultat z1.reel = 1.5 z1.imaginaire = -10.78 z2.reel = -1 z2.imaginaire = 3 z3.reel = 0 z3.imaginaire = 0 z4.reel = -5 z4.imaginaire = 0 z5.reel = 0 z4.imaginaire = -5

Page 202: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

202 CHAPITRE VIII ───────────────────────────────────────────────────

� Exercice 2

Les méthodes publiques (saisie et affichage) accèdent aux données privées.

#include <iostream.h> class complexe { float reel; float imaginaire; public: complexe(double x=0, double y=0){reel = x; imaginaire =y;} // Constructeur void affiche(char *), affiche(void), saisie(char *); };

void complexe::saisie(char * chaine) { cout << "Nombre complexe " << chaine << endl; cout << "saisir la partie reelle : " ; cin >> this->reel; cout << endl << "saisir la partie imaginaire : "; cin >> this->imaginaire; }

void complexe::affiche(char * chaine) { cout << "Nombre complexe " << chaine << endl; cout <<"partie reelle : "<< this->reel <<" partie imaginaire : "<<this->imaginaire<< endl; }

int main() // Initialisation avec appel du constructeur et transtypage fonctionnel {// complexe z0 = {1,10}; // Interdit si constructeur défini

void complexe::affiche(char *); void complexe::saisie(char *); complexe z1(1.5, -10.78); // 2 flottants complexe z2(-1,3); // 2 entiers et transtypage fonctionnel, identique à // complexe z2= complexe(-1,3); complexe z3 = complexe(); // Valeur par défaut, mais complexe z3(); interdit complexe z4 = complexe(-5); // Identique à complexe z4(-5, 0);

}

� Exercice 3

1°) Créer une classe de nombres complexes avec deux constructeurs. Il faudra redéfinir le constructeur par défaut pour éviter l'ambiguïté de l'exemple précédent.

2°) Instancier cinq objets de telle sorte que ces constructeurs soient appelés suite à un transtypage fonctionnel dans cinq situations d'initialisation différentes et qu'une situation ambiguë soit impossible.

#include <iostream.h> class complexe { public: float reel; float imaginaire; //constructeurs complexe() {reel =0; imaginaire = 0;} // Constructeur par défaut complexe(double x, // X = 0 impossible car ambiguïté avec le constructeur par défaut double y=0) // Nécessaire pour z4 {reel = x; imaginaire =y;} };

Page 203: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQ UES ───────────────────────────────────────────────────

203

int main() { // Initialisation avec appel des constructeurs et transtypage fonctionnel

complexe z1(1,10), z2= complexe(-1,3), z3 = complexe(), z4(-5) , z5(0,-5); cout << "z1.reel = " << z1.reel << "\tz1.imaginaire = " << z1.imaginaire << endl ; cout << "z2.reel = " << z2.reel << "\tz3.imaginaire = " << z2.imaginaire << endl ; cout << "z3.reel = " << z3.reel << "\tz3.imaginaire = " << z3.imaginaire << endl ; cout << "z4.reel = " << z4.reel << "\tz4.imaginaire = " << z4.imaginaire << endl ; cout << "z5.reel = " << z5.reel << "\tz5.imaginaire = " << z5.imaginaire << endl ; return (1);

}

// Résultat z1.reel = 1 z1.imaginaire = 10 z2.reel = -1 z2.imaginaire = 3 z3.reel = 0 z3.imaginaire = 0 z4.reel = -5 z4.imaginaire = 0 z5.reel = 0 z5.imaginaire = -5

� Exercice 4

On souhaite initialiser des tableaux d'entiers.

1°) Définir une classe TableauEntier avec deux données membres représentant l'adresse et la taille en octet du tableau.

2°) Définir :

• le constructeur par défaut,

• le constructeur d'initialisation d'un tableau d'une taille donnée,

• le constructeur d'initialisation d'un tableau à partir d'un autre. Il faudra surdéfinir les opérateurs d'affectation et crochet.

• une procédure init, appelée par les constructeurs pour initialiser le tableau.

• le destructeur.

3°) Intégrer une fonction en ligne permettant de connaître la taille du tableau.

#include <iostream.h> #include <assert.h> const int TailleDef = 100; // Taille par défaut class TableauEntier { public:

TableauEntier(int Taille = TailleDef); // Constructeur de tableau non dimensionné (défaut) TableauEntier(const int*, int); // Constructeur d'un tableau dimensionné TableauEntier(const TableauEntier &); // Constructeur d'un tableau à partir d'un autre ~TableauEntier(){delete [] adresse; } // Destructeur TableauEntier& operator =(const TableauEntier&);// Opérateur d'affectation surdéfini int& operator [] ( int); // Surdéfinition de l'opérateur [] int getSize() {return Taille;} // Méthode inline protected : // Données internes void init (const int*, int); int Taille, *adresse; // Taille du tableau, adresse du tableau

};

Page 204: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

204 CHAPITRE VIII ───────────────────────────────────────────────────

// Constructeur par défaut d'un tableau de dimension non définie // Allocation d'un tableau d'entier de Taille composantes (taille par défaut) TableauEntier::TableauEntier(int TailleDef) {init (0,TailleDef);} // Constructeur d'un tableau dont la dimension est fournie TableauEntier::TableauEntier(const int *tableau, int Taille) {init(tableau,Taille);} // Contructeur d'initialisation d'un tableau à partir d'un autre TableauEntier::TableauEntier(const TableauEntier &A) {init(A.adresse, A.Taille);} // Procédure init void TableauEntier::init(const int * tableau, int TailleDef) {adresse = new int[Taille=TailleDef]; assert(adresse !=0); // Traitement des exceptions for(int ix=0; ix < Taille; ++ix) adresse[ix]=(tableau !=0) ? tableau[ix] : 0; }

TableauEntier& TableauEntier::operator =(const TableauEntier&A) { if (this== &A) return *this; // Tableau lui même delete adresse; init(A.adresse, A.Taille); return *this; }

inline int& TableauEntier::operator [] ( int index) {return (adresse[index]);}

void swap(TableauEntier & tableau, int i, int j) { int tmp =tableau[i]; tableau[i]=tableau[j]; tableau[j]=tmp; }

int main() { int maTaille = 1024;

TableauEntier Tableau, A(maTaille), *pA=&Tableau, A2=Tableau, A3 ; cout << "Tableau.getSize() = " << Tableau.getSize() << "\n"; cout << "A.getSize() = " << A.getSize() << "\n"; cout << "(*pA).getSize() = " << (*pA).getSize() << "\n"; cout << "A2.getSize() = " << A2.getSize() << "\n";

A3=Tableau; cout << "A3.getSize() = " << A3.getSize() << "\n"; for(int i=0; i< maTaille;i++) Tableau[i]=i; cout << Tableau[1] << "\t" << Tableau[Tableau.getSize()] << "\n"; swap(Tableau, 1, Tableau.getSize()); cout << Tableau[1] << "\t" << Tableau[Tableau.getSize()] << "\n"; return 1;

}

// Résultat Tableau.getSize() = 100 A.getSize() = 100 (*pA).getSize() = 100 A2.getSize() = 100 A3.getSize() = 100 1 100 100 1

Page 205: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQ UES ───────────────────────────────────────────────────

205

� Exercice 5

Un objet postal est décrit par son poids, la valeur de l'affranchissement, et son type (pli ordinaire ou recommandé). Si l'objet est recommandé, il faut indiquer sa valeur déclarée.

1°) Construire la classe ObjetPostal avec les attributs poids, valeur, recommande.

2°) Définir les méthodes aValeurDeclaree, poidsObjet, recommander permettant d'accéder aux données membres précédentes.

3°) Définir un constructeur d'un objet postal.

// Fichier SacPostal.C #include <iostream.h> class ObjetPostal { private :

int poids, valeur, recommande;

public: int tarif; int aValeurDeclaree() {return (valeur >0);} int poidsObjet() {return poids;} void recommander() {recommande =1 ;}

// Constructeurs ObjetPostal(int); ObjetPostal();

};

// Constructeurs par défaut et explicite ObjetPostal::ObjetPostal() {poids = 20 ; valeur =0; recommande =0; }

ObjetPostal::ObjetPostal(int p) {poids = p ; valeur =0; recommande =0;}

int main() {ObjetPostal x; // Appel du constructeur par défaut pour l'instance x

cout << "x.poidsObjet() = " << x.poidsObjet() << endl ; ObjetPostal y= 160; // Appel du constructeur explicite ObjetPostal z(160); // Identique au précédent cout << "y.poidsObjet() = " << y.poidsObjet() << endl ; cout << "z.poidsObjet() = " << z.poidsObjet() << endl ; return 1;

}

// Résultat x.poidsObjet() = 20 y.poidsObjet() = 160 z.poidsObjet() = 160

Page 206: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

206 CHAPITRE VIII ───────────────────────────────────────────────────

� Exercice 6

La capacité d'un sac postal lui permet de contenir des instances de la classe ObjetPostal. Définir la classe SacPostal et les méthodes associées (constructeurs et destructeurs d'un sac).

#include "SacPostal.C" SacPostal::SacPostal(int cap) // Constructeur {capacite =cap; nbelts = 0; // Sac vide

sac = new ObjetPostal[cap]; // Allocation d'un tableau d'instances }

SacPostal::~SacPostal() // Destructeur { delete [capacite] sac;} // Restitution de l'espace mémoire utilisé par le tableau

int main() {ObjetPostal x;

ObjetPostal y= 160; SacPostal courrier(250); // 250 objets postaux dans le sac cout<<"courrier.nbelts = " << courrier.nbelts ; cout << "\tcourrier.capacité = " << courrier.capacite <<endl ;

}

// Résultat courrier.nbelts = 0 courrier.capacité = 250;

� Exercice 7

Analyser le résultat d'exécution du programme suivant. Conclusion sur le constructeur par défaut.

#include <iostream.h> #include <string.h> #include <stdlib.h> class Produit {private : char * nom; float prix; char * alloue(int LgrMem) {// Méthode privée d'allocation de la donnée nom

char * Ptr = new char [LgrMem]; if (Ptr == NULL) {cerr << "plus de place en mémoire" << endl ; exit (1); } return Ptr;

}

public: Produit( const char * Nom, float Valeur) // Un constructeur {nom = alloue(strlen(Nom)+1);

strcpy (nom, Nom); prix = Valeur; }

Produit() // Constructeur par défaut {nom = alloue(1);

nom[0] = '\0'; // Garantit le bon fonctionnement de la fonction AfficheToi prix = 0; cout << "appel du constructeur par défaut avec prix = " << prix << endl ;

}

~Produit() {delete [] nom;} // Destructeur d'un tableau d'instances

Page 207: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQ UES ───────────────────────────────────────────────────

207

void ChangeNom(const char * NouveauNom) // Modification du nom { delete [] nom;

nom=alloue(strlen(NouveauNom+1)); strcpy(nom, NouveauNom);

}

void AfficheToi() const { cout << "Produit " << nom << " de prix " << prix << " €" << endl ; } };

int main(void ) { Produit P1("SAVON",7.5); Produit * Ptr = new Produit; // Un objet dynamique local à main() Ptr->ChangeNom("BROSSE A DENTS"); Ptr->AfficheToi();

{Produit P2("LIVRE DE POCHE 1 VOL",25);// P2 local au bloc P2.AfficheToi(); P2.ChangeNom("POCHE SIMPLE"); P2.AfficheToi(); } // L'instance P2 est détruite à la sortie du bloc

Ptr->AfficheToi(); // On affiche à nouveau Ptr delete Ptr; // Destruction de l'objet pointé par Ptr cout << "Allocation d'un tableau de 3 instances " << endl ; Ptr = new Produit[3]; // Un tableau de 3 instances for (int k=0; k<3; k++) {cout << "k= " << k << " : "; Ptr[k].ChangeNom( "K"); Ptr[k].AfficheToi(); }

delete Ptr; // Destruction du tableau de trois instances P1.ChangeNom("SAVON MENAGER"); P1.AfficheToi(); return (1); }

// Résultat appel du constructeur par défaut avec prix = 0 Produit BROSSE A DENTS de prix 0 € Produit LIVRE DE POCHE 1 VOL de prix 25 € Produit POCHE SIMPLE de prix 25 € Produit BROSSE A DENTS de prix 0 € Allocation d'un tableau de 3 instances appel du constructeur par défaut avec prix = 0 appel du constructeur par défaut avec prix = 0 appel du constructeur par défaut avec prix = 0 K= 0 : Produit K de prix 0 € K= 1 : Produit K de prix 0 € K= 2 : Produit K de prix 0 € Produit SAVON MENAGER de prix 7.5 €

Page 208: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

208 CHAPITRE VIII ───────────────────────────────────────────────────

� Exercice 8

Analyser le résultat d'exécution du programme suivant. Conclusion sur le constructeur par défaut.

#include <iostream.h> #include <string.h> const int nbmaxcarac = 25; const float taux1 = 0.196, taux2 = 0.055; class Produit { private : char nom[nbmaxcarac+1]; float tauxTVA, prixHT;

public: void fixenom (char [] ), PrixHT (float), tva (float); float prixTTC() const { return prixHT * (1+tauxTVA);}; char * Nom() {return nom;}; float aff_prixHT() const { return prixHT;}; float taux() const { return tauxTVA;}

Produit (char param_nom[], float param_prixHT , float param_tauxtva=.196) {fixenom (param_nom);

PrixHT (param_prixHT); tva (param_tauxtva);

}

Produit (char * param_nom) {fixenom (param_nom);

tauxTVA=.196; prixHT=10/1.196;

}

Produit() {fixenom("Produit phare");

tauxTVA=.196; prixHT=10/1.196;

}

};

void Produit::fixenom (char * param_nom ) {strncpy(nom, param_nom, nbmaxcarac);

nom[nbmaxcarac] = '\0'; }

void Produit::PrixHT (float param_prixHT) { if ((param_prixHT < 0) | (param_prixHT > 10000))

{ cout << "prix errone" <<endl; prixHT = 0;} else

{prixHT = param_prixHT;} }

void Produit::tva (float param_tauxtva) {tauxTVA = param_tauxtva; }

Page 209: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INITIALISATION, ALLOCATION DYNAMIQUE, OBJETS STATIQ UES ───────────────────────────────────────────────────

209

main() { Produit P1("lait",2.5,taux2); Produit P2("voiture",6000,taux1);

Produit P3("voiture2",15000); Produit P4("sucre"); Produit P5; cout << P1.Nom()<<" prixHT "<<P1.aff_prixHT() << " taux "<< P1.taux() ; cout << " prixttc " << P1.prixTTC()<<endl; cout << P2.Nom()<<" prixHT "<<P2.aff_prixHT() << " taux "<< P2.taux() ; cout << " prixttc " << P2.prixTTC()<<endl; cout << P3.Nom()<<" prixHT "<<P3.aff_prixHT() << " taux "<< P3.taux() ; cout << " prixttc " << P3.prixTTC()<<endl; cout << P4.Nom()<<" prixHT "<<P4.aff_prixHT() << " taux "<< P4.taux() ; cout << " prixttc " << P4.prixTTC()<<endl; cout << P5.Nom()<<" prixHT "<<P5.aff_prixHT() << " taux "<< P5.taux() ; cout << " prixttc " << P5.prixTTC()<<endl;

}

// Résultat prix errone lait prixHT 2.5 taux 0.055 prixttc 2.6375 voiture prixHT 6000 taux 0.196 prixttc 7176 voiture2 prixHT 0 taux 0.196 prixttc 0 sucre prixHT 8.3612 taux 0.196 prixttc 10 Produit phare prixHT 8.3612 taux 0.196 prixttc 10

� Exercice 9

1°) Créer une classe de nombres entiers et une classe de nombres réels avec les caractéristiques suivantes :

• Chaque instance de la classe est décrite par un attribut appelés valeur.

• Les méthodes sont :

◊ instanciation à partir d'un ou plusieurs constructeurs,

◊ saisie d'une instance,

◊ affichage d'une instance,

◊ addition, multiplication, division de 2 instances opérant sur l'instance résultat.

• Ecrire une fonction amie de la classe, permettant d'en permuter deux instances. On essaiera les implémentations par adresse, valeur et référence.

2°) Afficher l'adresse de l'instance courante ainsi que son contenu (pointeur this).

3°) Mêmes questions avec une classe de nombres complexes dont chaque nombre est décrit par les attributs reel et imaginaire. Il faudra pouvoir instancier un nombre réel, complexe, imaginaire pur, à partir du constructeur par défaut ou par un appel explicite.

4°) Modifier si nécessaire les constructeurs pour instancier des tableaux (statiques ou dynamiques) de nombres.

Page 210: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

210 CHAPITRE VIII ───────────────────────────────────────────────────

// Corrigé partiel #include <iostream.h> class Entiers { int valeur; public : void saisie() , affichage(char *) , addition(Entiers, Entiers) ; Entiers(int init=0) {valeur = init ; } // Constructeur friend void swap(Entiers &, Entiers &); };

void Entiers::saisie() {cout << "valeur entière à saisie : " ; cin >> valeur; }

void Entiers::affichage(char * chaine) {cout << chaine << " : " << valeur << endl;}

void Entiers::addition(Entiers A, Entiers B) {valeur=A.valeur+B.valeur;}

void swap(Entiers & i, Entiers & j) {Entiers aux; aux.valeur=i.valeur ; i.valeur=j.valeur ; j.valeur=aux.valeur; }

class Reels { float valeur; public : void saisie(void), affichage(void), addition(Reels, Reels), multiplication(Reels, Reels); friend void swap (Reels &, Reels &); Reels(float); // Constructeur };

Reels::Reels(float init = 0){valeur = init;}

void Reels::saisie(void) {cout << " valeur réelle à saisir "; cin >> valeur; cout << endl;}

void Reels::affichage(void) { cout<<"valeur = "<<valeur<<"\tthis = "<<this<<"\tthis -> valeur = "<<this->valeur<< endl;}

void Reels::addition(Reels x, Reels y) {valeur= x.valeur+y.valeur;}

void Reels::multiplication(Reels x, Reels y) {valeur= x.valeur*y.valeur;}

void swap(Reels & v, Reels & w) { Reels aux; aux = v; v = w ; w = aux; } // Equivalent aux instructions suivantes car l'affectation est surdéfinie par défaut // aux.valeur= v.valeur; v.valeur=w.valeur; w.valeur=aux.valeur;

int main(void) { Entiers A,B,C; Reels a , b(0), c(3e-2),d; A.saisie(); A.affichage("A"); B.saisie(); C.addition(A,B); C.affichage("somme :"); swap(B,C); B.affichage("B"); C.affichage("C");

a.affichage(); b.affichage(); c.affichage(); a.saisie(); b.saisie(); c.saisie(); a.affichage(); b.affichage(); c.affichage(); c.addition(a,b); c.affichage(); d.multiplication(a,b); d.affichage(); swap(c, d); c.affichage(); d.affichage(); }

Page 211: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

SURDEFINITION DES OPERATEURS

1. GENERALITES ET SYNTAXE

■ Méthode opérateur

Tout opérateur du langage C++ étant implémenté sous la forme d'une méthode opérateur, il peut être surdéfini ce qui le généralise aux instances d'une classe

■ Exemple

L'opérateur + peut être surdéfini pour additionner deux nombres complexes.

■ Règles de syntaxe

La surdéfinition d'un opérateur prédéfini du langage est réalisée par une fonction dont la signature est constituée du mot clé operator suivi de son identificateur :

type_resultat operator opérateur_surdéfini(type argument,…)

La définition originelle des opérateurs sur les types de base ne peut être modifiée.

Il est interdit de définir de nouveaux opérateurs et symboles (par exemple **).

La précédence, le nombre d'opérandes (arité), la priorité, les règles d'associativité de l'opérateur restent celles de l'opérateur non surdéfini.

Les opérateurs = [] () et -> doivent être membres de la classe où ils sont surdéfinis.

Un opérateur peut être surdéfini dans une classe ou à l'extérieur, avec une syntaxe d'utilisation différente dans chaque cas. La version membre impose pour des raisons syntaxiques que l'argument de gauche soit une instance de la classe de l'opérateur.

■ Maximes associées à la surdéfinition des opérateurs

Ne jamais surdéfinir un opérateur dans un sens différent dont son sens "intuitif".

La sémantique de la version surdéfinie doit être compatible avec les types de base des opérandes.

■ Opérateurs autorisés

Tous les opérateurs du langage C++ peuvent être surdéfinis à l'exception des opérateurs :: . * ?: sizeof typeid static_cast dynamic_cast const_cast reinterpret_cast selon les règles suivantes :

CHAPITRE IX

Page 212: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

212 CHAPITRE IX ───────────────────────────────────────────────────

arité opérateurs associativité binaire () [] -> gauche à droite unaire + - ++ -- ! ~ * & new new[] delete droite à gauche binaire * / % droite à gauche binaire * -> .* droite à gauche binaire + - droite à gauche binaire << >> droite à gauche binaire < <= >= > droite à gauche binaire == != droite à gauche binaire & droite à gauche binaire ^ droite à gauche binaire || droite à gauche binaire && droite à gauche binaire | droite à gauche binaire = += -= *= /= %= gauche à droite binaire , droite à gauche

2. SURDEFINITON D’UN OPERATEUR NON MEMBRE D'UNE CLASSE

Un des arguments d'un opérateur surdéfini non membre d'une classe doit en être une donnée membre d'accès public.

■ Syntaxe

Type_résultat operator <symbole_associé_à_l'opérateur> (liste_des_paramètres_formels_typés) {corps de la méthode associée à l'opérateur surdéfini}

� Exemple

// Surdéfinition de l'opérateur + pour des instances de la classe complexe #include <iostream.h> class complexe { public : float reel , im; complexe(float,float); }; // Fin de la classe complexe

complexe::complexe(float x=0 , float y = 0) {reel=x; im=y;}

const complexe operator + (const complexe z1, const complexe z2) // Opérateur surdéfini non membre de la classe { complexe aux; aux.reel =z1.reel+z2.reel; aux.im =z1.im+z2.im; return aux; }

int main() { complexe z1(1,10), z2(3,-1), z;

// Les données membres d'accès public sont accessibles par l'opérateur surdéfini. z = z1+z2; cout << "z.reel = " << z.reel << "\tz.im = " << z.im << endl; return (1);

}

// Résultat z.reel = 4 z.im = 9

Page 213: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

SURDEFINITION DES OPERATEURS ───────────────────────────────────────────────────

213

3. SURDEFINITION D’UN OPERATEUR MEMBRE D'UNE CLASSE

■ Définition des opérateurs internes

Une deuxième technique de surdéfinition d'un opérateur consiste à le considérer comme une méthode de la classe sur laquelle il opère. Soient A et B deux opérandes et Opérateur la méthode opérateur surdéfinie. L'instruction

A Opérateur B a pour sémantique l'instruction A.Opérateur(B)

On en déduit que, défini dans une classe, l'opérateur surdéfini comporte toujours un argument de moins (le plus à gauche) que l'opérateur non surdéfini, l'objet qui traite le message étant un paramètre implicite de l'appel.

■ Type de retour

La méthode opérateur retourne l'instance courante (pointeur this) ou une instance temporaire selon les cas.

� Exemple 1

#include <iostream.h> class complexe // Addition surdéfinie pour des complexes { public:

float reel , im; complexe operator + (complexe z) {complexe aux; aux.reel =reel+z.reel; aux.im =im+z.im; return aux; }

};

int main() {complexe z1 = {1,10}, z2 = {3,-1}, z;

z = z1+z2; // En fait z1.+(z2) cout << "z.reel = " << z.reel << "\tz.im = " << z.im << endl ; return (1);

}

� Exemple 2

La méthode surdéfinie est inline, définie à l'extérieur de la classe.

#include <iostream.h> // Surdéfinition de l'opérateur définie à l'extérieur de la classe class complexe { public: float reel; float im;

complexe operator + (complexe z); };

inline complexe complexe::operator + (complexe z) {complexe aux; aux.reel =reel+z.reel; aux.im =im+z.im; return aux; }

int main() {complexe z1 = {1,10}, z2 = {3,-1}, z;

z = z1+z2; cout << "z.reel = " << z.reel << "\tz.im = " << z.im; return (1);

}

Page 214: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

214 CHAPITRE IX ───────────────────────────────────────────────────

4. AMITIE ET LEVEE PARTIELLE DE L'ENCAPSULATION

La surdéfinition d'un opérateur non membre d'une classe nécessite l'accès à certaines de ses données membres privées donc la levée (partielle ou totale) de leur encapsulation.

■ Définition

• Une fonction, une (toutes les) méthode(s) d'une classe peu(ven)t être autorisée(s) à accéder à la partie privée d'une autre classe (donnée ou méthode) si elle y est (sont) déclarée(s) amie(s) par le qualificatif friend .

• Leur syntaxe d'utilisation est inchangée.

• Une fonction peut être amie de plusieurs classes.

• L'amitié n'est pas transitive l'amie de mon amie n'étant par défaut pas mon amie.

■ Synopsis

friend type_retour fonction_amie(liste type et arguments); friend type_retour [classe::]méthode_amie(liste type et arguments); friend class classe_amie;

� Exemple 1

class X { // Définition des entités amies des objets de la classe X

friend void f(int, float); // La fonction f friend void Y::g(char *, int); // La méthode g de la classe Y friend class Z; // Toutes les méthodes de la classe Z

};

� Exemple 2

La méthode mult de la classe matrice est amie de la classe vecteur.

class matrice { vecteur mult(vecteur); // Déclaration ... };

vecteur matrice::mult(vecteur x){…} // Définition

class vecteur { friend vecteur matrice::mult(vecteur); // Déclaration d'amitié dans la classe vecteur

... };

Page 215: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

SURDEFINITION DES OPERATEURS ───────────────────────────────────────────────────

215

5. OPERATEURS RELATIONNELS

Tout opérateur relationnel retourne un type booléen (mot clé bool).

■ Opérations sur les chaînes de caractères

Dans la classe Chaine, les opérateurs de test d'identité et de comparaison de deux chaînes de caractères sont surdéfinis comme suit :

bool Chaine::operator ==(const Chaine &) const ; bool Chaine::operator <(const Chaine &) const ;

■ Définition d’une relation d’ordre

L'opérateur < surdéfini compare les modules de deux nombres complexes.

#include <iostream.h> #include <math.h> class complexe { float reel; float im;

friend complexe operator + (complexe, complexe); friend bool operator <(complexe, complexe); friend float module(complexe);

public : complexe (double, double); // Constructeur void affiche(char *); };

complexe::complexe (double x=0 , double y =0) {reel =x; im=y;}

void complexe::affiche(char * Texte) { cout << Texte << "partie réelle = "<<reel << "\tpartie imaginaire = " <<im<< endl;}

complexe operator + (complexe z1, complexe z2) {complexe aux; aux.reel =z1.reel+z2.reel; aux.im =z1.im+z2.im; return aux; }

bool operator < (complexe z1, complexe z2) { float module(complexe); return(module(z1) < module(z2));}

float module(complexe z) {return(sqrt(z.reel*z.reel+z.im*z.im));}

int main() {complexe z1(1,10), z2(3,-1), z;

z1.affiche("z1 : \t\t"); z2.affiche("z2 : \t\t"); z = z1+z2; z.affiche("z=z1 + z2 : \t"); if (z1 < z2) cout << " |z1| < |z2|"; else cout << "|z2| < |z1|" << endl; return (1);

}

// Résultat z1 : partie réelle = 1 partie imaginaire = 10 z2 : partie réelle = 3 partie imaginaire = -1 z=z1 + z2 partie réelle = 4 partie imaginaire = 9 |z2| < |z1|

Page 216: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

216 CHAPITRE IX ───────────────────────────────────────────────────

� Exemple

// Classe Produit : l'opérateur surdéfini < compare les prix respectifs de deux produits #include <iostream.h> #include <string.h> #include <stdlib.h> class Produit { char * nom; float prix;

public: Produit (const char * Nom, float Valeur) // Constructeur { nom = new char [strlen(Nom)+1];

if (nom == NULL) {cerr << "allocation impossible" << endl ; exit(1); } strcpy (nom, Nom); prix = Valeur;

}

~Produit() {delete nom;} // Destructeur

// Surdéfinition de l'opérateur < bool operator < (const Produit P) const { return (prix < P.prix);}

}; // Fin de la définition de la classe Produit

int main() {Produit P1("SAVON PROMO",7.5);

Produit P2("SAVON MARSEILLE", 9.3); if (P1 < P2) // Inférieur équivalent à "moins cher que" cout << "PROMO BON MARCHE" << endl ; // if (P1 < 5.5) cout << "vraiment pas cher" << endl ; // Erreur de compilation : Illegal structure operation

}

// Résultat PROMO BON MARCHE

■ Remarque

On ne peut dans ce programme comparer une instance de la classe Produit à un nombre décimal le compilateur ne disposant pas de règles implicites de conversion.

6. OPERATEUR DE TRANSTYPAGE

■ Transtypage d'un objet typé vers le type classe

La règle du transtypage d'un objet d'un type donné vers un type classe apporte une solution à ce problème.

■ Syntaxe

Un constructeur de la classe C avec un argument unique d'un type donné T définit une règle de transtypage de l'objet de type T en une instance de la classe C.

Page 217: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

SURDEFINITION DES OPERATEURS ───────────────────────────────────────────────────

217

� Exemple

// Classe Produit : surdéfinition d'opérateurs #include <iostream.h> #include <string.h> #include <stdlib.h> class Produit { char * nom;

float prix; void fixeNom (const char * Chaine) {nom = new char [strlen(Chaine)+1];

if (nom == NULL) {cerr << "allocation impossible" << endl ; exit (1); } strcpy (nom, Chaine);

}

public: // Constructeurs Produit (const char * Nom, float Valeur) {fixeNom (Nom); prix = Valeur; }

Produit (float Montant) // Conversion d'un flottant en un Produit {fixeNom ("PRODUIT TEMOIN"); prix = Montant; }

~Produit() {delete nom; } // Destructeur

// Surdéfinition de l'opérateur < bool operator < (const Produit P) const { return (prix < P.prix);} }; // Fin de la définition de la classe Produit

int main() {Produit P1("SAVON PROMO",0.75);

Produit P2("SAVON MARSEILLE",0.93); if (P1 < P2) cout << "PROMO TRES BON MARCHE" << endl ; // Appel du constructeur Produit(float) if (P1 < .55) cout << "VRAIMENT PAS CHER" << endl ; else cout << "PRIX NORMAL" << endl ; // if (1.05 < P1) cout << "PROMO TROP CHERE" << endl ; // Illegal structure operation if (Produit(1.05) < P1) cout << "PROMO TROP CHERE" << endl ; // OK

}

// Résultat PROMO BON MARCHE PRIX NORMAL

■ Surdéfinition de l'opérateur de transtypage

L'opérateur de transtypage surdéfini permet des conversions entre des classes différentes de même sémantique.

� Exemple

Une chaîne de longueur variable est convertie en une chaîne de longueur fixe (un tableau de caractères) par surdéfinition de l'opérateur char const * (cet opérateur opère sur l'instance qui l'invoque).

Chaine::operator char const * (void) const;

Page 218: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

218 CHAPITRE IX ───────────────────────────────────────────────────

■ Surdéfinition d'opérateur et fonction amie

La syntaxe impose d'utiliser un argument implicite dans la méthode surdéfinie du paragraphe précédent qui compare une instance de la classe Produit à un nombre décimal ce qui interdit l'opération réciproque.

Un opérateur ami, surdéfini, non membre de la classe, n'utilisant que des arguments explicites résout ce problème.

� Exemple

#include <iostream.h> #include <string.h> #include <stdlib.h>

class Produit { char * nom; float prix;

void fixeNom (const char * Chaine) { nom = new char [strlen(Chaine)+1];

if (nom == NULL) {cerr << "allocation impossible" << endl ; exit (1); } strcpy (nom, Chaine);

}

friend bool operator < (const Produit , const Produit) ;

public: // Constructeurs et conversion d'un float en Produit Produit (const char * Nom, float Valeur) {fixeNom (Nom); prix = Valeur; } Produit (float Valeur) {fixeNom ("PRODUIT TEMOIN"); prix = Valeur; }

~Produit() {delete nom;}

};

bool operator < (const Produit P, const Produit Q) {return (P.prix < Q.prix);}

int main() {Produit P1("SAVON PROMO",2.05), P2("SAVON MARSEILLE", 1.63);

if (P2 < P1) cout << "PROMO PAS BON MARCHE" << endl ; if (P2 < 5.5) cout << "P2 PAS CHER" << endl ; if (1.5 < P2) cout << "PROMO TROP CHERE" << endl ; return (1);

}

// Résultat PROMO PAS BON MARCHE P2 PAS CHER PROMO TROP CHERE

Page 219: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

SURDEFINITION DES OPERATEURS ───────────────────────────────────────────────────

219

7. OPERATEUR []

L'opérateur surdéfini [] retourne l'index d'une instance d'un tableau qu’un retour par référence permet de modifier.

� Exemple

Soit une classe de lignes de 80 caractères que le constructeur initialise par un unique caractère donné.

L'opérateur [] surdéfini permet d'accéder à un caractère d'une ligne. Quand il retourne une référence, il permet sa modification.

Version 1 #define TAILLE 80 #include <iostream.h> #include <stdlib.h> class Ligne // Une instance est une ligne de TAILLE caractères { private :

char t[TAILLE+1]; public: // Appel du constructeur Ligne(char C = ' '); // Ligne vide par défaut char & operator [](int Rang) // Position d'un caractère de la ligne {if (Rang < 1 || Rang > TAILLE)

{ cerr << "rang inacceptable pour une position de Ligne" << endl ; exit(1);} return t[Rang-1]; }

};

Ligne::Ligne(char C) {for (int k=0; k<TAILLE; k++) t[k]=C; t[TAILLE]='\0'; }

int main() {Ligne L, La('A');

cout << "La[3] = " << La[3] << endl ; // Modification du 3ième caractère l'opérateur surdéfini retournant une référence. La[3] = 'Z'; cout << "La[3] = " << La[3] << endl ; La[2] = La[3]; // L'affectation non surdéfinie fonctionne. cout << "La[2] = " << La[2] << endl ; return 1;

}

// Résultat La[3] = A La[3] = Z La[2] = Z

Page 220: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

220 CHAPITRE IX ───────────────────────────────────────────────────

Version 2 #define TAILLE 80 #include <iostream.h> #include <stdlib.h> class Ligne // Une instance est une ligne de TAILLE caractères { private : char t[TAILLE+1]; public: Ligne(); // Constructeur par défaut

Ligne(char); // Constructeur général // Position d'un caractère de la ligne rang de 1 à TAILLE char & operator [](int Indice) { cout << "Opérateur surdéfini " << endl;

if (Indice < 1 || Indice > TAILLE) { cerr <<"rang inacceptable pour une position de Ligne"<< endl; exit(1);} return t[Indice-1];

} };

Ligne::Ligne() { cout << "Constructeur par défaut : " << endl; for(int k=0; k < TAILLE; k++) t[k]=' ';t[TAILLE]='\0'; }

Ligne::Ligne(char C) { cout << "Constructeur " << endl;

for (int k=0; k<TAILLE; k++) t[k]=C; t[TAILLE]='\0';

}

int main() { Ligne L, La('A');

cout << "La[3] = " << La[3] << endl ; La[3] = 'Z'; // Modification possible cout << "La[3] = " << La[3] << endl ; // l'opérateur surdéfini retournant une référence. La[2] = La[3]; cout << "La[2] = " << La[2] << endl ; char toto = La[3]; cout << "toto = " << toto << endl; char chaine[TAILLE]; // Opérateur [] non surdéfini chaine[5] = 'x'; cout << "chaine[5]=" << chaine[5] << endl; return 1;

}

// Résultat Constructeur par défaut : Constructeur Opérateur surdéfini La[3] = A Opérateur surdéfini Opérateur surdéfini La[3] = Z Opérateur surdéfini Opérateur surdéfini Opérateur surdéfini La[2] = Z Opérateur surdéfini toto = Z chaine[5]=x

Page 221: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

SURDEFINITION DES OPERATEURS ───────────────────────────────────────────────────

221

8. CONSTRUCTEUR COPIE

Soient A et B deux instances d'une classe. L'exécution de l'instruction

A=B;

nécessite de surdéfinir l'affectation de A par B donc la copie membre à membre de l'instance B dans l'instance A, donc la copie membre à membre de chacun des attributs de l'instance B dans ceux de A.

■ Définition

Le constructeur copie est utilisé implicitement ou explicitement à la création de l'instance copie.

Son utilisation implicite peut provoquer des effets de bord.

� Exemple

#include <iostream.h> #include <string.h> #include <stdlib.h> class Produit { char * nom; float prix;

public: // Constructeur Produit (const char * Nom, float Valeur) {nom = new char [strlen(Nom)+1];

if (nom == NULL) {cerr << "allocation impossible" << endl ; exit (1); } strcpy (nom, Nom); prix = Valeur;

}

~Produit() {delete nom;} // Destructeur

// Un problème causé par le constructeur copie implicite dans l'exécution // de l'opérateur surdéfini de comparaison (transmission par valeur). Deux solutions : // opérateur de comparaison (transmission par référence) ou constructeur copie explicite bool operator < (const Produit &P) const { return (prix < P.prix);}

void AfficheToi(char * Titre) const { cout << Titre << ": " << nom << ", " << prix <<endl ;}

}; // Fin de la classe Produit

int main() { Produit P1("SAVON PROMO",7.5), P2("SAVON MARSEILLE", 9.3);

P2.AfficheToi("P2"); if (P1 < P2) cout << "PROMO BON MARCHE" << endl ; Produit P3("YAOURT",7.6); P3.AfficheToi("P3"); P2.AfficheToi("P2");

}

// Résultat exact obtenu P2: SAVON MARSEILLE, 9.3 PROMO BON MARCHE P3: YAOURT, 7.6 P2: SAVON MARSEILLE, 9.3

// Résultat obtenu sans passage par référence dans l'opérateur de comparaison surdéfini … P2: YAOURT, 9.3

Page 222: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

222 CHAPITRE IX ───────────────────────────────────────────────────

■ Interprétation

Le deuxième résultat d'exécution est faux pour les raisons suivantes :

• Lors de l'appel de l'opérateur surdéfini, la transmission par valeur provoque l'allocation d'une instance temporaire, locale à la fonction, copie de l'instance P2 où le constructeur copie par défaut recopie les pointeurs des attributs prix et nom. Le pointeur local est détruit à la sortie de la fonction appelée.

• Dans la fonction appelante, le pointeur accédant à la dernière donnée membre de P2 est réinitialisé à la fin de l'exécution de la méthode surdéfinie. C'est un bug d'exécution connu du langage.

■ Constructeur copie par défaut, constructeur copie explicite

Le constructeur copie par défaut ne copiant membre à membre que les pointeurs vers les attributs, il est préférable d'éviter son utilisation en le déclarant d'accès privé et de le réécrire en utilisant une transmission par référence.

Synopsis Identificateur_de_la_classe( Identificateur_de_la_classe & argument_formel)

Dans le programme suivant, l'opérateur surdéfini s'exécute sans problème avec des arguments transmis par valeur ou par référence.

� Exemple

#include <iostream.h> #include <string.h> #include <stdlib.h> class Produit { char * nom; float prix;

friend bool operator < (const Produit &, const Produit&);

public: // Constructeur Produit (const char * Nom, float Valeur) {nom = new char [strlen(Nom)+1];

if (nom == (char *) NULL) { cerr << "allocation impossible" << endl ; exit (1); } strcpy (nom, Nom); prix = Valeur;

}

// Constructeur copie Produit (const Produit & Source) { cout << "constructeur copie" << endl; nom = new char [strlen(Source.nom)+1];

if (nom == (char *) NULL) { cerr << "allocation impossible" << endl ; exit (1);} strcpy (nom, Source.nom); prix = Source.prix;

}

~Produit() {delete nom;}

void AfficheToi(char * Titre) const { cout << Titre << ": " << nom << ", " << prix <<endl ;}

}; // Fin de la définition de la classe Produit

Page 223: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

SURDEFINITION DES OPERATEURS ───────────────────────────────────────────────────

223

// Surdéfinition de l'opérateur < bool operator < (const Produit &P, const Produit & Q) { cout << "appel de l'opérateur surdéfini" << endl; return (P.prix < Q.prix);}

int main() {Produit P1("SAVON PROMO",0.75);

P1.AfficheToi("P1"); Produit Double1(P1); // L'instanciation provoque l’appel du constructeur copie Produit P2("SAVON MARSEILLE", 0.93); Produit Double2 = P2; // L'instanciation provoque l’appel du constructeur copie P2.AfficheToi("P2"); if (P1 < P2) cout << "PROMO BON MARCHE" << endl ;

Produit P3("YAOURT",0.76); P3.AfficheToi("P3"); P2.AfficheToi("P2"); Double2.AfficheToi("Double2");

}

// Résultat P1: SAVON PROMO, 0.75 constructeur copie constructeur copie P2: SAVON MARSEILLE, 0.93 appel de l'opérateur surdéfini PROMO BON MARCHE P3: YAOURT, 0.76 P2: SAVON MARSEILLE, 0.93 Double2: SAVON MARSEILLE, 0.93

9. AFFECTATION

L'opérateur d'affectation non surdéfini explicitement entre deux instances d'une même classe effectue l'opération membre à membre ce qui conduit à des problèmes d'allocation mémoire similaires à ceux rencontrés avec le constructeur copie implicite comme l'illustre le programme suivant :

// Opérateur d'affectation surdéfini utilisant le constructeur copie implicite #include <iostream.h> #include <string.h> #include <stdlib.h> class Produit { char * nom; float prix;

public: Produit (const char * Nom, float Valeur = 1) // Constructeur {nom = new char [strlen(Nom)+1];

if (nom == (char *) NULL) { cerr << "allocation impossible" << endl ; exit (1); } strcpy (nom, Nom); prix = Valeur;

}

Produit (const Produit & Source) // Constructeur copie { cout <<"constructeur copie"<<endl;

nom = new char [strlen(Source.nom)+1]; if (nom == (char *) NULL) { cerr << "allocation impossible" << endl ; exit (1); } strcpy (nom, Source.nom); prix = Source.prix;

}

Page 224: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

224 CHAPITRE IX ───────────────────────────────────────────────────

~Produit() {delete nom;}

void AfficheToi(char * Titre) const {cout << Titre << ": " << nom <<", " << prix<<endl; } }; // Fin de la définition de la classe Produit

int main() {Produit P1("YAOURT",0.83), P2("SAVON PROMO",0.75);

Produit P3("CUVETTE",0.23) , P4("DISQUE COMPACT",12); P1.AfficheToi("produit P1"); P2.AfficheToi("produit P2"); P1 = P2; P1.AfficheToi("produit P1 après exécution de P1=P2"); P1 = P3 = P4; P1.AfficheToi("produit P1 après exécution de P1=P3=P4"); P3.AfficheToi("produit P3 après exécution de P1=P3=P4"); Produit P5 = "CAHIER"; P5.AfficheToi("P5"); // Constructeur Produit("CAHIER",1) P5 = "SUCRE 1 KG"; // Constructeur copie par défaut P5.AfficheToi("P5"); // Affectation non surdéfini P6=P5; // Appel du constructeur copie P6.AfficheToi("P6");

} // Fin de la fonction main

// Résultat produit P1: YAOURT, 0.83 produit P2: SAVON PROMO, 0.75 produit P1 après exécution de P1=P2: SAVON PROMO, 0.75 produit P1 après exécution de P1=P3=P4: DISQUE COMPACT, 12 produit P3 après exécution de P1=P3=P4: DISQUE COMPACT, 12 P5: CAHIER, 1 P5: SUCRE 1 KG, 1 constructeur copie P6: SUCRE 1 KG, 1 Segmentation Fault (core dumped) // Selon l'implémentation

■ Surdéfinition de l'opérateur d'affectation

La méthode opérateur d'affectation surdéfinie doit retourner une référence car c'est une Lvaleur. Elle s'écrit dans l'exemple précédent :

Produit & operator = (const Produit & Source) // Surdéfinition de l'opérateur = { // L'allocation mémoire est réinitialisée pour gérer l'accroissement de la chaîne

delete nom; nom = new char [strlen(Source.nom)+1];

if (nom == (char *) NULL) { cerr << "allocation impossible" << endl ; exit (1); } strcpy (nom, Source.nom); prix = Source.prix; return * this; // Pointeur sur l'instance de l'appel.

}

■ Forme canonique d'une classe

La classe idéale est dotée de constructeur et destructeur d'allocation dynamique de mémoire, d'un constructeur copie et d'un opérateur d'affectation surdéfinis.

Page 225: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

SURDEFINITION DES OPERATEURS ───────────────────────────────────────────────────

225

10. INCREMENTATION ET DECREMENTATION

Ces deux opérateurs ont une sémantique différente selon leur utilisation en notation préfixée ou postfixée.

• En notation préfixée,

◊ l'incrémentation de l'objet est effectuée avant l'affectation,

◊ la fonction surdéfinie n'a pas d'argument et retourne une référence sur l'objet.

• En notation postfixée,

◊ l'incrémentation de l'objet est effectuée après l'affectation,

◊ la fonction surdéfinie utilise un argument de type int contenant sa valeur (initiale) d'appel et retourne la valeur (entière) de l'objet non incrémenté, sauvegardée dans une variable locale donc temporaire.

� Exemple

#include <iostream.h> class Entier { public: int i; Entier(int j=0) { i=j;} // Constructeur

Entier operator++(int i) // Surdéfinition de l'opérateur postfixé {Entier tmp(i); ++i; cout << "++ postfixé\t"; return tmp; // Retour de la valeur initiale non incrémentée }

Entier &operator++(void) // Surdéfinition de l'opérateur préfixé { ++i;

"++ préfixé\t" ; return *this;

} }; // Fin de la classe Entier

int main(void) {Entier a(1), c;

cout << "\ta.i = " << a.i << "\tc.i = " << c.i << endl; c=a++; // c = a; a++; cout << "a.i = " << a.i << " c.i = " << c.i << endl; c=++a; // a++ ; c = a; cout << "a.i = " << a.i << " c.i = " << c.i << endl;

}

// Résultat a.i = 1 c.i = 0 ++ postfixé a.i = 2 c.i = 1 ++ préfixé a.i = 3 c.i = 3

Page 226: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

226 CHAPITRE IX ───────────────────────────────────────────────────

11. GESTION DYNAMIQUE DE LA MEMOIRE

■ Allocation

Les opérateurs d'allocation dynamique de mémoire new et new[] peuvent être surdéfinis et opérer sur un nombre variable d'arguments dont le premier définit l'adresse initiale de la zone allouée.

■ Restitution

Les opérateurs surdéfinis conjugués delete et delete[] peuvent opérer sur un ou deux arguments, le premier étant un pointeur générique (void *) sur l'objet à détruire, le deuxième quand il existe, de type size_t, définissant la taille de la mémoire à restituer.

■ Cas des tableaux

La taille d'un tableau doit être stockée avec ce dernier, souvent dans son en-tête ce qui rend impossible toute hypothèse sur la structure de la mémoire allouée. L'utilisation de l'opérateur delete[] avec comme unique argument l'adresse du tableau nécessite la mémorisation de sa taille dans le programme. Dans ce cas, le compilateur transmet à l'opérateur new[] la taille d'un objet de base multipliée par leur nombre.

� Exemple : détermination de la taille de l'en-tête des tableaux

#include <iostream.h> #define TAILLE 512 // Surdéfinition des operateurs d'allocation dynamique new[] et delete[] int tampon[TAILLE]; // Tampon de stockage du tableau. class Temp { char i[17]; // Indice nombre premier. public: static void * operator new[](size_t taille) // Opérateur new[] surdéfini {cout << "operateur new[] surdéfini" << endl; return tampon;}

static void operator delete[](void *p, size_t taille) // Opérateur delete surdéfini { cout << "Taille de l'en-tete : " << taille-(taille/sizeof(Temp))*sizeof(Temp)<< endl; return ; } };

int main(void) { delete[] new Temp[10]; return 0; }

// résultat operateur new[] surdéfini Taille de l'en-tete : 4

■ Remarque

L'instance courante ne peut être transmise aux opérateurs de gestion dynamique de la mémoire n'étant pas encore créée ou est déjà détruite lorsqu'ils s'exécutent.

Page 227: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

SURDEFINITION DES OPERATEURS ───────────────────────────────────────────────────

227

12. CLASSE ENCAPSULEE OU IMBRIQUEE

■ Définition

Un objet d'une classe C peut référencer un objet d'une autre classe interne (classe encapsulée) ou externe (classe imbriquée).

■ Objets d'une classe externe

• Il est préférable de déclarer la classe imbriquée à l'extérieur et de définir les accès (privés, publics, amicaux) de ses données membres.

• Une donnée membre d'une classe imbriquée est par défaut hors de la portée de la classe englobante le contrôle d'accès s'y appliquant normalement.

• La liste des arguments imbriqués d'une méthode externe est introduite par le délimiteur : . L'ordre dans la liste est sans incidence.

• Les arguments des constructeurs des instances des classes membres sont spécifiés dans la définition du constructeur de la classe externe, pas dans les déclarations.

• Le constructeur d'une instance de la classe externe est appelé après ceux des instances membres, dans l'ordre de définition dans la classe. L'ordre inverse est appliqué à la destruction.

• Il peut être parfois nécessaire de définir un constructeur pour la classe externe dont le rôle est seulement d'assurer la transmission des arguments aux classes membres.

� Exemple

Dans la classe Interne, on définit un constructeur appelant une méthode d'affichage de l'attribut interne.

Dans la classe Externe, on définit une donnée membre locale et deux données imbriquées définies dans la classe Interne.

Le constructeur de la classe Externe appele celui de la classe Interne et une méthode d'affichage de l'attribut interne.

Classe C private

public

Imbriquée2

Imbriquée1

Page 228: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

228 CHAPITRE IX ───────────────────────────────────────────────────

Externe afficher() int Externe_a Externe(int) Interne Interne_a, Interne_b

privatepublic

Interne afficher() int x Interne(int)

#include <iostream.h> class Interne { int x; public: Interne(int x) {Interne::x=x;afficher();} // Classe Interne : constructeur

void afficher() {cout << "classe Interne = " << x << endl ;} };

class Externe { int Externe_a; // Donnée membre définie dans la classe Interne Interne_b, Interne_c; // Données membres imbriquées public: // Prototypes Externe(int); // Constructeur de la classe Externe void afficher(); };

// Le constructeur de la classe Externe appelle celui de la classe Interne. // Interne_b, Interne_c sont initialisés par le constructeur Interne()

Externe::Externe(int v) : Interne_b(102), Interne_c(v/2)

// Initialisation de la donnée membre Externe_a {Externe_a=v; afficher();}

void Externe::afficher() { cout << "classe Externe = " << Externe_a <<endl ; }

int main() { Externe e(17);

Interne f(-10); // Transtypage fonctionnel Externe g(28);

}

// Résultat classe Interne = 102 classe Interne = 8 classe Externe = 17 classe Interne = -10 classe Interne = 102 classe Interne = 14 classe Externe = 28

Page 229: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

SURDEFINITION DES OPERATEURS ───────────────────────────────────────────────────

229

13. DEREFERENCIATION, REFERENCE, SELECTION DE MEMBRE

L'opérateur de déréférenciation * permet de définir des classes d'objets sur lesquels des pointeurs peuvent opérer.

L'opérateur de référence & retourne une référence à l'objet sur lequel il opère.

L'opérateur de sélection de membre -> permet d'accéder à une classe encapsulée dans une autre. Il retourne un objet d'un type sur lequel il doit pouvoir à nouveau être appliqué (pointeur sur une variable structurée, union ou classe, etc.).

� Exemple

#include <iostream.h> class Encapsulee {public : int i;} objet; class Surcharge_operateurs // Classe Surcharge_operateurs des opérateurs surdéfinis { public : Encapsulee * operator -> (void) const { return &objet;} Encapsulee * operator & (void) const { return &objet;} Encapsulee & operator * (void) const { return objet;} };

void f(int i) {Surcharge_operateurs entree; entree->i=20; // Enregistre 20 dans objet.i cout << "i = " << i <<"\n" << " entree->i=" <<entree->i << endl; (*entree).i = 30; // Enregistre 30 dans objet.i. cout << " (*entree).i=" <<(*entree).i << endl; Encapsulee *p = &entree; p->i = 40; // Enregistre 40 dans objet.i. cout << " p->i=" <<p->i << endl; return ; }

int main() {void f(int); int a=1, b=200; f(a); f(b); } // Résultat i = 1 entree->i=20 (*entree).i=30 p->i=40 i = 200 entree->i=20 (*entree).i=30 p->i=40

Page 230: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

230 CHAPITRE IX ───────────────────────────────────────────────────

14. OPERATEUR FONCTIONNEL

L'opérateur fonctionnel () peut être surdéfini ce qui est pratique vu de sa n-arité. Ainsi, il permet d'écrire matrice(i,j,k) ou matrice(i,j,k,l,m).

� Exemple

// surdéfinition de l’opérateur fonctionnel #include <iostream.h> #include <stdlib.h> class Complexe { float reel, imaginaire; public : Complexe(float x=0, float y=0) {reel = x; imaginaire = y;}

operator float (void) // Retourne la partir réelle d'un nombre complexe. {cout << "appel de ()" << endl; return reel;} };

int main() {Complexe z(2,3); cout << (z) << endl; }

// Résultat appel de () 2

Page 231: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

SURDEFINITION DES OPERATEURS ───────────────────────────────────────────────────

231

15. EXERCICES

� Exercice 1

Analyser le programme suivant :

#include <iostream.h> #define TAILLE 10 class Complexes { float reel , imaginaire;

public: Complexes(double x=0, double y=0){reel = x; imaginaire =y;} void affiche(char *), affiche(int), saisie(char *), init( int);

};

void Complexes::affiche(char * chaine) { cout << chaine << " : partie reelle:" << reel << " partie imaginaire:" << imaginaire << endl; }

void Complexes::affiche(int k) { cout << "Z[" << k << "]" << "\t"; cout << "partie reelle :" << reel << " partie imaginaire : " << this->imaginaire << endl; }

void Complexes::saisie(char * chaine) { cout << chaine; cout << " : saisir la partie reelle :" ; cin >> this->reel; cout << endl << "saisir la partie imaginaire : "; cin >> imaginaire; }

void Complexes::init(int i) {reel=(float)i; imaginaire = -(float)i;}

typedef Complexes Complexe ;

int main() // Initialisation par appel du constructeur et transtypage fonctionnel { Complexe z1(1.5, -10.78), z2= Complexe(-1,3), z3 = Complexe(), z4 = Complexe(-5);

Complexe z5(0,-5), z6, Z[TAILLE]; z1.affiche("z1"); z2.affiche("z2"); z3.affiche("z3"); z4.affiche("z4"); z5.affiche("z5"); z6.saisie("z6"); z6.affiche("z6"); for (int i = 0; i < TAILLE ; i++) {Z[i].affiche(i); Z[i].ini t(i); Z[i].affiche(i);}

}

// Résultat z1 : partie reelle :1.5 partie imaginaire : -10.78 z2 : partie reelle :-1 partie imaginaire : 3 z3 : partie reelle :0 partie imaginaire : 0 z4 : partie reelle :-5 partie imaginaire : 0 z5 : partie reelle :0 partie imaginaire : -5 z6 saisir la partie reelle : 5 saisir la partie imaginaire : 6 z6 : partie reelle :5 partie imaginaire : 6 Nombre complexe Z[0] partie reelle :0 partie imaginaire : 0 Nombre complexe Z[0] partie reelle :0 partie imaginaire : -0 ... Nombre complexe Z[9] partie reelle :0 partie imaginaire : 0 Nombre complexe Z[9] partie reelle :9 partie imaginaire : -9

Page 232: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

232 CHAPITRE IX ───────────────────────────────────────────────────

� Exercice 2 : classes imbriquées

Analyser le programme suivant :

#include <iostream.h> class C { public :

class IMBRIQUEE1 { public : int a; IMBRIQUEE1(int init=0){a=init;} void imprime(char * chaine) {cout << chaine << " Imbriquee 1 : a = " << a << endl;} } z1;

class IMBRIQUEE2 { public : int b; IMBRIQUEE1 f(IMBRIQUEE2); IMBRIQUEE2(int init=3) {b=init;} } z2;

IMBRIQUEE1 C::f(IMBRIQUEE2 z) {IMBRIQUEE1 A;

A.a=z.b; A.imprime(" f : " ); return (A);

}

void imprime(char * chaine, IMBRIQUEE1 z1, IMBRIQUEE2 z2) {z1=f(z2); cout << chaine << endl << "z1.a : " << z1.a << " z2.b : " << z2.b << endl;} };

int main() {C::IMBRIQUEE1 A,D,C::f(IMBRIQUEE2); C x ; C::IMBRIQUEE2 B; x.imprime("x", A, B); A.imprime("A :"); (D=A).imprime("D"); return 1; }

Page 233: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

SURDEFINITION DES OPERATEURS ───────────────────────────────────────────────────

233

� Exercice 3 : variante de l’exercice 2

Analyser le programme suivant :

#include <iostream.h> class IMBRIQUEE1 { public : int a; IMBRIQUEE1(int init=0){a=init;} void imprime(char * chaine) {cout << chaine << " Imbriquee 1 : a = " << a << endl;} };

class IMBRIQUEE2 { public : int b; IMBRIQUEE2 f(IMBRIQUEE2); IMBRIQUEE2(int init=3) {b=init;} };

class C { public :

IMBRIQUEE1 z1; IMBRIQUEE2 z2; IMBRIQUEE1 f(IMBRIQUEE2); void imprime(char * chaine, IMBRIQUEE1 z1, IMBRIQUEE2 z2) {z1=f(z2); cout << chaine << endl << "z1.a : " << z1.a << " z2.b : " << z2.b << endl;}

};

IMBRIQUEE1 C::f(IMBRIQUEE2 z) { IMBRIQUEE1 A; A.a=z.b; A.imprime(" f : " ); return (A); }

int main() { IMBRIQUEE1 A, D, C::f(IMBRIQUEE2);

C x; IMBRIQUEE2 B; x.imprime("x", A, B); A.imprime("A :"); (D=A).imprime("D"); return 1;

}

// Résultat f : Imbriquee 1 : a = 3 x z1.a : 3 z2.b : 3 A : Imbriquee 1 : a = 0 D Imbriquee 1 : a = 0

Page 234: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

234 CHAPITRE IX ───────────────────────────────────────────────────

� Exercice 4 : opérations matricielles

On considère les matrices de n lignes et m colonnes. Créer une classe de matrices avec constructeur, destructeur, opérateurs mathématiques usuels d’addition, de soustraction, de multiplication, d’affectation de matrices et de l’opérateur fonctionnel.

#include <iostream.h> class matrice { typedef double * ligne; ligne *lignes;

unsigned short int n, m; // Nombre de lignes et de colonnes.

public: // Constructeurs et destructeurs matrice(unsigned short int, unsigned short int); matrice(const matrice &); ~matrice(void);

// Opérateurs surdéfinis matrice & operator =(const matrice &); double & operator () (unsigned short int , unsigned short int); matrice operator +(const matrice &) const; matrice operator -(const matrice &) const; matrice operator *(const matrice &) const;

};

// Constructeur matrice::matrice(unsigned short int nl, unsigned short int nc) { n = nl; m = nc; lignes = new ligne[n];

for (unsigned short int i=0; i<n; i++)lignes[i] = new double [m]; }

// Constructeur copie matrice::matrice(const matrice &source) { m = source.m; n = source.n; lignes = new ligne[n];

for (unsigned short int i=0; i<n; i++) { lignes[i] = new double [m];

for (unsigned short int j=0; j<m; j++)lignes[i][j] = source.lignes[i][j]; }

}

// Destructeur matrice::~matrice(void) { unsigned short int i; for (i=0; i<n; i++) delete[] lignes[i]; delete[] lignes; }

// Surdéfinition de l’opérateur d’affectation matrice &matrice::operator =(const matrice &source) { if (source.n!=n || source.m!=m) // Vérification des dimensions.

{ unsigned short int i;for (i=0; i<n; i++) delete[] lignes[i]; delete[] lignes; // Détruit m = source.m; n = source.n; lignes = new ligne[n]; // Et réalloue. for (i=0; i<n; i++) lignes[i] = new double [m];

}

for (unsigned short int i=0; i<n; i++) // Copie for (unsigned short int j=0; j<m; j++) lignes[i][j] = source.lignes[i][j];

}

Page 235: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

SURDEFINITION DES OPERATEURS ───────────────────────────────────────────────────

235

// Surdéfinition de l'opérateur () double &matrice::operator () (unsigned short int i , unsigned short int j) { return lignes[i][j]; }

// Addition de matrices matrice matrice::operator +(const matrice &m1) const { matrice tmp(n,m);

for (unsigned short int i=0; i<n; i++) for (unsigned short int j=0; j<m; j++) tmp.lignes[i][j] = lignes[i][j]+m1.lignes[i][j]; return tmp;

}

// Soustraction de matrices matrice matrice::operator -(const matrice &m1) const { matrice tmp(n,m);

for (unsigned short int i=0; i<n; i++) for (unsigned short int j=0; j<m; j++) tmp.lignes[i][j]=lignes[i][j]-m1.lignes[i][j];

return tmp; }

// Multiplication de matrices matrice matrice::operator *(const matrice &m1) const { matrice tmp(n,m1.m);

for (unsigned short int i=0; i<n; i++) for (unsigned short int j=0; j<m1.m; j++) { tmp.lignes[i][j]=0.; // Produit scalaire.

for (unsigned short int k=0; k<m; k++) tmp.lignes[i][j] += lignes[i][k]*m1.lignes[k][j]; }

return tmp; }

void imprimer(matrice m, const char * chaine, const int ligne, const int colonne) { cout << "matrice " << chaine << endl;

for(int i = 0; i < ligne; i++) { for(int j =0; j < colonne ; j++) cout << m(i,j) << " " ; cout << endl; }

}

int main() { int ligne, colonne;

cout << "lignes : " ; cin >> ligne ; cout << endl; cout << "colonnes : "; cin >> colonne ; cout << endl;

matrice m(ligne,colonne), n(ligne,colonne), p(ligne,colonne); for(int i=0;i<ligne;i++) for(int j=0;j<colonne;j++) m(i,j)=n(i,j)=i+j;

imprimer(m,"m , n",ligne,colonne); imprimer(m-n,"m-n",ligne,colonne); imprimer (m+n,"m+n",ligne,colonne); imprimer(m*n,"m*n",ligne,colonne);

}

Page 236: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

236 CHAPITRE IX ───────────────────────────────────────────────────

� Exercice 5 : classes imbriquées et surdéfinition

On considère des vecteurs et des matrices.

1°) Définir une classe Matrice et une classe Vecteur dotées des constructeurs adéquats. Le constructeur de la classe Matrice appelle celui de la classe Vecteur.

2°) Ecrire un opérateur surdéfini de multiplication qui permette :

• de multiplier un vecteur par un scalaire

• de multiplier deux vecteurs entre eux.

• de multiplier une matrice par un scalaire.

• de multiplier une matrice par un vecteur ou un vecteur par une matrice.

• de multiplier deux matrice s entre elles.

// Corrigé partiel #include <iostream.h> class Vecteur { public: float *x; unsigned int Taille; Vecteur(){}

Vecteur(unsigned int a) { if(a!=0) { x= new float[a]; for(unsigned int i=0; i<a;i++) *(x+i)=0;

Taille = a; } else cout << "Problème de taille (0 non permis)"<< endl; }

Vecteur operator * (float a) { Vecteur aux(Taille); for(unsigned int i=0;i<Taille;i++) aux.x[i]=a*x[i]; return aux; }

Vecteur operator * (Vecteur A) { Vecteur aux(Taille); for(unsigned int i=0;i<Taille;i++) aux.x[i]=x[i]*(A.x[i]); return aux; } };

class Matrice: public Vecteur { public:

Vecteur *M; unsigned int nbLignes, nbCol; Matrice(unsigned int a,unsigned int b) { if ((a!=0) && (b!=0)) {M= new Vecteur[b]; for(unsigned int i=0;i<b;i++) *(M+i)=Vecteur(a);}

else cout << "Problème de taille" << endl; nbLignes = a; nbCol = b;

}

Page 237: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

SURDEFINITION DES OPERATEURS ───────────────────────────────────────────────────

237

Matrice operator * (Vecteur A) { Matrice aux(A.Taille,1);

if (A.Taille == nbCol) { unsigned int i,j;

for (i=0;i<nbLignes;i++) { float temp=0;

for (j=0;j<nbCol;j++) temp+=M[j].x[i]*A.x[j]; aux.M[0].x[i]=temp; return aux;

} }

else { cout << "Multiplication impossible : taille incompatible !\n"; return aux; } }

};

#define LIGNES 3 #define COLONNES 5

int main() { int i,j,l;

Vecteur A(COLONNES ) , B(COLONNES ), C(COLONNES );

for(i=0; i<COLONNES ;i++) { A.x[i]=(float)i+1; B.x[i]=(float)10*i-2; } cout << "A" << " B" << endl;

for(i=0;i<COLONNES ;i++) cout << A.x[i] << " " << B.x[i] << endl; cout << endl; cout << "A*B" << endl;

for(i=0;i<COLONNES ;i++) cout << (A*B).x[i] << endl; cout << endl; cout << "10*A" << endl;

for(i=0;i<COLONNES ;i++) cout << (A*10).x[i] << endl; cout << endl;

Matrice M(LIGNES,COLONNES); // L'écriture n'est pas intuitive puisque les lignes viennent APRES les colonnes... M.M[0].x[0] = 2; M.M[1].x[0] = -2; M.M[2].x[0] = 8; M.M[0].x[1] = 1; M.M[1].x[1] = -15; M.M[2].x[1] = 24; M.M[0].x[2] = 10; M.M[1].x[2] = -5; M.M[2].x[2] = 1;

for(i=0; i< COLONNES;i++) C.x[i]=2+(float)i;

cout << "Matrice M: \n"; for(i=0;i<LIGNES;i++) { for(j=0;j<COLONNES;j++) cout << (M.M[j]).x[i] << " "; cout << endl;} cout << endl;

cout << "Vecteur C : " << endl; for(i=0;i< COLONNES; i++) cout << C.x[i] << endl; cout << endl;

cout << "M*C = \n"; for(i=0;i<LIGNES;i++) {cout << ((M*C).M[0]).x[i]; cout << endl; } return 1;

}

Page 238: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

238 CHAPITRE IX ───────────────────────────────────────────────────

Exercice 6 : surdéfinition de l’opérateur d'affectation

Surdéfinir l'opérateur d'affectation dans une classe de nombres complexes.

#include <iostream.h> class Complexes { float reel, imaginaire; public : Complexes & operator = (const Complexes &z); Complexes(float x=0, float y=0) {reel=x; imaginaire=y;}

void afficher() {cout << reel << " " << imaginaire << endl;}

};

Complexes & Complexes::operator =(const Complexes & z) { reel=z.reel; imaginaire=z.imaginaire;}

typedef Complexes Complexe ;

int main() {Complexe z1, z2(2,3); z1=z2; z2.afficher();}

// Résultats 2 3

� Exercice 7

Reprendre les classes de nombres entiers, réels, complexes pour y intégrer les méthodes suivantes :

1°) Surdéfinition de l'addition et de la multiplication de 2 nombres de même nature.

2°) Instanciation d'un tableau de nombres. On vérifiera que les constructeurs sont utilisés à l'instanciation.

3°) Surdéfinir l'opérateur de multiplication pour effectuer la multiplication d'un complexe par un nombre entier ou flottant.

4°) Essayer de surdéfinir les opérateurs d'addition et d'affectation pour qu'ils opèrent sur des tableaux d'instances et constater que c'est difficile.

5°) Surdéfinir l'opérateur < pour comparer les modules de deux nombres (même type ou type différent (entier, réel, complexe)).

Page 239: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

L'HERITAGE EN LANGAGE C++

1. DEFINITIONS

L'héritage permet de transmettre à une classe dérivée (fille, descendante) certaines propriétés d'une classe de base (mère, antécédente). Mathématiquement, c'est une relation d'inclusion, partielle ou totale entre classes.

Les propriétés des objets dérivés sont définies à partir de leur qualification d'accès de base et de la qualification d'héritage.

L'héritage d'une classe est simple et le graphe de la hiérarchie de classes un arbre. Plusieurs classes mères définissent un héritage multiple dont la représentation est un graphe sans cycle.

■ Syntaxe

Une classe dérivée est définie à partir de ses classes mères et de leur qualification d'héritage selon la syntaxe (identique avec les mots clés struct et union) suivante :

Classe_dérivée : [qualification_d_héritage] classe(s)_de_base {définition de la classe dérivée}

� Exemple 1

class C_mere1 {/* Classe mère 1 */ };[class C_mere2 {/* Classe mère 2 */ };][…]

class C_fille : public | protected | private C_mere1[, public | protected | private C_mere2 …] {/* Définition de la classe fille */};

� Exemple 2

Définir une classe de base avec deux données publiques B_a, B_b, une méthode publique d'addition int B_plus(int, int), une classe dérivée avec un champ D_c entier, une méthode D_plus d'addition du champ D_c aux champs dérivés qui appelle B_plus().

Définir une fonction f externe qui appelle les méthodes B_plus() et D_plus().

Basepublicint B_a, B_b;int B_plus();

Dérivéepublic :int D_c;int D_plus();

CHAPITRE X

Page 240: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

240 CHAPITRE X ───────────────────────────────────────────────────

#include <iostream.h> class Base { public:

int B_a, B_b; int B_plus() {return B_a+B_b;}

}; // Fin classe Base

class Derivee : public Base { public:

int D_c; int D_plus() {return D_c+B_plus();}

}; // Fin classe Dérivée

int f() { Derivee d; // Initialisation

d.B_a=d.B_b=d.D_c=2; return d.D_plus()*d.B_plus(); // Accès aux méthodes dérivées et de base

}

int main() { cout << "f=" << f() << endl ;}

// Résultat f()=24

■ Redéfinition de méthode dans une classe dérivée

Une méthode de base peut être redéfinie (overriding) dans la classe dérivée.

� Exemple

#include <iostream.h> class Base { public:

int B_a, B_b; int plus() {return B_a+B_b;}

};

class Derivee : public Base { public:

int D_a; int plus(Base B_z) {return D_a+B_z.plus();}

}; // int plus() {return a+plus();} provoquerait une récursivité infinie. int main() { int plus(Base); // Prototype classe dérivée

int plus(); // Prototype classe de base Base B_x; B_x.B_a=2; B_x.B_b = 3; cout << "B_x.plus =" << B_x.plus() << endl ; Derivee D_y; D_y.D_a =15; cout << "D_y.plus=" << D_y.plus(B_x) << endl ; return 1;

}

// Résultat B_x.plus =5 D_y.plus=20

Page 241: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

L'HERITAGE EN LANGAGE C++ ───────────────────────────────────────────────────

241

2. QUALIFICATIONS D'ACCES AUX OBJETS D'UNE CLASSE

Les accès aux membres d'une classe sont spécifiés par une qualification d'accès.

• Les objets d'accès public, qualifiés public, sont accessibles à tous.

• Les objets d'accès protégé, qualifiés protected, sont inaccessibles aux utilisateurs, accessibles aux développeurs de la classe et des classes dérivées.

• Les objets d'accès privé, qualifiés private, sont accessibles exclusivement au développeur de la classe.

Les différentes définitions peuvent être morcelées et désordonnées.

La qualification d'héritage affine les propriétés des objets dérivés.

3. QUALIFICATION D'HERITAGE

3.1 Qualifications d'héritage des objets dérivés

■ Définition

L'héritage public, private ou protected définit les règles d'accès des objets dérivés.

■ Synopsis

class B {...}; // Classe de base class Dl : public B {...}; // Héritage qualifié public class D2 : private B {...}; // Héritage qualifié privé class D3 : protected B {...}; // Héritage qualifié protégé class D4 : B {...}; // Héritage qualifié private par défaut struct D5 : B {...}; // Héritage qualifié public par défaut

public

private

protected

public(défaut)

private(défaut)

Base

D1

D2 D3D4

D5

3.2 Sémantique d'accès aux objets de base et dérivé s L'appartenance d'un objet à une classe implémente le concept HAS_A.

L'héritage public implémente le concept IS_A : tout objet dérivé est objet de base.

L'héritage privé ou protégé implémente le concept IS_A_KIND_OF : tout objet dérivé est une "sorte d'objet" de base presque pareil mais pas tout à fait identique ".

L'amitié est non transmissible par héritage et doit donc toujours être explicitée.

Page 242: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

242 CHAPITRE X ───────────────────────────────────────────────────

■ Héritage qualifié public

• Les objets protégés et publics de la classe de base le restent dans la classe dérivée, y sont accessibles par ses fonctions membres et amies et sont dérivables.

• Les objets privés ne sont pas héritables.

Toute instance dérivée étant constituée des attributs publics de sa classe de base nécessite sa construction donc l'appel d'un constructeur de la classe mère.

Le transtypage implicite de la classe dérivée vers la classe de base est autorisé.

■ Héritage qualifié private

• Les objets protégés et publics de la classe de base sont privés dans la classe dérivée ne sont accessibles que par ses fonctions membres et amies, ne sont plus dérivables.

• Les objets privés ne sont pas héritables.

objetsprotectedpublic

objet private

objets private

Héritage private

Classe dérivée

Classe de base

Le transtypage implicite de la classe dérivée vers la classe de base ou réciproquement est interdit.

■ Héritage qualifié protected

• Les membres dérivés d'objets protégés ou publics sont protégés et dérivables.

• Les objets privés ne sont pas héritables.

objet objet public private protected

objet public protected

Héritage public

Classe de base

Classe dérivée

Page 243: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

L'HERITAGE EN LANGAGE C++ ───────────────────────────────────────────────────

243

objetsprotectedpublic

objetsprotected

objetsprivate

Héritage protected

Classe dérivée

Classe de base

Le transtypage implicite de la classe dérivée vers la classe de base ou réciproquement est interdit.

accès de base qualif. d'héritage public protected private

public public protected private protected protected protected private private interdit interdit interdit

Tableau récapitulatif des qualifications d'accès des membres hérités

� Exemple

#include <iostream.h> class Base { public:

int Base_a; Base(): Base_a(0) {} // Constructeur par défaut Base(int A): Base_a(A) {} // Autre constructeur

};

class Derivee : public Base { public:

int Derivee_b; Derivee() : Derivee_b(0) {} // 2 constructeurs de la classe dérivée Derivee(int i, int j): Base(i), Derivee_b(j) {}

};

int main() { Base j, k(3);

cout << "j.Base_a = " << j.Base_a << " k.Base_a = " << k.Base_a ;

Derivee D, E(2,3); cout << "D.Base_a = " << D.Base_a <<" D.Derivee_b = " << D.Derivee_b ; cout << "E.Base_a = " << E.Base_a << " E.Derivee_b = " << E.Derivee_b << endl ;

}

// Résultat j.Base_a = 0 k.Base_a = 3 D.Base_a = 0 D.Derivee_b = 0 E.Base_a = 2 E.Derivee_b = 3

■ Requalification des qualifications d'accès dans les classes dérivées

Un objet dérivé peut être rendu public même si l'héritage est privé ou protégé par une redéfinition de la méthode de base dans la classe dérivée ou une requalification, selon les droits autorisés, de l'accès aux objets concernés.

Page 244: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

244 CHAPITRE X ───────────────────────────────────────────────────

� Exemple

class list { public: void add(int), remove(int), print(); };

class linkedlist : private list // Héritage qualifié privé { protected : list::remove; // La méthode (private) remove requalifiée protected

public: list::add; // La méthode (private) add requalifiée public }; // La méthode print reste qualifiée private

class listpublic :void add(int;void remove(int), print();

Héritage private

linkedlistpublic : void add(int) // Requalifiéeprotected : void remove(int) // Requalifiéeprivate : print() // Défaut

■ Limites à la requalification

La requalification des droits permet d'assouplir les qualifications d'accès de certains membres de la classe dérivée des effets d'une dérivation privée ou protégée. La dispense ne porte que sur l'identificateur associé.

La requalification des droits d'accès ne peut pas être utilisé pour :

• Elargir les droits de la classe de base : privée vers protégée, protégée vers public,

• Restreindre les droits définis dans la classe de base en cas de dérivation public.

• Il est interdit de requalifier les fonctions et opérateurs surdéfinis.

� Exercice 1

La classe ProduitPerissable, dérivée de la classe Produit a les propriétés suivantes :

• Un produit périssable à une durée de vie limitée et son constructeur appelle celui de la classe de base.

• La méthode d'affichage dans la classe dérivée est redéfinie.

• Une méthode spécifique permettant d'afficher le prix et la durée, spécifique à la classe dérivée, y est également définie.

Page 245: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

L'HERITAGE EN LANGAGE C++ ───────────────────────────────────────────────────

245

#include <iostream.h> #include <string.h> #include <stdlib.h> const int NbMaxCarac = 25;

class Produit // Version simplifiée pour l'introduction de l'héritage { protected : char nom[NbMaxCarac+1]; float prix; void fixeNom (const char * Texte);

public: Produit (char * Nom, float Prix) {fixeNom(Nom); prix = Prix;}

void AfficheToi() { cout << "Produit " << nom <<"\tprix: " << prix << endl;}

}; // Fin de la définition de la classe Produit

void Produit::fixeNom (const char * Texte) {strncpy (nom, Texte, NbMaxCarac); nom[NbMaxCarac] = '\0'; }

class ProduitPerissable : private Produit // Dérivation qualifiée private { private : int nombreDeJours; // Durée de conservation

public: ProduitPerissable (char * Nom, int Duree, float Prix) : Produit (Nom, Prix) {nombreDeJours = Duree;}

void AfficheToi() // Redéfinition de la fonction membre AfficheToi { Produit::AfficheToi(); // La fonction membre de la classe de base reste utilisable

cout << "\tvalidité: " << nombreDeJours << " jours" << endl ; }

void PrixEtDuree() // Spécifique à la classe dérivée { cout << prix <<"€, " << nombreDeJours << " jours" << endl ;}

}; // Fin de la définition de la classe ProduitPerissable

int main() { Produit P1("SAVON",7.5);

P1.AfficheToi(); ProduitPerissable P2("YAOURT", 15, 12.5); P2.AfficheToi(); // P2 a un comportement d'affichage propre à sa classe P2.PrixEtDuree();

return 1; } // Fin de main

// Résultat Produit SAVON prix: 7.5 Produit YAOURT prix: 12.5 validité: 15 jours 12.5€, 15 jours

Page 246: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

246 CHAPITRE X ───────────────────────────────────────────────────

� Exercice 2 : l'héritage multiple

1°) Définir une classe fille des classes Mère1 et Mère2 dont le constructeur appelle celui de ses classes mères, une classe petite fille dont le constructeur appelle celui de sa mère.

Mère1 Mère2

affiche()

Petite fille

m1 m2affiche() affiche()

Fille

2°) La classe Mere1 est la classe Reels des réels, Mere2 celle des imaginaires purs Imaginaires et la classe Complexe en dérive. Instancier, saisir, afficher un nombre complexe à partir des méthodes redéfinies dans la classe Complexe. Y surdéfinir l'opérateur d'addition pour additionner des complexes, réels ou imaginaires purs.

#include <iostream.h> class mere1 { int m1;

public: mere1(int i =0) {cout << "constructeur mère 1 "<< endl; m1=i;} void affiche() {cout<< "m1=" << m1 ;}

};

class mere2 { int m2;

public: mere2(int i=0) {m2=i;} void affiche() {cout<< "m2=" << m2<< endl;}

};

class fille : mere1, mere2 { public :

fille( int i1=0, int i2=0): mere1(i1),mere2(i2) { cout << "constructeur fille" << endl; } void affiche() { mere1::affiche(); mere2::affiche(); }

};

class petitefille : fille { public: petitefille(int i1=0, int i2=0): fille (i1,i2){cout <<"constructeur petitefille" << endl;}

fille::affiche; };

int main() {fille f(2,3); f.affiche(); petitefille pf(4,5) ; pf.affiche();}

// Résultat constructeur mère 1 constructeur fille m1=2 m2=3 constructeur mère 1 constructeur fille constructeur petitefille m1=4 m2=5

Page 247: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

L'HERITAGE EN LANGAGE C++ ───────────────────────────────────────────────────

247

■ Réponse à la 2ième question

#include <iostream.h> class Reels { public:

float Reel; Reels(float i=0) {cout << "constructeur Reel "<< endl; Reel=i;} void affiche() {cout<< "Reel=" << Reel << endl;}

};

class Imaginaires { public:

float Imaginaire; Imaginaires(float i=0) {Imaginaire=i;} void affiche() {cout<< "Imaginaire=" << Imaginaire<< endl;}

};

class Complexes : public Reels, public Imaginaires { public :

friend Complexes operator +(Complexes, Complexes); Complexes(float re=0,float im=0): Reels(re),Imaginaires(im) { cout << "constructeur Complexes" << endl; } void affiche(char * chaine) {cout << chaine; Reels::affiche(); Imaginaires::affiche(); }

};

Complexes operator +(Complexes z1, Complexes z2) { Complexes aux; aux.Reel=z1.Reel+z2.Reel; aux.Imaginaire=z1.Imaginaire+z2.Imaginaire;

return aux; }

class petitsComplexes : Complexes { public: petitsComplexes(float re,float im): Complexes (re,im) {cout << "constructeur petitsComplexes" << endl;}

Complexes::affiche; };

int main() {Complexes z1(2,3), z2; z1.affiche("\nz1\n");

petitsComplexes pf(4,5) ; pf.affiche("\npf\n"); (z1=z2+z1).affiche("\nz1=z2+z1\n"); Imaginaires i(8); i.affiche();

}

// Résultat constructeur Reel constructeur Complexes

z1 Reel=2 Imaginaire=3 constructeur Reel constructeur Complexes

pf Reel=4 Imaginaire=5 constructeur Reel constructeur Complexes

z1=z2+z1 Reel=2

Imaginaire=3 Imaginaire=8

Page 248: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

248 CHAPITRE X ───────────────────────────────────────────────────

4. CONSTRUCTEUR DANS LES CLASSES DERIVEES

■ Ordre d'exécution des constructeurs

• Toute instance d'une classe dérivée ne peut être initialisée sans appel préalable d'un constructeur d'une classe de base. Il faut donc toujours appeler, de préférence explicitement, le constructeur de la classe de base avant celui de la classe dérivée.

• Lors d'appel implicite, le constructeur de la classe mère est d'abord l'explicite, puis le constructeur par défaut explicite, puis le constructeur par défaut implicite.

■ Syntaxe

L'opérateur de résolution de visibilité sépare, de la gauche vers la droite, le constructeur de la classe fille de celui la classe mère.

Classe_fille::Classe_fille: Classe_mere(arguments_constructeur_classe_fille)

� Exemple

#include <iostream.h> class Mere { int m_i;

public: Mere(int); // Constructeur ~Mere(void); // Destructeur };

Mere::Mere(int i) {m_i=i; cout << "Constructeur de la classe mère : m_i = " << m_i<< endl; }

Mere::~Mere(void) { cout << "Destructeur de la classe mère" << endl;}

class Fille : public Mere { public: Fille(void); ~Fille(void); };

Fille::Fille(void) : Mere(2) // Le constructeur de la classe fille appelle celui de la classe mère { cout << "Constructeur de la classe fille" << endl ;}

Fille::~Fille(void) // Le destructeur de la classe fille appelle celui de la classe mère { cout << "Destructeur de la classe fille " << endl;}

int main() {Mere A(1); Fille B; Mere C(5); }

// Résultat Constructeur de la classe mère : m_i = 1 Constructeur de la classe mère : m_i = 2 Constructeur de la classe fille Constructeur de la classe mère : m_i = 5 Destructeur de la classe mère Destructeur de la classe fille Destructeur de la classe mère Destructeur de la classe mère

Page 249: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

L'HERITAGE EN LANGAGE C++ ───────────────────────────────────────────────────

249

5. HERITAGES MULTIPLES

5.1 Classe virtuelle

■ Position du problème

Soit la classe D héritant de deux classes mères B et C, filles d'une classe commune A selon la hiérarchie de classes suivante :

A

D

CB

Selon leur qualification d'héritage respective, les classes B et C héritent des données et méthodes publiques et protégées de A. La classe D hérite de données de B et C, donc des données héritables de A, dupliquées. Une solution (peu pratique et pas toujours efficace) est de spécifier le chemin de la hiérarchie de classes avec l'opérateur de résolution de portée (A::B::x, A::C::x,..., etc.).

■ Définition

Les objets membres d'une classe de base sont partagés par chaque classe dérivée où elle est déclarée virtuelle. Ainsi, les objets membres de la classe A déclarée virtuelle dans les classes B et C, ne sont transmis qu'une fois à la classe D.

■ Corollaire

L'unicité des données membres héritées d'une classe virtuelle par les différents chemins de la hiérarchie de classes est garantie quand l'héritage d'ordre 2 ou plus est multiple.

■ Syntaxe

La classe mère commune est qualifiée virtuelle dans ses classes filles, le spécificateur d'héritage étant précédé du mot clé virtual.

� Exemple

class A // La classe de base { protected : int Donnee; };

class B : virtual public A // Héritage qualifié public de la classe virtuelle A { protected : int Valeur_B; };

class C : virtual public A // A est encore virtuelle { protected : int valeur_C; }; // Nouvel attribut Donnee est acquit par héritage.

class D : public B, public C // L'attribut Donnee hérité de la classe virtuelle garanti unique. {...}; // La classe D

Page 250: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

250 CHAPITRE X ───────────────────────────────────────────────────

5.2 Classe virtuelle et constructeurs

■ Construction explicite dans la classe dérivée

Le constructeur d'une classe dérivée d'une classe virtuelle au deuxième ordre ou plus ne pouvant appeler celui de la classe virtuelle ce dernier pouvant être invoqué à partir de différents chemins de la hiérarchie de classes, chaque classe dérivée construit explicitement ses objets hérités de la classe virtuelle.

■ Constructeur par défaut

Pour éviter l'appel du constructeur par défaut, un constructeur d'une classe de base virtuelle doit être appelé explicitement par celui de toute classe dérivée quel que soit son ordre, les données de la classe de base virtuelle y étant alors garanties uniques. Cette règle s'applique même si un constructeur est défini dans une autre classe mère dérivée de la classe de base virtuelle.

Reprenons l'exemple introductif avec l'hypothèse suivante : la classe D utilise les constructeurs des classes B et C qui tous deux appellent celui de la classe virtuelle A. Pour éviter que l'objet hérité de A ne soit construit plusieurs fois, le compilateur ignore les appels implicites aux constructeurs des classes virtuelles dans les classes dérivées, ces derniers devant être explicités à chaque niveau de la hiérarchie des classes.

■ Transtypage

L'utilisation de l'opérateur de transtypage dynamique dynamic_cast est impérative pour convertir un pointeur sur un objet d'une classe de base virtuelle en un pointeur sur un objet d'une de ses classes dérivées.

� Exemple

#include <iostream.h> class mere1 { int m1;

public: mere1(int i=18) {cout << "constructeur mère 1 "; m1=i; cout<< "m1=" << m1 << endl;} void affiche() {cout<< "m1=" << m1 << endl;}

};

class mere2 { int m2;

public: mere2(int i=0) {cout << "constructeur mère 2 "; m2=i;cout<< "m2=" << m2<< endl;} void affiche() {cout<< "m2=" << m2<< endl;}

};

class fille1 : mere1, virtual mere2 { public :

fille1(int i1=0,int i2=0): mere1(i1),mere2(i2) {cout << "constructeur fille1" << endl;} void affiche() {mere1::affiche(); mere2::affiche();}

};

Page 251: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

L'HERITAGE EN LANGAGE C++ ───────────────────────────────────────────────────

251

class fille2 : virtual mere1, virtual mere2 { public :

fille2(int i1=0,int i2=0): mere1(i1),mere2(i2) {cout << "constructeur fille2" << endl;} void affiche() {mere1::affiche(); mere2::affiche();}

};

class petitefille : fille1 , fille2 { public:

petitefille(int i1,int i2): fille1(i1,i2),fille2(i1,i2){cout << "constructeur petitefille" << endl;} void affiche() {fille1::affiche(); fille2::affiche();}

}; int main() { fille1 f(1,2), g; f.affiche();g.affiche(); petitefille pf(4,8) ; pf.affiche(); }

// Résultat constructeur mère 2 m2=2 constructeur mère 1 m1=1 constructeur fille1 constructeur mère 2 m2=0 constructeur mère 1 m1=0 constructeur fille1 m1=1 m2=2 m1=0 m2=0 constructeur mère 2 m2=0 constructeur mère 1 m1=18 constructeur mère 1 m1=4 constructeur fille1 constructeur fille2 constructeur petitefille m1=4 m2=0 m1=18 m2=0

� Exercice

Reprendre la classe Complexe pour y intégrer les méthodes suivantes :

1°) Surdéfinition de l'addition et de la multiplication de 2 complexes.

2°) Surdéfinition des opérateurs d'addition et d'affectation pour qu'ils opèrent sur des tableaux d'instances.

3°) Surdéfinition de l'opérateur de multiplication pour multiplier un complexe par un nombre flottant. Idem avec un tableau de complexes.

4°) Définir la classe des nombres imaginaires purs, dérivée de la classe complexe qui utilisera les opérateurs d'addition et de multiplication de la classe complexe. Redéfinir la méthode d'affichage en testant les qualifications d'héritage public, privées.

5°) Idem avec la classe des nombres réels purs. Attention, c'est la classe complexe qui dérive des réels purs. Pas l'inverse. On va donc trouver deux héritages successifs…

Page 252: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les
Page 253: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

METHODES VIRTUELLES, LIAISON DYNAMIQUE, POLYMORPHISME

1. METHODES VIRTUELLES ET LIAISON DYNAMIQUE

Sur le plan sémantique, les méthodes virtuelles sont différentes des classes virtuelles, bien que le mot-clé virtual soit également utilisé pour les définir.

■ Position du problème

Une méthode redéfinie appelée est toujours la plus proche dans la hiérarchie de classes. Son appel si elle est de niveau supérieur nécessite donc d'être explicite en rappelant le nom de sa classe avec l'opérateur de résolution de portée.

Cette règle est imparfaite : soit une classe B dérivée de A dont la méthode [A::]x appelle la méthode y redéfinie dans B. Que se passe-t-il lorsqu'un objet de B invoque [A::]x ? La méthode appelée étant membre de A appellera A::y dont la redéfinition dans B est alors inopérante.

Classede base Améthodes x y

Classe Bméthode y redéfinie

Une possibilité consiste à redéfinir la méthode x dans la classe dérivée B.

■ Liaison dynamique

La meilleure solution est que la méthode x appelle A::y (resp. B::y) quand elle est invoquée par une instance de A (resp. de la classe B), la liaison étant effectuée dynamiquement à l'exécution et non statiquement à la compilation.

CHAPITRE XI

Page 254: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

254 CHAPITRE XI ───────────────────────────────────────────────────

■ Syntaxe

La méthode redéfinie dans la classe fille est spécifiée virtual dans la classe mère.

■ Méthode polymorphique

Une méthode virtuelle est invoquée par un objet de sa classe effective (mère ou fille).

Un tel comportement est dit polymorphique.

� Exemple

// Redéfinition d'une méthode de la classe de base #include <iostream.h> class DonneeBase { protected : int Numero; // Les données sont numérotées. int Valeur; // Et sont constituées d'une valeur entière public: virtual void Entre(void); // Méthode de saisie void MiseAJour(void); // Méthode de mise à jour };

void DonneeBase::Entre(void) { cout << "Numéro : "; cin >> Numero; cout << "Numéro : "<< Numero << endl << "Valeur : " ; cin >> Valeur; cout << "Valeur : " << Valeur << endl; return ; }

void DonneeBase::MiseAJour(void) {Entre(); return ;}

class DonneeDetaillee : public DonneeBase { int ValeurEtendue; // Les données détaillées ont en plus une valeur étendue. public: void Entre(void); // Redéfinition de la méthode de saisie };

void DonneeDetaillee::Entre(void) {DonneeBase::Entre(); cout << "Valeur étendue : "; cin >> ValeurEtendue; cout << "Valeur étendue : " << ValeurEtendue << endl; return ; }

int main(void) {DonneeBase A; A.Entre(); A.MiseAJour(); DonneeDetaillee B; B.Entre(); B.MiseAJour(); }

Page 255: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

METHODES VIRTUELLES, LIAISON DYNAMIQUE, POLYMORPHIS ME ───────────────────────────────────────────────────

255

// Résultat Numéro : 5 Numéro : 5 Valeur : 8 Valeur : 8 Numéro : 6 Numéro : 6 Valeur : 2 Valeur : 2 Numéro : 5 Numéro : 5 Valeur : 5 Valeur : 5 Valeur étendue : 6 Valeur étendue : 6 Numéro : 6 Numéro : 6 Valeur : 5 Valeur : 5 Valeur étendue : 6 Valeur étendue : 6

■ Remarques

L'appel B.Entre est correct quand B est une instance de la classe DonneeDetaillee.

L'appel B.MiseAJour est faux si Entre n'est pas virtuelle.

2. METHODE VIRTUELLE PURE - CLASSE ABSTRAITE

2.1 Définitions Une méthode virtuelle pure (pure virtual method), également appelée méthode abstraite, est déclarée dans sa classe mère, définie dans une classe dérivée.

Une classe abstraite comporte au moins une méthode abstraite. Sa vocation n'est pas d'instancier des objets mais simplement d'être utilisée par ses classes dérivées.

L'utilisation de méthodes virtuelles pures et de classes abstraites permet de créer des classes de base décrivant des objets dérivées accessibles avec des pointeurs sur les objets de base, la méthode effective étant définie dans la classe dérivée.

Syntaxe Une méthode virtuelle pure (abstraite) a sa déclaration terminée par l'affectation nulle, donc après le mot-clé const pour les méthodes const et après l'éventuelle liste des exceptions autorisées.

� Exemple

virtual type_resultat identificateur_méthode_virtuelle_pure (liste_arguments_typés) =0;

Page 256: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

256 CHAPITRE XI ───────────────────────────────────────────────────

2.2 Conteneur et objets polymorphiques

■ Définition

Un conteneur est un objet structuré pouvant en contenir d'autres, de type quelconque.

� Exemple : conteneur d'objets polymorphiques

• Un sac est un conteneur d'objets, non forcément uniques. Plusieurs occurrences d'un même objet peuvent y être placées simultanément.

• Le conteneur n'utilise que des pointeurs sur la classe mère abstraite.

• Deux objets identiques sont différenciés par un identifiant dont le choix est à leur charge. La classe abstraite dont ils dérivent est dotée d'une méthode le retournant. Définir deux fonctions permettant de mettre et de retirer une occurrence d'objet du sac ainsi qu'une fonction y détectant la présence d'une occurrence d'un objet. Les objets sont affichés par la méthode virtuelle pure de la classe abstraite print.

#include <iostream.h> class Object // Classe abstraite { unsigned long int h; // Identificateur de l'objet unsigned long int new_handle(); // Allocateur d'un nouvel identificateur public: Object(); // Constructeur virtual ~Object(); // Destructeur virtuel virtual void print() =0; // Méthode virtuelle pure unsigned long int handle() const; // Identification de l'objet }; unsigned long int Object::new_handle() // Allocateur d'un nouvel d'objet { static unsigned long int hc = 0; hc++; return hc; }

Object::Object() // Le constructeur de la classe Object {h = new_handle();} // Allocation d'un nouvel objet

Object::~Object() {} unsigned long int Object::handle() const { return h;} // Retourne l'identificateur de l'objet.

class Bag : public Object // Un sac pouvant en contenir un autre est implémenté sous la forme d'une liste chaînée { typedef struct baglist {baglist *next; Object *ptr; } BagList; BagList *head; // La tête de liste public: Bag(); // Constructeur ~Bag(); // Destructeur void print(); // Affichage du contenu du sac. bool has(unsigned long int) const; // Booléen vrai si le sac contient l'objet. bool is_empty() const; // Booléen vrai si le sac est vide. void add(Object &); // Ajout d'un objet dans le sac void remove(Object &); // Retrait d'un objet du sac Bag (const Bag &Source){}

Page 257: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

METHODES VIRTUELLES, LIAISON DYNAMIQUE, POLYMORPHIS ME ───────────────────────────────────────────────────

257

Bag & operator = (const Bag &source) {BagList *tmp = head;

while (tmp != (BagList *) NULL) tmp = tmp->next; head = tmp; BagList *tmp2 = source.head; while (tmp2 != (BagList *) NULL) {this->add(*(tmp2->ptr)); tmp2 = tmp2->next;} return *this;

} };

Bag::Bag() : Object() {}

Bag::~Bag() // Destruction de la liste des objets {BagList *tmp = head; cout <<" Destructeur Bag" << endl; while (tmp != (BagList *) NULL) tmp = tmp->next; head = tmp; }

void Bag::print() // Affichage de la liste des objets. { BagList *tmp = head;

while (tmp != (BagList *)NULL) {tmp->ptr->print();tmp = tmp->next; } }

bool Bag::has(unsigned long int h) const // Recherche de l'objet. { BagList *tmp = head;

while(tmp != (BagList *)NULL && tmp->ptr->handle() != h) tmp = tmp->next; return (tmp != (BagList *)NULL);

}

bool Bag::is_empty() const { return (head==(BagList *)NULL);}

void Bag::add(Object &o) // Ajout d'un objet à la liste { BagList *tmp = new BagList; tmp->ptr = &o; tmp->next = head; head = tmp; }

void Bag::remove(Object &o) // Suppression d'un objet de la liste { BagList *tmp1 = head, *tmp2 = (BagList *)NULL;

while (tmp1 != (BagList *)NULL && tmp1->ptr->handle() != o.handle()) {tmp2 = tmp1; tmp1 = tmp1->next; } // Recherche de l'objet

if (tmp1!=(BagList *)NULL) // Suppression de la liste. { if (tmp2!=(BagList *)NULL) tmp2->next = tmp1->next; else head = tmp1->next; delete tmp1; }

}

class MonObjet : public Object { public : int entier; void print(); MonObjet(int donnee=0){entier=donnee;} };

class MonObjet2 : public Object { public : float reel; void print(); MonObjet2(float donnee=0){reel=donnee;} };

void MonObjet::print() {cout <<entier << endl;}

void MonObjet2::print() {cout <<reel << endl;}

Page 258: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

258 CHAPITRE XI ───────────────────────────────────────────────────

int main() { Bag *MonSac = new Bag , *MonSac2 = new Bag; MonObjet a(1), b(5), c(8), d(10), e(3); cout << "On ajoute a=1 b=5 c=8 b=5 d=10 à sac n° 1" << endl; MonSac->add(a); MonSac->add(b); MonSac->add(c); MonSac->add(b); MonSac->add(d); MonSac->print(); cout << "On enleve b à sac n°1" << endl; MonSac->remove(b); MonSac->print(); cout << "On copie Sac 1 dans Sac 2 (copie inversée)" << endl; *MonSac2 = *MonSac; MonSac2->print(); cout << "On ajoute Sac 2 à Sac 1" << endl; MonSac->add(*MonSac2); MonSac->print(); delete MonSac; delete MonSac2;

Bag *TonSac = new Bag , *TonSac2 = new Bag; MonObjet2 A(1.4), B(5e-2), C(3.147), D(100.), E(3.); cout << "On ajoute A=1.4 B=5e-2 C=3.147 B=5e-2 D=10 à sac n°1" << endl; TonSac->add(A); TonSac->add(B); TonSac->add(C); TonSac->add(B); TonSac->add(D); TonSac->print(); cout << "On enleve B à sac n°1" << endl; TonSac->remove(B); TonSac->print(); cout << "On copie Sac 1 dans Sac 2 (copie inversée)" << endl; *TonSac2 = *TonSac; TonSac2->print(); cout << "On ajoute Sac 2 à Sac 1" << endl; TonSac->add(*TonSac2); TonSac->print(); delete TonSac; delete TonSac2; return 0; }

// Résultat On ajoute a=1 b=5 c=8 b=5 d=10 à sac n°1 10 5 8 5 1 On enleve b à sac n°1 10 8 5 1 On copie Sac 1 dans Sac 2 (copie inversée) 1 5 8 10 On ajoute Sac 2 à Sac 1 1 5 8 10 10 8 5 1 Destructeur Bag Destructeur Bag

Page 259: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

METHODES VIRTUELLES, LIAISON DYNAMIQUE, POLYMORPHIS ME ───────────────────────────────────────────────────

259

■ Remarques

• La classe abstraite Object sert de cadre (frame) aux classes dérivées.

• La classe Bag stocke des objets dérivés avec les méthodes add et remove.

• La faculté d'interdire à une méthode virtuelle pure définie dans une classe dérivée d'accéder en écriture aux données de la classe de base et à celles de sa classe peut faire partie de ses prérogatives par la qualification const du pointeur this (pointeur constant sur un objet constant). Ainsi, dans l'exemple ci-dessous, la méthode virtuelle pure print qualifiée const permet l'accès uniquement en lecture à l'objet h. Cette méthode d'encapsulation coopérative détecte les erreurs de compilation et peut s'appliquer aux méthodes virtuelles non pures ou aux fonctions non virtuelles.

� Exemple

class Object { unsigned long int new_handle(void);

protected : // Héritage de la classe Object qualifié protected unsigned long int h; public: Object(void); // Le constructeur de la classe Object

virtual void print(void) const=0; // Méthode virtuelle pure qualifiée constante unsigned long int handle(void); // Méthode retournant le numéro d'identification de l'objet };

3. RECAPITULATION DES REGLES DE DERIVATION

■ Règle 1

Un objet dérivé est utilisable comme un objet d'une de ses classes mères.

■ Règle 2 : affectation d'instances entre classes fille et mère

• L'affectation d'une instance dérivée à une instance de base est autorisée les données des champs non définis dans la classe mère étant perdues.

• L'inverse est interdit les données de la classe fille non définies dans la classe mère ne pouvant être ni initialisées ni affectées.

■ Règle 3 : affectation de pointeur

• Les pointeurs des classes dérivées sont compatibles avec ceux des classes mères. Un pointeur d'une classe dérivée peut être affecté à un pointeur d'une de ses classes de base à condition d'être doté des qualifications d'accès convenables.

• Un objet dérivé, accédé avec un pointeur d'une de ses classes mères, est considéré comme un de ses objets et les données spécifiques à la classe dérivée deviennent momentanément inaccessibles, même si le mécanisme des méthodes virtuelles demeure, le destructeur de la classe de base étant virtuel.

• Un pointeur vers une classe de base peut être converti en celui d'une classe dérivée. L'utilisation d'un transtypage fonctionnel est alors préférable.

Page 260: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

260 CHAPITRE XI ───────────────────────────────────────────────────

� Exemple

#include <iostream.h> class Mere { public: Mere(void); ~Mere(void); }; Mere::Mere(void) {cout << "Constructeur de la classe mère" << endl; return ;} Mere::~Mere(void) {cout << "Destructeur de la classe mère" << endl; return ;}

class Fille : public Mere { public: Fille(void); ~Fille(void); }; Fille::Fille(void) : Mere() {cout << "Constructeur de la classe fille" << endl; return ;} Fille::~Fille(void) {cout << "Destructeur de la classe fille" << endl; return ;}

Avec ces définitions, seule la première des deux affectations suivantes est autorisée :

int main() {Mere m; Fille f; m=f; } // F=m ; Erreur (ne compile pas).

Les mêmes règles sont applicables pour les pointeurs.

Mere *pm, m; Fille *pf, f; pf=&f; // Autorisé. pm=pf; // Autorisé. Les objets membres de la classe fille ne sont plus accessibles // Avec ce pointeur : *pm est un objet de la classe mère. // Pf=&m; // Illégal car transtypage nécessaire pf=(Fille *) &m; // Légal, mais dangereux les méthodes de la classe filles n'étant pas définies

■ Règle 4 : non transitivité de l'amitié par dérivation

• Par défaut, les relations d'amitié ne sont pas héritées. Soient une classe A amie d'une classe B et une classe C fille de B. La classe A n'est pas amie de la classe C.

• Cette règle s'applique également aux fonctions et méthodes amies.

■ Règle 5 : méthode virtuelle et constructeur

• Le mécanisme associé aux méthodes virtuelles est désactivé à l'exécution des constructeurs des objets de base pour éviter qu'une méthode virtuelle n'utilise une donnée sur un objet dérivé en cours d'instanciation (non encore initialisé).

• Une méthode virtuelle peut être appelée par un constructeur, la méthode effective étant celle de la classe de l'instance en cours de construction. Soit une classe (petite) fille A dérivée d'une classe B, définissant toutes les deux une méthode f dérivée d'une même méthode virtuelle. Son appel par le constructeur de B utilise B::f, même si l'objet instancié est membre de A.

■ Règle 6 : méthode virtuelle et destructeur

• L'opérateur (statique) delete recherchant le destructeur adéquat dans la classe la plus dérivée, il est fondamental de le déclarer virtuel. Invoqué dans une classe dérivée, il restitue la mémoire utilisée par l'objet dans sa totalité.

• Il est inutile de préciser la classe du destructeur celui-ci étant unique.

Page 261: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LES MODELES GENERATEURS D'OBJETS (TEMPLATE)

En langage C, le préprocesseur permet de générer des objets (constante ou fonction) conformément à un modèle symbolique dont nous verrons que l'absence de contrôle par le compilateur de l'objet généré peut produire des erreurs d'exécution indétectables appelées effets de bord.

La génération d'objets contrôlés à la compilation à partir d'un modèle (template) décrivant toutes ses propriétés (attributs, traitement) élimine toutes ces inconvénients.

1. LE PREPROCESSEUR DU LANGAGE C

■ Directives de compilation

Le préprocesseur, appelé préalablement à la compilation, modifie le code source d'un programme à partir de directives dont les fonctions sont les suivantes : inclusion de fichiers source, substitution symbolique de mots, définition de zone de compilation conditionnelle.

■ L'opérateur #

Toute directive de compilation est préfixée par l'opérateur # pour être différenciée des instructions du langage. Elle ne suit pas la règle du ; (terminateur d'instructions).

Les principales directives sont les suivantes :

include <nom_fichier> ou "nom_fichier", define définition d'une objet symbolique, undef suppression d'une définition d'un objet symbolique, if compilation d'une partie du code si une condition est vraie, else compilation d'une partie du code si la condition précédente est fausse, endif fin du code compilé sous condition, elif compilation d'une partie du code si une condition est vraie, ifdef inclusion de code si un objet symbolique est défini, ifndef inclusion de code si un objet symbolique n'est pas défini.

■ Généralités sur les substitutions symboliques

Une unité syntaxique (token) est un texte indivisible traité par le préprocesseur représentant un objet symbolique (constante symbolique ou fonction symbolique) sous l'une des formes :

#define symbole suite_de_caractères #define symbole(liste_de_caractères) suite de caractères

CHAPITRE XII

Page 262: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

262 CHAPITRE XII ───────────────────────────────────────────────────

1.1 Constante symbolique L'utilisation d'une constante symbolique, également appelée constante manifeste, a deux objectifs :

• meilleure lisibilité du code source. Ainsi, le symbole MAXFILES représente clairement le nombre maximum de fichiers alors qu'un simple nombre n'a pas de signification particulière.

• création d'un symbole dont la valeur peut être modifiée dans tout un programme suite à une simple modification de sa ligne de définition.

La portée d'une constante symbolique reste valide jusqu'à la prochaine définition #define du même symbole, où jusqu'à l'invalidation de la définition par la directive #undef symbole, où jusqu'à la fin du fichier source.

Une substitution symbolique représente l'action exécutée par le préprocesseur quand il substitue, dans tout ou partie du programme, un objet symbolique par l'unité syntaxique associée.

Une substitution de la forme

#define OUI 1

est appelée substitution du premier ordre.

� Description

Code source Code modifié après exécution du préprocesseur #define V 1 ... int main(void) int main(void) ... ... if(i == V) if(i == 1) Une substitution symbolique peut en utiliser une autre.

■ Constantes entières

Les constantes symboliques entières, de type short par défaut, peuvent être définies en format décimal (format par défaut), octal si la substitution commence par 0 (zéro), hexadécimal si la substitution commence par 0x ou 0X. Le type est unsigned si la substitution commence par u ou U.

Toute constante int dont la valeur excède les capacités par défaut de la machine devient automatiquement de type long, que l'on peut également définir explicitement. Il suffit de faire suivre la substitution du suffixe l ou L .

� Exemples

#define MAXLINE 10 /* Constante entière */ #define B 041 /* Constante octale de valeur décimale 33 */ #define HEXA 0XFF /* Constante hexadécimale de valeur décimale 255 */ #define HEXA 0xFF /* Constante hexadécimale de valeur décimale 255 */ #define LON 56897L /* Constante entière de type long */ #define LON 56897l /* Constante entière de type long */

Page 263: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LES MODELES GENERATEURS D'OBJETS (TEMPLATE) ───────────────────────────────────────────────────

263

■ Constantes réelles

Les constantes symboliques réelles sont de type double sous une des formes :

#define PI 3.1415 #define P 1E-6 #define P 1e-6 #define PI 0.31416e+2 #define ALPHA 31.45E-3

■ Constantes de type caractère

Une constante de type caractère est entouré par des simples quottes '. Trois substitutions sont possibles : directe, à partir du code ASCII en octal ('\ddd') ou en hexadécimal.

#define CAR 'b' #define CAR '\014' #define CAR '\0x0C'

■ Suppression d'une substitution symbolique

La directive undef permet la suppression d'une précédente substitution. Son utilisation sans argument supprime toutes les substitutions symboliques précédentes.

� Exemple

#define PI 3.14 ... #undef PI

1.2 Fonction symbolique La directive define définit des fonctions symboliques à partir de la syntaxe suivante :

#define symbole(liste_de_caractères) suite de caractères

La parenthèse ouvrante doit être accolée au symbole, le caractère suivant cette parenthèse faisant partie du mot substitué (même le caractère d'espacement).

■ Arguments d'une substitution fonctionnelle

Une substitution fonctionnelle a des arguments dont il n'est pas nécessaire de déclarer le type tant que les données restent cohérentes avec la substitution ce qui évite d'écrire des fonctions différentes selon les types de données et permet de décrire des objets génériques.

■ Effets de bord

L'utilisation systématique de parenthèses pour délimiter les identificateurs permettent d'éliminer certains effets de bord (cf. la fonction square ou le résultat du calcul de l'expression max(a++, b++ ) dans l'exemple ci-après, faux car le plus grand des deux nombres est évalué deux fois.

On constate également que les arguments de la fonction printf sont évalués de la droite vers la gauche et imprimés de la gauche vers la droite.

Page 264: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

264 CHAPITRE XII ───────────────────────────────────────────────────

� Exemple

#define square(A) (A * A ) /* Faux */ #define carre(A) ((A) * (A)) /* Exact */ #define max(A,B) ( (A > B) ? A : B) /* Faux */ #define maxi(A,B) ( ((A) > (B)) ? (A) : (B) ) /* Exact */ int main(void) { int i = 2 , j = 3 ;

printf (" i = %d square(i) = %d carre(i) = %d\n",i,square(i), carre(i)); printf (" i+1 = %d square(i+1) = %d carre(i+1) = %d\n", i+1, square(i+1), carre(i+1)); printf (" i = %d j = %d maxi(i,j) = %d\n", i,j ,maxi(i,j)); printf (" i = %d j = %d max(i,j) = %d\n" , i,j, max(i,j)); /* Attention aux effets de bord : le max est évalué deux fois */ printf (" i = %d j = %d maxi(i++,j++) = %d, i = %d j = %d\n",i,j ,maxi(i++,j++),i,j); /* Ordre d'évaluation des opérandes de droite à gauche, impression de gauche à droite */ i = j = 4; printf(" i = %d j = %d max(i+1,j+1) = %d\n", i , j , max(i+1, j+1)); return(1);

}

// Résultat i = 2 square(i) = 4 carre(i) = 4 i+1 = 3 square(i+1) = 5 carre(i+1) = 9 i = 2 j = 3 maxi(i,j) = 3 i = 2 j = 3 max(i,j) = 3 i = 3 j = 5 maxi(i++,j++) = 4, i = 2 j = 3 i = 4 j = 4 max(i+1,j+1) = 5

2. LES MODELES GENERATEURS D'OBJET

En langage C++, les modèles d’objets permettent de résoudre deux problèmes résultant d’une mauvaise utilisation du préprocesseur : effets de bord, non vérification syntaxique du code source généré à partir des substitutions symboliques.

■ Principe de la génération d’instances en C++

Un modèle d'objet permet la génération d'instances de cet objet conformes à ses spécifications selon l’algorithme suivant : création à la compilation, lors de la première utilisation d'un modèle générateur, d’un objet conforme à ce dernier dont le(s) modèle(s) type(s) a(ont) été remplacé(s) par son (leur) type effectif, déterminé(s) implicitement à partir du contexte d'utilisation ou avec des arguments explicites.

Cette phase de la compilation est appelée instanciation des arguments modèles.

■ Les différents modèles

En langage C++, les objets modèles sont appelés objets génériques, objets paramétrés, objets template, ou template et peuvent générer des arguments, des fonctions, des classes, des méthodes avec les caractéristiques suivantes :

• Un modèle d'argument est un type paramétré ou une constante de type intégral.

• Un modèle de classe a des membres paramétrés (attributs et/ou méthodes).

• Les modèles de fonctions et de méthodes opèrent sur des arguments paramétrés.

Page 265: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LES MODELES GENERATEURS D'OBJETS (TEMPLATE) ───────────────────────────────────────────────────

265

3. MODELE D'ARGUMENT

Syntaxe template <class|typename nom[=type] [, class|typename nom[=type] […]>

où nom est l'identificateur du type modèle.

■ Equivalence des mots clés class et typename

Dans ce contexte, l'un des mot-clé class ou typename définit un modèle de type.

� Exemple

template <class T, typename U> // Déclaration des modèles de type T, U

■ Interprétation

Les modèles de type T, U peuvent être instanciés à partir d'un quelconque type préalablement déclaré.

■ Modèle de type par défaut

Un modèle de type est défini par défaut quand son identificateur est suivi de l'opérateur d'affectation et d'un nom de type préalablement déclaré.

Sur le plan syntaxique, un type modèle avec une valeur par défaut impose d'en définir une à tous les types modèles qui le suivent dans la déclaration. La ligne suivante provoque donc une erreur de compilation :

template <class T=int, class V> // Int type modèle par défaut

� Exemple

#include <iostream.h> // Les types U, V, W sont paramétrés, W est de type char par défaut template <class U, class V, class W=char > void f(U argument1, V argument2, W argument3) { cout << "argument1 = "<< argument1 << " argument2 = " << argument2; cout << " argument3 =" << argument3 << endl; } int main() { int a=1;

double c=3.45; f(a,c,'c'); f(c,a,12);

}

//résultat argument1 = 1 argument2 =3.45 argument3 = c argument1 = 3.45 argument2 = 1 argument3 = 12

Page 266: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

266 CHAPITRE XII ───────────────────────────────────────────────────

4. MODELE DE FONCTION, DE CLASSE, DE METHODE

La définition d'un modèle de fonction ou de classe peut être complétée par la déclaration d'un ou plusieurs modèle d'arguments (type ou constante) utilisés comme des types traditionnels ou des constantes locales à la fonction.

4.1 Modèle de fonction

■ Déclaration

La déclaration et la définition d'un modèle de fonction (fonction génératrice modèle) sont similaires à celle d'une fonction traditionnelle, celles-ci devant toutefois être précédées de la spécification template.

Syntaxe template <liste_types_paramétrés> type_retour fonction(type_paramétré1 argument_fonction[,…]);

Description • liste_types_paramétrés représente la liste des arguments paramétrés dont chacun

représente un type (sauf en cas d'instanciation explicite) ce qui permet l'identification à la compilation des types modèles et des types effectifs.

• argument_fonction représente un argument formel.

• type_retour représente le type de l'objet retourné qui peut être un des types modèles de la liste.

■ Définition

La définition d'un modèle de fonction est similaire à sa déclaration et comporte au choix :

• des modèles d'argument, utilisables comme les arguments traditionnels,

• des modèles de variables locales,

• des modèles de constante utilisables comme des variables locales qualifiées const.

� Exemple

// Définition d'un modèle de fonction template <class T> T min(T x, T y) { return x<y ? x : y;}

■ Accès public à une fonction modèle, opérateur surdéfini

La fonction min ainsi définie, d'accès public, peut être appelée depuis toute classe où l'opérateur < est surdéfini.

Page 267: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LES MODELES GENERATEURS D'OBJETS (TEMPLATE) ───────────────────────────────────────────────────

267

� Exemple 1

#include <iostream.h> template <class T> T min(T x, T y) { return x<y ? x : y;}

int main(void) { int x=1, y=2; float a= -6.7, b=5.67; double c=4.56, d = 18.23; cout << min(x,y) << endl; // Instanciation avec des arguments de type int cout << min(a,b) << endl; // Instanciation avec des arguments de type float cout << min(c,d) << endl; // Instanciation avec des arguments de type double }

■ Classe, fonction modèle et amitié

• Une fonction modèle peut être amie de toute classe, éventuellement modèle.

• Toutes les instances d'une fonction modèle amie de la classe le sont.

� Exemple

#include <iostream.h> template <class T> // La fonction modèle min T min(T a, T b) {if (a < b) return a; else return b;}

class vecteur // La classe vecteur { int x,y; public : vecteur (int abs = 0, int ord = 0) // Le constructeur {x = abs; y = ord;} void affiche() {cout << x << " " << y << endl;}

// L'opérateur surdéfini est ami de la classe vecteur friend int operator < (vecteur, vecteur); };

int operator < (vecteur a, vecteur b) // L'opérateur < surdéfini { return a.x*a.x + a.y*a.y < b.x*b.x+ b.y*b.y;}

int main() { vecteur u(3,4), v(4,6), w;

w=min(u,v); cout << "min(u,v)= " ; w.affiche(); int a(1), b(3), c; cout << "min (a,b) = " << min(a,b) << endl; return 0;

}

// Résultat min(u,v)= 3 4 min(a,b)=1

■ Surdéfinition et fonction modèle

Une fonction modèle peut être surdéfinie par une fonction, éventuellement modèle.

L'ambiguïté entre une fonction instanciée à partir d'un modèle et une fonction traditionnelle est résolue par le choix de cette dernière.

Page 268: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

268 CHAPITRE XII ───────────────────────────────────────────────────

4.2 Modèle de classe Un modèle de classe (classe modèle ou classe template) peut être considéré comme un modèle de type donc comme une métaclasse (modèle de modèles) dont les modèles caractérisent exclusivement le caractère générique de la classe.

■ Déclaration et définition

Le qualificatif template précède déclaration et définition d'un modèle de classe.

Syntaxe de la déclaration template <arguments_paramétrés> class|struct|union identificateur_classe_ modèle; où arguments_paramétrés représente la liste des arguments modèles utilisés par la classe modèle.

■ Méthode d'une classe modèle

Une méthode d'une classe modèle peut comporter des modèles de type dans sa liste d'arguments sans nécessité de déclaration, leur type effectif étant déterminé à l'instanciation de la classe.

■ Méthode modèle définie à l'extérieur d'une classe modèle

• Les méthodes d'un modèle de classe définies à l'extérieur de la classe y sont qualifiées template.

• Les modèles de type d'une méthode externe à une classe sont toujours spécifiés, à sa définition et déclaration, entre les opérateurs de comparaison, après l'identificateur de la classe.

Syntaxe template <arguments_ paramétrés> type_retour identificateur_classe_paramétré<argts_paramétrés>::méthode(liste_arguments) {…}

■ Classe modèle et amitié

Les classes modèles peuvent être dotées de fonctions amies, éventuellement modèles.

4.3 Modèle de méthode

■ Définition

Soit une classe, éventuellement modèle. Une méthode modèle opère sur un argument modèle au moins, à l'exception des destructeurs. Elle peut être définie dans ou à l'extérieur la classe.

■ Classe d'appartenance non modèle

La syntaxe d'appel est identique à celle d'un modèle de fonction.

Page 269: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LES MODELES GENERATEURS D'OBJETS (TEMPLATE) ───────────────────────────────────────────────────

269

Classe A

public

privéadd(T)

� Exemple

#include <iostream.h> class A { int i;

public: template <class T> void addition(T); // Déclaration de la méthode modèle addition A(){i=3;} // Constructeur

};

template <class T> void A::addition(T valeur) { i=i+(( int) valeur); cout << i << endl; }

int main( void) { A objet;

objet.addition(1); objet.addition(-18.67); objet.addition(1e3); }

// Résultat 4 -14 986

■ Classe d'appartenance modèle

Deux spécifications du mot clé template sont nécessaires : une pour la classe, une pour la méthode. La définition d'un modèle de méthode dans une classe modèle est identique à celle d'un modèle de fonction. Il n'est pas obligatoire d'y indiquer les arguments modèles de la classe.

� Exemple

#include <iostream.h> template <class C=char> // Méthode modèle d'une classe modèle class string { public: template<class M> int compare(const M ); // Méthode modèle définie à l'extérieur de la classe modèle template<class M> // Constructeur modèle de la classe modèle string(const string<M> s) {/* … */} }; template <class C> template <class M> int string<C>::compare(const M s){return 1;}

Page 270: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

270 CHAPITRE XII ───────────────────────────────────────────────────

5. INSTANCIATION D'UN MODELE

La définition d'une objet modèle ne génère pas de code exécutable en l'absence d'instanciation.

■ Définitions

• L'instanciation des modèles est effectuée à l'exécution.

• L'instanciation implicite des arguments modèles est effectuée à la première utilisation d'une fonction ou d'une classe modèle.

• Le type effectif d'un argument modèle est déterminé par son contexte d'utilisation.

5.1 Instanciation implicite d'un modèle de fonction

■ Contexte d'appel non ambigu

Considérons le programme suivant :

template <class T> T min(T x, T y) { return x<y ? x : y;}

int main(void) { // Instanciation implicite avec des arguments de type int

int i = min(2,3); ...

} L'appel de la fonction modèle min avec les arguments entiers 2 et 3, provoque l'instanciation implicite de la fonction dont l'argument modèle de type T est remplacé par le type effectif int.

■ Résolution de l'ambiguïté

L'appel ambigu d'une fonction modèle provoque une erreur de compilation qui peut être évitée par une surdéfinition adéquate.

� Exemple

La fonction modèle min ne peut pas être instanciée dans le cas suivant :

int i=min(2,3.0);

le compilateur ne pouvant déterminer le type effectif des arguments (int ou double).

L'ambiguïté est résolue par la spécification explicite des arguments modèles à l'appel.

min<int>(2,3.0)

ou mieux

min<double >(2,3.0)

Page 271: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LES MODELES GENERATEURS D'OBJETS (TEMPLATE) ───────────────────────────────────────────────────

271

� Exemple

#include <iostream.h> template <class T> T min(T x, T y) { return x<y ? x : y;}

int main(void) { cout << min(2,3)<< endl; cout << min(4.6,-56.) << endl; cout << min<int>(2,3.15) << endl; // Spécification explicite cout << min<double >(2,3.15) << endl; // Spécification explicite }

■ Syntaxe simplifiée

La syntaxe peut être simplifiée quand le compilateur peut déduire l'instanciation de tous les objets modèles à partir des définitions.

� Exemple

La déclaration ci-dessous, externe à la fonction main(), provoque une instanciation implicite de la fonction modèle min avec des arguments de type int :

template int min(int, int);

Programme #include <iostream.h> template <class T> T min(T x, T y) { return x<y ? x : y;}

template int min(int, int); // Spécification du type par défaut de l'instanciation

int main(void) { cout << min(2,3)<< endl;

cout << min(4.6,-56.) << endl; cout << min<double >(2,3.15) << endl;

}

5.2 Instanciation explicite d'un modèle

■ Définition

La définition explicite de tous les objets modèles est appelée instanciation explicite.

Syntaxe identificateur_de_l'objet_paramétré < type_paramétré> identificateur_instance

■ Valeur par défaut

Une invocation avec une liste de valeur vide (opérateurs de comparaison) provoque l'instanciation avec les valeurs par défaut.

Page 272: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

272 CHAPITRE XII ───────────────────────────────────────────────────

� Exemple

template<class T = char>// Spécification du type par défaut d'une instanciation (char) class Chaine <T> {/* Définition de la classe Chaine */}; …

Chaine<> String; // L'instance String du type par défaut char de la classe Chaine

■ Efficacité du code généré

• L'instanciation d'un objet modèle n'est autorisée que si le type correspondant est défini. Ainsi, une instanciation ne peut s'effectuer qu'à partir d'un pointeur déréférencé sur une classe.

• Le compilateur ne générant que le code nécessaire à l'instanciation des objets modèles, seules les fonctionnalités effectivement utilisées de ce dernier génèrent du code dans le programme final.

5.3 Problèmes soulevés par l'instanciation d'un mod èle Le code source d'un objet modèle instancié dans plusieurs fichiers est compilé plusieurs fois ce qui provoque un accroissement de la taille de l'application et peut devenir rédhibitoire.

■ Conséquences

Pour l'éviter, les fichiers en-tête peuvent contenir leur déclaration et définition complète.

• Le nombre de fichiers sources tend alors à se réduire et la complexité des programme augmente ce qui est contraire à la philosophie traditionnelle de la séparation des déclarations et des définitions dans différents fichiers.

• Certains compilateurs imposent une instanciation explicite qui peut nuire à la portabilité de l'application.

• D'autres compilateurs génèrent des fichiers en-tête précompilés contenant le résultat de l'analyse des fichiers en-tête déjà traités ce qui peut imposer d'utiliser un unique fichier en-tête et accroît encore la complexité.

• D'autres encore gèrent une base de données des instances des objets modèles utilisées à l'édition de liens pour la résolution des références non satisfaites de la table des symboles.

• L'éditeur de liens peut être modifié pour regrouper les différentes instances d'un modèle donné.

Page 273: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LES MODELES GENERATEURS D'OBJETS (TEMPLATE) ───────────────────────────────────────────────────

273

6. MODELE SPECIALISE

On peut définir une version spécifique d'un modèle avec des arguments paramétrés donnés.

■ Définition

• Une fonction ou une classe modèle peut être spécialisée par un jeu donné des arguments modèles.

• Deux types de spécialisation sont définis : les spécialisations partielles (certains arguments modèles ont une valeur fixée) et les spécialisations totales (tous les arguments modèles ont une valeur déterminée).

6.1 Spécialisation partielle d'un modèle (fonction ou classe)

■ Objectif

Une spécialisation partielle définit l'implémentation d'une fonction ou d'une classe modèle pour des valeurs fixées de certains de ses arguments modèles dont la nature peut varier (par exemple un pointeur) pour imposer au compilateur le choix de l'implémentation correspondante.

■ Syntaxe

La liste des modèles d'arguments, préalablement déclarés, est spécifiée entre les opérateurs de comparaison à la définition du modèle.

� Exemple

#include <iostream.h> // Exemple de spécialisation partielle template <class T1, class T2, int I> // Définition du modèle de classe A class A { public : T1 champ1;

A(){ cout << " 0 " << endl;} };

template <class T, int I> // Spécialisation 1 class A<T, T*, I> { public: A(){ cout << " 1 " << endl;}};

template <class T1, class T2, int I> // Spécialisation 2 class A<T1*, T2, I> { public: A(){ cout << " 2 " << endl;}};

template <class T> // Spécialisation 3 class A<T*, int, 5> { public: A(){ cout << " 3 " << endl;}};

template <class T1, class T2, int I> // Spécialisation 4 class A<T1, T2*, I> { public: A(){ cout << " 4 " << endl;}};

template <class T2, int I> // Spécialisation 5 class A<T2, int, I> { public: A(){ cout << " 5 " << endl;}};

Page 274: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

274 CHAPITRE XII ───────────────────────────────────────────────────

int main(void) {A <int, float, 4> essai; A<float, int, 6> essai2; A<int, int* , 2> essai3; A<char *, double, 6> essai4; A<int, float*, 7> essai5; A<char *, int, 5> essai6; }

// Résultat 0 5 1 2 4 3

■ Règles de spécialisation des modèles de classes

• Le nombre des arguments modèles déclarés suivant le mot-clé template peut varier.

• Le nombre des arguments spécialisés doit rester constant (3 dans l'exemple précédent).

• Un argument modèle spécialisé ne peut être exprimé en fonction d'un autre argument modèle de la classe.

• Le type d'une des valeurs spécialisées ne peut dépendre d'un autre argument modèle.

• La liste des arguments de la spécialisation ne doit pas être identique à la liste implicite de la déclaration template correspondante.

• La déclaration de la liste des arguments modèles d'une spécialisation ne doit pas contenir des valeurs par défaut inutilisables.

� Exemple 1

template <int I, int J> struct B {}; template <int I> struct B<I, I*2> // Erreur ! Spécialisation incorrecte {};

� Exemple 2

template <class T, T t> struct C {}; template <class T> struct C<T, 1>; // Erreur! Spécialisation incorrecte!

Page 275: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LES MODELES GENERATEURS D'OBJETS (TEMPLATE) ───────────────────────────────────────────────────

275

6.2 Spécialisation totale d'un modèle de classe

Syntaxe La spécialisation totale impose de fournir une liste vide de arguments modèles entre les opérateurs de comparaison, après l'identificateur de la fonction ou de la classe modèle. La définition de cette fonction ou classe doit être précédée de l'instruction :

template <>

� Exemple 1

Soit la fonction min définie plus haut, utilisée sur la variable structurée Structure et devant comparer ses champs. Elle peut être spécialisée de la manière suivante :

#include <iostream.h> // Spécialisation totale struct Structure {int Clef; // Clef d'accès aux données. void *pData; // Pointeur sur les données. Structure(int a=10, void *p=(void*)NULL){Clef=a; pData=p;} };

template <class T> T min(T x, T y) { return x<y ? x : y;}

template <> Structure min<Structure>(Structure s1, Structure s2) {if (s1.Clef<s2.Clef) return s1; else return s2;}

int main() {int cle1=1, cle2=2; Structure Structure1(cle1,&cle1); Structure Structure2(cle2,&cle2), Structure3; cout << (min(Structure1, Structure2)).Clef << endl; cout << (min(Structure2,Structure3)).Clef<< endl; cout << (min(Structure2,Structure3)).pData<< endl; }

// Résultat 1 2 0x22ff70

■ Remarque

Certains compilateurs n'acceptent pas une liste vide des arguments template.

6.3 Spécialisation d'une méthode d'une classe modèl e La spécialisation partielle d'une classe modèle peut être assez lourde, en particulier si la structure de données qu'elle contient est identique dans les différentes versions spécialisées. Dans ce cas, il peut être plus simple de ne spécialiser que certaines méthodes ce qui permet d'éviter de redéfinir les données membres non concernées.

Syntaxe Une méthode est spécialisée par la définition de certains de ses arguments modèles.

Page 276: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

276 CHAPITRE XII ───────────────────────────────────────────────────

� Exemple

#include <iostream.h> // Méthode spécialisée d'une classe modèle template <class T> class Item { public :

T item; Item(){item=(T)0;} // Constructeur par défaut Item(T); void set(T); T get(void) const; void print(void) const;

};

template <class T> Item<T>::Item(T i) // Constructeur {item = i;}

// Accesseurs template <class T> void Item<T>::set(T i) {item = i; cout << "set : item = " << item << endl; }

template <class T> T Item<T>::get(void) const { cout << "get : item " << item << endl; return item;}

template <class T> // Fonction modèle d'affichage void Item<T>::print(void) const { cout << "print paramétré : " << item << endl;}

template <> // Fonction d'affichage spécialisée pour le type int * et la méthode print void Item<int *>::print(void) const { cout << *item << endl;}

int main(void) { int a=8,b,*pa=&a;

float pi=3.1416; Item <int> entier; Item <float> flottant(pi); Item <int *> pointeur(pa);

entier.set(a); entier.get(); entier.print(); flottant.get(); flottant.print(); pointeur.get(); pointeur.print();

}

// Résultat set : item = 8 get : item 8 print paramétré : 8 get : item 3.1416 print paramétré : 3.1416 get : item 0xbffff9d4 8

Page 277: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LES MODELES GENERATEURS D'OBJETS (TEMPLATE) ───────────────────────────────────────────────────

277

7. METACLASSE MODELE (TEMPLATE TEMPLATE)

• Une classe modèle peut être interprétée comme un argument décrivant un modèle de type d'une autre classe et considérée comme une méta classe (classe de classes).

• Elle est qualifiée template dans la déclaration template et est appelée méta classe modèle (classe template template).

• L'instanciation par défaut d'un type classe modèle est autorisée.

Syntaxe template <class MetaClasse, …, template <class T> class M > où

MetaClasse est une métaclasse modèle par la classe modèle M T est un modèle de type de la classe modèle M, définie préalablement

� Exemple

#define TAILLE 4 #include <iostream.h> // Déclaration de méta classe modèle template <typename T> class Tableau { public: T tab[TAILLE];}; // classe modèle Tableau

template <class U, class V, template <typename T> class C = Tableau> // C : instance de la classe modèle T (Tableau par défaut) // d'un modèle de type d'une des méta classes U ou V

class Dictionnaire // Définition de la méta classe modèle Dictionnaire { public: C<U> Clef; // Création de la dépendance : Clef est un Tableau de type U C<V> Valeur; // Création de la dépendance : Valeur est un Tableau de type V };

int main(void) { Dictionnaire <int, short> Dico; // Clef, Tableau de type int, Valeur Tableau de type short

Dictionnaire <float , char > Dico2; Tableau <int> liste; //liste, instancié comme tableau d'entier Tableau <float> liste_flottant;

for (int i=0; i < TAILLE; i++) { liste_flottant.tab[i]=(float) 32*i;

Dico.Clef.tab[i]=i; // Gestion de la clé d'accès au dictionnaire Dico.Valeur.tab[i]=32*i; // Accès au dictionnaire Dico2.Clef.tab[i]=liste_flottant.tab[i]; // Accès par l'intermédiaire du tableau Dico2.Valeur.tab[i]= 'i';

} }

■ Interprétation

• La classe modèle Dictionnaire associe des données membres à une classe modèle.

• Les données membres sont décrites par les classes (conteneurs) modèles Clef et Valeur dont le type (classe) modèle est défini par le méta argument modèle C.

• Ce dernier décrit les modèles de type U et V instanciés par le modèle de type Tableau comme conteneur par défaut.

Page 278: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

278 CHAPITRE XII ───────────────────────────────────────────────────

8. MODELES DE CONSTANTES

Syntaxe La déclaration d'un modèle de constante est effectuée selon la syntaxe :

template<type_argument_paramétré identificateur_argument_paramétré[=valeur_par_défaut][,…]>

Les arguments modèles peuvent être des types où des constantes.

■ Modèle de constante

Le type des modèles de constantes est l'un des suivants :

• type intégral (char , wchar _t, int, long, short, unsigned ) ou énuméré,

• pointeur ou référence (objet ou fonctions),

• pointeur sur membre.

� Exemple

// Déclaration d'arguments de type modèle de constante template <class T, int i, void (*f)(int)> // Modèle de type T, // Modèle de constante i de type int // Modèle de constante f de type pointeur sur une procédure avec un argument entier.

■ Remarque

Les arguments constants de type référence ne peuvent pas être initialisés avec une donnée (immédiate ou temporaire) lors de l'instanciation d'un modèle d'argument.

� Exemple

#include <iostream.h> template <int valeur> // Modèle de constante void f(void) { cout << "valeur = " << valeur << endl; }

int main(void) {f<5>(); // Affiche 5 const int a=8; f<a>(); // Affiche 8 }

Page 279: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LES MODELES GENERATEURS D'OBJETS (TEMPLATE) ───────────────────────────────────────────────────

279

9. GENERICITE ET METHODE VIRTUELLE

• Une méthode virtuelle ne peut pas être un modèle.

• Une méthode modèle avec le même identificateur qu'une méthode virtuelle d'une classe de base ne la surdéfinie pas.

• Une méthode virtuelle peut appeler une méthode modèle.

� Exemple

Une classe de base est dotée d'une méthode virtuelle f.

Une classe dérivée est dotée d'une méthode modèle f.

La méthode virtuelle appelle la méthode modèle et l'appel n'est pas récursif.

// Méthode modèle et méthode virtuelle #include <iostream.h> class B {public : virtual void f(int); // Méthode virtuelle };

void B::f(int entier) { cout << "B::f : " << entier << endl;}

class D : public B {public : template <class T> void f(T); // Méthode modèle ne surdéfinissant pas B::f(int) void f(int i) // Surdéfinition de la méthode virtuelle B::f(int) {f<>(i);} // Appel (non récursif) de la méthode modèle };

template <class T > void D::f(T type) {cout << "F paramétré : " << type << endl;}

int main(void) {B Base; Base.f(1); D Derive; Derive.f(2); }

// Résultat B::f : 1 F paramétré : 2

■ Méthodes modèles et traditionnelles

Soient deux méthodes modèles et non modèles de même signature. Cette dernière est toujours appelée sauf spécification explicite des arguments modèles entre les opérateurs de comparaison.

Page 280: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

280 CHAPITRE XII ───────────────────────────────────────────────────

� Exemple 1

// Surdéfinition d'une méthode classique par une méthode modèle #include <iostream.h> struct A // Prototypes { void f(int); // Méthode classique template <class T> void f(T); // Modèle de méthode };

void A::f( int entier) // Définition de la méthode classique { cout << "A::f( int) = " << entier << endl;}

template <> // Définition de la méthode modèle void A::f<int>(int entier) { cout << "A::f<int>(int) = " << entier << endl;}

int main(void) {A a; a.f(1); // Appel de la version non modèle a.f<>(2); // Appel de la version modèle spécialisée }

// Résultat A::f( int) = 1 A::f<int>(int) = 2

� Exemple 2

// Surdéfinition d'une méthode non modèle par une méthode modèle #include <iostream.h> struct A { void f(int); template <class T> void f(T); };

// Méthode classique void A::f( int entier) { cout << "A::f(int) = " << entier << endl;}

// Méthode modèle template <class T> void A::f(T valeur) { cout << "A::f(T) = " << valeur << endl;}

int main(void) {A a; a.f(1); // Appel de la méthode classique a.f<char >('c'); // Appel de la méthode modèle instanciée a.f<>('c'); // Appel de la méthode modèle spécialisée a.f<>(2); // Appel de la méthode modèle spécialisée }

// Résultat A::f(int) = 1 A::f(T) = c A::f(T) = c A::f(T) = 2

Page 281: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LES MODELES GENERATEURS D'OBJETS (TEMPLATE) ───────────────────────────────────────────────────

281

10. MOT CLE TYPENAME

■ Sémantique

Nous avons vu que le mot-clé typename peut être utilisé pour introduire les modèles de type dans les déclarations template. Il indique également qu'un identificateur inconnu est un type utilisable dans la définition des modèles d'objet.

■ Syntaxe

typename identificateur

� Exemple

// Le mot-clé typename #include <iostream.h> class A { public: typedef int Y; // Y est un type défini dans la classe A };

template <class T> class X // La classe modèle X suppose que le modèle de type T définit un type Y { public: typename T::Y i; }; int main() {X<A> x; // La classe A permet d'instancier à partir de la classe modèle X x.i=3; cout << "x.i=" << x.i << endl; }

11. LA BIBLIOTHEQUE TEMPLATE

La bibliothèque STL (Standard Template Library), intégrée au langage C++, propose un grand nombre de modèles d'objets génériques ainsi que quelques algorithmes.

11.1 Objets de base La bibliothèque STL comporte essentiellement trois classes d'objets : les conteneurs, les itérateurs, les algorithmes et leurs allocateurs.

■ Conteneur

Un conteneur contient des éléments de type générique.

Plusieurs classes de conteneurs sont proposées : les tableaux (classes vector et deque), les listes (list), les piles (stack), les files (queue), les ensembles (set), les ensembles multiples (multiset), les cartes (map) et les multicartes (multimap).

■ Itérateur

Un itérateur permet de définir des opérations sur les conteneurs et peut être interprété comme un pointeur permettant d'accéder à ses éléments typés.

Page 282: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

282 CHAPITRE XII ───────────────────────────────────────────────────

■ Algorithme

• Les algorithmes opèrent sur les données d'un conteneur avec des itérateurs.

• Le code modèle est réutilisable.

• Les algorithmes proposés n'utilisent pas de conteneur spécifique.

■ Allocateur

Un allocateur est un gestionnaire de mémoire utilisé par un conteneur comme dernier paramètre générique.

11.2 Vecteur et itérateur Un vecteur est un tableau d'objets dont deux références, une sur le début et une sur la fin de la collection, permettent de décrire la totalité. Sa taille peut augmenter dynamiquement.

La méthode begin retourne un itérateur sur le début du vecteur et end un itérateur sur sa fin.

� Exemple 1

Le programme suivant crée un tableau de 3 entiers et y stocke trois valeurs. La méthode copy utilise deux itérateurs (un sur le premier élément, un sur le dernier élément) pour délimiter puis envoyer un flot de données sur l'itérateur en sortie.

#include <vector> #include <iterator> #include <iostream> using namespace std; int main(void) {vector<int> myVector(3); // Instanciation d'un vecteur de 3 composantes myVector[0] = 2; myVector[1] = 4; myVector[2] = 6; copy(myVector.begin(),myVector.end(), ostream_iterator<int>(cout," ")); cout << endl; return 0; }

// Résulat 2 4 6

� Exemple 2

Le vecteur est créé avec trois composantes et la méthode push_back permet de modifier la taille du tableau par ajout de composante. Ainsi, les trois appels de la méthode push_back en modifient dynamiquement le nombre (jusqu'à 6).

Page 283: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LES MODELES GENERATEURS D'OBJETS (TEMPLATE) ───────────────────────────────────────────────────

283

#include <vector> #include <iterator> #include <iostream> using namespace std; int main(void) {vector<int> myVector(3);

myVector.push_back(3); myVector.push_back(4); myVector.push_back(5); myVector[1] += 2; copy(myVector.begin(),myVector.end(), ostream_iterator<int>(cout," ")); cout << endl; return 0;

}

// Résultat 0 2 0 3 4 5

■ Principales méthodes

iterator begin(); Retourne un itérateur sur le début de la collection.

iterator end(); Retourne un itérateur après le dernier élément de la collection.

void push_back(const T &); Dépose d'un élément en dernière position de la collection.

T &back(); Retourne le dernier élément de la collection. void pop_back(); Suppression du dernier élément de la

collection. iterator insert(iterator,const T &); Insertion d'un élément à une position

donnée. iterator erase(iterator); Suppression d'un élément à une position

donnée.

� Exemple

Saisie, tri, affichage de nombres entiers à partir des flux d'entrée/sortie.

#include <stdlib.h> #include <iostream> #include <fstream> #include <vector> #include <iterator> #include <algorithm> using namespace std;

int main(void) { typedef vector<int> vector_int;

ifstream source("input.txt"); vector<int> v; istream_iterator<int> start(source); istream_iterator<int> end; back_insert_iterator<vector_int> dest(v); copy(start,end,dest); sort(v.begin(),v.end()); copy(v.begin(),v.end(),ostream_iterator<int>(cout,"-")); cout << endl << "Fin du test !" << endl; return 0;

}

Page 284: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

284 CHAPITRE XII ───────────────────────────────────────────────────

11.3 Les deques Un deque est un objet d'une collection pouvant être manipulé comme un vector par l'utilisation de la méthode push_front, (insertion en tête de la séquence).

■ Principales méthodes

iterator begin(); Retourne un itérateur sur le début de la collection.

iterator end(); Retourne un itérateur après le dernier élément de la collection ().

void push_back(const T &); Dépose d'un élément en dernière position. T &back(); Retourne le dernier élément de la collection. void pop_back(); Suppression du dernier élément de la

collection. void push_front(const T &); Dépose d'un élément en tête de la collection. T &front(); Retourne le premier élément de la collection. void pop_front(); Supprime le premier élément de la

collection. iterator insert(iterator,const T &); Insertion d'un élément à une position

donnée. iterator erase(iterator); Suppression d'un élément à une position

donnée.

12. FONCTIONS EXPORTEES

Les bibliothèques de modèles peuvent ne pas être fournies, la norme permettant de les compiler séparément et d'exporter les définitions des modèles dans des fichiers.

Syntaxe Les fonctions et classes modèles concernées sont exportées à partir du mot clé export.

■ Description

• Les définitions des fonctions et des classes exportées doivent être qualifiées export.

• L'exportation d'une classe modèle exporte toutes ses méthodes non qualifiées inline, toutes ses données statiques, toutes ses classes membres et toutes ses méthodes modèles non statiques.

• Une fonction modèle qualifiée inline, les fonctions et les classes modèles définies dans un espace de nommage anonyme ne peuvent pas être exportées.

� Exemple

export template <class T> void f(T); // Fonction dont le code n'est pas fourni

// Code exporté de f export template <class T> void f(T p) {…} // Corps de la fonction.

Page 285: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LES MODELES GENERATEURS D'OBJETS (TEMPLATE) ───────────────────────────────────────────────────

285

13. EXERCICES

� Exercice 1 : le préprocesseur et la substitution fonctionnelle.

Le volume d'une sphère est calculé avec les directives #define et #include.

1°) Ecrire la fonction symbolique vol(r) qui appelle la fonction symbolique surf(r) qui appelle la fonction symbolique circonf(r). Définir π par une constante symbolique.

2°) Répartir le fichier source en 4 fichiers principal.c, vol.c, surf.c, circonf.c, pi.h.

principal.c vol.c surf.c circonf.c pi.h

� Exercice 2 : insuffisances du préprocesseur et modèle objet

1°) Définir la fonction symbolique square(x) (x*x) et l'utiliser sur des nombres entiers (courts, longs, avec ou sans signe), des nombres flottants, char et sur l'expression x+3.

2°) Définir un modèle de fonction carre(T) qui puisse opérer sur des objets d'un des types précédents et calculer le carré de l'expression entier+3. Conclusion.

#include<iostream.h> #define square(x) (x*x) // Effets de bords possibles

template<class T> T carre(T var) {return (var*var);}

int main() { char caract='u'; int entier=2; float flottant=3.2;

entier=square(entier); cout<< "entier="<<entier << "\tsquare(flottant) =" <<square(flottant); cout<<"\tsquare(entier+3)="<<square(entier+3)<<"\nsquare(caract):"<<square(caract) ; cout<<"\tcarre(3) = " << carre(3) << " carre(entier+3)= "<< carre(entier+3)<<endl;

}

// Résultats entier=4 square(flottant) =10.24 square(entier+3)=19 square(caract):13689 carre(3) = 9 carre(entier+3)= 49

� Exercice 3

Soit une classe modèle A contenant la méthode f(char *, T type). Instancier f.

#include <iostream.h> template <class T = double > class A { public: void f(char *,T type); };

template <class T> void A<T>::f(char *chaine, T objet) { cout << "A<T>::f(" << chaine << ")" << " objet = " << objet << " ";}

int main(void) { A< char > a; // Instanciation explicite de la classe modèle A<char >

char c='c'; a.f("char ", c); // Instanciation explicite de la méthode modèle A<char >::f() A<int> i; // Instanciation explicite de la classe modèle A<int> int b=25; i.f("int",b ); // Instanciation explicite de la méthode modèle A<int>::f() A<> d; // Instanciation par défaut de la méthode modèle A<double >::f() d.f("defaut",3.1416e0); return 0;

}

// Résultat A<T>::f(char ) objet = c A<T>::f(int) objet =25 A<T>::f(défaut) objet =3.1416

Page 286: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

286 CHAPITRE XII ───────────────────────────────────────────────────

� Exercice 4 : instanciation explicite d'un objet modèle

Définir une classe modèle Pile constituée d'un tableau et de son pointeur de pile, d'un constructeur, d'un destructeur, de méthodes d'empilement, dépilement, et test de pile vide.

Instancier la pile avec des entiers (type par défaut), flottants, caractères.

#include <stdio.h> const int MAXSIZE = 128;

template<class Type = int> class Pile {Type Tableau[MAXSIZE]; int Pointeur_Pile; public: Pile(void) {Pointeur_Pile = 0;}; void push(Type in_data) {Tableau[Pointeur_Pile++] = in_data;}; Type pop(void) { return Tableau[--Pointeur_Pile];}; int vide(void) { return (Pointeur_Pile == 0);}; };

int main(void) { int x = 12, y = -7; float reel = 3.1415;

Pile<> int_Pile; // Instanciation avec le type int par défaut Pile<float> float_Pile; // Instanciation avec le type float Pile<char *> string_Pile; // Instanciation avec le type char *

char nom[] = "John Lee Hooker"; int_Pile.push(x); int_Pile.push(y); int_Pile.push(77);

float_Pile.push(reel); float_Pile.push(-12.345); float_Pile.push(100.01);

string_Pile.push("Première ligne"); string_Pile.push("Deuxième ligne"); string_Pile.push("Troisième ligne"); string_Pile.push(nom);

printf("Pile d'entiers ---> "); printf("%8d",int_Pile.pop());printf("%8d", int_Pile.pop()); printf("%8d\n",int_Pile.pop());

printf(" Pile de flottants ---> "); printf("%8.3f ", float_Pile.pop()); printf("%8.3f ", float_Pile.pop()); printf("%8.3f\n", float_Pile.pop()); printf("Chaînes de caractères\n");

do {printf("%s\n", string_Pile.pop());} while (!string_Pile.vide()); return 0;

}

// Résultat Pile d'entiers ---> 12 -7 77 Pile de flottants ---> 3.141 -12.345 100.010 Chaînes de caractères John Lee Hooker Troisième ligne Deuxième ligne Première ligne

Page 287: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LES MODELES GENERATEURS D'OBJETS (TEMPLATE) ───────────────────────────────────────────────────

287

� Exercice 5 : modèle de classe

Modéliser une classe de pile d'objets constitués en une liste chaînée avec les méthodes suivantes : constructeur, destructeur, empilement, dépilement d'une instance de la pile, test de pile vide, vidage de la pile, surdéfinition de l'opérateur d'affectation. #include <iostream.h> // Classe modèle de pile, liste chaînée allouée dynamiquement template <class T> class Stack { typedef struct stackitem {T Item; struct stackitem *Next; } StackItem; StackItem *Tete;

public: Stack(void); // Constructeurs Stack(const Stack<T> &); // Type effectif ("Stack<T>"). ~Stack(void); // Destructeur Stack<T> &operator =(const Stack<T> ); // Surdéfinition de l'opérateur d'affectation void push(T); T pop(void); // Empilement, dépilement bool is_empty(void) const; // Test de pile vide void flush(void); // Vidage de la pile

};

template <class T> Stack<T>::Stack(void) {Tete = NULL;}

template <class T> Stack<T>::Stack(const Stack<T> &Init) { Tete = NULL;

StackItem *tmp1 = Init.Tete, *tmp2 = NULL; while (tmp1!=NULL) { if (tmp2==NULL) {Tete= new StackItem; tmp2 = Tete;}

else {tmp2->Next = new StackItem; tmp2 = tmp2->Next;} tmp2->Item = tmp1->Item; tmp1 = tmp1->Next;

} if (tmp2!=NULL) tmp2->Next = NULL;

}

template <class T> Stack<T>::~Stack(void) {flush();}

template <class T> Stack<T> & Stack<T>::operator =(const Stack<T> Init) {flush(); StackItem *tmp1 = Init.Tete, *tmp2 = NULL; while (tmp1!=NULL) { if (tmp2==NULL) {Tete = new StackItem; tmp2 = Tete;} else {tmp2->Next = new StackItem; tmp2 = tmp2->Next;} tmp2->Item = tmp1->Item; tmp1 = tmp1->Next; }

if (tmp2!=NULL) tmp2->Next = NULL; return *this; }

template <class T> void Stack<T>::push(T Item) {StackItem *tmp = new StackItem; tmp->Item = Item; tmp->Next = Tete; Tete = tmp;}

template <class T> T Stack<T>::pop(void) {T tmp; StackItem *ptmp = Tete; if (Tete!=NULL) {tmp = Tete->Item; Tete = Tete->Next; delete ptmp;} return tmp; }

template <class T> bool Stack<T>::is_empty(void) const { return (Tete==NULL);}

template <class T> void Stack<T>::flush(void) {while (Tete!=NULL) pop();}

Page 288: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

288 CHAPITRE XII ───────────────────────────────────────────────────

int main(void) { Stack <int> int_Pile; Stack <float> float_Pile[5]; int i, k;

k=int_Pile.is_empty(); (k==1) ? cout <<" k = " << k << " int_Pile_is_empty " << endl : cout <<" k = " << k << " int_Pile_is_not_empty " << endl;

int_Pile.push(k); k=int_Pile.is_empty(); (k==1) ? cout <<" k = " << k << " int_Pile_is_empty " << endl : cout <<" k = " << k << " int_Pile_is_not_empty " << endl;

int_Pile.flush(); k=int_Pile.is_empty(); (k==1) ? cout <<" k = " << k << " int_Pile_is_empty " << endl : cout <<" k = " << k << " int_Pile_is_not_empty " << endl;

for( i = 0; i < 5; i++ ) { int_Pile.push(i); float_Pile[i].flush(); float_Pile[i].push((float)i); }

for( i = 0; i < 5; i++ ) cout << " pop " << int_Pile.pop() << endl;

k=int_Pile.is_empty(); (k==1) ? cout <<" k = " << k << " int_Pile_is_empty " << endl : cout <<" k = " << k << " int_Pile_is_not_empty " << endl;

for(i=0; i < 5 ;i++) {k= float_Pile[i].is_empty(); (k==1) ? cout <<" k = " << k << " float_Pile_is_empty " << endl : cout <<" k = " << k << " float_Pile_is_not_empty " << endl; }

}

� Exercice 6 : le modèle objet et les classes dérivées

1°) Définir un modèle de classe Nombres pouvant opérer sur des nombres d'un type prédéfini quelconque avec les méthodes modèles suivantes : constructeur, destructeur, saisie, impression, addition et multiplication surdéfinies.

L'instancier à partir de nombres de type entier, réel…

2°) Construire une classe modèle de nombres complexes dérivée de la classe modèle Nombres ainsi que les méthodes de saisie et d'affichage correspondantes et l'instancier avec des nombres entiers, réels, complexes.

Les constructeurs, opérateurs d'addition et de multiplication seront redéfinis dans la classe dérivée.

3°) Définir un modèle de constructeur d'un tableau de nombres (classe mère) si nécessaire.

4°) Définir un constructeur d'un tableau de nombres complexes si nécessaire.

5°) Surdéfinir les opérateurs d'addition et de multiplication pour qu'ils opèrent sur des complexes, des tableaux de complexes.

Page 289: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

LES MODELES GENERATEURS D'OBJETS (TEMPLATE) ───────────────────────────────────────────────────

289

// Corrigé partiel #include<iostream.h> template<class T> T carre(T var) {return (var*var);}

template <class Type= double> // Type double par défaut class Nombre {Type valeur;

public: // Constructeur et destructeur Nombre(Type a=0){valeur=a;} ~Nombre(){}

// Prototypes Nombre <Type> operator *(const Nombre <Type>); Nombre<Type>& operator = (const Nombre<Type>); template <class T> friend T carre(T);

void affiche(char * chaine) {cout << chaine << valeur << endl;} };

template <class Type> // Surdéfinition de l'opérateur d'affectation Nombre<Type> & Nombre<Type>::operator = (const Nombre<Type> y) {valeur=y.valeur; }

// Surdéfinition de l'opérateur * template <class Type> Nombre <Type> Nombre<Type>::operator *(const Nombre<Type> y) { return valeur*y.valeur; }

template <class Type= float> // La classe modèle Complexes dérive de la classe Nombre class Complexes : public Nombre<Type> { Type reel, imaginaire;

public: // Constructeur et destructeur Complexes(Type a=0, Type b=0){reel=a; imaginaire=b;} ~Complexes(){} // Prototypes Complexes <Type> operator *(const Complexes <Type>); Complexes<Type>& operator = (const Complexes<Type>); template <class T> friend T carre(T); void affiche(char * chaine) {cout << chaine << reel << " "<< imaginaire << endl;}

};

template <class Type> // Surdéfinition de l'opérateur d'affectation Complexes<Type> & Complexes<Type>::operator = (const Complexes<Type> y) { reel=y.reel; imaginaire=y.imaginaire; }

template <class Type> // Surdéfinition de l'opérateur * Complexes <Type> Complexes<Type>::operator *(const Complexes<Type> y) { Complexes<Type> aux;

aux.reel=reel*y.reel-imaginaire*y.imaginaire; aux.imaginaire= reel*y.imaginaire+imaginaire*y.reel; return aux;

}

// Définition des types "utilisateurs" Entier, Reel, Complexe typedef Nombre<int> Entier; typedef Nombre<float> Reel; typedef Complexes<> Complexe;

Page 290: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

290 CHAPITRE XII ───────────────────────────────────────────────────

int main() { Entier entier1(10), entier2(20), entier3;

entier2.affiche("entier2 : "); entier3.affiche("entier3 : "); entier3=entier2; entier3.affiche("entier3 :"); entier1=entier2*entier3; entier1.affiche("entier1 : "); entier3=entier1*entier2; entier3.affiche("entier3 :"); carre(entier3).affiche("carre(entier3):"); Reel reel1(10.25), reel2(20.89), reel3; reel3.affiche("reel3 :"); reel3=reel2; reel3.affiche("reel3 :"); reel1=reel2*reel3; reel1.affiche("reel1 :"); carre(reel1).affiche("carre(reel1) : ");

Complexe z(5,8); Complexes<float > z1(0,1), z2; z.affiche("z : "); z1.affiche("z1 : "); (z1*z1).affiche("z1*z1="); return 1;

}

// Résultat entier2 : 20 entier3 : 0 entier3 :20 entier1 : 400 entier3 :8000 carre(entier3):64000000

reel3 :0 reel3 :20.89 reel1 :436.392 carre(reel1) : 190438

z : 5 8 z1 : 0 1 z1*z1= -1 0

Page 291: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ESPACES DE NOMMAGE

Les espaces de nommage sont des zones de déclaration d'identificateurs qui y sont regroupés pour éviter d'éventuels conflits d'identification entre différents modules d'une application. Par exemple, deux programmeurs définissant un même identificateur de classe dans deux fichiers différents risquent de provoquer un conflit à l'édition de lien ou à l'utilisation de fichiers sources communs.

Ce conflit est du à l'unicité de l'espace de nommage par défaut du langage C++, dont la portée est globale et dans lequel aucun conflit d'identificateur n'est autorisé. L'utilisation d'espaces de nommage non globaux est une solution à ce problème.

1. ESPACE NOMME ET ANONYME

1.1 Espace nommé Un identificateur associé à un espace de nommage est appelé espace de nommage nommé ou plus simplement espace de nommage, espace nommé, espace.

Syntaxe namespace identificateur_espace_noms {déclarations | définitions} identificateur_espace_noms est le nom de l'espace de nommage, déclarations et/ou définitions contiennent la liste des identificateurs le constituant.

■ Extension

Un espace peut être découpé en plusieurs zones, la première étant utilisée pour des déclarations, les suivantes pour des extensions.

La syntaxe d'une extension d'espace est identique à celle de sa déclaration.

� Exemple

namespace A // L'espace de nommage A { int i;} … namespace B // L'espace B { int i;} … namespace A // Extension de l'espace A { int j;}

Les identificateurs déclarés ou définis dans un espace ne doivent pas entrer en conflit et peuvent être surdéfinis. Ils sont accessibles par l'opérateur de résolution de portée.

CHAPITRE XIII

Page 292: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

292 CHAPITRE XIII ───────────────────────────────────────────────────

� Exemple

#include <iostream.h> int i=1; // I global

namespace A { int i=2; // I redéfini dans l'espace A.

int j=i; // Référence A::i. }

int main(void) { cout << "i = " << i << endl;

cout << "A::i = " << A::i << endl; A::i=3; cout << "A::i = " << A::i << endl; return 0;

}

// Résultat i=1 A::i=2 A::i=3

■ Définition

Les fonctions et méthodes d'un espace définis à l'extérieur avec l'opérateur de résolution de portée imposent une déclaration préalable dans l'espace.

� Exemple

namespace A { int f(void); } // Déclaration de A::f

int A::f(void) // Définition externe de A::f { return 0;}

■ Définition récursive d'espace

• Un espace peut être défini dans un autre.

• Cette déclaration apparaissant au niveau le plus externe de l'espace conteneur, l'espace contenu ne peut être déclaré dans une fonction ou une classe.

� Exemple

namespace Conteneur // Un espace dans un autre { int i; // Conteneur::i. namespace Contenu {int j;} // Conteneur::Contenu::j }

1.2 Espace anonyme • Un espace anonyme est caractérisé par l'absence d'identificateur à sa déclaration.

• Ce type d'espace garantissant l'unicité de ses identificateurs peut être utilisé à la place du mot-clé static pour garantir l'unicité des objets d'un fichier.

• Un espace anonyme peut être déclaré dans un autre espace.

Page 293: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ESPACES DE NOMMAGE ───────────────────────────────────────────────────

293

� Exemple

namespace // Espace anonyme { int i;} // ::i est unique;

■ Portée d'un identificateur global

Un identificateur global est masqué par un identificateur local identique.

Dans des espaces nommés, l'accès est réalisé à partir de l'opérateur de résolution de portée. Il est impossible dans un espace anonyme.

� Exemple

#include <iostream.h> namespace // Ambiguïté entre espaces anonyme et nommé { int i = 10;} // Définition de ::i

void f(void) {i++;

cout << " f()::i = " << i << endl; } // Utilisation de ::i

namespace A { namespace

{ int i; int j;} // Définitions de A::i , A::j et initialisation implicite à 0

void g(void) { cout << "fonction g" << endl;

i++; // Résolution de l'ambiguïté entre ::i et A::i cout << " i = " << i << endl; A::i++; cout << " A::i = " << A::i << endl << " j = " << j << endl;

} // Fin de l'espace A } // Fin de l'espace anonyme global

int main(void) {f(); A::g(); } // Résultat f()::i = 11 fonction g i = 1 A::i = 2 j = 0

1.3 Alias d'espace de nommage Un alias d'espace permet d'accéder simplement à un espace lorsque sa dénomination est complexe. Il ne peut être en conflit avec d'autres identificateurs du même espace.

Syntaxe namespace identificateur_alias_espace = identificateur_espace_nommage;

Page 294: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

294 CHAPITRE XIII ───────────────────────────────────────────────────

2. DECLARATION USING

2.1 Règles d'utilisation La déclaration using permet d'identifier un objet d'un espace sans spécifier ce dernier.

Syntaxe using identificateur;

identificateur décrit le chemin d'accès (espace et opérateur de résolution de portée).

� Exemple

namespace A { int i , j;} // Déclare A::i, A::j.

void f(void) { using A::i; // A::i accessible par l'alias i.

i=1; // Equivalent à A::i=1 j=1; // Erreur l'alias j n'étant pas défini !

}

■ Règles d'utilisation

• L'accès à un objet par un alias est identique à son accès usuel dans l'espace global.

• Un alias permet de référencer uniquement les identificateurs visibles à sa déclaration. Sa portée est réduite à l'espace où il est défini.

• Un alias peut être déclaré plusieurs fois quand les déclarations multiples sont licites (déclarations de variables ou de fonctions externes aux classes).

� Exemple

namespace A // Déclarations using multiples { int i;

void f(void); void f(void){}

}

namespace B { using A::i; // Déclaration de l'alias B::i, identique à A::i.

using A::i; // Légal : double alias de A::i. using A::f; // Déclare void B::f(void), fonction alias à A::f

}

int main(void) { B::f(); // Appelle A::f.

return 0; }

Dans un espace étendu après une déclaration using, un nouvel identificateur identique à celui d'un alias prédéfini n'est pas pris en compte.

Page 295: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ESPACES DE NOMMAGE ───────────────────────────────────────────────────

295

� Exemple

namespace A // Extension d'espace par une déclaration using { void f(int);} using A::f; // F est alias de A::f(int).

namespace A { void f(char ); } // F est toujours alias de A::f(int), pas de A::f(char )

void g() {f('a'); } // Appelle A::f(int), même si A::f(char ) existe

■ Conflit entre déclarations using et identificateurs locaux

• Quand plusieurs déclarations (locales et using) utilisent un même identificateur, ce dernier se rapporte à un objet unique ou représente une fonction surdéfinie.

• Une ambiguïté provoque une erreur de compilation contrairement à la directive using qui diffère la détection d'erreur à l'utilisation de l'identificateur ambigu.

� Exemple

namespace A { int i;

void f(int); }

void g(void) { int i; // Déclaration locale de i.

using A::i; // Erreur : i est déjà déclaré. void f(char ); // Déclaration locale de f(char ). using A::f; // Pas d'erreur, il y a surdéfinition de f. return ;

}

2.2 Héritage et déclaration using Une déclaration using utilisée dans la définition d'un objet membre d'une classe se réfère à une classe de base, l'identificateur associé devant y être accessible.

� Exemple

namespace A { float f;}

class Base { int i; public: int j; };

class Derivee : public Base { // using A::f; // Illégal : f n'est pas membre de la classe de base

// using Base::i; // Interdit : Base::i est d'accès privé public: using Base::j; // Légal. };

L'identificateur j est un synonyme de Base::j dans la classe Derivee.

Page 296: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

296 CHAPITRE XIII ───────────────────────────────────────────────────

■ Requalification des droits d'accès

La déclaration using dans les classes dérivées peut rétablir des qualifications d'accès modifiés par un héritage à des membres de la classe de base. Elle doit porter sur une zone de déclaration de la classe de base où les d'accès sont qualifiés public.

� Exemple

class Base { public:

int i; };

class Derivee : private Base { public: using Base::i; // Rétablit l'accessibilité sur Base::i qui redevient public

// protected : using Base::i; // Interdit sauf requalification public préalable. };

■ Remarques

• Certains compilateurs interprètent différemment l'accessibilité de membres introduits avec une déclaration using qui selon leur grammaire permet de restreindre l'accessibilité des droits et non pas de les rétablir ce qui implique l'impossibilité de requalifier l'accessibilité de données restreintes par une qualification d'héritage, qui doit alors être défini de manière plus permissive, les accès étant ajustés cas par cas.

• Bien que cette interprétation soit licite sur le plan sémantique, les projets actuels de norme semblent indiquer qu'elle n'est pas correcte.

• Soit une fonction d'une classe de base, introduite dans une classe dérivée à partir d'une déclaration using, surdéfinie par une fonction de même signature définie dans la classe dérivée. Cette dernière surdéfinit, sans ambiguïté, la fonction de la classe de base.

3. DIRECTIVE USING

La directive using permet l'accès à tous les identificateurs d'un espace de nommage sans nécessité d'utiliser l'opérateur de résolution de portée.

Syntaxe using namespace identificateur_espace_nommage;

� Exemple

namespace A { int i; } // Déclare A::i *

void f(void) { using namespace A; // Tous les identificateurs de l'espace A sont accessibles

i=1; // Équivalent à A::i=1. }

Page 297: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ESPACES DE NOMMAGE ───────────────────────────────────────────────────

297

■ Portée

Les directives using sont valides à partir de la ligne où elles sont déclarées jusqu'à la fin du bloc de portée courante.

Un espace de nommage étendu après une directive using permet d'utiliser les identificateurs définis dans l'extension comme ceux qui y sont définis préalablement.

� Exemple

namespace A { int i;}

using namespace A; // Extension de l'espace de nommage

namespace A { int j;}

void f(void) { i=0; // Initialise A::i.

j=0; // Initialise A::j. return ;

}

■ Prévention des conflits

La définition d'identificateurs d'un espace par une directive using peut provoquer des conflits d'identificateur.

Aucune erreur n'est signalée sauf si un des identificateurs cause d'un conflit est utilisé.

� Exemple

namespace A; { int i;} // Définit A::i.

namespace B { int i; // Définit B::i.

using namespace A; // A::i et B::i sont en conflit. Aucune erreur n'apparaît. }

void f(void) { using namespace B;

i=2; // Erreur car ambiguïté. return ;

}

Page 298: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les
Page 299: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

REPRISE DES ERREURS D'EXECUTION EN LANGAGE C++

1. PRINCIPES SEMANTIQUES

■ Définitions

• En langage C++, une exception représente une interruption de l'exécution naturelle du programme résultant d'un événement ayant provoqué une erreur d'exécution suivie de l'activation d'un traitement permettant de rétablir un mode de fonctionnement cohérent du programme.

• La génération d'une exception stoppe donc l'exécution du programme et en transmet le contrôle à un gestionnaire d'exceptions qui l'attrape.

■ Remarques

• Le traitement des erreurs d'exécution est réalisé par le(s) gestionnaire(s) d'exceptions approprié(s).

• Une erreur d'exécution dans une fonction en provoque une terminaison anormale ainsi que celle anormale de la fonction appelante ce qui la propage dans la pile des fonctions appelantes jusqu'à ce qu'elle soit traitée ou jusqu'à la fin du programme.

■ Traitement traditionnel

En langage C, le code de retour d'une fonction indique à la fonction appelante si elle s'est correctement exécutée ce qui détermine le traitement ultérieur. Cette technique, lourde et délicate, nécessite de tester les codes de retour de chaque fonction.

Certains programmes gèrent le traitement des erreurs dans un code global de nettoyage, externe, qui n'est exécuté que si l'exécution se déroule sans erreur. Cette stratégie rend le programme moins structuré, car toutes les variables doivent être accessibles depuis le code de traitement des erreurs ce qui nécessite une portée globale. En outre, le traitement de code d'erreurs à valeurs multiples reste posé.

CHAPITRE XIV

Page 300: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

300 CHAPITRE XIV ───────────────────────────────────────────────────

2. GENERATION ET TRAITEMENT D'UNE EXCEPTION

2.1 Principes de gestion des exceptions La gestion des erreurs d'exécution en langage C++ suit l'algorithme ci-après.

■ Algorithme de recherche du gestionnaire d'exception approprié

Une erreur d'exécution provoque la génération d'une exception, l'interruption de l'exécution et la recherche du gestionnaire d'exception approprié. Elle suit donc le même parcourt que celui de la remontée des erreurs à savoir :

• La première des fonctions appelantes de la pile contenant un gestionnaire d'exception approprié prend le contrôle et effectue le traitement de reprise.

◊ S'il est complet, le programme reprend son exécution normale.

◊ Dans le cas contraire, le gestionnaire d'exception peut stopper l'exécution du programme ou propager l'exception par sa relance pour rechercher dans la pile des fonctions appelantes le gestionnaire d'exception approprié.

• L'algorithme est donc récursif.

■ Gestion des variables automatiques

La norme garantit que tous les objets de classe de mémorisation automatique sont détruits lorsque l'exception qui remonte dans la pile sort de leur portée.

■ Corollaire

Quand les ressources sont encapsulées dans des classes avec destructeur(s), la remontée des exceptions provoque le nettoyage (utilisation du garbage collector).

2.2 Génération d'une exception

■ Génération d'une exception

Une erreur d'exécution est caractérisée par une exception typée lancée par la méthode throw qui crée un objet typé la caractérisant.

Synopsis throw(objet_typé_caractéristique);

■ Portée d'une exception

La portée du traitement associé à une exception donnée est définie par une zone du programme protégée des erreurs d'exécution.

Le bloc d'instructions la constituant est introduit avec la clause try.

Syntaxe try // Délimitation d'une zone de prise en compte d'une exception. {/* Code susceptible de générer une exception */ }

Page 301: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

REPRISE DES ERREURS D'EXECUTION EN LANGAGE C++ ───────────────────────────────────────────────────

301

2.3 Gestionnaire d'exception Tout gestionnaire d'exception, introduit par la méthode catch, suit le bloc try associé.

Syntaxe catch (type [&][objet_temporaire]) // Gestionnaire d'exception {/* Traitement de l'exception associée à la classe */ }

■ Gestionnaire d'exception(s) et constructeur copie

Les objets de classe de mémorisation automatique définis dans le bloc try et l'objet construit pour générer une exception étant détruit si l'exception provoque la sortie du bloc, le compilateur en effectue préalablement une copie pour le transférer au premier bloc catch susceptible de le recevoir ce qui peut nécessiter un constructeur copie.

■ Utilisation d'une transmission par référence

• La méthode catch peut transmettre ses arguments par valeur ou référence.

• L'utilisation d'une référence évite une copie de l'objet généré par l'exception et garantit que les modifications sont visibles dans les blocs catch des fonctions appelantes ou de portée supérieure, si l'exception est relancée après traitement.

■ Liste de gestionnaires d'exception(s)

• Plusieurs gestionnaires d'exception(s) peuvent être définis, chacun traitant l'exception dont l'objet associé est du type indiqué par son argument.

• Un objet temporaire peut être utilisé pour la transmission d'informations relatives à la nature de l'erreur. Son usage n'est pas obligatoire.

■ Gestionnaire universel

• Le gestionnaire d'exceptions universel (décrit par trois points de suspension dans sa clause catch) gère tout type d'exception.

• Une variable temporaire ne peut être associée à l'exception son type étant indéfini.

� Exemple

#include <iostream.h> // Utilisation des exceptions class Erreur // Exception associée à l'objet Erreur { public:

int cause; // Entier permettant de spécifier la cause de l'exception Erreur(int c) : cause(c) {} Erreur(const Erreur &source) : cause(source.cause) {} // Constructeur copie

};

class other {}; // Objet correspondant à toute autre exception

Page 302: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

302 CHAPITRE XIV ───────────────────────────────────────────────────

int main(void) { int i; // Type de l'exception à générer.

cout << "Tapez 0 (une exception de type Erreur), 1 (une exception de type entier : )"; cin >> i; // Génération d'une exception cout << endl; try // Bloc de prise en charge des exceptions {switch (i) {case 0: {Erreur a(0); throw(a);} // Génére l'objet classe Erreur interrompant le code. case 1: {int a=1; throw(a);} // Exception de type entier. default: {other c; throw(c);} // C instancié puis lancé } } // Fin du bloc try

catch (Erreur &tmp) // Blocs de réception { cout << "Erreur! (cause " << tmp.cause << ")" << endl;}

catch (int tmp) // Traitement de l'exception de type int { cout << "Erreur int ! (cause " << tmp << ")" << endl;}

catch (...) // Traitement des autres types d'exception { cout << "Exception inattendue !" << endl;} return 0; }

// Résultat Tapez 0 (une exception de type Erreur), 1 (une exception de type entier) : Erreur int ! (cause 1) Tapez 0 (une exception de type Erreur), 1 (une exception de type entier) : Erreur! (cause 0) Tapez 0 (une exception de type Erreur), 1 (une exception de type entier) : Exception inattendue !

2.4 Propagation et relance d'une exception • Pour rétablir un état cohérent des données sur lesquelles elle opère, la fonction de

traitement des erreurs résultant d'une génération d'exception libère les ressources non encapsulées des objets de classe de mémorisation automatique.

• Elle relance ensuite l'exception (dont le parcours s'arrête dès le traitement effectif de l'erreur) pour l'exécution d'un traitement ultérieur par la fonction appelante.

• Une exception différente de l'exception reçue peut être générée comme dans le cas, toujours possible, où le traitement de l'erreur provoquerait lui-même une erreur.

• La méthode throw relance l'exception en cours de traitement.

Syntaxe throw() ;

Description L'exception relancée a comme argument l'objet généré à la précédente exception.

Page 303: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

REPRISE DES ERREURS D'EXECUTION EN LANGAGE C++ ───────────────────────────────────────────────────

303

3. GESTIONNAIRE D'EXCEPTIONS ELEMENTAIRES

La méthode std::terminate est appelée. Par défaut, elle appelle la fonction abort de la bibliothèque C qui provoque l'interruption de l'exécution sans la libération des ressources allouées pouvant ainsi occasionner des problèmes de gestion mémoire. La méthode std::set_terminate permet de masquer cet appel.

Synopsis set_terminate(void(*)(void));

� Exemple

#include <iostream.h> #include <exception> #include <stdlib.h> using namespace std; void mon_gestionnaire(void) { cout << "Exception non gérée précédemment reçue !" << endl;

cout << "Je termine le programme proprement..."<< endl; exit(-1);

}

int lance_exception(void) { throw 2;} // Exécution de la procédure mon_gestionnaire // { throw (double) 2;} // Message : Exception de type double reçue

int main(void) { set_terminate(&mon_gestionnaire); // Masquage de l'appel à la fonction abort

try {lance_exception();} catch(double d) {cout << "Exception de type double reçue : " <<d << endl;} return 0;

}

4. EXCEPTION TYPEE

■ Exceptions explicites

La méthode throw spécifie la liste explicite des exceptions typées qu'une fonction peut lancer, après son en-tête, par sa liste (de types) d'arguments.

� Exemple

int fonction(void) throw(int, double, erreur) // Liste des types d'exception autorisés (int, double, erreur) {/* Corps de la fonction */}

■ Exceptions implicites

Une exception non explicitée provoque une erreur d'exécution puis l'appel de la méthode std::unexpected, dont le comportement par défaut (terminaison impropre du programme) est similaire à celui de la méthode std::terminate.

Page 304: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

304 CHAPITRE XIV ───────────────────────────────────────────────────

Une exception non autorisée termine l'exécution avec la méthode std::terminate. Ce dernier est modifié par le masquage de la fonction appelée par défaut par la méthode std::set_unexpected, dont l'argument pointe sur la procédure adéquate.

Une exception différente peut être relancée : si elle est autorisée, le programme reprend son cours à partir du gestionnaire correspondant. Sinon, le programme est terminé par la génération d'une exception de type std::bad_exception, déclarée comme suit dans le fichier en-tête correspondant :

class bad_exception : public exception { public:

bad_exception(void) throw(); bad_exception(const bad_exception &) throw(); bad_exception &operator =(const bad_exception &) throw(); virtual ~bad_exception(void) throw(); virtual const char *what(void) const throw();

};

� Exemple 1

#include <iostream> // Gestion de la liste des exceptions autorisées #include <exception> using namespace std; void mon_gestionnaire(void) { cout << "exception illégale lancée." << endl;

cout << "Relance d'une exception de type int." << endl; throw 2;

}

int f() throw(int) { static int i = 0;

if(i==0) { cout << "f : throw 5 " << endl; throw 5 ; } else {cout << "f : throw 5.2 " << endl; throw 5.2; } // Génération d'un flottant non prise en compte i++;

}

int main(void) { set_unexpected(&mon_gestionnaire);

try {cout << "f(0) " << endl; f();} catch (int i) {cout << "Exception de type int reçue : " << i << endl;} cout << "Relance de la fonction f()" << endl << "f() " << endl; f();

}

// Résultat f(0) f : throw 5 Exception de type int reçue : 5 Relance de la fonction f() f() f : throw 5 Abandon

Page 305: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

REPRISE DES ERREURS D'EXECUTION EN LANGAGE C++ ───────────────────────────────────────────────────

305

� Exemple 2

#include <iostream> // Gestion d'une liste d'exceptions autorisées #include <exception> using namespace std; void mon_gestionnaire(void) { cout << "exception illégale lancée et captée ." << endl; cout << "Relance d'une exception de type int." << endl; throw 2; }

void f(void) throw(int) { static int i = 0; i++; if(i==1) { cout << "f("<< i << ") : throw 5 " << endl; throw 5 ; } else { cout << "f("<< i << ") : throw 5.2 " << endl; throw 5.2 ; } }

void g(void) throw(float) { cout << "g() : throw float " << endl; throw 3.45 ; }

int main(void) {set_unexpected(&mon_gestionnaire);

try {f(); g();} catch (...) { cout << "Exception reçue " << endl << "Lancement de la fonction g()" << endl; g(); }

}

// Résultat f(1) : throw 5 Exception reçue Lancement de la fonction g() g() : throw float exception illégale lancée et captée . Relance d'une exception de type int. Erreur de segmentation

5. EXCEPTION ET CONSTRUCTEUR

• La génération d'une exception nécessitant la construction d'un objet typé la caractérisant, ce dernier contient des informations typées sur la nature des erreurs. Il est donc licite qu'un constructeur puisse traiter une erreur de construction.

• La génération d'une exception par un constructeur interrompt la construction de l'objet donc l'appel de son destructeur, d'où la nécessité d'intégrer la destruction des objets partiellement initialisés suite à son lancement. Cette règle n'est valide que pour des objets alloués dynamiquement, le comportement de l'opérateur delete étant modifié quand l'exception est générée dans un constructeur.

Page 306: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

306 CHAPITRE XIV ───────────────────────────────────────────────────

■ Syntaxe

• Un bloc try intégré au constructeur lui permet de gérer des exceptions.

• Les blocs catch suivent sa définition et libèrent les ressources allouées par le constructeur préalablement à la génération de l'exception.

■ Règles d'utilisation

• L'exception doit être captée par le programme qui a provoqué la création de l'objet.

• Un constructeur constitué d'un bloc catch puis d'un bloc try provoque la relance de l'exception, le bloc catch associé détruisant les objets partiellement construits.

◊ Ce traitement est différent de celui du bloc catch simple où les exceptions ne sont pas relancées après traitement sauf relance explicite par la méthode throw.

◊ Un programme déclarant des objets globaux dont le constructeur peut lancer une exception à leur initialisation risque de mal se terminer si aucun gestionnaire d'exception ne peut la capter lors de la relance par la méthode catch.

• Lorsqu'un objet est construit par une allocation dynamique, l'opérateur delete procède à sa désallocation et son appel au traitement de l'exception est inutile.

� Exemple

• La création dynamique d'un objet A provoque une erreur d'initialisation et la génération d'une exception, traitée dans le bloc catch qui suit le constructeur.

• L'opérateur delete est appelé explicitement, le destructeur de l'objet A jamais.

#include <iostream> #include <stdlib.h> using namespace std; class A { char *pBuffer; int *pData;

public: A() throw(int); // Prototype du constructeur ~A(){ cout << "A::~A()" << endl;} // Destructeur static void *operator new(size_t taille) {cout << "new()" << endl; return malloc(taille);} static void operator delete(void *p) { cout << "delete" << endl; free(p);}

};

A::A() throw(int) // Constructeur susceptible de lancer une exception try // Bloc try { pBuffer = NULL; pData = NULL; cout << "Début du constructeur" << endl;

pBuffer = new char [256]; cout << "Lancement de l'exception" << endl; throw 2; // Code inaccessible : pData = new int;

} // Fin du bloc try

catch (int) {cout << "Je fais le ménage..." << endl; delete[] pBuffer; delete pData; }

int main(void) { try {A *a = new A;}

catch (...) {cout << "Aïe, même pas mal !" << endl;} return 0;

}

Page 307: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

REPRISE DES ERREURS D'EXECUTION EN LANGAGE C++ ───────────────────────────────────────────────────

307

6. EXCEPTION ET ALLOCATION MEMOIRE

La norme impose de lancer une exception lorsque l'opérateur new ou new[] provoque un défaut de mémoire. Ces opérateurs se comportent alors de deux manières : retour d'un pointeur nul ou appel d'un gestionnaire d'erreur. Trois cas possibles :

• correction de l'erreur et retour à l'opérateur qui réitère sa requête.

• aucune action : l'opérateur achève le programme ou retourne à la fonction appelante par génèration de l'exception std::bad_alloc.

• le gestionnaire d'erreur est masqué par la méthode std::set_new_handler qui retourne l'adresse du gestionnaire d'erreur précédent. Rappelons que la méthode std::set_new_handler est déclarée dans le fichier en-tête new.

7. HIERARCHIE DES EXCEPTIONS

Les exceptions pouvant être dérivées, un gestionnaire d'exceptions peut les traiter par traitement d'un objet d'une de ses classes de base.

Les erreurs sont classifiées selon une hiérarchie de classesd'exceptions, l'écriture de traitements paramétrés utilisant des objets d'un certain niveau de cette dernière.

� Exemple

#include <iostream> using namespace std; // Classe de base des exceptions lors de manipulations de fichiers class ExRuntimeError {}; class ExFileError : public ExRuntimeError {}; // Classes des erreurs de manipulation des fichiers : class ExInvalidName : public ExFileError {}; class ExEndOfFile : public ExFileError {}; class ExNoSpace : public ExFileError {}; class ExMediumFull : public ExNoSpace {}; class ExFileSizeMaxLimit : public ExNoSpace {}; void WriteData(const char *szFileName) // Entrée/sortie sur un fichier { if (szFileName == NULL) throw ExInvalidName(); // Exemple d'erreur

else // Traitement de la fonction { throw ExMediumFull();} // Lancement d'une exception

}

void Save(const char *szFileName) {try {WriteData(szFileName);} // Traitement d'un erreur spécifique catch (ExInvalidName &) {cout << "Impossible de faire la sauvegarde" << endl;} // Traitement de toutes les autres erreurs en groupe catch (ExFileError &) {cout << "Erreur d'entrée / sortie" << endl;}

}

int main(void) { Save(NULL); Save("data.dat"); return 0; }

Page 308: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

308 CHAPITRE XIV ───────────────────────────────────────────────────

■ Règles de gestion de la liste des exceptions

• La liste des exceptions gérées dans une fonction n'étant pas explicitée dans sa signature, elle n'apparaît pas dans celle des surdéfinitions. Elle est définie après les déclarations des méthodes qualifiées const et doit être placée avant l'affectation nulle dans les déclarations des fonctions virtuelles pures.

• Les exceptions n'étant pas gérées par le mécanisme standard de gestion des erreurs des langages C et C++, les tests de validité d'une opération doivent être explicites. Lancer une exception de report du traitement en cas d'échec peut être nécessaire.

• La norme spécifie que les exceptions générées par la machine hôte du programme ne sont pas obligatoirement portables dans les autres implémentations.

■ Retour sur le mot clé try

Quand une classe fille hérite d'une ou plusieurs classes mère, l'appel des constructeurs des classes de base doit être effectué au travers de la méthode try et de son premier bloc. Rappelons que les constructeurs des classes de base peuvent également générer des exceptions.

Syntaxe Classe::Classe try : Base(arguments) [, Base(arguments) [...]] {} catch(...)

Page 309: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

TYPES DYNAMIQUES

1. IDENTIFICATION DYNAMIQUE D'UN TYPE

■ Position du problème

L'interprétation dynamique du type d'un objet (dérivé ou de base) pouvant être ambiguë, les objets polymorphiques intègrent des informations relatives à ce dernier ce qui garantit la validité des transtypages lors de dérivations ou à l'appel de méthodes virtuelles.

1.1 La classe type_info Les informations du type dynamique sont définies dans l'espace de nommage std comme suit :

#include <typeinfo> class type_info { public:

virtual ~type_info(); bool operator ==(const type_info &rhs) const; bool operator !=(const type_info &rhs) const; bool before(const type_info &rhs) const; const char *name() const; private : type_info(const type_info &rhs); type_info &operator =(const type_info &rhs);

};

• Les objets de la classe ne peuvent être copiés les opérateurs d'affectation et le constructeur copie surdéfinis étant d'accès privé.

• Les opérateurs de comparaison surdéfinis testent l'égalité de deux objets de la classe et permettent de comparer le type de deux expressions.

• Les informations de type des objets sont codées sous forme de chaînes de caractères dont une représente le type, accessible par la méthode name.

• La méthode before permet de définir une hiérarchie de types dans une hiérarchie de classes. Son utilisation est délicate l'ordre entre les différentes classes pouvant dépendre de l'implémentation.

CHAPITRE XV

Page 310: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

310 CHAPITRE XV ───────────────────────────────────────────────────

1.2 L'opérateur typeid L'opérateur typeid accède au type dynamique d'une expression.

Syntaxe const type_info & typeid(expression)

L'objet retourné caractérise :

• le type statique d'un objet non polymorphique,

• le type dynamique d'un objet polymorphique sur lequel opère un pointeur ou une référence sur une classe mère de sa classe effective.

� Exemple

#include <iostream.h> #include <typeinfo> class Base { public: virtual ~Base(void){}; };

class Derivee : public Base { public:

virtual ~Derivee(void){}; };

int main(void) { Derivee* pd = new Derivee;

Base* pb = pd; Base* pB=new Base; const type_info &t1=typeid(*pd); // T1 qualifie le type de *pd. const type_info &t2=typeid(*pb); // T2 qualifie le type de *pb. cout << t1.name() << endl << t2.name() << endl; cout << typeid(*pb).before(typeid(*pd)) << endl; cout << typeid(*pB).before(typeid(*pd)) << endl; return 0 ;

}

// Résultat Derivee Derivee 0 1

■ Remarques

• Les objets t1 et t2 qualifient le type Derivee et sont identiques.

• Le type dynamique t2 accède au type effectif de l'objet pointé par pb.

• Les informations de type effectif sont différentes sur un pointeur et un pointeur déréférencé. Quand ce dernier est nul, l'opérateur typeid génère une exception dont l'objet est une instance de la classe bad_typeid, définie comme suit :

class bad_typeid : public logic { public:

bad_typeid(const char * what_arg) : logic(what_arg) {return ;} void raise(void) {handle_raise(); throw *this;}

};

Page 311: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

TYPES DYNAMIQUES ───────────────────────────────────────────────────

311

2. TRANSTYPAGES EN LANGAGE C++

2.1 Généralités sur les opérateurs de transtypage

■ Rappels

• Les règles de dérivation garantissent, à d'utilisation d'un pointeur sur une classe, l'existence et l'appartenance de l'objet à la classe de base du pointeur ce qui permet de convertir un pointeur sur un objet de base en un pointeur sur un objet dérivé.

• Il est interdit d'utiliser un pointeur sur un objet de base pour initialiser un pointeur sur un objet dérivé, la grammaire du langage imposant un transtypage explicite.

■ Opérateurs de transtypage

Le langage C++ fournit un jeu d'opérateurs garantissant ces règles : transtypage dynamique, statique, de constante, de réinterprétation des données.

2.2 Transtypage dynamique

■ Définition

Le transtypage dynamique, implémenté par l'opérateur dynamic_cast, convertit une expression en un pointeur ou une référence d'une classe.

Syntaxe dynamic_cast<type>(expression)

où type désigne le type cible de l'expression à convertir.

■ Règles d'utilisation

• L'opérateur dynamic_cast contrôle la validité du transtypage.

• Il ne supprime pas les qualifications de constance (opérateur const_cast).

• Il permet de qualifier constant un type complexe.

• Il n'opère pas sur les types de base du langage à l'exception du pointeur générique.

• Le transtypage d'un pointeur ou d'une référence d'une classe dérivée vers une classe de base est effectué sans vérification dynamique par l'opérateur dynamic_cast, cette opération étant toujours autorisée.

� Exemple

Les instructions :

B *pb; // La classe B est dérivée de A A *pA=dynamic_cast<A *>(pB);

sont équivalentes aux instructions :

B *pb; A *pA=pB;

Page 312: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

312 CHAPITRE XV ───────────────────────────────────────────────────

• Tout autre opération de transtypage doit opérer sur un type polymorphique.

• Le transtypage d'un pointeur vers un pointeur générique retourne l'adresse de l'objet le plus dérivé.

• Le transtypage d'un pointeur ou d'une référence d'un objet dérivé vers un pointeur ou une référence d'un objet dérivé d'ordre supérieur est effectué après vérification du type dynamique.

◊ Le pointeur nul est retourné si le type cible est un pointeur.

◊ Une exception de type bad_cast (Cf. ci-dessous) est générée quand l'expression caractérise un objet ou une référence.

• Lors d'un transtypage, aucune ambiguïté n'est autorisée pendant la recherche dynamique du type dans les héritages multiples même si la coexistence de plusieurs objets de même type est possible. Cette restriction mise à part, l'opérateur dynamic_cast parcourt une hiérarchie de classesverticalement (conversion d'un pointeur d'objet dérivé vers celui d'un objet dérivé d'ordre supérieur) ou horizontalement (conversion d'un pointeur d'objet vers celui d'un objet frère dans la hiérarchie de classes).

• L'opérateur dynamic_cast convertit un pointeur d'une classe virtuelle vers une classe fille sans permettre l'accès aux objets inaccessibles de la classe de base.

� Exemple

struct A { virtual void f(void) { return ;}; }; struct B : public virtual A {}; struct C : public virtual A, public B{}; struct D {virtual void g(void) { return ;};}; struct E : public virtual B, public C, public D {}; int main(void) { E e; // E contient deux objets dérivés de la classe B, un de la classe A

// Les objets dérivés des classes C et D sont frères. A *pA=&e; // Dérivation légale car objet dérivé de A unique. // C *pC=(C *) pA; // Illégal car A classe de base virtuelle. C *pC=dynamic_cast<C *>(pA); // Légal. Transtypage dynamique vertical. D *pD=dynamic_cast<D *>(pC); // Légal. Transtypage dynamique horizontal. B *pB=dynamic_cast<B *>(pA); // Légal return 0 ;

}

■ La classe bad_cast

La classe bad_cast est définie comme suit dans l'en-tête typeinfo :

class bad_cast : public exception { public:

bad_cast(void) throw(); bad_cast(const bad_cast&) throw(); bad_cast &operator =(const bad_cast&) throw(); virtual ~bad_cast(void) throw(); virtual const char * what(void) const throw();

};

Page 313: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

TYPES DYNAMIQUES ───────────────────────────────────────────────────

313

2.3 Transtypage statique

Syntaxe L'opérateur static _cast effectue le transtypage statique d'objet non polymorphique.

static _cast<type>(expression)

où type désigne le type cible de l'expression valide à convertir.

Synopsis Construction d'un objet temporaire du type indiqué initialisé avec la valeur retournée par l'opérateur.

■ Règles

• Contrairement à dynamic_cast, l'opérateur static _cast permet d'effectuer les conversions entre des types autres que ceux des classes définies par l'utilisateur, sans vérification de la validité de la conversion.

• Quand l'expression est invalide, le transtypage ne peut être effectué qu'entre classe dérivée et classe de base.

• Toute valeur d'une expression peut être supprimée par conversion vers le type void.

• Un objet peut être requalifié const ou volatile.

• L'opérateur static _cast ne permet pas de supprimer la qualification const.

2.4 Transtypage de constance et de volatilité La suppression des qualificatifs const et volatile s'effectue avec l'opérateur const_cast

Syntaxe const_cast<type>(expression)

où type désigne le type cible de l'expression à convertir.

Synopsis • L'opérateur const_cast opère essentiellement sur une référence ou un pointeur.

• Le type cible à moins de contraintes que le type source qualifié const ou volatile.

• Il n'effectue pas les conversions effectuées par les autres opérateurs

• Il contrôle la validité du transtypage d'une référence en la convertissant en pointeur et en vérifiant que les attributs sont const ou volatile.

• Il n'opère pas sur des pointeurs de fonction.

Page 314: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

314 CHAPITRE XV ───────────────────────────────────────────────────

2.5 Opérateur de réinterprétation des données L'opérateur de transtypage le plus délicat est reinterpret_cast.

Syntaxe reinterpret_cast<type>(expression)

où type désigne le type cible de l'expression à convertir.

Synopsis • L'opérateur reinterpret_cast ne permet pas de supprimer les qualificatifs const et

volatile.

• Il doit être symétrique : l'interprétation d'un type T1 comme type T2 puis la celle du résultat en type T1 doit redonner le type de l'objet initial.

• La conversion du type est effectuée sans vérification de la validité de l'opération.

� Exemple

Les instructions :

double f=2.3; int i=1, m; int & j=&m; j=reinterpret_cast<int &>(i); j++;

sont équivalentes aux instructions :

double f=2.3; int i=1; *(( int *) &f)=i;

Page 315: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ANNEXES

1. PROGRAMMATION OBJET ET APPEL SYSTEME

1.1 Appel système L'appel système est l'interface programmatique permettant à un processus utilisateur d'accéder aux ressources de la machine en exécutant en mode système une fonction du système d'exploitation qu'il protège donc par un mécanisme d'encapsulation.

Du point de vue de l'utilisateur, un appel système est similaire à l'appel d'une fonction de la bibliothèque C qui provoque un changement du mode d'exécution. L'interruption générée par l'appel système est traitée par le noyau du système d'exploitation dans le contexte du processus utilisateur appelant en trois phases :

Transfert des arguments et changement de contexte Transfert des arguments d'appel depuis la pile utilisateur vers la pile système ce qui permet au noyau de contrôler la validité de l'appel dans son espace propre.

Choix de l'appel et exécution Détermination de l'appel, contrôle de la validité de ses arguments, exécution.

Fin d'exécution et retour en mode utilisateur Deux situations :

• succès : le code de retour est nul et les informations sont transférées de la pile système à la pile utilisateur.

• échec : le code de retour vaut -1 et la variable errno retourne le code de l'erreur.

1.2 Cycle de vie d'un fichier

■ Définitions

Les définitions et opérations de base sur les fichiers sont les suivantes :

• Le fichier doit être créé, préalablement à toute opération (création).

• Lors de son utilisation, il est nécessaire d'initialiser certaines données (ouverture).

• Il est ensuite possible de lire ou d'écrire des données (lecture et écriture).

• Après utilisation, le fichier doit être refermé (fermeture).

• La suppression du fichier réalise sa destruction.

Page 316: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

316 ANNEXES ───────────────────────────────────────────────────

■ Appels systèmes de gestion des fichiers

Les principaux appels système associés aux opérations précédentes sont :

creat création du fichier et gestion de ses droits d'accès open ouverture du fichier (lecture, écriture, mis à jour) write écriture read lecture lseek positionnement du pointeur close fermeture d'un fichier link création d'un lien (nom synonyme) unlink suppression d'un lien chmod modification des droits d'accès stat informations sur l'état d'un fichier access contrôle des droits d'accès.

Les appels système creat et open utilisent le nom externe du fichier (chemin d'accès ou pathname). Tous les autres utilisent son nom interne (descripteur du fichier).

La suppression d'un fichier est effective quand tous ses chemins d'accès sont détruits.

Le cycle de vie d'un fichier et les appels système correspondants sont résumés dans le schéma suivant :

open

close

creat link

write

read

lseek

unlink

1.3 Opérations sur les fichiers

■ Descripteur de fichier et ouverture

Un descripteur de fichier est défini à sa création ou à son ouverture permettant à l'utilisateur d'y accéder ensuite, après contrôle des droits d'accès.

Synopsis #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(char *chemin , int mode_ouverture, mode_t droits);

Page 317: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ANNEXE ───────────────────────────────────────────────────

317

Description chemin est le nom externe du fichier, de type quelconque. Pour les répertoire, seule l'ouverture en lecture est possible et il est recommandé d'utiliser l'appel opendir.

L'appel open peut être bloquant dans les situations suivantes :

• ouverture d'un tube nommé en écriture en l'absence de lecteur ou en lecture en absence de rédacteur,

• ouverture d'un terminal non prêt.

L'argument mode_ouverture permet de paramétrer le comportement des opérations de lecture/écriture ultérieures. Il est construit par disjonction bit à bit de constantes symboliques définies dans le fichier <fcntl.h> dont la fonction est de permettre l'exécution d'une opération spécifique ou de modifier le comportement de l'opération. Cette disjonction comporte exactement une des constantes suivantes

O_RDONLY si le fichier chargé est accessible en lecture seulement, O_WRONLY si le fichier chargé est accessible en écriture seulement, O_RDWR si le fichier chargé est accessible en écriture et en lecture.

Les constantes essentielles retenues par les normes POSIX et SYSTEM V sont :

O_TRUNC si le fichier existe, sa taille devient nulle. O_CREAT création d'un fichier ordinaire d'une taille vide s'il n'existe pas.

Un paramètre supplémentaire est alors nécessaire pour définir ses droits ultérieurs.

O_EXCL si le drapeau O_CREAT est positionné et que le fichier existe, retourne une erreur (-1)

O_NOCTTY le terminal courant ne sera pas le terminal de contrôle du processus,

O_APPEND écriture en mode ajout à la fin du fichier, O_NONBLOCK modifie l'opération d'ouverture bloquante en renvoyant un

message d'erreur avec la variable errno contenant la constante EAGAIN sauf dans le cas d'un tube nommé.

O_SYNC les opérations d'écriture en mode bloc sont synchrones (vidage du cache),

O_NDELAY les opérations normalement bloquantes ne le sont plus et renvoient un message d'erreur.

■ Création : appel creat

La création d'un fichier peut être réalisée avec l'appel système creat.

Synopsis #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int creat(const char *chemin, mode_t droits_acces);

Description chemin nom du fichier, droits_acces droits d'accès du fichier.

Page 318: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

318 ANNEXES ───────────────────────────────────────────────────

■ Lecture et écriture

Les opérations de lecture et d'écriture sont réalisées, après l'ouverture du fichier, par les appels système read et write.

Synopsis size_t read(int fd, void* buf, size_t nbyte); size_t write(int fd, void* buf, size_t nbyte);

avec

fd le descripteur de fichier,

buf l'adresse du premier octet en mémoire de la zone où sera exécutée l'opération de lecture ou d'écriture,

nbyte le nombre de bytes à transférer en lecture ou en écriture.

Cas particuliers nbyte= 0 fin du fichier rencontrée. nbyte= -1 erreur en lecture ou en écriture.

■ Algorithme de lecture

Début retour -1 si erreur de paramètre d'appel s'il n'existe pas de verrou exclusif impératif en lecture alors

si la fin du fichier n'est pas atteinte, alors lecture depuis la position courante de nbyte caractères au plus (fin de fichier éventuellement atteinte) et retour du nombre d'octets effectivement lus sinon retour 0

sinon si le mode de lecture est bloquant (drapeaux O_NOBLOCK et O_NDELAY non positionnés), alors blocage du processus jusqu'à la suppression du verrou

sinon aucun caractère n'est lu et errno vaut EAGAIN is

Fin

■ Algorithme d'écriture

Début

retour -1 si erreur de paramètre d'appel s'il n'existe pas de verrou, exclusif, impératif ou partagé en écriture alors

écriture (asynchrone par défaut, synchrone avec le drapeau O_SYNC) depuis la position courante ou depuis la fin du fichier (drapeau O_APPEND) de nbyte caractères au plus et retour du nombre d'octets effectivement écrits.

sinon si aucun des drapeaux O_NOBLOCK et O_NDELAY n'est positionné

alors le processus est bloqué jusqu'à la suppression du verrou sinon aucun caractère n'est écrit et errno vaut EAGAIN

is Fin

Page 319: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ANNEXE ───────────────────────────────────────────────────

319

� Exemple 1

#define BUFSIZE 512 #include <stdio.h> int main(void) /* écriture sur la sortie standard du caractère saisi sur l'entrée standard */ { char buf[BUFSIZE];

int n; while (( n = read(0,buf,BUFSIZE)) >0) write(1,buf,n);

}

� Exemple 2 : écriture simplifiée de la commande UNIX de copie d'un fichier cp

#define BUFSIZE 1024 #define PMODE 644 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> int main (int argc, char *argv[]) {int f1,f2,n;

char buf[BUFSIZE]; /* prototype */ void erreur( char *, char *); int open(char *, int, mode_t); if(argc != 3) erreur ("Usage : cp depuis vers", (char *) NULL); if((f1=open(argv[1],O_RDONLY,444))==-1)erreur("cp: ouverture impossible %s",argv[1]); if((f2 = creat(argv[2],PMODE)) == -1) erreur("cp : création impossible %s",argv[2]); while ((n = read(f1, buf, BUFSIZE)) > 0) { if (n==-1) exit(-1);

if (write(f2, buf, n) != n) erreur("cp : erreur en écriture", (char *) NULL); }

return(1); }

void erreur(char *s1, char *s2) {fprintf(stderr,s1,s2); printf("\n"); exit(-1); }

� Exemple 3

/* Utilisation des appels système read, write pour écrire une fonction getchar bufférisee */ #define BUFSIZE 512 #define CMASK 0377 struct _iobuf { char *_ptr; /* pointeur sur l'octet courant */

int _rcnt; /* nombre d'octets en lecture dans le tampon */ int _wcnt; /* nombre d'octets en écriture dans le tampon */ char *_base; /* adresse de base du début du tampon */ char _flag; /* mode d'ouverture du fichier */ char _file; /* numéro du fichier */ char _cbuff; /* tampon d'un caractère */ char _pad; /* tampon pour le nombre pair de bytes */

}

Page 320: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

320 ANNEXES ───────────────────────────────────────────────────

#define putc(c,p) \ (--(p)->_wcnt>=0?((int)(* (p)->_ptr++= (c))) : _flsbf((c),p)) #define putchar(c) putc(c,stdout) int main(void) { int c;

while ((c = getchar()) != EOF) putchar(c); exit(0);

}

getchar() { static char buf [BUFSIZE ];

static char *bufp = buf; static int n = 0; if (n == 0)

{n = read(0,buf,BUFSIZE); bufp = buf; } return((--n >= 0) ? *bufp++ & CMASK : EOF); }

■ Fermeture

L'appel système close supprime la connexion entre un fichier ouvert et son descripteur assurant ainsi la fermeture des fichiers. Bien entendu, le fichier est réutilisable.

Synopsis #include <unistd.h> int close(int fd);

■ Accès aléatoire et déplacement dans un fichier

L'accès à un octet d'un fichier ordinaire est séquentiel (appel système read ou write), ou aléatoire avec l'appel système lseek.

Synopsis off_t int lseek(int fd, off_t offset , int origine);

Description La variable offset modifie le pointeur sur l'octet courant du fichier dont le descripteur est fd; la nouvelle position (en octets) est calculée de la façon suivante :

origine = 0 déplacement absolu et croissant par rapport à l'origine du fichier, origine = 1 déplacement relatif et croissant par rapport à la position courante, origine = 2 déplacement absolu et croissant par rapport à la fin du fichier (ajout).

� Exemple

lseek(fd,0L,2);

Page 321: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ANNEXE ───────────────────────────────────────────────────

321

2. LA GESTION OBJET DES ENTREES/SORTIES

2.1 L'objet fichier Sur les systèmes d'exploitation actuels, l'utilisateur a une vision uniforme des entrées sorties que celles-ci s'effectuent sur un fichier local ou distant, un périphérique, local ou distant, un IPC (outils de communications interprocessus), etc.

Un fichier représente donc un objet stocké localement ou à distance dont la sémantique d'utilisation impose de généraliser les opérations traditionnelles sur les fichiers locaux aux réseaux.

Les systèmes Unix et Windows distinguent sept classes de fichiers :

■ Fichier ordinaire

Un fichier ordinaire (ou encore normal, régulier, banalisé) est une suite finie d'octets sans organisation particulière.

■ Répertoire

Un répertoire (ou dossier) est un conteneur d'autres fichiers, d'un type quelconque.

La racine de l'arborescence est le conteneur racine qui permet d'associer le nom du fichier et sa position dans l'arborescence.

■ Fichiers standard

Trois fichiers utilisés pour la saisie et l'affichage, appelés fichiers standard sont ouverts au début de toute session de travail. Ce sont:

• le fichier standard d'entrée: le clavier,

• le fichier standard de sortie: l'écran,

• le fichier standard d'affichage des messages d'erreurs système : l'écran.

e n t r é e s t a n d a r d C o m m a n d e

s o r t i e s t a n d a r d

m e s s a g e s d ' e r r e u r s t a n d a r d

Il ne faut pas confondre les fichiers ordinaires et les fichiers standard.

dir1 fichier1, fichier2

dir2 fichier3

dir3 fichier4

Page 322: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

322 ANNEXES ───────────────────────────────────────────────────

■ Fichier spécial

Un fichier spécial représente un périphérique fonctionnant dans un des modes suivants :

• mode b (bloc): supports magnétiques de stockage (disque, bande, etc.) dont les entrées/sorties sont gérées via le cache,

• mode c (caractère): tous les périphériques y compris les pécédents.

■ Tube nommé

Les tubes nommés (named pipe) permettent de gérer les accès concurrents.

■ Lien matériel

Le descripteur d'un fichier est appelé inode.

Un lien matériel est une association entre un chemin d'accès à un fichier donné dans un système de fichiers et son descripteur.

Un autre lien matériel sur ce dernier définit un deuxième chemin d'accès à ce fichier sans en faire de copie effective car il n'existe qu'une unique occurrence du fichier même si différents chemins d'accès permettent d'y accéder.

Le fichier est supprimé si le nombre de liens devient nul.

■ Lien symbolique

Un deuxième type de liens est défini: le lien symbolique. Ce dernier définit un chemin d'accès synonyme de celui du fichier (ordinaire ou répertoire). C'est une entrée d'un répertoire quelconque de l'arborescence décrite par inode contenant un nouveau chemin d'accès (absolu ou relatif) d'un inode existant (banalisé ou répertoire) d'un système de fichiers quelconque de l'arborescence.

On en déduit la représentation suivante :

/usr/local/toto

/usr/local/tutu

inode 2 liens physiques

inode1

inode 2

/home/prof/toto

/usr/local/toto

lien physique

lien symbolique

Page 323: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ANNEXE ───────────────────────────────────────────────────

323

Le lien symbolique généralise la notion de lien matériel à un système de fichiers quelconque. Toutefois, contrairement à un lien matériel, le déplacement ou la suppression d'un fichier ne modifie pas un lien symbolique sur ce fichier qui ne pointe sur rien si le fichier n'est pas remplacé ou qui pointe sur le nouveau fichier dont l'inode est identique au fichier antérieur.

■ Liens, réseaux locaux, Internet

Le protocole NFS a permis de généraliser la notion de lien symbolique sur des systèmes de fichiers distants ce qui est d'un intérêt considérable pour la transparence d'un fichier dans un réseau.

Le lien hypertexte est un lien symbolique sur le réseau Internet

■ Création et suppression

La commande ln permet de créer des liens et leur suppression est effectuée à partir de la commande usuelle de suppression d'un fichier rm.

2.2 Gestion des flux

■ Entrées/sorties de haut niveau et bibliothèque C

Les opérations d'entrées/sorties de haut niveau assurent le transfert des données entre le processus utilisateur et le noyau. Elles sont implémentées dans la bibliothèque standard du langage C sous forme de fonctions.

■ Entrées/sorties de bas niveau et appels système

Les opérations d'entrées/sorties de bas niveau assurent le transfert des données entre le noyau et le support physique. Elles sont implémentées sous la forme d'appels système.

Page 324: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

324 ANNEXES ───────────────────────────────────────────────────

■ Entrées/sorties en mode bloc

Elles gèrent la lecture et l'écriture dans le cache d'une lecture anticipé (read ahead) et d'une écriture différée (delayed write), réalisées au niveau supérieur par les primitives de haut niveau et au niveau inférieur par les pilotes de périphérique en mode bloc. L'accès au périphérique est indépendant de l'application.

■ Entrées/sorties en mode caractère

L'application gérant elle-même le tampon d'entrées/sorties accède directement au fichier sans utiliser de cache et les pilotes gèrent les données en mode caractère.

■ Entrées/sorties en mode cru (entrées/sorties en mode raw)

L'application gère ses tampons et l'entrée/sortie, usuellement en mode bloc, n'utilise pas le cache.

■ Prise de communication (socket)

L'accès au réseau est réalisé via le descripteur de la prise de communication utilisée, accessibles comme les autres fichiers par les appels système adéquats.

2.3 Attributs de propriété et sécurité

■ Propriétaire

L'utilisateur propriétaire est celui auquel le fichier est rattaché et dont il peut modifier les caractéristiques de sécurité et d'accès. Son identité peut être modifiée par l'administrateur ou le (futur ex) propriétaire du fichier. C'est un acte irréversible pour l'ex propriétaire.

■ Groupe

• Un groupe est un ensemble d'utilisateurs dotés de privilèges communs.

• Un utilisateur peut appartenir à plusieurs groupes.

• Le groupe d'un fichier est modifiable (propriétaire ou administrateur).

• Le groupe de travail d'une session est modifiable par l'utilisateur.

■ Drapeaux de sécurité

Les protections d'accès d'un fichier sont définies par les drapeaux de sécurité, répartis en trois champs: un pour l'utilisateur propriétaire, un pour les membres de son groupe et un pour les autres utilisateurs, chacun constitué par trois drapeaux d'accès rwx. Leur signification varie selon le type du fichier (ordinaire ou répertoire).

r w x fichier ordinaire lecture écriture script suppression fichier exécutable modification répertoire listable modification traversable création, ajout suppression

Page 325: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ANNEXE ───────────────────────────────────────────────────

325

� Exemple

Un fichier ordinaire avec les droits rwxr-x—x peut être lu, écrit et exécuté respectivement par le propriétaire, lu et exécuté par les membres de son groupe et exécuté par les autres utilisateurs.

■ La commande chmod

Synopsis

chmod modif_autorisations_courantes fichier(s)

Cette commande de modification des attributs de sécurité d'un fichier est utilisée sous forme symbolique ou octale.

Utilisateur Opérateur Permissions u propriétaire + ajouter r g groupe - supprimer w o autres (others) = assigner x

� Exemple

chmod go-rx archive chmod g+w donnees

Les droits d'accès d'un fichier sont définis par l'interprétation (par 3) des 9 drapeaux en octal.

LECTURE = 4 ECRlTURE = 2 EXECUTlON = 1

644 -rw-r—-r-- droits par défaut pour fichier 755 drwxr-xr-x droits par défaut pour répertoire

■ Attributs par défaut

Le masque de création des drapeaux de sécurité est utilisé à la création des fichiers et des répertoires pour définir leurs attributs par défaut.

Les droits par défaut des fichiers ordinaires sont définis selon la règle:

droits_par_defaut=non(masque) et 666

Les droits par défaut des répertoires sont définis selon la règle:

droits_par_defaut=non(masque)

L'exécution de la commande umask indique la valeur par défaut du masque de création courant, souvent 022. Ainsi, avec ce masque, on obtient:

Fichiers ordinaires rw-r—r— Répertoires rwxr-xr-x

ce qui constitue des droits d'accès raisonnables.

Le masque de protection peut-être défini par l'administrateur ou redéfini par l'utilisateur dans son profil de démarrage. Il est fondamental pour l'administrateur de s'assurer d'une valeur par défaut acceptable du masque pour l'ensemble des utilisateurs (génération système, fichier de démarrage d'une session, etc.).

Page 326: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

326 ANNEXES ───────────────────────────────────────────────────

2.4 Usurpation d'identité Le fichier contenant la liste des utilisateurs et leur mot de passe crypté appartient à l'administrateur avec les droits rw-------. Un utilisateur ne peut donc accéder directement à ce fichier, ni en lecture, ni en écriture.

Par contre, il peut y modifier son mot de passe par usurpation temporaire de l'identité du propriétaire de la commande réservée à cet usage dont le propriétaire est l'administrateur et dont le lecteur pourra constater que les droits sont rwsr-xr-x.

■ Le bit suid sur un fichier ordinaire

Le bit s est appelé bit sUID (set user id). Son positionnement autorise un utilisateur non propriétaire du fichier à usurper temporairement l'identité de son propriétaire pour l'exécuter avec les droits de ce dernier.

Le propriétaire réel d'un processus en cours d'exécution est l'utilisateur qui a lancé le processus, le propriétaire effectif étant le propriétaire du fichier exécutable qui en a défini les droits d'accès, autorisant, par le bit s d'autres utilisateurs à l'exécuter.

Synopsis chmod 4555 fichier_exécutable

Avec la commande précédente, le fichier exécutable a les droits r-sr-xr-x

■ Le bit suid sur un répertoire

Tout fichier créé dans ce répertoire hérite du groupe du répertoire si ce dernier a les droits drwxrwsr-x.

■ Le bit Sgid sur un fichier ordinaire

Le bit S, appelé bit SGID, est utilisé avec les fichiers exécutables d'un groupe, permettant à un utilisateur d'exécuter la commande avec les privilèges de ce dernier sans en être membre.

Synopsis chmod 2555 fichier_exécutable

Avec la commande précédente, le fichier exécutable a les droits r-xr-Sr-x

■ Précautions d'utilisation

Tous les fichiers avec l'un des bits s ou S sont sensibles car ils peuvent devenir des failles dans la sécurité du système. Il faut donc en minimiser l'utilisation et éviter que leur propriétaire ne soit l'administrateur par la création d'utilisateur dédié avec des droits limités par le bit suid.

Un fichier exécutable avec le bit suid ne doit pas être accessible en lecture, un pirate pouvant l'utiliser à l'insu de son propriétaire.

2.5 Protection des fichiers d'accès public Le bit de gluance (sticky bit), ou encore le bit t, a deux utilisations distinctes : l'une sur les fichiers et l'autre sur les répertoires.

Page 327: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ANNEXE ───────────────────────────────────────────────────

327

■ Fichier

Il permet le maintien de l'image du fichier en mémoire à la fin de son exécution, d'où son appellation. Les plus fréquents sont les éditeurs et les outils de production de logiciels : compilateurs, assembleurs, éditeur de liens.

■ Répertoire

Le bit t utilisé sur des répertoires d'accès publics (rwxrwxrwx) empêche un utilisateur de détruire des fichiers dont il n'est pas propriétaire.

■ Synopsis

chmod 1777 répertoire

Avec la commande précédente, le répertoire a les droits drwxrwxrwt

2.6 Listes de Contrôles d'Accès aux objets Les systèmes sécurisés actuels permettent de définir des listes de contrôle d'accès (LCA) ou ACL (Access Control List).

La norme POSIX P1006.3 définit les LCA comme un moyen formel d'exprimer des opérations sur des objets, selon l'identité de l'entité émettrice de la requête.

Une LCA est une entrée dans un fichier de la forme :

:type:principal:permissions

Le champ principal est le nom de l'entité à qui appliquer les droits.

Le champ type spécifie l'interprétation du champ principal (le propriétaire du fichier, un autre utilisateur (local ou distant), un groupe (local ou distant), etc).

Le champ permission est constitué des droits classiques RWX complété par les drapeaux CID, avec les interprétations :

C habilitation à modifier les ACL, I droits d'insertion de fichier dans un répertoire, D droits de destruction de fichier dans un répertoire.

� Exemples

root:/etc:RWXCID

Sur AIX (IBM), les ACL comportent les informations suivantes :

• attributs de sécurité spéciaux (bits s, S, t)

• permissions de base constituant les attributs traditionnels,

• permissions étendues, décrites sous la forme d'ACE (Access Control Entry), qui, pour chaque instance décrite, suivent le format :

opération type_d'accès [[info_utilisateur] [info_groupe]

Page 328: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

328 ANNEXES ───────────────────────────────────────────────────

Opérations Trois types d'opérations sont supportés :

deny interdiction du type d'accès spécifié permit autorisation du type d'accès spécifié specify spécification du type d'accès

Types d'accès • L'utilisateur ou le groupe auquel s'appliquent les droits décrits est précédé du

suffixe u: ou g:.

• Une entrée est crée pour chaque utilisateur ou groupe.

• Plusieurs utilisateurs ou groupes peuvent être combinés dans une entrée.

� Exemple

attributes base permissions owner(philipp): rwx group(root): r-x other: r-- extended permissions: enabled deny rwx g:staff permit rwx u:root, g:staff permit r-x g:sysadm

■ Commandes associées

aclget affichage d'une ACL, aclput définition d'une ACL, acledit modification d'une ACL.

■ Test des droits d'accès

Synopsis #include <unistd.h> int access (char *chemin, int type_access);

Description Le paramètre est un disjonction logique (opérateur sur les bits |) des constantes :

R_OK accessible en lecture, W_OK accessible en écriture, X_OK accessible en exécution, F_OK test d'existence du fichier.

Page 329: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ANNEXE ───────────────────────────────────────────────────

329

2.7 Appel système de modification des attributs

■ Modification des droits d'accès

Synopsis #include <sys/stat.h> int chmod(const char* ref, mode_t mode_acces); int fchmod(int fd, mode_t mode_acces);

Description Le fichier référencé, par son descripteur ou par son nom reçoit les attributs définis par la variable mode_acces. Le propriétaire effectif du fichier doit être également le propriétaire du fichier ou l'administrateur.

� Exemple et exercice

Ecrire un programme qui illustre la différence des droits d'accès entre le propriétaire réel et le propriétaire effectif et qui modifie le propriétaire et le groupe d'un fichier.

#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> char *chemin="essai"; void acces_autorise(void) { if(access(chemin,W_OK)) printf("accès en écriture sur %s interdit au propriétaire réel\n", chemin); else printf("accès en écriture sur %s autorisé au propriétaire réel\n",chemin); } int main(void) {mode_t mode; if(access(chemin,F_OK)) {printf("fichier %s inexistant\n",chemin); exit(0);} system("ls -l essai"); acces_autorise(); if(chown(chemin,200,100)==-1) perror("chown"); acces_autorise(); system("ls -l essai"); }

// Résultat -rwxr-xr-x 1 root other 21100 Mai 03 14:03 essai -rwxr-xr-x 1 user1 dba 21100 Mai 03 14:03 essai

■ Modification du propriétaire

Synopsis #include <sys/stat.h> int chown(const char* ref, uid_t uid, gid_t gid); int fchown(int fd, uid_t uid, gid_t gid);

Description Modification du propriétaire ou du groupe du fichier et suppression du bit s ou S.

Page 330: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

330 ANNEXES ───────────────────────────────────────────────────

■ Création d'un inode associé à un fichier spécial

L'appel système à utiliser est variable selon le type du fichier créé.

Fichier ordinaire fichier spécial tube lien symbolique open mknod pipe symlink creat mkfifo

Synopsis #include <sys/stat.h> int mknod(const char *chemin, mode_t droits, dev_t majeur/mineur);

3. QUELQUES APPELS SYSTEME

Les appels systèmes peuvent différer selon les implémentations mais le respect de la norme doit garantir la portabilité des applications. Voici quelques appels systèmes :

■ Gestion des identificateurs

getpid identification du processus courant, getppid identification du processus père du processus actif, getuid identification du propriétaire du processus actif, geteuid identification du propriétaire effectif du processus actif, setuid modification du propriétaire d'un processus actif, seteuid modification du propriétaire effectif d'un processus actif.

■ Gestion de fichiers (compléments)

dup création d'un descripteur de fichier clone, pipe création d'un tube, chdir définition du répertoire de travail, chroot changement de la racine du système de fichiers, chown changement du propriétaire du fichier, stat, lstat, fstat information sur le fichier, link création d'un lien matériel, unlink suppression d'un lien matériel, mount montage d'un volume, umount démontage d'un volume, fcntl modification du comportement des appels systèmes,

■ Gestion et synchronisation des processus

fork création d'un processus, exec* remplacement du code d'un processus, exit terminaison d'un processus. wait attente du signal de fin d'un fils, pause attente d'un signal quelconque, signal action à exécuter à réception d'un signal, kill émission d'un signal à un processus.

Page 331: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ANNEXE ───────────────────────────────────────────────────

331

■ IPC

shmget création d'une zone de mémoire partagée, shmat attachement à un processus d'une zone de mémoire partagée, shmdt détachement d'un processus d'une zone de mémoire partagée, shmctl caractéristiques et destruction d'une zone de mémoire partagée. msgget création d'une file de messages, msgsnd émission d'un message vers une file, msgrcv lecture d'un message d'une file, msgctl opérations sur les files de messages. semget création d'un tableau de sémaphores, semop opérations sur un tableau de sémaphores, semctl opérations sur les files de messages.

4. GENERATION D'APPLICATIONS ET COMMANDE MAKE

On dispose de générateurs d'applications dont le code (source, compilé ou édité) peut provenir de différents langages de telle sorte que l'on puisse modifier un fichier (source, bibliothèque,...) sans avoir à recompiler l'ensemble. Ainsi, l'utilitaire sccs gère les différentes versions d'un même programme et l'utilitaire make, outil de développement et d'administration, génère des exécutables à partir de fichiers sources ou objets modifiés, en prenant en compte les aspects suivants :

• Graphe des dépendances des fichiers (sources, objets, exécutables) assurant la description des dépendances entre les différents fichiers utilisés,

• Prise en compte automatique de toute modification d'une dépendance,

• Description explicite ou implicite des actions de génération d'un applicatif.

■ Définitions

Le fichier cible, (ou plus simplement la cible) est le nom du fichier à générer.

La (les) dépendance(s) est (sont) le(s) fichier(s) nécessaire(s) (source(s) et/ou objet(s)) pour générer la cible.

L'action est la description des règles de production de la cible à partir de ses dépendances.

Le fichier (makefile ou Makefile) contient la description (explicite ou implicite) des règles de production de cibles à partir de leurs dépendances. On peut y définir des variables et des macros et l'algorithme utilisé garantit que seules sont exécutées les actions nécessaires.

■ Syntaxe d'écriture

cible : dépendance(s) action

Le caractère de tabulation est nécessaire devant la description de l'action.

Page 332: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

332 ANNEXES ───────────────────────────────────────────────────

� Exemple

Soient les fichiers : /* fichier principal.c */ #include <stdio.h> void main(void) { float x,y;

float circonf(float), surface(float); x = circonf((float)100); y = surface((float)100); printf("circonférence = %6.4f , surface = %6.4f\n",x,y);

}

/* fichier pi.h */ #define PI 3.1416

/* fichier circonf.c */ #include "pi.h" float circonf( float r) { return((float) 2*PI*r);}

/* fichier surface.c */ #include "pi.h" float surface(float r) {return((float)PI*r*r);}

En appliquant la syntaxe d'écriture définie ci-dessus, on obtient :

circonf.o : circonf.c pi.h cc -c circonf.c

■ Règle de déclenchement

L'action a lieu si la cible n'existe pas ou si sa date de dernière modification est plus ancienne que celle d'une dépendance.

� Exemple

Avec les fichiers précédents, le graphe des dépendances est le suivant :

# Fichier Makefile (version 1) #cible résultat resultat : principal.o circonf.o surface.o #action cc principal.o circonf.o surface.o -o resultat

#cible principal.o principal.o : principal.c cc -c principal.c

circonf.o : circonf.c pi.h cc - c circonf.c

surface.o : surface.c pi.h cc -c surface.c

La scrutation est verticale, du bas vers le haut.

Si le fichier principal.o est supprimé après la création du fichier resultat, l'exécution de la commande make ne provoque que la création des cibles principal.o et resultat.

Page 333: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ANNEXE ───────────────────────────────────────────────────

333

■ Règles implicites

L'algorithme utilise des règles implicites. Ainsi, tout fichier objet ou exécutable est obtenu à partir d'un fichier source.c, ce qui s'écrit explicitement par :

* : *.o cc -o * *.o *.o : *.c cc -c *.c

En l'absence de fichier makefile, la commande

make bidon

lance la compilation et l'édition de lien du fichier bidon.c et affiche le message :

cc -o bidon bidon.c

Le fichier contenant la description des règles de production explicites est le fichier Makefile du répertoire courant (défaut) ou un fichier quelconque (option f).

� Exemple

Avec les règles implicites, le fichier Makefile se simplifie :

# fichier Makefile (version 2) resultat : principal.o circonf.o surface.o cc principal.o circonf.o surface.o -o resultat circonf.o : circonf.c pi.h cc - c circonf.c surface.o : surface.c pi.h cc -c surface.c

On peut croire que le fichier pi.h est une dépendance implicite puisque il est inclus par le préprocesseur dans les dépendances implicites circonf.c et surface.c ce qui est faux. Il suffit de modifier le fichier pi.h et le fichier Makefile de la façon suivante :

# fichier Makefile (version 2.2 erronée) resultat : principal.o circonf.o surface.o cc principal.o circonf.o surface.o -o resultat circonf.o : circonf.c cc - c circonf.c surface.o : surface.c cc -c surface.c

L'exécution de la commande make ne "voit pas" la modification du fichier pi.h. On peut se poser la même question avec les cibles circonf.o et surface.o avec les dépendances implicites circonf.c et surface.c. Soit le fichier Makefile :

# fichier Makefile (version 2.3) resultat : principal.o circonf.o surface.o cc principal.o circonf.o surface.o -o resultat circonf.o : pi.h cc - c circonf.c surface.o : pi.h cc -c surface.c

Page 334: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

334 ANNEXES ───────────────────────────────────────────────────

Une modification d'une dépendance implicite (circonf.o) ou explicite (pi.h) de la cible circonf.c est détectée par la commande et les deux dernières actions sont conformes aux règles implicites. D'où :

# fichier Makefile (version 2.4) resultat : principal.o circonf.o surface.o cc principal.o circonf.o surface.o -o resultat circonf.o : pi.h surface.o : pi.h

■ Variables internes du fichier Makefile

Une variable interne peut être définie selon la syntaxe :

OBJ = principal.o circonf.o surface.o

Son contenu est obtenu par les caractères $() selon la syntaxe $(OBJ).

� Exemple

# fichier Makefile (version 3) : variables internes OBJ et EXEC OBJ = principal.o circonf.o surface.o EXEC = resultat $(EXEC) : $(OBJ) cc $(OBJ) -o $(EXEC) circonf.o : circonf.c pi.h surface.o : surface.c pi.h

■ Variable interne initialisée à l'appel

Une variable interne peut être initialisée à l'appel de la commande ainsi :

make "variable_interne=valeur_d'initialisation"

� Exemple

make "EXEC=application1"

■ Cible par défaut ou explicite

La cible est explicite ou, par défaut, la première rencontrée.

� Exemple

make circonf.o

■ Autres utilisations du fichier Makefile

N'importe quelle commande peut être utilisée.

� Exemple

listing : principal.c cat principal.c

Page 335: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ANNEXE ───────────────────────────────────────────────────

335

■ Variables internes prédéfinies

La commande make utilise les macros suivantes : # ligne de commentaire $@ nom de la cible courante suffixée $* nom de la cible courante non suffixée $< dépendance à l'origine de la cible $? dépendance dont la date est postérieure à celle de la cible.

■ Règles implicites et règles explicites

La règle utilisée est explicite ou par défaut implicite.

■ Règles de précédence

Supposons l'existence des fichiers fichier.c, fichier.s, fichier.f. La dépendance implicite de la cible fichier.o est le fichier suffixé par .c. La modification explicite des règles de précédence implicites est effectuée avec le mot clé .SUFFIXE.

Synopsis .SUFFIXE cible dépendance1 dépendance2...dépendancen

L'ordre de priorité d'évaluation des dépendances est de gauche à droite.

� Exemple

.SUFFIXE .o .f .s .c

Le suffixe de la dépendance d'une cible.o est par .f, par défaut .s, par défaut .c.

■ Règles d'inférence implicites

Les règles d'inférence définissent les règles de production de la cible à partir de ses dépendances. Ainsi, les règles d'inférences implicites pour le choix du compilateur ainsi que ses options de compilation sont :

CC=cc CFLAGS=-O

Les règles d'inférences implicites des fichiers objets, des bibliothèques et de leurs clés sont les suivantes

# les dépendances sont suffixée par.c, la cible est suffixée par.a .c .a:

# compilation du fichier source (dépendance $<) et création de la cible (fichier objet) $(CC) -c $(CFLAGS) $<

# règle de production de la bibliothèque (la cible) à partir de la clé (dépendance) ar rv $@ $*.o # nettoyage des fichiers objets rm $*.o

Page 336: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

336 ANNEXES ───────────────────────────────────────────────────

■ Règles d'inférences de mise à jour de bibliothèques

Les parenthèses d'une dépendance indiquent que le nom intérieur est celui d'un fichier objet et que le nom extérieur est celui d'une bibliothèque le contenant. La mise à jour d'une bibliothèque est réalisée la règle de dépendance explicite :

libxx.a : libxx.a(mod.o) libxx.a(mod2.o)

La macro-instruction ar rv $@ $*.o est interprétée de la façon suivante :

la variable $@ est remplacée par libxx.a la variable $* est remplacée par la clé.

� Exemples

#fichier makefile version 4 EXEC = resultat OBJ = principal.o DIR = /home/philipp/biblio $(EXEC) : $(OBJ) lib2.a lib3.a cc -o $(EXEC) $(OBJ) -L$(DIR) -l2 -l3 lib2.a : lib2.a(surface.o) lib3.a : lib3.a(circonf.o) circonf.o : pi.h surface.o :pi.h #fichier makefile version 5 EXEC = resultat OBJ = principal.o DIR = /home/philipp/biblio BIB2 = 2 BIB3 = 3 LIB2 = lib2.a LIB3 = lib3.a $(EXEC) : $(OBJ) $(LIB2) $(LIB3) cc -o $(EXEC) $(OBJ) -L$(DIR) -l$(BIB2) -l$(BIB3) $(LIB2) : $(LIB2)(surface.o) $(LIB3) : $(LIB3)(circonf.o) circonf.o : pi.h surface.o : pi.h

■ Quelques options de la commande make

-i les codes d'erreurs sont ignorés -s exécution en mode silencieux -r ne tient pas compte des règles de dépendance par défaut -n affichage sans exécution de l'ensemble des commandes à effectuer pour la mise à

jour -p affichage des macros par défaut et de la liste des dépendances par défaut -d mode debug : affichage des informations relatives aux dates des fichiers.

■ La commande touch

La commande touch définit l'instant courant comme date de dernière modification d'un fichier ce qui fait croire à l'utilitaire make qu'un fichier vient d'être modifié.

Page 337: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

ANNEXE ───────────────────────────────────────────────────

337

5. REGLES DE PRIORITE DES OPERATEURS

Ce tableau présente les priorités, par ordre décroissant des opérateurs du langage C++.

Opérateur Désignation :: résolution de portée [] accès aux composantes d'un tableau () appel de fonction type() transtypage fonctionnel explicite . sélection de membre -> sélection de membre par déréférenciation ++ post incrémentation — post décrémentation new allocation dynamique d'instance new[] allocation dynamique de tableau d'instances delete destruction d'instance d'objet créé dynamiquement delete[] destruction de tableaux d'instances créés dynamiquement ++ pré/post incrémentation — pré/post décrémentation * déréférenciation & référence + plus unaire - moins unaire ! négation logique ~ complément logique sizeof taille d'instance d'objet ou de type typeid identification de type (type) transtypage const_cast transtypage de constance dynamic_cast transtypage dynamique reinterpret_cast transtypage de réinterprétation static _cast transtypage statique .* sélection de membre par pointeur sur membre ->* sélection de membre par pointeur sur membre par déréférenciation * multiplication / division % division entière modulo + addition - soustraction << décalage à gauche (bit à bit) >> décalage à droite (bit à bit) < test d'infériorité > test de supériorité <= infériorité ou égalité >= supériorité ou égalité == identité logique != différence logique & et logique (champ de bits)

Page 338: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

338 ANNEXES ───────────────────────────────────────────────────

Opérateur Désignation ^ ou exclusif logique (champ de bits) | ou inclusif logique (champ de bits) && et logique || ou logique ?: opérateur ternaire = opérateur d'affectation *= Opérateur composé : multiplication et affectation /= Opérateur composé : division et affectation %= Opérateur composé : modulo et affectation += Opérateur composé : addition et affectation -= Opérateur composé : soustraction et affectation <<= Opérateur composé : décalage à gauche et affectation >>= Opérateur composé : décalage à droite et affectation &= Opérateur composé : et logique et affectation |= Opérateur composé : ou inclusif logique et affectation ^= Opérateur composé : ou exclusif logique et affectation , Opérateur virgule

Page 339: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

BIBLIOGRAPHIE

■ Langage C

C as a Second Language For Native Speakers of Pascal, Müldner and Steele, Addison-Wesley.

The C Programming Language, Brian W. Kernigham and Dennis M. Ritchie, Prentice Hall.

■ Langage C++

Programmer en C++, Claude Delanoy, Eyrolles

The C++ Programming Language, Bjarne Stroustrup, Addison-Wesley.

Working Paper for Draft Proposed International Standard for Information Systems — Programming Language C++, ISO.

Premières leçons de programmation : J. ARSAC - 1980 Cedic-Nathan

The science of programming : DIJKSTRA - 1981 Digue

Pascal - Manuel de l'utilisateur : Kathleen JENSEN - Niklaus WIRTH

The art of programming - Donald Knuth (1968)

Some notes on structure programming - Edger Dijkstra

A discipline of programming - Edger Dijkstra

The science of programming - D. Gries (1981)

■ Bibliothèque C / appels systèmes POSIX et algorithmique

Programmation système en C sous Linux, Christophe Blaess, Eyrolles.

Introduction à l'algorithmique, Thomas Cormen, Charles Leiserson, et Ronald Rivest, Dunod.

Page 340: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INDEX

r -, 85

--, 86

! !, 85 !=, 85

" ", 83

# #define, 261

% %, 85

& &, 88, 153 &&, 85

( (), 81, 153 () ? :, 86

* *, 85, 153 *this, 175

. .*, 153

/ /, 85 //, 152

[ [], 153

^ ^, 88

| |, 88 ||, 85

~ ~, 89

+ +, 85 ++, 86

< <, 85 <<, 89 <=, 85

= ==, 85

> >, 85 ->, 153 >=, 85 >>, 89

0 0, 83 0x, 262 0X, 262

A abstraction, 23, 24, 69, 139, 143, 148 Abstraction, 146 abstraction des données, 23 abstraction procédurale, 23

Page 341: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INDEX ───────────────────────────────────────────────────

341

accès aléatoire, 320 accès concurrents, 322 access, 316 Access Control Entry, 327 Access Control List, 327 ACE, 327 ACL, 327 acledit, 328 aclget, 328 aclput, 328 action, 44 ADA, 11 adressage, 60 adressage absolu, 60 adressage direct, 60 adressage immédiat, 60 adressage indexé, 62 adressage indirect, 61 adressage normal, 60 adressage par base et déplacement, 61 adressage par rapport à l'adresse courante, 62 adressage relatif, 61 adressage symbolique, 17 adresse, 96, 177 adresse absolue, 61 adresse de base, 61 adresse de retour, 65 adresse effective, 60 adresse réelle, 60 adresse symbolique, 79 affectation de pointeur, 259 Aggregation, 142 agrégat, 113, 142 agrégation, 142 Agrégation, 142 algorithme, 16, 19, 20, 21, 22, 23, 24, 26, 27, 33, 34, 35, 36, 37, 38, 40, 48, 52, 55, 59, 65, 67, 89, 130, 133, 135, 300, 331, 333 algorithmes, 282 Al-Khoarizmi, 19 allocateurs, 282 allocation de mémoire, 141 allocation dynamique, 148, 149, 156, 179, 192, 193, 224, 226, 306, 337 alphabet, 43 alphanumérique, 17 ambiguïté, 156, 160, 164, 202, 267, 270, 293, 295, 296, 297, 312 American Standard Code for Information Interchange, 54 analyse amortie, 21 analyse du cas pire, 21 analyse en moyenne, 21 analyse formelle, 16 analyse syntaxique, 40 analyseur syntaxique, 17, 92 appel de fonction, 95 appel système, 315 argc, 108 arguments effectifs, 95 argv, 108 arité, 211

Array, 145 ASCII, 54, 77, 80, 83, 263 assembleur de haut niveau, 70 assesseur, 172 attribut, 113, 139 attribut protégé, 173 attributs, 113 attributs de classe, 170 auto, 179 avertissement, 91

B Bag, 145 BASIC, 26 bibliothèque, 18 bibliothèques, 3, 15, 16, 17, 25, 40, 51, 52, 70, 71, 284, 335, 336 bit de contrôle, 54 bit de gluance, 326 bit de remplissage, 121 bit le plus signicatif, 55 bit S, 326 bit suid, 326 bits d'alignement, 113 bits de poids faible, 55 bits de poids fort, 55 Bjarne Stroustrup, 151 bloc, 72 bogue de l'an 2000, 14 bogues, 16 bool, 215, 256, 257, 287, 309 branchement, 64 branchement conditionnel, 11 Branchement conditionnel, 26 branchements multiples, 46 bug, 16

C cache, 322 calcul de la longueur d'une chaîne de caractères, 107 calcul effectif, 19 calloc, 192, 193 caractère d'échappement, 83 case, 47 cast, 81 cerr, 153 chaîne de caractères, 79, 83 champ, 113, 170 champ de bits, 121 champs, 141 char, 77, 79 chdir, 330 chemin d'accès, 316 chgrp, 324 chmod, 316, 325 chown, 324, 329, 330 chroot, 330 cible, 331 cin, 153 class, 153, 170, 173, 265

Page 342: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

342 INDEX ───────────────────────────────────────────────────

classe, 3, 31, 41, 42, 63, 73, 74, 82, 113, 140, 141, 142, 143, 144, 145, 150, 155, 156, 158, 159, 165, 169, 170, 171, 172, 173, 174, 176, 177, 182, 184, 185, 186, 187, 188, 189, 190, 193, 195, 198, 199, 200, 201, 202, 203, 205, 206, 209, 211, 212, 213, 214, 215, 216, 217, 218, 219, 221, 222, 223, 224, 225, 227, 228, 229, 236, 238, 239, 240, 241, 242, 243, 244, 245, 246, 248, 249, 250, 251, 253, 254, 255, 256, 259, 260, 264, 266, 267, 268, 269, 270, 272, 273, 274, 275, 276, 277, 279, 281, 284, 285, 286, 287, 288, 291, 292, 295, 296, 300, 301, 302, 308, 309, 310, 311, 312, 313 classe abstraite, 141, 255 classe d'allocation, 179 classe de base, 141, 239 classe de classes, 268 classe de mémorisation, 74 classe dérivée, 141, 173, 239 classe encapsulée, 227 classe fille, 141, 239 classe générique, 264 classe imbriquée, 227 classe mère, 141, 239 classe modèle, 145 classe par défaut, 182 classe template, 268 classe Template, 145 classe virtuelle, 141, 249 classes d'objets, 170 clog, 153 close, 316, 320 codage, 53 code exécutable, 16 code mort, 18 code source, 16, 261, 262, 264, 331 coercition, 81 collection non ordonnée avec doublon, 145 collection non ordonnée sans doublon, 145 collection ordonnée avec doublon, 145 collection ordonnée et indexée, 145 comparaison de chaînes de caractères, 108 comparaison de pointeurs, 105 Compatibilité, 14 compilation, 16, 17, 82, 91, 92, 94, 144, 157, 165, 169, 172, 174, 182, 186, 194, 216, 259, 261, 264, 265, 266, 295, 331, 333, 335 compilation conditionnelle, 261 compilation séparée, 94 compilation simultanée, 94 complément à 1, 55 complément à 2, 55 complexité, 3, 12, 13, 16, 20, 22, 25, 33, 35, 36, 38, 44, 143, 148 comportement, 142 comportement asymptotique, 20, 21 composant logiciel, 15 concaténation de chaînes de caractères, 98 conditions limites, 16 confidentialité, 197 const, 78, 153, 157 constante, 262 constante de type caractère, 263 constante en virgule flottante, 263

constante entière, 262 constante manifeste, 262 constante symbolique, 152, 157, 261 constructeur, 141, 151, 184 constructeur copie, 221, 223 constructeur copie implicite, 221 constructeur explicite, 185 constructeur implicite, 185 constructeur par défaut, 150, 185, 188, 189, 193, 195, 196, 202, 203, 204, 205, 206, 207, 208, 209, 220, 248, 250 Container, 145 conteneur, 256, 281 conteneur d'objets, 145 conversion de pointeurs, 105 conversion explicite, 81 conversion implicite, 80, 189 couche objet, 139, 142 cout, 153, 154 creat, 316, 317, 330 création, 315, 317 cycle de vie d'un fichier, 316 cycle perpétuel, 12

D débogeur symbolique, 17 debugger, 17 déclaration, 43, 74, 156 déclaration du type du résultat d'une fonction, 94 déclaration struct, 114 déconstructeur, 143 default, 47 définition, 43, 74, 114, 156 définition d'une fonction, 91 définition littérale, 83 delayed write, 324 delete, 149, 150, 156, 193, 194, 195, 196, 203, 204, 206, 207, 212, 216, 217, 218, 221, 222, 224, 226, 234, 260, 287, 305, 306, 337 delete[], 194 délimiteur, 72, 83, 152, 227 délimiteur de fin de chaîne, 83 dépendances, 331 dépilement, 65 déplacement, 61 descripteur de fichier, 316 destructeur, 141, 143, 151, 184, 190, 195, 203, 206, 216, 217, 218, 221, 224, 234, 248, 259, 260, 286, 287, 288, 289, 300, 305, 306 destructeur explicite, 190 destructeur par défaut, 190 destruction, 315 Dijkstra, 11, 39 diviser pour régner, 24 Diviser pour régner, 24 do, 48 document technique de référence, 15 domaine de définition, 51 domaine de valeur, 51, 91 domaines de définition, 91 donnée alphanumérique, 54

Page 343: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INDEX ───────────────────────────────────────────────────

343

donnée membre, 170 donnée numérique, 54 donnée structurée, 113 données, 139 données membres, 113, 141, 173, 174, 184, 185, 188, 198, 203, 205, 212, 214, 227, 228, 249, 275, 277 double, 78, 79 double précision, 56 droits d'accès, 324 dup, 330 dynamic_cast, 250

E EBCDIC, 54, 77 écriture, 315 éditeur de texte, 16 Efficacité, 14 else...if, 46 émetteur, 140 empilement, 65 encapsulation, 139, 142, 143, 146, 148, 172, 214, 259 encapsulé, 172 enjoliveur de programmes, 17 enregistrement logique, 113 ensemble, 145 ensemble d'objets, 145 entrée standard, 71, 319 Entrées/sorties de bas niveau, 323 Entrées/sorties de haut niveau, 323 Entrées/sorties en mode bloc, 324 Entrées/sorties en mode caractère, 324 Entrées/sorties en mode cru, 324 Entrées/sorties en mode raw, 324 enum, 79, 125 énumération, 76, 152, 158 EOF, 83 Ergonomie, 14 erreur de troncature, 56 escape sequence, 83 exception, 151, 307 exec*, 330 exit, 330 explicit, 190 export, 284 expression, 42, 71 Extended Binary Coded Decimal Interchange Code, 54 Extensibilité, 14 extern, 156, 179

F fchown, 329 fcntl, 330 fermeture, 315, 320 fichier cible, 331 fichier en tête, 157 fichier ordinaire, 321 fichier répertoire, 321 fichier spécial, 322 fichier standard, 321

fichier standard d'affichage des diagnostics d'erreurs, 321 fichier standard de sortie, 321 fichier standard d'entrée, 321 FIFO, 66 file d'attente, 66 first in, first out, 66 float, 77, 79 flux, 153 fonction, 153 fonction 91, 127 fonction atoi, 127 fonction constructeur, 143 fonction de fonction, 92 fonction générique, 159 fonction générique amie, 267 fonction inline, 170 fonction membre, 141, 170 fonction récursive, 32, 33, 95 fonction sans argument, 93 fonction statique, 198 fonction statique, 183 fonction template, 266 for, 45 fork, 330 format libre, 153, 154 forme normalisée, 57 free, 192 friend, 214 fstat, 330 fstream.h, 155

G garbage collector, 143 Generalization, 141 généricité, 281 Généricité, 144, 279 getAttr, 172 geteuid, 330 getpid, 330 getppid, 330 getuid, 330 goto, 50 grammaire, 41 graphe des dépendances, 331 Gries, 11, 37, 339 group, 324 groupe, 324

H header, 157 héritage, 141, 239 héritage multiple, 151 héritage multiple, 239 Héritage multiple, 141 héritage qualifié private, 241 héritage qualifié protected, 241 héritage qualifié public, 241 héritage simple, 151, 239 hexadécimal, 17, 53, 54, 58, 83, 123, 262

Page 344: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

344 INDEX ───────────────────────────────────────────────────

hiérarchie de classes, 141 Hiérarchie de classes, 141

I identificateur, 73 identificateur de fonction, 92 identificateur d'objet, 139 if...else, 46 ifstream, 155 implémentation, 142 impression des composantes d'un tableau, 86 inclusion de fichiers, 261 inconsistance, 24 indexation, 62 induction mathématique, 24 Inheritance, 141 initialisation, 184 initialisation d'un pointeur, 105 initialisation d'une variable structurée, 114 inode, 322, 323 instance, 170 instances, 140 instanciation, 141, 170 instanciation des paramètres génériques, 264, 270 instanciation du paramètre générique, 278 instanciation explicite, 271 instanciation implicite, 270 instruction, 72 instruction exécutable, 42, 156 instruction généralisée, 72 instruction non exécutable, 42 instruction symbolique, 159 instruction vide, 72 int, 77, 79 intégrité, 98 Intégrité, 14 interface d'accès, 143 interface d'objet, 143 interruption, 64 invariant, 24, 31, 78 invariant de boucle, 30, 78 iostream.h, 153 istream, 155 itérateur, 281 itération, 28, 29, 50

J Java, 3, 11, 23, 28, 39, 42, 44, 52, 69, 140, 179 jeu d'essais, 19

K Kernighan et Ritchie, 3, 71 kill, 330 Knuth, 11, 339

L LAC, 327 langage machine, 11, 16, 17, 19, 69

last in, first out, 66 Le bit s, 326 lecture, 315 Left value, 75 levée de l'encapsulation, 214 libération de la mémoire, 156 lien dynamique, 253 lien hypertexte, 323 lien matériel, 322, 323 Lien matériel, 322 lien symbolique, 322, 323 LIFO, 66 link, 316, 330 liste, 66, 145, 326 liste chaînée, 66, 67, 131, 132, 133, 287 liste d'énumération, 125 liste vide, 159 listes de contrôle d'accès, 327 ln, 323 long, 78, 79 long double, 78, 79 long int, 79 lseek, 316, 320 lstat, 330 Lvaleur, 75, 85, 118, 165, 224 Lvaleur, 75 L-valeur, 117 Lvaleur modifiable, 75 Lvalue, 75

M macro-définitions de type fonction, 261 macro-substitution du premier ordre, 262 main, 52 maintenance, 16 make, 331, 332, 333, 334, 335, 336 makefile, 331 Makefile, 331 malloc, 192, 193 message, 140, 154, 155, 176, 213 message d'erreur, 91 métaclasse, 268, 277 métalangage, 40 méthode, 23, 113, 139, 141, 170, 172 méthode abstraite, 255 méthode constructeur, 185 méthode des approximations successives, 15, 30 méthode inline, 172, 174 méthode multi-classes, 141 méthode opérateur, 211 méthode spécifiée constante, 175 méthode statique, 200 méthode virtuelle, 144, 253, 279 méthode virtuelle pure, 255 méthodes, 139 méthodes itératives, 24, 26, 28 mkfifo, 330 mknod, 330 mode bloc, 322 mode c, 322 modèle abstrait, 139

Page 345: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INDEX ───────────────────────────────────────────────────

345

modèle abstrait, 140 modificateur, 172 modulo, 85 most significant bit, 55 mount, 330 msgctl, 331 msgget, 331 msgrcv, 331 msgsnd, 331 multiplication égyptienne, 89, 127 mutable, 158

N new, 149, 150, 156, 157, 193, 194, 195, 196, 204, 206, 207, 212, 216, 217, 218, 221, 222, 223, 224, 226, 234, 236, 259, 287, 306, 307, 310, 337 niveau d'indirection, 61 noalias, 78 nom externe, 316 nom symbolique, 43 nombre entier non signé, 55 nombre entier signé, 55 nommage, 152, 158 nommage des agrégats, 152 non signé intégral, 191

O Object Interface, 143 objet, 139, 155 Objet, 139 objet de référence, 163 objet encapsulé, 143 objet générique, 144 objet modèle, 151 objet paramétré, 144, 145, 151 Objet polymorphique, 145 objet statique, 198 objet structuré, 113 objet template, 264 objets polymorphiques, 145, 256, 309 octal, 262 ofstream, 155 open, 316, 330 opendir, 317 opérateur, 42, 155 opérateur &, 163 opérateur *, 163 opérateur ~, 190 opérateur ->, 115 opérateur arithmétique, 41 opérateur binaire, 84 opérateur composé, 87 opérateur d'adresse, 163 opérateur de coercition, 71, 81 opérateur de décalage logique, 89 opérateur de déréférenciation, 96, 153, 163 opérateur de référence, 96, 153, 163, 229 opérateur de résolution de portée, 198, 249, 253, 291, 292, 293, 296 opérateur de résolution de visibilité, 156

opérateur de sélection de membre, 115, 170, 229 opérateur de transtypage, 71, 81 opérateur d'indirection, 96, 163, 192 opérateur fonctionnel, 230 opérateur logique, 41 opérateur modulo, 85 opérateur relationnel, 41 opérateur scope, 170 opérateur surdéfini, 211 opérateur ternaire, 84, 86 opérateur unaire, 84 opérateurs sur les champs de bits, 88 opération, 140 opération de décalage logique, 89 opération et logique, 88 opération ou exclusif, 88 opération ou logique, 88 opérations, 139 opérations sur les bits, 88 operator, 211 opérer, 139 ordre d'évaluation, 90 ostream, 155 ouverture, 315 overriding, 240 Overriding, 144

P padding, 121 paramètre template template, 277 Pascal, 28, 339 PASCAL, 3, 11, 40, 43, 339 pause, 330 picture element, 60 pile, 33, 38, 64, 65, 66, 75, 98, 99, 110, 111, 112, 146, 147, 148, 149, 150, 164, 173, 286, 287, 288, 299, 300 pile des adresses de retour, 65 pile des arguments, 110 pile d'exécution, 98, 99, 110, 164 pile logicielle, 64 pile matérielle, 64 pilotes, 324 pipe, 71, 322, 330 pixels, 60 pointeur, 63, 96 pointeur constant déréférencé, 98, 102, 109, 163 pointeur de pile, 64 pointeur déréférencé, 163, 165, 272, 310 pointeur générique, 192, 226 polymorphisme, 144, 159 POP, 65 POPD, 65 portabilité, 56, 69, 70, 126, 143 Portabilité, 14 portage, 16 portée, 160, 179 POSIX P1006.3, 327 post-incrémentation, 86 post-indexation, 62 pré-incrémentation, 86 pré-indexation, 62

Page 346: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

346 INDEX ───────────────────────────────────────────────────

préprocesseur, 159 principe de localisation, 46 principe du partage, 24 printf, 101 printf, 111 Prise de communication, 324 private, 173, 241 problème calculatoire, 19 Problème du représentant de commerce, 22 problème indécidable, 22 problème NP-complet, 20 procédure, 51, 93 procédures, 139 profileur, 18 programmation dynamique, 24 programmation modulaire, 25 propriétaire, 324 propriétaire effectif, 326 propriétaire réel, 326 protected, 173, 241 Prototypage, 159 prototype, 43, 92, 94, 163 prototype de fonction, 160 prototype des fonctions sans argument, 93 public, 173, 241 pure virtual method, 255 PUSH, 65

Q qsort, 52 qualificatif, 78 qualification de l’héritage, 239 qualifications du contrôle d'accès, 241 Quick sort, 52

R ramasseur d'ordures, 143 read, 316 read ahead, 324 realloc, 192, 193 récepteur, 140 récursion, 24, 174 récursivité, 95, 115 Récursivité, 32, 35, 95 redéfinition d'une méthode, 144 réels DCB, 56 réels flottants, 56 réels virgule fixe, 56 référence, 16, 17, 139 référence, 163 référence en avant, 17, 172, 179, 181 référence externe, 17 référence interne, 17 register, 179, 184 registre de base, 61 registres, 74 règles de précédence, 335 règles de production, 331 règles d'inférence, 335 règles explicites, 335

règles implicites, 335 rémanence, 197 remontée, 33, 300 représentation interne, 23, 142 requalification, 244 résultat d'une fonction, 51, 93 return, 51, 93, 159 Réutilisabilité, 14 robustesse, 143 Robustesse, 14 rupture de séquence, 11, 45, 172 Rvaleur, 75, 85, 118 R-valeur, 117

S sac, 145 scanf, 101, 111 SCCS, 18 sélecteur d'objet membre, 153 sélecteur sur pointeur, 153 sémantique, 3, 16, 23, 26, 40, 69, 123, 159, 172, 185, 211, 213, 217, 225, 253, 296, 321 semctl, 331 semget, 331 semop, 331 Set, 145 setAttr, 172 seteuid, 330 setuid, 326, 330 shmat, 331 shmctl, 331 shmdt, 331 shmget, 331 short, 78, 79 short int, 79 signal, 330 signature, 140, 159, 160, 175, 187, 198, 279, 296, 308 signature d'une fonction, 160, 211 signatures, 143 signed, 78, 79 signed int, 79 signed long, 79 signed long int, 79 signed short, 79 signed short int, 79 simple précision, 56 Simula, 140 simulation, 16 size_t, 191 sizeof, 191, 212 Smalltalk, 11 SmallTalk, 140 sortie standard, 71, 319 soustraction de pointeurs, 105 spécialisation partielle, 273 spécialisation totale, 275 spécificateur const, 164 spécificateur d'accès, 173 spécificateur inline, 174 spécification, 14, 15, 25, 144, 266, 270, 271, 272, 279 stack pointer, 64

Page 347: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

INDEX ───────────────────────────────────────────────────

347

Standart Template Library, 281 stat, 316, 330 static, 179, 198 stdarg.h., 111 stddef.h, 191 stdio.h, 49, 83, 84, 86, 87, 95, 98, 101, 107, 116, 117, 119, 120, 121, 126, 128, 131, 137, 147, 191, 192, 286, 332 sticky bit, 326 STL, 281 strcat, 98 strcmp, 108 streams, 153 struct, 79, 113, 153, 170, 173 structure, 113 structure de bloc, 72 structure de données, 64 structure de données abstraite, 23, 64, 113 substitution symbolique, 261, 262 suite de Fibonacci, 95 Suite de Fibonacci, 36 support, 16 surcharge d'un opérateur, 144 surcharge d'une fonction, 144 surcharge d'une méthode, 144 surcharge d'une procédure, 144 surdéfinition, 142, 144 surdéfinition de l'opérateur [], 219 surdéfinition de l'opérateur d'affectation, 223 surdéfinition des fonctions, 159 surdéfinition des opérateurs, 42 surdéfinition d'un opérateur, 144 surdéfinition d'une fonction, 144 surdéfinition d'une méthode, 144 surdéfinition d'une procédure, 144 switch, 47 symlink, 330 syntaxe, 40

T table, 64 table des symboles, 17, 272 tableau, 42, 79, 145, 153 tableau de dimension incomplète, 108 tableau de structures, 120 tableau variables structurées, 120 template, 144, 264 Terminateur, 72 test, 11, 45 test de fin de boucle, 28 test de régression, 18 tests de couverture, 18 this, 175, 200, 213 token, 72, 261 touch, 336 Tours de Hanoi, 37 transcodage, 53 translatable, 61 translation d'adresse, 61 transmission des arguments, 98 transmission des arguments par adresse, 101

transmission par adresse, 98, 163 transmission par référence, 99, 151, 163, 164, 221, 222, 301 transmission par valeur, 98, 163 transtypage, 81, 105, 109, 156, 189, 190, 192, 193, 201, 202, 203, 216, 217, 228, 231, 242, 243, 250, 259, 260, 311, 312, 313, 314, 337 transtypage explicite, 189 transtypage fonctionnel, 156 transtypage fonctionnel, 189 transtypage implicite, 242 transtypage par appel de fonction, 156 tube, 71, 317, 330 tube nommé, 322 typage, 142 type, 74 type abstrait, 23, 147, 148, 149 type agrégat, 76 type arithmétique, 76 type arithmétiques, 152 type booléen, 152 type composite, 76 type de base, 76 type de structure abstrait, 114 type dérivé, 77 type dérivés, 153 type du résultat d'une fonction, 91 type entier non signé, 76 type entier signé, 76 type énuméré, 76, 152, 158 type flottant, 76, 152 type fonction, 76 type générique, 264, 265, 268, 269 type incomplet, 77 type intégral, 77, 152 type majeur, 77 type non qualifié, 77 type objet, 77 type pointeur, 76 type prédéfini, 75 type scalaire, 76 type structuré, 76 type symbolique, 159 type synonyme, 126 type tableau, 76 type union, 76 typedef, 79, 126, 158 typename, 281 types abstraits, 142, 148 types de base, 77, 152, 211, 311

U umask, 325 umount, 330 undef, 263 union, 79, 113, 123, 153 unité syntaxique, 25, 111, 261, 262 Unix, 3, 23 unlink, 316, 330 unsigned, 78, 79 unsigned char, 79

Page 348: DU PROCEDURAL A L'OBJET : LES LANGAGES C ET C++karlaoui.free.fr/Site Epmi/Programmation/Cours/Les_langages_C_et_C... · ... BASES DE LA PROGRAMMATION ORIENTEE ... Pointeur sur les

348 INDEX ───────────────────────────────────────────────────

unsigned int, 79 unsigned long, 79 unsigned long int, 79 unsigned short, 79 unsigned short int, 79 utilisateur propriétaire, 324

V va_arg, 111 va_end, 111, 112 va_list, 111 va_start, 111 valeur par défaut, 161, 265 Valeur par défaut d'un type générique, 265 Validité, 14 variable, 42, 73 variable automatique, 179, 182 variable externe, 179 variable globale, 179 variable interne-statique, 182 variable locale, 179 variable pointeur, 96 variable qualifiée constante, 82 variable qualifiée constante de type caractère, 83 variable qualifiée constante en virgule flottante, 82 variable qualifiée constante entière, 82 variable register, 179 variable statique, 179, 182, 197 variable statique externe, 183 variable structurée, 113 variable symbolique, 43 variables d'instance, 141 variables structurées, 113, 114, 116, 120, 121, 123, 126 Vérifiabilité, 14 virtual, 253 visibilité, 179 void, 71, 79 void, 93 void, 93 volatile, 78 Von Neumann, 11

W wait, 330 while, 47 write, 316

Z zone, 113