E N C G - Casablanca

Bienvenue sur le Forum Officiel des Etudiants de l'Ecole Nationale de Commerce et de Gestion - Casablanca
 
AccueilFAQRechercherS'enregistrerMembresGroupesConnexion
CHATBox ... discutez en ligne sur le Forum !
BIENVENUE AUX NOUVEAUX ENCG'istes

Partagez | 
 

 Introduction au langage C/C++ (2)

Voir le sujet précédent Voir le sujet suivant Aller en bas 
AuteurMessage
Quantum Theory



Messages : 106
Réputation : 5
Date d'inscription : 10/08/2010
Localisation : A chercher entre le "A" et le "Z" // :)
Profession : Etudiant

MessageSujet: Introduction au langage C/C++ (2)   Ven 3 Sep - 1:18


Chapitre 4

Les fonctions

Le meilleure façon de rédiger un programme complexe est de le construire à partir d'éléments

ou de modules indépendants, qu'on assemble un peu comme des briques (On peut aussi penser au

proverbe : diviser pour régner lorsqu'il s'agit de maîtriser une tâche complexe). Chaque langage de

programmation a une façon à lui de désigner et d'organiser ces modules ; en C++, on ne dispose que

d'une seule sorte de module, la fonction. Il existe des fonctions « standards », préprogrammées, qui

économisent bien du travail, et les «fonctions utilisateur», écrite par le programmeur. Je rappelle

que l'ensemble des modules ou fonctions présentes dans un programme est régi par une fonction

principale, qui s'appelle justement « main».

4.1 Les fonctions mathématiques

Toutes les fonctions mathématiques courantes sont prédéfinies en C (un avantage certain sur

Pascal). Les plus courantes sont rassemblées dans le tableau suivant.

Fonction Description Exemple

sqrt(x) racine carrée sqrt(121.0)

¡! 11.0
exp(x) exponentielle,

e x exp(2) ¡! 7.389056
log(x) logarithme à base

e log(7.389056) ¡! 2.0
log10(x) logarithme à base 10 log10(2.0)

¡! 0.30103
fabs(x) valeur absolue fabs(-2.0)

¡! 2.0
ceil(x) arrondi au plus petit ceil(3.2)

¡! 3.0
entier non inférieur à

x ceil(-4.7) ¡! -4.0
floor(x) arrondi au plus grand floor(7.6)

¡! 7.0
entier non supérieur à

x floor(-8.Cool ¡! -9.0
pow(x,y) puissance,

x y pow(2,10) ¡! 1024.0
pow(27.0,0.3333)

¡! 3.0
sin(x) sinus (argument en radian) sin(1.5707963)

¡! 1.0
cos(x) cosinus (idem) cos(1.5707963)

¡! 0.0
tan(x) tangente (idem) tan(0.0)

¡! 0.0
Comme pour la lecture et l'écriture, ces fonctions ne font pas partie du langage C++ au sens

strict : il faut donc prévenir le compilateur que l'on souhaite les utiliser, en insérant au début la

directive

#include <cmath> . Ceci n'est pas suffisant pour certains systèmes ; il faudra encore, au
moment de la compilation, indiquer que l'on veut utiliser une librairie de fonctions mathématiques

« -lm » pour Linux).

Les fonctions mathématiques convertissent automatiquement leur argument dans le «type

double» (double précision, 13 chiffres significatifs, 8 octets) et renvoient un résultat de même

20

Intro_C

21
type. La notion de «type» sera détaillée un peu plus loin.

Pour C++, le générateur de nombres aléatoires n'est pas une fonction mathématique. La fonction

correspondante s'appelle

rand() (sans arguments). Le résultat est un nombre entier aléatoire
compris entre 0 et

RAND_MAX (en majuscules). Ces deux entités sont définies dans un autre fichier
d'entête, « cstdlib».

4.2 Fonctions de l'utilisateur

J'aborde maintenant l'écriture de fonctions propres à l'utilisateur. Toute fonction doit en principe

correspondre à une tâche précise et bien définie et son nom doit refléter cette tâche. De plus,

une fonction bien conçue est appelée à être réutilisée souvent et donc à économiser du temps de

programmeur. Voici un premier exemple simpliste, une fonction qui calcule le cube d'un entier.

Elle fait partie d'un programme qui dresse la table des cubes des dix premiers entiers.

1

/ ? f_cube . cpp : exemple de f onc t i on ? /
2

#include <ios t ream>
3

#include <c s t d l i b >
4

using namespace s td ;
5

int cube ( int ) ;
6

int main ( void ){
7

int x ;
8

for ( x = 1 ; x <= 1 0 ; x++)
9 cout << x << "\ t " << cube ( x ) << endl ;

10 system( " pause " ) ;

11

return 0 ;
12 }

13

int cube ( int y ){
14

return y ? y ? y ;
15 }

On a vu, dans les chapitres précédents, que ce programme, grâce à l'entête

<iostream> , pouvait
utiliser les fonctions d'écriture comme

cout << . La ligne 5 joue un peu le même rôle : elle prévient
le compilateur que je vais utiliser une fonction recevant un argument entier, dont le résultat sera

aussi un entier et qui s'appellera cube. Le compilateur pourra ainsi vérifier que, tout au long du

programme, mes instructions seront conformes à cette définition. La ligne 5 est le « prototype »

de la fonction cube ou encore sa déclaration. On peut mentionner des noms de variables dans la

déclaration ; le compilateur n'en tient aucun compte, mais cela peut aider à la compréhension du

programme. Si la fonction ne renvoyait aucune information vers le programme principal (comme

par exemple une fonction destinée à afficher un message), il faudrait la déclarer de type

void .
La fonction principale commence à la ligne 6 ; elle contient essentiellement une boucle « for»

qui répète 10 fois la ligne 9, où se fait tout le travail. Cette ligne contient un «appel» de la fonction

cube

, avec son «argument effectif», x.
La fonction

cube elle-même est définie lignes 13-15 ; elle se présente de façon assez semblable à
main

