Vous êtes sur la page 1sur 21

Programmation en C++

M. El Ansari, Enseignant Chercheur

Faculté des Sciences


Département de Mathématiques et Informatique
Agadir

2010-2011
c
2
Table des matières

1 Introduction 5

2 Les incompatibilités entre C++ et C 7


2.1 Les fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.1.1 Définitions et prototypes . . . . . . . . . . . . . . . . . . . . . . . . 7
2.1.2 Arguments et valeur de retour d’une fonction . . . . . . . . . . . . 8
2.2 Le qualificatif const . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.3 Compatibilité entre le type void* et les autres pointeurs . . . . . . . . . . . 10

3 Les entrées-sorties en C++ 11


3.1 Ecriture sur la sortie standard . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2 Lire sur l’entrée standard . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

4 Les spécificités de C++ 15


4.1 Commentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.2 Déclarations et initialisations . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.3 Les références . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
4.4 Passage de paramètres par référence . . . . . . . . . . . . . . . . . . . . . . 16
4.5 Arguments par défaut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
4.6 Surdéfinition de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
4.7 Les opérateurs new et delete . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.8 Spécification inline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.8.1 Rappel sur les macros en C . . . . . . . . . . . . . . . . . . . . . . 21
4.8.2 Les fonctions inline . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

3
4
Chapitre 1

Introduction

Le langage C++ a été conçu à partir de 1982 par Bjarne Stroustrup (AT&T Bell
Laboatories). L’idée est d’ajouter au langage C des classes analogues à celles du langage
Simula. Il s’agissait de greffer sur un langage classique des possibilités de Programmation
Orientée Objet.
Les extensions de C++ par rapport au C ANSI ne sont pas toutes véritablement
liées à la P.O.O. En fait, nous pourrions caractériser C++ par cette formule : C + + =
C ±E+S+P
– C désigne le C norme ANSI.
– E représente les écarts de C++ par rapport à la norme ANSI de C.
– S représente les spécifités de C++ qui ne sont pas véritablement axées sur la P.O.O.
– P représente les possibilités de P.O.O.
Les principaux écarts concernent essentiellement (cf. Chap. 2) :
– les définitions de fonctions : en-têtes, prototypes, arguments et valeur de retour,
– la portée du qualificatif const,
– les compatibilités entre pointeurs.
Bref résumé des spécifités de C++ (voir Chap. 4) :
– nouvelle forme de commentaire (en fin de ligne),
– plus grande liberté dans l’emplacement des déclarations,
– notion de référence facilitant la mise en oeuvre de la transmission d’arguments par
adresse,
– surdéfinition de fonctions : attribution d’un même nom à différentes fonctions, la
reconnaissance de la fonction réellement appelée se faisant d’après le type et le
nombre des arguments figurant dans l’appel (on parle parfois de signature),
– nouveau opérateur de gestion dynamique de la mémoire : new et delete,
– possibilité de définir des fonctions en ligne (inline), ce qui accroı̂t la vitesse
d’exéction.

5
6
Chapitre 2

Les incompatibilités entre C++ et C

2.1 Les fonctions

2.1.1 Définitions et prototypes


En C++ comme en C ANSI, la définition d’un fonction a la forme suivante :
double fexple(int u, double v)
{
.../*corps de la fonction */
}
En C++, une fontion doit
– être définie avant utilisation
– ou être déclarée par un prototype : float fonction(int, double, *char) ;
A partir du moment où l’on doit utiliser une fonction en dehors du fichier où elle est
définie, on place son prototype dans un fichier en-tête ; ce dernier est incorporé, en cas de
besoin, par la directive #include, ce qui évite tout risque de faute d’écriture du prototype.

Example :

/* ----fonction.cpp-----*/
int somme(int i, int j) {
return i+j ;
}
/* ------ fonction.h-------*/
int somme(int, int) ;
/* Programme principal */
/* ------ fonction.h-------*/
#include<stdio.h>
#include"fontions.h"
main() {
int a=2, b=3 int c ;
c=somme(a,b) ;
printf("somme=%d ",c) ;
return 0 ;
}

