Transcript Rappel_C

Un survol du language C
Le language C
Le language C appartient à une famille de languages
(Pascal, Cobol, Fortran, etc.) où un programme est vu
comme une série d’instructions. Chaque instruction produit
une transformation locale de la mémoire.
Les types de données
Toutes les valeurs en C sont du type réel ou entier. En fait il s’agit de deux
« familles » de types puisqu’il existe plusieurs sortes d'entiers et de réels.
Voici les deux principaux types en C.
int :
Les variables de ce type peuvent contenir un nombre entier, qui
reflète typiquement la taille naturelle des nombres entiers sur la
machine utilisée (souvent 32 bits).
double : Les variables de ce type peuvent contenir un nombre réel en
point flottant en double précision (64 bits)
Les constantes
Les constantes de type int.
décimales:
17
octales:
021
hexadécimales: 0x11
8
-68
010
-0104
(commence par un zéro)
0X8
-0x44
(commence par 0x ou 0X)
Les constantes de type double.
point flottant:
1200.0
3.1416
-.00067
avec exposant: 12e2
31416E-4
-67e-5
mixtes:
3.1416e0 -6.7E-4
1.2e3
Déclaration des variables
La déclaration des variables se fait selon le modèle suivant:
type variable1, variable2, ... ;
Remarquez la virgule séparant deux variables ainsi que le pointvirgule à la fin.
Exemples:
int nbre;
int a,b,c;
double x;
définit nbre comme une variable entière
définit trois variables entières dont les noms
respectifs sont a, b et c.
définit x comme une variable réelle.
Les opérateurs arithmétiques
+
*
/
addition
soustraction
multiplication
division
}
% opérateur de modulo
}
donne le reste de la division entière
Donne un int si les deux
opérandes sont de type int,
donne un double sinon
Ne s'applique qu'à des
opérandes de type int.
Donne un int.
Exemple: 4 % 3 vaut 1
12 % 3 vaut 0
17 % 5 vaut 2
Les opérateurs de comparaisons
type
d'opérateur
égal
plus grand
plus grand ou égal
plus petit
plus petit ou égal
différent
notation
mathématique
=
>

<


notation en C
==
>
>=
<
<=
!=
Les opérateurs logiques
Type d’opérateur
ET
OU
NON
Notation en C
&&
||
!
La priorité des opérateurs en C
Opérateurs
()
!
* /
+ < <= > >=
== !=
&&
||
=
Associativité
de gauche à droite
de droite à gauche
de gauche à droite
de gauche à droite
de gauche à droite
de gauche à droite
de gauche à droite
de gauche à droite
de droite à gauche
Les instructions
En C, une instruction est simple ou composée (dans ce
dernier cas on parle aussi d'un bloc d'instructions).
Instructions simples:
Ex. x = x + 1;
y = cos(x);
x = cos(x / y - 8) * 2;
Blocs d'instructions:
Ex. {
x = x + 1;
y = cos(x);
x = cos(x / y - 8) * 2;
}
3 instructions simples
1 bloc d’instructions
Instruction conditionnelle
Forme 1
if (expression) then instruction
L'instruction est évaluée si et seulement si la valeur de
l'expression est différente de 0.
Expression
= 0  FAUX
 0  VRAI
