Vous êtes sur la page 1sur 140

Université Mohamed 1er

Faculté des Sciences


Département d’Informatique

Filière : SMI et SMA S3


Module : Programmation 1
Année universitaire: 2020/2021
Pr. A. TAHIRI
1

CONTENU DU MODULE : Programmation 1

• Introduction au langage C
• Types de base, variables, constantes
• Opérateurs et expressions
• Les entrées sorties en C
• Les structures de contrôle
• Les tableaux
• Les pointeurs
2

1
Introduction
• Définition : Un langage informatique est un
ensemble de caractères, de symboles et de
mots-clés régis par des règles. Cet ensemble est
utilisé pour donner des instructions à un
ordinateur.

– il est plus compréhensible par l'humain que le


langage machine.
– Exemples de langages informatiques : Pascal, Fortran,
C, C++, Java …
3

Introduction
• Définition 1 : Un algorithme est une suite finie
d’opérations (instructions) à exécuter dans un
ordre déterminé sur un certain nombre d’objets
considérés comme données, puis il fournit un
nouvel ensemble d’objets constituants les
résultats souhaités. En plus il est écrit avec un
langage humain, non compréhensible par la
machine.

• La notation algorithmique doit être indépendante


des particularités des langages de programmation
et du système informatique. 4

2
Introduction
• Définition 2 : Un programme est l’expression (la
traduction) d’un algorithme dans un langage de
programmation donné.

• Etapes d’un traitement informatique : La mise au


point d'un programme informatique se fait en
plusieurs étapes :

Introduction

Etape d’analyse (par Algorithme


l’utilisateur)

Etape de programmation Langage de


(par l’utilisateur) programmation

Etape de compilation Langage


(par le compilateur) machine

Etape d’exécution (par


résultats
l’ordinateur) 6

3
Introduction
• La description d’un algorithme consiste à :
– Définir les données d’entrée et les résultats
souhaités du problème.
– Définir les relations entre les données.
– Définir les opérations à réaliser sur les données.

• L’étape de programmation consiste à :


– Traduire les instruction de l’algorithme en un
langage de programmation.

Introduction
– L’étape de compilation consiste à :
• Traduire le programme en langage machine.
• Cette traduction s’effectue à l’aide d’un programme appelé
compilateur.

– L’étape d’exécution consiste à :


• Visualiser les résultats souhaités.
• Cet étape est réalisée par l’ordinateur.

4
Programmation en langage C
• Le langage C est l’un des programmes les plus
utilisés dans le domaine de la programmation.
Ceci est dû au fait que les programmes
développés avec le langage C soient lisibles,
facilement modifiables et portables.
• Le langage C est un langage de haut niveaux
(structures de contrôles et structures de
données); aussi c’est un langage du bas niveau
(manipulation de la mémoire: bit et l’octet) .
C’est le point fort et la particularité de ce
langage. 9

Conception d’un programme en langage C

• La conception et l’exécution d’un programme par


le langage C passe par trois étapes :
– édition d’un programme source à l’aide d’un éditeur
de texte.
– Cette phase permet donc de créer un fichier texte. Ce
fichier écrit en code ASCCI (American Standard Code
for Information Interchange) sera sauvegardé avec
l’extension ".c " ( c minuscule )

10

5
– compilation du programme source à l’aide d’un
programme particulier appelé compilateur. Ce
programme vérifie la syntaxe, la grammaire et la
sémantique du programme source, et génère par la suite
le programme exécutable portant le même le nom que le
programme source mais avec une extension différente
qui est ".exe"
– Ce programme exécutable n’est autre que la traduction en
langage machine du programme source ;

– exécution du programme (de l’exécutable) généré dans la


phase précédente. Cela permet d’exécuter l’ensemble des
instructions du programme source.
11

Structure d’un programme C.


• Un programme C se présente sous la forme
générale :

<directives de compilation>
<déclaration des noms externes>
<textes (corps) des fonctions>

– La partie directives de compilation, contient ce dont le


compilateur a besoin pour compiler correctement le
programme.
• Exemple : #include <stdio.h>
#define N 10
12

6
Structure d’un programme C.
• La partie déclaration des noms externe on trouve
la déclaration des noms globaux dont la portée
est l’ensemble du programme, comme une
variable globale.

• Le corps des fonctions est la seule partie


obligatoire, elle contient au minimum la fonction
principale main().
– Dans cette partie on trouve aussi la définition des
différentes fonctions.
13

Importation des bibliothèques


• Elle se fait par le biais de la commande "#include"
suivie du nom de la bibliothèque écrite entre deux
crochets.
• L’instruction #include< stdio.h > permet d’importer la
bibliothèque des fonctions standards d’entrée-sortie
qui sont déjà prédéfinies.
• Les fonctions de cette bibliothèque peuvent être
utilisées par la suite dans le programme qu’on cherche
à développer. Nous donnons ci-dessous quelques
exemples de bibliothèques les plus utilisées :
14

7
exemples

• stdio.h : contient les déclarations des fonctions


d’entrée-sortie.
• math.h : contient les déclarations des fonctions
mathématiques.
• string.h : contient les déclarations des fonctions
permettant de manipuler les chaines de
caractères.

15

Corps du programme
• La fonction main ou le programme principal
commence toujours par l’instruction
main()
{
...
}
• L’exécution du programme consiste à exécuter
les instructions de la fonction main qui se
trouvent entre les accolades.
16

8
Instructions
• Une instruction est une action à faire par
l’ordinateur. Elle peut être simple comme elle peut
être composée.

• Dans le langage C, toutes les instructions se


terminent par un point-virgule ‘‘ ; ’’ .

• L’exécution des instructions en langage C se fait


d’une manière séquentielle en respectant l’ordre de
leur emplacement dans le programme. Ainsi, la
première instruction exécutée est celle qui se trouve
en tête de la fonction main. 17

Structure d’un programme C.


• Exemples de programmes en C:

#include<stdio.h>
main()
{
printf(" premier programme en c ") ;

getch();

} 18

9
Structure d’un programme C.
#include<stdio.h>
#define N 10
int i=1;
main()
{
printf(" N = %d et i = %d " , N , i ) ;

getch();
}
19

Déclaration et stockage des variables

• Définition : une Variable est un élément de


programme permettant de stocker des
informations (des valeurs).
• Une variable possède :
– Un nom (identificateur):
• permet de l'identifier de façon unique.
• doit commencer par une lettre
• doit être composé de lettres, de chiffres et du caractère
de soulignement « _ »
• les espaces ne sont pas autorisés
• C distingue les majuscules et les minuscules.

20

10
Déclaration et stockage des variables

Exemples d’identifiants :

Identifiant correct Identifiant incorrecte


nom1 1nom
nom_2 nom.2
Nom_de_variable Nom de variable
deuxieme_choix deuxième_choix
mot_francais mot_français

21

Déclaration et stockage des variables

• Mots réservés : Les mots suivants ne peuvent


pas être utilisés comme noms de variables. Ce
sont des mots réservés du langage C :

auto break case char const continue


default do double else enum extern float
for goto if int long register return short
signed sizeof static struct switch typedef
union unsigned void volatile while
22

11
Déclaration et stockage des variables
– Un type : indique
• la nature des informations qui seront représentées
dans la variable,
• La déclaration du type d’un objet permet de définir
l’espace mémoire qu’il lui sera alloué et les fonctions
qui lui sont applicables
• les opérations qu'on peut appliquer à cette variable.
– Une valeur :
• C’est la donnée stockée dans la variable.
• Une variable doit être déclarée avant son
utilisation!
23

Déclaration et stockage des variables


• En C, il n’y a que deux types fondamentaux :

– Les entiers : quatre variantes des nombres entiers


sont offertes :
• Char , short int , int et long int
• Ces quatre variantes peuvent être signés ou non signés : int
ou unsigned int.

– Les réels : trois variantes des nombres réels sont


offertes :
• float, double et long double.
24

12
Type caractère
• Le mot clé char est utilisé pour déclarer les
caractères.
• Le langage C ne fait pas de distinction entre un
caractère (exemple le caractère ‘a’) est son
code ASCII (97 en décimal pour le caractère
‘a’).
• Un caractère a besoin d’un octet pour être
représenté.

25

Type entier
• Le mot clé int est réservé pour déclarer les entiers. Ce mot
clé peut être précédé par l’un des deux attributs de
précision short et long.

• La plus part des implémentations mettent les short int sur


16 bits, les int sur 32 bits et les long int sur 64 bits, selon
les cas.

• De même, le mot clé int peut être précédé par l’un des
deux attributs de représentation signed et unsigned.
• Les types signed int peuvent représenter des entiers
signés, alors que le type unsigned int ne représente que
des entiers non signés.
26

13
Type réel
• Les nombres flottants peuvent être manipulés
selon trois types différents selon la précision
requise dans le programme.

• Les mots clés utilisés pour ces trois types sont


float, double et long double.

27

Les tableaux suivants donne une classification des


types de données. Cette représentation varie
d’une machine à une autre. Sur les machines à
32 bits on trouve:

Type Signification Taille (en Plage de valeurs


(Algorithme) octet)
char caractère 1 (code ASCII)

int entier 4 -2147483648 à


2147483647
unsigned int entier non 4 0 à 4294967295
signé
short entier court 2 -32768 à 32767
28

14
Type Signification Taille Plage de valeurs
(Algorithme) (octet)
unsigned Entier court 2 0 à 65535
short non signé
long int entier long 8 - pow(2,63) à
pow(2,63) - 1
unsigned long entier long 8 0 à pow(2,64) - 1
int non signé
float réel simple 4 - 3.4*1038 à 3.4*1038
précision
double réel double 8 - 1.7*10308 à 1.7*10308
précision
Long double réel long 10 -3.4*104932 à
double 3.4*104932
précision 29

Déclaration d’une variable


• La déclaration d’une variable permet de
préciser son type ainsi de lui allouer un espace
mémoire en fonction de son type et d’associer
un symbole (le nom de la variable, son
identificateur) à cet espace.
• La déclaration d’une variable se fait en
précisant son type suivi de son nom.

30

15
Exemples
• int i ;
Déclare une variable de type entier qui a le nom i
• short int j ;
Déclare une variable de type entier court qui a le
nom j
• float x , y ;
Déclare deux variables de type réel qui ont
respectivement les noms x et y
• char c1 , c2 ;
Déclare deux variables de type caractère qui ont
respectivement les noms c1 et c2
31

La valeur d’une variable


• Les valeurs des variables données dans
l’exemple précédent ???

Aucune valeur

• Pour le moment elles n’ont pas encore reçues


de valeurs

32

16
La valeur d’une variable
• Une variable peut avoir une valeur comme elle peut ne
pas l’avoir.
• Mais toute variable qui subit une opération arithmétique
doit avoir une valeur avant cette opération.
• Une variable peut recevoir une valeur de deux façons
différentes:
– de manière interne: le programme lui-même qui donnera la
valeur à cette variable. Soit par initialisation soit par affectation
d’une expression ayant une valeur (opération arithmétiques sur
des variables)
– de manière externe: le programme demandera la valeur de la
variable. L’utilisateur qui donnera cette valeur au programme
par le clavier ( la saisie de la valeur  la saisie de la variable 33
)

Instruction d’affectation (manière interne)


L’affectation désigne l’opération d’affecter une valeur à une variable.
En langage C, on utilise l'opérateur d'affectation " = "
l’instruction est la suivante :
Nom_variable = valeur ;

Exemple :
i = 1 ; après cette affectation, le contenu de la
variable i est égal à 1.
c = ‘B' ; après cette affectation, le contenu de la
variable c est égal à ‘B’ .

Rq : Les variables i et c doivent être déclarées avant ces affectations.


34

17
Initialisation
• Il est possible de donner des valeur à des
variables pendant leurs déclarations
(initialiser), l’instruction est :

Type Nom_variable = valeur ;

– Exemple :
• char c = 'A' ;
• int i = 1 ;
• float x = 1.5 ;
35

Déclaration des constantes


• Une constante est une variable qui conserve sa
valeur durant toute l’exécution du programme où
elle est déclarée.
• En C, on associe une valeur à une constante en
utilisant :
– la directive #define :
#define nom_constante valeur
Ici la constante ne possède pas de type.
– Par le mot clé const :
const type nom = valeur ;
Dans cette instruction la constante est typé.
36

18
Exemples
• #define Pi 3.141592

Pi sera remplacé par 3.141592 dans tout le programme.

• const float Pi = 3.141592 ;


Ici même chose sauf que la variable Pi à un type.

• #define Mot 'A'


Le cas d’un caractère 37

la lecture d’une variable (manière externe):


les entrées
• Il s’agit de l’instruction permettant à la machine de
dialoguer avec l’utilisateur.
– Dans un sens, des instructions permettent à
l’utilisateur de rentrer des valeurs à partir du clavier
pour qu’elles soient utilisées par le programme. Cette
opération est la lecture.

• En langage C : Pour saisir au clavier des données, il faut


