Post on 04-Apr-2015
1
Projet informatique 2A
Séances de soutien
Francois.Portet@imag.frNicolas.Castagne@imag.fr
Ces transparents s’inspirent largement des cours 1A PET (M. Desvignes) et PMP (R Bressoux, E Moisan, N Castagne)
Merci à eux
2
Séance I. L’environnement LINUX
Premier programme de l'année scolaire
Compilation et exécution
Rappel sur les Types de base, E/S, Structures de contrôle
Séance II. Rappel sur les tableaux, pointeurs et fonctions
Compilation séparée et Makefile
Types structurés, tableaux et chaînes de caractères
Entrées sorties fichier
Séance III Allocation dynamique
Débogueur (DDD, Valgrind)
Types abstraits ; exemple des listes
Récursivité
Séance IV suite de la séance III
Arbres et graphes
Planning prévisionnel
3
Tableaux
44
Tableaux Tableaux
Collection de variables de même type, rangées continûment en mémoire Déclaration : spécifier
le type des éléments le nom du tableau le nombre des éléments
Exemples : float c[100]; /* tableau de 100 réels */ int tab[10]; /* tableau de 10 entiers*/
Exemple : tab1.cmain() { int i; int tab[10]; /* Tableau de 10 entiers */ float c[20]; /* Tableau de 20 réels */ for (i=0; i<10; i++) tab[i]= 2*i; /*Mettre 0,2,4,6..dans les elements*/ for (i=0; i<10; i++) printf("%d ",tab[i]); /* afficher les éléments de t */}
tab[9]: 18
tab[2]: 4
0
tab[1]: 2
tab[0]: 0tab &tab[0]
Adresse
&tab[1]
&tab[2]
…
&tab[9]
55
Tableaux
18
4
0
2
0tab &tab[0]
Adresse
&tab[1]
tab[2]
&tab[-2]
…
&tab[9]
&tab[900]
tab+1
tab+2
tab+9
Remarques Nombre d’éléments constant, non modifiable Le nom du tableau est son adresse (ie l’endroit où il se trouve en mémoire
Accès à un élément du tableau : nom_du_tableau[expression entiere]
Exemple : tab[i]
Comment fait le compilateur : on part du début du tableau, on ajoute i : c’est l’endroit où se trouve notre élément
Attention : Pas de vérification sur les indices. Si on demande un élément avant ou après la fin du tableau, c’est une erreur à l’execution du programme mais pas d’erreurs à la compilation
66
Tableaux : accès et débordements
Exemple 2 : tab2.cmain() { int i; int tab[10]; for (i=0; i<10; i++) tab[i]= 2*i; puts("Voici les elements : "); /* afficher les éléments de t */ for (i=0; i<5; i++) printf("%d ",tab[i]); puts(""); puts("Voici les adresses : "); /* afficher les adresses */ for (i=0; i<5; i++) printf("%p ",tab+i); puts(""); tab[-2]= 18952; printf("%d ",tab[900]); }
18
4
0
2
00xbffff384 &tab[0]
Adresse
&tab[1]
&tab[2]
&tab[-2]
…
&tab[9]
&tab[900]
0xbffff388
0xbffff38c
0xbffff3a8
0xbffff37c
0xc0000194
77
Tableaux : opérations globales ? Attention : AUCUNE opération globale sur un tableau
les opérations et les E/S doivent se faire élément par élément En particulier
T1==T2 ne teste pas l’égalité de 2 tableaux T1=T2 ne recopie pas les éléments de T1 dans T2
Exemples : tab3.cmain() {int i; double t1[10], t2[10]; /* t1 et t2 sont 2 tableaux de 10 reéls */ for (i=0; i<10; i++) {t1[i]=2*i; t2[i]=log(100*i+1); } printf("Emplacement de t1:%p de t2:%p\n",t1,t2); printf("Emplacement de t1[1]:%p de t2[1]:%p\n",t1+1,t2+1); printf("Valeur de t1[0]:%lf de t2[0]:%lf\n",t1[0],t2[0]); if (t1==t2) printf("t1 et t2 sont au meme endroit\n"); else printf("t1 et t2 ne sont pas au meme endroit\n"); for (i=0; i<10; i++) t2[i]= t1[i]; /*Copie de t2 dans t1: on peut remplacer cette ligne par: memcpy(t2,t1,sizeof(t1)); Mais on ne peut pas utiliser t1=t2; */ for (i=0; i<10; i++) printf(”Valeur de t2[%d] : %lf\n”,i,t1[i]);}
88
Tableaux : fonctions de la libc Les fonctions travaillant sur les zones mémoires : comme un tableau est une zone
mémoire continue, on peut utiliser ces fonctions avec les tableaux #include <string.h>
void * memmove(void *s1, const void *s2, size_t n); Copie n octets de la zone de mémoire src vers la zone dest. Les deux zones peuvent se chevaucher.
void *memcpy (void *dest, const void *src, size_t n); idem, mais les zones ne peuvent pas se chevaucher
int memcmp (const void *s1, const void *s2, size_t n); compare les n premiers octets des zones mémoire s1 et s2.
void *memset (void *s, int c, size_t n); remplit les n premiers octets de la zone mémoire pointée par s avec l’octet c.
void swab (const void * from, void * to, ssize_t n); copie n octets de la zone from dans la zone to, en échangeant les octets adjacents
9
Pointeurs
Pointeurs
1010
Pointeurs Variable contenant l’adresse d’un autre objet (variable ou fonction)
Adresse : numéro d’une case mémoire
Déclaration : type_pointé* identificateur; Exemples :
int* p1; /* p1 contient l’adresse d’un entier */double* p2; /* p2 contient l’adresse d’un réel */
ATTENTION : un pointeur doit toujours être initialisé avant d’être utilisé = Il doit contenir une adresse légale :
soit celle d’un objet existant soit celle obtenu par une demande d’allocation dynamique soit NULL, qui est la valeur 0. Il est interdit de lire et écrire à l'adresse 0
Remarque : on affiche la valeur d’un pointeur en hexadecimal par printf("Voici la valeur du pointeur %p\n",p);
1111
PointeursPointeurs
Exemple : p1.cmain() {
int i=0;
int* p1=NULL; /* p1: pointeur sur un entier */
p1 = &i; /*p1 pointe i, ie contient l’adresse de i*/
/* ICI, i ou *p1 sont une seule et meme chose */
*p1 = 5; /* identique à i=5; */
printf("Valeur de i:%d, Adresse de i:%p\n",i,&i);
printf("Valeur de p:%p, Valeur pointée:%d\n",p1,*p1);
printf("Adresse de p:%p\n", &p1);
}
0
p= 0
i= 00xbffff38c &i
Adresse
&p
…
0xbffff388 p1=0xbffff38c
i= 5
1212
Opérations sur pointeurs Affectation : donner une valeur au pointeur, celle d’une adresse légitime.
p1=&i; /* &i : l'adresse de i */
Indirection : trouver la valeur pointée par p.
j = *p1; /* *p1 : ce qu'il y a à l'adresse p1 */
Comparaison : == et != : p1==p2; p1 et p2 regardent ils la meme adresse ? <, >, <=, >= : par exemple, p1<p2 sur un meme tableau, p1 est il avant p2
Arithmétique Adresse + entier ==> adresse : p1 +1 est l’adresse de l’élément suivant p1
Adresse - Adresse ==> entier : p2 -p1 est donc le nombre d’éléments entre les adresses contenues dans p2 et p1. Valide uniquement si p1 et p2 sont de meme type.
ATTENTION : les pointeurs étant typés, les opérations se font en nombre d’éléments et non
en nombre d’octets
1313
Adresse et tableaux Nom du tableau : adresse du tableau
Accès à un élément t[i] : on part de l‘adresse de début du tableau, on ajoute i et on obtient l’adresse du ième
élément. L’élément est obtenu par l’opérateur d’indirection *
Conséquences L’élément t[i] s ’écrit aussi *(t+i) L’adresse de t[i] s’écrit &t[i] ou bien (t+i)
1414
Adresses et tableaux Exemple : p3.cmain() { int tab[10]; int i; for (i=0; i<10; i++) tab[i]= 2*i; puts("Voici l’element d’indice 2 : "); printf("%d %d",tab[2],*(tab+2)); puts(""); puts("Voici les adresses : "); for (i=0; i<5; i++) printf("%p %p",tab+i, &tab[i]); }
18
4
0
2
00xbffff364 &tab[0]
Adresse
&tab[1]
&tab[2]
…
&tab[9]
0xbffff368
0xbffff36c
0xbffff388
2 manières différentes d'ecrire l'adresse de t[i]
1515
Parcourir un tableau avec un
pointeur Exemple : p4.cmain() {int* p=NULL; int i; int tab[5]; for (i=0; i<5; i++) tab[i]=2*i+1; p=tab; while (p<tab+5) { printf(" Pointeur: %p ",,p); printf(" Valeur %d",*p);
*p=234; printf("Valeur modifiee %d\n",*p); p++; }
9
5
0
3
10xbffff384 &tab[0]
Adresse
&tab[1]
&tab[2]
…
&tab[4]
0xbffff388
0xbffff38c
0xbffff394
7 &tab[3]0xbffff390
i= 5 &i0xbffff398
p= 0 &p0xbffff39c 0xbffff384
tab[0]=234
0xbffff388
tab[1]=234
0xbffff38c
tab[2]=234
0xbffff390
tab[3]=234
tab[4]=234
0xbffff394
16
Structures
Structures, définition de types
1717
Créer ses propres types
Pourquoi ? Clarifier l'écriture d’un programme
Exemple : j’aime pas les int* je veux indiquer clairement qu’une variable est un octet et pas un caractère
Définir un nouveau type : instruction typedef
• typedef ancien_type nouveau_type;
1818
Créer ses propres types
typedef unsigned char OCTET; typedef int* POINTEUR;
void swap(POINTEUR p1, POINTEUR p2) { int c=*p1; *p1=*p2; *p2=c; } main() { OCTET a; int i,j; POINTEUR p; a=156; i=32546; p=&i; j=-5; printf("Valeur de l’octet a :%d\n",a);
printf("Valeur de i et de *p :%d %d\n",i,*p); swap(&i,&j); }
POINTEUR peut remplacer int* partout
1919
Les structures
Regroupement d’informations de types identiques ou différents
Relatif à une même entité abstraite. permet de manipuler sous un même nom plusieurs
éléments d'informations
Exemple : Un point : une structure comportant les deux coordonnées
X et Y du point Un complexe : les parties réelle et imaginaire du
complexe. un etat civil : une structure regroupant le nom, prénom,
n°SS, age....
Déclaration de typestruct ident1 {
type nom_du_champ; ....
} /* fin de structure */
Définition de variable :struct nomdestructure identif_var;
a1.re
a1.im
a1
struct complex {double im,re;};
struct complex a1;
Nom du type
Nom de la variable
2020
Structures (2)
Un élément chimique avec les informations de type différent:
struct element_atomique { char nom[20] ; char symbole[4] ; int nummeroatomique; double masseatomique; double densite ; double fusion ; /* Temperature de fusion en ° */ double vap; /* Temperature de vaporisation en ° */ double rayon; /* rayon atomique en A */ double rayon_cov; /* rayon de covalence en A */ char rayonionique[24] ; /* Rayon ionique */ } x;
x.nomX
x.symbole
x.numeroat
x.masseato
x.densitéLe type s’appelle
struct element_atomique
2121
Structure (3)
Accès à un champ Pour une variable structurée :opérateur . Pour une structure pointée : opérateur ->
Opérations sur les structures : aucune sauf Affectation de structures meme type Passage et retour de structures par valeur dans les fonctions
Les éléments de la structure sont des variables à part entière : ils ont une adresse
typedef struct { int x,y} T_POINT;
void aff(T_POINT a) {
printf("%d %d\n",a.x,a.y);
}
main() { T_POINT a1, *pa1, a2;
a1.x=1; a1.y=-1;
pa1=&a1; pa1->x=0;
a2 = a1;
aff(a2);
aff(*pa1);
}
a1.x
a1.y a1
pa1
1
-1
0xbffff398
0
a2.x
a2.y
a2
-1
00xbffff38c
0xbffff394
0xbffff398
On définit une structure anonyme et un nouveau yype
pa1 est un pointeur. Donc, pa1->x est la partie x de a1
aff est une fonction qui utilise un T_POINT : elle a accès aux 2 coordonnées de a, a.x et a.y
2222
Structures (4)
Aucune autre action globale, sauf la copie en particulier
Comparaison de structure a1==a2 Lecture/ecriture de structure : printf("%lf",a1);
Il faut tout faire champ par champ
Exemple : struct3.cmain() { T_POINT a3,a1,a2={1,-1};
scanf("%lf %lf",&a1.x, &a1.y); a3 = a2; /* Copîe a2 dans a3 : a3.x=1 et a3.y=-1 */ puts("Point a1"); aff(a1); /* affiche un point */ puts("Point a2"); aff(a2); /* affiche un point */ /* Ce code est impossible if (a1==a2) puts("a1 et a2 identiques"); else puts("a1 et a2 different"); */ if(a1.x==a3.x && a1.y==a3.y) puts("a1 et a3 identiques"); else puts("a1 et a3 different");}
Lecture clavier des 2 champs
2323
Structure (5)
Une structure est un type utilisable pour construire d’autres types On peut faire
des tableaux de structuresdes structures de structures
2424
Exos Ex1 : faire un programme qui calcule la somme des éléments d'un tableau en
utilisant uniquement des indices pointeurs (et pas entiers) Ex2 : faire un programme qui copie un tableau de N entiers (N=10) dans un
autre tableau en utilisant uniquement des indices pointeurs Ex3 : faire un programme qui génère 100 nombres entiers dans un tableau, les
affiche et trouve le minimum. Pour utiliser: $ man 3 rand, pour générer les nombres aléatoires
Ex4 : faire un programme qui utilise un tableau mono-dimensionel d'entiers comme une matrice. Pour cela faire une fonction int element(int mat, int M, int N, int i, int j) qui retourne l'élèment de la ie ligne et de la je colonne, ou M et N sont les dimensions de la matrice mat. Faites le programme principal qui teste cette fonction.
Ex5 ecrire une structure Image contenant un tableau pour stocker les pixels et deux champs pour stocker les dimensions. Ecrire deux fonctions pour initialiser (initialiser une image) et remplir aléatoirement l'image.
Je veux ajouter un champs à ma structure, quelles conséquences pour les deux foncctions ? Que se passe-t'il si je ne retourne pas la structure passée en paramètre ?
25
VIII. Fonctions et procédures
VIII.1. Introduction
VIII.2. Le mécanisme de base
VIII.3. Passage par valeur et « passage par adresse »
VIII.4. Notion de contrat de fonction, prototype d’une fonction
26
VIII.1. Introduction
#include <math.h>
x, y, z réels double x, y, z ; i entier int i ;
z ← | x | z = fabs(x) ; associe un réel à un réel, z ← arctg ( x ) z = atan(x) ; associe un réel à un réel, i ← ⌊ x ⌋ i = floor(x) ; associe un entier à un réel, z ← xy z = pow(x, y) ; associe un réel à deux réels.
Une fonction mathématique retourne une valeur de sortie (un résultat)
établie à partir de différents paramètres d’entrée (des arguments) :
VIII.1.1 En maths …
27
En programmation, une fonction prend en entrée des données et renvoie des
résultats après avoir exécuté différentes instructions.
Une fonction ou procédure apparaît comme :
- un bloc d’instructions regroupées sous un nom particulier,
- qui pourra retourner une unique valeur de sortie (un résultat)
- qui pourra admettre 0, 1, 2, … paramètres d’entrée (des
arguments).Les avantages sont nombreux :
- réutilisation du même bloc d’instructions (sur des valeurs différentes),
- réutilisation dans d’autres programmes (bibliothèques).
- décomposition du problème en fonctions qui effectuent des actions
simples :
- conception du programme plus facile, structuration
- lisibilité du code accrue,
- débuggage facilité.
VIII.1. IntroductionVIII.1.2 En programmation…
28
VIII.2. Le mécanisme de base
#include "stdio.h" // bibliothèque de fonctions d’entrées/sorties
float discr ( float x , float y , float z){
float discriminant ; // bloc d’instructions (entre {…} ) de ladiscriminant = y * y – 4 * x * z ; // fonction nommée discr
//return discriminant ; //
}
int main( ){
float a = 4. , b = 3. , c = -1.5 , d , e ;
d = discr ( a , b , c ) ;e = discr ( a , 3. , 9.) ;
return 0 ;} Le main est la fonction qui est automatiquement
appelée lors de l’exécution : c’est le point d’entrée du programme.
1/ appel de la fonction discr
2/ retour
VIII.2.1. Exemple
29
float discr ( float x , float y , float z ){
float discriminant ;discriminant = y * y – 4 * x * z ;
return discriminant ;}
Le nom de la fonction
La liste des arguments (type et nom) en entrée de la fonction.
Les arguments sont des variables !
Cette liste peut être vide.
Le résultat renvoyé par la fonction.Ici, c’est la valeur de la variable locale discriminant
Le type retourné.C’est le type de la valeur retournée.Le type peut être void
(aucune valeur retournée)
Corps de la fonction.Bloc d’instructions qui seront exécutées à chaque appel de la fonction.
VIII.2.2. Déclaration d’une fonction
VIII.2. Le mécanisme de base
Variable locale
30
int main(int argc, int ** argv){
float a = 4. , b = 3. , c = -1.5 , res ;
res = discr ( a , b , c ) ;
printf( " discr de %f %f %f vaut %f\n", a, b, c, res);
printf( " discr de %f %f %f vaut %f\n", a, 3., 9., discr ( a , 3. , 9 ) );
return 0 ;}
Appel de la fonction discr avec les valeurs de a, b et c.On suppose la fonction discr déclarée par ailleurs.La valeur retournée par discr est affectée à d.
VI.2.3. Appel de fonction
VIII.2. Le mécanisme de base
31
float discr ( float a , float y , float z){
float discriminant ;
discriminant = y * y – 4 * a * z ;
return discriminant ;}
int main( ){ float a = 4. , b = 3. , d ;
d = discr ( a , b , -1.5 ) ;
return 0 ;}
VIII.2.4. ce qui se passe…
a = 4
b = 3
d = ???
Représentation
de la mémoire
Avant l’appel de fonction
VIII.2. Le mécanisme de base
32
a, y et z
sont les
« paramètres »
discriminant
est une
« variable
locale »
float discr ( float a , float y , float z){
float discriminant ;
discriminant = y * y – 4 * a * z ;
return discriminant ;}
int main( ){ float a = 4. , b = 3. , d ;
d = discr ( a , b , -1.5 ) ;
return 0 ;}
a = 4
y = 3
z = -1,5
discriminant = ???
Appel de la fonction : copie des valeurs des paramètres effectifs dans de nouvelles variables
VIII.2.4. ce qui se passe…
VIII.2. Le mécanisme de base
a = 4
b = 3
d = ???
33
float discr ( float a , float y , float z){
float discriminant ;
discriminant = y * y – 4 * a * z ;
return discriminant ;}
int main( ){ float a = 4. , b = 3. , d ;
d = discr ( a , b , -1.5 ) ;
return 0 ;}
a = 4
y = 3
z = -1,5
discriminant = ???
Appel de la fonction : copie des valeurs des paramètres effectifs dans de nouvelles variables
VIII.2.4. ce qui se passe…
VIII.2. Le mécanisme de base
Ce n’est pas
la même
variable
"a" !
Elle est ailleurs
en mémoire !
!
a = 4
b = 3
d = ???
34
float discr ( float a , float y , float z){
float discriminant ;
discriminant = y * y – 4 * a * z ;
return discriminant ;}
int main( ){ float a = 4. , b = 3. , d ;
d = discr ( a , b , -1.5 ) ;
return 0 ;}
a = 4
y = 3
z = -1,5
discriminant = 33
Appel de la fonction. Ca se passe comme d‘habitude dans le « main »
VIII.2.4. ce qui se passe…
VIII.2. Le mécanisme de base
a = 4
b = 3
d = ???
35
float discr ( float a , float y , float z){
float discriminant ;
discriminant = y * y – 4 * a * z ;
return discriminant ;}
int main( ){ float a = 4. , b = 3. , d ;
d = discr ( a , b , -1.5 ) ;
return 0 ;}
Fin d’appel de fonction : destruction de toutes les variables locales et paramètres
d prend la
valeur
retournée
VIII.2.4. ce qui se passe…
VIII.2. Le mécanisme de base
discriminant = 33
a = 4
b = 3
d = 33
36
VIII.2. Le mécanisme de baseVIII.2.5. Notion de portée de variable
float test( float a, float b){
float d = a * b ;c = a * b ;return d;
}
int main( ){ float a = 4., b = 5, c ;
c = test(a, b); // ou c =test(b,a) si on veut… d = 10.;
return 0 ;}
Erreur :
"c" n’est pas définie ici !!
Erreur :
"d" n’est pas définie ici !!
Ce code ne compile pas car :
Rappel :
Ce n’est pas le même a !Ce n’est pas le même b !
les variables sont locales au bloc ou elles sont définies ( entre { } )
37
Lorsque l’on rencontre une instruction return, l’exécution de la fonction est arrêtée.
On reprend l’exécution des instructions qui suivent l’appel de la fonction.
Une fonction peut posséder plusieurs instructions return.
int maximum( int a , int b) {
if ( a > b )return a ;
elsereturn b ;
}
VIII.2.6. return
VIII.2. Le mécanisme de base
38
Une fonction peut ne pas renvoyer de résultat : son type de retour est
noté void.
Une telle fonction est appelée une procédure.#include "stdio.h »
void afficheMax( int a , int b ) { // procédure de type voidif ( a > b ) {
printf("a est le max\n”);return ;
}printf(”b est le max\n”);
}
int main( ) {afficheMax(3 , 5 ) ; // pas de valeur à récupérer !
return 0 ;}
Remarque : une procédure peut utiliser des instructions return, qui alors ne retourne rien.
VIII.2.7. Type void
VIII.2. Le mécanisme de base
39
Écrire une fonction qui échange les valeurs de deux variables a et b de type int.
VIII.3.1. Le problème
#include <stdio.h>
void echange( int a , int b ) {
int temp ;temp = b ; b = a ; a = temp ;
}
int main( ){
int a = 1 , b = 2 ;echange( a, b ) ;printf("a: %d et b: %d\n", a, b);return 0 ;
}
Solution naïve :
Que valent a et b ?
Que valent a et b ?
VIII.3. Passage « par valeur » et « par adresse »
40
Ceci ne fonctionne pas car a et b (du main) et a et b (de echange) sont des
variables différentes bien qu’étant des homonymes !
En fait, c’est comme si on avait écrit le programme suivant :
#include <stdio.h>
void echange( int n , int m ) {
int temp ;temp = n ; n = m ; m = temp ;
}
int main( ){
int a = 1 , b = 2 ;echange( b , a ) ;printf("a: %d et b: %d\n", a, b);return 0 ;
}
VIII.3.1. Le problème
VIII.3. Passage « par valeur » et « par adresse »
41
Reprenons le déroulement du programme :
1) on définit a et b dans le main ; on leur affecte les valeurs 1 et 2.
2) on appelle la fonction echange avec les VALEURS de a et b.
3) les paramètres m et n reçoivent les valeurs 1 et 2.
4) la fonction echange échange les valeurs de m et n :
les paramètres m et n locaux à la fonction valent 2 et 1.
5) on revient dans le main … a et b valent toujours 1 et 2 !!!
Passage par
valeur.
!
VIII.3.1. Le problème
VIII.3. Passage « par valeur » et « par adresse »
42
#include <stdio.h>
void echange( int * p_a , int * p_b ) {
int temp ;temp = *p_b ; *p_b = *p_a ; *p_a = temp ;
}
int main( ){
int a = 1 , b = 2 ;echange( &a , &b ) ;printf("a: %d et b: %d\n", a, b);return 0 ;
}
Les pointeurs viennent à notre
secours :
VIII.4.3. La solution
VIII.3. Passage « par valeur » et « par adresse »
43
Reprenons le déroulement du programme :
1) on définit a et b dans le main ; on leur affecte les valeurs 1 et 2.
2) on appelle la fonction echange avec les ADRESSES de a et b.
3) p_a et p_b reçoivent les ADRESSES de a et b.
4) la fonction echange échange les valeurs des cases mémoire pointées
par p_a et p_b.
5) on revient dans le main … a et b valent maintenant 2 et 1 !
Passage par
adresse.
!
VIII.3.2. La solution
VIII.3. Passage « par valeur » et « par adresse »
44
void echange( int * p_a , int * p_b ) {
int temp ;temp = *p_b ;
*p_b = *p_a ;
*p_a = temp ;}
int main( ){
int a = 1 , b = 2 ;echange( &a , &b ) ;
return 0 ;}
a = 1
b = 2
p_a = 0xbffff3b0
p_b =0xbfffff3b4
temp = 2
Appel de la fonction : copie des valeurs des paramètres effectifs dans de nouvelles variables
p_b vaut
l’adresse de b.
donc
*p_b vaut 2
valeurs adresses
0xbffff3b0
0xbffff3b4
0xbffff388
0xbffff3bc
0xbffff3c0
VIII.3.3. Exemple
VIII.3. Passage « par valeur » et « par adresse »
45
void echange( int * p_a , int * p_b ) {
int temp ;temp = *p_b ;
*p_b = *p_a ;
*p_a = temp ;}
int main( ){
int a = 1 , b = 2 ;echange( &a , &b ) ;
return 0 ;}
a = 1
b = 1
p_a = 0xbffff3b0
p_b =0xbfffff3b4
temp = 2
Appel de la fonction : copie des valeurs des paramètres effectifs dans de nouvelles variables
valeurs adresses
0xbffff3b0
0xbffff3b4
0xbffff388
0xbffff3bc
0xbffff3c0*p_b est la
même case
mémoire que
la variable
« b » du main
=> On affecte
1 à cette
variable
(c’est à dire la
valeur de *p_b)
VIII.3.3. Exemple
VIII.3. Passage « par valeur » et « par adresse »
46
Une variable “a” définie dans une fonction n’est utilisable que dans cette
fonction. On dit que a est locale à la fonction.
Une variable “a” définie dans une autre fonction est une variable homonyme
mais différente de la première ; elle occupera une autre case mémoire lors
de l’appel de la fonction.
Pour modifier la valeur d’une variable lorsqu’on appelle une
procédure, il faut utiliser le mécanisme du passage “par adresse”
avec des pointeurs …
Faire attention à la manipulation des & et des * !!!
Remarque : les termes “passage par valeur” et “passage par adresse” sont en fait des
abus de langage… En C, on passe toujours “par valeur”, valeurs qui valent parfois “des
adresses”..
!
VIII.4. Passage par valeur et par adresse
VIII.3.4. Résumé
47
Avant même d’écrire le corps d’une fonction, il faut être très sur le
« contrat » qu’elle doit remplir, c’est à dire :
•Son rôle (ce qu’elle fait)•Son prototype. Le « prototype » ou la « signature » est constitué de :
nom de la fonction, type de retour, type et nom des
paramètres
Chacun des paramètres est soit :
IN : utilisé uniquement en entrée. Passé par valeur en C.
OUT : utilisé uniquement en sortie. Passé par pointeur en C.
IN/OUT : utilisé en entrée et en sortie. Passé par pointeur en C.
•Le champ d’utilisation de la fonction, et en particulier l’état que doit
respecter le système avant l’appel de la fonction : préconditions•L’état que dans lequel sera le système après l’appel de la fonction•Les cas d’erreur, et la façon dont la fonction réagit en cas d’erreur.
VIII.4.1. Introduction
VIII.4. Notion de contrat de fonction
48
Le « contrat de fonction » peut être formalisé à l’aide de commentaire.
Exemple : définir une fonction résolvant dans une équation du second
degré.
Le « contrat » d’une telle fonction pourrait s’écrire :
VIII.4. Notion de contrat de fonctionVIII.4.2. Exemple
Prototype
Con
trat
49
VIII.4. Notion de contrat de fonctionVIII.4.3. Utilisation du prototype comme promesse d’existence
Pour que le compilateur puisse vérifier les appels de fonctions, la fonction ou son
prototype doivent être définis avant le premier appel de la fonction.
#include <stdio.h>
//commentaire précisant « contrat »void echange( int * p_a , int * p_b) ; // Prototype de la fonction
int main( ){
int a = 1 , b = 2 ;echange( &a , & b ) ; // Appel de la fonctionreturn 0 ;
}
void echange( int * p_a , int * p_b) // Définition de la fonction{
int temp ;temp = *p_b ; *p_b = *p_a ; *p_a = temp ;
}
50
/* ******************************************************* Titre ******************************************************* */// Entête (#include, #define, etc.)…// Prototypes des fonctions et procédures, accompagné de commentaires pour les « contrats »…int main( ) { // fonction principale
… // Définition des variables du main… // Appels des fonctions et procédures// Dorénavant le main sera être réduit à son minimum : // pour l’essentiel, définition de variables et appels de fonctions et procédures.return 0 ;
}
// Définition des fonctions// fonction exemple<type retour> exemple(<liste des arguments>) {
… // Définition des variables locales de la fonction exemple… // Instructions de la fonction exemple
return … ; // valeur de retour}
…
VIII.4. Notion de contrat de fonctionVIII.4.4 Structure générale d’un programme
51
Exercice : Calcul des racines d’une équation du second degré – suite et fin !
Écrire un programme qui :
- demande les coefficients a, b, c de l’équation a x² + b x + c = 0 ;
- calcule la / les racine(s) réelle(s) de l’équation
- affiche les résultats
On écriera une fonction générique de résolution des équations du second degré,
qui traitera tous les cas possibles et qui ne ne fera aucune entrée/sortie.
Exercice
52
Compilation séparée et Makefile
53
Exercice : Calcul des racines d’une équation du second degré – suite et fin !
Récrire le programme précédent mais en séparant le main de la fonction de calcul
d'une équation du second degré, il devrait y avoir au moins les fichiers suivants :
- Makefile
- testcalcul.c
- fonction_equa.c
- fonction_equa.h
Écrire un autre main (calcul_alea.c) dans lequel on effectue 10 calculs à partir de
valeur aléatoires pour a,b et c. Ajouter une ligne à votre Makefile pour pouvoir le
compiler
Exercice
54
Les chaines de caractères
5555
Les chaînes de caractères En C, les chaînes de caractères ne sont pas un type particulier mais
sont : Des tableaux de char, exemple : char t[256]; Terminés par ’\0’.
"Ceci est une chaine" est une chaine de caractère constante
Pour utiliser une chaîne de caractère, 2 solutions Créer un tableau et utiliser les fonctions d’E/S sur les chaînes (scanf, gets) qui
ajoutent un \0 en fin de chaine. Créer un tableau et le remplir élément/élément, y compris le ’\0’ terminal.
Exemple : chaine0.cmain() {char s[256]; s[0]=’u’; s[1]=’n’; s[2]=’e’; s[3]=’\0’;
/* Affichage de s */ puts(s); /* ou printf("%s\n",s);}
????
’e’
0
’n’
’u’0xbffff2e0 &s[0]
Adresse
&s[1]
&s[2]
&s[255]
0xbffff3e1
0xbffff3df
’\0’ &s[3]
????les autres valeurs sont
aléatoires car on ne les a pas entrées
5656
Les chaînes de caractères Exemple : chaine1.c : crée et lit 2 chaines au clavier avec 2 methodes
main() { int i; char s1[256], s2[256]; puts("Entrer une chaine");
/* Lecture d’une chaine. Gets positionne la marque de fin de chaine*/ gets(s1); /* On peut aussi utiliser scanf("%s",s1); *//* Affichage d’une chaine. Utilise la marque de fin de chaine pour afficher les éléments du
tableau réellement utilise*/ puts(s1); /* ou printf(”%s",s1); */
/* Lecture caractères/caractères de 10 caractères */ for (i=0; i< 10; i++) s2[i]=getchar();/* Ajout de la marque de fin de chaine */ s2[i]='\0';/* Affichage de s2 */ puts(s2);/* Affichage de s2 element/element*/ for (i=0; s2[i]!= '\0'; i++) printf("%c", s2[i]); }
5757
Les chaînes de caractères
Les chaînes de caractères sont des tableaux
==> AUCUNE opération globale n’est possible : s1==s2 ne compare pas 2 chaînes s1=s2 ne copie pas s2 dans s1
Il faut utiliser les fonctions spécifiques Concaténation : strcat Copie : strcpy, strncpy Comparaison : strcmp, strncmp, strcasecmp, strncasecmp, strcoll Recherche : strstr, strchr Longueur : strlen Decoupage de la chaine en sous chaine : strtok
Toutes ces fonctions mettent le resultat dans une chaîne de caractères Attention : la chaîne recevant le résultat doit exister et doit avoir une taille
suffisante
5858
Chaînes de caractères
Copie d'une chaînechar* strcpy (char* destin, char* source);
main() { char t1[256], t2[256]; printf("tapez une caine"); gets(t1);
strcpy(t2,t1); puts(t2);
strcpy("chaine", t1);}
Concaténation de 2 chaînes : char* strcat (char* destin, char * source);main() { char t1[256], t2[256];
printf("tapez 2 chaines"); gets(t1); gets(t2);
strcat(t1,t2); puts(t1);
strcat ("ceci est une chaine", t2);}
Duplication (allocation et copie) d'une chaîne Cbaine4.cchar* strdup (char* source);
main() { char t1[256], t2[256]; char* s1; :/* Pas de taille: pointeur */ gets(t1);
s1 = strdup(t1); puts(s1);
t2=strdup(t1);}
Autre exemplemain() { char t1[256], t2[256], t3[256]; gets(t1);
strcat(strcpy(t2,t1),".obj"); strcat(strcpy(t3,t1),".exe");
puts(t1); puts(t2); puts(t3);}
59
Comparaison de 2 chaînesint strcmp (char* chain1, char* chain2)
Retourne : un entier négatif si chain1 inférieure à chain20 si chain1 identique à chain2un entier positif si chain1 supérieure à chain2 ordre lexicographique.
int strcasecmp (char* chain1, char* chain2) majuscules et minuscules indifférentes
Comparaison de 2 chaînes sur n octetsint strncmp(char* chain1, char* chain2, int n)/*n= Nombre de caractères à comparer */
59
Chaînes de caractères Formation/extraction dans des chaines
de caractèresint sprintf(char* s, char* format, valeurs)int sscanf(char* s, char* format, adresses) main() { char t1[256], t2[256]; int i; float x; double y;
sprintf(t1,"Valeurs %d et %f et %lf", i,x,y); puts(t1);
strcpy(t1,"1000 10.3 truc 3.14259"); sscanf(t1,"%d %f %s %lf ",&i,&x,t2,&y);
sscanf(t1, "%d %f %lf ",i,x,y) ;}
6060
Chaînes de caractères
Longueur d'une chaîne unsigned strlen ( char* chaine)
Recherche d'une chaînechar* strstr (char* chain1, char* chain2)
Recherche de la chaine chain2 dans chaine1. Retourne l'adresse de la première occurrence de chaine2 dans chaine1
Recherche d'un caractèrechar* strchr (char* chain1, int n)
Recherche du caractère n dans chaine1. Retourne l'adresse de la première occurrence du caractère n dans chaine1
6161
Chaînes de caractères Découpage d’une chaine en motsChar* strtok(char* str, char* separateur)
Découpe la chaine str en mots. Les séparateurs sont définis par les caractères de la chaine separateur.
#include <stdio.h>#include <string.h>main() { char t1[256], t2[256]; char* p; int i =0;/* Les separateurs sont espace, virgule, point et point virgule et d'interrogation */ strcpy(t2, " ;.,."); strcpy(t1,"Voici une chaine. Vide? Non"); /*Iniitalisation du decoupage */ p=strtok(t1, " ;.,."); while (p!=NULL) { printf("mot numero %d : %s \n",i,p); i++; /* On cherche le mot suivant */ p= strtok(NULL, " ;.,."); }}
6262
Exo : Chaînes de caractères Ex1 : faire un programme qui initialise une chaine de caractères avec toutes les
lettres de l'alphabet et qui affiche la chaine obtenue. Quel caractère ne faut il pas oublier ?
Ex2 : en utilisant man 3 fgets faire un programme qui saisie un texte sur le terminal et qui l'affiche en retour
Ex3 : faire un programme qui demande le nom et le prénom à l'utilisateur et qui affiche “bonjour PRENOM NOM” en utilisant strcat.
Ex4 : faire un programme qui demande de deviner un chiffre entre 1 et 6 (tiré aléatoirement dans le programme). Lorsque le chiffre est trouvé le programme doit afficher : « il vous a fallu NOMBRE essai(s) pour trouver la solution ». Vous devez utiliser la fonction sprintf (man 3 sprintf) pour générer cette chaine.
63
XIII. Entrées / sorties sur fichiers
XIII.1. Introduction
XIII.2. Ouverture et fermeture d’un fichier
XIII.3. Lecture et écriture dans un fichier texte
XIII.4. Exercice
64
Pourquoi ?
Stockage des donnés dans un fichier
(nombres, caractères, …)
Acquisition de donnés nombreuses et complexes
Comment ?
Création d'un flux vers un fichier
Ouverture du fichier
Echange avec le fichier (lectures/écritures)
Fermeture du flux
XIII.1. Introduction
65
Deux types de fichiers : Fichier texte, fichier binaire
Quand vous affichez un nombre sur l'écran avec printf("%d",i)
i est une variable entière, codée sur 4 octets, en base 2.
Il y a création d’une chaîne de caractères contenant les chiffres du nombre (base 10)
Si i vaut 10, la chaîne contient les 3 caractères ’1’ , ’0’ et fin de chaîne
Si i vaut 256845, la chaîne contient les 7 caractères ’2’ ,’5’ ,’6’ ,’8’ ,’4’ ,’5’ et fin de chaîne.
Ensuite, le système écrit la chaîne à l'écran.
Les nombres affichés prennent un nombre d’octets différent, chaque chiffre est codé sur un
octet
mais ils sont lisibles facilement sur n’importe quelle machine (less, more, editeur de texte,
etc..)
Dans un fichier, si il se passe la même chose, ce sont alors des fichiers texte.
L’autre solution est d’écrire directement les 4 octets composant le nombre
Tous les entiers sont alors écrits avec 4 octets, sans aucune transformation
Ce sont des fichiers binaires,
on les lit difficilement directement. Il faut utiliser la commande od, et interpréter soi-même la
sortie
XIII.1. Introduction
66
Rappel : il existe deux types de fichiers :
Les fichiers texte qui contiennent des caractères
Exemple : un fichier source .c est un fichier texte
Exemple : un fichier image au format ppm est un fichier texte
Les fichiers binaires qui contiennent des données binaires
Exemple : un fichier exécutable .exe est un fichier binaire
Exemple : un fichier pdf .pdf est un fichier binaire
Exemple : un fichier image jpeg .jpg est un fichier binaire
En C, il est possible de travailler avec ces deux types de fichiers…
XIII.1. Introduction
67
XIII.1. Introduction
67
En plus, sur INTEL, dans un entier, les octets de poids faible sont en tete
et ceux de poids fort en queue.
Code ascii du caractère 6, puis de 5, de 5, de 3 et de 6
Texte (n1.txt) ou binaire (n1.bin)
68
Pour lire / écrire dans un fichier, il faut à chaque fois :
0. Inclure les headers nécessaires :
#include <stdlib.h>
#include <stdio.h>
1. Ouvrir le fichier avec la fonction fopen() qui renvoie un pointeur sur le fichier de
type FILE *.
2. Vérifier que l'ouverture s’est bien passé en testant la valeur du pointeur
qu'on a reçu. Si le pointeur vaut NULL, c'est que l'ouverture du fichier n'a pas
marché, dans ce cas on ne peut pas continuer (il faut afficher un message
d'erreur).
3. Si l'ouverture a marché alors on peut lire et/ou écrire dans le fichier
4. Une fois qu'on a terminé de travailler sur le fichier, il faut penser à le fermer
avec la fonction fclose().
XIII.1. Introduction
69
Pour travailler avec un fichier, il faut commencer par l’ouvrir en appelant la
fonction fopen() dont le prototype est :
FILE* fopen(const char* nomDuFichier, const char* modeOuverture);
nomDuFichier : une chaine de caractères qui contient le chemin vers le fichier
a ouvrir
modeOuverture : une indication qui dit si vous voulez juste écrire dans le fichier,
juste lire dans le fichier, ou les deux à la fois
fopen() renvoie un pointeur de type FILE *.
En cas d’échec, le pointeur renvoyé vaut NULL.
ATTENTION : toujours tester si l’ouverture s’est bien passée ( le pointeur
renvoyé est non NULL).
Quand on a fini de travailler avec un fichier, il faut toujours le fermer avec fclose():
int fclose(FILE * ptrVersFichier);
XIII.2. Ouverture et fermeture d’un fichier
70
Les modes d’ouverture à connaître sont :
"r" : ouverture en mode « fichier texte »
"b" : ouverture en mode « fichier binaire »
XIII.2. Ouverture et fermeture d’un fichier
71
Les modes d’ouverture à connaître sont :
"r" : lecture seule. Vous pourrez lire le contenu du fichier, mais pas écrire dedans. Le fichier
doit avoir été créé au préalable.
"w" : écriture seule. Vous pourrez écrire dans le fichier, mais pas lire son contenu. Si le
fichier n'existe pas, il sera créé.
"a" : mode d'ajout. Vous écrirez dans le fichier, en partant de la fin du fichier. Vous
rajouterez donc du texte à la fin du fichier. Si le fichier n'existe pas, il sera créé.
"r+" : lecture et écriture. Vous pourrez lire et écrire dans le fichier. Le fichier doit avoir été
créé au préalable.
"w+" : lecture et écriture, avec suppression du contenu au préalable. Le fichier est donc
d'abord vidé de son contenu, et vous écrivez et lisez ensuite dedans. Si le fichier n'existe
pas, il sera créé.
"a+" : ajout en lecture / écriture à la fin. Vous écrivez et lisez du texte à partir de la fin du
fichier. Si le fichier n'existe pas, il sera créé.
XIII.2. Ouverture et fermeture d’un fichier
72
int main() { FILE* filePtr =NULL ; // création du pointeur et initialisation filePtr = fopen("test.bin", "rb"); //ouverture en lecture seule, mode binaire de ./test.bin if (filePtr == NULL) { // vérification ouverture ! printf("erreur ouverture\n");
exit (-1); } // Maintenant, on peut lire dans le fichier… fclose(filePtr); // fermeture du fichier return 0;}
XIII.2. Ouverture et fermeture d’un fichier
int main() { FILE* filePtr =NULL ; // création du pointeur et initialisation filePtr = fopen("…/dossier/fichier.txt", "wt"); // ouverture en écriture seule, mode texte
// Si le fichier existe, son contenu est détruit ! if (filePtr == NULL) { // vérification ouverture ! printf("erreur ouverture\n");
exit (-1); } // Maintenant, on peut écrire dans le fichier… fclose(filePtr); // fermeture du fichier return 0;}
73
Une fois que le fichier est ouvert (on dispose d’une variable de type FILE * non
nulle) avec le modeOuverture adéquat, il est possible de lire et/ou écrire dans le
fichier.
La lecture / l’écriture d’un fichier texte est très similaire à ce qui se passe quand
on lit au clavier ou écrit à l’écran. Les prototypes des principales fonctions sont:
// lecture
int fscanf(FILE *stream, const char *format, ...); //comme scanf ! Retourne le nombre de données lues
int fgetc(FILE *stream);
char * fgets(char *s, int n, FILE *stream);
// ecriture
int fprintf(FILE *stream, const char *format, ...); //comme printf ! Retourne le nombre de données écrites
int fputs(const char *s, FILE *stream);
Notes : toutes opération de lecture / écriture dans un fichier avancent dans le fichier…
XIII.3. Lecture et écriture dans un fichier texte
74
Dans un fichier, les données sont organisées. C’est la notion de « format de fichier ». Pour
lire/écrire un fichier, il faut se mettre d’accord sur son format.
Le programme suivant lit un fichier contenant 3 valeurs entières, par exemple :
15 20 30 contenu du fichier test.txt (tel qu’affiché par exemple par gedit)
XIII.3. Lecture et écriture dans un fichier texte
int main() { FILE* fichier = NULL;int valeurs[3]; // Tableau ou on va stocker les 3 entiers du fichierint nbLus ;fichier = fopen("unRepertoire/test.txt", "r");if (fichier ==NULL ) { // verification erreur
printf("Impossible d'ouvrir le fichier test.txt\n");exit(-1);
}// si l’ouverture s’est bien passée…nbLus = scanf(fichier, "%d %d %d", &valeurs [0], &valeurs [1], valeurs + 2);printf( "Nombre de valeurs lues : %d\n", nbLus);printf("Les données du fichier sont : %d, %d et %d", valeurs[0], valeurs[1], valeurs[2]);fclose(fichier);return 0;
}
75
Rappel : Fichier binaire : copie de la représentation mémoire Ouverture d'un fichier binaire : flag “b” dans le mode d’ouverture
lecture dans un fichier binaire
int fread(void* t, int size, int nbel, FILE *fp); lecture de nb objets de taille size à partir du flux fp range les objets lus (nb * size octets) à l'adresse de l’objet t. Cette adresse est souvent celle d'un tableau. Retourne le nombre d'objets lus ou 0 si erreur ou fin de fichier Le tableau doit être alloué et contenir assez de place
Ecriture dans un fichier binaire
int fwrite(void *t, int size, int nbel, FILE *fp); ecriture de nb objets de taille size à partir du flux fp dans l’objet t. Retourne le nombre d'objets lus ou 0 si erreur ou fin de fichier
XIII.4. Lecture et écriture dans un fichier binaire
76
Dans un fichier binaire, on connait le nombre d’octets occupé par chaque donnée : c’est le nombre d’octet du type de la donnée 1 int = 4 octets, 1 double = 8 octets, ....
Accès direct à un élément possibleExemple : si on a a un fichier de double, tous les double occupent la même taille dans le fichieron sait calculer la position du i ième double par rapport au début du fichier !
Savoir où on est dans le fichier
long ftell(FILE *fp); Retourne la position courante dans le fichier, en nombre d’octets.
Se positionner dans le fichier à un octet donné
int fseek(FILE *fp, int offset, int from); Positionne la prochaine lecture/ecriture à offset octets de from Pour “from” : 0:debut du fichier, 2:fin du fichier ou 1:position actuelle)
XIII.4. Lecture et écriture dans un fichier binaire
7777
Exemple : lecture de n1.bin (5 entiers) et ecriture de x+1 dans n2.binmain() { FILE* f1, *f2; int i;
int t[5];
f1=fopen("n1.bin","rb");
f2=fopen("n2.bin","wb");
if (fread(t,sizeof(*t),5,f1) <5) /* Lecture des 5 entiers */
printf("Impossible de lire 5 entiers");
else { for (i=0;i<5;i++) t[i]=t[i]+1; /* on ecrit aussi t[i]++; */
if (fwrite(t,sizeof(*t),5,f2) <5) /* ecriture des 5 entiers */
printf("Impossible d'ecrire 5 entiers");
}
fclose(f1); fclose(f2);
}
On ecrit les 5 entiers en 1 seule opération
On lit les 5 entiers en 1 seule opération,
mais il faut que t puisse contenir ces 5 entiers
XIII.4. Lecture et écriture dans un fichier binaire
7878
Exemple de positionnement dans un fichier binaire avec int fseek(FILE *fp, int offset, int from);
Exemple : fichier5.cmain() {
FILE* fp; int a; /* Lecture ET ecriture */ fp=fopen("Fichier_a_lire","r+b"); fseek(fp,2*sizeof(*t),0); /* On se positionne sur le 3ième réel dans le file*/ a=16785; if (fwrite(&a,sizeof(*t),1,fp) <1) printf("Impossible d'ecrire"); /* On revient au debut du fichier */ fseek(fp,0,0); /* Lecture et affichage des nombres */ while (fread(&a,sizeof(a),1,fp) ==1) printf("%d ",a); puts("");
}}
65537
65536
127
1
Debut du fichier
123456789
16785
XIII.4. Lecture et écriture dans un fichier binaire
79
Écrire un programme qui :
Ouvre un fichier "notes.bin" contenant des float
Lit tous les entiers dans un tableau
Recherche la note minimum (réutiliser la fonction de l’exercice « recherche de
minimum »
L’affiche.
XIII.4 Exercice
8080
Exo : programme de traitement d'images
Nous utiliserons le format ppm Le programme doit être capable:
• De representer une image en memoire• D'initialiser une image • D'afficher la valeur des pixels à l'écran• De charger/écrire une image au format ppm vers/depuis un fichier ppm• D'effectuer différentes transformations : negatif, binarisation, etc.
Une image sera représentée par un grand tableau mono-dimensionel Commencez par organiser votre code en fichiers c et h Ecrivez votre makefile Definissez vos structures de données puis vos prototypes de fonctions Commencez par les fonctions les plus simples et compiler/tester regulièrement Pour les plus avancés, écrivez votre programme de façon à ce qu'il fonctionne
comme une commande du shell avec des arguments. Par ex: $ ./traiteimage negatif entree.ppm imagemodif.ppm où négatif est une option, entree.ppm est l'image à traitée et imagemodif est le
resultat du traitement