Accueil | informatique |cours du langage c++ | cours en ligne du langage c++ | Chapitre 1 : Présentation du langage C++
|
Après 1982, les deux langages C et C++ ont continué d’évoluer parallèlement. C a été normalisé par l’ANSI en 1990. C++ a connu plusieurs versions, jusqu’à sa normalisation par l’ANSI en 1998.
Nous vous proposons ici d’examiner les caratéristiques essentielles de C++. Pour vous permettre de mieux les appréhender, nous commencerons par de brefs rappels concernant la programmation structurée (ou procédurale) et par un exposé succinct des concepts de P.O.O. .
• l’exactitude : aptitude d’un logiciel à fournir les résultats voulus, dans des conditions normales d’utilisation (par exemple, données correspondant aux spécifications) ;
• la robustesse : aptitude à bien réagir lorsque l’on s’écarte des conditions normales d’utilisation ;
• l’extensibilité : facilité avec laquelle un programme pourra être adapté pour satisfaire à une évolution des spécifications ;
• la réutilisabilité : possibilité d’utiliser certaines parties (modules) du logiciel pour résoudre un autre problème ;
• la portabilité : facilité avec laquelle on peut exploiter un même logiciel dans différentes implémentations ;
• l’efficience : temps d’exécution, taille mémoire...
La contradiction n’est souvent qu’apparente et essentiellement liée à l’importance des projets concernés. Par exemple, il est facile d’écrire un programme exact et robuste lorsqu’il comporte une centaine d’instructions ; il en va tout autrement lorsqu’il s’agit d’un projet de dix hommes-années ! De même, les aspects extensibilité et réutilisabilité n’auront guère d’importance dans le premier cas, alors qu’ils seront probablement cruciaux dans le second, ne serait-ce que pour des raisons économiques.
La programmation structurée a manifestement fait progresser la qualité de la production des logiciels. Notamment, elle a permis de structurer les programmes, et, partant, d’en améliorer l’exactitude et la robustesse. On avait espéré qu’elle permettrait également d’en améliorer
Programmes = algorithmes + structures de données |
Le grand mérite de l’encapsulation est que, vu de l’extérieur, un objet se caractérise uniquement par les spécifications de ses méthodes, la manière dont sont réellement implantées les données étant sans importance. On décrit souvent une telle situation en disant qu’elle réalise une « abstraction des données » (ce qui exprime bien que les détails concrets d’implémentation sont cachés). À ce propos, on peut remarquer qu’en programmation structurée, une procédure pouvait également être caractérisée (de l’extérieur) par ses spécifications, mais que, faute d’encapsulation, l’abstraction des données n’était pas réalisée.
L’encapsulation des données présente un intérêt manifeste en matière de qualité de logiciel. Elle facilite considérablement la maintenance : une modification éventuelle de la structure des données d’un objet n’a d’incidence que sur l’objet lui-même ; les utilisateurs de l’objet ne seront pas concernés par la teneur de cette modification (ce qui n’était bien sûr pas le cas avec la programmation structurée). De la même manière, l’encapsulation des données facilite grandement la réutilisation d’un objet.
Plus précisément, on utilise chaque objet comme s’il était de cette classe de base, mais son comportement effectif dépend de sa classe effective (dérivée de cette classe de base), en particulier de la manière dont ses propres méthodes ont été redéfinies. Le polymorphisme améliore l’extensibilité des programmes, en permettant d’ajouter de nouveaux objets dans un scénario préétabli et, éventuellement, écrit avant d’avoir connaissance du type effectif de ces objets.
À l’opposé, on peut toujours tenter d’appliquer, avec plus ou moins de bonheur, ce que nous aurions tendance à nommer « une philosophie P.O.O. » à un langage classique (Pascal, C...). On retrouve là une idée comparable à celle qui consistait à appliquer les principes de la programmation structurée à des langages comme Fortran ou Basic.
Le langage C++ se situe à mi-chemin entre ces deux points de vue. Il a en effet été obtenu en à un langage procédural répandu (C) les outils permettant de mettre en œuvre tous les principes de la P.O.O.. Programmer en C++ va donc plus loin qu’adopter une philosophie P.O.O. en C, mais moins loin que de faire de la P.O.O. pure avec Eiffel !
À l’époque où elle est apparue, la solution adoptée par B. Stroustrup avait le mérite de préserver l’existant, grâce à la quasi-compatibilité avec C++, de programmes déjà écrits en C. Elle permettait également une « transition en douceur » de la programmation structurée vers la P.O.O.. Malheureusement, la contrepartie de cette souplesse est que la qualité des programmes écrits en C++ dépendra étroitement des décisions du développeur. Par exemple, il restera tout à fait possible de faire cohabiter des objets (dignes de ce nom, parce que réalisant une parfaite encapsulation de leurs données) avec des fonctions classiques réalisant des effets de bord sur des variables globales... Quoi qu’il en soit, il ne faudra pas perdre de vue que, de par la nature même du langage, on ne pourra exploiter toute la richesse de C++ qu’en se plaçant dans un contexte hybride mêlant programmation procédurale (notamment des fonctions « usuelles ») et P.O.O.. Ce n’est que par une bonne maîtrise du langage que le programmeur pourra réaliser du code de bonne qualité.
En ce qui concerne les types de base des données, on trouvera :
• les types numériques usuels : entiers avec différentes capacités, flottants avec différentes capacités et précisions ;
• le type caractère ;
• une convention de représentation des chaînes de caractères ; on verra qu’il ne s’agit pas d’un type chaîne à part entière, lequel apparaîtra en fait dans les possibilités orientées objet de C++, sous forme d’une classe.
On trouvera les agrégats de données que sont :
• les tableaux : ensembles d’éléments de même type, de taille fixée à la compilation ;
• les structures : ensembles d’éléments de types quelconques ; on verra qu’elles serviront de « précurseurs » aux classes.
Les opérateurs de C++ sont très nombreux. En plus des opérateurs arithmétiques ((+, -, *, /) et logiques (et, ou, non), on trouvera notamment des opérateurs d’affectation originaux permettant de simplifier en x += y des affectations de la forme x = x + y (on notera qu’en C++, l’affectation est un opérateur, pas une instruction !).
Les structures de contrôle comprennent :
• la structure de choix : instruction if ;
• la structure de choix multiple : instruction switch ;
• les structures de boucle de type « tant que » et « jusqu’à » : instructions do... while et while ;
• une structure très générale permettant de programmer, entre autres, une « boucle avec compteur » : instruction for .
Les pointeurs sont assez spécifiques à C++ (et à C). Assez curieusement, on verra qu’ils sont également liés aux tableaux et à la convention de représentation des chaînes. Ces aspects sont en fait inhérents à l’historique du langage, dont les germes remontent finalement aux années 80 : à l’époque, on cherchait autant à simplifier l’écriture des compilateurs du langage qu’à sécuriser les programmes !
La notion de procédure se retrouvera en C++ dans la notion de fonction. La transmissions des arguments pourra s’y faire, au choix du programmeur : par valeur, par référence (ce qui n’était pas possible en C) ou encore par le biais de manipulation de pointeurs. On notera que ces fonctions sont définies indépendamment de toute classe ; on les nommera souvent des « fonctions ordinaires », par opposition aux méthodes des classes.
C++ dispose de la notion de classe (généralisation de la notion de type défini par l’utilisateur). Une classe comportera :
• la description d’une structure de données ;
• des méthodes.
Sur le plan du vocabulaire, C++ utilise des termes qui lui sont propres. On parle en effet de :
• « membres données » pour désigner les différents membres de la structure de données associée à une classe ;
• « fonctions membres » pour désigner les méthodes.
À partir d’une classe, on pourra « instancier » des objets (nous dirons aussi créer des objets) de deux façons différentes :
• soit par des déclarations usuelles, les emplacements étant alors gérés automatiquement sous forme de ce que l’on nomme une « pile » ;
• soit par allocation dynamique dans ce que l’on nomme un « tas », les emplacements étant alors gérés par le programmeur lui-même.
C++ permet l’encapsulation des données, mais il ne l’impose pas. On peut le regretter mais il ne faut pas perdre de vue que, par sa conception même (extension de C), le C++ ne peut pas être un langage de P.O.O. pure. Bien entendu, il reste toujours possible au concepteur de faire preuve de rigueur, en s’astreignant à certaines règles telles que l’encapsulation absolue.
Comme la plupart des langages objets, C++ permet de définir ce que l’on nomme des « constructeurs » de classe. Un constructeur est une fonction membre particulière qui est exécutée au moment de la création d’un objet de la classe. Le constructeur peut notamment prendre en charge l’initialisation d’un objet, au sens le plus large du terme, c’est-à-dire sa mise dans un état initial permettant son bon fonctionnement ultérieur ; il peut s’agir de banales initialisations de membres données, mais également d’une préparation plus élaborée correspondant au déroulement d’instructions, voire d’une allocation dynamique d’emplacements nécessaires à l’utilisation de l’objet. L’existence d’un constructeur garantit que l’objet sera toujours initialisé, ce qui constitue manifestement une sécurité.
De manière similaire, une classe peut disposer d’un « destructeur », fonction membre exécutée au moment de la destruction d’un objet. Celle-ci présentera surtout un intérêt dans le cas d’objets effectuant des allocations dynamiques d’emplacements ; ces derniers pourront être libérés par le destructeur.
Une des originalités de C++ par rapport à d’autres langages de P.O.O. réside dans la possibilité de définir des « fonctions amies d’une classe ». Il s’agit, soit de fonctions usuelles, soit de fonctions membres qui sont autorisées (par une classe) à accéder aux données (encapsulées) de la classe. Certes, le principe d’encapsulation est violé, mais uniquement par des fonctions dûment autorisées à le faire.
La classe est un type défini par l’utilisateur. La notion de « surdéfinition d’opérateurs » va permettre de doter cette classe d’opérations analogues à celles que l’on rencontre pour les types prédéfinis. Par exemple, on pourra définir une classe complexe (destinée à représenter des nombres complexes) et la munir des opérations d’addition, de soustraction, de multiplication et de division. Qui plus est, ces opérations pourront utiliser les symboles existants : +, -, *, /. On verra que, dans certains cas, cette surdéfinition nécessitera le recours à la notion de fonction amie.
Le langage C disposait déjà de possibilités de conversions explicites ou implicites. C++ permet de les élargir aux types définis par l’utilisateur que sont les classes. Par exemple, on pourra donner un sens à la conversion int -> complexe ou à la conversion complexe -> float ( complexe étant une classe).
Naturellement, C++ dispose de l’héritage et même (ce qui est peu commun) de possibilités dites « d’héritage multiple » permettant à une classe d’hériter simultanément de plusieurs autres. Le polymorphisme est mis en place, sur la demande explicite du programmeur, par le biais de ce que l’on nomme (curieusement) des fonctions virtuelles (en Java, le polymorphisme est « natif » et le programmeur n’a donc pas en s’en préoccuper).
Les entrées-sorties de C++ sont différentes de celles du C, car elle reposent sur la notion de « flots » (classes particulières), ce qui permet notamment de leur donner un sens pour les types définis par l’utilisateur que sont les classes (grâce au mécanisme de surdéfinition d’opérateur).
Avec sa normalisation, le C++ a été doté de la notion de patron ( template en anglais). Un patron permet de définir des modèles paramétrables par des types, et utilisables pour générer différentes classes ou différentes fonctions qualifiées parfois de génériques, même si cette généricité n’est pas totalement intégrée dans le langage lui-même, comme c’est par exemple le cas avec ADA.
En toute rigueur, certaines des extensions du C++ ne sont pas liées à la P.O.O. Elles pourraient en fait être ajoutées au langage C, sans qu’il soit pour autant « orienté objet ». Ici, nous étudierons directement le C++, de sorte que ces extensions non P.O.O. seront tout naturellement présentées au fil des prochains chapitres.
Par ailleurs, certaines possibilités du C deviennent inutiles (ou redondantes) en C++. Par exemple, C++ a introduit de nouvelles possibilités d’entrées-sorties (basées sur la notion de flot) qui rendent superflues les fonctions standards de C telles que printf ou scanf . Ou encore, C++ dispose d’opérateurs de gestion dynamique ( new et delete ) qui remplacent avantageusement les fonctions malloc , calloc et free du C.
Comme ici, nous étudions directement le langage C++, il va de soi que ces « possibilités inutiles » du C ne seront pas étudiées en détail. Nous nous contenterons de les mentionner à simple titre informatif, dans des remarques titrées « En C ».
Par ailleurs, il existe quelques incompatibilités mineures entre C et C++. Là encore, elles ne poseront aucun problème à qui ne connaît pas le C. À titre d’information, elles seront récapitulées en Annexe H.
En outre, C++ dispose de la totalité de la bibliothèque standard du C, y compris de fonctions devenues inutiles ou redondantes. Bien entendu, là encore, les fonctions indispensables seront introduites au fil des différents chapitres. L’Annexe G viendra récapituler les principa- les fonctions héritées de C.
les autres chapitres
Chapitre 2 : Généralités sur le langage C++ .
Chapitre 3 : Les types de base de C++ .
Chapitre 4 : Opérateurs et expressions .
Chapitre 5 : Les entrées-sorties conversationnelles de C++ .
voir aussi
cours en PDF du langage c++
cours vidéos du langage c++
exercices en ligne du langage c++
Exercices en PDF du langage c++
exrcices vidéos du langage c++