utiliser la fonction scanf de la bibliothèque d’entrée-
sortie stdio.h (STandarD Input Output Header ce qui
signifie En-tête Standard d'Entrée/Sortie).
38

19
Sa syntaxe est la suivante :

scanf("format", &arg1, &arg2, …) ;

• arg1, arg2,… sont des variables dont les valeurs sont à


saisir à partir du clavier. Les arguments (les variables) de
la fonction scanf doivent être précédées du symbole &.

• la partie "format" est une suite de caractères précédée


du symbole % (spécificateurs de format ). Les
spécificateurs de format permettent de spécifier le
format de chaque variable.
39

Les symboles précisant les différents formats des variables


sont donnés dans le tableau ci-dessous :
SYMBOLE TYPE AFFICHAGE
%d int entier relatif
%ld long int entier long
%u unsigned int entier naturel non signé
%f float rationnel en notation décimale

%lf double rationnel double en notation


décimale

%e float, double rationnel en notation


scientifique
%c char caractère
40

20
Les sorties d’un programme
• Dans l’autre sens, d’autres instructions permettent au
programme de communiquer des valeurs à l’utilisateur
en les affichant à l’écran. Cette opération est l’écriture.

• Instructions d’écriture (ou affichage) de données :


la fonction printf est utilisée pour afficher à l’écran du
texte, des valeurs ou les deux. La syntaxe la suivante :

printf("format", expr1, expr2, …) ;

avec : 41

• expr1,… : sont les variables et les expressions dont


les valeurs sont à représentées.
• Le nombre de spécificateurs de format doit être
égale au nombre d’expressions
• Format : est le format de représentation, il peut
contenir du texte, des spécificateurs de format
(codes format : suite de caractères précédée du
symbole % indiquant le format d’affichage. Aussi
on peux trouver des séquences d’échappement.
• le format d’affichage des données est la même
que pour la fonction scanf.
• Avec la fonction printf on peux afficher un type
double par %f et un type long int par %d.
42

21
Exemples
#include<stdio.h>
main()
{
float x ;
double y ;
scanf("%f%lf", &x, &y);
printf(" la valeur de x=%f et de y=%lf ", x, y);
getch();
}
43

Autre écriture
#include<stdio.h>
main()
{
float x ;
double y ;
scanf("%f%lf", &x, &y); 
printf(" la valeur de x=%f et de y=%f ", x, y) ; 
getch();
}
44

22
Exemple
#include<stdio.h>
main()
{
double x=10.5 , y=2.5 ;
printf("%e divisé par %e égal à %e", x , y , x/y ) ;
}

Ce programme va afficher à l’écran :


1.0500000e+001 divisé par 2.500000e+000 égal à
4.200000e+000
45

Cas particulier: les caractères


• Écriture d’un caractère :
La fonction putchar permet d’afficher un
caractère à l’écran, ainsi l’instruction
char B ; putchar(B) ;
joue le même rôle que l’instruction :
char B ; printf("%c", B ) ;

• Lecture d’un caractère :


La fonction getchar permet de saisir un caractère
du clavier. B = getchar() ;
46

23
B=getchar() joue le même rôle que l’instruction :
scanf("%c",&B );

attention c’est faux


scanf lit aussi l’espace vide, saut de ligne et tabulation.
B= getchar() ; on peux l’écrire aussi

scanf(" %c",&B ); on ajoute un espace vide avant le %

De préférence on utilise getchar() pour lire un


caractère.
47

#include<stdio.h>
main()
{
char B;
B = getchar();
printf("vous avez tapé le caractère : \n") ;
putchar(B);
printf ("vous avez tapé le caractère :%c" , B ) ;
}
ce programme permet de lire un caractère à partir du
clavier et de l’afficher à l’écran par les deux fonctions
putchar et printf.
48

24
#include<stdio.h>
main()
{
char c = 'A' ;
printf(" le code Ascii de %c est %d ", c , c ) ;
}
Ce programme va afficher à l’écran :
le code Ascii de A est 65

ici on manipule la variable c de type caractère


une fois comme un caractère et une fois comme
un entier 49

Séquences d’échappement
• permettent d'afficher des caractères non
imprimables :
– \n : nouvelle ligne.
– \a : déclenche un signal sonore (alarme).
– \\ : back slash ( \ ). Affiche la barre oblique \
– \f : Provoque un saut de page
– \t : tabulation horizontale .
– \v : Génère une tabulation verticale
– \’ : apostrophe.
– \" : guillemet.
50

25
Opérateurs
• Les opérateurs sont des symboles qui
permettent d’effectuer des opérations sur des
variables pour produire un résultat.

• Une expression est un ensemble de variables


(ou valeurs), reliées par des opérateurs, et qui
est équivalent à une seule valeur qui peut être
affecter à une variable.

51

Operateurs
• On distingue plusieurs types d'opérateurs :

• les opérateurs d'affectation


• les opérateurs arithmétiques
• les opérateurs d'incrémentation
• les opérateurs de comparaison
• les opérateurs logiques

52

26
Opérateur d'affectation
• L’opérateur d’affectation permet d’affecter une
nouvelle valeur, une variable ou une expression
à une variable.
• La syntaxe est :
Var = expression ;
• Cette instruction permet d’affecter à la variable
Var la valeur obtenue suite à l’exécution de
l’instruction expression.

53

Opérateurs arithmétiques
• Les opérateurs arithmétiques sont présentés de la façon
suivante :
+ Addition nom_var = expression1 + expression2 ;
- Soustraction nom_var = expression1 - expression2 ;
* Multiplication nom_var = expression1 * expression2 ;
/ Division nom_var = expression1 /expression2 ;
% Modulo (reste de la division euclidienne) pour le
type entier seulement
nom_var = expression1 %expression2 ;
• Rq : si l’opération division est réalisée sur deux variables
de type entiers alors le résultat est un entier.
54

27
Les opérateurs d’affectation :
• Les opérateurs d’affectation :
– Ajouter à : +=
– Diminuer de : -=
– Multiplier par : *= Non commutatifs
– Diviser par : /=
– Modulo : %=

• En général, soit un opérateur de calcul op, exp1


et exp2 deux expressions, alors :
• exp1 op= exp2 ;  exp1= exp1 op exp2 ;
55

Exemples
i += 1 ;  i=i+1 ;

j -= 2 + i*k ;  j = j – ( 2 + i*k ) ;

p *= i ;  p=p*i ;

x /= 2 ;  x=x/2 ;

y %= k ;  y=y%k ;
56

28
Opérateurs d'incrémentation
• Les opérateurs d’incrémentation et de
décrémentation permettent d’augmenter ou de
diminuer d’une unité les valeurs des variables.
– Opérateur d’incrémentation : ++
• L’expression x = i++ affecte d'abord la valeur de i à x et
incrémente la variable i après.

• x = ++i incrémente la variable i d'abord et après affecte la


valeur incrémentée de i à x .

• Par conséquent: Les expressions suivantes sont équivalentes :

i++ ; i += 1 ; i=i+1 ;
57

Opérateurs et expressions.
– Opérateur de décrémentation : --

• L’expression x = i-- affecte d'abord la valeur de i à x et


décrémente la variable i après.

• x = --i décrémente la variable i d'abord et après affecte la


valeur de i décrémentée à x.

• Les expressions suivantes sont équivalentes :

i-- ; i -= 1 ; i = i – 1 ;

58

29
Opérateurs et expressions.
– Exemples :

• si i=1, alors :
– x = i++ donne x=1 et i=2.
– x = ++i donne x= 2 et i=2.

• si i=2, alors :
– x = i-- donne x=2 et i=1.
– x = --i donne x= 1 et i=1.

59

Opérateurs de comparaison
• Ces opérateurs sont utilisés dans les tests logiques entre deux ou
plusieurs variables. Le résultat de ces operateurs soit vrai soit faux.
• En C il n y a pas le type logique ou booléen. Tous ce qui est nul est faux
et tous ce qui est non nul est vrai

exp1 op exp2

où op peut être : < , <= , > , >=


• == Egalité logique

• != Différence logique

60

30
Opérateurs logiques
• Les expressions comportant des opérateurs
logiques donnent un résultat vrai (équivalent à
toute valeur différente de zéro) ou faux
(équivalent à l’entier 0). Les opérateurs logiques
sont :
• && et logique
• || ou logique
• ! négation logique

61

Exemples :

Expression Résultat

( 32<0 ) && ( 2.3>0 ) 0


( 4>3 ) || ( 2 > 22 ) 1
( ( 2*2 ) != (2+2) ) || ! (32 >12) 0
( 4>3 ) || ( 2 > 2.5 ) -1.3678

62

31
Commentaires :
• Un commentaire est un texte explicatif destinée au
lecteur du programme et qui n’a aucune incidence
sur sa compilation, c’est-à-dire n’est pas pris en
compte par le compilateur.
• Les commentaires sont formés de caractères
quelconques placés entre les symboles /* et */
• Peuvent être insérées à tout endroit du programme
et sur plusieurs lignes.
• Le symbole // permet d’insérer un commentaire
d'une seule ligne, à partir du // jusqu'à la fin de la
ligne.
• Les commentaires ne peuvent pas être imbriqués.
63

Séparateurs
• Les séparateurs (espaces, plusieurs espaces,
tabulations, saut de ligne) permettent
d’organiser la présentation du code source. Ils
sont utiles pour délimiter certaines parties du
code source. Par exemple, un espace est
nécessaire pour déclarer une variable de type
entier dont l'identificateur est a :

int a ;
64

32
Délimiteurs
Les délimiteurs servent à délimiter les éléments du
programme, ils sont au nombre de cinq :

Délimiteur Symbole Signification


Le point-virgule termine une déclaration de variable ou une
; instruction
La virgule sépare deux éléments dans une liste
,
Les parenthèses encadrent une liste de paramètres
( )
Les accolades encadrent un bloc ou une liste de valeurs
{ } d'initialisation
Les crochets encadrent une dimension ou l'indice d'un
[ ] tableau
65

Priorité des opérateurs


• Lorsque plusieurs opérateurs apparaissent dans une
même expression, l’évaluation suit l’ordre de priorité.
C’est-à-dire, l’opérateur de priorité plus élevé
précède celui de priorité plus faible.
• Dans le cas de priorités identiques, l’évaluation
s’effectue de gauche à droite.
• L'ordre de l'évaluation correspond en principe à celui
utilisé en mathématiques.

• Les parenthèses permettent de forcer une évaluation


de commencer par un opérateur de priorité plus
faible.
• Une expression est évaluée en respectant des règles
de priorité et d’associativité des opérateurs 66

33
Le tableau suivant donne la priorité de tous les opérateurs.
La priorité est décroissante de haut en bas dans le tableau.

Catégorie Opérateurs
Référence ( ) [ ] ->
Unaire ++ --
arithmétique * / %
arithmétique + -
relationnel < <= > >=
relationnel == !=
logique &&
logique ||
Affectation = += -= *= /= %=
séquentiel ,
67

Exemples
• * est plus prioritaire que + , ainsi 2 + 3 * 7
vaut 23 et non 35
• 13%3*4 vaut 4 et non 1

• Remarque: en cas de doute il vaut mieux utiliser


les parenthèses pour indiquer les opérations à
effectuer en priorité.
• Ex: (2 + 3) * 7 vaut 35
68

34
Fonctions mathématiques
• les fonctions mathématiques sont déclarées dans
la bibliothèque math.h

• Pour pouvoir utiliser les fonctions mathématiques,


il faut utiliser l’entête #include<math.h>

• Soient x et y deux variables de type double, voici


la liste des fonctions les plus courantes :
69

Fonctions mathématiques
• sin(x) : sinus de x
• cos(x) : cosinus de x
• tan(x) : tangente de x
• asin(x) : arc sinus de x
• acos(x) : arc cosinus de x
• atan(x) : arc tangente de x
• sinh(x) : sinus hyperbolique de x
• cosh(x) : cosinus hyperbolique de x
• tanh(x) : tangente hyperbolique de x
70

35
Fonctions mathématiques
• exp(x) : exponentielle de x
• log(x) : logarithme népérien de x (ln(x), x>0)
• log10(x) : logarithme à base 10 (log10(x), x>0)
• pow(x,y) : x exposant y (x^y ) (il se produit une
erreur si x=0 et y  0 , ou si x<0 et y n'est
pas un entier)
• sqrt(x) : racine carrée de x (x0)
• fabs(x) : valeur absolue de x (|x|)
• floor(x) : le plus grand entier inférieur ou égal à x
• ceil(x) : le plus petit entier supérieur ou égal à x
71

Conversions implicites
• Les types short et char sont systématiquement convertis en
int indépendamment des autres opérandes
• La conversion se fait en général selon une hiérarchie qui ne
change pas les valeurs :
int  long int  float  double  long double
• Exemple1 : n * x + p ( int n , p ; float x ; )
exécution prioritaire de n * x : conversion de n en float
exécution de l'addition : conversion de p en float
• Exemple2 : p1 * p2 + p3 * x
(char p1 ; short p2 , p3 ; float x ; )
p1 , p2 et p3 d'abord convertis en int
p3 converti en float avant multiplication ;
après l’expression est convertie en float 72

36
Opérateur de forçage de type ( cast )
Il est possible d’effectuer des conversions explicites ou de
forcer le type d’une expression c’est le casting ou le
cast
Syntaxe : (<type>) <expression>

Exemple : int n , p ;
(double) (n / p) ; convertit l’entier (n / p) en double

Remarque : la conversion se fait après calcul

(double) (n/p) != (double) n /p != (double) (n) / (double) (p)


73

conversions explicites (cast)

float n = 4.6 , p = 1.5 ;

(int) n / (int) p = 4 / 1 = 4

(int) n / p = 4 / 1.5 = 2.66

n / (int) p = 4.6 / 1 = 4.6

n / p = 4.6 / 1.5 = 3.06


74

37
Structures de contrôle

• Les structures de contrôle définissent la façon


avec laquelle les instructions sont effectuées. Elles
conditionnent l'exécution d'instructions à la
valeur d'une expression. On distingue :

– Les structures alternatives (tests)


– Les structures répétitives (boucles)
– Les structures de branchement

75

• Les structures alternatives (tests) : permettent


d’effectuer des choix càd de se comporter
différemment suivant les circonstances (valeur d'une
expression). En C, on dispose des instructions : if…else
et switch.
• Les structures répétitives (boucles) : permettent de
répéter plusieurs fois un ensemble donné
d’instructions. Cette famille dispose des instructions :
while, do…while et for.
• Les structures de branchement: permettent
d’interrompre le déroulement de la boucle ou bien de
sauter les instructions relatives à cette itération sans
pour autant sortir de la boucle ou bien de provoquer
un saut à un endroit du programme. En C, on dispose
des instructions : break , continue et goto
76

38
L’instruction if…else
• Syntaxe : if ( expression )
bloc-instruction1 ;
else
bloc-instruction2 ;
• bloc-instruction1 et 2 peut être une seule instruction terminée par
un point virgule ou une suite d’instructions délimitées par des
accolades { }
• expression est évaluée, si elle est vraie (valeur différente de 0),
alors bloc-instruction1 est exécuté. Si elle est fausse (valeur 0)
alors bloc-instruction2 est exécuté
• La partie else est facultative. S’il n’y a pas de traitement à réaliser
quand la condition est fausse, on utilisera simplement la forme :

if (expression) bloc-instruction1 ; 77

exemples
float a , b , max ;
if (a > b)
max = a ;
else
max = b ;

int a ;
if ( (a%2) == 0 )
printf(" %d est paire" , a);
else
printf(" a est impaire ",a);

78

39
Imbrication des instructions if
On peut imbriquer plusieurs instructions if…else .
Ceci peut conduire à des confusions, par exemple :
if (N>0)
if (A>B)
MAX=A;
else
MAX=B ;
(interprétation 1 : si N=0 alors MAX prend la valeur B)
(interprétation 2 : si N=0 MAX ne change pas)

En C un else est toujours associé au dernier if qui ne possède


pas une partie else (c’est l’interprétation 2 qui est juste)
79

pour éviter toute ambiguïté ou pour forcer une certaine interprétation


dans l’imbrication des if , il vaut mieux utiliser les accolades
if(a<=0)
{ if(a==0)
printf("a est nul ");
else
printf(" a est strictement négatif "); }
else
printf(" a est strictement positif " );

Pour forcer l’interprétation 1: if (N>0)


{ if (A>B)
MAX=A;
}
else MAX=B; 80

40
Exemples
#include<stdio.h>
main()
{
float a, b, c, d ;
printf (" entrer trois réels ");
scanf ("%f%f%f", &a,&b,&c) ;
if ( (b != 0) && (c != 0) )
{
d = a/(b* c);
printf("%f divisé par %f * %f = %f \n",a,b,c,d) ;
}
else
printf("Division impossible\n");
}
81

Calcul des racines d’un polynôme de degré deux à coefficients réels


#include<stdio.h>
#include<math.h>
main( ) {
float a,b,c,x1,x2 ,d ;
scanf("%f %f %f \n",&a,&b,&c);
d=b*b-4*a*c ;
if (d > 0) {
x1=(-b-sqrt(d))/(2*a) ; x2=(-b+sqrt(d))/(2*a) ;
printf("les solutions de l’equation sont %f et %f \n",x1,x2); }
else
if (d == 0)
{ x1=-b/(2*a) ;
printf("la solution de l’equation est double et est egale a %f \n",x1); }
else
printf("l’equation n’admet pas de solution reelle \n") ;
} 82

41
L’instruction d’aiguillage switch

• L’instruction switch permet de choisir des


instructions à exécuter selon la valeur d’une
variable qui doit être de type entier ou
caractère.
• L’instruction switch peut remplacer plusieurs
instructions imbriquées de if. Elle permet
d'effectuer une suite de tests d'égalité
consécutifs pour une valeur donnée et de
déclencher des instructions selon la valeur. Sa
syntaxe est :
83

switch (<expression>) {
case constante_1 : instruction_1 ;
case constante_2 : instruction_2 ;


case constante_N : instructions_N ;
default : instructions ;
}

84

42
• expression : est une expression de type entier ou
caractère.

• constante1, constante2…: expression constante de


type entier ou caractère.

• Instructions_i : chaque suite d’instructions sera


exécutée lorsque l'expression testée est égale la
constante qui la précède.

• default : le mot clé default précède la suite


d'instructions qui sera exécutée si l'expression n'est
pas égale à une de ces constantes.
85

exemple
#include <stdio.h>
main() {
int a;
printf("Donner l'un des chiffres 1, 2, 3 et 4 : ");
scanf("%d", &a);
switch(a) {
case 1 : printf("la valeur est un \n");
case 2 : printf("la valeur est deux \n");
case 3 : printf("la valeur est trois \n");
case 4 : printf("la valeur est quatre \n");
default : printf("le nombre est different de 1, 2, 3 et 4.\n");
}
getch();
}
86

43
• Le programme s’exécute de la manière suivante :
une fois le compilateur rencontre l’instruction
switch, il évalue la valeur de l’expression figurant
après le mot switch (la variable a dans notre
exemple), puis cherche dans le bloc de switch s’il
existe un cas (case) identique à cette valeur et
exécute les instructions se trouvant après ce cas
et celles qui le suivent.
• Dans le cas contraire (aucun des cas ne
correspond à la valeur de l’expression figurant
après le mot switch), le compilateur exécute
l’instruction se trouvant après "default".
87

• Ainsi, si on saisit 2 dans l’exemple précédent, le


programme donne :
la valeur est deux
la valeur est trois
la valeur est quatre
le nombre est différent de 1, 2, 3 et 4

• Si nous voudrions exécuter juste les instructions du


cas identique à l’expression figurant après le mot
switch, il faut ajouter l’instruction "break" ; qui
indique la sortie de la structure conditionnelle.
88

44
l’exemple précédent devient :
switch(a) {
case 1 : printf("la valeur est un \n");
break;
case 2 : printf("la valeur est deux \n");
break;
case 3 : printf("la valeur est trois \n");
break;
case 4 : printf("la valeur est quatre \n");
break;
default: printf("le nombre est different de 1, 2, 3 et 4.\n");
}
getch() ; }
En saisissant 2 dans cet exemple, le programme donne :
la valeur est deux 89

Instructions répétitives (itérations)


• Un traitement itératif ou une boucle effectue
plusieurs fois une suite d'instructions (ou un bloc
d'instructions) suivant que une condition est
vraie (il répète l’exécution de ces instructions) .

• Le langage C dispose de trois types de boucles :


– la boucle "while" ;
– la boucle "do … while" ;
– la boucle "for" ;
90

45
Instruction "while"

• Cette instruction (boucle) est réalisée autant


de fois que la condition est vérifiée. Sa syntaxe
est :
while (expression)
{
suite d'instructions ( ; )
} ;

91

• expression : est une condition de poursuite, elle est évaluée


avant la première itération, ainsi la suite d'instructions est
exécutée zéro ou plusieurs fois. si l’expression est vraie, c’est-
à-dire retourne une valeur non nulle, la suite d'instructions est
exécutée. si l’expression est fausse, l’exécution passe à
l’instruction qui suit la suite d’instructions.

• expression ou la condition (dite condition de contrôle de la


boucle) est évaluée à chaque itération. Les instructions (corps
de la boucle) sont exécutés tant que la condition est vraie, on
sort de la boucle dès que la condition devient fausse.

• dans la boucle while le test de continuation s'effectue avant


d'entamer le corps de boucle qui, de ce fait, peut ne jamais
s'exécuter
92

46
Exemples :

Le programme suivant affiche les entiers de 0 à 9 :


main()
{
int i = 0 ;
while (i<10)
{
printf(" %d \n ", i) ;
i++;
} ;
}
93

ce programme calcule la factoriel :


main() {
int i, n, fact;
fact = 1;
i=1;  compteur
printf("donner un entier\n");
scanf("%d",&n);
while (i<= n)
{
fact = fact * i;
i = i+1;
};
} 94

47
L’instruction do…while
• Permet de répéter l’exécution de certaines
instructions jusqu’à ce qu’une condition est
fausse. Contrairement à l’instruction " while",
l’instruction "do … while" teste sa condition
après exécution des instructions. Sa syntaxe est :

do
{
suite d'instructions ( ; )
}
while (expression) ;
95

• expression : est une condition de poursuite,


elle est évaluée après avoir exécuté la
première itération, ainsi la suite d'instructions
est exécutée au moins une fois.

• L’exécution de la suite d’instructions est


répétée tant que l’expression est vraie.

96

48
Le programme suivant permet d’afficher les
nombres de 0 à 9.

main() {
int i = 0;
do
{
printf("%d \n", i) ;
i++;
}
while (i<10) ;
}

97

• Contrôle de saisie d'une note saisie au clavier


jusqu’à ce que la valeur entrée soit valable

main()
{ int N;
do {
printf (" Entrez une note comprise entre 0 et 20 \n");
scanf("%d",&N);
} while ( (N < 0) || (N > 20) ) ;
....
.....
}
98

49
Instruction "for"
• Permet de répéter l’exécution d’une suite
d’instructions un nombre de fois défini. Cette
boucle peut être perçue comme étant une
variante de l’instruction "while".
• Sa syntaxe est :
for(expression1 ; expression2 ; expression3 )
{
Suite d’instructions ( ; )
} ;
99

• expression1 : est évaluée une seule fois au début de


l'exécution de la boucle. Elle effectue l’initialisation des
données ou de la boucle, sert à initialiser les variables de
la boucle.

• expression2 : donne la condition d'arrêt de la boucle. Si


elle est fausse, le programme sort de la boucle et passe
l’instruction qui suit. Cette expression2 est évaluée et
testée avant chaque passage dans la boucle. Elle constitue
le test de continuation de la boucle.

• expression3 : sert à incrémenter la variable de la boucle.


Elle est exécutée à chaque itération après la suite
d’instructions. Elle est évaluée après chaque passage. Elle
est utilisée pour réinitialiser les données de la boucle 100

50
• Exemples :
Le programme suivant affiche le carré des
nombres de 0 à 10

main()
{
int i;
for (i=0 ; i <=10 ; i ++ )
printf("Le carré de %d est %d \n ", i, i*i ) ;
}

101

• Le programme suivant calcule la somme des


nombres inférieurs à un nombre n :

#include <stdio.h>
main()
{
int i, n, s;
printf("Donner un entier : \n ");
scanf("%d", &n);
s=0;
for(i=1; i<=n; i++)
s = s+i;  les { } ?
printf(" la somme est %d ",s);
}
102

51
N. B. dans la syntaxe de la boucle for, expression1
et expression3 peuvent être une séquence. En
pratique, expression1 et expression3 contiennent
souvent plusieurs initialisations ou
réinitialisations, séparées par des virgules :

for( i=0, j=n ; i<10 ; i++ , j-- )


{
….
….
} 103

Calcul de x à la puissance n où x est un réel non nul


et n un entier positif ou nul
main ( )
{ float x , puiss ;
int n, i;
printf (" Entrez respectivement les valeurs de x et n \n");
scanf ("%f%d", &x, &n);
for (puiss =1, i=1; i<=n; i++)
puiss*=x;
printf ("%f à la puissance %d est égal à : %f", x , n, puiss );
}
104

52
calcul du factoriel d’un entier N.

#include<stdio.h>
main( ) {
int N , p , i ;
printf("Donner la valeur de N : ");
scanf("%d", &N);
for ( i=1 , p=1 ; i<=N ; i++ )
p=p*i;
printf("le factoriel de %d est %d \n",N , p ) ;
getch ();
}
105

Relation entre for et while


for (expr1 ; expr2 ; expr3) expr1;
{ while(expr2)
instructions ; {
} ; instructions ;
expr3;
} ;

expr1 ; expr1 ;
for ( ; expr2 ; expr3 ) for ( ; expr2 ; )
{ {
instructions ; instructions ;
} ; expr3 ;
} ;
106

53
Instructions de branchement non conditionnel

• Instruction "break" : Nous avons vu le rôle de


l’instruction "break" au sein de l’instruction de
branchement multiple (aiguillage) "switch".

• L’instruction "break" est généralement utilisée à


l’intérieur d’une boucle et permet d’interrompre le
déroulement de la boucle en passant à la première
instruction qui suit la boucle.

• En cas de boucles imbriquées, l’instruction "break"


fait sortir de la boucle la plus interne.
107

calcul de la puissance d’un réel a


#include<stdio.h>
main( ) {
float a , b=1 ;
int n,m,i ;
printf("Donner la valeur de a : ") ; scanf("%f", &a) ;
printf("Donner la valeur de n : ") ; scanf("%d", &n) ;

for (i=1 ; ; i++) {


b=b*a ;
if(i>=n)
break;
} ;
printf("%f a la puissance %d est egale a %f \n",a,n,b) ;
getch (); }
108

54
Exemple: Table de multiplication
#include<stdio.h>
main() {
int i,j;
for ( i=1 ; i<= 10 ; i++ )
{
for ( j=1 ; j<= 10 ; j++ )
{
printf ("%6d ", i * j) ;
};
printf ("\n \n") ;
}
getch() ; }
109

j 110

55
Juste la partie sous la diagonale
#include<stdio.h>
main(){
int i,j;
for ( i=1 ; i<= 10 ; i++ )
{
for ( j=1 ; j<= 10 ; j++ )
{
printf ("%6d ", i * j) ;
if(i==j) break ;
} ;
}
getch() ; }
111

j 112

56
Instruction "continue"

• Chaque fois où l’instruction "continue" est


rencontrée dans un cycle d’une boucle elle est
associée à une structure conditionnelle.

• L’instruction "continue" permet, dans le cas où


la structure conditionnelle est vérifiée, de
sauter les instructions relatives à cette
itération sans pour autant sortir de la boucle.
113

#include<stdio.h>
main( ) {
int i =2 ;
do {
i++;
if(i==5)
continue;
printf("\n la valeur de i est %d",i);
}
while (i<7);
getch (); }
La valeur de i est 3
La valeur de i est 4
La valeur de i est 6
La valeur de i est 7
114

57
Table de multiplication Sans la diagonale

for ( i=1 ; i<= 10 ; i++ )


for ( j=1 ; j<= 10 ; j++ )
{
if ( i == j )
{
printf (" ") ;
continue ;
} ;
printf ("%6d ", i * j) ;
} ;
115

116

58
Instruction "goto"
• Cette instruction provoque un saut à un
endroit du programme repéré par une
étiquette.
• Le programme continue alors à l’instruction
se trouvant juste après cette étiquette.

• Exemple : calcul de la limite de la série


géométrique 𝑆= Σ pow(a, i) somme sur 𝑖≥ 0
pour 0<a<1.
117

#include<stdio.h>
#include<math.h> /* bibliothèque des fonctions math */
main( ) {
float a , t , s=1;
Int i=1 ;
printf("Donner la valeur de a = "); scanf("%f",&a);
while (i<50) {
t=s;
s = s + pow(a,i) ;
i++;
if( (s-t) < 0.00001 )
goto terminer ;
} ;
terminer : ; /* l’étiquette */
printf("\n la limite de la serie est %f", s) ;
getch (); }
118

59
Les Tableaux

119

Les tableaux
Représentation des variables en mémoire

int float char double double float int int int

a x c x1 x2 y i j k

120

60
Les tableaux

• Ensemble de variables ayant un même rôle


• Si dans un programme on veux:
– Stocker les termes d’une suite.
– Stocker les composantes d’un vecteur.
– Stocker les coefficients d’une matrice.
– Stocker les coefficients d’un polynôme.
– Stocker les caractères d’une chaîne de caractères.
– Etc.

121

Les tableaux
• Un tableau est une variable composée (une
variable structurée, une structure de données)
d’un nombre fini de variables élémentaires de
même type.
• C’est donc une zone mémoire constituée de cases
de taille identique et dans lesquelles sont rangées
des données de même type. Ce sont des
représentations naturelles des vecteurs et des
matrices.
• Un tableau représente une suite d’éléments
comme une variable simple représente un élément
simple.
122

61
• Le nombre de cases d’un tableau est connu au
moment de sa déclaration et la zone mémoire
réservée à un tableau possède un début et une fin.
• Un tableau est composé d’éléments de même type
et chaque élément d’un tableau est référencé ( ou
récupéré) par un indice unique.
• Pour accéder à une case, on utilise un indice qui
repère le numéro de la case à laquelle on fait
référence.
• Le premier élément du tableau porte l’indice 0 et le
i-ème élément porte l’indice (i-1).
• Comme toute variable, une case d'un tableau doit
être initialisée avant d'être utilisée.
123

Tableau à une dimension


• Un tableau à une dimension est un tableau dont les
éléments sont des variables simples. Ils ne sont pas
eux même des tableaux, ils sont des variables
élémentaires. Les tableaux à une dimension sont les
représentations naturelles des vecteurs, ainsi la
syntaxe de sa déclaration est :

Type Nom_du_Tableau[Taille]

Type = le type des variables élémentaires du tableau
Nom_du_Tableau = le nom du tableau
Taille = la taille du tableau
124

62
Exemple :
int A[5] ;
cette déclaration permet de réserver un espace
mémoire pour cinq variables de type entier. Le
nom du tableau est A.
Pour accéder à une case du tableau, nous
utilisons un indice qui repère le numéro de la
case à laquelle on fait référence.
Le premier élément du tableau porte l’indice 0
et le i-ème élément porte l’indice (i-1).

A[0] A[1] A[2] A[3] A[4]


125

Représentation en mémoire
• Les éléments d’un tableau T de taille N sont
rangés dans la mémoire à des emplacements
contigus (l’un après l’autre).
Un indice
0 1 2 3 4 i N-1

• • • • • • •
T[0] T[1] T[2] T[3] T[i] T[N-1]

Un élément

Représentation d’un tableau en mémoire


126

63
Opérations sur un tableau
• Toutes les opérations réalisées sur une variable peuvent
être appliquées aux éléments des tableaux.
• En effet, nous pouvons par exemple opérer les
opérations suivantes sur les éléments du tableau A de
l’exemple ci-dessus :

• A[2]=0 ; /* affecte au 3ème élément du tableau A la


valeur 0 */
• b=A[4] ; /* affecte à l’entier b la valeur du 5ème
élément du tableau A */
• printf (" %d ",A[1]) ; /* affiche le 2ème élément du
tableau A */
• scanf (" %d ",&A[0]) ; /* saisit au clavier le 1ier
élément du tableau A */
127

• Soit X un tableau de N éléments d’un certain type T


(entier par exemple):
– X[i] désigne l’élément d’indice i dans le tableau X.
– Les indices du tableau X vont de 0 à N-1.
– Par exemple : int X[4]; déclare un tableau de 4
entiers. Son premier élément est X[0] et son dernier
élément est X[3].
– X[i] est considéré comme une variable simple de type
T. On peut lui appliquer toutes les opérations
possibles sur le type T.

– Exemples : On peut écrire :


X[1] = 3; /* affectation de 3 à X[1] */ 128

64
scanf("%d", &X[2]) ; /* je lis X[2] */

printf("%d", X[0]) ; /* j’affiche X[0] */

X[3]++ ; /* j’incrémente la valeur de X[3] */

X[0]-- ; /* je décrémente la valeur de X[0] */

A = sqrt(X[2]) ; /* je calcule la racine carrée de


X[2] */
129

• Les indices dans un tableau A peuvent subir des


opérations arithmétiques des entiers. En effet, si i et j
sont des entiers alors les instructions suivantes sont
correctes :
A[i+1] , A[5*i] , A[2*i+j] ;
le résultat doit être inferieur à la taille du tableau A
• Si A et B sont deux tableaux, alors l’instruction :
B=A ;
n’est pas correcte même dans le cas où les tableaux A et
B sont de même type et de même taille.

• En C, le nom d'un tableau est le représentant de


l'adresse du premier élément du tableau : pour un
tableau T : T=&T[0] , ici une égalité
130

65
Initialisation des tableaux à une dimension

• La déclaration d’un tableau réserve de la place mémoire


proportionnelle à sa taille mais n’initialise pas les éléments
du tableau. C’est pourquoi il faut procéder à l’initialisation
du tableau. Nous distinguons trois types d’initialisation:

• Initialisation à la déclaration : Nous affectons les valeurs


au tableau au moment de sa déclaration.

• Exemple : int A[5]={2,4,6,8,10} ;

Suite à cette déclaration, nous avons A[0]=2 , A[1]=4 ,


A[2]=6 , A[3]=8 et A[4]= 10.
131

• float B[4] = {-1.5, 3.3, 7.2, -2} ;


• Il est possible de ne pas indiquer la dimension
explicitement lors de l'initialisation. Dans ce
cas elle est égale au nombre de valeurs de la
liste.
• Ex: int T[ ] = {1, 2, 3, 4, 5} ;
tableau de 5 éléments
• int B[ ] = {10, 20, 40, 40, 50} ;
signifie que B est un tableau de 5 éléments
qui sont : 10 , 20 , 30 , 40 et 50 .
132

66
• On ne peut pas affecter un tableau à un autre.
L’affectation globale de tableau est interdite en C.

• Cela signifie que si :


int A[5] , B[5] ;
il ne faut pas écrire : B=A ;

• Pour recopier un tableau de taille N dans un autre


de même taille, il faut l’effectuer élément par
élément (par une boucle for par exemple) :

for(i = 0; i < N; i++)


B[i] = A[i] ; 133

Soit les déclarations suivantes:


• int A[5] = {2 , 4 , 6 } ;
initialise le tableau A à {2,4,6,0,0}, c.à.d.
A[0]=2 , A[1]=4 , A[2]=6 , A[3]=0 et A[4]=0 .

• int A[5]={ } ;
initialise le tableau A à {0,0,0,0,0}, c.à.d.
A[0]=0 , A[1]=0 , A[2]=0 , A[3]=0 et A[4]=0 .

• int A[5] ; n’initialise pas le tableau A.

• int T[3] = {1, 2, 3, 4, 5} ; est une erreur


134

67
Initialisation par affectation
• Nous affectons les valeurs au tableau un par
un si la taille est petite ( < 10 )

• Exemple :
int A[5];
A[0]=2;
A[1]=4;
A[2]=6;
A[3]=8;
A[4]=10;
135

• Dans ce cas, étant donné que la taille du


tableau est connue, il est plus judicieux
d’utiliser une boucle pour initialiser le tableau.

• Exemple :

int A[25];
for (i=0 ; i<25 ; i++)
A[i]=2*(i+1) ;

136

68
Lecture d’un tableau
• Les éléments du tableau de taille N sont lus un
par un par une boucle qui répétera l’instruction
de la lecture N fois

• Exemple : Saisie des éléments d’un tableau T


d’entiers de taille N :
for( i=0 ; i<N ; i++ )
{ printf ("Entrez l'élément T[%d] \n ", i ) ;
scanf(" %d" , &T[i]);
} ;
137

Affichage des éléments d’un tableau T

• Affichage des éléments d’un tableau T de taille N :

for(i=0 ; i<N ; i++ )


printf (" %d \t ",T[i] ) ;

• on ne peut pas saisir, afficher ou traiter un tableau


en entier, ainsi on ne peut pas écrire printf(" %d", T )
ou scanf(" %d",&T)
• On traite les tableaux élément par élément de façon
répétitive en utilisant des boucles
138

69
Exemples
#include <stdio.h>
main() {
int i;
char A[7]={'b','o','n','j','o','u','r'} ;
/* Initialisation à la
déclaration */
printf("Affichage du tableau A : \n") ;
for (i=0 ; i<7 ;i++ )
printf("%c",A[i]) ;

Après exécution du programme nous obtenons les


résultats suivants :
Affichage du tableau A :
bonjour
139

float B[3] ; /* Initialisation par affectation */


for (i=0;i<3;i++)
B[i]=2*(i+1)-1 ;
printf("\n \n Affichage du tableau B : \n") ;
for (i=0;i<3;i++)
printf(" B[%d] = %f \t", i , B[i]) ;

Affichage du tableau B :
B[0] = 1.000000
B[1] = 3.000000
B[2] = 5.000000
140

70
int C[5] ; /* Initialisation par lecture */
printf("\n \n Donner les valeurs du tableau C \n") ;
for (i=0 ; i<5 ;i++ ) {
printf("Donner la valeur de C[%d] : \n",i);
scanf( "%d" , &C[i]);
} ;
printf("Affichage du tableau C : \n");
for (i=0;i<5;i++)
printf(" C[%d] = %d \t ",i , C[i] ) ;
getch();
}
141

Donner les valeurs du tableau C


Donner la valeur de C[0] :
67
Donner la valeur de C[1] :
-33
Donner la valeur de C[2] :
7
Donner la valeur de C[3] :
80
Donner la valeur de C[4] :
-91
Affichage du tableau C :
C[0] = 67 C[1] = -33 C[2] = 7 C[3] = 80 C[4] = -91
142

71
Calcul du nombre d'étudiants ayant une note supérieure à 10 :

main ( )
{ float notes[30] ;
int nbre , i ;
for(i=0;i<30;i++)
{ printf ("Entrez notes[%d] \n ",i) ;
scanf(" %f" , &notes[i]); } ;
nbre=0;
for (i=0; i<30; i++)
if (notes[i]>10)
nbre+=1 ;

printf (" le nombre de notes > à 10 est égal à : %d", nbre); 143
}

Taille d’un tableau


• La taille d’un tableau est le nombre de ses éléments.
• Elle doit être de type int (ou d’un type compatible
au type int ).
• Elle est définie soit par :
– Une valeur numérique
– Une constante définie par le macro #define
– Une constante symbolique définie par le mot clé
const
– Une expression symbolique
– Variable dont la valeur est connue avant la
déclaration du tableau et ne changera pas après
144

72
Exemple
• Quelle est la taille des tableaux suivants :
#define N 10 15, constante numérique
const int K = 5;
int a[15]; 10, constante #define N
int b[N];
char c[K]; 5, constante symbolique
float d[2 * N];
double e[N + K + 1]; 20, expression symbolique

short P ; scanf(" %d" ,&P); 16, expression symbolique


double T[P] ;
P, variable du programme
145

#define N 30
main ( )
{ float notes[N] ; /* avant été 30 */
int nbre , i ;
for(i=0;i < N ; i++)
{ printf ("Entrez notes[%d] \n ",i);
scanf(" %f" , &notes[i]);
} ;
nbre=0;
for (i=0; i<N; i++)
if (notes[i]>10) nbre+=1;
printf (" le nombre de notes > à 10 est égal à : %d", nbre);
}
146

73
main ( ) {
int N ;
scanf(" %d", &N) ;
float notes[N] ;
int nbre , i ;
for(i=0;i<N ; i++)
{ printf ("Entrez notes[%d] \n ",i); scanf("%f", &notes[i]) ; };
nbre=0;
for (i=0; i<N; i++)
if (notes[i]>10) nbre+=1;
printf (" le nombre de notes > à 10 est égal à : %d", nbre ) ;

N= nbre ; /*une erreur à ne pas faire */


} 147

la somme des éléments d’un tableau


#include <stdio.h>
main ( ) {
int N , i;
scanf("%d" , &N);
float T[N] , Som=0 ;
for(i=0 ; i<N ;i++ )
scanf("%f" , &Ti]);
for (i=0; i<N ; i++)
Som+=T[i] ; // ou Som = Som+T[i] ;
printf (" la somme des éléments du tableau = % f", Som); }
148