7
8

2.1.2 Arguments et valeur de retour d’une fonction

Points communs à C et C++

En C++ comme en C ANSI, les arguments d’une fonction ainsi que la valeur de retour
peuvent :
– ne pas exister,
– être une valeur scalaire d’un des types de base,
– être une valeur de type structure.
Remarques :
– il est possible de transmettre la valeur d’une structure, aussi bien en argument qu’en
valeur de retour,
– il n’est pas possible de faire de même avec les tableaux.

Différences C et C++

Ces différences portent uniquement sur la syntaxe des en-têtes et des prototypes des
fonctions à savoir :
1- Fonctions sans arguments : Pour déclarer ou définir une fonction sans arguemnts, en
CANSI, on utilise float fonction(void) ; . En C++, on utilise float fonction() ;
2- Fonctions sans valeur de retour : En C ANSI, on peut employer le mot void pour
définir (en-tête) ou déclarer (prototype) une fonction sans arguments. En C++, on doit
absolument le faire.

2.2 Le qualificatif const

La norme C ANSI a introduit le qualificatif const qui permet d’indiquer qu’une valeur
ne change pas directement. Le même qualificatif est utilisé par C++ pour définir des
entités constantes. Des exemples typiques d’utilisations de cette fonctionnalité pour des
variables sont (Cf. ) :
– la représentation des bornes de boucles. C’est le cas de variable taille qui ne doit
pas être modifiée une fois qu’il est fixée ;
– le passage de paramètres dans des fonctions qui ne sont pas sensées modifier la valeur
de ces paramètres. C’est le cas de la fonction contient qui effectue une recherche
et qui ne doit pas modifier la valeur des paramètres qui lui sont fournis.
9

/* Un tableau ’tab’ de ’taille’ \l\ments contient-il ’elt’? */


bool contient(const int tab[], const int taille, const int elt)
{
int i;
for(i=0;(i < taille) \&\& (tab[i] != elt);i++);
return (i != taille);
}

int main()
{
const int j; /* Erreur de compilation */
int taille = 1000;
int tab[1000];
bool appartient;
int i;
...
appartient = contient(tab,taille,i);
...
}

Utilisation des constantes

Portée

Lorsque const s’applique à une variable globale, C++ limite la portée du symbole au
fichier source contenant la déclaration ; C n’imposait aucune limitation.
En C++, il devient plus facile de remplacer certaines instructions #define par des
déclarations de constantes. Ainsi, là où en C vous procédiez de cette façon :
#define N 8 #define N 3
....... .......
fichier1 fichier2
vous pouvez en, C++, procéder ainsi :
const N = 8 const N = 3
....... .......
fichier1 fichier2
En C, vous auriez obtenu une ereur au moment de l’édition de liens. Vous auriez pu
l’éviter :
– soit en déclarant N static, dans au moins un des deux fichiers (mieux de le faire dans
les deux) :
static const int N = 8 ; static const int N = 3 ;
– soit, si N avait eu la même valeur dans les deux fichiers, en plaçant dans le second
fichier :
extern const int N ;

Utilisation dans une expression

Une expression constante est une expression dont la valeur est calculée lors de la
compilation. Ainsi avec const int p = 3 ; l’expression 2*p*5 n’est pas une expression
constante en C alors qu’elle en est une en C++.
10

Pour la déclaration des tableaux, le compilateur doit connaı̂tre la taille à réserver sur
la pile. Ainsi, les instructions :
const int nel = 15 ;
...
double t1[nel + 1], t2[2*nel][nel] ;
seront acceptées en C++, alors qu’elles étaient refusées en C.

2.3 Compatibilité entre le type void* et les autres


