Sie sind auf Seite 1von 115

Rappels et compléments

du langage C

1
Rappels

2
Rappel

Un programme C est une fonction de nom main()


qui prend des arguments textuels en paramètre et
renvoie un code de retour d’erreur entier
Quelques possibilités
◮ Une déclaration de variable par ligne
◮ Une instruction élémentaire (pas bloc) par ligne
◮ Structure de contrôle : début de ligne
◮ Accolade ouvrante : fin de ligne
◮ Accolade fermante : seule sur une ligne
◮ Étiquette : seule sur une ligne

3
Types d’objet
◮ Types scalaires : objets élémentaires proches du
matériel
Types arithmétiques : pour faire des calculs au sens
classique
Pointeurs : référence à d’autres objets ou fonctions
◮ Types structurés
Vecteurs : répétition ordonnée d’un même type
Structures : rassemble éléments hétérogènes
Union : fait coexister plusieurs types au même
endroit mémoire
• Types de fonction : précise à la fois type valeur de
retour et type
arguments
• Types incomplets
◮ Incomplétude totale void
◮ Vecteur de taille non précisée

4
Types synonymes

Le langage C permet de créer de


nouveaux noms de types de données
grâce à la fonction typedef.
Par exemple : typedef int longueur
fait du nom longueur un synonyme de
int. La déclaration longueur l est
équivalente à int l.
! Un typedef ne crée pas de nouveaux
types mais de nouveaux noms pour
des types existants

5
Principaux codes de conversion
- printf

c char: caractère affiché en clair


(convient aussi à short ou int compte
tenue des conversions systématiques)
d int (convient aussi à char)
u unsigned int (convient aussi à
unsigned char ou unsigned short)
ld long
lu unsigned long
f double ou float (compte tenu des
conversions systématiques float-
>double)écrit en notation décimale avec
six chiffres après la virgule
e double ou float écrit en notation
exponentielle
s chaîne de caractères

6
Principaux codes de conversion
- scanf

c char
d int
u unsigned int
hd short int
hu unsigned short int
ld long int
lu unsigned long int
f, e float écrit indifféremment dans l’une
des deux notations: décimale
(éventuellement sans point, c’est-à-dire
comme un entier) ou exponentielle(avec la
lettre e ou E)
lf , le double avec la même présentation
que ci-dessus
s chaîne de caractères

7
Les classes d’allocation
de mémoire

8
Déclaration et définition

Une déclaration : une association de type avec un nom de


variable ou de fonction (dans ce cas la déclaration contient
aussi le type des arguments de la fonction, les noms des
arguments peuvent être omis),
Une définition de variable est l’association d’un identifiant à
un type et la spécification d’une classe mémoire.
Une définition c’est une déclaration et si c’est une variable,
une demande d’allocation d’espace pour cette variable, si
c’est une fonction la définition du corps de fonction contenant
les instructions associées à cette fonction.
De manière simpliste, une déclaration fait référence à une
définition dans une autre partie du programme.
Elle peut se traduire par “je sais qu’il existe une variable
ayant ce type et portant ce nom”.

9
Variables et déclarations

Les déclarations des variables peuvent


être :
 En dehors de toute fonction, il s’agit
alors de variables globales
 À l’intérieur d’un bloc, il s’agit de
variables locales
 Dans l’entête d’une fonction, il s’agit
alors de paramètres ou d’arguments
formels
 soit dans les parenthèses (fonction
définie avec un prototype)
 soit entre le nom de la fonction et
la première accolade (sans prototype)

10
Avec prototype – sans
prototype
 Exemple avec prototype ;
 long i = 1;
 int fonction (int j) {
 short k;
 …
 }
 Exemple sans prototype :
 long i =1;
 int fonction(j)
 int j;
 {
 short k;
 …
 }
 Ci-dessus i est une variable globale, k une
variable locale et j un argument formel
(paramètre)de fonction

11
Règle fondamentale de visibité

Toute variable ou fonction doit être


déclarée avant d’être utilisée.
(Par convention, dans le cas où une
fonction n’est pas connue, le compilateur
considère qu’elle retourne une valeur de
type int et il essaye d’inférer le type des
paramètres à partir de l’appel (les appels
postérieurs devront se conformer à ce
premier appel).
La fonction peut aussi être définie plus
loin dans le fichier.)

12
Exemple
int a; /*variable globale*/
void f1(){
long a; ; /*variable locale à f1*/
a = 50; /*modification locale à f1*/
}
void f2(void){
a= 10; /*modification globale*/
}
void f3(float a ){/*paramètre de f3*/
a = 10; /*modification paramètre local à f3*/
}
les règles de visibilité de noms nous permettent de dire
:
1. la fonction f3 peut appeler les fonctions f3, f2, et f1 ;
2. la fonction f2 peut appeler la fonction f2, et f1 ;
3. la fonction f1 ne peut que s’appeler elle-même ;

13
Les classes d’allocation
mémoire

La classe mémoire sert à expliciter la


visibilité d’une variable et son implantation en
machine.
Les classes mémoire sont :
global : durée de vie = celle du programme
auto : dans la pile
static : local au fichier ou garde une vie
après la mort
extern : définie ailleurs
register : essaye de garder en registre
(très rapide mais rare, donc chère...)

14
Les classes d’allocation
mémoire
Les classes mémoire sont :
global cette classe est celle des variables définies en
dehors d’une fonction. Ces variables sont accessibles à
toutes les fonctions. La durée de vie des variables de type
global est la même que celle du programme en cours
d’exécution.
local ou auto : cette classe comprend l’ensemble des
variables définies dans un bloc. C’est le cas de toute
variable définie à l’intérieur d’une fonction. L’espace
mémoire réservé pour ce type de variable est alloué dans
la pile d’exécution. C’est pourquoi elles sont appelées
aussi auto c.a.d automatique car l’espace mémoire
associé est créé lors de l’entrée dans la fonction et il
est détruit lors de la sortie de la fonction. La durée
de vie des variables de type local est celle de la
fonction dans laquelle elles sont définies.

15
Les classes d’allocation
mémoire

static ce qualificatif modifie la visibilité de la