74
Tableau à plusieurs dimensions
• On peut définir un tableau à n dimensions de la
façon suivante:
Type Nom_du_Tableau[D1][D2]…[Dn] ;
où Di est le nombre d’éléments dans la dimension i
• Exemple : pour stocker les notes de 20 étudiants en
5 modules dans deux examens, on peut déclarer un
tableau :
float notes[20][5][2] ;
notes[i][j][k] est la note de l’examen k dans
le module j pour l’étudiant i
149

Tableaux à deux dimensions (Matrices)


• Un tableau à deux dimensions est un tableau dont les
éléments sont des tableaux à une dimension.

• Un tableau à deux dimensions A[n][m] est à interpréter


comme un tableau unidimensionnel de dimension n dont
chaque composante A[i] est un tableau unidimensionnel
de dimension m.

• Ainsi, on définit par récurrence un tableau de dimension k


(k ≥ 2) comme étant un tableau dont les éléments sont des
tableaux de dimension (k-1). Les éléments d’un tableau de
dimensions k sont identifiés à l’aide de k indices.
150

75
Tableaux à deux dimensions (Matrices)

• Un tableau à deux dimensions A[n][m] contient


n* m composantes. Ainsi lors de la déclaration,
on lui réserve un espace mémoire dont la taille
(en octets) est égal à : n*m* taille du type

