Cours de Langage C UNA

Télécharger au format pdf ou txt
Télécharger au format pdf ou txt
Vous êtes sur la page 1sur 80

1

PROGRAMMATION
STRUCTURE

Le langage C

UNA/UFR-SFA H. EDI
PROCESSUS DE DEVELOPPEMENT 2

Aujourd’hui, les applications logicielles sont de plus en plus


imposantes et complexes (plusieurs millions de lignes de code)

Développement logiciel  marché mondial de plus en plus concurrentiel :

le délai de mise sur le marché et les coûts de développement sont des facteurs décisifs

Dans ce contexte, les entreprises rencontrent souvent des difficultés :


pour la conduite de projet (gestion des équipes, processus de développement, validation, etc.),
pour assurer la fiabilité du code,
pour assurer la maintenance du système,
dans la réutilisation du code

La maîtrise du Génie Logiciel devient alors une nécessité incontournable pour les entreprises
développant des systèmes logiciels de grandes tailles ou à exploitation critique :
Projet logiciel

Système de conduite
Système de conduite de centrale nucléaire
de procédés industriels
PROCESSUS DE DEVELOPPEMENT 3

4 phases de développement : Spécification  Conception  Programmation  Test

Aujourd’hui, deux grandes familles de processus de développement définissent la manière dont ces
phases doivent s’enchaînées :
les processus séquentiels : modèle en cascade, modèle en V, …
les processus itératifs : Unified Process (UP, RUP, 2TUP,…), méthodes agiles (XP,…)
La plupart de ces processus s’appuie sur une modélisation orientée objet

Cycle de développement : une démarche itérative s’appuyant sur des outils …


Le langae C++ est un bon outil pour la programmation objet

Pour un bon apprentissage du C++, utilisation aujourd’hui le C

ETAPE 1 n : Conception.
Modélisation du monde réel

ETAPE 2 n ETAPE 3 n
Programmation Cycle n Test du programme
Implémentation

Projet Informatique
PROCESSUS DE DEVELOPPEMENT 4

Le langage C++

Première version du C en 1978 (norme ANSI),

Le C++ est 100% compatible avec le C ( intérêt industriel !)

Réutilisation du code existant


Effort d’apprentissage réduit,
Apport de la structuration objet

Langage objet le plus utilisé en ingénierie


Apparition de langages dits « C++ like » tels que JAVA, C#, …

Liens très étroits avec les outils de conception U.M.L

Génération
de code
Visual C++ 6.0 Pro
Rational Rose Visual Studio. net

Reverse
Rational Rose Enterprise Edition.lnk engineering Microsoft Visual C++ 6.0.lnk
APPROCHE OBJET 5

Programme  Composé de séquences d’instructions, de variables et de types

Type : notion fondamentale en programmation


Donne une sémantique (sens) aux mots binaires stockés en mémoire,
Définit le niveau d’abstraction des entités manipulées,

Développement d’une application


Approche procédurale (FORTRAN,C) Approche objet (C++)
Programme construit autour
Programme construit autour
des structures de données (objets)
des traitements (procédures)
définies par une classe

Diagramme de classes,
Organigramme
Diagramme de séquences,
etc …

Idée directrice de l’approche objet


Considérer des éléments de programme comme des entités autonomes ne
communiquant qu’à travers une interface réduite

Notion de boite noire  concept de modularité et d’encapsulation


Réutilisation du code  concept de d’héritage et de généricité
6
Force du langage C 7

Le langage C est un langage de bas niveau dans le sens où il permet l’accès à des données que
manipulent les ordinateurs (bits, octets, adresses) et qui ne sont pas souvent disponibles à partir de
langages évolués tels que Fortran, Pascal ou ADA.

Le langage C a été conçu pour l’écriture de systèmes d’exploitation et du logiciel de base. Plus de 90%
du noyau du système UNIX est écrit en langage C. Le compilateur C lui-même est écrit en grande partie
en langage C ou à partir d’outils générant du langage C. Il en est de même pour les autres outils de la
chaîne de compilation (assembleur, éditeur de liens, pré-processeur). De plus, tous les utilitaires du
système sont écrits en C (shell, outils).

Il est cependant suffisamment général pour permettre de développer des applications variées de type
scientifique ou encore pour l’accès aux bases de données. Par le biais des bases de données, il est
utilisé dans les applications de gestion. De nombreux logiciels du domaine des ordinateurs personnels,
tels que Microsoft Word ou Microsoft Excel, sont eux-aussi écrits à partir de langage C ou de son
successeur orienté objet.

Le compilateur de langage C se limite aux fonctionnalités qui


peuvent être traduites efficacement en instructions machine.
Compilation d’un programme 8

Le compilateur lit ce que


génère le pré-processeur et
crée les lignes d’assembleur
correspondantes.
Le compilateur lit le texte
source une seule fois du
début du fichier à la fin. Cette
lecture conditionne les
contrôles qu’il peut faire, et
explique pourquoi toute
variable ou fonction doit être
déclarée avant d’être
utilisée.

Fichier source
STRUCTURE DE BASE 9

Délimiteurs de commentaires
/* et */ pour des commentaires qui se prolongent sur plusieurs lignes
// pour un commentaire en fin de ligne (unique)
Le point d’entrée de tout
programme est repéré par la
procédure main() /* programme principal
------------------- */
principal.cpp

void main ( )
Début du bloc {
déclaration var 1 ; // pression Un bloc d’instructions est
Le corps du programme … divisé en 2 zones :
zone de déclaration,
(ou d’un bloc d’instructions) déclaration var n ;
est toujours délimité par séquence d’instructions,
les accolades { et }
instruction 1 ;

instruction m ;
Fin du bloc
}

Toutes les déclarations (variable, type, sous-programme)


et toutes les instructions doivent se terminer par un (;)
LES IDENTIFICATEURS 10

Identificateur : Nom donné à une variable, un type, un sous-programme …


