Transcript Rappel_C

Un survol du language C

1

Le language C

Language impératif:

Un programme est vu comme une séquence d’instructions. Chaque instruction produit une transformation de l'état du programme (mémoire).

Langage procédural:

Basé sur le concept de d'appels de fonctions (procédures, sous-routines). Chaque fonction est une séquence d'instructions et peut être appelée à partir de n'importe quelle autres fonctions (y compris elle même).

2

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 principaux types en C.

char: 8 bits short int:

longueur des mots machines (souvent 32 bits)

long float:

simple précision (souvent 32 bits)

double:

double précision (souvent 64 bits)

long double

3

Les constantes

Les constantes de type int .

décimales: 17 8 -68 octales: 021 010 -0104 (commence par un zéro) hexadécimales: 0x11 0X8 -0x44 (commence par 0x ou 0X)

Les constantes de type double.

point flottant: 1200.0 avec exposant: 12e2 mixtes: 1.2e3 3.1416 -.00067

31416E-4 -67e-5 3.1416e0 -6.7E-4 4

Déclaration des variables

La déclaration des variables se fait selon le modèle suivant:

type variable1, variable2, ... ;

Remarquez la

virgule virgule

à la fin.

séparant deux variables ainsi que le

point Exemples: int nbre;

définit nbre comme une variable entière

int a,b,c;

définit trois variables entières dont les noms respectifs sont a, b et c.

double x[10];

x est un tableau de 10 réels 5

Les opérateurs arithmétiques

+ addition soustraction * multiplication / division % opérateur de modulo }

} 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.

donne le reste de la division entière Exemple: 4 % 3 vaut 1 12 % 3 vaut 0 17 % 5 vaut 2 6

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

= = > > = < < = ! =

7

Les opérateurs logiques

Type d’opérateur ET OU NON Notation en C &&

|| !

8

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 9

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 10

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  0  FAUX VRAI 11

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.

12

Instruction conditionnelle

if

(expression)

then

instruction

else if

(expression)

then else

instruction instruction

Forme 3

13

Autre forme conditionnelle

switch

(expression) {

case

expression-constante

: case

expression-constante

: default:

instructions } instruction instruction 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.

14

Autre forme conditionnelle

switch

{

case case

(expression) expression-constante expression-constante

: :

instruction instruction

valide:

5 2*3 + 5

default:

instructions

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.

15

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.

16

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 17

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 18

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.

19

L'opérateur conditionnel “?:”

Exemple: z =

(x<5) ? 1 : 2 (x<5) ? y : z = 0 20

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); } 21

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) 22

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); 23

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. 24

Exemple: math.h

Plusieurs fonctions mathématiques courantes font partie de la bibliothèque standard. Pour les utiliser il suffit d'inclure au début du programme la ligne suivante: #include Il est alors possible d'employer des fonctions telles que:

cos(x) pow(x,y) sqrt(x) ldexp(x,n)

cosinus de x x à la puissance y racine carrée de x

x

2

n

et plusieurs autres.

25

Exemple: ldexp(x,n)

Examinons plus en détails la fonction

ldexp

.

Nous savons que cette fonction retourne la valeur

x

2

n

Considérons quelques exemples: ldexp(1 , 1) retourne ldexp(1.1, 1) retourne ldexp(1, 1.1) retourne 2 2 .

 2 1  * 1 2 1 .

1 * 2 1 2  1 * 2 1 .

1  2 .

1435 ...

Pourquoi???

26

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

doubl

e 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 * 2

1 .

1 

ldexp(1, 1.1)

ldexp(1, 1)

1 * 2

1 

2

27

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.

28

Exemple: pow(x,y)

La fonction d'exponentiation suivant: pow(x, y)  x y a le prototype

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?

29

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

30

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

.

En-tête { Nom de la fonction Type de la valeur retournée Nom des paramètres Types des paramètres Corps { Déclaration des variables Instructions 31

Les paramètres

Paramètres formels

: Ceux utilisés dans la définition.

Paramètres d'appel

: Ceux utilisés lors de l'appel.

32

Passage des paramètres

Par copie seulement:

Les paramètres d'appel sont copiés dans les paramètres formels

:

création de nouvelles variables.

Les tableaux ne sont pas passés par copie. On utilise plutôt les pointeurs: seul l'adresse du tableau est copiée.

33

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.

34

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.

35

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 36

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

37

La récursion

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.

38

Exemple

Appel à factoriel(4)

n nfact scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); int factoriel (int n){ if (n  1) return 1; return n * factoriel(n-1

)

; }

.

.

.

.

.

.

39

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); n nfact