• Les tableaux à deux dimensions sont les


représentations naturelles des matrices et sont
déclarés ainsi :

151

Type Nom_du_Tableau[Taille1][Taille2]

Type = le type des variables élémentaires du tableau
Nom_du_Tableau = le nom du tableau
Taille1= le nombre de lignes
Taille2 = le nombre de colonnes

int A[2][3] ;
On peut représenter le tableau A de la manière suivante :
A[0][0] A[0][1] A[0][2]
A[1][0] A[1][1] A[1][2]
152

76
Exemple : float A[4][3] ;
on réserve un espace mémoire pour 4×3 variables réel. Chaque
élément du tableau (relatif au premier indice) est un tableau de
taille 3 (ce sont les lignes du tableau). Les éléments du tableau A
sont notés A[i][j] où 1 ≤ i ≤ 4 et 1 ≤ j ≤ 3. Ainsi, les éléments du
tableau A sont ordonnés comme suit :
A[0][0]
A[0][1]
A[0][2]
A[1][0]
A[1][1]
A[1][2]
A[2][0]
A[2][1]
A[2][2]
A[3][0]
A[3][1]
A[3][2] 153

Initialisation d’une Matrice


• Comme pour les tableaux unidimensionnels, les tableaux à
deux dimensions peuvent être initialisés selon trois types
d’initialisation.