Doit commencer par une lettre ou un _
Ne doit par être un mot clef du langage C (ces mots clés sont les suivants :

type des données :


char const double float int long short signed unsigned void volatile

classes d’allocation : auto extern register static

Constructeurs : enum struct typedef union

instructions de boucle : do for while

Sélections : case default else if switch

ruptures de séquence : break continue goto return

Divers: asm entry fortran sizeof

Pas d’équivalence entre majuscule et minuscule

Mots clefs du C en minuscule


DECLARATIONS ET MODULES 11

REGLES DE COMPILATION
Règle 1 : toute variable ou constante doit être déclarée avant de pouvoir être utilisée

Règle 2 : tout type défini par l’utilisateur doit être déclaré avant de pouvoir être utilisé.

Règle 3 : tout sous-programme (procédure ou fonction) doit être déclaré avant de pouvoir être
appelé.

INTERET DE LA DECLARATION
permet de vérifier dès la compilation que :
l’utilisation d’une variable ou d’un type est cohérent (affectation de valeurs compatibles avec le
type ou la structure de la variable),
l’appel d’un sous-programme est cohérent avec la manière dont celui-ci est spécifié
(notamment, bon nombre et bon type d’arguments)

permet de faire de la compilation séparée  gain de temps


décomposition d’un programme en un fichier principal (contenant main()) et de multiples
modules
DECLARATIONS ET MODULES 12

MODULE (à voire plus loin)


En C, un module est le groupement de deux fichiers :

un fichier avec l’extension .h (« header file »  fichier entête) qui contient la


déclaration des sous-programmes et des types,
un fichier avec l’extension .c (fichier source) qui contient les instructions (ou
définition) des sous-programmes.

En général, le fichier « entête » et le fichier « source » porte le même nom  celui du


module :
Module Interface  fichiers Interface.h et Interface.cpp

Pour utiliser un sous-programme ou un type situé dans le module nom depuis un


programme situé dans un autre fichier, il faut satisfaire les règles 2 et 3. Pour cela, il
suffit de placer au début de ce fichier la directive de compilation suivante :

#include ”nom.h”
TYPES SIMPLES PREDEFINIS 13

Type Algorithme C
entier simple signé ENTIER int
entier long signé Entier long
réel simple précision REEL float
réel double précision REEL double
caractère CHARACTERE char
pointeur POINTEUR *
non typé - void

Remarques :
Attribut unsigned pour les types à valeur entière (char, int, long)
Un entier égal à 0 est associé à la valeur booléenne false (et  0, à la valeur true)
L’instruction typedef permet de renommer un type :
typedef ancien_nom nouveau_nom;
DECLARATION D’UNE VARIABLE 14

id_type id_var = valeur_initiale ;

Obligatoire Facultatif

id_var : nom (ou identificateur) de la variable,


id_type : type de la variable  permet de lui allouer une place en mémoire :
réserve le nombre d’octet adéquat,
fixe sa position  son adresse mémoire

valeur_initiale : Permet éventuellement de donner une valeur initiale à la variable


Exemple :
int a = -12 ;
unsigned long Compteur = 0 ;
double Volume ;
char Lettre = ’a’ ;

Correspond à la déclaration d’une variable dont :


la création et la destruction sont gérées automatiquement par le compilateur,
la durée de vie s’étend de l’endroit où elle est déclarée jusqu’à la fin du bloc au sein duquel elle
a été déclarée.
la portée (ou visibilité) est limitée à cette même zone.
LES OPERATEURS 15

TYPE UNAIRES BINAIRES TERNAIRES

arithmétique - : négation + : Addition


i++  i=i+1 ++ : incrémentation de 1 - : Soustraction
-- : décrémentation de
* : multiplication
i--  i=i-1 1
/ : division
% : modulo
+=,-=,
*=,/=, : version condensée
%=
logique ! : négation == : égal ? : : test
!= : différent conditionnel
Utiliser pour les
tests logiques > >= : supérieur, supérieur ou égal
< <= : inférieur, inférieur ou égal
&& : et logique
| | : ou logique
bit à bit ~ : négation binaire & : et binaire
(complément à un) | : ou binaire
^ : ou exclusif binaire
Utiliser pour
<< : décalage à gauche
affecter une valeur >> : décalage à droite
à une variable &= |= ^=
<<= >>= : version condensée
divers (type) : conversion de = : Affectation
type
sizeof(...): taille de
l'argument en
nombre d'octets
DECLARATION D’UNE CONSTANTE 16

CONSTANTE : Entité dont la valeur ne peut pas être modifiée durant l’exécution du
programme,
const id_type id_const = valeur ;
Mot clef C Obligatoire

Exemple :
const int a = 12 ;
const PRECISION = 5.0E-6 ;
const double VOLUME_MAX = 20.0 ;
const char Lettre = ’a’ ;

Lorsqu’un ensemble de constante ont une sémantique commune, il est préférable de


les remplacer par un type énumération :
const int NORD = 0 ;
const int SUD = 1 ;
enum Orientation { NORD,SUD,EST,OUEST } ;
const int EST = 2 ;
const int OUEST= 3 ;
LE TYPE « ENUMERATION » 17

Déclaration d’un type « énumération»


enum identificateur { label1, label2, ... , labeli, ..., labeln} ;
Nom du type éléments de la liste d’énumération

constantes entières

Par défaut, valeurs numérotées à partir de 0


avec un pas de progression de 1
Exemples :
enum Logical {FALSE, TRUE} ;

enum Feux_Tricolore {ROUGE, ORANGE, VERT} ;

enum Les_Mois { JANVIER=1, FEVRIER, MARS, AVRIL, MAI, JUIN, JUILLET, AOUT,
SEPTEMBRE, OCTOBRE, NOVEMBRE, DECEMBRE} ;

enum Orientation {NORD, SUD, EST, OUEST} ;

enum Bouton { PLUS=100, MOINS, FICHIER, SIMULER } ;

Utilisation d’un type « énumération»


permet de définir des listes de constantes,
souvent utilisées en association avec la structure d'aiguillage switch() pour
libeller chaque case.
LE TYPE « ENUMERATION » 18

Déclaration d’une variable de type « énumération»


S’effectue d’une manière totalement identique à celle d’une variable d’un type prédéfini :

Exemples :
Logical TERMINER ;
Noms de type Feux_Tricolore Feux = ROUGE ;
« énumération » Les_Mois Precedent = AOUT ;
Noms des variables de type « énumération »

Seuls les opérateurs de comparaisons (>,<, ==, !=,<=,>=) sont directement utilisables,
Les_Mois Courant;
Les_Mois Suivant = OCTOBRE ;
 if (Courant < Suivant) ...

Les opérateurs arithmétiques (+,-, *,/,%,++,--.) sont utilisables seulement après


conversion vers un type entier (char, int, long)
Les_Mois Courant = SEPTEMBRE ;
Les_Mois Suivant ;

 Suivant = Courant + 1 ; incorrect


 Suivant = (Les_Mois)((int)Courant + 1) ; correct
Les commentaires 19

Comme tout langage évolué, le langage C autorise la présence de commentaires


dans vos programmes source. Il s’agit de textes explicatifs destinés aux lecteurs
du programme et qui n’ont aucune incidence sur sa compilation.

Ils sont formés de caractères quelconques placés entre les symboles /* et */ ou //.
Ils peuvent apparaître à tout endroit du programme où un espace est autorisé. En
général, cependant, on se limitera à des emplacements propices à une bonne
lisibilité du programme.

Voici quelques exemples de commentaires :

sur une lignes


// programme de calcul de racines carrées

sur plusieurs lignes


/* de programme source
============================================
commentaire quelque peu esthétique *
et encadré, pouvant servir, *
par exemple, d’en-tête de programme *
============================================ */
20

LES INSTRUCTIONS
INSTRUCTIONS D’ENTREE/SORTIE 21

Sortie standard

Entrée standard l’écran

le clavier

Nécessite l’inclusion d’une librairie en début de programme,


#include <stdio.h>
Instruction d’affichage à l’écran printf ( ”format”, var1, var2,…, varn);

Instruction de saisie au clavier, scanf ( ”format”, &var1) ;

spécificateurs de format

%d : entier en notation décimale


%o : entier en notation octale
%x : entier en notation hexadécimale
%u : entier en notation décimal non signée
%p : pointeur
%f : réel en représentation flottante
%e : réel en représentation exposant
%c : caractère
%s : chaîne de caractères
%l : élément long (entier ou réel)
Plus sur printf() et scanf() 22

Les fonctions printf() et scanf() transforment des objets d’une représentation à


partir d’une chaîne de caractères (vision humaine) en une représentation
manipulable par la machine (vision machine), et vice et versa.

Pour réaliser ces transformations ces fonctions sont guidées par des formats qui
décrivent le type des objets manipulés (vision interne) et la représentation en
chaîne de caractères cible (vision externe).

Par exemple, un format du type %x signifie d’une part que la variable est du type
entier et d’autre part que la chaîne de caractères qui la représente est exprimée
en base 16 (hexadécimal).

Pour printf(), un format est une chaîne de caractères dans laquelle sont insérés
les caractères représentant la ou les variables à écrire.

Pour scanf(), un format est une chaîne de caractères qui décrit la ou les variables
à lire.
Pour chaque variable, un type de conversion est spécifié. Ce type de
conversion est décrit par les caractères qui suivent le caractère “%”.
résumé des déclarations de variables et des formats 23
déclaration lecture écriture format externe
int i ; scanf ("%d",&i); printf ("%d",i ); décimal
int i ; scanf ("%o",&i); printf ("%o",i ); octal
int i ; scanf ("%x",&i); printf ("%x",i ); hexadécimal
unsigned int i ; scanf ("%u",&i); printf ("%u",i ); décimal
short j ; scanf ("%hd",&j); printf ("%d",j ); décimal
short j ; scanf ("%ho",&j); printf ("%o",j ); octal
short j ; scanf ("%hx",&j); printf ("%x",j ); hexadécimal
unsigned short j ; scanf ("%hu",&j); printf ("%u",j ); décimal
long k; scanf ("%ld",&k); printf ("%ld",k); décimal
long k; scanf ("%lo",&k); printf ("%lo",k); octal
long k; scanf ("%lx",&k); printf ("%lx",k); hexadécimal
unsigned long k; scanf ("%lu",&k); printf ("%lu",k); décimal

float l ; scanf ("%f",&l); printf ("%f",l ); point décimal


float l ; scanf ("%e",&l); printf ("%e",l ); exponentielle
float l ; printf ("%g",l ); la plus courte des deux
double m; scanf ("%lf",&m); printf ("%f",m); point décimal
double m; scanf ("%le"&m); printf ("%e",m); exponentielle
double m; printf ("%g",m); la plus courte
long double n; scanf ("%Lf"&n); printf ("%Lf",n); point décimal
long double n; scanf ("%Le"&n); printf ("%Le",n); exponentielle
long double n; printf ("%Lg",n); la plus courte
char o; scanf ("%c",&o); printf ("%c",o); caractère
char p [10]; scanf ("%9s",p); printf ("%s",p); chaîne de caractères
scanf ("%9s",&p[0]);
INSTRUCTIONS D’ENTREE/SORTIE 24

Exemple
# include <stdio.h>

void main () Donnez b : 5


{ Resultat : 10*5=50
int a = 10 ; Fin
int b ;

printf ("Donnez b : " ) ;


scanf ("%d", &b ) ;
prin tf ("Resultat : %d*%d=%d \n",a,b,
n", a, b,
a*b ) ;
printf ("Fin") ;
}

un certain nombre d’abréviations est aussi disponible :


’\a’ : alert (sonnerie, BEL) ;
’\b’ : backspace (BS) ;
’\f’ : saut de page (FF) ;
’\n’ : saut de ligne (LF) ;
’\r’ : retour chariot (CR) ;
’\t’ : tabulation horizontale (HT) ;
’\v’ : tabulation verticale (VT) ;
Lectures multiples avec scanf() 25

# inc lude < s t d i o . h>


i n t main ( i n t a rgc , char a rgv [ ] )
{
inti=10;
floatl=3.14159;
char p [ 5 0 ] = " Bonjour " ;
p r i n t f ( "%d b o n j o u r %f %s \ n " , i , l , p ) ;
s c a n f ( "%d%f%49s " , &i , &l , p ) ;
p r i n t f ( " Apres l e c t u r e au c l a v i e r : %d %f %s \ n " , i , l , p ) ;
r e turn 0 ;
}
DONNÉES EN ENTRÉE
23 6.55957 salut
DONNÉES ÉCRITES SUR LE FICHIER STANDARD DE SORTIE
10 3.141590 Bonjour
Apres lecture au clavier : 23 6.559570 salut
INSTRUCTIONS CONDITIONNELLES 26

Structure conditionnelle avec alternative : if

C/C++

vrai faux
condition_logique 
if (condition_logique)
{
séquence 1 séquence 2 séquence 1 ;
}
else
{
séquence 2 ;
}

- la condition logique doit toujours être entre parenthèses ( …),


- peuvent être imbriquées,
- si la séquence d'instruction ne comporte qu'une seule instruction, les accolades { et } peuvent
éventuellement être omises,
- si elle n’est pas nécessaire, l’alternative else peut être omise.

Un else se rapporte toujours au dernier if rencontré auquel un else


n’a pas encore été attribué.
INSTRUCTIONS CONDITIONNELLES 27

Structure de sélection avec alternative multiple sur conditions


C/C++


vrai if (condition_1)
Condition_1 séquence 1
{
séquence 1 ;
faux }
vrai else if (condition_2)
Condition_2 séquence 2 {
séquence 2 ;
faux }


vrai else if (condition_n )
Condition_n séquence n {
séquence n ;
faux }
séquence n+1 else
{
séquence n+1 ;
}

- permet « d’aiguiller » l’exécution du programme vers le bloc d'instructions dont la condition logique
d’entrée est vérifiée,

- le dernier else n'est pas obligatoire mais recommandé.


Exemple de if 28

Voici un exemple d’utilisation de if imbriqués. Il s’agit d’un


programme de facturation avec remise. Il lit en donnée un
simple prix hors taxes et calcule le prix TTC correspondant
(avec un taux de TVA constant de 18,6 %). Il établit ensuite
une remise dont le taux dépend de la valeur
ainsi obtenue, à savoir :
● 0 % pour un montant inférieur à 1 000 F
● 1 % pour un montant supérieur ou égal à 1 000 F et
inférieur à 2 000 F
● 3 % pour un montant supérieur ou égal à 2 000 F et
inférieur à 5 000 F
● 5 % pour un montant supérieur ou égal à 5 000 F
Solution de l’exemple de if 29

#include <stdio.h>
#define TAUX_TVA 18.6
main()
{
double ht, ttc, net, tauxr, remise ;
printf("donnez le prix hors taxes : ") ;
scanf ("%lf", &ht) ; EXECUTION
ttc = ht * ( 1. + TAUX_TVA/100.) ;
if ( ttc < 1000.) donnez le prix hors taxes : 500
tauxr = 0 ; prix ttc 593.00
else if ( ttc < 2000 ) remise 0.00
tauxr = 1. ; net à payer 593.00
else if ( ttc < 5000 ) __________________________
tauxr = 3. ; donnez le prix hors taxes : 4000
else prix ttc 4744.00
tauxr = 5. ; remise 142.32
remise = ttc * tauxr / 100. ; net à payer 4601.68
net = ttc - remise ;
printf ("prix ttc %10.2lf\n", ttc) ;
printf ("remise %10.2lf\n", remise) ;
printf ("net à payer %10.2lf\n", net) ;
}
INSTRUCTIONS CONDITIONNELLES 30

Structure de sélection avec alternative multiple sur valeurs


C/C++

valeur_1 
expression séquence 1
switch ( expression )
{
valeur_2
séquence 2 case valeur_1 : séquence 1 ;
break ;
valeur_3 case valeur_2 : séquence 2 ;
séquence 3 break ;
case valeur_3 : séquence 3 ;
break ;
valeur_n 
séquence n
default case valeur_n : séquence n ;
break ;
séquence n+1 default : séquence n+1 ;

}

- permet « d’aiguiller » l’exécution du programme vers le bloc d'instructions dont la condition d’entrée (valeur associée au
case) correspond au résultat de l’expression (située dans le switch),
- la valeur de expression doit être un type discret (char, int, long) ou un type énumération,
- l'étiquette default n'est pas obligatoire mais recommandée,
- l’instruction break permet de sortir de la structure switch après l’exécution des instructions associées à un case donné,
- Pour exécuter la même séquence d’instructions pour des case différents (conjonction de valeurs), il
suffit d’écrire ces case consécutivement et de n’associer le break qu’au dernier case de la liste.
Exemple de switch 31