variable, ou son implantation :
– dans le cas d’une variable locale, il modifie
son implantation en lui attribuant une partie de
l’espace de mémoire globale. Une variable
locale de classe statique a un nom local
mais a une durée de vie égale à celle du
programme en cours d’exécution.
– dans le cas d’une variable globale, ce
prédicat restreint la visibilité du nom de
la variable à l’unité de compilation. Une
variable globale de type statique ne peut pas
être utilisée par un autre fichier source
participant au même programme par une
référence avec le mot réservé extern (voir
point suivant).

16
Exemple sur l’utilisation de
static

int a; /*variable globale*/


void f1(void){
static long a = 1;/*variable statique locale à
f1*/
a + = 10; /*modification locale à f1*/
}
void f2(void){
for(a = 1;a<10;a++)/*modification globale*/
f1();
}
main()
{
f2();
}

17
Exemple sur l’utilisation de
static : commentaire

La durée de vie d’une variable locale statique est la même


que celle des variables globales. A chaque appel, la
fonction retrouve la valeur de la variable locale statique
qu’elle a modifiée lors des appels précédents.
L’initialisation d’une variable statique interne à une
fonction est faite à la compilation, et non à l’entrée
dans la fonction.
Lorsque le programme s’exécute, la variable entière a
global prend successivement les valeurs : 1 2 3 4 5 6 7 8
9
la variable a locale à f1 prend successivement les valeurs
: 1 11 21 31 41 51 61 71 81 91.
Nous pouvons donc avoir une variable interne à une
fonction qui compte le nombre d’appels à cette fonction.

18
Les classes d’allocation
mémoire
extern ce qualificatif permet de spécifier que la ligne
correspondante n’est pas une tentative de définition mais
une déclaration . Il précise les variables globales
(noms et types) ou fonctions qui sont définies dans
un autre fichier source et qui sont utilisées dans ce
fichier source.

register ce qualificatif permet d’informer le compilateur


que les variables locales définies dans le reste de la ligne
sont utilisées souvent. Le prédicat demande de les
mettre si possible dans des registres disponibles du
processeur de manière à optimiser le temps
d’exécution. Le nombre de registres disponibles pour de
telles demandes est variable selon les machines. Il est de
toute façon limité (4 pour les données, 4 pour les pointeurs
sur un 680X0). Seules les variables locales peuvent être
qualifiées register.

19
Qualificatifs d’aide au
compilateur
Une définition de variable qualifiée du mot const informe le compilateur
que cette variable est considérée comme constante et ne doit pas être
utilisée dans la partie gauche d’une affectation. Ce type de définition
autorise le compilateur à placer la variable dans une zone
mémoire accessible en lecture seulement à l’exécution.
Le qualificatif volatile informe le compilateur que la variable
correspondante est placée dans une zone de mémoire qui peut
être modifiée par d’autres parties du système que le programme
lui-même. Ceci supprime les optimisations faites par le compilateur lors
de l’accès en lecture de la variable. Ce type de variable sert à décrire des
zones de mémoire partagées entre plusieurs programmes ou encore des
espaces mémoires correspondant à des zones d’entrées-sorties de la
machine.
Les deux qualificatifs peuvent être utilisés sur la même variable, spécifiant
que la variable n’est pas modifiée par la partie correspondante du
programme mais par l’extérieur.

20
Les pointeurs

21
Introduction

Une variable est destinée à contenir une valeur


du type avec lequel elle est déclarée.
Physiquement cette valeur se situe en
mémoire.

Prenons comme exemple un entier nommé x :


int x;
/*Réserve un emplacement en mémoire pour un
entier.*/
x = 10;
/*Ecrit la valeur 10 dans l'emplacement
réservé. */

22
Illustration

Pour obtenir l'adresse d'une variable on


fait précéder son nom par l'opérateur '&'
(adresse de) :
Pour afficher l’adresse de x, on exécute
l’instruction:
printf("%p",&x); Ce qui dans le cas du
schéma ci-dessus, afficherait 62.

23
Pointeur - définition

Variable spéciale destinée à contenir


une adresse mémoire d’un objet. c'est
à dire une valeur identifiant un
emplacement en mémoire. On la déclare
ainsi :

type *identifiant;
définit identifiant comme variable de
type pointeur vers type

24
Illustration

int *px; // Réserve un emplacement pour


stocker une adresse mémoire.
La déclaration d’un pointeur alloue de
l’espace mémoire au pointeur mais pas
pour l’objet pointé.
Il est conseillé d’initialiser un pointeur
avec la valeur NULL avant son utilisation
effective (constante prédéfinie qui vaut
0) ce qui, par convention, indique que le
pointeur ne pointe sur rien.

25
Illustration

px = &x; // Ecrit l'adresse de x


dans cet emplacement.
printf("%d",*px); //Affiche la valeur
de x par pointeur déréférencé (10
dans le cas du schéma).

26
Quelques remarques utiles
Pour différencier un pointeur d'une variable ordinaire, on
fait précéder son nom de l’opérateur de référencement
(d’indirection) '*' lors de sa déclaration.

Déclarer une variable de type pointeur alloue la place


pour le pointeur, ne déclare pas objet pointé ni alloue
place mémoire pour objet pointé ; erreur de débutant

Profondeur arbitraire : on peut aussi manipuler adresse


de variable de type pointeur
char ␣*** v;
v est une variable de type « pointeur vers une variable
de type pointeur vers une variable de type pointeur vers
une variable de type char »

27
Quelques remarques utiles

Même si dans un système donné un


pointeur a toujours la même taille
(4 octets pour un système à
adressage sur 32 bits), le langage
impose de leur donner un type.

Si on ne sait pas à l'avance sur quel


type de données le pointeur va
pointer, on peut lui donner le type
void. (diapo. pointeur générique)

28
TABLEAUX ET POINTEURS

29
TABLEAUX ET POINTEURS

Le nom d'un tableau est considéré


comme un pointeur (constant) sur son
premier élément (Dans la réalité le nom
d'un tableau référence un emplacement
en mémoire).

30
Tableaux - Rappel

L’initialisation d’un tableau se fait en