• Initialisation à la déclaration se fait en indiquant la liste des


valeurs respectives entre accolades ligne par ligne

float A[3][4] = { {-1.5, 2.1, 3.4, 0} , {8.3, 7.5,1, 2.7 } , {3.1, 0, 4,-2} } ;

A[0][0]=-1.5 A[0][1]=2.1 A[0][2]=3.4 A[0][3]=0


A[1][0]=8.3 A[1][1]=7.5 A[1][2]=1 A[1][3]=2.7
A[2][0]=3.1 A[2][1]=0 A[2][2]=4 A[2][3]=-2
154

77
Nous affectons les valeurs au tableau au moment de sa
déclaration.
Exemple :
int A[2][3]={ {2,4,6} ,{-8,10,-4} } ;
Suite à cette déclaration, nous avons
A[0][0]=2 A[0][1]=4 A[0][2]=6
A[1][0]=-8 A[1][1]= 10 A[1][2]=-4

Remarque : la déclaration
int A[2][3]={ {2,4,6} , {-8,10,-4} };
est équivalente à
int A[2][3]={2,4,6,-8,10,-4} ; 155

Initialisation par affectation


Nous affectons dans le programme les valeurs au tableau
un par un.
Exemple :
int A[2][3];
A[0][0]=2;
A[0][1]=4;
A[0][2]=6;
A[1][0]=-8;
A[1][1]= 10;
A[1][2]=-4;

Si le nombre d’éléments est petit , sinon il faut faire une


boucle pour chaque dimension
156

78
Initialisation par affectation
De manière générale : Si A est un tableau de taille
N×M et on veux initialiser chaque élément par la
formule A[i][j] = 2i – j alors nous pouvons le faire de
la manière suivante :
int A[N][M] ;
int i , j ;
for (i=0 ; i<N ; i++)
for (j=0 ; j<M ;j++)
A[i][j]=2*i – j ;
Bien sûr N et M ont des valeurs avant la déclaration
du tableau 157

Initialisation par lecture


Les éléments d’une matrice sont lus un par un et
ligne par ligne. Soit A est un tableau de taille N×M

int A[N][M];
int i , j ;
printf(" Donner les valeur du tableau ");
for (i=0 ; i<N ; i++)
for (j=0 ; j<M ; j++)
scanf( "%d", &A[i][j]);
158

79
Affichage des éléments d’une matrice

Affichage des éléments d’un tableau de taille N×M


d’entiers A[N][M] :

for(i=0;i<N;i++)
{
for(j=0;j<M;j++)
printf (" %d \t", A[i][j] ) ;
printf(" \n ") ;
};
159

Représentation d’un tableau à deux


dimensions en mémoire
• Les éléments d’un tableau sont stockées en mémoire
à des emplacements contigus ligne après ligne

• Rappel : En C, le nom d'un tableau est le représentant


de l'adresse du premier élément du tableau pour un
tableau T : T=&T[0] (une égalité)

• Comme pour les tableaux unidimensionnels, le nom


d'un tableau A à 2 dimensions est le représentant de
l'adresse du premier élément: A = &A[0][0] (une
égalité) 160

80
Exemples
#include<stdio.h>
#define N 5
main() {
int T[N][N] , i , j ;
for(i = 0; i  N; i++)
for(j = 0; j  N; j++)
scanf("%d", &T[i][j]) ;
for(i = 0; i  N; i++)
{ for(j = 0; j  N; j++)
printf("%d\t", T[i][j]);
printf(" \n ") ;
};
}
161

Exemple : calcul du produit de deux


matrices
Soient 𝐴=(𝐴𝑖𝑗) 1 ≤ 𝑖 ≤ 𝑛 1≤𝑗≤𝑚
et 𝐵=(𝐵𝑖𝑗) 1 ≤ 𝑖≤ 𝑚 1 ≤ 𝑗 ≤ 𝑘
deux matrices et 𝐶=(𝐶𝑖𝑗) 1 ≤ 𝑖 ≤ 𝑛 1 ≤ 𝑗 ≤ 𝑘
leur matrice produit.

Le programme suivant permet de calculer la matrice


C à partir des matrices A et B. On rappelle que pour
1 ≤ 𝑖 ≤ 𝑛 𝑒𝑡 1 ≤ 𝑗 ≤ 𝑘 on a :

162

81
#include <stdio.h>
#define n 3
#define m 2
#define k 4
main() {
int A[n][m] = {{3,-5},{0,4},{1,-1}} , B[m][k] = {{1,-2,5,0},{0,3,-6,4}} ;
int C[n][k] , i , j , p , S ;

for (i=0 ; i < n ; i++ )


{
for (j=0 ; j < k ; j++)
{
S=0;
for (p=0 ; p < m ; p++)
S = S + A[i][p]*B[p][j] ;
C[i][j] = S ;
} ;
} ; 163

printf("Affichage du tableau A : \n");


for (i=0;i<n;i++) {
for (j=0;j<m;j++)
printf("%d \t",A[i][j]);
printf("\n"); } ;
printf("\n\n Affichage du tableau B : \n");
for (i=0;i<m;i++) {
for (j=0;j<k;j++)
printf("%d \t",B[i][j]);
printf("\n"); } ;
printf("\n\n Affichage du tableau C : \n");
for (i=0;i<n;i++) {
for (j=0;j<k;j++)
printf("%d \t",C[i][j]) ;
printf("\n"); } ;
getch(); }
164

82
Les Pointeurs

165

Taille d’une variable

• Définition: Pour chaque variable, C réserve une


zone mémoire nécessaire pour contenir sa
valeur.
• La taille de cette zone mémoire dépend du type
de la variable (int, char, float, double, etc).
• L’opérateur sizeof permet de calculer la taille en
octets d’un type donné ou d’une variable de ce
type.

166

83
Exemple
Un programme qui calcule la taille en octets des types de
base du langage C.
#include<stdio.h>
main() {
printf(" un char occupe %d octets \n ", sizeof(char));
printf(" un int occupe %d octets \n ", sizeof(int));
printf(" un float occupe %d octets \n ", sizeof(float));
printf(" un double occupe %d octets \n ", sizeof(double));
}

Le programme affichera:

1 4 4 8
167

Exemple
• Calculer la taille en octets de chacune des variables
suivantes :

• int a, b[100];
• char c[15], d[10];
• float x[10], z[20];
• La taille d’un tableau de N éléments et de type T est :
N  sizeof(T).
• La taille du tableau b sera alors
100  sizeof(a).
168

84
#include<stdio.h>
main() {
int a, b[100];
char c[15], d[10];
float x[10], z[20];
printf(" Taille de a : %d octets \n ", sizeof(a) );
printf(" Taille de b : %d octets \n ", sizeof(b) );
printf(" Taille de c : %d octets \n ", sizeof(c) );
printf(" Taille de d : %d octets \n ", sizeof(d) );
printf(" Taille de x : %d octets \n ", sizeof(x) );
printf(" Taille de z : %d octets \n ", sizeof(z) );
}
Le programme affichera:
4 400 15 10 40 80
169

Adresse d’une variable


• L’adresse d’une variable est l’adresse du premier octet de
la zone mémoire réservée par le système d’exploitation
pour contenir la valeur de cette variable.

• L’adresse d’une variable est calculée en utilisant


l’opérateur d’adresse noté par &.

• Ainsi, si X désigne le nom d’une variable, alors &X est


l’adresse de la variable X.
• L’adresse d’une variable est un nombre entier positif.
• Pour afficher l’adresse d’une variable, il faut utiliser Le
format d’affichage des entiers
170

85
Exemple
Un programme qui déclare une variable a de type int et une
variable b de type float, puis affiche l’adresse de a et celle de b
#include<stdio.h>
main() {
int a ;
float b ;
printf(" Adresse de a : %d \n ", &a) ;
printf(" Adresse de b : %d \n ", &b ) ;
}
Le programme affichera:

Adresse de a : 2686792
Adresse de b : 3546124
171

Modes d’adressage
• Avant de parler de pointeurs, nous allons brièvement
passer en revue les deux modes d'adressage
principaux :

• Adressage direct : Dans la programmation, nous


utilisons des variables pour stocker des informations.
La valeur d'une variable se trouve à un endroit
spécifique dans la mémoire interne de l'ordinateur. Le
nom de la variable nous permet alors d'accéder
directement à cette valeur.

• Adressage direct = Accès au contenu d'une variable


par le nom de la variable.
172

86
Adressage direct
• int x ;
• x=5;
• Adressage direct = Accès au contenu d'une variable
par le nom de la variable.

Ad Ad+1 Ad+2 …

x 173

Adressage indirect
• Adressage indirect : Si nous ne voulons pas ou
nous ne pouvons pas utiliser le nom d'une
variable X, nous pouvons copier l'adresse de cette
variable dans une variable spéciale P.
• Ensuite, nous pouvons retrouver l'information de
la variable X en passant par la variable P.
• Adressage indirect = Accès au contenu d'une
variable X, en passant par une variable qui
contient l'adresse de la variable X.
174

87
Adressage indirect
• Soit X une variable contenant la valeur 5 et P
une variable qui contient l'adresse de X.
• En mémoire, X et P peuvent se présenter
comme suit:

Ad
Ad 5

P X
175

• La plupart des langages de programmation


offrent la possibilité d'accéder aux données
dans la mémoire de l'ordinateur à l'aide de
variables spéciales, c.-à-d. à l'aide de variables
auxquelles on peut attribuer les adresses
d'autres variables. Se sont les pointeurs

• On peut donc définir un pointeur comme une


variable spéciale qui peut contenir l'adresse
d'une autre variable.
176

88
Notion de pointeur
• Un pointeur est une variable dont la valeur est l’adresse
d’une autre variable. Si un pointeur p contient l’adresse
d’une variable a, on dit que p pointe vers a. Un pointeur
est identifié dans un programme par un nom et un type.

 Valeur a
p

Schéma qui illustre un pointeur p pointant vers une


variable a 177

• Une variable est caractérisée dans le langage C par


son nom et son adresse. Une fois la variable
déclarée, un espace mémoire lui est alloué. La
taille de cet espace dépond du type de la variable
que l'on a déclarée.
• Cet espace mémoire représente l’adresse de la
variable et ne change pas tant que la variable est
dans l’espace de validité du programme. Pour
modifier la valeur d’une variable, il est possible
d’écrire directement la nouvelle valeur dans son
espace mémoire.
• Cet espace mémoire sera libéré une fois la variable
n’est plus dans l’espace de validité du programme.
178

89
• Le langage C permet de manipuler les adresses
par le biais de variables appelées pointeurs.

• Un pointeur est une variable contenant


l'adresse d'une autre variable d'un type donné

• Un pointeur doit avoir un type qui est celui des


variables sur lesquelles il peut pointer. Ainsi,
un pointeur de type entier (int) ne peut pointer
que sur des variables de type entier.
179

Intérêts des pointeurs


• Les pointeurs présentent de nombreux avantages :

• Ils sont indispensables pour permettre le passage


par référence pour les paramètres des fonctions

• La notion de pointeur est une technique de


programmation très puissante, permettant de
définir des structures dynamiques, c'est-à-dire qui
évoluent au cours du temps
180

90
Intérêts des pointeurs
• Ils permettent de créer des structures de données
(listes et arbres) dont le nombre d’éléments peut
évoluer dynamiquement. Ces structures sont très
utilisées en programmation.

• Par opposition aux tableaux par exemple qui sont


des structures de données statiques, dont la taille
et l’adresse sont figées à la définition.

• Ils permettent d'écrire des programmes plus


compacts et efficaces
181

• Le nom d’une variable permet d'accéder directement à sa


valeur (adressage direct).
• Un pointeur qui contient l'adresse de la variable, permet
d'accéder indirectement à sa valeur (adressage indirect).
• Le nom d'une variable est lié à la même adresse, alors
qu’un pointeur peut pointer sur différentes adresses
• Les pointeurs et les noms de variables ont le même rôle :
Ils donnent accès à un emplacement dans la mémoire
interne de l'ordinateur. Il faut quand même bien faire la
différence :
– Un pointeur est une variable qui peut 'pointer' sur différentes
adresses.
– Le nom d'une variable reste toujours lié à la même adresse.
182

91
Déclaration d’un pointeur
• Le type d’un pointeur dépend du type de la variable pointée.
Ceci est important pour connaître la taille de la valeur
pointée. En C, chaque pointeur est lié à un type de donnée
(même si la valeur d’un pointeur, qui est une adresse, est
toujours un entier).

• La syntaxe de déclaration d’un pointeur est la suivante :


Type * Nom_pointeur ;

• Type : désigne le type de la variable pointée par le pointeur.


• Nom_pointeur : est le nom du pointeur.
• Le symbole * est l’opérateur qui indiquera au compilateur
que c’est un pointeur

183

Exemple
• On déclare un pointeur vers un entier, un
pointeur vers un char, un pointeur vers un float,
et un pointeur vers un double.

• Solution :
int *a ;
char *b ;
float *x ;
double *y ;
184

92
• Dans la déclaration d’un pointeur, le symbole * est associé
au nom de la variable pointeur et non pas au type. Ainsi, la
déclaration :
int *a , b ;
• Ne déclare pas deux pointeurs a et b, mais un pointeur a vers
un int et une variable simple b de type int.
• Pour déclarer deux pointeurs a et b dans la même ligne, il
faut écrire :
int *a , *b ;
• la valeur d’un pointeur donne l’adresse du premier octet
parmi les n octets où la variable est stockée
• Lors du travail avec des pointeurs, nous utilisons :
– un opérateur 'adresse de': & pour obtenir l'adresse d'une variable
– un opérateur 'contenu de': * pour accéder au contenu d'une adresse
185

Initialisation d’un pointeur


• Pour initialiser un pointeur de type T, il faut lui
affecter l’adresse d’une variable de même type T.
• On déclarer une variable a de type int et on
l’initialise par la valeur 7 et une autre b par 3,
puis on déclare un pointeur de type int et on
l’initialise par l’adresse de la variable déclarée a.
Puis on le redirection vers la variable b.
• Solution :
int a = 7 , b=3;
int * p = &a ;
p=&b; 186

93
p=&a;

3 Ad1 7
b p a
Ad2 Ad1

p=&b;

3 Ad2 7
b p a 187