main()
{
int n ;

printf ("donnez un entier : ") ;


Exécution
scanf ("%d", &n) ;
switch (n)
donnez un entier : 2
{
deux
case 0 : printf ("nul\n") ;
au revoir
break ;
_______
case 1 : printf ("un\n") ;
donnez un entier : 25
break ;
grand
case 2 : printf ("deux\n") ;
au revoir
break ;
default : printf ("grand\n") ;
}
printf ("au revoir\n") ;
}
INSTRUCTIONS DE REPETITION 32

Structure de répétition sur compteur


Pas > 0

initialisation C/C++

for ( var=début ; var<=fin ; var=var+pas )
faux {
condition_logique
séquence d'instructions ;
}
vrai 
séquence
d'instructions
Pas < 0

modifieur
C/C++

for ( var=début ; var>=fin ; var=var+pas )
{
séquence d'instructions ;
}

- à utiliser lorsque le nombre de répétition est connu
d'avance (1 à n fois),
- doit être interprétée comme une structure while
Exemple de for 33

main() Exécution
{
int i ;
bonjour 1 fois
for ( i=1 ; i<=5 ; i++ ) bonjour 2 fois
{ bonjour 3 fois
printf ("bonjour ") ; bonjour 4 fois
printf ("%d fois\n", i) ;
bonjour 5 fois
}
}

La ligne : for ( i=1 ; i<=5 ; i++ )

comporte en fait trois expressions. La première est évaluée (une seule


fois) avant d’entrer dans la boucle. La deuxième conditionne la
poursuite de la boucle. Elle est évaluée avant chaque parcours. La
troisième, enfin, est évaluée à la fin de chaque parcours.
INSTRUCTIONS DE REPETITION 34

Structure de répétition sur condition : tant que (…) faire {…}

C/C++
faux
condition_logique

