La présentation est en train de télécharger. S'il vous plaît, attendez

La présentation est en train de télécharger. S'il vous plaît, attendez

Séances de soutien Projet informatique 2A

Présentations similaires


Présentation au sujet: "Séances de soutien Projet informatique 2A"— Transcription de la présentation:

1 Séances de soutien Projet informatique 2A Francois.Portet@imag.fr
Ces transparents s’inspirent largement des cours 1A PET (M. Desvignes) et PMP (R Bressoux, E Moisan, N Castagne) Merci à eux 1

2 Planning prévisionnel
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

3 Tableaux 3 3

4 Tableaux 4 4 Tableaux Adresse
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.c main() { 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 tab[0]: 0 &tab[0] tab[1]: 2 &tab[1] tab[2]: 4 &tab[2] tab[9]: 18 &tab[9] 4 4 4

5 Tableaux 5 Adresse 5 Remarques Accès à un élément du tableau :
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 &tab[-2] tab &tab[0] 2 &tab[1] tab+1 tab+2 4 tab[2] tab+9 18 &tab[9] &tab[900] 5 5 5

6 Tableaux : accès et débordements
Adresse Exemple 2 : tab2.c main() { 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]); } &tab[-2] 0xbffff37c 0xbffff384 &tab[0] 2 &tab[1] 0xbffff388 4 &tab[2] 0xbffff38c 18 &tab[9] 0xbffff3a8 0xc &tab[900] 6 6 6

7 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.c main() {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]); } 7 7 7

8 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 8 8 8

9 Pointeurs Pointeurs 9 9

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

11 Pointeurs Pointeurs 11 11 Exemple : p1.c main() { 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); } Adresse 0xbffff388 p1=0xbffff38c p= &p 0xbffff38c i= i= &i 11 11 11

12 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 12 12 12

13 Adresse et tableaux 13 13 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)‏ 13 13 13

14 2 manières différentes d'ecrire l'adresse de t[i]
Adresses et tableaux Adresse Exemple : p3.c main() { 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]); } 0xbffff364 &tab[0] 2 &tab[1] 0xbffff368 4 &tab[2] 2 manières différentes d'ecrire l'adresse de t[i] 0xbffff36c 18 &tab[9] 0xbffff388 14 14 14

15 Parcourir un tableau avec un pointeur
Adresse Exemple : p4.c main() {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++; } 0xbffff384 tab[0]=234 1 &tab[0] 3 &tab[1] 0xbffff388 tab[1]=234 tab[2]=234 5 &tab[2] 0xbffff38c 7 tab[3]=234 &tab[3] 0xbffff390 9 tab[4]=234 &tab[4] 0xbffff394 0xbffff398 i= &i 0xbffff394 0xbffff39c p= 0xbffff390 0xbffff38c 0xbffff388 0xbffff384 &p 15 15 15

16 Structures, définition de types

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

18 POINTEUR peut remplacer int* partout
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 18 18

19 Les structures 19 Nom du type Nom de la variable
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 type struct ident1 { type nom_du_champ; .... } /* fin de structure */ Définition de variable : struct nomdestructure identif_var; struct complex { double im,re; }; struct complex a1; Nom du type Nom de la variable a1 a1.re a1.im 19 19

20 struct element_atomique
Structures (2)‏ x.nom X x.symbole x.numeroat x.masseato x.densité 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; Le type s’appelle struct element_atomique 20 20

21 Structure (3)‏ 21 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 0xbffff38c a2.x a2 a2.y -1 0xbffff394 0xbffff398 pa1 a1.x 0xbffff398 1 a1 a1.y -1 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); On définit une structure anonyme et un nouveau yype aff est une fonction qui utilise un T_POINT : elle a accès aux 2 coordonnées de a, a.x et a.y pa1 est un pointeur. Donc, pa1->x est la partie x de a1 21 21