L’accès à la variable pointée


• L’opérateur * permet d’accéder à la valeur de la
variable pointée par un pointeur donné.
Considérons un pointeur p qui pointe vers une
variable w de type T.
L’expression *p est équivalente à w.

• Donc, on peut manipuler la variable w soit


directement par son nom ou soit indirectement
par *p. En fait, *p est considérée comme une
variable de type T et tout ce qui est applicable sur
w, on peut l’appliquer sur *p.
188

94
Exemple
• Si un pointeur p pointe sur une variable a, alors
toutes les opérations réalisées sur la variable a
(accès direct )peuvent être réalisées sur *p (accès
indirect) .

• int a=3 , b , *p ;
• p = &a ;
• b=a–1 /* est équivalente à b = *p - 1 */
• a += 2 /* est équivalente à *p += 2 */
• ++a /* est équivalente à ++*p */
• a++ /* est équivalente à (*p)++ */
189

#include <stdio.h>
main() {
int n=2 , m= -4 , k ;
int *p , *q ;
p=&n ;
q=&m ;
k=*p**q;
printf(" %d \t %d \t %d",*p,*q,k); /* affiche 2 -4 -8 */
(*p)-- ;
(*q)++ ;
printf(" %d \t %d \t %d",*p,*q,n); /* affiche 1 -3 1 */
(*p)+=4 ;
(*q)-=2 ;
printf(" %d \t %d \t %d",*p,*q,m); } /* affiche 5 -5 -5 */
190

95
• int *p;
on déclare un pointeur vers une variable de type int
• int i=10, j=30;
deux variables de type int
• p=&i;
on met dans p, l’adresse de i (p pointe sur i)
• printf("*p = %d \n",*p);
On affiche : *p = 10
• *p=20;
On met la valeur 20 dans la case mémoire pointée par p (i
vaut 20 après cette instruction)
• p=&j;
p pointe sur j
• i=*p;
On affecte le contenu de p à i (i vaut 30 après cette
instruction)
191

float a, *p;
p=&a;
printf("Entrez une valeur : \n");
scanf("%f ",p);
// supposons qu’on saisit la valeur 1.5
printf("Adresse de a= %d, contenu de a= %f \n" , p,*p);
*p+=0.5;
printf ("a= %f \n" , a);
// affiche a=2.0

192

96
Remarque : si un pointeur P pointe sur une
variable X, alors *P peut être utilisé partout où
on peut écrire X

• X+=2 équivaut à (*P)+=2


• ++X équivaut à ++*P
• X++ équivaut à (*P)++ // != *P++

les parenthèses ici sont obligatoires car


l’associativité des opérateurs unaires * et ++ est
de droite à gauche
193

Initialisation d’un pointeur


Toute utilisation d’un pointeur devra être précédée par une
initialisation. Pour initialiser un pointeur, nous utilisons
l’opérateur unaire "&" qui fournit l’adresse en mémoire
d’une variable donnée.
A la déclaration d’un pointeur p, on ne sait pas sur quel
zone mémoire il pointe. Ces instructions peuvent générer
des problèmes :
int *p;
*p = 10; // *p n’a pas se sens, elle n’existe pas

provoque un problème mémoire car le pointeur p n’a pas


été initialisé. Toute utilisation d’un pointeur doit être
précédée par une initialisation.
194

97
Aussi l’écriture suivante provoque un problème
mémoire
int *p ;
p=200 ; // ou p= 231564 ;

On ne peut pas le faire, car on ne sait pas si l’adresse


200 (ou l’adresse 231564) est occupée par une autre
variable du programme ou d’un autre programme,
voir un programme système.

Il faut laisser le compilateur choisir les adresses


convenable, c’est allocation dynamique (voir fin du
chapitre)
195

On peut initialiser un pointeur en lui affectant :


• l’adresse d’une variable
Ex: int a , *p1 ; p1=&a ;
• un autre pointeur déjà initialisé
Ex: int *p2 ; p2 = p1 ;
• la valeur 0 désignée par le symbole NULL, défini dans
<stddef.h> et <stdio.h>

Ex: int *p ; p=NULL;

on dit que p pointe ‘nulle part’: aucune adresse mémoire


ne lui est associé. Un pointeur peut aussi être initialisé
par une allocation dynamique (voir fin du chapitre)
196

98
Exemple :
int *p , b=3 ; p=&b ;
printf (" %d " , b) ; /* affiche 3 */
printf (" %d " , *p) ; /* affiche 3 */
*p est la valeur se trouvant dans l’adresse de b.

Exemple :
int *p , a=5 , b=3 ;
p=&a ; /* p pointe sur la variable a */
b=*p ; /* affecte à la variable b la valeur pointée par p */
*p = 1 ; /* affecte la valeur 1 à la variable pointée par p */
printf (" %d " , b) ; /* affiche 5 */
printf (" %d " , *p) ; /* affiche 1 */
printf (" %d " , a) ; /* affiche 1 */
197

Affectation de pointeurs

• L’affectation d’un pointeur à un autre est possible


si les deux pointeurs sont de même type.

• Si p et q sont deux pointeurs de type T, alors


l’expression p = q; permet d’affecter le pointeur q
au pointeur p.

• Cela a pour effet de faire pointer p et q vers la


même variable (la même adresse).
198

99
Exemple
• On déclare une variable a de type int et on
l’initialise par la valeur 7, puis on déclare deux
pointeurs de type int qui vont pointer vers la
variable a déclarée.

• Solution :
int a = 7 ;
int * p = &a , * q ;
q =p ; 199

Opérations sur les pointeurs


• La valeur d’un pointeur étant un entier, certaines
opérations arithmétiques sont possibles : ajouter ou
soustraire un entier à un pointeur ou faire la
différence de deux pointeurs (et pas plus)

• Addition d’un entier à un pointeur:


• Si p est un pointeur vers une variable a de type T et
k un entier, alors l’expression
p + k ou k + p
est un pointeur vers la k ème variable de type T
après la variable a.
200

100
p  a

p+3 

201

Exemple
• Soit a une variable de type int et p est un pointeur vers a.
Sachant que le type int est codé sur 4 octets et que
l’adresse de la variable a est 1030, quel est le contenu du
pointeur p+3 ?

• Solution : Le pointeur p pointe vers a, donc le contenu


de p est l’adresse de p, c’est-à-dire 1030.
• Le pointeur p + 3 pointe vers l’entier qui se trouve 3 fois
après celui qui contient la variable a.
• Donc le contenu du pointeur p + 3 est

1030 + 3 * 4 = 1042.
202

101
#include <stdio.h>
main() {
double i = 3;
double *p, *q, *r;
p = &i;
q = p + 1;
r = p+ 3 ;
printf("p = %d \t q = %d \t r = %d \n",p,q,r);
getch() ; }

il affiche
p=2686784 q=2686792 r=2686808 203

Soustraction d’un entier à un pointeur

• Si p est un pointeur vers une variable a de


type T et k un entier, alors l’expression
p-k
est un pointeur vers la k ème variable de
type T avant la variable a.

204

102
p-5 

p  a

205

Exemple
• Soit a une variable de type int et p est un pointeur vers
a. Sachant que le type int est codé sur 4 octets et que
l’adresse de la variable a est 1030, quel est le contenu
du pointeur p-5 ?

• Solution :Le pointeur p pointe vers a, donc le contenu de


p est l’adresse de p, c’est-à-dire 1030.

• Le pointeur p - 5 pointe vers l’entier qui se trouve 5 fois


avant celui qui contient la variable a. Donc le contenu du
pointeur p - 5 est
1030 - 5 * 4 = 1018.
206

103
#include <stdio.h>
main() {
int i = -1;
int *p, *q, *r;
p = &i;
q = p - 1;
r=p-3;
printf("p = %d \t q = %d \t r = %d \n",p,q,r);
getch() ; }

il affiche
p=2686788 q=2686784 r=2686776
207

Incrémentation d’un pointeur


• Si p est un pointeur vers une variable a de type
T. L’expression p++ est le pointeur qui pointe
vers la variable de même type que a qui se
trouve immédiatement après a.
• L’expression p++ est équivalente à l’expression
p = p + 1.
• Si p est un pointeur alors p + 1 ne modifie pas
le contenu de p. Par contre, l’appel à p++ va
faire pointer p vers l’adresse (p+1) et dans ce
cas le contenu de p va changer.
208

104
Exemple
• La variable a de type int est rangée en mémoire à l’adresse
1000. Quel est le contenu de chacun des pointeurs suivants :
int * p = &a;
int * q = p++;
int * r = ++q;
• Le pointeur p pointe vers a, donc le contenu de p est l’adresse
de a, c’est-à-dire 1000.
• q = p++; est équivalente à q = p; p = p + 1; Donc le contenu du
pointeur q est 1000, mais le pointeur p est incrémenté et par
suite, le contenu de p est 1004.
• r = ++q; est équivalente à q = q + 1; r = q; Donc le contenu du
pointeur q est 1004, et celui de r est 1004.
• La variable a ?? Elle n’est plus pointée par ces pointeurs 209

Décrémentation d’un pointeur


• Si p est un pointeur vers une variable a de
type T. L’expression p-- est le pointeur qui
pointe vers la variable de même type que a
qui se trouve immédiatement avant a.

• L’expression p-- est équivalente à l’expression


p = p - 1.

210

105
Exemple
• La variable a de type int est rangée en mémoire à l’adresse 1000.
Quel est le contenu de chacun des pointeurs suivants :
int * p = &a;
int * q = p--;
int * r = --q;

• Le pointeur p pointe vers a, donc le contenu de p est l’adresse de a,


c’est-à-dire 1000.
• q = p--; est équivalente à q = p; p = p - 1; Donc le contenu du
pointeur q est 1000, mais le pointeur p est décrémenté et par
suite, le contenu de p est 996.
• r = --q; est équivalente à q = q - 1; r = q; Donc le contenu du
pointeur q est 996, et celui de r est 996.
• r = ++q; est équivalente à q = q + 1; r = q; Donc le contenu du
pointeur q est 1004, et celui de r est 1004.
211

Différence de deux pointeurs

• Si p et q deux pointeurs respectivement vers deux


variables de type T. L’expression p - q est un entier
signé représentant le nombre de variables de type T
entre la variable pointée par p et celle pointée par q.

• Si par exemple, p=&a; et q=&b; où a et b sont deux


variables de type T, alors l’expression
( p – q ) vaut ( &a - &b) / sizeof(T).

212

106
Exemple
• Les variables a et b de type int sont rangées en
mémoire respectivement aux adresses 1000 et
1040. Quel est la valeur de l’expression (p - q)
où p est un pointeur vers a et q est un pointeur
vers b ?

• Solution :
• La valeur de (p – q) est
(&a - &b) / sizeof(int)
= (1000 - 1040) / 4 = -10.
213

• la somme de deux pointeurs n’est pas autorisée


• Produit et division aussi
• ils n’ont aucun sens

#include <stdio.h>
main()
{
int a;
int *p, *q;
p=&a ;
q=p+3;
printf("%d", q-p); /* cette instruction va afficher 3 */
printf("%d", p-q); /* cette instruction va afficher -3 */
getch();
}
214

107
Relation entre un tableau et un pointeur

• En C, le nom d’un tableau d’un certain type est


considéré comme un pointeur vers le premier
élément de ce tableau. Il est considéré comme un
pointeur constant sur le début du tableau.

• Comme on l’a déjà vu, le nom d'un tableau T


représente l'adresse de son premier élément
(T=&T[0]). Avec le formalisme pointeur, on peut
dire que T est un pointeur constant sur le premier
élément du tableau. 215

Relation entre un tableau et un pointeur

• Ainsi, si tab désigne un tableau de type T, alors


tab est un pointeur vers le premier élément de
tab, à savoir tab[0].

• Par suite, tab[0] est équivalent à *tab.

• Généralement, si k est un entier, alors


tab[k] est équivalent à *(tab + k).
216

108
écrire un programme qui saisit dix valeurs
entières, les stocke dans un tableau et les
affiche, en utilisant un pointeur.

#include<stdio.h>
main() {
int T[10] ;
int i , n;
for(i = 0; i  10; i++)
{ printf(" Valeur numero %d : ", i );
scanf("%d", T+i );
} ; 217

for(i = 0; i  10; i++)


printf(" T[%d] = %d \n ", i , * ( T + i ) ) ;
getch();
}
• les parenthèses dans l’expression *(T+i) sont
nécessaires car l’expression *T+i est équivalente
à T[0]+i.
• Attention T++ et T=T+i vont détruire le contenue
du tableau T (T est un pointeur constant )
218

109
Relation entre un pointeur et un tableau
• En C, un pointeur p vers une variable de type T
est considéré comme un tableau d’éléments de
type T dont le premier élément et *p (c’est-à-
dire la variable pointée par p).
• Par conséquent, on peut accéder au premier
élément de ce tableau en utilisant soit *p, soit
p[0].
• Généralement, si k est un entier, alors
* (p + k) est équivalent à p[k].
• Aucune réservation de l’espace mémoire; c’est
dangereuse comme écriture , il faut associer ce
pointeur à un tableau déjà déclaré
219

• En déclarant un tableau T et un pointeur P du


même type, l’instruction P=T fait pointer P sur
le premier élément de T (P=&T[0]) et crée une
liaison entre P et le tableau T. A partir de là,
on peut manipuler le tableau T en utilisant P,
en effet :

P pointe sur T[0] et *P désigne T[0]


P+1 pointe sur T[1] et *(P+1) désigne T[1]
….
P+i pointe sur T[i] et *(P+i) désigne T[i]
220