vrai
while ( condition_logique )
{
séquence séquence d'instructions ;
d'instructions }

- utilisée lorsque le nombre de répétition est a priori inconnu (0 à ? fois),

- toujours initialiser les variables intervenant dans la condition logique,

- la séquence d'instructions répétée doit obligatoirement contenir une action


qui modifie la valeur de la condition logique,

TD 1 : Exercice 4.4
Exemple de while() 35

main() Exécution
{ donnez un nombre : 15
int n, som ; donnez un nombre : 25
donnez un nombre : 12
som = 0 ; donnez un nombre : 60
while (som<100)
{ somme obtenue : 112
printf ("donnez un nombre : ") ;
scanf ("%d", &n) ;
som += n ;
}
printf ("somme obtenue : %d", som) ;
}
répète l’instruction qui suit (ici un bloc) tant que la condition mentionnée est
vraie, comme le ferait do... while. En revanche, cette fois, la condition de
poursuite est examinée avant chaque parcours de la boucle et non
après. Ainsi, contrairement à ce qui se passait avec do... while, une telle
boucle peut très bien n’être parcourue aucune fois si la condition est fausse
dès qu’on l’aborde (ce qui n’est pas le cas ici).
INSTRUCTIONS DE REPETITION 36

Structure de répétition sur condition : faire {…} tant que (…)

C/C++

séquence 
d'instructions
do
{
séquence d'instructions ;
}
condition_logique while ( condition_logique ) ;
vrai 
faux
- utilisée lorsque le nombre de répétition est a priori inconnu (1 à ? fois),

- l’initialisation des variables intervenant dans la condition logique n’est pas


indispensable,

- la séquence d'instructions répétée doit obligatoirement contenir une action


qui modifie la valeur de la condition logique,

TD 1 : Exercice 4.6
Exemple de do … while() 37

Exécution
main() donnez un nb >0 : -3
{ vous avez fourni -3
int n ; donnez un nb >0 : -9
do vous avez fourni –9
{ donnez un nb >0 : 12
printf ("donnez un nb >0 : ") ; vous avez fourni 12
scanf ("%d", &n) ; réponse correcte
printf ("vous avez fourni %d\n", n) ;
}
while (n<=0) ;
printf ("réponse correcte") ;
}
répète l’instruction qu’elle contient (ici un bloc) tant
que la condition mentionnée (n<=0) est vraie (c’est-à-
dire, en C, non nulle). Autrement dit, ici, elle demande
un nombre à l’utilisateur (en affichant la valeur lue)
tant qu’il ne fournit pas une valeur positive.
38

LES TABLEAUX
TABLEAUX A UNE DIMENSION 39

TABLEAU : Structure de données de dimension finie dont les éléments sont tous de
même type

Déclaration d’un tableau :


type nom [ n ] = { val1, val2, ..., valn } ;

Type des éléments Nombre Valeurs initiales (facultatif)


d’éléments
Nom du tableau

Exemple :
int tab[5] ; (tableau de 5 entiers)
float Temperature[3] = {23.0, 10.4, 34.1 }; (tableau de 3 réels initialisés)
unsigned char PDU[] = { 0x1C, 0xFE, 0x2A, 0x10 } ; (dimension  4)
double Cond_Init[6] = { 5.0E-3, 1.0e-8 } ; ( 4 derniers éléments initialisés à 0)
Remarques :
en C, les tableaux commencent à l’indice 0 ( indice du 1er élément : 0),
pas de vérification de débordement,
Exemple pour remplir le tableau de 10 entier:
For(i=0;i<10;i++)
scanf(‘’%d’’,&tab[ i ]);
CHAINE DE CARACTERES 40

CHAINE DE CARACTERE :
Tableau de caractères où le dernier caractère est le caractère \0 (délimiteur de
chaîne).
"Chaine"  C h a i n e \0 (Attention : ‘a’  “a“)

Déclaration d’une chaîne de caractère :

char nom [ n ] = “texte “ ;


texte initial (facultatif)

Nom de la chaîne Dimension = nombre de caractères effectifs + 1 (pour le \0)

La manipulation des chaînes de caractères au moyen de fonctions


fournies dans la librairie <string.h>
41
Généralités sur les fonctions portant sur des chaînes

La fonction strlen: La fonction strlen fournit en résultat la longueur d’une chaîne dont on lui
a transmis l’adresse en argument.
strlen ("bonjour") vaudra 7 ; de même, avec : char * adr = "salut" ; strlen (adr) vaudra 5.

La fonction strcat: strcat ( but, source ); Cette fonction recopie la seconde chaîne (source)
à la suite de la première (but), après en avoir effacé le caractère de fin.

La fonction strncat: strncat (but, source, lgmax); travaille de façon semblable à strcat en
offrant en outre un contrôle sur le nombre de caractères qui seront concaténés à la chaîne
d’arrivée (but).

La fonction: strcmp ( chaîne1, chaîne2 ); compare deux chaînes dont on lui fournit
l’adresse et elle fournit une valeur entière définie comme étant :
● positive si chaîne1 > chaîne2 (c’est-à-dire si chaîne1 arrive après chaîne2, au
sens de l’ordre défini par le code des caractères) ;
● nulle si chaîne1 = chaîne2 (c’est-à-dire si ces deux chaînes contiennent exactement
la même suite de caractères) ;
● négative si chaîne1 < chaîne2.
Par exemple (quelle que soit l’implémentation) : strcmp ("bonjour", "monsieur") est négatif et :
strcmp ("paris2", "paris10") est positif.
42
Généralités sur les fonctions portant sur des chaînes

La fonction : strncmp ( chaîne1, chaîne2, lgmax ); travaille comme strcmp mais elle limite
la comparaison au nombre maximal de caractères indiqués par l’entier lgmax.
Par exemple : strncmp ("bonjour", "bon", 4) est positif tandis que : strncmp ("bonjour", "bon",
2) vaut zéro.

La fonction : strcpy ( but, source ); recopie la chaîne située à l’adresse source dans
l’emplacement d’adresse destin. Là encore, il est nécessaire que la taille du second
emplacement soit suffisante pour accueillir la chaîne à recopier, sous peine d’écrasement
intempestif. Cette fonction fournit comme résultat l’adresse de la chaîne but.

La fonction : strncpy ( but, source, lgmax ); procède de manière analogue à strcpy, en


limitant la recopie au nombre de caractères précisés par l’expression entière lgmax.

La fonction : strchr ( chaîne, caractère ); recherche, dans chaîne, la première position où


apparaît le caractère mentionné.

La fonction : strrchr ( chaîne, caractère ); réalise le même traitement que strchr, mais en
explorant la chaîne concernée à partir de la fin. Elle fournit donc la dernière occurrence du
caractère mentionné.

La fonction : strstr ( chaîne, sous-chaîne ); recherche, dans chaîne, la première


occurrence complète de la sous-chaîne mentionnée.
TABLEAUX MULTIDIMENSIONNELS 43

TABLEAU MULTIDIMENSIONNEL :

création de tableaux à p dimensions d1, d2, d3,…,dp en juxtaposant p


opérateurs d’indexation [ ].

Déclaration d’un tableau à p dimensions :

type nom [n1][n2]…[np-1][np]= { val1, val2, ..., valk } ;

Type des éléments Nombre d’éléments sur la Valeurs initiales (facultatif)


ième dimension k = n1 x n2 x …x np-1 x np
Nom du tableau

Remarque : l’initialisation d’un tableau multidimensionnel est réalisé ligne par ligne

Exemple pour remplir le tableau de 10 lignes et 6 colonnes on a besoin de


deux indices i et j qui sont des variables de type entier :

For(i=0;i<10;i++)
For(j=0;j<10;j++)
scanf(‘’%d’’,&tab[ i ] [ j ]);
Les enregistrements : le type structure 44

Nous avons déjà vu comment le tableau permettait de désigner sous un seul


nom un ensemble de valeurs de même type, chacune d’entre elles étant repérée
par un indice.

La structure, quant à elle, va nous permettre de désigner sous un seul nom un


ensemble de valeurs pouvant être de types différents. L’accès à chaque élément
de la structure (nommé champ) se fera, cette fois, non plus par une indication de
position, mais par son nom au sein de la structure.

Une structure est donc une variable composée de plusieurs champs qui sert à
représenter un objet réel ou un concept.
Par exemple une voiture peut être représentée par les renseignements suivants :
la marque, la couleur, l’année, . . .
Le langage C permet de définir des modèles de structures comme les autres
langages évolués.
Déclaration d’une structure 45

Voyez tout d’abord cette déclaration :


struct enreg
{
int numero ;
int qte ;
float prix ;
};

Celle-ci définit un modèle de structure mais ne réserve pas de variables


correspondant à cette structure. Ce modèle s’appelle ici enreg et il précise le
nom et le type de chacun des champs constituant la structure (numero, qte et
prix). Une fois un tel modèle défini, nous pouvons déclarer des variables du type
correspondant (souvent, nous parlerons de structure pour désigner une variable
dont le type est un modèle de structure).
Par exemple : struct enreg art1 ;
réserve un emplacement nommé art1 « de type enreg » destiné à contenir deux
entiers et un flottant.

De manière semblable : struct enreg art1, art2 ;


réserve deux emplacements art1 et art2 du type enreg.
Utilisation d’une structure 46

En C, on peut utiliser une structure de deux manières :


● en travaillant individuellement sur chacun de ses champs ;
● en travaillant de manière globale sur l’ensemble de la structure.
Utilisation des champs d’une structure
Chaque champ d’une structure peut être manipulé comme n’importe quelle variable du type
correspondant. La désignation d’un champ se note en faisant suivre le nom de la variable
structure de l’opérateur « point » (.) suivi du nom de champ tel qu’il a été défini dans le
modèle (le nom de modèle lui-même n’intervenant d’ailleurs pas). Voici quelques exemples
utilisant le modèle enreg et les variables art1 et art2 déclarées de ce type.

art1.numero = 15 ; affecte la valeur 15 au champ numero de la structure art1.


printf ("%e", art1.prix) ; affiche, suivant le code format %e, la valeur du champ prix de la
structure art1.
scanf ("%e", &art2.prix) ; lit, suivant le code format %e, une valeur qui sera affectée au
champ prix de la structure art2. Notez bien la présence de l’opérateur &.
art1.numero++ incrémente de 1 la valeur du champ numero de la structure art1.

Remarque : La priorité de l’opérateur « . » est très élevée, de sorte qu’aucune des


expressions ci-dessus ne nécessite de parenthèses (voyez le tableau du chapitre 3, « Les
opérateurs et les expressions en langage C »).
Utilisation d’une structure 47

Utilisation globale d’une structure


Il est possible d’affecter à une structure le contenu d’une structure définie à partir du
même modèle.

Par exemple, si les structures art1 et art2 ont été déclarées suivant le modèle enreg
défini précédemment, nous pourrons écrire :
art1 = art2 ; Une telle affectation globale remplace avantageusement :
art1.numero = art2.numero ;
art1.qte = art2.qte ;
art1.prix = art2.prix ;

Notez bien qu’une affectation globale n’est possible que si les structures ont été définies
avec le même nom de modèle ; en particulier, elle sera impossible avec des
variables ayant une structure analogue mais définies sous deux noms différents.
L’opérateur d’affectation et, comme nous le verrons un peu plus loin, l’opérateur d’adresse
& sont les seuls opérateurs s’appliquant à une structure (de manière globale).

Remarque : L’affectation globale n’est pas possible entre tableaux. Elle l’est, par contre,
entre structures. Aussi est-il possible, en créant artificiellement une structure contenant un
seul champ qui est un tableau, de réaliser une affectation globale entre tableaux.
Utilisation d’une structure 48

Initialisations de structures

On retrouve pour les structures les règles d’initialisation qui sont en vigueur pour tous les
types de variables, à savoir :
● En l’absence d’initialisation explicite, les structures de classe statique sont, par défaut,
initialisées à zéro ; celles possédant la classe automatique ne sont pas initialisées par
défaut (elles contiendront donc des valeurs aléatoires).

● Il est possible d’initialiser explicitement une structure lors de sa déclaration. On ne peut


toutefois employer que des constantes ou des expressions constantes et cela aussi bien
pour les structures statiques que pour les structures automatiques,alors que, pour les
variables scalaires automatiques, il était possible d’employer une expression quelconque
(on retrouve là les mêmes restrictions que pour les tableaux).

Voici un exemple d’initialisation de notre structure art1, au moment de sa déclaration :


struct enreg art1 = { 100, 285, 2000 } ;

Vous voyez que la description des différents champs se présente sous la forme d’une liste
de valeurs séparées par des virgules, chaque valeur étant une constante ayant le type du
champ correspondant. Là encore, il est possible d’omettre certaines valeurs.
Utilisation d’une structure : typedef 49

Définir des synonymes avec typedef

La déclaration typedef permet de définir ce que l’on nomme en langage C des types
synonymes.
A priori, elle s’applique à tous les types et pas seulement aux structures. C’est pourquoi
nous commencerons par l’introduire sur quelques exemples avant de montrer l’usage que
l’on peut en faire avec les structures. Exemples d’utilisation de typedef

La déclaration : typedef int entier ;


signifie que entier est synonyme de int, de sorte que les déclarations suivantes sont
équivalentes : int n, p ; entier n, p ;

De même : typedef int * ptr ; signifie que ptr est synonyme de int *. Les déclarations
suivantes sont équivalentes : int * p1, * p2 ; ptr p1, p2 ;

Notez bien que cette déclaration est plus puissante qu’une substitution telle qu’elle
pourrait être réalisée par la directive #define. Nous n’en ferons pas ici de description
exhaustive, et cela d’autant plus que son usage tend à disparaître. À titre indicatif, sachez,
par exemple, qu’avec la déclaration : typedef int vecteur [3] ;
les déclarations suivantes sont équivalentes : int v[3], w[3] ; vecteur v, w ;
Typedef : Application aux structures 50

En faisant usage de typedef, les déclarations des structures art1 et art2 du


précédemment peuvent être réalisées comme suit :

struct enreg
{
int numero ;
int qte ;
float prix ;
};
typedef struct enreg s_enreg ;
s_enreg art1, art2 ;
ou encore, plus simplement :
typedef struct
{
int numero ;
int qte ;
float prix ;
} s_enreg ;
s_enreg art1, art2 ;

Par la suite, nous ne ferons appel qu’occasionnellement à typedef, afin de ne pas vous
enfermer dans un style de notations que vous ne retrouverez pas nécessairement dans
les programmes que vous serez amené à utiliser.
Tableaux de structures 51

Voyez ces déclarations : struct point


{
char nom ;
int x ;
int y ;
};
struct point courbe [50] ;
La structure point pourrait, par exemple, servir à représenter un point d’un plan, point qui
serait défini par son nom (caractère) et ses deux coordonnées. Notez bien que point est un
nom de modèle de structure, tandis que courbe représente effectivement un tableau de 50
éléments du type point. Si i est un entier, la notation : courbe[i].nom représente le nom du
point de rang i du tableau courbe. Il s’agit donc d’une valeur de type char. Notez bien que la
notation : courbe.nom[i] n’aurait pas de sens.
De même, la notation : courbe[i].x désigne la valeur du champ x de l’élément de rang i du
tableau courbe. Par ailleurs : courbe[4] représente la structure de type point correspondant
au cinquième élément du tableau courbe.
Enfin courbe est un identificateur de tableau, et, comme tel, désigne son adresse de
début. Là encore, voici, à titre indicatif, un exemple d’initialisation (partielle) de notre variable
courbe, lors de sa déclaration :
struct point courbe[50]= { {'A', 10, 25}, {'M', 12, 28},, {'P', 18,2} };
Structures comportant d’autres structures 52

Supposez que, à l’intérieur de nos structures employe et courant définies, nous ayons besoin
d’introduire deux dates : la date d’embauche et la date d’entrée dans le dernier poste
occupé. Si ces dates sont elles-mêmes des structures comportant trois champs
correspondant au jour, au mois et à l’année, nous pouvons alors procéder aux déclarations
suivantes : struct date
{
int jour, mois, annee ;
};
struct personne
{
char nom[30] , prenom[20] ;
float heures [31] ;
struct date date_embauche ;
struct date date_poste ;
} employe, courant ;
Vous voyez que la seconde déclaration fait intervenir un modèle de structure (date)
précédemment défini.
La notation : employe.date_embauche.annee représente l’année d’embauche
correspondant à la structure employe. Il s’agit d’une valeur de type int.

courant.date_embauche représente la date d’embauche correspondant à la structure


courant. Il s’agit cette fois d’une structure de type date. Elle pourra éventuellement faire
l’objet d’affectations globales comme dans :
courant.date_embauche = employe.date_poste ;
53

SOUS-PROGRAMMES ET MODULES
NOTION DE FONCTION ET DE PROCEDURE 54

Dans certains langages, on trouve deux sortes de modules, à savoir :


● Les fonctions, assez proches de la notion mathématique correspondante.
Notamment, une fonction dispose d’arguments (en C, comme dans la plupart des
autres langages, une fonction peut ne comporter aucun argument) qui
correspondent à des informations qui lui sont transmises et elle fournit un unique
résultat scalaire (simple) ; désigné par le nom même de la fonction, ce dernier peut
apparaître dans une expression. On dit d’ailleurs que la fonction possède une valeur
et qu’un appel de fonction est assimilable à une expression.

● Les procédures (terme Pascal) ou sous-programmes (terme Fortran ou


Basic) qui élargissent la notion de fonction. La procédure ne possède plus de
valeur à proprement parler et son appel ne peut plus apparaître au sein d’une
expression. Par contre, elle dispose toujours d’arguments. Parmi ces derniers,
certains peuvent, comme pour la fonction, correspondre à des informations qui lui
sont transmises. Mais d’autres, contrairement à ce qui se passe pour la fonction,
peuvent correspondre à des informations qu’elle produit en retour de son appel. De
plus, une procédure peut réaliser une action, par exemple afficher un message (en
fait, dans la plupart des langages, la fonction peut quand même réaliser une action,
bien que ce ne soit pas là sa vocation).
NOTION DE FONCTION ET DE PROCEDURE 55

En C, il n’existe qu’une seule sorte de module, nommé fonction (il en ira de même en C++
et en Java, langage dont la syntaxe est proche de celle de C). Ce terme, quelque peu abusif,
pourrait laisser croire que les modules du C sont moins généraux que ceux des autres
langages. Or il n’en est rien, bien au contraire ! Certes, la fonction pourra y être utilisée
comme dans d’autres langages, c’est-à-dire recevoir des arguments et fournir un résultat
scalaire qu’on utilisera dans une expression, comme, par exemple, dans : y = sqrt(x)+3 ;
Mais, en C, la fonction pourra prendre des aspects différents, pouvant complètement
dénaturer l’idée qu’on se fait d’une fonction. Par exemple :

● La valeur d’une fonction pourra très bien ne pas être utilisée ; c’est ce qui se passe
fréquemment lorsque vous utilisez printf ou scanf. Bien entendu, cela n’a d’intérêt que parce
que de telles fonctions réalisent une action (ce qui, dans d’autres langages, serait
réservée aux sous-programmes ou procédures).
● Une fonction pourra ne fournir aucune valeur.
● Une fonction pourra fournir un résultat non scalaire (nous n’en parlerons toutefois que dans
le chapitre consacré aux structures).
● Une fonction pourra modifier les valeurs de certains de ses arguments (il vous faudra
toutefois attendre d’avoir étudié les pointeurs pour voir par quel mécanisme elle y parviendra).
Ainsi, donc, malgré son nom, en C, la fonction pourra jouer un rôle aussi général que la
procédure ou le sous-programme des autres langages.
NOTION DE FONCTION 56

DECLARATION D’UNE FONCTION Paramètres formels

type_retour nom_fonction ( type1 arg1, type2 arg2, ..., typen argn ) ;

Spécifie le type de la La liste d’argument peut être vide mais la Ne pas oublier le ; qui marque le
valeur retournée par la présence des ( ) reste obligatoire fait qu’il s’agit juste d’une
fonction déclaration (pas de corps en
suivant)

DEFINITION D’UNE FONCTION


type_retour nom_fonction ( type1 arg1, type2 arg2, ..., typen argn )
Doit être { Attention, pas de ; car
identique à la déclaration des variables locales ; ici, l’entête est suivie
déclaration du corps
 Corps de la fonction
séquence d'instructions ;
Cette instruction doit apparaître dans toute fonction et
 doit toujours être la dernière.
return ( valeur_a_retourner ) ; Elle retourne une donnée qui doit être compatible avec
le type spécifié dans l’entête.
}

APPEL D’UNE FONCTION Liste de paramètres compatibles avec les paramètres formels

variable = nom_fonction ( par1, par2, ..., parn) ;


Affectation de la valeur retournée
Exemple de définition et d’utilisation 57

Nous vous proposons d’examiner tout d’abord un exemple simple de fonction


correspondant à l’idée usuelle que l’on se fait d’une fonction, c’est-à-dire recevant
des arguments et fournissant une valeur.

/***** le programme principal (fonction main) *****/


#include <stdio.h>
main()
{
float fexple (float, int, int) ; /* déclaration de fonction fexple */
float x = 1.5 ;
float y, z ;
int n = 3, p = 5, q = 10 ;
y = fexple (x, n, p) ; /* appel de fexple avec les arguments x, n et p */
printf ("valeur de y : %e\n", y) ;
z = fexple (x+0.5, q, n-1) ; /* appel de fexple avec les arguments x+0.5, q et n-1 */
printf ("valeur de z : %e\n", z) ;
}
/*************** la fonction fexple ****************/
float fexple (float x, int b, int c) {
float val ; /* déclaration d’une variable "locale" à fexple
val = x * x + b * x + c ;
return val ; }
Exemple de définition et d’utilisation 58

Nous y trouvons tout d’abord, de façon désormais classique, un programme


principal formé d’un bloc. Mais, cette fois, à sa suite, apparaît la définition d’une
fonction. Celle-ci possède une structure voisine de la fonction main, à savoir un
en-tête et un corps délimité par des accolades ({ et }). Mais l’en-tête est plus
élaboré que celui de la fonction main puisque, outre le nom de la fonction
(fexple), on y trouve une liste d’arguments (nom + type), ainsi que le type de la
valeur qui sera fournie par la fonction (on la nomme indifféremment « résultat »,
« valeur de la fonction », « valeur de retour »...) :

float fexple (float x, int b, int c)


| | | | |
type de la nom de la premier deuxième troisième
"valeur fonction argument argument argument
de retour" (type float) (type int) (type int)
Les noms des arguments n’ont d’importance qu’au sein du corps de la fonction.
Ils servent à décrire le travail que devra effectuer la fonction quand on l’appellera
en lui fournissant trois valeurs.
59
FONCTION : Quelques règles
Arguments muets et arguments effectifs

Les noms des arguments figurant dans l’en-tête de la fonction se nomment des «
arguments muets », ou encore « arguments formels » ou « paramètres formels »
(de l’anglais : formal parameter). Leur rôle est de permettre, au sein du corps de
la fonction, de décrire ce qu’elle doit faire.

Les arguments fournis lors de l’utilisation (l’appel) de la fonction se nomment des


« arguments effectifs » (ou encore « paramètres effectifs »). Comme le laisse
deviner l’exemple précédent, on peut utiliser n’importe quelle expression comme
argument effectif ; au bout du compte, c’est la valeur de cette expression qui sera
transmise à la fonction lors de son appel.

Notez qu’une telle « liberté » n’aurait aucun sens dans le cas des paramètres
formels : il serait impossible d’écrire un en-tête de fexple sous la forme float
fexple (float a+b, ...) pas plus qu’en mathématiques vous ne définiriez une

fonction f par f (x + y) = 5 !
60
FONCTION : Quelques règles
L’instruction return
● L’instruction return peut mentionner n’importe quelle expression. Ainsi, nous
aurions pu définir la fonction fexple précédente d’une manière plus simple :
float fexple (float x, int b, int c)
{
return (x * x + b * x + c) ;
}
● L’instruction return peut apparaître à plusieurs reprises dans une fonction,
comme dans cet autre exemple :
double absom (double u, double v)
{
double s ;
s=a+b;
if (s>0) return (s) ;
else return (-s)
}
Notez bien que non seulement l’instruction return définit la valeur du résultat,
mais, en même temps, elle interrompt l’exécution de la fonction en revenant dans
la fonction qui l’a appelée (n’oubliez pas qu’en C tous les modules sont des
fonctions, y compris le programme principal).
NOTION DE PROCEDURE 61

Cas des fonctions sans valeur de retour ou sans arguments


DECLARATION D’UNE PROCEDURE Paramètres formels

void nom_proc ( type1 arg1, type2 arg2, ..., typen argn ) ;


Une procédure ne
retourne pas de valeur
La liste d’argument peut être vide mais la Ne pas oublier le ; qui marque le fait
présence des ( ) reste obligatoire qu’il s’agit juste d’un déclaration (pas
de corps en suivant)

DEFINITION D’UNE PROCEDURE


void nom_proc ( type1 arg1, type2 arg2, ..., typen argn ) Attention, pas de ; car
ici, l’entête est suivie
Doit être { du corps
identique à la déclaration des variables locales ;
déclaration  Corps de la procédure
séquence d'instructions ;
}

Echange d’informations entre procédure et programme


par modification éventuelle des paramètres transmis 
dépend du mode de passage des paramètres
APPEL D’UNE PROCEDURE
Pas de mot-clef pour l’appel nom_proc ( par1, par2, ..., parn) ;
Liste de paramètres compatibles avec les paramètres formels
PROCEDURE : Cas des fonctions sans valeur de retour 62
ou sans arguments

Quand une fonction ne renvoie pas de résultat, on le précise, à la fois dans l’en-tête et
dans sa déclaration, à l’aide du mot-clé void. Par exemple, voici l’en-tête d’une fonction
recevant un argument de type int et ne fournissant aucune valeur : void sansval (int n)
et voici quelle serait sa déclaration : void sansval (int) ;
Naturellement, la définition d’une telle fonction ne doit, en principe, contenir aucune
instruction return. Certains compilateurs ne détecteront toutefois pas l’erreur. Quand une
fonction ne reçoit aucun argument, on place le mot-clé void (le même que précédemment,
mais avec une signification différente !) à la place de la liste d’arguments (attention, en
C++, la règle sera différente : on se contentera de ne rien mentionner dans la liste
d’arguments). Voici l’en-tête d’une fonction ne recevant aucun argument et renvoyant une
valeur de type float (il pourrait s’agir, par exemple, d’une fonction fournissant un nombre
aléatoire !) : float tirage (void)
Sa déclaration serait très voisine (elle ne diffère que par la présence du point-virgule !) :
float tirage (void) ;
Enfin, rien n’empêche de réaliser une fonction ne possédant ni arguments ni valeur de
retour. Dans ce cas, son en-tête sera de la forme : void message (void)
et sa déclaration sera : void message (void) ;

Remarque En toute rigueur, la fonction main est une fonction sans argument et sans
valeur de retour. Elle devrait donc avoir pour en-tête « void main (void) ». Certains
compilateurs fournissent d’ailleurs un message d’avertissement (« warning ») lorsque vous
vous contentez de l’en-tête usuel main.
DECOMPOSITION EN MODULE 63

 : principal.cpp
Déclaration d'éléments situés dans des
#include <librairie.h>
librairies prédéfinies (par exemple,
<stdio.h>, <string.h>, etc.). 
#include "liste_proc.h"

Déclaration de procédures (par
exemple) contenues dans des fichiers void main ( )
définis par l'utilisateur (ici, fichiers
{
situés dans le répertoire courant).
// définition des variables ;

// séquence d'instructions ;
nom_proc_2 ( arguments ) ;

}

 : liste_proc.h

void nom_proc_1 ( liste des arguments ) ;


Déclaration de procédures (par exemple)
void nom_proc_2 ( liste des arguments ) ;

 : liste_proc.cpp

#include <librairie.h>

void nom_proc_1 ( liste des arguments )


Définition des procédures déclarées dans {
liste_proc.h corps de la fonction 1 ;

}

void nom_proc_2 ( liste des arguments )


{
corps de la fonction 2 ;

}
PASSAGE D’ARGUMENTS PAR VALEUR 64

Lorsqu’une variable est transmise par valeur à un sous-programme, le contenu de cette


variable reste le même avant et après l’appel du sous-programme, même si au sein de ce
sous-programme, la variable formelle correspondante est modifiée.

Argument transmis par valeur

#include <iostream.h> module.h


#include "module.h"
float Moyenne ( int a, int b ) ;
void main ( )
{
int x = 100 ; /* 100 = 0x0064 */
int y = 300 ; /* 300 = 0x012C */
module.cpp
float Resultat = 0.0 ; la valeur de x est copiée dans a
Resultat = Moyenne ( x, y ) ;
float Moyenne ( int a, int b )
cout << "Moyenne de " << x << " et " << y << " = "
<< Resultat ; {
} float Res ;

la valeur de x n’est pas modifiée a = a + b ; /* 400=0x0190*/


Res = (float)(a) / 2.0 ;

 Moyenne de 100 et 300 = 200


}
return ( Res ) ;

Modification de
la valeur de a
dans Moyenne
PASSAGE D’ARGUMENTS PAR ADRESSE 65

Lorsqu’une variable est transmise par adresse (ou encore par référence) à un sous-
programme, le contenu de cette variable peut être modifiée par les traitements effectués
sur la variable formelle correspondante de ce sous-programme.
Pour cela, il suffit simplement d’ajouter dans l’entête du sous-programme le symbole & devant
le nom des paramètres formels que l’on désire transmettre par adresse.
au sein du sous-programme, ce paramètre est manipulé comme s’il avait été transmis par valeur,
à l’appel du sous-programme, on fournit simplement la variable en paramètre.
Argument transmis par
référence
#include <iostream.h>
module.h
#include "module.h"
float Moyenne ( int &a, int b ) ;
void main ( )
{ L’adresse de x est transmise au sous-
int x = 100 ;
int y = 300 ;
programme  les variables x et a
module.cpp
float Resultat = 0.0 ; occupent alors la même zone
mémoire.
Resultat = Moyenne ( x, y ) ; float Moyenne ( int &a, int b )
cout << "Moyenne de " << x << " et " << y << " = " {
<< Resultat ; float Res ;
} la variable x ressort avec la valeur de a
a = a + b ;
Modification de Res = ( float )(a) / 2.0 ;
Moyenne de 400 et 300 = 200 la valeur de a (et
return ( Res ) ;
donc aussi de x) }
dans Moyenne
RETOUR DE FONCTION 66

operateur.h affichage.h
int minimum ( int val1, int val2 ) ; void Afficher ( int Nbre, char Lettre );

operateur.cpp affichage.cpp
int minimum ( int val1, int val2 ) #include <iostream.h>
{
int min ; void Afficher ( int Nbre, char Lettre )
{
if (val1>val2) int i ;
min = val2 ;
else for (i=1 ; i<=Nbre ; i++)
min = val1 ; {
cout << Lettre ;
return ( min ) ; }
} cout << endl ;
}
}
#include <iostream.h>
#include "operateur.h"
#include "affichage.h"