pointeurs
En C ANSI, le type générique void* est compatible avec les autres types pointeurs, et
ce dans les deux sens. Ainsi, avec les déclarations :
void * gen ;
int * adi ;
ces deux affectations sont légales en C ANSI :
gen = adi ;
adi = gen ;
Elles font intervenir des conversions implicites, à savoir :

int * -> void *

pour la première et

void *-> int *

pour la seconde.
En C++, seule la conversion d’un pointeur quelconque en void * peut être implicite
(gen = adi). Cependant, l’opéartion inverse est dangereuse. La conversion de void * en
un pointeur de type donné revient à associer un type à une adresse. Il est généralement
dangereux d’utiliser un pointeur converti en un type différent de celui de l’objet pointé.
Example :

void f(int *pi)


{
void * pv = pi; /* ok:conversion implicite de int* en void* */
*pv; /* erreur:l’indirection ne s’applique pas a un type void* */
pv++; /* erreur:on ne peut pas incrementer un type void*
(la taille de l’objet point\ est inconnue)*/
int* pi2 = (int *)pv; /* revonversion explicite en int* */
double * pd1 = pv; /* erreur */
double *pd2 =pi; /* erreur */
double * pd3 = (double *)pv; /* hasardeux */
}
Chapitre 3

Les entrées-sorties en C++

C++ dispose de toutes les routines offertes pae la bibliothèque standard du C ANSI.
Mais il comporte également de nouvelles possibilités d’entrées-sorties reposant sur les no-
tions de flots (ou streams) et de surdéfinition (voir plus loin). Pour pouvoir les utiliser,
il faut inclure l’en-tête <iostream>. Les entrées- sorties sont ensuite réalisées par l’in-
termédiaire des flots suivants :
– cin : flot d’entrée standard (par défaut le clavier) ;
– cout : flot de sortie standard (par défaut l’écran).

3.1 Ecriture sur la sortie standard


Premier progamme C++ :
#include<iostream.h>
void main(){cout << "bonjour tout le monde\n" ;}
Il affiche :
bonjour tout le monde
<iostream.h> est la bibliothèque du C++ permettant de faire des entrées-sorties.
– cout : désigne un flot de sortie attaché à la sortie standard ;
– << : prend un flot (stream) en premier opérande et une chaı̂ne de caractères en
deuxième opérande. Dans le programme précedent, le flot cout reçoit la valeur
”bonjour tout le monde”.
le retour à la ligne "\n" en C peut être remplacé par endl en C++ :
cout<<"bonjour tout le monde"<<endl ;
On peut utiliser cout pour afficher la valeur d’une variable :
#include<iostream.h>
void main(){
int n =2 ;
cout<<"la valeur de n est :"<<n<<endl ;}
Le résultat d’exécution :
la valeur de n est :2
On peut chı̂ner l’utilisation de l’opérateur << pour écrire plusieurs chı̂nes de caractères
à la suite les unes des autres :
cout<<...<<...<<... ;
D’une manière générale, on peut utiliser << pour envoyer sur cout la valeur d’une
expression du type suivant :
– type de base quelconque (caractère, entier, flottant) ;
– chaı̂ne de caractères (char *) : on obtient les caractères formant la chaı̂ne ;

11
12

– pointeur, autre que char * : on obtient l’adresse correspondante en hexadécimal.


Pour obtenir l’adresse d’une chaı̂ne de caractères on peut la convertir en (void *).

3.2 Lire sur l’entrée standard