110
Version 1:
main(){
float T[100] , *pt;
int i,n;
do { printf("Entrez n \n " ); scanf(" %d" ,&n);
} while(n<0 ||n>100);
pt=T;
for(i=0;i<n;i++)
{ printf ("Entrez T[%d] \n ",i );
scanf(" %f" , pt+i);
};
for(i=0;i<n;i++)
printf (" %f \t",*(pt+i));
221

Version 2: sans utiliser i


main() {
float T[100] , *pt;
int n;
do {printf("Entrez n \n " ); scanf(" %d" ,&n);
}while(n<0 ||n>100);
for(pt=T;pt<T+n;pt++)
{ printf ("Entrez T[%d] \n ",pt-T );
scanf(" %f" , pt);
} ;
for(pt=T;pt<T+n;pt++)
printf (" %f \t",*pt);
}
222

111
quelle est la valeur de la variable s après
l’exécution de ce programme :

#include<stdio.h>
main() {
int x[6] = {1, 2, 3, 4, 5, 6} , i, s = 0;
int * p = &x[0] ; // ou int *p=x ;
for(i = 0; i  6; i++)
{
s + = * (p + i);
};
}
223

• Les variables utilisées : x est un tableau de 6


entiers, k et s deux entiers et p est un pointeur
vers un entier. La valeur initiale de s est 0.
• Le pointeur p va parcourir le tableau x en
utilisant le pointeur p et calcule dans s la
somme des éléments de x par l’instruction s =
s + *(p + k);.
• Finalement, on trouve que la valeur de s est
1 + 2 + 3 + 4 + 5 + 6 = 21.

112
#include <stdio.h>
main()
{
int t[8]={-2,6,3,-2,1,9,0,-1} ;
int *p;
for(p=t ;p<t+8 ;p++)
printf("%d \t", *p);
/* cette instruction va afficher : -2 6 3 -2 1 9 0 -1 */
getch();
}
225

#include<stdio.h>
#define N 10
main() {
int T[N]={3,6,0,8,97,0,5,6,0,8} ;
int *Deb, *Fin, *Comp ;
Deb = &T[0] ;
Fin = &T[N-1];
for(Comp=Deb ; Comp <= Fin ; Comp++)
printf("%d \n ",*Comp) ;
getch(); }

il affiche le tableau T
226

113
Pointeurs de type pointeurs
• Puisque les pointeurs sont des variables, et on peut déclarer
des pointeurs qui pointent sur n’importe quel type de
variables, alors il est possible de déclarer des pointeurs qui
pointent sur des pointeurs.
• La syntaxe de la déclaration est la suivante :
Type **Nom_pointeur ;

Type : désigne le type du pointeur pointée par le pointeur.


Nom_pointeur : est le nom du pointeur.

• Exemple float **p ;

• Cette instruction déclare un pointeur qui pointe sur des


pointeurs de type réel. Ce concept est important pour les
tableaux à deux dimensions comme nous le verrons après. 227

• Le nom d'un tableau A à deux dimensions est un pointeur constant


sur le premier élément du tableau c-à-d A[0][0]. En déclarant un
tableau A[N][M] et un pointeur P du même type, on peut
manipuler le tableau A en utilisant le pointeur P en faisant pointer
P sur le premier élément de A (P=&A[0][0]), car la matrice A peut
être considérée comme un vecteur. Ainsi :

P pointe sur A[0][0] et *P désigne A[0][0]


P+1 pointe sur A[0][1] et *(P+1) désigne A[0][1] ….
P+M pointe sur A[1][0] et *(P+M) désigne A[1][0] ….
P+i*M pointe sur A[ i][0] et *(P+i*M) désigne A[i][0] ….
P+i*M+j pointe sur A[ i][ j] et *(P+i*M+j) désigne A[i][j]
ici la matrice A est considérée comme un tableau à 1 dimension,
on peut la représenter par un pointeur simple
228

114
float A[N][M];
• Cette instruction est interprétée par le compilateur comme
étant une déclaration d’un tableau à N lignes et M
colonnes. Chaque ligne de ce tableau est considérée
comme un tableau à une dimension. La ième ligne du
tableau peut être notée A[i]. Ainsi, A[i] est un tableau de
taille M dont les composantes sont :
A[i][0] , A[i][1] , … , A[i][M] .
• Par conséquent, le nom A[i] du tableau est équivalent à
&A[i][0] (adresse de A[i][0]).
• par suite, pour tout 0 ≤ j ≤ M-1, nous avons :
A[i]+j est équivalent à &A[i][j] .
• La matrice A est considérée ici comme un pointeur de
pointeur. La matrice est normalement représentée par un
pointeur de pointeur.
229

Saisie et affichage d’une matrice


#define N 10
#define M 20
main( ){
int i , j , A[N][M] , *pt ;
pt=&A[0][0] ;
for(i=0;i<N;i++)
for(j=0;j<M;j++)
{ printf ("Entrez A[%d][%d]\n ",i,j ) ; scanf("%d", pt+i*M+j ) ; };
for(i=0;i<N;i++)
{ for(j=0;j<M;j++) printf (" %d \t", *( pt+i*M+j) ) ;
printf ("\n");} ;
}
230

115
• Remarque : Les différences entre les tableaux et les
pointeurs peuvent être résumées ainsi :
• La déclaration d’un pointeur P réserve dans la
mémoire uniquement l’espace mémoire nécessaire
pour stocker sa valeur (qui est une autre adresse).
• La déclaration d’un tableau T réserve dans la
mémoire un espace mémoire proportionnel à la
taille du tableau et dont l’emplacement est statique.
• La taille du pointeur P peut changer au cours du
programme alors que celle du tableau T reste
constante et ne peut pas être modifiée.
• Les expressions P=T et P++ sont autorisées mais les
instructions T=P ou T++ ne le sont pas. 231

Allocation dynamique de mémoire


• Quand on déclare une variable dans un programme, on lui
réserve implicitement un certain nombre d’octets en
mémoire. Ce nombre est connu avant l’exécution du
programme
• Or, il arrive souvent qu’on ne connaît pas la taille des
données au moment de la programmation. On réserve
alors l'espace maximal prévisible, ce qui conduit à un
gaspillage de la mémoire
• Il serait souhaitable d’allouer la mémoire en fonction des
données à saisir (par exemple la dimension d’un tableau)
• Il faut donc un moyen pour allouer la mémoire lors de
l'exécution du programme : c’est l’allocation dynamique
de mémoire 232

116
• Avant de manipuler un pointeur, et notamment de
lui appliquer l’opérateur d’indirection *, il faut
l’initialiser. Sinon, par défaut, la valeur du pointeur
est égale à une constante symbolique notée NULL
définie dans stdio.h. En général, cette constante
vaut 0.
• Le test p== NULL permet de savoir si le pointeur p
pointe vers un objet.
• On peut initialiser un pointeur p par une affectation
sur p. Par exemple, on peut affecter à p l’adresse
d’une autre variable. Il est également possible
d’affecter directement une valeur à *p. Mais pour
cela, il faut d’abord réserver à *p un espace-
mémoire de taille adéquate.
233

• L’adresse de cet espace-mémoire sera la valeur de p.

• L’opération qui consistant à réserver un espace-


mémoire pour stocker l’objet pointé s’appelle
allocation dynamique.

• L’allocation dynamique se fait en C par la fonction


malloc (" Memory ALLOCation ", c'est-à-dire
"Allocation de mémoire") de la librairie standard
stdlib.h.

Sa syntaxe est malloc( N)


Où N est le nombre d’octets nécessaire
234

117
• Cette fonction retourne un pointeur de type char * pointant
vers un objet de taille N octets.

• Car cette fonction malloc retourne une adresse qui est en


réalité le numéro ou l’adresse d’un octet dans la mémoire.
Or dans un octet on ne peut mettre qu’un caractère (char).
• Pour réserver l’espace nécessaire à des pointeurs vers des
objets qui ne sont pas de type char, il faut convertir le type
de la sortie de la fonction malloc à l’aide de l’operateur cast.
• L’argument N est souvent donné à l’aide de la fonction
sizeof(type) qui renvoie le nombre d’octets utilisés pour
stocker un objet de ce type.
• Ainsi, pour initialiser un pointeur vers un entier, on écrit :
#include <stdlib.h>
int *p;
p = (int*)malloc(sizeof(int));
235

• On aurait pu écrire également


p = (int*)malloc(4);

• puisqu’un objet de type int est stocké sur 4 octets.


Mais on préférera la première écriture qui a
l’avantage d’être portable.

• Aussi sans opérateur de cast est possible.


#include <stdlib.h>
int *p;
p = malloc(sizeof(int));

• Mais l’écriture standard est avec l’opérateur de cast


236

118
#include <stdio.h>
#include <stdlib.h>
main() {
int i = 3 ;
printf(" Adresse de i = %d\n",&i );
int *p ;
printf("valeur de p avant initialisation = %d\n",p);
p = (int*)malloc(sizeof(int));
printf("valeur de p apres initialisation = %d\n",p);
p = &i;
printf("valeur de *p = %d\n",*p);
printf("valeur de p apres redirection vers i = %d\n", p);
getch() ; }
237

Ce programme affichera

Adresse de i = 2293556
valeur de p avant initialisation = 4198592
valeur de p après initialisation = 7868248
valeur de *p = 3
valeur de p après redirection vers i = 2293556

Pour savoir si un pointeur est chargé on test sa


valeur, elle doit différente de NULL. C-à-d le
compilateur a trouvé un espace convenable pour ce
pointeur. Autrement dit la fonction malloc n’a pas
échoué. 238

119
#include <stdio.h>
#include <stdlib.h>
main() {
int i = 3 ; printf(" Adresse de i = %d\n",&i);
int *p ; printf("valeur de p avant initialisation = %d\n",p);
p = (int*)malloc(sizeof(int));
printf("valeur de p apres initialisation = %d \n",p);
*p=i;
printf("valeur de *p apres (*p=i ) est %d et de i = %d \n",*p,i);
*p=*p+2;
printf("valeur de *p apres (*p=*p+2 ) est %d et de i = %d \n",*p,i);
i=i-1;
printf("valeur de *p apres ( i=i-1 ) est %d et de i = %d \n",*p,i);
p = &i;
printf("valeur de *p apres (p=&i ) est %d de i est %d \n",*p,i);
printf("valeur de p apres (p=&i ) est %d\n",p) ; getch(); }
239

Ce programme affichera

Adresse de i = 2293556
valeur de p avant initialisation = 4198592
valeur de p apres initialisation = 8195928
valeur de *p apres (*p=i) est 3 et de i = 3
valeur de *p apres (*p=*p+2) est 5 et de i = 3
valeur de *p apres (i=i-1) est 5 et de i = 2
valeur de *p apres (p=&i ) est 2 et de i est 2
valeur de p apres (p=&i) = 2293556

Rq : Avant la redirection p vers i , *p n’est pas i


240

120
La fonction malloc permet également d’allouer un espace
pour plusieurs objets contigus en mémoire.
On peut écrire par exemple

#include <stdio.h>
#include <stdlib.h>
main(){
int *p ;
p = (int*)malloc(3 * sizeof(int));
*p = 3 ; *(p+1)=6 ; *(p+2)=13 ;

printf("p = %d \t *p = %d \n " ,p ,*p ) ;


printf("p+1 = %d \t *(p+1) = %d \n " ,p+1 , *(p+1));
printf("p+2=%d \t *(p+2)=%d ", p+2, *(p+2) );
getch();} 241

On a ainsi réservé, à l’adresse donnée par la


valeur de p, 12 octets en mémoire, qui
permettent de stocker 3 objets de type int.

Le programme affiche
p = 3608472 *p = 3
p+1 = 3608476 *(p+1) = 6
p+2 = 3608480 *(p+2) = 13

242

121
• A la fin de ce code, p est un pointeur contenant une
adresse qui a été réservée par le système d’exploitation.
En pratique, nous sommes devant deux possibilités :
• L'allocation a réussi, et dans ce cas notre pointeur
contient une adresse.
• L'allocation a échoué, et dans ce cas notre pointeur
contient l'adresse NULL (qui est équivalent à l’absence
d’adresse).

• Dans certains cas, l’opération d’allocation de mémoire


échoue (par exemple dans le cas où il n’y pas assez de
mémoire libre ou si l’utilisateur demande de réserver
une grande partie de la mémoire vive).
• Il est donc recommandé de tester si l'allocation a
marché. Cela consiste à faire le test suivant :
243

int *p;
p = (int*)malloc(4*sizeof(int)) ;
if (p == NULL) // Si l'allocation a échoué
exit(0);
// On arrête immédiatement le programme
// dans le cas contraire, on continue le programme
// normalement.
• Si le pointeur est différent de NULL, le programme
peut continuer, sinon il faut afficher un message
d'erreur ou même mettre fin au programme (en
utilisant l’instruction exit(0)), par ce qu'il ne pourra
pas continuer correctement s'il n'y a plus de place
en mémoire.
244

122
Exemple :
#include <stdio.h>
#include <stdlib.h> /* à ajouter */
main() {
int *p,*q , i ;
p=(int*)malloc(5*sizeof(int));
q=(int*)malloc(5*sizeof(int));
if (p==NULL)
exit(0);
else {
for (i=0;i<5;i++) {
*(p+i)=i+1; /* ou p[i]=i+1; */
printf("%d \t", *(p+i)); } ; } ;
245

printf("\n") ;
if (q==NULL)
exit(0);
else {
for (i=0;i<5;i++) {
q[i]=i-1; /* ou *(q+i)=i-1 ; */
printf("%d \t", q[i]); } ;} ;
getch() ; }
Après exécution, le programme affiche :

1 2 3 4 5
-1 0 1 2 3
246

123
Autres fonctions d’allocation
• Il existe d’autres fonctions d’allocation
dynamique de mémoire dans la bibliothèque
<stdlib.h>

• La fonction calloc de la librairie stdlib.h a le


même rôle que la fonction malloc mais elle
initialise en plus l’objet pointé *p à zéro.
• Sa syntaxe est
calloc( nb-objets , taille-objets )

247

Ainsi, si p est de type int*, l’instruction


p = (int*)calloc(N , sizeof(int) );

est strictement équivalente à


p = (int*)malloc(N * sizeof(int) );
for (i = 0; i < N; i++)
*(p + i) = 0;

L’emploi de calloc est simplement plus rapide.

248

124
Allocation dynamique de la mémoire
pour un pointeur de type pointeur
• Etant donné qu’il est possible d’allouer de la
mémoire pour un pointeur sur n’importe quelle type
de variable, l’allocation dynamique de la mémoire
pour un pointeur de type pointeur est donc possible.
Sa syntaxe est :

float **p;
p=(float**)malloc(n*sizeof(float *));
for (i=0;i<n;i++)
p[i]=(float*)malloc(m*sizeof(float));
249

• La 1ere malloc permet d’allouer un espace mémoire pour


un tableau de n pointeurs de type réel.
• De même, les instructions malloc dans la boucle for
permettent d’allouer à chaque pointeur p[i] m places
mémoires de type réel. Il faut écrire de préférence