void main()
{
const int Nmax = 10 ; principal.cpp
int Nbre ;

cout << "Nbre de lettre : " ;


cin >> Nbre ;
Afficher ( minimum(Nbre,Nmax), ’O’ ) ;
}
67

GESTION DYNAMIQUE DES VARIABLES


La gestion dynamique de la mémoire 68A

En langage C un programme comporte en définitive deux types de données :


● statiques ou automatiques ;
● dynamiques.

-Les données statiques ou automatiques occupent un emplacement


parfaitement défini lors de la compilation.

-Les données dynamiques , en revanche, n’ont pas une taille définie a


priori. En effet, elles ne sont créées et détruites qu’au fur et à mesure de
l’exécution du programme. Elles sont souvent gérées sous forme de ce que l’on
nomme une pile (stack en anglais), laquelle croît ou décroît suivant les
besoins du programme. Plus précisément, elle croît à chaque entrée dans une
fonction pour faire place à toutes les variables locales nécessaires pendant la
durée de vie de la fonction ; elle décroît d’autant à chaque sortie. leur libération
dépend, cette fois, de demandes explicites faites lors de l’exécution du
programme.
GESTION D’UNE VARIABLE 69

Mémoire
La phase de déclaration permet au
compilateur de savoir où et comment créer
Adresses en une variable.