4 .

.

.

.

.

.

40

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); n nfact

4 .

.

.

.

.

.

41

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); n nfact

4 .

.

.

.

.

.

42

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); if (n  1) return 1 return n * factoriel(n-1

);

n nfact n

4 4 .

.

.

.

.

.

43

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); if (n  return n * factoriel(n-1

);

n nfact n

4 4 .

.

.

.

.

.

44

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); if (n  1) return 1 retutn n * factoriel(n-1

);

n nfact n

4 4 .

.

.

.

.

.

45

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); if (n  1) return 1 return n * factoriel(n-1

);

if (n  1) return 1 return n * factoriel(n-1

);

n nfact n n

4 4 3 .

.

.

.

.

.

46

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); if (n  1) return 1 return n * factoriel(n-1

);

if (n  return n * factoriel(n-1

);

n nfact n n

4 4 3 .

.

.

.

.

.

47

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); if (n  1) return 1 return n * factoriel(n-1

);

if (n  1) return 1 retutn n * factoriel(n-1

);

n nfact n n

4 4 3 .

.

.

.

.

.

48

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); if (n  1) return 1 return n * factoriel(n-1

);

if (n  1) return 1 return n * factoriel(n-1

);

if (n  1) return 1 return n * factoriel(n-1

);

n nfact n n n

4 4 3 2 .

.

.

.

.

.

49

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); if (n  1) return 1 return n * factoriel(n-1

);

if (n  1) return 1 return n * factoriel(n-1

);

if (n  return n * factoriel(n-1

);

n nfact n n n

4 4 3 2 .

.

.

.

.

.

50

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); if (n  1) return 1 return n * factoriel(n-1

);

if (n  1) return 1 return n * factoriel(n-1

);

if (n  1) return 1 retutn n * factoriel(n-1

);

n nfact n n n

4 4 3 2 .

.

.

.

.

.

51

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); if (n  1) return 1 return n * factoriel(n-1

);

if (n  1) return 1 return n * factoriel(n-1

);

if (n  1) return 1 return n * factoriel(n-1

)

if (n  1) return 1 return n * factoriel(n-1

)

n nfact n n n n

4 4 3 2 1 .

.

.

.

.

.

52

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); if (n  1) return 1 return n * factoriel(n-1

)

if (n  1) return 1 return n * factoriel(n-1

)

if (n  1) return 1 return n * factoriel(n-1

)

si (n  return n * factoriel(n-1

)

n nfact n n n n

4 4 3 2 1 .

.

.

.

.

.

53

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); if (n  1) return 1 return n * factoriel(n-1

)

if (n  1) return 1 return n * factoriel(n-1

)

if (n  1) return 1 n nfact n n n

4 4 3 2 .

.

.

.

.

.

54

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); if (n  1) return 1 return n * factoriel(n-1

)

if (n  1) return 1 retourner n * factoriel(n-1

)

n nfact n n

4 4 3 .

.

.

.

.

.

55

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); if (n  1) return 1 retourner n * factoriel(n-1

)

n nfact n

4 4 .

.

.

.

.

.

56

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); n nfact

4 24 .

.

.

.

.

.

57

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); n nfact

4 24 .

.

.

.

.

.

58

int n, nfact; scanf("%d", &n); if (n < 0) printf(“entrée négative: %d\n”, n); else nfact  factoriel(n); printf(“la factorielle de n est %d\n”, nfact); n nfact

4 24

La factorielle de 4 est 24 est 24

.

.

.

.

.

.

59

À 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.

60

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.

max 0 1 2 3 4

.

.

.

.

.

.

61

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

.

.

.

max

1324 .

.

.

char char 62

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.

63

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.

64

Position des variables dans la mémoire

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]; a b[0] b[1] b[2] b[3] c

.

..

d[0] d[1] d[2]

.

..

.

..

.

..

.

..

.

..