p=(float**)malloc(n*sizeof(float*));
if (p==NULL){
printf(" On n'a pas pu allouer de la mémoire " );
exit(0) ; } ;
for (i=0;i<n;i++) {
p[i]=(float*)malloc(m*sizeof(float));
if (p[i]==NULL) {
printf(" On n'a pas pu allouer de la mémoire " ) ;
exit(0) ; } ; } ;
250

125
Par exemple, pour créer avec un pointeur de pointeur
une matrice à k lignes et n colonnes à coefficients
entiers, on écrit :

main()
{
int k, n;
int **tab;
tab = (int**)malloc(k * sizeof(int*));
for (i = 0; i < k; i++)
tab[i] = (int*)malloc(n * sizeof(int));
....
} 251

• La première allocation dynamique réserve pour


l’objet pointé par tab l’espace mémoire
correspondant à k pointeurs sur des entiers. Ces k
pointeurs correspondent aux lignes de la matrice.
• Les allocations dynamiques suivantes réservent
pour chaque pointeur tab[i] l’espace mémoire
nécessaire pour stocker n entiers.
• Si on désire en plus que tous les éléments du
tableau soient initialisés à zéro, il suffit de
remplacer l’allocation dynamique dans la boucle
for par
tab[i] = (int*)calloc(n, sizeof(int));
252

126
#include <stdio.h>
#include <stdlib.h>
main() {
int t[6][4] , i , j ; int **p ;
p=(int**)malloc(6*sizeof(int*));
for (i=0;i<6;i++)
{ p[i]=(int*)malloc(4*sizeof(int)) ; p[i]=&t[i][0] ; };
for (i=0;i<6;i++)
{ for (j=0;j<4;j++) t[i][j]=(i+1)*(j+1) ; } ;
for (i=0;i<6;i++){
for (j=0;j<4;j++) {
printf("%d \t", *(p[i]+j));} ; /* c'est équivalent à
printf("%d \t", p[i][j]); */
printf("\n"); } ;
getch(); } 253

• Après exécution, le programme affiche :


1 2 3 4
2 4 6 8
3 6 9 12
4 8 12 16
5 10 15 20
6 12 18 24

• Ici un pointeur sur la tête de chaque ligne


• Il fallait tester si cette allocation a réussi. Ainsi le
programme sera écrit de la manière la plus correcte
suivante:
254

127
#include <stdio.h>
#include <stdlib.h>
main()
{
int t[6][4] , i , j ;
int **p ;
p=(int**)malloc(6*sizeof(int*));
if (p==NULL){
printf(" On n'a pas pu allouer de la mémoire " );
exit(0) ;
};
255

for (i=0;i<6;i++) {
p[i]=(int*)malloc(4*sizeof(int));
if (p[i]==NULL) {
printf(" On n'a pas pu allouer de la mémoire " ) ;
exit(0) ; }
else
p[i]=&t[i][0] ; };

for (i=0;i<6;i++)
{ for (j=0;j<4;j++) t[i][j]=(i+1)*(j+1) ; } ;
for (i=0;i<6;i++) {
for (j=0;j<4;j++) { printf("%d \t", *(p[i]+j)) ; } ;
printf("\n"); } ;
getch(); }
256

128
#include <stdio.h>
#include <stdlib.h>
main() {
int **p , i , j ;
p=(int**)malloc(6*sizeof(int*));
if (p==NULL){
printf("le système n'a pas pu allouer de l espace memoire");
exit(0) ;} ;
for (i=0;i<6;i++) {
p[i]=(int*)malloc(4*sizeof(int));
if (p[i]==NULL) {
printf(" On n’a pas pu allouer de la mémoire ");
exit(0) ; } ; } ;
257

for (i=0;i<6;i++) {
for (j=0;j<4;j++)
p[i][j]=(i+1)*(j+1); } ;
for (i=0;i<5;i++) {
for (j=0;j<4;j++) { printf("%d \t", *(p[i]+j)) ; } ;
printf("\n"); } ;
getch() ; }

Ici on n’avait pas besoin d’utiliser un autre tableau,


on utilise directement le pointeur pour le
stockage.

On rappelle que *(p[i]+j)) est équivalente à p[i][j]


258

129
• Contrairement aux tableaux à deux dimensions,
on peut choisir des tailles différentes pour
chacune des lignes tab[i].
• Par exemple, si l’on veut que tab[i] contienne
exactement i+1 éléments, on écrit

for (i = 0; i < k; i++)


tab[i] = (int*)malloc((i + 1) * sizeof(int));

On aura une structure


de stockage de cette forme
259

Fonction free
• L’un des avantages de la gestion dynamique est la
possibilité de libérer les espaces mémoires affectés
aux pointeurs une fois on en a plus besoin. En
effet, la fonction free permet de libérer tout
espace mémoire alloué à un pointeur qui ne sera
plus utilisé dans la suite du programme.

• Si on n'a plus besoin d'un bloc de mémoire réservé


par malloc, alors on peut le libérer à l'aide de la
fonction free , dont la syntaxe est :
free(pointeur) ;
260

130
#include <stdio.h>
#include <stdlib.h>
main() {
int i , *p;
p=(int*)malloc(5*sizeof(int));
if (p==NULL)
exit(0);
else {
for (i=0;i<5;i++) {
*(p+i)=i; /* ou p[i]=i; */
printf("%d \t", *(p+i)); } ;} ;
free(p);
getch(); }

Remarque: une fois l’espace mémoire réservé au pointeur p


est libéré, il n’est plus possible de manipuler le pointeur261p.

Saisie et affichage d’un tableau


#include<stdio.h>
#include<stdlib.h>
main()
{ float *pt ; int i , n ;
printf("Entrez la taille du tableau \n" ) ; scanf(" %d" ,&n);
pt=(float*) malloc(n*sizeof(float));
if (pt==Null)
{ printf( " pas assez de mémoire \n" ) ; exit(0) ; } ;
printf(" Saisie du tableau \n " );
for(i=0;i<n;i++)
{ printf ("Élément %d ? \n ",i+1) ; scanf(" %f" , pt+i); } ;
printf(" Affichage du tableau \n " );
for(i=0;i<n;i++) printf (" %f \t",*(pt+i));
free(pt);}
262

131
• L’un des principaux avantages de l’utilisation
des pointeurs et la possibilité de libérer
l’espace alloué au pointeur avant la fin du
programme.

• Cela n’est pas possible avec les tableaux,


puisque la place mémoire allouée au moment
de la déclaration d’un tableau reste occupée
jusqu’à la fin de l’exécution du programme.

263

La fonction realloc
• Passons maintenant à la dernière fonction d'allocation : la
fonction realloc() voici sa syntaxe :

realloc ( P , N )

• La fonction realloc() tente de redimensionner un bloc


mémoire donné à cette fonction par son adresse P
(pointeur) avec une nouvelle taille N.

• Dans le cadre d'un tableau s’il faudra le redimensionner


avec une nouvelle taille, cette taille sera soit supérieure à
l’ancienne, soit inferieure à l’anicienne.
264

132
• Comme d'habitude, si une erreur se produit, la
fonction retourne le pointeur NULL. Si en
revanche, tout s'est bien passé, la fonction
renvoie un pointeur vers l'adresse du nouveau
bloc réalloué.

• La fonction réalloue le bloc mémoire tout en


gardant le contenu de ce qui se trouvait dans le
bloc précédent. La fonction ne fait donc qu'un
changement de taille.
265

La fonction realloc
• La fonction realloc (P , N ) de <stdlib.h> permet de
modifier la taille d’une zone préalablement allouée
par malloc, calloc ou realloc.

• Le pointeur P doit être l’adresse de début de la zone


dont on veut modifier la taille. Quant à N, elle
représente la nouvelle taille souhaitée.

• Cette fonction restitue l’adresse de la nouvelle zone


ou un pointeur nul dans le cas où l’allocation a
échoué.
266

133
• Lorsque la nouvelle taille demandée est
supérieure à l’ancienne, le contenu de l’ancienne
zone est conservé (quitte à le recopier si la
nouvelle adresse est différente de l’ancienne).
• Dans le cas où la nouvelle taille est inférieure à
l’ancienne, le début de l’ancienne zone (c’est-à-
dire taille octets) verra son contenu inchangé.
• Ceci est donc utile pour un tableau dynamique :
en effet, on peut ajouter ou enlever une case à la
fin du tableau sans le modifier.
• Rien de tel qu'un exemple pour illustrer
comment fonctionne la fonction : 267

int * tab ;
/* Création d'un tableau de 3 entiers */
tab = (int*)calloc ( 3 , sizeof(int) ) ;
tab[0] = 1 ; tab[1] = 2 ; tab[2] = 3 ;
/* Ajout d'un element au tableau */
tab = (int*) realloc (tab , 4 * sizeof(int) );
tab[3] = 4;
for ( i = 0 ; i < 3 ; i++ ){
printf(" tab[%d] = %d \n", i , tab[i] );}

268

134
• Voici donc comment gérer les réallocations mémoire
de manière correcte. Tout d'abord, au lieu d'affecter
le retour de la fonction realloc à notre tableau
initial, nous affectons le retour à une variable
temporaire.
• Celle ci, si elle vaut NULL après l'appel à la
fonction realloc(), indique une erreur de
réallocation.
• Si tel est le cas, nous affichons un message d'erreur,
nous libérons la mémoire occupée par notre tableau
initial et nous quittons le programme.
• Si la réallocation s'est bien passé, c'est à dire
que realloc() n'a pas retourné NULL, alors, on
affecte le résultat de la fonction à notre tableau 269

• Nous avons vu que la fonction realloc() ne


pouvait intervenir qu'après une allocation
via malloc() ou calloc(). Ceci n'est pas tout à fait
vrai, en effet, la fonction realloc() peut faire
office de fonction d'allocation dans un cas
particulier.
• Ce cas particulier est tout simplement le cas où
la fonction reçoit en adresse de base le
pointeur NULL. Dans ce cas alors la fonction se
comporte exactement comme la
fonction malloc().
• Ainsi notre premier exemple d'utilisation de
realloc aurait pu s'écrire comme ceci : 270

135
int *tab= NULL , *temp ;
/* Création d'un tableau de 3 entiers */
tab= (int*) realloc ( tab , 3 * sizeof(int) ) ;
if ( tab== NULL ){ printf("Allocation impossible") ; exit(0); } ;
tab[0] = 1 ; tab[1] = 2 ; tab[2] = 3 ;
/* Redimensionnement*/
temp = (int*) realloc (tab, 4 * sizeof(int ) );
if ( temp == NULL ){
printf("Reallocation impossible");
free(tab); exit(0);}
else{
tab = temp;} ;
/* Ajout d'un élement */
tab[3] = 4;
for ( i = 0 ; i < 3 ; i++ )
{ printf(" tab[%d] = %d \n", i , tab[i] ); } ;
271

• Il arrive également qu'on alloue dynamiquement


de la place en mémoire et qu'on se rende compte
par après que la taille n'est pas suffisante.
• Par exemple, si on alloue 500 octets pour contenir
125 nombres et qu'on se rend compte après qu'en
fait il y a 200 nombres, il va falloir réallouer de la
mémoire, mais on ne va pas tout sauver et tout
recopier, nous allons utiliser la fonction realloc.
• Celle-ci reçoit deux arguments : le premier étant le
pointeur contenant l'adresse à redimensionner et
le deuxième étant la nouvelle taille du tableau, par
exemple le programme suivant :
272

136
#include <stdio.h>
#include <stdlib.h>
main() {
int nombre , *tableau = NULL , x = 0;
scanf("%d", &nombre);
tableau = (int*)malloc(sizeof(int) * nombre);
for (x = 0; x < nombre; x++) {
scanf("%d", &tableau[x]); } ;
for (x = 0; x < nombre; x++) {
printf ("Nombre %d : %d\n", (x + 1), tableau[x]); };
tableau = (int*)realloc(tableau, sizeof(int) * (nombre + 1));
tableau[nombre] = 100;
for (x = 0; x < nombre + 1; ++x) {
printf ("Nombre %d : %d\n", (x + 1), tableau[x]); };
free(tableau); getch();}
273

• il est préférable pour cette fonction realloc() de


passer par un pointeur secondaire temporaire.

• Parce qu'en faisant comme l'exemple


précédent de realloc, si il y a un problème
d'allocation, la valeur du pointeur sera écrasée
par NULL et toute les données du tableau
seront perdues

• il est donc préférable de passer par un pointeur


temporaire :
274

137
#include <stdio.h> #include <stdlib.h> main() {
int nombre = 0 , *tableau = NULL , *temp = NULL , x = 0 ;
scanf ("%d", &nombre);
tableau =(int*) malloc(sizeof(int) * nombre);
for (x = 0; x < nombre; x++) { scanf ("%d", &tableau[x]); } ;
for (x = 0; x < nombre; x++) {
printf ("Nombre %d : %d\n", (x + 1), tableau[x]); } ;
temp = (int*)realloc(tableau, sizeof(int) * (nombre + 1));
if (temp != NULL)
tableau = temp;
else
exit(0);
tableau[nombre] = 100;
for (x = 0; x < nombre + 1; x++) {
printf ("Nombre %d : %d\n", (x + 1), tableau[x]); } ;
free(tableau); }
275

Dans cet exemple, nous récupérons d'abord


dans le pointeur temp la valeur renvoyée
par realloc, si cette valeur est bien différente
de NULL, nous échangeons les adresses
mémoires et la plaçons dans la variable tableau
sinon, on quitte le programme.

276

138
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
main() { char *str ;
str = (char *) malloc(15); // où str = malloc(15);
strcpy(str, "tutorialspoint");
// recopie la chaîne "tutorialspoint" dans str
printf("mot = %s, Address = %d \n", str , str ) ;
str = (char *) realloc(str, 25) ;
strcat(str, ".com");
// recopie la chaîne ".com" à la suite de la chaîne str
printf("mot = %s, Address = %d \n", str , str ) ;
free(str); }
277

Après exécution le programme affiche

mot = tutorialspoint , Addresse = 355090448


mot = tutorialspoint.com , Addresse = 355090448

• L’adresse est restée la même mais le compilateur


à augmenté la taille
278

139
Fin du programme

Bonne chance

279

140

Vous aimerez peut-être aussi