Décimale Hexadécimales La position en mémoire d’une variable est


donnée par son adresse (rang du premier octet
0x0000 octet 1
0 occupé par la variable)
1 0x0001 octet 2
octet 3
2 0x0002 Exemple :
3 0x0003 octet 4
octet 5 Sur P 16 bits : int codé sur 2 octets
4 0x0004

Soit la déclaration d’un entier i initialisé avec la valeur


698 (soit 10111010 00000010 en binaire) :

int i = 698 ;
64520 0xFC08
64521 0xFC09 10111010 Variable
64522 0xFC0A 00000010 i Au niveau du compilateur, les informations suivantes
64523 0xFC0B sont déduites :
64524 0xFC0C  nom de la variable : i
 adresse mémoire : 64521 (par exemple)
 nombre de registres occupés : 2 (car entier)
 valeur initiale : 698  00000010 10111010
LE TYPE * (« pointeur ») 70

Type * : le type * (« pointeur ») est le type à déclarer pour les variables stockant
des adresses mémoires (au même titre que le type int permet de stocker des valeurs
entières, par exemple)

Utilisation
Permet d’accéder à une variable de manière indirecte (par son adresse)

Permet de gérer dynamiquement la mémoire (création/destruction dynamique de


variables, etc) et de définir des structures de données complexes (tableau, chaîne de
caractère, liste, arbre, graphe, etc),
Permet de mettre en œuvre certaines des relations UML (association, agrégation par
référence, etc)