On peut également lire des informations sur l’entrée standard en utilisant le stream
cin. Le programme suivant demande d’entrer une valeur sur l’entrée standard, puis il
la lit avec l’opérateur >> et le stream cin. Par la suite, il affiche la valeur entrée par
l’utilisateur.
void main(){
int n ;
cout<<"entrer une valeur :" ;
cin>>n ;
cout<<"la valeur entree est :"<<n<<endl ;
}
De même pour <<, l’opérateur >> accepte presque tous les types et il est associatif.
Exemple 1 : Manque de synchronisme entre clavier et écran
#include<iostream.h>
void main()
{
int n,p ;
cout<<"Donner n :" ;
cin>>n ;
cout<<"merci pour "<<n<<endl ;
cout<<"Donner p :" ;
cin>>p ;
cout<<"merci pour "<<p<<endl ;
}
Première exécution :
Donner n :2
merci pour 2
Donner p :5
merci pour 5
Deuxième exécution :
Donner n :2 5
merci pour 2
Donner p :merci pour 5
Exemple 2 : Boucle infinie
#include<iostream.h>
void main()
{
int n ;
do
{cout<<"donner un nombre entier :" ;
cin>>n ;
cout<<"voici son carre :"<<n*n<<endl ;
}while (n) ;
}
Exécution :
donner un nombre entier :3
13

voici son carre :9


donner un nombre entier :’
voici son carre :9
donner u nombre entier :voici son carre :9
donner un nombre entier :voici son carre :9
donner un nombre entier :voici son carre :9
donner un nombre entier :voici son carre :9
...

D’ne manière générale :


cin >> n >> p ;
sera équivalent à :
(cin >> n) >> p ;
Nous pouvons dire que la valeur de n est extraite du flot cin ; ensuite, la valeur de p
est extraite du flot cin >> n.
14
Chapitre 4

Les spécificités de C++

Le langage C++ dispose d’un certain nombre de spécificités qui ne sont pas axées sur
la P.O.O.

4.1 Commentaires

Il existe deux moyens de faire des commentaires en C++. Le premier, disponible


sous C, correspond à des commentaires longs. Il correspond aux délimiteurs /* et */.
Le deuxième moyen corrrespond à des commentaires de fin de ligne en inroduisant le
délimiteur //.

Exemple :