int int int int int int int int int 65

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); printf(“%p\n”, tab+2); 4027630993 4027630994

Note:

‘%p’ sert à afficher les adresses.

66

Les tableaux d’entiers

Exemple:

int tab[5]; printf(“%p\n”, tab); printf(“%p\n”, tab+1); printf(“%p\n”, tab+2); 4027630976 +4 4027630980 4027630984 +4

Question:

Pourquoi?

67

L’incrémentation d’une adresse

L’adresse 16220 n’est pas valide Incrémenter une adresse ne veux pas dire ajouter 1, cela veut dire aller à l’adresse suivant la variable courante.

En général cela n’est utile que si on est dans un tableau.

a: 16216

.

..

b[0]: b=24600 b[1]: b+1=24604 b[2]: b+2=24608 b[3]: b+3=24612 d[0]: d=54316 d[1]: d+1=54317 d[2]: d+2=54318

.

..

.

..

.

..

int int int int int

.

..

.

..

68 char char char

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.

69

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 70

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 71

Exemple

Les expressions logiques suivantes sont vraies: • &n == 12556 • *(12560) == 60 • *(12560) < c2 • *(&r) == 12.345

n: 12556 c1: 12560 c2: 12561 r: 12562

.

..

.

..

5000000 60 61 12.345

.

..

.

..

int char char double 72

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) 73

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 74

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; 75

p:

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 */ tab : 0 1 2 3 4 5 6 7 8 9 10 76

Quelques utilités des pointeurs

• Pour implémenter le passage de paramètres par référence • Pour implémenter le passage de tableaux en paramètre • Pour utiliser des indices négatifs au tableaux • Fondamental en structure de données 77

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; M p=NULL; if (p != NULL) printf("%d",*p); 78

Les tableaux à deux dimensions

Exemple: int Tab[N][M] L’adresse de Tab[i][j] est &Tab[0][0] + i*M + j Exemple avec N=3 et M=2 Tab[0][0] = &Tab[0][0] Tab[0][1] = &Tab[0][0] + 1 Tab[1][0] = &Tab[0][0] + 2 Tab[1][1] = &Tab[0][0] + 3 Tab[2][0] = &Tab[0][0] + 4 Tab[2][1] = &Tab[0][0] + 5

.

..

.

..

.

..

.

..

int int int int int

.

..

.

..

79 int

Les tableaux à deux dimensions

Exemple: int Tab[N][M] L’adresse de Tab[i][j] est &Tab[0][0] + 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 essentiel de connaître M Cela explique pourquoi on doit préciser M dans les paramètres formels.

Ex

. f(char tab[N][M]) ou f(char tab[][M]) 80

Les constantes de type caractère

En C

une constante de type caractère est un nombre entier

sous la forme d'un caractère entre apostrophes, comme ‘a’.

écrit 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’ vaut 97 ‘A’ vaut 65 ‘B’ vaut 66 ‘0’ vaut 48 81

Les séquences d'échappement

‘\a’ ‘\b’ ‘\f’ ‘\n’ ‘\r’ caractère d'alerte (sonnerie, retour en arrière (backspace) saut de page (formfeed) fin de ligne (newline)

bell

) retour de chariot (carriage return) ‘\t’ ‘\v’ ‘\\’ ‘\?’ tabulation horizontale tabulation verticale backslash point d'interrogation ‘\’’ ‘\” ’ apostrophe guillemet ‘\o

oo

’ nombre octal ‘\x

hh

’ nombre hexadécimale 82

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 */ 83

Exemple

#include /* copie l’entrée sur la sortie; première version*/ main(){ int c; c = getchar(); Pourquoi un int?

while (c != EOF) { putchar(c); c = getchar(); } } 84

Exemple

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.

85

Exemple

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

ÿ

caractère blanc selon le jeu de caractères utilisé.

ou encore le 86

Exemple

#include /* copie l’entrée sur la sortie, seconde version */ main(){ int c; while ((c=getchar()) != EOF) putchar(c); } 87

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.

88

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 B o n j 3 4 5 o u r 6 7 8 g r 9 10 11 12 13 14 15 16 o u p e !

\0 89

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 B o n j 3 4 5 o u r 6 7 8 g r 9 10 11 12 13 14 15 o u p e !