Déclaration d’une variable de type « pointeur »

type_élément_pointé * idptr ;
Nom de la variable
Type de l’élément pointé
Identificateur du type « pointeur »

Connaître l’adresse de début d’une zone mémoire n’est pas suffisant pour y accéder,
Il faut aussi connaître la taille de la zone mémoire pointée  fournie par le type de
l’élément pointé.
VARIABLE “AUTOMATIQUE” / DYNAMIQUE 71

 Vue d’un programme, une variable est définie par un nom et un


type,

Variable
Vue de la machine, une variable est définie par une adresse
mémoire et son occupation mémoire.

Possibilité d’utiliser 2 sortes de variables :


Variable « automatique » Variable dynamique
 Création et destruction gérée automatiquement par  Création et destruction à la charge du programmeur
le système

void main() void main()


Déclaration comme un pointeur
{ {
int a ; int * a ;
La variable a n’existe pas
encore (non utilisable)
Variable a utilisable de a = malloc(sizeof(int) ;
sa déclaration jusqu’à la Variable a utilisable depuis sa
sortie du bloc création jusqu’à sa destruction
delete(a) ;
La variable a n’existe plus
(non utilisable)
} }
GESTION DYNAMIQUE DE LA MEMOIRE 72

INTERETS de la Gestion Dynamique de la mémoire :


Lorsqu’on ne dispose pas dès la compilation de toutes les informations
nécessaires pour créer cette variable,
Permet de créer cette variable au cours de l’exécution du programme, lorsque
l’information devient disponible
Exemples :
Création de variables occupant de gros volumes de mémoire  création au moment
opportun et destruction lorsqu’elles deviennent inutiles,
Création de tableaux dont la dimension exacte n’est connue qu’en cours d’exécution
(fournie par un utilisateur ou calculée par un algorithme)  évite de sur-dimensionner les
tableaux et de réserver inutilement de la mémoire
Création de structures de données particulières qui sont par nature construite de manière
dynamique (c.a.d, au cours de l’exécution), comme des listes (file d’attente), des arbres
(arbre d’exploration pour les systèmes d’aide à la décision ou les jeux), des graphes, etc.

Gestion d’une variable dynamique


Déclaration d’une variable dynamique : type * ptr ;
Allocation d’une variable dynamique : ptr = malloc(sizeof(type)) ;
Destruction d’une variable dynamique : free(ptr) ;
ACCESSIBILITE 73

Opérateur d’indirection * : opérateur unaire qui permet d’accéder (en lecture ou écriture) à la valeur
contenu dans une variable dynamique.
Variable « automatique » Variable dynamique
void main() void main()
{ {
int a ; int * a ;
La variable a n’existe pas encore (non utilisable)

a = 6 ; a = malloc(sizeof(int)) ;
cout << a ; *a = 6 ;
Accès direct à la valeur
contenue dans la variable a printf(format, a) ;
Accès indirect à la valeur de la
*a = *a+1 ; variable a
a = a+1 ;
free(a) ;
La variable a n’existe plus (non utilisable)
} }

Opérateur unaire & : opérateur qui permet d’obtenir l’adresse Adresses en Mémoire
mémoire de toute variable d’un programme Décimale
0
Hexadécimales
0x0000 octet 1

1 0x0001 octet 2
Soit la déclaration de l’entier i initialisé avec la 2 0x0002 octet 3
0x0003 octet 4
valeur 698 (sur un P 16 bits) : 3
4 0x0004 octet 5

int i = 698 ;

En supposant que le compilateur est réalisé 64520 0xFC08


64521 0xFC09 10111010 Variable
l’implantation mémoire ci-contre, alors : 64522 0xFC0A 00000010 i
64523 0xFC0B
&i  64521 (ou 0xFC09) 64524 0xFC0C
STRUCTURES DE DONNEES DYNAMIQUES 74

TABLEAU A UNE DIMENSION (vecteur)


déclaration : type * ptr ;
allocation : ptr = new type [dim] ;
ptr ème
accès au i élément : ptr[i-1] ;
dim libération : free[] ptr ;

TABLEAU A DEUX DIMENSION (matrice)


dim2 Une matrice dynamique ne peut pas être créée par :
ptr = new type [dim1][dim2]
Tableau de valeurs
déclaration : type ** ptr ;
Tableau de valeurs
dim1 allocation : ptr = malloc(dim1*sizeof(type));
for(i=0 ; i<dim1 ; i++)
ptr[i] = molloc(dim2*sizeof(type));

Tableau de accès à l’élément (i,j) : ptr[i-1][j-1]


pointeurs
libération : for (i=0 ; i<dim1 ; i++)
Accès avec ptr[1][2] possible car ptr[1] est un free ( ptr[i]) ;
pointeur, donc contient une adresse à laquelle on peut free (ptr );
à nouveau appliquer l'opérateur d'indexation [ ].
La gestion dynamique de la mémoire 75

Les outils de base de la gestion dynamique : malloc et free

Commençons par étudier les deux fonctions les plus classiques de gestion
dynamique de la mémoire, à savoir malloc et free.

La fonction malloc
Premier exemple
Considérez ces instructions :
#include <stdlib.h>
.....
char * adr ;
.....
adr = malloc (50) ;
.....
for (i=0 ; i<50 ; i++) *(adr+i) = 'x' ;

L’appel : malloc (50)


alloue un emplacement de 50 octets dans le tas et en fournit l’adresse en retour.
Ici, cette dernière est placée dans le pointeur adr. L’instruction for qui vient à la
suite n’est donnée qu’à titre d’exemple d’utilisation de la zone ainsi créée.
La gestion dynamique de la mémoire 76

Second exemple long * adr ;


.....
adr = malloc (100 * sizeof(long)) ;
.....
for (i=0 ; i<100 ; i++) *(adr+i) = 1 ;
Cette fois, nous nous sommes alloué une zone de 100 * sizeof(long) octets (notez l’emploi de sizeof qui
assure la portabilité). Mais nous l’avons considérée comme une suite de 100 entiers de type long. Pour ce
faire, vous voyez que nous avons pris soin de placer le résultat de malloc dans un pointeur sur des
éléments de type long. N’oubliez pas que les règles de l’arithmétique des pointeurs font que : adr + i
correspond à l’adresse contenue dans adr, augmentée de sizeof(long) fois la valeur de i (puisque adr pointe
sur des objets de longueur sizeof(long)).

D’une manière générale


Le prototype de malloc (qui figure dans stdlib.h) est précisément :
void * malloc (size_t taille) (stdlib.h)
Il montre tout d’abord que le résultat fourni par malloc est un pointeur générique. D’autre part, nous
constatons que l’unique argument de malloc est d’un type a priori inattendu (nous aurions pu penser à int
ou long). En fait, size_t est un nom de type prédéfini par typedef size_t est précisément défini dans le
fichier stddef.h, mais vous n’avez pas Programmer en besoin d’inclure vous-même ce fichier car cela est
déjà prévu dans stdlib.h. Le type exact lui correspondant dépend de l’implémentation. Cela signifie donc
que la taille maximale des objets que l’on peut s’allouer par malloc dépend de l’implémentation. (En
pratique, toutefois,
on peut toujours compter sur un minimum de 64 Ko).

Enfin, il faut savoir que malloc fournit un pointeur nul (NULL) dans le cas où l’allocation
mémoire a échoué. Bien entendu, dans un programme opérationnel, il sera nécessaire de
s’assurer qu’aucun problème de cette sorte n’apparaît.
La gestion dynamique de la mémoire 77

La fonction free
L’un des intérêts essentiels de la gestion dynamique est de pouvoir récupérer des
emplacements dont on n’a plus besoin. Le rôle de la fonction free est de libérer un
emplacement préalablement alloué. Voici un exemple de programme, exécuté ici dans un
environnement DOS. Il vous montre comment malloc peut profiter d’un espace préalablement
libéré sur le tas. Notez que la dernière allocation a pu se faire dans l’espace libéré par le
précédent appel de free.

Exemple d’utilisation de la fonction free


#include <stdio.h>
#include <stdlib.h>
main()
{
char * adr1, * adr2, * adr3 ;
adr1 = malloc (100) ;
printf ("allocation de 100 octets en %p\n", adr1) ;
adr2 = malloc (50) ;
printf ("allocation de 50 octets en %p\n", adr2) ;
free (adr1) ;
printf ("libération de 100 octets en %p\n", adr1) ;
adr3 = malloc (40) ;
printf ("allocation de 40 octets en %p\n", adr3) ;
}
78

GESTION DES FICHIERS


LES FICHIERS : introduction 79

Nous avons déjà eu l’occasion d’étudier les « entrées-sorties


conversationnelles », c’est-à-dire les fonctions permettant d’échanger
des informations entre le programme et l’utilisateur. Nous vous
proposons ici d’étudier les fonctions permettant au programme
d’échanger des informations avec des fichiers. A priori, ce terme de
fichier désigne plutôt un ensemble d’informations situé sur une «
mémoire de masse » telle que le disque ou la disquette. Nous verrons
toutefois qu’en C, comme d’ailleurs dans d’autres langages, tous les
périphériques, qu’ils soient d’archivage (disque, disquette...) ou de
communication (clavier, écran, imprimante...), peuvent être
considérés comme des fichiers. Ainsi, en définitive, les entrées-
sorties conversationnelles apparaîtront comme un cas
particulier de la gestion de fichiers.
LES FICHIERS : introduction 80

On distingue traditionnellement deux techniques de gestion de fichiers :

● l’accès séquentiel consiste à traiter les informations séquentiellement, c’est-à-


dire dansl’ordre où elles apparaissent (ou apparaîtront) dans le fichier ;

● l’accès direct consiste à se placer immédiatement sur l’information souhaitée,


sans avoir à parcourir celles qui la précèdent.

En fait, pour des fichiers disque (ou disquette), la distinction entre accès
séquentiel et accès direct n’a plus véritablement de raison d’être. D’ailleurs,
comme vous le verrez, en langage C, vous utiliserez les mêmes fonctions dans
les deux cas (exception faite d’une fonction de déplacement de pointeur de
fichier). Qui plus est, rien ne vous empêchera de mélanger les deux modes
d’accès pour un même fichier. Cependant, pour assurer une certaine
progressivité à notre propos, nous avons préféré commencer par vous montrer
comment travailler de manière séquentielle.

Vous aimerez peut-être aussi