Instruction conditionnelle
Forme 2
if (expression) then
instruction
else
instruction
La première instruction est évaluée si la valeur de
l'expression est différente de 0, sinon la seconde instruction
est évaluée.
Instruction conditionnelle
Forme 3
if (expression) then
instruction
else if (expression) then
instruction
else
instruction
Autre forme conditionnelle
switch (expression)
{
case expression-constante: instruction
case expression-constante: instruction
default: instructions
}
L'expression doit être de type entier.
Elle est d'abord évaluée puis l'instruction correspondante
est exécutée ainsi que les instructions suivantes.
Autre forme conditionnelle
switch (expression)
{
case expression-constante: instruction
case expression-constante: instruction
default: instructions
}
valide:
5
2*3 + 5
non valide:
3*x + 5
x
L'expression doit être de type entier.
Elle est d'abord évaluée puis l'instruction correspondante
est exécutée ainsi que les instructions suivantes.
L'instruction break
Pour sortir d'un switch sans exécuter toutes les instructions,
on peut utiliser l'instruction break.
Le break peut aussi être utilisé pour sortir des boucles.
Exemple
switch (n)
{
case 0: printf(“0”);
case 1: printf(“1”);
case 2: printf(“2”);
default: printf(“3”);}
Si n vaut 1 alors le programme affichera simplement: 123
Exemple
switch (n)
{
case 0: {printf(“0”); break;}
case 1: {printf(“1”); break;}
case 2: {printf(“2”); break;}
default: printf(“3”);
}
Si n vaut 1 alors le programme affichera simplement: 1
L'opérateur conditionnel “?:”
expression1 ? expression2 : expresion3
expression1 est d'abord évaluée.
Si sa valeur est différente de 0 alors expression2 est
évaluéee. Sinon, expression3 est évaluée.
La valeur d'une expression conditionnelle est égale à la
valeur de l'expression qui est évaluée (expression2 ou
expression3).
Le type d'une expression conditionnelle est le type le plus
général entre celui de expression2 et expression3. Donc le
type est double si une des deux expression est de type
double.
L'opérateur conditionnel “?:”
Exemple:
z = (x<5) ? 1 : 2
(x+1 < y) ? (4*5 + 2) : pow(2,3)
(x<5) ? printf(“1”); : printf(“2”);
La boucle while
while (expression) instruction
Exemple. Pour afficher les 100 premiers entiers positifs:
int compteur;
compteur = 0;
while (compteur < 100){
compteur + +;
printf(“%d”, compteur);
}
La boucle do-while
do instruction1 while (expression2)
Équivalent à:
instruction1
while (instruction2) instruction1
Exemple. Pour lire et afficher une liste de nombre se terminant
par 0.
int n;
do{
scanf(“%d”, &n);
printf(“%d”, n); }
while (n !=0)
La boucle for
for (expression1 ; expression2 ; expression3) instruction
Équivalent à:
expression1;
while(expression2) {
instruction
expression3;
}
Exemple. Pour afficher les n premiers entiers positifs pairs:
int i;
for (i=2; i<=2*n; i=i+2)
printf(“%d ”, i);
La bibliothèque standard du C
Il y a peu d'opérateurs arithmétiques en C, mais à partir de ceux
que nous avons vus il est possible d'en construire d'autres.
On distingue les opérateurs de base des opérateurs complexe,
construits à partir des opérateurs de base, en appelant ces
derniers fonctions.
La plupart des environnements supportants le C standard
disposent d'une large collection de fonctions appelée
bibliothèque standard.
Exemple: math.h
Plusieurs fonctions mathématiques courantes font partie de
la bibliothèque standard. Pour les utilisées il suffit
d'inclure au début du programme la ligne suivante:
#include <math.h>
Il est alors possible d'employer des fonctions telles que:
cos(x)
pow(x,y)
sqrt(x)
ldexp(x,n)
et plusieurs autres.
cosinus de x
x à la puissance y
racine carrée de x
x2 n
Exemple: ldexp(x,n)
Examinons plus en détails la fonction ldexp.
Nous savons que cette fonction retourne la valeur x2 n
Considérons quelques exemples:
ldexp(1 , 1) retourne
ldexp(1.1, 1) retourne
ldexp(1, 1.1) retourne
2  1* 21
2.2  1.1* 21
2  1* 21.1  2.14
Pourquoi???
Exemple: ldexp(x,n)
Pour comprendre ce qui se passe il faut savoir que la
fonction ldexp attend deux nombres en entrée: x et n.
• x doit être un double
• n doit être un entier
• la fonction retourne un double
Donc lorsque l'on exécute ldexp(1, 1.1), la fonction reçoit
en fait les valeurs 1 et 1 puisque la partie fractionnaire
du second opérande est tronquée.
1* 21.1  ldexp(1, 1.1)  ldexp(1, 1)  1* 21  2
Les prototypes de fonctions
Morale: avant d'utiliser une fonction de la bibliothèque
il faut connaître son prototype.
Le prototype d'une fonction indique
• le nom de la fonction
• le type des paramètres
• le type de la valeur de retour
Exemple:
double ldexp(double, int)
indique que ldexp est une fonction à deux paramètres (un
double et un int) qui retourne un double.
Exemple: pow(x,y)
La fonction d'exponentiation pow(x, y)  x y a le prototype
suivant:
double pow(double x, double y)
de sorte qu'on ne peut pas l'utiliser de la façon suivante:
pow(2, 3) % 5
Question: Pourquoi?
Exemple: pow(x,y)
Il faut plutot écrire:
(int) pow(2, 3) % 5
ou encore
( (int)pow(2,3) ) % 5
L'opérateur cast permet de convertir le type d'une expression
en un autre type.
(type) expression
Définition des fonctions
Dans la plupart des langages de programmation, la
définition d'une fonction comporte deux parties:
l'en-tête et le corps.
{
Nom de la fonction
Type de la valeur retournée
Nom des paramètres
Types des paramètres
En-tête
Corps
{
Déclaration des variables
Instructions
Les paramètres
Paramètres formels: Ceux utilisés dans la définition.
Paramètres d'appel: Ceux utilisés lors de l'appel.
Passage de paramètres
Par copie: Les paramètres d'appel sont copiés dans les
paramètres formels: création de nouvelles variables.
Par référence: Les paramètre formels réfèrent aux paramètres
d'appel: plusieurs noms pour une même case mémoire.
L'appel de fonctions
• Créations de nouvelles variables pour chacun des paramètres
passés par copie.
•Le contrôle est donné à la fonction: la première ligne du corps
de la fonction est d'abord exécutée.
Remarques:
Le nom des variables déclarées dans une fonctions est local à
cette fonction et il est invisible aux autres fonctions.
Deux fonctions distinctes peuvent utiliser le même
identificateur pour nommer deux cases mémoire distinctes.
Retour d'une fonction
À l'intérieur d'une fonction, l'instruction
return expression;
est exécutée de la façon suivante:
• L'expression est d'abord évaluée.
• Le résultat est retourné à la fonction appelante
• Toutes les variables ayant été créées après l'appel de la
fonction sont détruites.
• Le contrôle est redonné à la fonction appelante.
Concepts importants
• Utilisations des fonctions pour étendre
les possibilités de l'ordinateur.
• Prototype d'une fonction
• Définition d'une fonction.
• Les paramètres
• L'appel d'une fonction
• Le retour d'une fonction
Structure d'un programme en C
Un programme en C est une collection de fonctions qui
interagissent entre elles afin d'exécuter un calcul.
Une des fonctions doit porter le nom main: c'est la
première fonction à être appelée lors de l'exécution du
programme.
Comme on l'a vu, on peut utiliser les fonctions de la
bibliothèque standard ou encore, on peut définir nos
propres fonctions
La récursion
Nous avons vu qu'un programme est constitué d'un ensemble
de fonctions.
Il est possible pour une fonction donnée d'appeler une autre
fonction.
Que se passe-t-il si une fonction s'appelle elle-même?
C'est ce que l'on appelle la récursion.
Étude de cas 6.1
Pas-à-pas avec n=4
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
n
entier
nfact
entier
.
.
.
.
.
.
n
entier n nfact
lire n nfact
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
entier
4
entier
nfact
.
.
.
.
.
.
n
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
entier
4
entier
nfact
.
.
.
.
.
.
n
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
entier
4
entier
nfact
.
.
.
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
n
entier
4
entier
nfact
n
4
entier
si (n  1) retourner 1
retourner n * factoriel(n-1)
.
.
.
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
n
entier
4
entier
nfact
n
4
entier
si
si (n
(n 
 1)
1) retourner
retourner 11
retourner n * factoriel(n-1)
.
.
.
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
n
entier
4
entier
nfact
n
4
entier
si (n  1) retourner 1
retourner
retourner nn ** factoriel(n-1)
factoriel(n-1)
.
.
.
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
si (n  1) retourner 1
retourner n * factoriel(n-1)
n
entier
4
entier
nfact
n
4
3
n
entier
entier
si (n  1) retourner 1
retourner n * factoriel(n-1)
.
.
.
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
si (n  1) retourner 1
retourner n * factoriel(n-1)
n
entier
4
entier
nfact
n
4
3
n
entier
entier
si (n  1) retourner 1
retourner n * factoriel(n-1)
.
.
.
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
si (n  1) retourner 1
retourner n * factoriel(n-1)
n
entier
4
nfact
n
entier
entier
4
3
n
entier
si (n  1) retourner 1
retourner
retourner nn ** factoriel(n-1)
factoriel(n-1)
.
.
.
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
si (n  1) retourner 1
retourner n * factoriel(n-1)
n
entier
4
entier
nfact
n
4
3
2
n
n
entier
entier
entier
si (n  1) retourner 1
retourner n * factoriel(n-1)
si (n  1) retourner 1
retourner n * factoriel(n-1)
.
.
.
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
si (n  1) retourner 1
retourner n * factoriel(n-1)
n
entier
4
entier
nfact
n
4
3
2
n
n
entier
entier
entier
si (n  1) retourner 1
retourner n * factoriel(n-1)
si (n  1) retourner 1
retourner n * factoriel(n-1)
.
.
.
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
si (n  1) retourner 1
retourner n * factoriel(n-1)
n
entier
4
entier
nfact
n
4
3
2
n
n
entier
entier
entier
si (n  1) retourner 1
retourner n * factoriel(n-1)
si (n  1) retourner 1
retourner
retourner nn ** factoriel(n-1)
factoriel(n-1)
.
.
.
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
si (n  1) retourner 1
retourner n * factoriel(n-1)
n
entier
4
entier
nfact
n
4
3
2
1
n
n
n
entier
entier
entier
entier
si (n  1) retourner 1
retourner n * factoriel(n-1)
si (n  1) retourner 1
retourner n * factoriel(n-1)
si (n  1) retourner 1
retourner n * factoriel(n-1)
.
.
.
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
si (n  1) retourner 1
retourner n * factoriel(n-1)
n
entier
4
entier
nfact
n
4
3
2
1
n
n
n
entier
entier
entier
entier
si (n  1) retourner 1
retourner n * factoriel(n-1)
si (n  1) retourner 1
retourner n * factoriel(n-1)
si (n  1) retourner 1
retourner n * factoriel(n-1)
.
.
.
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
si (n  1) retourner 1
retourner n * factoriel(n-1)
n
entier
4
entier
nfact
n
4
3
2
n
n
entier
entier
entier
si (n  1) retourner 1
retourner n * factoriel(n-1)
si (n  1) retourner 1
retourner
retourner nn *1
*1
.
.
.
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
si (n  1) retourner 1
retourner n * factoriel(n-1)
n
entier
4
entier
nfact
n
4
3
n
entier
entier
si (n  1) retourner 1
retourner
retourner nn ** factoriel(n-1)
2
.
.
.
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
n
entier
4
entier
nfact
n
4
entier
si (n  1) retourner 1
retourner
retourner nn ** factoriel(n-1)
6
.
.
.
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact 
factoriel(n)
24
écrire “la factorielle de ” n “est” nfact
n
entier
4
24
nfact
.
.
.
entier
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire
écrire“la
“lafactorielle
factoriellede
de”” nn “est”
“est” nfact
nfact
n
entier
4
24
nfact
.
.
.
entier
.
.
.
entier n nfact
lire n
si (n < 0) alors écrire “entrée négative: ” n
sinon
nfact  factoriel(n)
écrire “la factorielle de ” n “est” nfact
n
entier
4
24
nfact
entier
La factorielle de 4 est 24
est 24
.
.
.
.
.
.
À retenir
• Une fonction peut s'appeler elle-même
• Sans condition d'arrêt, l'exécution de la
fonction n'aurait pas de fin.
Les adresses
Les cases mémoires ont toutes un numéro qui les distingue les
unes des autres: ce numéro est appelé adresse.
C’est par cette adresse que le processeur peut communiquer
avec la mémoire.
0
1
2
3
4
.
.
.
max
.
.
.
Les adresses et les variables
Le nom que l’on donne au cases mémoire est traduit en une adresse juste
avant l’exécution d’un programme. Cela est nécessaire afin que le
processeur sache à quelle case mémoire est associée chaque variable.
En général il est impossible de prévoir à quelle adresse sera placée une
variable. Le nom des variables est donc nécessaire.
c1 = c2;
Lire le contenu de la case 3.
Mettre ce qui a été lu dans la
case 1.
0
c1: 1
2
c2: 3
4
char
.
.
.
max
char
1324
.
.
.
Le partage de la mémoire
Sur les systèmes modernes il peut y avoir plusieurs usagers
se partageant la mémoire et chaque usager peut exécuter
plusieurs programmes simultanément.
Cela signifie que l’on n’est pas libre d’utiliser toutes les
cases mémoires comme on le veut. Une case peut être
occupée par un programme à un certain moment et libre à un
autre. Cette situation est aléatoire.
Pour cette raison, on ne mentionne jamais explicitement une
adresse dans un programme même si cela est théoriquement
possible.
Adresses valides et non valides
Exemple. Dans le pseudo-code suivant:
Lire le contenu de la case 3.
Mettre ce qui a été lu dans la case 1.
Que se passe t-il si au moment de l’exécution la case
mémoire 1 est déja utilisée par un autre programme.
La case est alors non valide et il y aura erreur à l’exécution.
C’est pour cette raison que l’on utilise des variables.
Avant l’exécution, une adresse valide est associée à chaque
variable. Seul notre programme pourra utiliser ces cases
mémoire.
Position des variables dans la mémoire
a
Sauf pour les tableaux, il n’y a
aucune garantie que les variables
occupent des cases adjacentes en
mémoire.
Exemple.
int a,b[4],c,d[3];
..
.
..
.
int
int
int
int
int
int
b[0]
b[1]
b[2]
b[3]
c
..
.
..
.
d[0]
int
int
int
d[1]
d[2]
..
.
..
.
Les adresses et les tableaux
Le nom d’un tableau correspond à l’adresse du début du tableau.
Exemple:
char tab[5];
printf(“%p\n”, tab);
4027630992
printf(“%p\n”, tab+1);
4027630993
printf(“%p\n”, tab+2);
4027630994
Note: ‘%p’ sert à afficher les adresses.
Les tableaux d’entiers
Exemple:
int tab[5];
printf(“%p\n”, tab);
4027630976
printf(“%p\n”, tab+1);
+4
4027630980
printf(“%p\n”, tab+2);
4027630984
Question: Pourquoi?
+4
L’incrémentation d’une adresse
a: 16216
L’adresse 16220
n’est pas valide
..
.
..
.
int
int
int
int
b[0]: b=24600
b[1]: b+1=24604
Incrémenter une adresse ne
veux pas dire ajouter 1, cela
veut dire aller à l’adresse
suivant la variable courante.
b[2]: b+2=24608
b[3]: b+3=24612
En général cela n’a du sens
que si on est dans un tableau.
d[0]:
int
..
.
..
.
d=54316
char
char
char
d[1]: d+1=54317
d[2]: d+2=54318
..
.
..
.
Remarque
Si Tab est un tableau alors
L’adresse de Tab[0] est Tab,
l’adresse de Tab[1] est Tab + 1,
l’adresse de Tab[2] est Tab + 2,
etc.
Cela est vrai quelque soit le type des éléments de Tab.
L’opérateur &
Il est possible de connaître, pendant l’exécution d’un
programme, l’adresse associée à une variable.
En C, cela est possible à l’aide de l’opérateur unaire &
Exemple:
char c;
int n, tab[1000];
L’adresse de c est &c
L’adresse de n est &n
L’adresse de tab[3] est &tab[3] ou tab+3
L’opérateur *
Il est aussi possible de connaître, pendant l’exécution d’un
programme, le contenu de la case mémoire située à une
adresse donnée.
En C, cela est possible à l’aide de l’opérateur unaire *
Exemple:
char c;
int tab[1000];
• Le contenu de l’adresse tab + 25 est *(tab + 25)
• *(tab + 25) est donc identique à tab[25]
• *(&c) est identique à c
• *c n’a aucun sens
Exemple
Les expressions logiques
suivantes sont vraies:
• &n == 12556
• *(12560) == 60
• *(12560) < c2
• *(&r) == 12.345
..
.
n:
c1:
c2:
r:
12556
12560
12561
12562
..
.
..
.
5000000
60
61
12.345
..
.
int
char
char
double
Résumé des opérations sur les adresses
On peut:
• Additionner une adresse et un entier
• Déterminer l’adresse d’une variable
• Déterminer le contenu d’une adresse
On ne peut pas
• Additionner deux adresses
(mais on peut soustraire deux adresses d’un même tableau)
Les pointeurs
Un pointeur est une variable pouvant contenir une adresse.
Exemple:
int *pn;
char *pc;
double *pr;
pointeur sur une valeur entière
pointeur sur un caractère
pointeur sur un double
Les pointeurs et les tableaux
En C les pointeurs sont intimement liés aux tableaux.
Exemple:
int tab[10], *p;
p=tab;
tous équivalent:
tab[3] = 70;
*(tab + 3) = 70;
p[3] = 70;
*(p + 3) = 70;
Remarque
Le nom d’un tableau est une adresse constante et non pas
un pointeur qui est une variable.
Exemple:
int tab[10], *p;
p=tab;
p = tab;
tab = p;
/* Valide */
/* Non valide */
0
tab:
p:
1
2
3
4
5
6
7
8
9
10
Quelques utilités des pointeurs
• Pour implanter le passage de paramètres par référence
• Pour implanter le passage de tableaux en paramètre
• Pour utiliser des indices négatifs au tableaux
• Fondamental en structure de données
Le pointeur NULL
Il est parfois utile d'indiquer qu'un pointeur ne contient
aucune adresse. On utilise alors le pointeur NULL
dont la valeur est 0.
Exemple:
int *p;
p=NULL;
M
if (p != NULL) printf("%d",*p);
Les tableaux à deux dimensions
Exemple: int Tab[N][M]
L’adresse de Tab[i][j] est Tab + i*M + j
..
.
Exemple avec
N=3 et
M=2
Tab[0][0]:
Tab[0][1]:
Tab[1][0]:
Tab[1][1]:
Tab[2][0]:
Tab[2][1]:
Tab+0+0 = Tab
Tab+0+1 = Tab+1
Tab+2+0 = Tab+2
Tab+2+1 = Tab+3
Tab+4+0 = Tab+4
Tab+4+1 = Tab+5 ...
..
.
..
.
int
int
int
int
int
.. int
..
..
Les tableaux à deux dimensions
Exemple: int Tab[N][M]
L’adresse de Tab[i][j] est Tab + i*M + j
Remarque: Pour calculer l’ adresse de Tab[i][j]
il n ’est pas nécessaire de connaître N
mais
il est nécessaire de connaître M
Cela explique pourquoi il est nécessaire de préciser M
dans les paramètres formels.
Ex. f(char tab[N][M]) ou f(char tab[][M])
Les constantes de type caractère
En C une constante de type caractère est un nombre entier écrit
sous la forme d'un caractère entre apostrophes, comme ‘a’.
La valeur d'une constante de type caractère est égale à la valeur du
caractère d'après le jeu de caractère de la machine (ex. ASCII).
Exemples:
‘a’
‘A’
‘B’
‘0’
vaut
vaut
vaut
vaut
97
65
66
48
Les séquences d'échappement
‘\a’
‘\b’
‘\f’
‘\n’
‘\r’
‘\t’
‘\v’
‘\\’
‘\?’
‘\’’
‘\” ’
‘\ooo’
‘\xhh’
caractère d'alerte (sonnerie, bell)
retour en arrière (backspace)
saut de page (formfeed)
fin de ligne (newline)
retour de chariot (carriage return)
tabulation horizontale
tabulation verticale
backslash
point d'interrogation
apostrophe
guillemet
nombre octal
nombre hexadécimale
Les variables de type caractère
En C, les caractères sont des entiers de 8 bits.
Pour déclarer une variable de type caractère, on procède de la façon
suivante:
char c1;
char c2 = ‘a’;
char c3 = 97;
/* c1 est une variable de type caractère */
/* c2 est une variable de type caractère
initialisée à 97 */
/* c3 et c2 contiennent la même valeur */
Exemple 1: Copier des fichiers
#include <stdio.h>
/* copie l’entrée sur la sortie; première version*/
main(){
int c;
Pourquoi un int?
c = getchar();
while (c != EOF) {
putchar(c);
c = getchar();
}
}
Exemple 1: Copier des fichiers
La valeur retourné par getchar() peut être
un des 256 caractères ASCII
OU
la valeur EOF
La fonction getchar() peut donc retourner 257 valeur possibles.
Mais un char est un entier de 8 bits et ne peut donc représenter que 256
valeurs possibles.
On doit donc utiliser plus de bits (et donc un int) pour faire la
différence entre un caractère et EOF.
Exemple 1: Copier des fichiers
Exemple:
Sur un Pentium III, un int est un entier de 32 bits et EOF
s’écrit en binaire de la façon suivante:
11111111111111111111111111111111
32 bits
Losrque l’on met EOF dans une variable de type char on ne
met que les 8 premiers bits, c’est-à-dire 11111111 = 255
Le caractère dont la valeur est 255 peut être ÿ ou encore le
caractère blanc selon le jeu de caractères utilisé.
Exemple 1: Copier des fichiers
#include <stdio.h>
/* copie l’entrée sur la sortie, seconde version */
main(){
int c;
while ((c=getchar()) != EOF)
putchar(c);
}
Les constantes de type chaîne
Un constante de type chaîne est une séquence de caractères,
éventuellement vide, placée entre guillemets.
Exemple:
“Je suis une chaîne ”
“Bonjour groupe!\n”
“Comment allez-vous\?\n”
Note: Les guillemets ne font pas partie de la chaîne.
Les chaînes de caractères
En C il n’y a pas de variable de type chaîne de caractères.
Une chaîne de caractère est un tableau de caractères se terminant par le
caractère \0 (le caractère NUL ayant la valeur 0)
Ainsi la chaîne “Bonjour groupe!” serait représentée de la façon
suivante:
0
1
2
3
4
5
6
B o
n
j
o
u
r
7
8
9
10 11
12 13 14 15 16
g
r
o
p
u
e
! \0
Déclarer un tableau de caractères
Les 4 déclarations suivantes ont le même effet:
char chaine[]={'B','o','n','j','o','u','r',' ','g','r','o','u','p','e','!','\0'};
char chaine[16]={'B','o','n','j','o','u','r',' ','g','r','o','u','p','e','!','\0'};
char chaine[]="Bonjour groupe!";
char chaine[16]="Bonjour groupe!";
0
1
2
3
4
5
6
B o
n
j
o
u
r
7
8
9
10 11
12 13 14 15
g
r
o
p
u
e
! \0
Assignation
L'opération suivante est illégale:
char chaine[16];
chaine="Bonjour groupe!";
On ne peut pas assigner de façon dynamique une chaine à un
tableau de caractères.
Types composés
En plus des types de base (entier, réels, charactères, etc) il
est possible dans la plupart des langages de programmation
de définir ses propres types.
Il s’agit en fait de rassembler une ou plusieurs variables,
qui peuvent être de types différents, et de les regrouper
sous un seul nom afin de les manipuler plus facilement.
Exemple en C
struct complexe {
double reel;
double imag;
};
/* défini un nouveau type */
struct complexe x;
/* déclare une variable de type complexe */
x.reel:
x.imag:
Où définir un nouveau type
Si on défini un nouveau type à
l’intérieur d’une fonction alors il
ne sera visible qu’à l’intérieur de
cette fonction.
Pour qu’un type composé soit
visible dans toutes les fonctions
d’un fichier, il faut le déclarer au
début du fichier, à l’extérieur de
toute fonction.
Note: La même chose s’applique à
la déclaration de variables: c’est ce
que l’on appelle les variables
globales.
#include <stdio.h>
struct complexe {
double reel;
double imag;
};
M
fonction(...){
struct complexe x;
M
}
Assignation de valeurs dans une structures
struct complexe {
double reel;
double imag;
};
struct complexe x;
x.reel = 5;
x.imag = 3;
x.reel:
x.imag:
5
3
Accéder aux membres d’une structure
struct complexe x, y, z;
z.reel:
5
3
M
5
3
M
5
z.imag:
8
x.reel:
x.imag:
x.reel = 5;
x.imag = 3;
y = x;
z.reel = x.réel;
z.imag = 8;
y.reel:
y.imag:
Comparer deux structures
x.reel:
if (x == y)
printf(“Deux structures égales”);
if (x != z)
printf(“Deux structures differentes”);
x.imag:
y.reel:
y.imag:
z.reel:
z.imag:
5
3
M
5
3
M
5
8
Remarque: La comparaison x<y n'est pas valide car elle n'a
aucun sens à priori.
Les structures et les fonctions
• On peut passer des structures en paramètre.
• On peut utiliser les structures comme valeur de retour.
• Contrairement aux tableaux, les structures sont passées
par copie.
typedef
Dans l’exemple précédent, il est laborieux d’avoir à écrire
autant de struct complexe.
Le C fournit une fonctionnalité appelée typedef servant à créer
des noms de nouveaux types de données.
Exemple:
typedef struct complexe Complexe
Complexe x, y;
Le nom Complexe devient synonyme de struct complexe
Les structures et les pointeurs
Les pointeurs de structures sont si fréquemment utilisés
qu’il existe une notation abrégée.
Exemple:
struct complexe *pc, x;
pc = &x;
pc->reel = 3; /* identique à (*pc).reel=3 */
pc->imag = 5; /* identique à (*pc).imag=5 */
Allocation dynamique de la
mémoire
Jusqu’à maintenant, toute la mémoire que nous avons utilisée
dans nos programmes devait avoir été allouée avant
l'exécution à l’aide des déclarations de variables.
Il est parfois utile d’allouer une partie de l’espace mémoire
en cours d’exécution.
Exemple
Par exemple si on a besoin de mémoriser un certains
nombre d’objets mais que ce nombre n’est pas connu avant
l’exécution du programme.
Il faut alors allouer suffisament d’espace au cas ou le
nombre d’objet est grand.
Si le nombre d’objets est petits, on gaspille inutilement de
l’espace mémoire.
Le fichier d’entête stdlib.h
Le fichier d’entête stdlib.h contient des déclarations de
fonctions traitant, entre autres, de l’allocation de la
mémoire:
- malloc
- free
- calloc
- realloc
void *malloc(size_t size)
size_t est le type d’entiers positifs retourné par l’opérateur
sizeof
malloc retourne un pointeur sur un espace mémoire réservé
à un objet de taille size, ou bien NULL si cette demande ne
peut être satisfaite. La mémoire allouée n’est pas initialisée.
Pointeurs sur void
La fonction malloc ne sait pas à quoi servira l’espace
mémoire qui lui est demandée.
Elle ne sait pas quel type d’objet utilisera cet espace.
Alors, elle retourne un pointeur générique qui peut être
converti en n’inporte quel type de pointeur: un pointeur sur
void
void free(void * p)
free libère l’espace mémoire pointé par p; elle ne fait rien si p
vaut NULL.
p doit être un pointeur sur un espace mémoire alloué par
malloc, calloc ou realloc.
void *calloc(size_t nobj, size_t size)
calloc retourne un pointeur sur un espace mémoire réservé à
un tableau de nobj objets, tous de taille size, ou bien NULL
si cette demande ne peut pas être satisfaite.
La mémoire allouée est initialisée par des zéros.
void *realloc(void *p, size_t size)
realloc change en size la taille de l’objet pointé par p.
Si la nouvelle taille est plus petite que l’ancienne, seul le
début du contenu de l’objet est conservé.
Si la nouvelle taille est plus grande, le contenu de l’objet est
conservé, et l’espace mémoire supplémentaire n’est pas
initialisé.
realloc retourne un pointeur sur un nouvel espace mémoire,
ou bien NULL si cette demande ne peut pas être satisfaite,
auquel cas *p n’est pas modifié.
Exemple
On veut lire des entiers et les mettre en mémoire.
Plutôt que de créer un tableau avant l’exécution, on utilise
calloc pendant l’exécution.
int *p, n;
scanf(“%d”, &n);
p = (int *) calloc(n, sizeof(int));
si plus tard cet espace n’est plus suffisant, alors on utilise:
p = (int *) realloc(p, 2*n);
p:
…
Exemple
Liste chaînée
struct noeud{
int valeur;
noeud *suivant;
};
struct noeud *chaine, *p;
chaine:
p:
Exemple 5
chaine = (struct noeud) malloc(sizeof(struct noeud));
valeur suivant
chaine:
p:
Exemple 5
chaine = (struct noeud) malloc(sizeof(struct noeud));
chaine -> valeur = 8;
chaine:
p:
8
Exemple 5
chaine -> suivant = (struct noeud) malloc(sizeof(struct noeud));
chaine:
p:
8
Exemple 5
p = chaine -> suivant;
chaine:
p:
8
Exemple 5
p -> valeur = 5;
chaine:
p:
8
5
Exemple 5
p -> suivant = (struct noeud) malloc(sizeof(struct noeud));
chaine:
p:
8
5
Exemple 5
p = p -> suivant;
chaine:
p:
8
5
Exemple 5
p -> valeur = 13;
p -> suivant = NULL;
chaine:
p:
8
5
13
0
Exemple 5
p = chaine;
while (p != NULL){
printf(“%d\n”, p->valeur);
p = p->suivant;
}
chaine:
p:
8
5
13
0