\0 90

Affectation

L'opération suivante est

illégale:

char chaine[16]; chaine="Bonjour groupe!";

On ne peut pas affecter de façon dynamique une chaine à un tableau de caractères.

91

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.

92

Exemple en C

struct complexe { double reel; }; double imag; struct complexe x; /* défini un nouveau type */ /* déclare une variable de type complexe */ x.reel: x.imag: 93

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

.

M #include struct complexe { double reel; }; double imag;

fonction

(...){ M struct complexe x; } 94

Déclarer une structure

struct complexe { double reel; }; double imag; struct complexe x={5,3}; x.reel: x.imag: 5 3 95

Accéder aux membres d’une structure

struct complexe x, y, z; x.reel = 5; x.imag = 3; y = x; z.reel = x.réel; z.imag = 8; x.reel: x.imag: y.reel: y.imag: z.reel: z.imag: 5 M 3 5 3 M 5 8 96

Comparer deux structures

if (x == y) printf(“Deux structures égales”); if (x != z) printf(“Deux structures differentes”); x.reel: x.imag: y.reel: y.imag: z.reel: z.imag: 5 M 3 5 3 M 5 8

Remarque:

La comparaison

x

aucun sens à priori.

n'est pas valide car elle n'a 97

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.

98

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

des noms de nouveaux types de données.

servant à créer Exemple:

typedef struct complexe Complexe Complexe x, y;

Le nom

Complexe

devient synonyme de

struct complexe

99

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; pc->imag = 5; /* identique à (*pc).reel=3 */ /* identique à (*pc).imag=5 */ 100

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.

101

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’objets est grand.

Si le nombre d’objets est petits, on gaspille inutilement de l’espace mémoire.

102

Le fichier d’entête stdlib.h

Le fichier d’entête fonctions traitant, entre autres, de l’allocation de la mémoire:

stdlib.h

contient des déclarations de - malloc - free - calloc - realloc 103

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.

104

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 converti

qui peut être en n’inporte quel type de pointeur: un pointeur sur

void

105

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.

106

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.

107

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é.

108

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:

109

Exemple

Liste chaînée

struct noeud{ int valeur; }; noeud *suivant; struct noeud *chaine, *p; chaine: p: 110

Exemple 5

chaine = (struct noeud*) malloc(sizeof(struct noeud)); chaine: p: valeur suivant 111

Exemple 5

chaine = (struct noeud*) malloc(sizeof(struct noeud)); chaine -> valeur = 8; chaine: p: 8 112

Exemple 5

chaine -> suivant = (struct noeud*) malloc(sizeof(struct noeud)); chaine: p: 8 113

p = chaine -> suivant;

Exemple 5

chaine: p: 8 114

p -> valeur = 5;

Exemple 5

chaine: p: 8 5 115

Exemple 5

p -> suivant = (struct noeud*) malloc(sizeof(struct noeud)); chaine: p: 8 5 116

p = p -> suivant;

Exemple 5

chaine: p: 8 5 117

p -> valeur = 13; p -> suivant = NULL;

Exemple 5

chaine: p: 8 5 13 0 118

Exemple 5

p = chaine; while (p != NULL){ printf(“%d\n”, p->valeur); } p = p->suivant; chaine: p: 8 5 13 0 119

Les fichiers

• FILE *fopen(char *nom, char *mode) – retourne NULL si une erreur se produit.

• int fclose(FILE fp); • modes: – "r" : lecture – "w" : écriture – "a" : ajout • 3 pointeurs de fichiers particuliers: – stdin, stdout, stderr 120

Opérations sur les fichiers

• int getc(FILE *fp) • int putc(int c, FILE *fp) • int fscanf(FILE *fp, char *format, ...) • int fprintf(FILE *fp, char *format, ...) • int fseek(FILE *fp, long deplacement, int origine) – SEEK_SET : début du fichier – SEEK_CUR : position courante – SEEK_END : fin du fichier • int feof(FILE *fp) 121

Exemple Copier un fichier dans un autre

FILE * fp1=fopen("fichier1", "r"); FILE * fp2=fopen("fichier2", "a"); while (!feof(fp)) { fputc(fgetc(fp),fp2); } fclose(fp); fclose(fp2); 122