/*
cei est un commentaire de
plusieurs lignes
/
int i =2 ;// Commentaire court (fin de ligne)

4.2 Déclarations et initialisations

En C++, l’emplacement des déclarations est libre. Le programmur n’est pas obligé de
les mettre au début d’une fonction. Par contre, on ne peut utiliser la variable déclarée que
dans les instructions du bloc où est effectuée la déclaration et postérieures à la déclaration.
L’avantage de cette spécificité du C++ est de pouvoir déclarer une variable juste au
moment où l’on en a besoin et cela clarifie le programme.

15
16

void f() {
...
i = 8; //incorrect
...
int i; //i est declare ici
...
i = 8; // correct
...
{
...
float f; // f est declare ici
...
f = 4.0; //correct
...
} // fin de la portee de f
f = 4.0; // incorrect
...
i = 5; // correct
...
}// fin de la portee de i

4.3 Les références


Il est possible de déclarer un identificateur comme référence d’une autre variable.
Considérez, par exemple, ces instructions :

int n;
int & p = n;

La seconde signifie que p est une référence à la variable n. Ainsi, dans la suite, n et p
désigneront le même emplacement mémoire. Par exemple, avec :

n = 3;
cout << p;

nous obtiendrons la valeur 3.


Il n’est pas possible de déclarer une référence sans l’initilaiser, comme dans :
int & p ; // incorrect
On ne peut pas initialiser une référence avec une constante. La déclaration suivante
est incorrecte :
int & n = 4 ; // incorrect

4.4 Passage de paramètres par référence


En C, les arguments sont passés par valeur. Ce qui signifie que les paramètres d’une
fonction ne peuvent pas être modifiés par la fonction.
Ci-dessous la fonction echange est censée d’échanger les valeurs des deux paramètres
n et p.
17

void echange(int a, int b){


int c = a;
a = b;
b = c;
}
main() {
int n = 10, p = 20;
cout << "avant appel :"<< n <<" "<<p<<endl;
echange(n,p);
cout << "apres appel :"<< n <<" "<<p<<endl;
}

Malheureusement, pour la raison invoquée plus haut, la sortie de ce programme est :


avant appel :10 20
apres appel :10 20
Les programmeurs C ont l’habitude de palier à cet inconvénient du C en passant
l’adresse des paramètres en entrée de la fonction. Ainsi, notre exemple deviendra :

void echange(int * a, int * b){


int c = *a;
*a = *b;
*b = *c;
}
main() {
int n = 10, p = 20;
cout << "avant appel :"<< n <<" "<<p<<endl;
echange(&n,&p);
cout << "apres appel :"<< n <<" "<<p<<endl;
}

La sortie de ce programme correspond à ce que notre intuition attend :


avant appel :10 20
apres appel :20 10
Cette manipulation de pointeurs est lourde à gérer pour le programmeur. C++ palie à
cet inconvénient en permettant le passage de paramètres par référence ; la progrmmation
devient plus légère et le résultat correct. Il suffit de changer la déclaration de la fonction
echange :
void echange(int & a, int & b){.....}
L’appel de la fonction est le même que pour le passage par valeur :
echange(n, p) ;

4.5 Arguments par défaut

En C ANSI, il est indispensable que l’appel d’une fonction contienne autant d’ar-
guments que la fonction en attend effectivement. C++ permet de s’affranchir de cette
contrainte en permettant l’attribution de valeurs par défaut à des arguments.
18

void f(int, int = 12);


main () {
int n = 10; int p = 20;
f(n),p);
f(n);
}
void f(int a, int b) {
cout <<" Premier argument : "<< a;
cout <<" Second argument : "<<b<<endl;
}

La sortie de ce programme est :

Premier argument : 10 Second argument : 20


Premier argument : 10 Second argument : 12

Dans une fonction, les derniers arguments peuvent prendre des ”valeurs par défaut”.
Déclaration :
float f(char, int = 10, char* = "Tout") ;
Appels :

f(c, n, "rien");
f(c, n); // <-> f(c, n, "Tout")
f(c); // <-> f(c,10, "Tout")
f(); // erreur

Seuls les derniers arguments peuvent avoir des valeurs par défaut.
float f(char = ’a’, int, char* = "Tout") ; // erreur

4.6 Surdéfinition de fonctions


La surcharge est une technique permettant d’améliorer la réutilisabilité en conception
objet. Elle permet d’attribuer le même nom à plusieurs opérateurs ou a plusieurs fonctions.
Cette technique est disponible en C++, qui offre ainsi la possibilité de définir plusieurs
fonctions portant le même nom, à la condition que ces fonctions aient des profils différents.
Exemple :

#include<iostream>
using namespace std;
float max(float,float);
float max(float,float,float);
float max(int,float[]);

void main()
{
float x,y,z;
float T[]={11.1f,22.2f,33.3f,44.4f,7.7f,8.8f};
19

x=max(1.86f,3.14f);
y=max(1.86f,3.14f,37.2f);
z=max(6,T);
cout<<x<<" "<<y<<" "<<z<<endl;
}

float max(float a,float b){


return(a>b)?a:b;
}
float max(float a,float b,float c){
return max(a,max(b,c));
}
float max(int n,float t[]){
if(!n) return 0;
float m=t[0];
for(int i=2;i<n;i++)
m=max(m,t[i]);
return m;
}

Résultats :
3.14 37.2 44.4
L’analyse de la signature déetermine la fonction à utiliser. Les promotions et conver-
sions usuelles s’appliquent.Le type de retour n’intervient pas

float min(int,float);
double min(int,float); //erreur

On peut aussi surcharger les opérateurs : définir par exemple l’addition de nombres com-
plexes et représenter cette addition par le signe +.
Exemple 1 :

void affiche(char *); //affiche I


void affiche(void *); //affiche II
char *ad1;
double *ad2;
...
affiche (ad1); // appelle affiche I
affiche (ad2); //appelle affiche II apres conv\rsion de ad2 en void*

Exemple 2 :

void essai(int, double); //essai I


void essai(double, int); //essai II
int n, p; double z; char c;
...
essai(n,z); //appelle essai I
essai(c,z); //appelle essai I, apr\s conversion de c en int
essai(n,p); //erreur de compilation (deux possibilit\s d’appel)
20

Exemple 3 :

void test(int n=0, double x=0); // test I


void test (double y = 0, int p=0); // test II
int n; double z;
...
test(n,z); // appelle test I
test(z,n); //appelle test II
test(n); // appelle test I
test(z); // appelle test II
test(); //erreur de compilation contenu de l’ambigut

4.7 Les opérateurs new et delete


Tout comme C, C++ permet de manipuler dynamiquement la mémoire. En C ces
manipulations étaient implantées grâce aux fonctions malloc et free disponibles dans les
bibliothèques standards C. En C++, ces manipulations sont imlantées via les opérateurs
new et delete et il n’est plus nécessaire d’utiliser l’opérateur sizeof conjointement avec
la fonction malloc. Le principe d’utilisation reste globalement inchangé, le programmeur
doit être vigilant : à chaque allocation doit correspondre une désallocation de mémoire.
Lorsque les allocations portent sur des tbleaux plutôt que des objets simples, les
opérateurs new et delete sont remplacés respectivement par new [] et elete []. L’ex-
emple suivant donne des exemples d’utilisation de l’allocation dynamique de mémoire en
C++ et la comaraison par rapport aux mêmes exemples en C :

// Allocation d’une variable et d’un tableau en C

#include<stdlib.h>
main()
{
int *pi = malloc(sizeof(int));
int *tab = malloc(sizeof(int)*10);

if ((pi != NULL) && (tab != NULL)) {


...
free(pi);
free(tab);
}
}

// Allocation d’une variable et d’un tableau en C++

void main ()
{
int *pi = new int;
int *tab = new int[10];

if ((pi != NULL) && (tab != NULL)) {


...
delete pi;
21

delete tab;
}
}

Attention :Les mécanismes d’allocation dynamique de mémoire en C ou en C++ font


intervenir des structures iternes gérées par le langage. Ces structures sont différentes
selon que l’on utilise la paire malloc/free ou la paire new/delete. Il est ainsi d’usage
de n’utiliser que la première en C et que la deuxième en C++.

4.8 Spécification inline


4.8.1 Rappel sur les macros en C
En C, on peut définir une macro avec le mot-clé define :
#define carre(A) A*A
Cela permet à priori d’utiliser carre comme une fonction normale :

int a = 2;
int b = carre(a);
cout<<"a = "<<a<<" b = "<<b<<endl;

Le résultat sera correct dans ce cas simple :


a = 2 b = 4
Quand il voit une macro, le C remplace partout dans le code les expressions carr(x)
par x*x.
L’avantage d’une macro par rapport à une fonction est la rapidité d’exéution. En
effet, le temps à recopier les valeurs des paramètres disparaı̂t. La contrepartie est un
espace mémoire du programme plus grand.
Plus embêtant sont les effets de bord des macros. Si l’on programme :
b = carre(a++) ;
On attendrait le résultat suivant :
a = 3 b = 4
En réalité, ce n’est pas le cas car, à la compilation, le C remplace malheureusement
carre(a++) par a++*a++. Ce qui donne le résultat suivant :
a = 4 b = 4

4.8.2 Les fonctions inline


Le C++ palie à cet inconvénient en permettant de définir des fonctions, dites en ligne,
avec le mot-clé inline.
inline int carre(int x) {return x*x ;}
Une fonction définie avc le mot-clé inline aura le même avantage qu’une macro en C,
à savoir un gain de temps d’exécution et le même inconvénient, à savoir une plus grande
place mémoire occupée par le programme.
L’avantage des fonctions en ligne est la disparition des effets de bord.

Vous aimerez peut-être aussi