: une entête où sont précisés le type de la fonction, son nom, le type et le nom de l'argument. Le
corps de la fonction (entre accolades) est ici réduit à peu de chose : le résultat ligne 14. Remarquez

que le prototype est suivi d'un point-virgule, alors que l'entête est suivie d'une accolade ouvrante

qui marque le début du corps.

Le fonctionnement du programme est simple. L'ordinateur exécute l'une après l'autre les instructions

de

main . Lorsqu'il parvient à la ligne 9, il imprime la valeur de x , puis exécute les
instructions contenues dans la fonction (une seule ici), en remplaçant « l'argument formel»

y par
la valeur courante de

x . L'identificateur cube contient la valeur de x 3 . L'ordinateur reprend la suite
des instructions de

main , c'est-à-dire qu'il imprime la valeur de x 3 , incrémente x et recommence
tout, tant que la condition de contrôle est vérifiée.

Intro_C

22
Voici un deuxième exemple, la recherche du maximum de trois nombres.

1

/ ? max3 . cpp : maximum de t r o i s nombres ? /
2

#include <ios t ream>
3

#include <c s t d l i b >
4

using namespace s td ;
5

int maximum( int x , int y , int z ){
6

int max = x ;
7

i f ( y > max) max = y ;
8

i f ( z > max) max = z ;
9

return max ;
10 }

11

int main ( void ){
12

int a , b , c ;
13 cout << "donnez t r o i s e n t i e r s : " ;

14 c in >> a >> b >> c ;

15 cout << "Le plus grand e s t : " << maximum( a , b , c ) << endl ;

16 system( " pause " ) ;

17

return 0 ;
18 }

J'ai adopté ici une présentation différente. De même que l'on peut déclarer et initialiser une variable

en seule instruction (

int uvw = 321 ), on peut déclarer et définir une fonction en une fois. Dans
ce cas le prototype est inutile et disparaît. Il semble que la majorité des programmeurs préfère la

première présentation (déclaration et définition séparées), peut-être parce que l'ensemble est plus

lisible si le corps de la fonction est volumineux.

4.3 Types et conversion de type

4.3.1 des types différents

Toutes les variables d'un programme en C++ doivent avoir un type ; celui-ci peut être soit

prédéfini par le langage soit défini par l'utilisateur. On peut comparer la mémoire de l'ordinateur à

une bibliothèque, avec des casiers de tailles différentes : des petits casiers pour les livres de poche,

des gros casiers pour les atlas. En ce qui concerne les variables numériques, il existe 8 types prédéfinis,

auxquels on peut encore rattacher le type « char», très voisin d'un entier. Ils sont listés dans

le tableau ci-dessous, par ordre de taille décroissante (et donc de nombre de chiffres significatifs

décroissant).

spécification spécification

Type pour printf pour scanf

long double %Lf %Lf

double %f %lf

float %f %f

unsigned long int %lu %lu

long int %ld %ld

unsigned int %u %u

int %d %d

short %hd %hd

char %c %c

Tous les compilateurs ne reconnaissent pas tous ces types, en particulier « long double». J'ai

indiqué dans le même tableau les codes de formattage utilisés en C pour les entrées-sorties ; remar

Intro_
C

23
quez le piège classique : la spécification de format des nombres en double précision, très commun

en calcul scientifique, est différente pour la lecture et l'écriture ! Les utilisateurs de C++ n'ont pas

à tenir compte de cette remarque, puisque

cin et cout formattent automatiquement les données
qui leur sont soumises.

4.3.2 promotion

J'ai déjà dit que les fonctions de la bibliothèque mathématique attendaient un argument de

type « double» ; je peux quand même calculer la racine carrée de 4 par l'expression

sqrt(4) . Dans
ce cas, il y a « promotion (conversion) automatique» de l'argument en son équivalent

double , soit
4.00, cela sans perte d'information (je peux ranger une livre de poche dans le casier destiné à une

encyclopédie). Le résultat sera donné en double précision.

La même opération de promotion a lieu chaque fois que j'écrit une expression en mélangeant

des types ; tous les arguments sont temporairement convertis dans le type le plus précis. Ainsi, si

x = 1.0

et y sont des float , alors que a = 2 est un int , l'affectation y = x + a + 1; donnera
à

y la valeur 4.00. Il est toutefois peu prudent de faire aveuglément confiance à ce mécanisme. De
plus, le compilateur va protester si j'écris simplement

y = a .
D'autre part, il faut faire attention aux divisions entre entiers. Ainsi, le fragment de programme

int a = 2, b = 3, c = 7;

cout << a/b << '\t' << c/a << endl;

affichera

0 3 Pour obtenir des résultats plus précis (mais fractionnaires), il faut convertir l'un
(au moins) des facteurs en un nombre fractionnaire. Ceci se fait proprement par un «transtypage»

(«cast» en anglais) :

int a = 2, b = 3, c = 7;

cout << (double)a/b << '\t' << (double)c/(double)a << endl;

Cette écriture est conforme à la norme C et acceptée en C++. En C++, il faut en principe écrire

static_cast<double>(a)/b

.
4.3.3 dégradation