22 Lecture clavier des 2 champs
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.c main() { 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 22 22

23 Structure (5)‏ Une structure est un type utilisable pour construire d’autres types On peut faire des tableaux de structures des structures de structures 23 23

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

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 VIII.1.1 En maths …
Une fonction mathématique retourne une valeur de sortie (un résultat) établie à partir de différents paramètres d’entrée (des arguments) : #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.

27 VIII.1. Introduction VIII.1.2 En programmation…
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é.

28 VIII.2. Le mécanisme de base
VIII.2.1. Exemple #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 la discriminant = y * y – 4 * x * z ; // fonction nommée discr // return discriminant ; // } int main( ) float a = 4. , b = 3. , c = , d , e ; d = discr ( a , b , c ) ; e = discr ( a , 3. , 9.) ; return 0 ; 2/ retour 1/ appel de la fonction discr Le main est la fonction qui est automatiquement appelée lors de l’exécution : c’est le point d’entrée du programme.

29 VIII.2. Le mécanisme de base
VIII.2.2. Déclaration d’une 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 nom de la fonction float discr ( float x , float y , float z ) { float discriminant ; discriminant = y * y – 4 * x * z ; return discriminant ; } Variable locale 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. Le résultat renvoyé par la fonction. Ici, c’est la valeur de la variable locale discriminant

30 VIII.2. Le mécanisme de base
VI.2.3. Appel de fonction 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. 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 ; }

31 Représentation de la mémoire
VIII.2. Le mécanisme de base VIII.2.4. ce qui se passe… Avant l’appel de fonction 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 ; Représentation de la mémoire a = 4 b = 3 d = ???

32 VIII.2. Le mécanisme de base
VIII.2.4. ce qui se passe… Appel de la fonction : copie des valeurs des paramètres effectifs dans de nouvelles variables 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, y et z sont les « paramètres » discriminant est une « variable locale » a = 4 y = 3 z = -1,5 discriminant = ??? a = 4 b = 3 d = ???

33 Elle est ailleurs en mémoire !
VIII.2. Le mécanisme de base VIII.2.4. ce qui se passe… Appel de la fonction : copie des valeurs des paramètres effectifs dans de nouvelles variables 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 Ce n’est pas la même variable   "a" ! Elle est ailleurs en mémoire ! ! y = 3 z = -1,5 discriminant = ??? a = 4 b = 3 d = ???

34 VIII.2. Le mécanisme de base
VIII.2.4. ce qui se passe… Appel de la fonction. Ca se passe comme d‘habitude dans le « main » 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 a = 4 b = 3 d = ???

35 VIII.2. Le mécanisme de base
VIII.2.4. ce qui se passe… Fin d’appel de fonction : destruction de toutes les variables locales et paramètres 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 ; discriminant = 33 d prend la valeur retournée a = 4 b = 3 d = 33

36 VIII.2. Le mécanisme de base
VIII.2.5. Notion de portée de variable les variables sont locales au bloc ou elles sont définies ( entre { } ) Ce code ne compile pas car : 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 ! ! Rappel : Ce n’est pas le même a ! Ce n’est pas le même b ! Erreur : "d" n’est pas définie ici ! !

37 VIII.2. Le mécanisme de base
VIII.2.6. return 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 ; else return b ; }

38 VIII.2. Le mécanisme de base
VIII.2.7. Type void 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 void if ( 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.

39 VIII.3. Passage « par valeur » et « par adresse »
VIII.3.1. Le problème Écrire une fonction qui échange les valeurs de deux variables a et b de type int. #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 ?

40 VIII.3. Passage « par valeur » et « par adresse »
VIII.3.1. Le problème 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 ;

41 ! VIII.3. Passage « par valeur » et « par adresse »
VIII.3.1. Le problème 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.

42 VIII.3. Passage « par valeur » et « par adresse »
VIII.4.3. La solution Les pointeurs viennent à notre secours : #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 ;

43 ! VIII.3. Passage « par valeur » et « par adresse »
VIII.3.2. La solution 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.

44 VIII.3. Passage « par valeur » et « par adresse »
VIII.3.3. Exemple Appel de la fonction : copie des valeurs des paramètres effectifs dans de nouvelles variables valeurs adresses 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 ; 0xbffff388 p_a = 0xbffff3b0 0xbffff3bc p_b =0xbfffff3b4 p_b vaut l’adresse de b. donc *p_b vaut 2 0xbffff3c0 temp = 2 0xbffff3b0 a = 1 0xbffff3b4 b = 2

45 VIII.3. Passage « par valeur » et « par adresse »
VIII.3.3. Exemple Appel de la fonction : copie des valeurs des paramètres effectifs dans de nouvelles variables valeurs adresses 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 ; 0xbffff388 p_a = 0xbffff3b0 0xbffff3bc *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) p_b =0xbfffff3b4 0xbffff3c0 temp = 2 0xbffff3b0 a = 1 0xbffff3b4 b = 1

46 ! VIII.4. Passage par valeur et par adresse VIII.3.4. Résumé
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”.. !

47 VIII.4. Notion de contrat de fonction
VIII.4.1. Introduction 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.

48 VIII.4. Notion de contrat de fonction
VIII.4.2. Exemple 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 : Prototype Contrat

49 VIII.4. Notion de contrat de fonction
VIII.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 fonction return 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 VIII.4. Notion de contrat de fonction
VIII.4.4 Structure générale d’un programme /* ******************************************************* 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

51 Exercice 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.

52 Compilation séparée et Makefile
52 52

53 Exercice 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

54 Les chaines de caractères
54 54

55 les autres valeurs sont aléatoires car on ne les a pas entrées
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.c main() {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); } Adresse 0xbffff2e0 ’u’ &s[0] 0xbffff3e1 ’n’ &s[1] ’e’ &s[2] ’\0’ &s[3] les autres valeurs sont aléatoires car on ne les a pas entrées ???? 0xbffff3df ???? &s[255] 55 55 55

56 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]); } 56 56 56

57 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 57 57 57

58 Chaînes de caractères 58 58 Copie d'une chaîne
char* 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); 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.c char* 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 exemple main() { char t1[256], t2[256], t3[256]; strcat(strcpy(t2,t1),".obj"); strcat(strcpy(t3,t1),".exe"); puts(t1); puts(t2); puts(t3); 58 58 58

59 Chaînes de caractères 59 59 Formation/extraction dans des chaines
int 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," truc "); sscanf(t1,"%d %f %s %lf ",&i,&x,t2,&y); sscanf(t1, "%d %f %lf ",i,x,y) ; } Comparaison de 2 chaînes int strcmp (char* chain1, char* chain2)‏ Retourne : un entier négatif si chain1 inférieure à chain2 0 si chain1 identique à chain2 un 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 octets int strncmp(char* chain1, char* chain2, int n)‏ /*n= Nombre de caractères à comparer */ 59 59 59

60 Chaînes de caractères 60 60 Longueur d'une chaîne
unsigned strlen ( char* chaine)‏ Recherche d'une chaîne char* 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ère char* 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 60 60 60

61 Chaînes de caractères 61 61 Découpage d’une chaine en mots
Char* 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, " ;.,."); } 61 61 61

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

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 XIII.1. Introduction 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

65 XIII.1. Introduction 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 , 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

66 XIII.1. Introduction 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…

67 XIII.1. Introduction Texte (n1.txt) ou binaire (n1.bin)‏
En plus, sur INTEL, dans un entier, les octets de poids faible sont en tete et ceux de poids fort en queue. 67 Code ascii du caractère 6, puis de 5, de 5, de 3 et de 6

68 XIII.1. Introduction 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().

69 XIII.2. Ouverture et fermeture d’un fichier
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);

70 XIII.2. Ouverture et fermeture d’un fichier
Les modes d’ouverture à connaître sont : "r" : ouverture en mode « fichier texte » "b" : ouverture en mode « fichier binaire »

71 XIII.2. Ouverture et fermeture d’un fichier
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éé.

72 XIII.2. Ouverture et fermeture d’un fichier
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; 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 XIII.3. Lecture et écriture dans un fichier texte
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…

74 XIII.3. Lecture et écriture dans un fichier texte
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 :  contenu du fichier test.txt (tel qu’affiché par exemple par gedit) int main() { FILE* fichier = NULL; int valeurs[3]; // Tableau ou on va stocker les 3 entiers du fichier int 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 XIII.4. Lecture et écriture dans un fichier binaire
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. 75 75

76 XIII.4. Lecture et écriture dans un fichier binaire
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 possible Exemple : si on a a un fichier de double, tous les double occupent la même taille dans le fichier on 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) 76 76

77 XIII.4. Lecture et écriture dans un fichier binaire
Exemple : lecture de n1.bin (5 entiers) et ecriture de x+1 dans n2.bin main() { 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 lit les 5 entiers en 1 seule opération, mais il faut que t puisse contenir ces 5 entiers On ecrit les 5 entiers en 1 seule opération 77 77 77

78 XIII.4. Lecture et écriture dans un fichier binaire
Exemple de positionnement dans un fichier binaire avec int fseek(FILE *fp, int offset, int from); Exemple : fichier5.c main() { 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(""); } Debut du fichier 1 127 65536 16785 65537 78 78 78

79 XIII.4 Exercice É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.

80 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 80 80 80


Télécharger ppt "Séances de soutien Projet informatique 2A"

Présentations similaires


Annonces Google