mettant une accolade ouvrante, la liste des
valeurs servant à initialiser le tableau, et
une accolade fermante.
6
int Tab[10]={6,8,4,3,9,1,5,4,3,2};
8
*Tab ⇔ Tab[0]
4
tab
3
9
1
5
4
3
2
31
Arithmétique d’adresse et
tableaux

il est possible d’additionner ou de


soustraire un entier (n) à une adresse.
l’opération suppose que l’adresse de
départ et l’adresse résultante sont les
adresses de deux variables contenues
dans le même tableau.
tab + n est l’adresse du nième entier à
partir du début du tableau( n valant
entre 0 et 9).
printf("%d",*(Tab+1)); // Affiche 8 le
deuxième élément du tableau

32
Arithmétique d’adresse et
tableaux

L’addition ou la soustraction d’un entier est


possible pour toute adresse dans un tableau.
Ainsi, &tab[3] + 2 donne la même adresse que
&tab[5]. De même, &tab[3] - 2 donne la même
adresse que &tab[1].
Il est aussi possible de réaliser une soustraction
entre les adresses de deux variables
appartenant à un même tableau. Cette
opération retourne une valeur qui correspond
au nombre d’objets entre les deux adresses.
Ainsi, &tab[5] - &tab[3] doit donner la valeur 2 .
De même, &tab[3] - &tab[5] retourne la valeur
-2.

33
TABLEAUX ET POINTEURS

tab 6
int *pTab; 8
ptab est un pointeur vers un entier
4
pTab = Tab; 3
ptab 9
1
5
ptab prend l’adresse du premier 4
élément de tab
NB. Les noms de tableaux étant des 3
constantes, il n’est pas possible de les
affecter 2

34
TABLEAUX ET POINTEURS

7
tab
8
pTab[0]++;
4
3
ptab 9
1
5
4
3
2

35
TABLEAUX ET POINTEURS

pTab++; tab 7
ptab 8
4
3
9
le pointeur contient maintenant
l'adresse du deuxième élément 1
du tableau
!on ne peut pas incrémenter le 5
pointeur Tab car c'est un 4
pointeur constant.
3
2

36
Exemple de manipulation

int t[10];

int i;
for (i = 0 ; i < 10 ; i++)
*(t+i) = 1;

int i; int *p;


for (p = t, i = 0 ; i < 10 ; i++, p++)
*p = 1;

37
Tableaux multidimensionnels

Un tableau à deux dimensions est, par


définition, un tableau de tableaux.
Il s'agit donc en fait d'un pointeur vers un
pointeur.

considérons le tableau : double mat[NL]


[NC];
mat pointe vers un objet lui-même de type
pointeur d'entier.
mat est l'adresse du premier élément du
tableau = &mat[0][0].
mat[i] est l’adresse du premier élément de
la ligne d'indice i. mat[i] = &mat[i][0] =
mat +i .

38
Adresses des sous-tableaux et Noms des
différents éléments du tableau mat

mat mat[0][0]mat[0][1]mat[0][2]mat[0][3]mat[0][4]
mat[0]
mat[1][0]mat[1][1]mat[1][2]mat[1][3]mat[1][4]
mat+1
mat+2

mat+3
Mat[4][0]
mat+4

39
Allocation dynamique de la
mémoire

40
Allocation dynamique de la
mémoire

La bibliothèque stdlib fournit des fonctions


qui permettent de réserver et de libérer de
manière dynamique (à l’exécution) la
mémoire.
La fonction qui permet de réserver de la
mémoire est la fonction malloc. Sa
syntaxe est :
Void * malloc(unsigned int taille)
malloc réserve une zone de taille octets en
mémoire et renvoie l’adresse du début de
la zone sous forme d’un pointeur non typé
(ou NULL si l’opération n’est pas possible)

41
Allocation dynamique de la
mémoire

En pratique, on a besoin du type d’un


pointeur pour pouvoir l’utiliser. On souhaite
d’autre part ne pas avoir à préciser la taille de
la mémoire en octets surtout s’il s’agit de
structures. L’usage consiste donc, pour
réserver de la mémoire pour une variable
d’un type donné à:
Déclarer un pointeur du type voulu

Utiliser la fonction sizeof ()qui renvoie la


taille en octets du type passé en paramètre
Forcer malloc à renvoyer un pointeur du type
désiré

42
Illustration

Soit : int *p; En mémoire ???

Pour réserver la mémoire pour un


pointeur p vers un entier:
p = (int*) malloc(sizeof(int))
FFF
En mémoire
FFF

Schématiquement:
p

43
Exemple

int *p = malloc(10*sizeof(int));
if(p)
{ for (i=0;i<10;i++)
{ p[i] = i;
printf("%d",p[i]);
}
free(p);
}
Dans cet exemple l'instruction malloc nous retourne
un pointeur sur une zone mémoire de la taille de 10
entiers. Ce qui correspond à un tableau de 10
entiers.
La fonction free() permet de libérer la mémoire
précédemment réservée. Sa syntaxe est : void
free(void* p)

44
Fonction calloc

La fonction calloc de la librairie stdlib.h a


le même rôle que malloc mais en plus elle
initialise l’objet pointé *p à zéro. L'emploi
de calloc est simplement plus rapide
Sa syntaxe :
calloc(unsigned int nb,unsigned int taille)

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

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

45
Pointeur vers pointeur

 On déclare un pointeur qui


pointe sur un objet de type type
* de la même manière qu'un
pointeur, c'est-à-dire type
**nom_du_pointeur;
 Exactement comme pour les
tableaux à une dimension, les
pointeurs de pointeurs ont de
nombreux avantages sur les
tableaux multidimensionnels (la
première c’est qu’ils sont
variables).

46
Pointeurs et tableaux
multidimensionnels

void pointeurmatrice(int nl,int nc) {


double **pt;
int i,j;
/*initialisation des pointeurs*/
pt = (double **)malloc(nl * sizeof(double *);
for (i=0;i<nl;i++)
pt[i] = (double *)malloc(nc * sizeof(double);
/*utilisation de la matrice pt. Par exemple*/
for(i=0;i<nl;i++)
for(j=0;j<nc;j++)
pt[i][j] = 0;
/* libération (Indispensable dans le cas d’une
variable locale)*/
for (i=0;i<nl;i++)
free(pt[i];
free(pt);
}

47
Schéma illustratif de la
mémoire

pt

*pt *(pt+1) *(pt+2)

Pt[0]
=*pt Pt[1] =*(pt+1)
Pt[0][0]

Pt[1][0]
Pt[0][1]

Pt[1][1]

Pt[1][2]

48
Commentaire
 La première allocation dynamique réserve
pour l'objet pointé par pt l'espace mémoire pour
nl pointeurs vers des réels.
 Ces nl pointeurs correspondent aux lignes .
 Les allocations dynamiques suivantes
réservent pour chaque pointeur pt[i] l'espace
mémoire nécessaire pour stocker nc réels.
 On peut choisir des tailles différentes pour
chacune des lignes pt[i].
for (i = 0; i < nl; i++) pt[i] = (int*)malloc((i
+ 1) * sizeof(double));
 pour initialiser tous les éléments du tableau à
zéro, on met:
pt[i] = (double*)calloc(nc, sizeof(double));

49
Schéma illustratif : nl = 6 nc =
5

50
Pointeurs et tableaux
multidimensionnels

#include < stdio.h>


#include < stdlib.h>
main()
{ int nl, nc; double **pt;
pt = (double**)malloc(nl * sizeof(double*));
for (i = 0; i < nl; i++)
pt[i]= (double*)calloc(nc ,
sizeof(double));
for (i = 0; i < nl; i++)
free(pt[i]);
free(pt);
}

51
LES STRUCTURES

52
LES STRUCTURES
Le langage C permet de définir des modèles de structures
comme les autres langages évolués.
struct art // déclaration de structure
{ int numero;
int qte;
double prix;
};

struct art art1; art2;// variables

Pour simplifier, on peut nommer notre type article :

Typedef struct
{ int numero;
int qte;
double prix;
}article;

article art1; art2;// variables

53
Opérations sur structures

 Les opérations permises sur une structure


sont l’affectation (en considérant la
structure comme un tout), la récupération
de son adresse (opérateur &) et l’accès à
ses membres.
 On peut initialiser une structure au
moment de sa déclaration, par exemple :
article art1 ={15,50,12.5}
 Quelques opérations :
art1.numero = 15;
printf ("%e", art1 .prix);
art2 = art1;

54
Pointeurs et structures

Les objets de type structure en C sont des


Lvalues. Ils possèdent une adresse, c’est
l'adresse du premier élément du premier
champ de la structure.

L’allocation dynamique de la mémoire pour


un objet de type struct nom {. . .}; se fait
de la même manière que pour un pointeur
quelconque:

struct nom *p;

p = (struct nom *)malloc(sizeof(struct


nom));

55
Pointeurs et structures

Si p est un pointeur sur une structure, on peut


accéder à un champ de la structure pointé par
l'expression : (*p).champ
Exemple :
article *p;
Pour accéder à prix : (*p).prix
L'usage de parenthèses est ici indispensable car
l'opérateur d'indirection * a une priorité plus élevée
que l'opérateur de membre de structure.

La notation (*p).champ peut être simplifiée grâce à


l'opérateur pointeur de membre de structure, noté
->. Cette expression est équivalente à p->champ

Exemple :
(*p).prix p->prix

56
Exemple: tableau et structures

#include <stdio.h>
#include <stdlib.h>
Typedef struct
{ int numero, qte;
double prix;
}article;
main()
{ int n, i; article* tab;
printf("nombre d‘articles = ");
scanf("%d",&n);
tab = (article *)malloc(n * sizeof(article));
/* saisie des articles dans un tableau*/
for (i =0 ; i < n; i++) {

57
Exemple: tableau et structures

printf("\n saisie de l’article %d\n",i);


tab[i].numero = i;
Printf("quantité = "); scanf("%d",&tab[i].qte);
printf("\n prix = ");
scanf("%lf",&tab[i].prix);
}
/*affichage d’un article de numéro donné*/
printf("\n Entrez un numero ");
scanf("%d",&i); printf("\n article numéro %d:",i);
printf("\n prix unitaire = %.2f",tab[i].prix);
printf("\n quantité = %d\n",tab[i].qte);
free(tab);
}

58
Unions

Les unions permettent l’utilisation d’un


même espace mémoire par des données de
types différents à des moments différents.
12.1 Définition
La définition d’une union respecte une
syntaxe proche de celle d’une structure
union nom_de_union {
type1 nom_champ1 ;
type2 nom_champ2 ;
type3 nom_champ3 ;
type4 nom_champ4 ;
...
typeN nom_champ_N ;
} variables ;

59
Exemple

Dans ce qui suit; on définit deux


variables z1 et z2 construites sur le
modèle d’une zone qui peut contenir soit
un entier, soit un entier long, soit un
nombre avec point décimal, soit un
nombre avec point décimal long.
union zone {
int entier;
long entlong;
float flottant;
double flotlong;
} z1,z2;

60
Exemple

Lorsque l’on définit une variable


correspondant à un type union, le
compilateur réserve l’espace
mémoire nécessaire pour stocker le
plus grand des champs appartenant à
l’union. Dans notre exemple, le
compilateur réserve l’espace
mémoire nécessaire pour stocker un
double pour chacune des variables z1
et z2.

61
Accès aux champs

La syntaxe d’accès aux champs d’une union est


identique à celle pour accéder aux champs d’une
structure.
Une union ne contient cependant qu’une donnée à la
fois et l’accès à un champ de l’union pour obtenir une
valeur, doit être fait dans le type qui a été utilisé
pour stocker la valeur.
Les unions ont étés inventées pour utiliser un même
espace mémoire avec des types de données
différents dans des étapes différentes d’un même
programme.
Elles sont, par exemple, utilisées dans les
compilateurs.
Les différents “champs” d’une union sont à la même
adresse physique. Ainsi, les égalités suivantes sont
vraies :
&z1.entier == (int*)&z1.entlong
&z1.entier == (int*)&z1.flottant
&z1.entier == (int*)&z1.flotlong

62
Pointeurs et fonctions

63
Pointeurs comme paramètres de
fonctions

Une autre utilité des pointeurs est de


permettre à des fonctions d'accéder aux
données elles même et non à des copies.
void echange(int *a, int *b)
{ int t;
t = *a; *a = *b; *b = t;
}
int x = 10, y = 11;
printf("x = %d y = %d\n",x,y);//10 11
echange(&x, &y);
printf("x = %d y = %d\n",x,y); //11 10

64
Illustration

À l’appel
x 10 y 11

a b

Début d’exécution

x 10 y 11
*a = *b;
2
a b

t = *a; 1 10 t

65
Illustration

x 11 y 11

a b

3 *b = t;
t 10

x 11 y 10

a b

10 t

66
passage de paramètres sous
forme de pointeur tableau

int maxtab(int *tab, int n){


int i, nmax = 0;
for (i = 0; i < n; i++)
if (tab[i] > nmax) nmax=tab[i];
return nmax;
}
NB.la taille du tableau est fournit à la fonction.
Celle-ci ne devant pas accéder en dehors des
limites du tableau, elle doit pouvoir en
contrôler le traitement .
int maxtab(int *tab, int n)⇔
int maxtab(int tab[], int n)⇔
int maxtab(int tab[10], int n])

67
TRANSMISSION DE LA VALEUR
D’UNE STRUCTURE

#include <stdio.h>
Struct enreg { int a;double b;}
int main (void)
{ struct enreg x;
void fct (struct enreg y);
x.a = 1; x.b = 12.5;
printf(" \navant appel fct : %d %e",x.a,x.b);
fct(x);
printf(" \nretour à main: %d %e: "x.a,x.b);
Return 0;
}
void fct(struct enreg s){
s.a = 0; s.b = 1;
Printf(" \ndans fct %d %e ", s.a, s.b);
}

68
TRANSMISSION DE L’ADRESSE
D’UNE STRUCTURE

#include <stdio.h>
Struct enreg { int a;double b;}
int main (void)
{ struct enreg x;
void fct (struct enreg *);
x.a = 1; x.b = 12.5;
printf(" \navant appel fct : %d %e",x.a,x.b);
fct(&x);
printf(" \nau retour à main: %d %e: "x.a,x.b);
return 0;
}
void fct(struct enreg *ps){
s->a = 0; s->b = 1;
Printf(" \ndans fct %d %e ", ps->a, ps->b);
}

69
Exécution

Transmission par valeur


Avant appel fct 1 1.25000e+01
Dans fct 0 1.00000e+00
Au retour dans main 1 1.2500e+01

Transmission par adresse


Avant appel fct 1 1.25000e+01
Dans fct 0 1.00000e+00
Au retour dans main 0 1.0000e+00

70
Fonction qui renvoie un objet
de type structure.

Soit : struct complexe


{double reel,image;}
.
Struct complexe conjugue(struct
complexe x};
{ struct complexe y;
y.reel = x.reel;
y.image = -x.image;
return y;
}

Struct complexe x,z;


z = complexe(x);

71
Pointeurs génériques

72
Pointeurs génériques

 C ne permet les affectations entre


pointeurs que si ceux-ci sont de même
type. Pour écrire des fonctions
indépendantes d’un type particulier (par
exemple une fonction d’échange) le
mécanisme de typage peut être contourné
utilisant des pointeurs génériques.

 Pour créer un pointeur générique, il faut le


déclarer de type void*

73
La fonction memcpy

 L’utilisation de pointeurs génériques ne


permet pas d’utiliser l’opérateur de
déréférencement * et donc d’accéder au
contenu de la variable pointée. Ceci pose
un problème si l’on veut copier des
données d’une variable désignée par un
pointeur générique vers une autre.

 La librairie string.h fournit une fonction qui


permet de résoudre ce problème :
memcpy():
 Syntaxe: void *memcpy(void *pa, void *pb,
unsigned int n)
 memcpy copie n octets de l’adresse pb
vers l’adresse pa et retourne pa.

74
Applications

 Prenons l’exemple de la fonction


echange. Si l’on n’utilise pas de
pointeurs génériques, il faut écrire
autant de versions de cette fonction
que de types de variables à
permuter.
 Pour pouvoir utiliser la fonction
quelque soit le type des données à
manipuler il est nécessaire de
passer en paramètre la taille des
données à échanger qui dépend de
leur types.

75
Fonction echange version
générique

void echange (void* pa, void* pb, int


taille)
{
void* pc;
pc = malloc(taille);
memcpy(pc,pa,taille);
memcpy(pa,pb,taille);
memcpy(pb,pc,taille);
free(pc);
}

76
Utilisation

Ainsi en déclarant :
int i =2, j=3; float x=3.4, y=6.5;
Struct complexe cx={1,2},cy={3,4};
L’appel peut être effectué pour
différents types de données
echange(&i,&j,sizeof(i));
echange(&x,&y,sizeof(x));
echange(&cx,&cy,sizeof(cx);

! Sizeof() renvoie la taille du type de


la variable passée en paramètre.

77
Les pointeurs de fonction

78
Les pointeurs de fonction

 La valeur renvoyée par le nom (seul)


d'une fonction est l'adresse de son code
en mémoire. les pointeurs de fonction
sont des pointeurs qui, au lieu de pointer
vers des données pointent vers du code
exécutable.

 La déclaration d’un pointeur de fonction


ressemble à celle d’une fonction sauf
que l’identificateur de la fonction est
remplacé par l’identificateur du pointeur
précédé d’un astérisque (*) le tout mis
entre parenthèses.

79
Pointeurs de fonctions

 int (* p_fonction) (int x, int y); déclare un


pointeur vers une fonction de type entier
nécessitant deux paramètres de type
entier.

 Au pointeur ainsi déclaré doit ensuite être


affectée l'adresse d'une fonction ayant la
même signature c'est-à-dire une fonction
recevant en paramètre deux entiers et
retournant un entier.

 Le pointeur s'utilise alors avec la même


syntaxe que la fonction.

80
Exemples d’utilisation

int(*pmax)(int*, int);
pmax = max;
pmax prend comme valeur l’adresse du
code exécutable de la fonction max

pmax(Tab,10) max(tab,10)

L’intérêt des pointeurs de fonction


est, surtout, de faire passer en
paramètre d’une fonction une autre
fonction.
( diapo qui suit )

81
Recherche du minimum d’une
fonction y =f(x) entre deux
bornes- sans pointeur de fonction

#include <stdio.h>
float carre(float x)
{ return x*x; }
float parabole(float x)
{ return x∗x - 4 ∗ x + 2; }
float min_carre(float a, float b)
{ int i; float pas; float vmin; float valeur;
pas =(b-a)/100;
vmin = carre(a);
for (i =1 ; i < 101 ; i++){
valeur = carre(a+i*pas);
if (vmin > valeur)
vmin = valeur;
}
return vmin;
}

82
Recherche du minimum d’une
fonction y =f(x) entre deux
bornes- sans pointeur de fonction
(suite)

float min_parabole(float a, float b)


{ int i; float pas; float vmin; float valeur;
pas =(b-a)/100;
vmin = parabole(a);
for (i =1; i<101; i++){
valeur=parabole(a+i*pas);
if (vmin > valeur)
vmin = valeur;
}
return vmin;
}
int main( )
{ printf("%f\n",min_carre(-3.0, 3.0);
printf("%f\n",min_parabole(-3.0, 3.0);
return 0;
}

83
Exemple : Recherche du minimum
d’une fonction y =f(x) entre deux
bornes

#include <stdio.h>
float carre(float x)
{ return x*x; }
float parabole(float x)
{ return x∗x - 4 ∗ x + 2; }
float min_fct(float a, float b, float
(* pF) ( float x))
{ int i; float pas; float vmin; float
valeur;
pas =(b-a)/100;
vmin = pF(a);

84
Recherche du minimum d’une
fonction y =f(x) entre deux bornes
(suite)

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


{ valeur=pF(a+i*pas);
if (vmin > valeur)
vmin = valeur;
}
return vmin;
}
int main( )
{ printf("%f\n",min_fct(-3.0, 3.0, carre);
printf("%f\n",min_fct(-3.0, 3.0,
parabole);
return 0;
}

85
Les fichiers

86
Les fichiers

Un fichier désigne un ensemble


d’informations situé sur une mémoire de
masse telle que disque et disquette.
En C, un fichier n’est pas structuré à
l’avance pour ce qui est de son contenu.
Ses données sont simplement rangés sous
la forme d’une suite continue de
caractères (octets).
Les informations stockées dans un fichier
peuvent être de différents types.

87
Accès séquentiel - Accès direct

On distingue généralement deux


techniques de gestion de fichiers :
L’accès séquentiel : cette technique
consiste à traiter les informations dans
l’ordre où elles sont stockées dans le
fichier.
L’accès direct suppose la possibilité
de se placer directement sur
l’information souhaitée sans avoir à
parcourir toutes celles qui la précèdent.
En C, on peut utiliser les deux modes
d’accès sur un même fichier.

88
Fichiers binaires - Fichiers texte

Il existe deux façons de coder les


informations stockés dans un fichier:
En binaire : les informations sont codés
sous forme brut, sans aucune
transformation. Les fichiers concernés
sont dits binaires.
En ASCII : Les fonctions de lecture et
d’écriture accompagnent le transfert
d’information d’opérations de
formatage analogues à celles que
réalisent printf ou scanf. Les fichiers
concernés par ces opérations sont
appelés des fichiers texte.
Dans les fichiers texte, chaque octet
représente un caractère.

89
Déclaration
Lorsqu’un programme doit lire ou écrire des données dans
un fichier, ces données sont acheminées à leur destination
en passant par un tampon (buffer). Ce tampon est une
zone de mémoire RAM dans laquelle on range une quantité
(assez importante ) de données.
L’emplacement mémoire du tampon d’E/S d’un fichier est
donné par une variable structurée de type FILE.
Le type FILE est défini dans le header stdio.h. Ses champs
contiennent l’adresse du tampon ainsi que d’autres
informations sur le fichier concerné.
La déclaration d’un fichier se fait par l’instruction suivante:
FILE *f;
f est défini en tant que pointeur sur un objet de type FILE.
Cette déclaration réserve un emplacement pour le pointeur
f. f est une variable logique qui sera associée dans le
programme au fichier physique en mémoire.

90
Instruction d’ouverture d’un
fichier

La fonction suivante crée la structure de type


FILE ( le fichier )et en fournit l’adresse en
résultat. Si le fichier existe déjà, la fonction
permet l’ouverture du fichier.
FILE *fopen(char *nom, char *mode);
L’argument nom contiendra le nom du fichier
physique concerné qui peut comporter le
chemin du fichier.
L’argument mode indique la nature des
opérations que le programme devra exécuter
après ouverture du fichier.

Exemple :
FILE *f;
f = fopen(" exemple.dat ", " wb ");

91
Le valeurs possibles de mode pour
les fichiers textes :

r : (read) lecture seule. Le fichier doit exister. Le pointeur


de position est positionné en lecture au début du fichier. Le
contenu du fichier n’est pas détruit.
w : (write) écriture seule(le fichier peut exister ou non. S’il
existe, son contenu est détruit. Le pointeur est positionné
en écriture au début du fichier )
w+ : lecture /écriture (destruction ancienne version )
r+ : lecture/écriture d’un fichier existant
a : (append) écriture seule (sans destruction du contenu du
fichier; le pointeur est positionné en fin de fichier)
a+ : lecture/écriture d’un fichier existant, le pointeur est
positionné à la fin du fichier.

92
Mode pour les fichiers binaires:

rb : lecture seule (read binaire)


wb : écriture seule (destruction
ancienne version ) (write binaire)
wb+ : lecture /écriture (destruction
ancienne version )
rb+ : lecture/écriture d’un fichier
existant
ab : écriture seule (sans destruction du
contenu du fichier; le pointeur est
positionné en fin de fichier)
ab+ : lecture/écriture d’un fichier
existant, le pointeur est positionné à la
fin du fichier.

93
Opérations de lecture et
d’écriture

Pour lire ou écrire des données dans un


fichier, on dispose de fonctions analogues aux
opérations d’entrée sorties habituelles. Outre
la lecture et écriture par caractère et avec
formatage, on dispose de fonctions de
transfert de blocs quelconques de données.
La position à laquelle on lit ou on écrit dans
un fichier est donnée par un pointeur
spécifique nommé seek pointer. Ce pointeur
est géré par le système d’exploitation et il
signale la position de traitement courante
dans le fichier c’est-à-dire l’emplacement de
l’octet auquel va s’effectuer la prochaine
opération.

94
Lecture et écriture en mode
caractère

int fgetc(FILE *fichier); lit un caractère et


retourne un entier. retourne EOF( constante
prédéfinie) si erreur ou fin de fichier.

int getc(FILE *fichier); elle a le même rôle que


la première.

int fputc(int entier, FILE *fichier); écrit la


valeur entier à la position courante du pointeur.
Le type de entier est converti de int en unsigned
char. La valeur de retour n’est autre que le
caractère lu ou EOF en cas d’erreur .

int putc(char c, FILE *fichier); écrit la valeur c


à la position courante du pointeur; retourne EOF
si erreur .

95
Exemple : lecture caractère par
caractère

#include<stdio.h>
#include <stdlib.h>
void main()
{
FILE * fichier; char c, nom[21];
printf ("entrer le nom du fichier");
gets(nom);
if ((fichier = fopen(nom, "r "))== NULL) {
printf( "erreur d’ouverture"); exit (-1);
}
while ((c = fgetc(fichier)) !=EOF)
printf("%c",c);
fclose(fichier);
}

96
Lecture et écriture en mode
chaine

Des fonctions analogues à gets et puts permettent de lire


et écrire une chaine de caractères dans un fichier.
char *fgets(char *chaîne, int lgmax, FILE *fichier); lit
lgmax-1 caractères à partir de la position courante et
les range dans chaine en ajoutant " \0 ". retourne NULL
si erreur ou fin de fichier.
fgets lit donc un certain nombre de caractères et les range
à l’emplacement référencé par chaine jusqu’à se
produise un des évènements suivants :
 Le caractère de saut de ligne « \n » a été lu
 lgmax-1 caractères ont déjà été lus
 La fin de fichier a été rencontrée

int fputs(char *chaîne, FILE *fichier); écrit la chaîne de


caractères à la position courante du pointeur.(\0 exclu).
retourne EOF si erreur .

97
Lecture et écriture formatées
La fonction fprintf écrit des données dans un fichier en les
formatant. Elle admet le prototype :
int fprintf(FILE *fichier, char *format, liste d’expressions);
Comme printf, fprintf accepte un nombre variable de
paramètres. Elle retourne comme résultat le nombre de
caractères écrits. Une valeur de retour négative (comme EOF)
indique une erreur.
fprintf(stdout, … ) et printf( …) sont équivalents.

La fonction fscanf lit des données dans un fichier en les


formatant. Elle admet le prototype :
int fscanf(FILE *fichier, char *format, liste d’adresses);
fscanf retourne le nombre de caractères correctement lus. La
valeur de retour EOF indique la fin de fichier ou une erreur.
fscanf(stdin, … ) et scanf( …) sont équivalents.

98
Ecriture dans un fichier texte
#include <stdio.h>
#include <stdlib.h>
main () {
char c; int x, y; char nomfich[21]; FILE *sortie;
printf("nom du fichier à créer :");
scanf("%20s", nomfich);
sortie = fopen (nomfich,"w");
if (!sortie ){
printf ("erreur d'ouverture"); exit(-1);
}
printf("donner les noms (* pour finir) et les coordonnées
des points");
do {
scanf(" %c%d%d", &nom, &x, &y);
if (c != ‘*’ )
fprintf(sortie, "%c%d%d",c, x, y);
} while (c != ‘*’ )
fclose(sortie);
}

99
Lecture à partir d’un fichier
texte
#include <stdio.h>
#include <stdlib.h>
main () {
char c; int x, y; char nomfich[21]; FILE *entree;
printf("nom du fichier à lister :"); scanf("%20s",
nomfich);
entree = fopen (nomfich,"r");
if (!entree ){
printf ("erreur d'ouverture"); exit(-1);
}
do{
fscanf(entree, " %c%d%d", &c, &x, &y);
if ( !feof(entree) )
printf( "%c%d%d",c, x, y);
} while (!feof(entree) );
printf("***fin de fichier***");
fclose(entree);
}

100
Instructions de lecture et
d’écriture

L’instruction d’écriture permet de transférer


plusieurs blocs consécutifs de même taille à partir
d’une adresse donnée vers le fichier. L’instruction
retourne le nombre de blocs effectivement écrits.
Syntaxe :
int fwrite( void *p, int taille_bloc, int nb_bloc,
FILE *fichier);
p est l’adresse d’un bloc
taille_bloc est la taille d’un bloc, en octets
nb_bloc est le nombre de blocs transférés au fichier
fichier : l’adresse de la structure décrivant le fichier
(le fichier logique)
Exemple
int n; FILE *fichier
...
fwrite(&n,sizeof(int), 1, fichier);

101
Instruction de lecture

L’instruction de lecture permet de transférer


plusieurs blocs consécutifs de même taille à
partir du fichier vers une adresse donnée.
En cas de succès fread retourne le nombre de
blocs (non pas octets) effectivement lus.
En cas d’erreur ou si c’est la fin de fichier
l’instruction retourne une valeur inférieur (en
général 0).
Syntaxe :
int fread(void *p, int taille_bloc, int
nb_bloc, FILE *fichier);
Exemple
int n; FILE *fichier
...
fread(&n,sizeof(int), 1, fichier);

102
La fonction feof

Syntaxe :
int feof(FILE *fichier);
Cette fonction est surtout utilisée en lecture.
Elle retourne 0 tant que la fin de fichier n’est pas
atteinte. elle est vrai lorsque le pointeur est
positionné au-delà de la fin du fichier c à d quand
la dernière lecture a échoué
feof n’est jamais vrai après l’ouverture (en lecture )
du fichier même lorsque celui-ci est vide. Il faut au
moins une lecture.
Exemple :
do
{ fread(&n, sizeof(int), 1, fichier);
if (!feof(fichier)) printf(« \n %d », n);
} while (!feof(fichier));

103
Fermeture d’un fichier

Toujours à la fin d’une session de


traitement d’un fichier, il faut fermer
le fichier. L’instruction de fermeture
est la suivante :
Syntaxe :
int fclose (FILE *fichier);

104
Création d’un fichier

#include <stdio.h>
#include <stdlib.h>
int main()
{char *nom = " exemple.dat "; int n;
FILE *fichier = fopen(nom, "wb ");
if (fichier == NULL) {
printf ("erreur d'ouverture"); exit(-1);
}
do {
printf(" donner un entier ");
scanf(" %d ", &n);
fwrite(&n, sizeof(int), 1, fichier);
}while (n);
fclose(fichier);
return 0;
}

105
Liste séquentielle d’un fichier

#include <stdio.h>
#include <stdlib.h>
void main()
{char *nom = " exemple.dat "; int n; FILE
*fichier;
fichier = fopen(nom, " rb ");
if (!fichier ){
printf ("erreur d'ouverture"); exit(-1);
}
do
{ fread(&n, sizeof(int), 1, fichier);
if (!feof(fichier)) printf("\n %d " , n);
} while (!feof(fichier));

fclose(fichier);
}

106
Accès direct
Il est possible d’accéder à partir d’une position dans le fichier
vers une autre position à l’aide de la fonction fseek.
Cette fonction est utilisée surtout pour les fichiers binaires.
int fseek(FILE * f, int p, int position); déplace le pointeur
de p cases à partir de position.
Valeurs possibles pour position :
SEEK_SET (ou 0) : à partir du début du fichier
SEEK_CUR (ou 1) : à partir de la position courante du pointeur.
SEEK_END (ou 2) : en arrière à partir de la fin du fichier
Retourne 0 si le pointeur a pu être déplacé.
long int ftell(FILE *f); retourne le nombre d'octets du
début de fichier jusqu'à la position courante. Cette fonction
est habituellement utilisée avant fseek.

107
Exemple
#include <stdio.h>
#include <stdlib.h>
void main()
{char *nom = "exemple.dat"; int n; long num;
FILE *fichier = fopen (nom," rb ");
if (!fichier ){
printf ("erreur d'ouverture"); exit(-1);
}
do {
printf(" rang dans le fichier : ");
scanf("%d" , &num);
if (num) {
fseek(fichier, (num -1) *sizeof(int), SEEK_SET);
fread(&n,sizeof(int),1,fichier);
printf ("%d \n" , n);
}
} while (num)
fclose(fichier);
}

108
ANNEXES

109
Les chaînes de caractères

une chaîne de caractères est un tableau


de caractères se terminant par le
caractère nul '\0'.
une chaîne de caractères peut être
définie à l'aide d'un pointeur:
char *chaine;
des affectations sont possibles comme :

chaine = "ceci est une chaîne";


toute opération valide sur les pointeurs
l’est sur chaine, comme l'instruction
chaine++;

110
Principales fonctions de la
bibliothèque string.h pour la
manipulation des chaines de
caractères
 La fonction strlen(chaine) fournit la longueur de chaine.
 La fonction strcat(ch1,ch2) recopie la seconde chaine ch2 à la
suite de la première ch1.
 La fonction strncat(ch1,ch2,lgmax) travaille de la même façon
que strcat en offrant un contrôle sur le nombre de caractères
concaténés.
 La fonction strcmp(ch1,ch2) compare deux chaines et fournit
une valeur positive entière si ch1>ch2, nulle si ch1=ch2,
négative si ch1<ch2.
 La fonction strncmp(ch1,ch2,lgmax) travaille comme strcmp
mais limite la comparaison au nombre lgmax de caractères.
 Les fonctions stricmp(ch1,ch2) et strnicmp(ch1,ch2,lgmax)
travaillent comme strcmp et strncmp mais sans tenir compte de
la différence entre majuscules et minuscules.
 La fonction strcpy(destin,source) recopie le chaine source dans
l’emplacement d’adresse detin.
 La fonction strncpy(destin,source,lgmax) limite la copie au
nombre de caractères lgmax.
 La fonction strchr(ch,caractere) recherche dans ch la première
position où apparaît le caractère mentionné.
 La fonction strrchr(ch,caractere) opère de même mais en
partant de la fin de ch.
 La fonction strstr(ch,ssch) recherche dans ch la première
occurrence de la sous chaine ssch.

111
Branchement multiple switch

switch (expression )
{case constante-1: liste d’instructions 1 ; break;
case constante-2: liste d'instructions 2 ;break;
...
case constante-n: liste d'instructions n; break;
default: liste d'instructions ;break;
}
Si la valeur de expression est égale à l'une des
constantes, la liste d'instructions correspondant est
exécutée. Sinon la liste d'instructions correspondant
à default est exécutée. L'instruction default est
facultative

112
Branchement non conditionnel
break

L'instruction break peut être employée à


l'intérieur de n'importe quelle boucle. Elle
permet d'interrompre le déroulement de la
boucle, et passe à la première instruction
qui suit la boucle. En cas de boucles
imbriquées, break fait sortir de la boucle la
plus interne:
main()
{ int i;
for (i = 0; i < 5; i++) {
printf("i = %d\n",i);
if (i == 3) break;
}
printf(" i a la sortie de la boucle= %d\n",i);
}//i = 3

113
Branchement non conditionnel
continue

L'instruction continue permet de passer


directement au tour de boucle suivant, sans
exécuter le reste des instructions du corps de
la boucle.
main()
{int i;
for (i = 0; i < 5; i++) {
if (i == 3) continue;
printf(" i = %d",i);
}
printf(" \nvaleur de i a la sortie de la boucle =
%d",i);
}
//i = 0 i = 1 i = 2 i = 4
//valeur de i a la sortie de la boucle = 5

114
Exemple

 void diag(int *p, int n){


int i;
for (i = 0; i<n ; i++){
*p = 0;
p + = n+1;
}
}
 Cette fonction sert à placer la valeur
0 dans la diagonale d’un tableau
carré de taille quelconque
 Pour l’appeler :
int t[30][30]; diag(t,30);

115

Das könnte Ihnen auch gefallen