La conversion vers un type moins précis est en fait une dégradation (comme si je voulais

absolument faire pénétrer un dictionnaire dans le casier d'un livre de poche). Si j'appelle la fonction

cube

du paragraphe précédent (qui attend un argument entier) avec un argument fractionnaire,
cube(2.7)

, celui-ci sera tronqué à sa partie entière et j'obtiendrais le résultat 8 au lieu de 19.683.
Pour prendre volontairement la partie entière, il existe les fonctions

floor et ceil , décrites plus
haut.

4.4 Passage des arguments

Il y a en principe deux façons simples de transmettre un (ou des) argument(s) à une fonction :

le « passage par valeur» et le « passage par référence» (on dit aussi « passage par adresse »). En

C++, sauf recours à un formalisme spécial, les arguments simples (type entier, flottant, double,

caractère) sont passés par valeur, ce qui veut dire qu'une copie de l'argument d'appel est transmise

à la fonction. Ceci a un avantage évident : si l'argument est modifié dans le corps de la fonction, cela

n'affecte pas la variable du programme principal. Dans d'autres langages (Fortran), on pratique

l'appel par référence : toute modification de l'argument dans la fonction appelée est répercutée dans

le programme principal. La convention du C++ a aussi un inconvénient : comme la fonction ne peut

(par l'intermédiaire du mot réservé

return ) renvoyer qu'une valeur unique, comment pourrais-je
construire une fonction dont le résultat serait un ensemble de valeurs (composantes d'un vecteur

par exemple) ? La solution sera abordée dans un prochain chapitre.

Intro_C

24
4.5 portée des variables

À partir du moment on l'on commence à décomposer un programme en blocs et en fonctions,

on doit se demander quel est le domaine de validité de chaque variable ou dans quelle portion du

programme chaque variable est définie.

Si un programme se compose de plusieurs blocs, il est possible de définir des variables à l'intérieur

de chaque bloc, ces variables étant invisibles à l'extérieur de leur bloc de définition, comme

dans l'exemple un peu artificiel qui suit.

1

// por t e e1 . cpp : p o r t é e des v a r i a b l e s
2

#include <ios t ream>
3

#include <c s t d l i b >
4

using namespace s td ;
5

void fonc ( int ) ;
6

int nb = 1000 , val = 128;
7

int main ( void ){
8 cout << " d i v e r s e s v a l e ur s des v a r i a b l e s : \n" ;

9 cout << "\t

¡ nb dans main : " << nb << "\n\n" ;
10 cout << "\t

¡ val dans main : " << val << "\n\n" ;
11 fonc ( 1 0 0 ) ;

12 cout << "\t

¡ nb apr e s fonc : " << nb << "\n" ;
13 system( " pause " ) ;

14

return 0 ;
15 }

16

void fonc ( int nb){
17 cout << "nb au debut de fonc : " << nb << endl << endl ;

18 cout << " val au debut de fonc : " << val << endl << endl ;

19 {

20 nb = 2 0 ;

21 cout << "nb dans l e premier bloc de fonc : " << nb << "\n\n" ;

22 }

23 {

24 nb = 3 0 ;

25 cout << "nb dans l e deuxieme bloc de fonc : " << nb <<endl << endl ;

26 cout << " val dans l e deuxieme bloc de fonc : " << val << "\n\n" ;

27 }

28 }

J'ai défini « au niveau global» ou encore « à la profondeur 0», c'est à dire en dehors de toute fonction

ou bloc, des entiers

nb = 1000, val = 128 . Ces objets (identificateurs) sont visibles de tout le
programme, sauf s'ils sont masqués par une définition ultérieure (voir plus loin). On peut définir

des variables dans une fonction ou à l'intérieur d'un bloc (entre accolades). Ces définitions sont

locales au bloc ou à la fonction. Les variables déclarées dans un bloc (fonction) ne sont visibles que

dans ce bloc (fonction) et dans les sous-blocs qu'il (ou elle) contient. D'autre part, une déclaration

masque toutes les déclarations d'une variable de même nom à une profondeur inférieure.

L'exécution de portée1.exe donne comme résultat

Intro_C

25
diverses valeurs des variables:

- nb dans main: 1000

- val dans main: 128

nb au debut de fonc: 100

val au debut de fonc: 128

nb dans le premier bloc de fonc: 20

nb dans le deuxieme bloc de fonc: 30

val dans le deuxieme bloc de fonc: 128

- nb apres fonc: 1000

La logique de ce programme peut être représentée par le dessin ci-contre. Le programme est

contenu dans le fichier

portee1.cpp , analogue à une grande boite et qui constitue l'espace de travail
du compilateur. À l'intérieur, on trouve quatre objets,

nb, val,main et fonc , dont deux sont aussi
des boites. Ces déclarations sont globales. Dans

main , on s'intéresse aux deux variables nb et val .
Comme on ne trouve pas de déclaration dans

main , ce sont les initialisations précédentes qui sont
valables.

main appelle fonc avec l'argument nb = 100 et c'est cette valeur qui a cours à l'intérieur
de la boite

fonc . Seulement, fonc contient deux autres boites, anonymes et qui contiennent chacune
une définition de

nb . C'est cette définition qui est la bonne dans la boite où elle se trouve.
Intro_C

26
Intro_C

portee1.cpp
nb = 1000 ; val = 128

main

cout

<< nb ; cout << val ;
fonc(100) ;

cout

<< nb ;
fonc(nb)

cout

<< nb ; cout << val ;
nb = 20 ; cout

<< nb ;
nb = 30 ; cout

<< nb ; cout << val
Intro_C

27
Les déclarations de fonctions sont toujours globales (à la différence de Pascal, où l'on peut définir

une fonction à l'intérieur d'une autre fonction. Voici encore un exemple, à peine plus compliqué.

Pour gagner de la place, j'ai fait figurer plusieurs instructions sur la même ligne, ce qui ne favorise

pas la lisibilité ; elles sont bien sûr lues et exécutées de gauche à droite.

1

// por t e e2 . cpp : aut r e exemple de p o r t e e des v a r i a b l e s
2

#include <ios t ream>
3

#include <c s t d l i b >
4

using namespace s td ;
5

void a ( void ) ; void b( void ) ; void c ( void ) ;
6

int x = 1 ;
7

int main ( void ){
8

int x = 8 ;
9 cout << "x , d e f i n i dans main , vaut : " << x << endl ;

10 a ( ) ; b ( ) ; c ( ) ;

11 a ( ) ; b ( ) ; c ( ) ;

12 cout << "x , d e f i n i dans main , vaut : " << x << endl ;

13 system( " pause " ) ;

14

return 0 ;
15 }

16

void a ( void ){
17

int x = 2 5 ;
18 cout << "x au debut de a : " << x << endl ;

19 x++;

20 cout << "x a l a f i n de a : "<< x << endl ;

21 }

22

void b( void ){
23

s tat ic int x = 5 0 ;
24 cout << "x ( s t a t i c ) au debut de b : "<< x << endl ;

25 x++;

26 cout << "x ( s t a t i c ) a l a f i n de b : " << x << endl ;

27 }

28

void c ( void ){
29 cout << "x ( g l o b a l ) au debut de c : " << x << endl ;

30 x

? = 1 0 ;
31 cout << "x ( g l o b a l ) a l a f i n de c : " << x << endl ;

32 }

J'ai introduit une nouveauté, le type de variable

static ; une telle variable conserve sa valeur
entre deux appels de la fonction. Essayez de prévoir ce que fera ce programme avant de regarder

le résultat.

Intro_C

28
x, defini dans main, vaut : 8

x au debut de a: 25

x a la fin de a: 26

x(static) au debut de b: 50

x(static) a la fin de b: 51

x(global) au debut de c: 1

x(global) a la fin de c: 10

x au debut de a: 25

x a la fin de a: 26

x(static) au debut de b: 51

x(static) a la fin de b: 52

x(global) au debut de c: 10

x(global) a la fin de c: 100

x, defini dans main, vaut : 8

4.6 La récurrence

Beaucoup d'objets mathématiques peuvent être définis de manière récursive, par une relation

de récurrence. Le langage C++ permet de même des définitions de fonctions par récurrence. La

fonction factorielle est l'exemple traditionnel dans ce domaine. Sa définition mathématique explicite

est

n

! = n ¢ ( n ¡ 1) ¢ ( n ¡ 2) ¢ ¢ ¢ 2 ¢ 1 :
Je peux aussi utiliser la définition récursive équivalente

n

! = n ¢ ( n ¡ 1)!
En C++, j'écrirai soit un fragment de programme itératif :

fact = 1;

for( cptr = n; cptr >= 1; cptr--)

fact *= cptr;

soit un programme appelant une fonction définie par récurrence :

1

/ ? facto_r . cpp : f a c t o r i e l l e , forme r é c u r s i v e ? /
2

#include <ios t ream>
3

#include <c s t d l i b >
4

using namespace s td ;
5

int f a c t ( int ) ;
6

int main ( void ){
7

int i ;
8

for ( i = 1 ; i <= 1 0 ; i++)
9 cout << i << " ! = " << f a c t ( i ) << endl ;

10

return 0 ;
11 }

12

int f a c t ( int x ){
13

i f ( x <= 1)
14

return 1 ;
15

el se
16

return ( x ? f a c t (x ¡ 1) ) ;
17 }

La programmation récursive peut être très élégante et concise. Elle souffre de deux inconvénients.

Les risques d'erreur spectaculaire sont grands. Si je me trompe dans la condition d'arrêt ou

Intro_C

29
dans la définition de la fonction, la récurrence peut devenir infinie : il n'y a plus qu'à arracher la

prise de courant. De plus, le temps de calcul est souvent élevé, car l'ordinateur doit effectivement

évaluer toutes les valeurs intermédiaires de la fonction récurrente,

fact ici.
4.7 Passage d'arguments par référence

On a vu que les arguments «simples» d'une fonction (nombres, caractères) étaient transmis

«par valeur» : la fonction reçoit une copie de l'argument. Si cette pratique accroît la sécurité de

la programmation (il est impossible de modifier, depuis la fonction, une variable du programme

principal), elle ne facilite pas les communications entre fonctions. Si une fonction calcule des valeurs,

comment renvoyer ces données dans le programme principal ? C'est possible pour une valeur

unique : l'intruction

return permet justement d'affecter à l'identificateur de la fonction une valeur
visible du programme appelant.

Les tableaux sont traités de façon diamètralement opposée : ils sont transmis «par référence» ou

«par adresse». Tout se passe comme si la fonction et le programme appelant partageait les mêmes

données : toute modification apportée à un élément du tableau dans la fonction est immédiatement

répercutée dans

main . L'utilisation de tableaux comme arguments de fonctions est détaillée dans
le chapitre 6.

Il serait commode de pouvoir transmettre plusieurs valeurs de nature différente d'une fonction

à une autre : comme elles sont de nature différente, elles ne peuvent pas être des éléments d'un tableau.

C++ offre cependant cette possibilité, appelée «transmission par référence». Un paramètre

par référence est un pseudonyme (un alias) de l'argument correspondant. Pour passer un paramètre

par référence, il suffit de faire suivre le type du paramètre (dans l'entête et dans le prototype de la

fonction) par une esperluette (&). Les connaisseurs du C peuvent considérer que ce mécanisme est

une version simplifiée du passage par adresse, à l'aide d'un pointeur. Le programme suivant met

en oeuvre le passage normal (par valeur) et le passage par référence.

1

// pas sag e . cpp : pas sag e d ' argument par v a l e u r e t par r e f e r enc e
2

#include <ios t ream>
3

#include <c s t d l i b >
4

using namespace s td ;
5

int cubeVal ( int ) ;
6

void cubeRef ( int &);
7

int main ( void ){
8

int a = 3 , b = ¡ 5;
9 cout << "a avant cubeVal : " << a << endl ;

10 cout << " r e s u l t a t de cubeVal : " << cubeVal ( a ) << endl ;

11 cout << "a apr e s cubeVal : " << a << endl ;

12 cout << "b avant cubeRef : " << b << endl ;

13 cubeRef (b ) ;

14 cout << "b apr e s cubeRef : " << b << endl ;

15 system( " pause " ) ;

16

return 0 ;
17 }

18

int cubeVal ( int aval ){
19

return aval ? = aval ? aval ;
20 }

21

void cubeRef ( int & aRef ){
22 aRef

? = aRef ? aRef ;
23 }

avec le résultat :

Intro_C

30
a avant cubeVal: 3

resultat de cubeVal: 27

a apres cubeVal: 3

b avant cubeRef: -5

b apres cubeRef: -125

Remarquez, ligne 21, que la manipulation d'un paramètre passé par référence est identique à

celle d'un paramètre normal.

Il est possible (encore que d'un intérêt faible) d'utiliser un alias dans le corps d'une fonction,

comme dans l'extrait ci-dessous.

int i = 1;

int &iRef = i;

++iRef; //i est incrémenté par l'intermédiaire de son pseudo

Toute variable qui en référence une autre doit être initialisée au moment de sa déclaration ; on

peut comprendre cette contrainte en remarquant que

iRef , par exemple est déclarée comme une
copie, mais une copie de quoi demande le compilateur ? On doit répondre immédiatement à cette

question.


Chapitre 5

Entrées et sorties

5.1 Généralités

Un programme, écrit par exemple en C++, doit souvent lire des données pour pouvoir fonctionner

; il écrit fréquemment des résultats.

On dit que l'échange de données entre un programme et l'extérieur fait intervenir un « flot »,

c'est à dire une suite d'octets. Pour l'utilisateur (mais pas pour la machine), chacun de ces octets a

une signification : lettre, nombre, pixel, échantillon de son... Pendant une opération de lecture (plus

généralement d'entrée de données), les octets passent d'un dispositif extérieur (clavier, disquette,

modem) à la mémoire centrale. Pendant une opération d'écriture (sortie de données), les octets

passent de la mémoire centrale vers un dispositif extérieur (écran, imprimante, disque).

On distingue les E/S (entrées/sorties) de bas niveau (non-formattées), où l'on se contente de

spécifier le nombre d'octets à transmettre entre tel et tel partenaire, et les E/S de haut niveau

(formattées), où les octets sont regroupés en ensembles significatifs pour l'utilisateur (nombres

entiers, fractionnaires, chaînes de caractères). Je ne parlerai pas des premières, les secondes suffisant

à toutes les applications habituelles.

Les manuels emploient souvent les expressions de «dispositif standard de sortie», l'écran, et

de «dispositif standard d'entrée», le clavier. Vous avez déjà constaté que l'on pouvait capter des

données tapées au clavier et afficher des résultats à l'écran très simplement à l'aide des «opérateurs»

cin

et cout . Dans ce chapitre, je vais détailler les propriétés de ces opérateurs, je montrerai
comment on peut personnaliser les affichages et j'expliquerai comment on peut lire et écrire dans un

fichier sur disque ou sur disquette. Ce dernier point est évidemment important pour les applications,

mais aussi dans le cadre de l'enseignement. Pendant la mise au point d'un programme, il est

fastidieux de retaper les données à chaque essai et bien plus commode de les lire sur le disque dur.

Une dernière remarque générale. Les concepteurs du C++ se sont donné du mal pour que ces

opérations de lecture et d'écriture soient «robustes», ce qui signifie que tous les types classiques de

données sont lus ou écrits correctement, sans précaution particulière (ce qui est loin d'être le cas

en C pur).

5.2 Les opérateurs d'insertion et d'extraction

L'écriture à l'écran se fait, comme nous le savons, à l'aide de « l'opérateur»

<< , qui insère les
éléments à afficher dans le flot de sortie représenté par

cout (dans le sens des flèches). Je rappelle
que l'on peut écrire indifféremment

cout << "Bienvenue à tous" << endl;

\\

cout << "Bienvenue à tous\n";

31

Intro_C

32
\\

cout << "Bienvenue";

cout << " à";

cout << " tous";

cout << endl;

\\

cout << "Bienvenue" << " à"

<< " tous" << '\n';

(associativité de gauche à droite et équivalence du caractère d'échappement

\n avec le « manipulateur
de flot»,

endl ).
Les mêmes règles s'appliquent à l'affichage des nombres, entiers ou fractionnaires ; l'opérateur

<<

est « assez malin » pour savoir à quel type de donnée il a affaire et pour agir en conséquence.
Symmétriquement, la lecture des données se fait au moyen de « l'opérateur d'extraction du flot

d'entrée»,

>> , dans la direction des flèches.
Les opérateurs

<< et >> ont une priorité élevée : il ne faut donc pas hésiter à utiliser des
parenthèses, comme dans le morceau de code suivant, pour être sûr de l'interprétation.

cout << "donnez deux entiers: ";

cin >> x >> y;

cout << x << (x == y ? "est " : "n'est pas ") << "égal à" << y ;

qui n'est pas correctement compilé si l'on enlève les parenthèses.

Lorsque l'on veut lire une série de données en nombre inconnu, on peut utiliser une boucle «

while», comme ceci

cout << "entrez un nombre (fin-de-fichier pour arrêter): ";

while( cin >> nb){

.............

cout << "entrez un nombre (fin-de-fichier pour arrêter): ";

}

La lecture s'interrompra lorsque l'utilisateur tapera « ctrl-Z» (fin de fichier). L'extraction fournit

un résultat nul, interprété comme faux.

On parvient au même résultat en examinant un par un les caractères entrés et en interrompant

la lecture dès qu'on détecte le caractère fin-de-fichier (EOF) :

char c;

while ( (c = cin.get()) != EOF) {

.........

}

5.3 Opérateurs de mise en forme des nombres

Les entêtes de la plupart des fonctions décrites dans ce paragraphe se trouvent dans le fichier

« iomanip», qu'il faut appeler par

#include <iomanip> ; cette bibliothèque contient <iostream> ,
il est donc en principe inutile d'appeler cette dernière, mais tous les compilateurs ne sont pas au

courant.

5.3.1 nombre de chiffres significatifs

Pour les applications scientifiques et techniques, il est commode de pouvoir choisir le nombre

de chiffres après la virgule ; il existe deux méthodes pratiques de le faire, montrées dans l'exemple

ci-dessous.

Intro_C

33
1

// decimal . cpp : nombre de de c imal e s
2

#include <iomanip>
3

#include <c s t d l i b >
4

using namespace s td ;
5

int main ( void )
6 {

7

double r2 = s q r t ( 2 ) ;
8

int nbchs ;
9 cout << " spontanement : " << r2 << endl ;

10 cout << " avec l a f o n c t i o n cout . p r e c i s i o n : " << endl ;

11

for ( nbchs = 1 ; nbchs <= 1 0 ; nbchs++){
12 cout . p r e c i s i o n ( nbchs ) ;

13 cout << r2 << endl ;

14 }

15 cout << " spontanement : " << r2 << endl ;

16 cout << " avec l e manipulateur s e t p r e c i s i o n : " << endl ;

17

for ( nbchs = 1 ; nbchs <= 1 0 ; nbchs++)
18 cout << s e t p r e c i s i o n ( nbchs ) << r2 << endl ;

19 cout << " spontanement : " << r2 << endl ;

20 system( " pause " ) ;

21

return 0 ;
22 }

avec le résultat

spontanement: 1.41421

avec la fonction cout.precision:

1

1.4

1.41

1.414

1.4142

1.41421

1.414214

1.4142136

1.41421356

1.414213562

spontanement: 1.414213562

avec le manipulateur setprecision:

1

1.4

1.41

1.414

1.4142

1.41421

1.414214

1.4142136

1.41421356

1.414213562

spontanement: 1.414213562

Spontanément, C++ affiche

p 2 avec 5 chiffres après la virgule ; j'ai modifié ce comportement
d'abord à l'aide de la fonction

cout.precision(n) , dont l'argument est le nombre de chiffres puis
avec le « manipulateur»

setprecision(n) . Remarquez que l'effet produit sur le nombre de chiffres
Intro_C

34
est permanent, tant qu'une nouvelle instruction ne vient pas le modifier.

5.3.2 largeur de champ

Il est possible de choisir la largeur de la zone (champ) où va apparaître une donnée, grâce à la

fonction

cout.width ou en insérant le manipulateur setwidth(n) .
1

//l_champ . cpp
2

#include <iomanip>
3

#include <c s t d l i b >
4

using namespace s td ;
5

int main ( void ){
6

double mi l l e = 1000 , c e n tmi l l e = 1 0 0 0 0 0 . 5 ;
7 cout << " spontanement : " << endl

8 << mi l l e << '

? ' << c e n tmi l l e << ' ? ' <<endl << endl ;
9

for ( int nbchs = 3 ; nbchs <= 8 ; nbchs++){
10 cout . width ( nbchs ) ;

11 cout << mi l l e << '

? '<< c e n tmi l l e << ' ? ' << endl ;
12 }

13 cout << endl ;

14 cout << " spontanement : " << endl

15 << mi l l e << '

? '<< c e n tmi l l e << ' ? ' << endl << endl ;
16

for ( int nbchs = 3 ; nbchs <= 8 ; nbchs++)
17 cout << setw ( nbchs ) << mi l l e << '

? ' << c e n tmi l l e << ' ? ' << endl ;
18 cout << endl ;

19 cout << " spontanement : " << endl

20 << mi l l e << '

? ' << c e n tmi l l e << ' ? ' << endl ;
21 system( " pause " ) ;

22 }

Avec le compilateur que j'utilise, j'obtiens le même résultat dans les deux cas. Au contraire des

modifications de précision, ces instructions n'ont pas d'effet permanent : il faut les renouveler pour

chaque objet à imprimer.

Intro_C

35
spontanement:

1000*100000*

1000*100000*

1000*100000*

1000*100000*

1000*100000*

1000*100000*

1000*100000*

spontanement:

1000*100000*

1000*100000*

1000*100000*

1000*100000*

1000*100000*

1000*100000*

1000*100000*

spontanement:

1000*100000*

C++ est très économe sur les zéros qu'il affiche : le nombre 1.2 sera affiché tel quel, même

si la précision demandée est de 6 chiffres après la virgule. D'autre part, C++ fait de son mieux

pour corriger les erreurs de programmation :

mille et centmille seront affichés tant bien que mal,
même si vous ne réservez que deux caractères pour le faire. Le cas de

centmille (100000.5) est
particulier : si on n'indique pas la précision, C++ imprime 6 chiffres (100000), la largeur du champ

ne fait rien à l'affaire. Enfin, vous remarquez que le nombre 1000 apparaît à droite du champ. Cous

trouverez dans les manuels plus détaillés des instructions pour modifier ce cadrage.

5.3.3 notation scientifique

Pour les nombres très grands ou très petits, il est préférable d'employer la notation scientifique.

C++ sait le faire, à condition de lui demander.

1

// s c i e n t i f . cpp : format s pour l e s nombres
2

#include <ios t ream>
3

#include <c s t d l i b >
4

using namespace s td ;
5

int main ( void ){
6

double r2 = s q r t ( 2 ) , micro = 1.234 e ¡ 9, mega = 1.234 e9 ;
7

int nb ;
8 cout << " spontanement : " << ' \ t ' << ' \ t ' << r2 << " " << micro

9 << " " << mega << endl ;

10 cout . s e t f ( i o s : : s c i e n t i f i c ) ;

11 cout << " no t a t i on s c i e n t i f i q u e : " << ' \ t ' << r2 << ' \ t '

12 << micro << ' \ t ' << mega << endl ;

13 cout . uns e t f ( i o s : : s c i e n t i f i c ) ;

14 cout . s e t f ( i o s : : f i x e d ) ;

15 cout << " v i r g u l e f i x e : " << ' \ t ' << ' \ t ' << r2 << " " << micro

16 << " " << mega << endl ;

17 cout . uns e t f ( i o s : : f i x e d ) ;

18 cout << " spontanement : " << ' \ t ' << ' \ t ' << r2 << " "<< micro

Intro_C

36
19 << " " << mega << endl ;

20 system( " pause " ) ;

21 }

Ce programme utilise des «drapeaux» (flags) que l'on peut installer (

set ) ou désinstaller ( unset ).
Ils se trouvent dans la bibliothèque

ios ; plutôt que de lire celle-ci en entier, avec un include , je
vais chercher les objets qui m'intéressent, avec l'opérateur de résolution de portée

::
spontanement: 1.41421 1.234e-09 1.234e+09

notation scientifique: 1.414214e+00 1.234000e-09 1.234000e+09

virgule fixe: 1.414214 0.000000 1234000000.000000

spontanement: 1.41421 1.234e-09 1.234e+09

Toutes les fonctions et manipulateurs précédents peuvent se combiner dans un même programme,

avec des résultats que je vous laisse le soin de découvrir.

5.3.4 un parfum de classe

Vous avez du remarquer que les identificateurs de nombreuses fonctions spécialisées de C++

sont de la forme nom1.nom2(), comme

cin.get() ou cout.width() . On voit apparaître ici une
trace de ce qui fait l'originalité du C++, la possibilité de définir des classes (ou des objets). Je peux

donner une idée simpliste de ce dont il s'agit par analogie avec le type « record (enregistrement)»

du Pascal. Cette structure de donnée est commode lorsque l'on veut rassembler des données de

nature différente ayant un point commun (nom, prénom, âge, taille et sexe d'un même individu

par exemple). On définit alors un enregistrement à plusieurs champs, auxquels on accède par

des identificateurs comme

rec1.nom , rec1.prenom , rec1.age . La construction correspondante
existe en C/C++, elle s'appelle une

struct , tout simplement. Le C++ pousse l'idée un cran
plus loin : une classe (en simplifiant) est une sorte d'enregistrement qui contient non seulement

des données mais encore des définitions de fonctions, capable d'opérer sur ces données. Ainsi, la

fonction

cout.width() est un « membre» de la classe cout .
5.4 Les fichiers

Les programmes traitent souvent de grandes quantités de données, et peuvent aussi produire

énormément de résultats. Il serait tout à fait impossible de saisir à la main ces monceaux d'octets

ou de les analyser en temps réel sur l'écran. Pour assembler, archiver ou analyser beaucoup de

données, on a recours à des fichiers. Un fichier réside sur un organe de stockage, disque, disquette,

CD, etc. Je donne ici quelques éléments d'un vaste sujet.

Nous allons nous intéresser aux fichiers « séquentiels», où les octets sont rangés en file, sans

structure spéciale autre que celle prévue par le programmeur. Il existe aussi des fichiers « à accès

aléatoire», où l'on peut aller chercher une information connaissant son rang (comme les plages d'un

disque). Ils ne sont pas abordés ici.

La manipulation d'un fichier en C++ ressemble beaucoup à celle des entrées-sorties habituelles,

et c'est normal : tout est fait pour qu'un quelconque périphérique se présente comme un fichier.

L'ensemble des fonctions utiles est déclaré dans le fichier

fstream qu'il faut donc inclure au début
du programme.

Le processus de déclaration d'un fichier est à peu près le même quelque soit le langage. On commence

par établir une relation entre le nom du fichier tel qu'il est ou sera connu sur le périphérique

(disque par exemple) et une variable qui le représente dans le programme (

ASSIGN en Pascal). On
précise ensuite s'il s'agit de lire, créer ou ajouter dans un fichier. Il ne reste plus qu'à lire, écrire

ou rajouter. Les choses vont paraître un peu mystérieuses parce que je ne veux pas entrer dans

Intro_C

37
le détail des opérations sur les classes et que je ne présente que ce qui ressemble à des fonctions

tordues.

5.4.1 Création d'un fichier

Examinons le petit programme qui suit.

1

// c r e_f i ch . cpp : c r é a t i on d 'un f i c h i e r s é q u e n t i e l .
2

#include <ios t ream>
3

#include <f s t ream>
4

#include <c s t d l i b >
5

using namespace s td ;
6

int main ( void ){
7

int nb1 = 1234; double nb2 = 5 . 6 7 8 9 ;
8

char msg [ ] = " bi en l e bonjour chez vous ! " ;
9 of s t r eam f i c h ( "D: / CoursC/ g l o b a l / e s s a i . dta " , i o s : : out ) ;

10 f i c h << msg << endl ;

11 f i c h << nb1 << ' \ t ' << nb2 << endl ;

12 cout << " f i n " << endl ;

13 system( " pause " ) ;

14

return 0 ;
15 }

Il crée un fichier et y inscrit une phrase et deux nombres ; on peut vérifier que le contenu de

essai.dta

est bien
bien le bonjour chez vous!

1234 5.6789

À la ligne 9 je définis l'objet

fich et je l'initialise pour qu'il corresponde au fichier extérieur
D:\CoursC\global\essai.dta

. J'ajoute ios::out pour indiquer que je veux écrire dans le fichier
(cette mention est facultative). Si le fichier n'existe pas, il sera créé ; s'il existe, son contenu sera

écrasé. On peut éviter cette issue fâcheuse en remplaçant

ios::out par ios::app , qui demande
que les données soient ajoutées à la fin du fichier existant. On pourrait procéder en deux étapes :

établir la relation d'abord et ouvrir le fichier plus tard, par les lignes suivantes.

ofstream fich;

.........

fich.open("D:\CoursC\global\essai.dta", ios::out);

Le fichier ainsi créé est, comme on dit, un fichier texte : il peut être lu par n'importe quel

éditeur de texte, ce qui est commode. Le programme précédent pourrait être amélioré en prévoyant

la conduite à tenir si on ne parvient pas à ouvrir le fichier.

Le fichier

fich sera fermé automatiquement lorsque le programme se terminera (à la différence
du C et du Pascal). On peut fermer explicitement les fichiers dont on n'a plus l'usage par

fich.close();

L'imprimante est considérée comme un fichier dans lequel on peut écrire ; il est donc possible

d'imprimer en remplaçant dans le programme

"D:\CoursC\global\essai.dta" par le nom de
fichier de l'imprimante, souvent

LPT1: . Ceci est vrai pour un fonctionnement dans une fenêtre
DOS, mais pas sous Windows ni sans doute lorsque l'imprimante est accessible par l'intermédiaire

d'un réseau.

Intro_C

38
5.4.2 Lecture d'un fichier

Le procédé, très voisin du précédent, est illustré ci-dessous. Avec un éditeur de texte ou avec

un programme, j'ai créé le fichier param.txt dont le contenu figure ci-dessous.

mardi

123

0.0258

jeudi

654

10.98

Le programme

lec_fich.cpp
1

// l e c_f i c h . cpp : l e c t u r e d 'un f i c h i e r s é q u e n t i e l .
2

#include <ios t ream>
3

#include <f s t ream>
4

#include <c s t d l i b >
5

using namespace s td ;
6

int main ( void ){
7

int nb1 ; double nb2 ; char j our [ 2 0 ] ;
8 i f s t r e am f i c h ( "D: / CoursC/ g l o b a l /param. txt " , i o s : : in ) ;

9

for ( int i = 1 ; i <= 2 ; i++){
10 f i c h >> j our >> nb1 >> nb2 ;

11 cout << j our << ' \ t ' << nb1 << ' \ t ' << nb2 << endl ;

12 }

13 cout << " f i n " << endl ;

14 system( " pause " ) ;

15

return 0 ;
16 }

produit alors le résultat

mardi 123 0.0258

jeudi 654 10.98

fin

Le fichier est fermé automatiquement en fin de programme, mais il pourrait l'être sur demande

(

fich.close() ).
En conclusion de cette brève introduction aux fichiers, il faut retenir que ces objets sont extrêmement

commodes et que leur emploi n'est pas plus compliqué que celui d'une imprimante.

5.5 «string»

Vous avez remarqué que j'utilisais beaucoup de mots ou de phrases dans les programmes qui

illustrent ce cours. Nous venons de voir une application (les fichiers) où les noms jouaient un

rôle plus important que celui d'un simple exemple. Dans le jargon de l'informatique, un mot

ou une phrase constituent une «chaîne de caractères». Les chaînes se manipulent facilement en

Pascal et de façon plus tortueuse en C (comme décrit dans un chapitre suivant). C++ (pas C)

comporte cependant une bibliothèque de fonctions spécialisées dans la manipulation aisée de chaînes

particulières que je vais décrire sommairement. Pour éviter des confusions, j'emploierai le mot

«string» plutôt que «chaîne» qui sera réservé aux chaînes de caractères de style C. D'autre part,

les strings sont des objets que l'on doit manipuler avec les règles de la programmation objet ; pour

Intro_C

39
simplifier, je vais masquer cet aspect des choses. Pour utiliser ces ressources, il faut appeler la

bibliothèque :

# include <string>

5.5.1 déclaration et initialisation

La déclaration d'une string est banale :

string s, str, nom_fich, titre;

Une string peut être initialisée de façon normale

s = "programmation";

titre = 'X';

str = s;

Il existe plusieurs méthodes de déclaration et initialisation couplées :

string nom1("Dupont");

string nom2 = "Durand";

string etoiles (10,'*'); // une rangée de 10 étoiles

Vous avez du remarquer qu'à aucun moment je n'ai précisé la longueur de la «string». Celle-ci est

arbitraire et n'est limitée que par la taille de la mémoire de l'ordinateur, un avantage considérable

par rapport aux chaînes de caactères du C ou du Pascal.

5.5.2 lecture et écriture

La lecture et l'écriture de strings sont aussi conventionnelles

cout << s << endl;

cin >> nom_fich;

La lecture s'arrête au premier blanc. Pour lire tout jusqu'au passage à la ligne :

getline (cin,titre);

On pourrait lire ou écrire dans un fichier en remplaçant simplement

cin par le nom complet du
fichier.

5.5.3 quelques opérations

Il est très facile de concaténer des strings, à l'aide des opérateurs + et +=. Ainsi

string s1 = "Du", s2 = "pont ", s3 = "de Nemours", s;

s = s1 + s2;

s += s3;

cout << s;

affichera le résultat

Dupont de Nemours .
À chaque string, on peut associer les fonctions

size et length qui renvoient la longueur de
l'objet ; le fragment

int n1 = s1.length();

int n2 = s2.size();

associé aux déclarations précédentes crée deux entiers de valeurs respectives 2 et 4.

Les caractères individuels d'une «string» sont accessibles, comme les éléments d'un tableau

(voir chapitre suivant), ce qui ne signifie pas qu'une string est représentée par un tableau. La numérotation

commence à zéro.

L'instruction cout << s3[3] produit N et l'affectation s2[3] = 'd'
crée la chaîne

pond .
Intro_C

40
5.5.4 un exemple

Le programme qui suit crée un fichier et y dépose une phrase ; l'utilisateur n'est pas obligé de

taper le nom complet du fichier, le répertoire et le suffixe sont ajoutés par le programme. Certains

logiciels anciens redoutent les noms de fichiers comportant plus de 8 caractères, d'où l'affichage de

la longueur du nom.

12

// f i c h_s t r . cpp : s t r i n g comme nom de f i c h i e r

3

#include <f s t ream>
4

#include <s t r ing>
5

#include <c s t d l i b >
6

using namespace s td ;
7

int main ( void ){
8 s t r i n g nom_fich , base = "D: / CoursC/ g l o b a l /" ;

9 cout << "Nom du Fi c h i e r a c r e e r : " ; c in >> nom_fich ;

10 cout << " l e nom de vot r e f i c h i e r comporte " << nom_fich . s i z e ( )

11 << " c a r a c t è r e s \n" ;

12 nom_fich = base + nom_fich + " . dta " ;

13 of s t r eam f i c h ( nom_fich . c_str ( ) , i o s : : out ) ;

14 f i c h << " que l beau programme ! " << endl ;

15 system( " pause " ) ;

16 }

Attention :

Une «string» ne peut pas être utilisée telle quelle dans une déclaration de fichier ;
il faut la convertir en chaîne de style C; c'est ce que fait la fonction

c_str() affectée à l'objet
nom_fich

Revenir en haut Aller en bas
Voir le profil de l'utilisateur http://allah-ahad.blogspot.com/
 
Introduction au langage C/C++ (2)
Voir le sujet précédent Voir le sujet suivant Revenir en haut 
Page 1 sur 1
 Sujets similaires
-
» NOUVELLE INTRODUCTION : FIPAR OU LES PREMICES D'UN AUTRE ECHEC ?
» La stratégie du choc, interview de Naomi KLEIN
» Numéro de TVA intracommunautaire
» refus de vente et condition pourl appliquer.. a lire
» quel système de fidélité?

Permission de ce forum:Vous ne pouvez pas répondre aux sujets dans ce forum
E N C G - Casablanca :: Espace Scientifique :: Technologie, Informatique & Internet-
Sauter vers: