Accéder au contenu principal

Présentation du langage c++ - informatique


Accueil | informatique |cours du langage c++ | cours en ligne du langage c++ | Chapitre 1 : Présentation du langage C++

chapitre 1 : présentation du langage c++


Le langage C++ a été conçu à partir de 1982 par Bjarne Stroustrup (AT&T Bell Laborato- ries), dès 1982, comme une extension du langage C, lui-même créé dès 1972 par Denis Ritchie, formalisé par Kerninghan et Ritchie en 1978. L’objectif principal de B. Stroustrup était d’ajouter des classes au langage C et donc, en quelque sorte, de « greffer » sur un langage de programmation procédurale classique des possibilités de « programmation orientée objet » (en abrégé P.O.O.).
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. .

1 Programmation structurée et programmation orientée objet
1.1 Problématique de la programmation
Jusqu’à maintenant, l’activité de programmation a toujours suscité des réactions diverses allant jusqu’à la contradiction totale. Pour certains, en effet, il ne s’agit que d’un jeu de construction enfantin, dans lequel il suffit d’enchaîner des instructions élémentaires (en nombre restreint) pour parvenir à résoudre n’importe quel problème ou presque. Pour d’autres, au contraire, il s’agit de produire (au sens industriel du terme) des logiciels avec des exigences de qualité qu’on tente de mesurer suivant certains critères, notamment :
• 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.

1.2 La programmation structurée
En programmation structurée, un programme est formé de la réunion de différentes procédures et de différentes structures de données, généralement indépendantes de ces procédures. D’autre part, les procédures utilisent un certain nombre de structures de contrôle bien définies (on parle parfois de « programmation sans go to »).
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

l’extensibilité et la réutilisabilité. Or, en pratique, on s’est aperçu que l’adaptation ou la réutilisation d’un logiciel conduisait souvent à « casser » le module intéressant, et ceci parce qu’il était nécessaire de remettre en cause une structure de données. Or, ce type de difficulté apparaît précisément à cause du découplage existant entre les données et les procédures, lequel se trouve résumé par ce que l’on nomme « l’équation de Wirth » :
Programmes = algorithmes + structures de données

1.3 Les apports de la programmation orientée objet
1.3.1 Objet
C’est là qu’intervient la programmation orientée objet (en abrégé P.O.O), fondée justement sur le concept d’ , à savoir une association des données et des procédures (qu’on appelle alors méthodes) agissant sur ces données. Par analogie avec l’équation de Wirth, on pourrait dire que l’équation de la P.O.O. est :
Méthodes + Données = Objet

1.3.2 Encapsulation
Mais cette association est plus qu’une simple juxtaposition. En effet, dans ce que l’on pourrait qualifier de P.O.O. « pure » , on réalise ce que l’on nomme une . Cela signifie qu’il n’est pas possible d’agir directement sur les données d’un objet ; il est nécessaire de passer par l’intermédiaire de ses méthodes, qui jouent ainsi le rôle d’interface obligatoire. On traduit parfois cela en disant que l’appel d’une méthode est en fait l’envoi d’un « message » à l’objet.
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.

1.3.3 Classe
En P.O.O. apparaît généralement le concept de classe , qui correspond simplement à la généralisation de la notion de type que l’on rencontre dans les langages classiques. En effet, une classe n’est rien d’autre que la description d’un ensemble d’objets ayant une structure de données commune et disposant des mêmes méthodes. Les objets apparaissent alors comme des variables d’un tel type classe (on dit aussi qu’un objet est une « instance » de sa classe).
1.3.4 Héritage
Un autre concept important en P.O.O. est celui d’héritage. Il permet de définir une nouvelle classe à partir d’une classe existante (qu’on réutilise en bloc !), à laquelle on ajoute de nouvelles données et de nouvelles méthodes. La conception de la nouvelle classe, qui « hérite » des propriétés et des aptitudes de l’ancienne, peut ainsi s’appuyer sur des réalisations antérieures parfaitement au point et les « spécialiser » à volonté. Comme on peut s’en douter, l’héritage facilite largement la réutilisation de produits existants, d’autant plus qu’il peut être réitéré autant de fois que nécessaire (la classe C peut hériter de B, qui elle-même hérite de A).
1.3.5 Polymorphisme
Généralement, en P.O.O, une classe dérivée peut « redéfinir » (c’est-à-dire modifier) certaines des méthodes héritées de sa classe de base. Cette possibilité est la clé de ce que l’on nomme le polymorphisme, c’est-à-dire la possibilité de traiter de la même manière des objets de types différents, pour peu qu’ils soient tous de classes dérivées de la même classe de base.
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.

1.4 P.O.O., langages de programmation et C++
Nous venons d’énoncer les grands principes de la P.O.O. sans nous attacher à un langage particulier Or manifestement, certains langages peuvent être conçus (de toutes pièces) pour appliquer à la lettre ces principes et réaliser ce que nous nommons de la P.O.O. « pure ». C’est par exemple le cas de Simula, Smalltalk ou, plus récemment, Eiffel ou Java. Le même phénomène a eu lieu, en son temps, pour la programmation structurée avec Pascal.
À 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é.

2. C++ et la programmation structurée
Les possibilités de programmation structurée de C++ sont en fait celles du langage C et sont assez proches de celles des autres langages, à l’exception des pointeurs.
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.

3. C++ et la programmation orientée objet
Les possibilités de P.O.O. représentent bien sûr l’essentiel de l’apport de C++ au langage C.
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.

4. C et C++
Précédemment, nous avons dit, d’une façon quelque peu simpliste, que C++ se présentait comme un « sur-ensemble » du langage C, offrant des possibilités de P.O.O.
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.

5 C++ et la bibliothèque standard
Comme tout langage, C++ dispose d’une bibliothèque standard, c’est-à-dire de fonctions et de classes prédéfinies. Elle comporte notamment de nombreux patrons de classes et de fonc- tions permettant de mettre en œuvre les structures de données les plus importantes (vecteurs dynamiques, listes chaînées, chaînes...) et les algorithmes les plus usuels. Nous les étudierons en détail le moment venu.
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 1 : Présentation du langage C++ .
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 ligne du langage c++
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++