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

Convention sur les acétates

Présentations similaires


Présentation au sujet: "Convention sur les acétates"— Transcription de la présentation:

0 Premier cours Présentation du chargé de cours, du professeur et du chargé de laboratoire Présentation du plan de cours déroulement de la session acétates,notes de cours, exercices, livres recommandés Niveau du cours Difficulté des examens Déroulement des laboratoires Dynamisme et implication dans la classe Avertissement sur la révision du langage C (les étudiants avancées peuvent sont encouragées à rester) Début de la présentation des acétates Présentation des premiers laboratoires (prévoir au moins 45 min)

1 Convention sur les acétates
Concernant la présentation des acétates : malgré le fait qu’elles soient relativement bien remplies, les acétates tentent de présenter un contenu : cohérent facile à suivre avec un contenu pouvant servir de référence Concernant les exemples : plusieurs exemples sont inclus afin d’illustrer les différents concepts enseignés certains exemples contiennent quelques notions plus avancés à titre de référence afin d’illustrer davantage les notions du langage C, peu d’exemple sont en pseudo code

2 Convention sur les acétates
Coloration du texte en fonction des éléments du langage C Mots réservés en bleu Constantes en rouge Instructions en mauve Commentaires en vert Format des exemples Stéréotypes de : déclarations, fonctions, éléments de langage, ... Exemples de : déclarations, fonctions, éléments de langage, portions de code, programmes, ... Exemple de pseudo code pour représenter des : algorithmes, éléments logiques, ...

3 Généralité du langage C
Importance de la programmation Que permet la programmation? Outil technologique incomparable Donne une longueur d’avance à ceux qui la connaisse Exemples concrets d’application Informatique « utilitaire » Automatisation de tâches routinières () Informatique de gestion Gestion de projet gestion des dépassements de coût, gestion des feuilles de temps, ... Gestion des ressources – gestion du matériel, gestion du personnel, ... Gestion de l’information – sauvegarde d’information, base de connaissance, ... Informatique industriel Contrôle de procédé contrôle industriel, optimisation de systèmes de production, ... Manipulation de données acquisition, interprétation, prise de décision, représentation, ... robotique industriel, représentation graphique, vision, réseaux de neurones, *** Cette acétate a pour mandat de justifier la pertinence du cours et de pousser davantage la stimulation des étudiants Que permet la programmation ? Permet de créer des outils informatiques puissants permettant la manipulation d’information Outil technologique incomparable Aucun autre outil ne peut le faire : irremplaçable Donne une longueur d’avance à ceux qui la connaisse En tant qu’ingénieur et surtout en GPA : - Travailler avec une équipe multidisciplinaire - Faire la soumission d’un projet multidisciplinaire - Travailler étroitement avec une équipe de développement logiciel - Réaliser de simples programmes ou de simples macros permettant d’automatiser des tâches routinières - Évidemment… développer des applications robustes et performantes

4 Généralité du langage C
Historique 1960 – Algol60 très abstrait, donne le Pascal, PL/I et CPL 1967 – BCPL par Martin Richards Basic Combined Programming Language 1970 – Langage B par Ken Thompson afin d’assurer l’évolution de Unix écrit en assembleur, son créateur crée ce langage inspiré du BCPL 1972 – Langage C par Dennis Ritchie et Ken Thompson après modification du langage B

5 Généralité du langage C
Près du langage machine initialement destiné à la programmation de système assembleur évolué Langage typé faible permet l’affectation de types différents contrairement au langage C++

6 Généralité du langage C
Constituants d’un programme Essentiel Fonction main Utilisation d’instructions de contrôle, de branchement et d’itération Utilisation d’instructions d’accès mémoire, d’arithmétique et de logique Possible Plusieurs fichiers sources référencés par #include Déclaration, définition et utilisation de fonctions personnalisées Appel de fonctions provenant des déclarations personnalisées bibliothèques standard

7 Généralité du langage C
Exemple d’un programme très simple #include "stdio.h" /* début du programme */ void main(void) { printf("Ceci est un programme simple!"); return; } Ceci est un programme simple! - aucune entrée - Entrée Sortie

8 Généralité du langage C
Exemple d’un programme #include "stdio.h" /* début du programme */ void main(void) { char C; do { C = getch(); printf("%c - %d - %x\n", C, C, C); } while (C != EOF); return; } G P A , c J a ' a d o f r e GPA665, J’adore Entrée Sortie

9 Généralité du langage C
Exemple d’un programme #include "stdio.h" /* début du programme */ void main(void) { int i, n, Sum; float Mean; n = 15; Sum = 0; Mean = 0.0f; for (i = 0; i < n; i++) { Sum += i; printf("Somme cumulative a l'element %02d : %03d\n", i, Sum); } Mean = (float) Sum / n; printf("\nMoyenne des %d element(s) : %0.2f", n, Mean); return; Somme cumulative a l'element 00 : 000 Somme cumulative a l'element 01 : 001 Somme cumulative a l'element 02 : 003 Somme cumulative a l'element 03 : 006 Somme cumulative a l'element 04 : 010 Somme cumulative a l'element 05 : 015 Somme cumulative a l'element 06 : 021 Somme cumulative a l'element 07 : 028 Somme cumulative a l'element 08 : 036 Somme cumulative a l'element 09 : 045 Somme cumulative a l'element 10 : 055 Somme cumulative a l'element 11 : 066 Somme cumulative a l'element 12 : 078 Somme cumulative a l'element 13 : 091 Somme cumulative a l'element 14 : 105 Moyenne des 15 element(s) : 7.00 - aucune entrée - Entrée Sortie

10 Généralité du langage C
Compilateur et éditeur de liens *.a pour les archives (obj provenant des bibliothèques)

11 Types de base 5 types de base 2 modificateurs de type
char, int, float, double et void 2 modificateurs de type Signe (pour les entiers seulement char et int : où b représente le nombre de bit) signed (par défaut) : variable  [ -2b/2, 2b/2 – 1 ] unsigned : variable  [ 0, 2b – 1] Taille mémoire short : réduit le domaine des valeurs possibles uniquement pour int long : augmente l’intervalle [min, max] uniquement pour int et float selon la norme ANSI certains compilateurs supportent aussi le type double Attention au type bool

12 Types de base Le type char Un seul type, deux interprétations :
caractère lorsque utilisé en conjonction avec les fonctions de gestion de chaîne de caractères et la table Ascii entier autrement

13 Types de base Le type int (16 bits) ou short int

14 Types de base Le type int (32 bits) ou long int

15 Types de base Le type float

16 Types de base Le type long float ou double

17 Types de base Le type long double Ce n’est pas un type standard du C ou du C++, il est seulement disponible sur certains compilateurs (par exemple : VisualC++ et C++Builder)

18 Types de base Le type void Ne permet pas de déclarer une variable
Permet d’indiquer au compilateur que le type est indéfini pour les cas suivants : retour de fonction (aucune valeur de retour) paramètres d’une fonction (aucun paramètre passé à la fonction) pointeur de type indéfini

19 Éléments du langage et règles d’écriture
Les identificateurs Sert à nommer les : variables étiquettes fonctions Constitués d’une suite de lettres et de chiffres commençant par une lettre le seul autre caractère permis : '_' Il faut faire attention à : le langage C distingue les majuscules des minuscules la liste des mots réservés Les types externes peuvent être restreint à un maximum de 8 caractères pour certains compilateurs

20 Éléments du langage et règles d’écriture
Les mots réservés du langage C auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while

21 Éléments du langage et règles d’écriture
Les commentaires Pour déterminer une zone de commentaires, on utilise : /* pour le début */ pour la fin Attention aux : imbrications de commentaires commentaires commençant par //

22 Éléments du langage et règles d’écriture
Les instructions Appel d’un opérateur ou d’une fonction Plusieurs instructions peuvent être regroupées par un bloc Les blocs d’instructions Défini par des accolades { marque le début d’un bloc } marque la fin d’un bloc { /* début d’un bloc d’instructions */ /* instruction 1 */ /* */ /* instruction n */ } /* fin d’un bloc d’instructions */

23 Éléments du langage et règles d’écriture
Les expressions Correspondent à une combinaison d’éléments tel que des : identificateurs constantes variables tableaux pointeurs structures unions appels de fonctions opérateurs unaires ou binaires ... chaque ligne de code excluant celles ayant uniquement des commentaires

24 Éléments du langage et règles d’écriture
Les constantes Entier : octal : commençant par un zéro décimal : tel quel hexadécimal : commençant par un zéro et un x 0xCAFE les majuscules et minuscules fonctionnent 0Xcafe Nombre réel de type float : terminant par f f de type double : tel quel

25 Éléments du langage et règles d’écriture
Les constantes Caractère(s) un seul caractère selon la table de caractères ASCII : entre apostrophes '0' le caractère zéro est équivalent à l’entier 48 une chaîne de caractères respectant les normes établies : entre guillemet "GPA665" la chaîne de caractères "GPA665" correspond au tableau de caractères : il existe plusieurs caractères spéciaux : \a - beep - \\ barre oblique inverse \b retour arrière \' apostrophe \f chargement de page \" guillemet \n saut de ligne \? point d’interrogation \r retour en début ligne \nnn valeur ASCII en octal \t tabulation horizontale \xnnn valeur ASCII en hexadécimal \v tabulation verticale \0 fin de la chaîne de caractère

26 Éléments du langage et règles d’écriture
Les opérateurs Unaires – Sont évalués de droite à gauche ++ var effectue une pré incrémentation -- var effectue une pré décrémentation var ++ effectue une post incrémentation (de gauche à droite) var -- effectue une post décrémentation (de gauche à droite) (type) exp spécifie une modification de type (« type cast ») Sizeof() ou sizeof(var ou type) retourne la taille en octet d’une variable ou d’un type & var retourne l’adresse de la variable * exp retourne le contenu de l’adresse spécifiée (déréférence) + exp retourne la valeur d’une variable (type numérique) - exp retourne la valeur négative d’une variable (type numérique) ~ exp retourne le complément bit à bit d’une variable ! exp retourne le complément logique d’une variable Donner des exemples et expliquer plus en détail

27 Éléments du langage et règles d’écriture
Les opérateurs Binaires – Sont évalués de gauche à droite Opérateurs multiplicatifs exp1 * exp2 retourne le résultat de la multiplication exp1 / exp2 retourne le résultat de la division exp1 % exp2 retourne le résultat du modulo (reste de la division entière) Opérateurs additifs exp1 + exp2 retourne le résultat de l’addition exp1 - exp2 retourne le résultat de la soustraction Opérateurs de décalage de bits exp1 << exp2 retourne le résultat du décalage à gauche * exp1 >> exp2 retourne le résultat du décalage à droite * Opérateurs relationnels exp1 < exp2 retourne le résultat de la comparaison est plus petit que exp1 > exp2 retourne le résultat de la comparaison est plus grand que exp1 <= exp2 retourne le résultat de la comparaison plus petit ou égal exp1 >= exp2 retourne le résultat de la comparaison plus grand ou égal exp1 == exp2 retourne le résultat de la comparaison est égal à exp1 != exp2 retourne le résultat de la comparaison est différents de Donner des exemples et expliquer plus en détail

28 Éléments du langage et règles d’écriture
Les opérateurs Binaires – Sont évalués de gauche à droite (suite) Opérateurs sur les bits exp1 & exp2 retourne le résultat du ET logique bit à bit exp1 | exp2 retourne le résultat du OU logique bit à bit exp1 ^ exp2 retourne le résultat du OU EXCLUSIF logique bit à bit Opérateurs logiques exp1 && exp2 retourne le résultat du ET logique exp1 || exp2 retourne le résultat de OU logique Opérateur d’évaluation séquentielle exp1 , exp2 effectue les expressions séquentiellement Ternaire – Est évalué de gauche à droite exp ? val1 : val2 retourne val1 si exp est vraie sinon val2 Donner des exemples et expliquer plus en détail

29 Éléments du langage et règles d’écriture
Les opérateurs D’assignation – Sont évalués de droite à gauche var = exp assignation directe var *= exp assignation suite à la multiplication var /= exp assignation suite à la division var %= exp assignation suite au modulo var += exp assignation suite à l’addition var -= exp assignation suite à la soustraction var <<= exp assignation suite au décalage de bits à gauche var >>= exp assignation suite au décalage de bits à droite var &= exp assignation suite au ET logique bit à bit var |= exp assignation suite au OU logique bit à bit var ^= exp assignation suite au OU EXCLUSIF logique bit à bit Ils sont tous équivalent à : var = var opérateur exp Donner des exemples et expliquer plus en détail

30 Éléments du langage et règles d’écriture
Les opérateurs Autres opérateurs ( exp ) - appel de fonction - spécification de priorité dans une expression [ exp ] - retourne un élément spécifique d’un tableau (décalage plus déréférence) var . élément - retourne l’élément spécifié d’une structure (indirection) ou d’un type union var -> élément - retourne l’élément spécifié par le pointeur d’une structure (indirection) Donner des exemples et expliquer plus en détail

31 Éléments du langage et règles d’écriture
Priorité des opérateurs Ordre Opérateurs 1 ( ) [ ] > 2 (signe) -(signe) *(déréférence) &(adresse) ! ~ (type)(« type cast ») sizeof 3 *(multiplication) / % 4 +(addition) -(soustraction) 5 >> << 6 == != 7 & 8 ^ 9 | 10 && 11 || 12 ? : 13 = += -= *= /= %= &= ^= |= <<= >>= 14 , Donner des exemples et expliquer plus en détail Si deux opérateurs possèdent la même priorité, ils seront exécuté de gauche à droite selon l’ordre d’apparition sur la ligne d’instruction

32 Éléments du langage et règles d’écriture
Les instructions de contrôle L’instruction conditionnelle if Attention aux if imbriqués. Utilisez les définitions de bloc. if ( exp ) inst1 [ else inst2 ] if (n != 0) { Mean = Sum / n; } else { printf("Erreur : division par 0"); }

33 Éléments du langage et règles d’écriture
Les instructions de contrôle L’instruction conditionnelle switch switch ( integer-exp ) { [ case constexp1 : inst1 [ break ;] ] ... [ case constexpn : instn [ break ;] ] [ default : instd ] }

34 Éléments du langage et règles d’écriture
#include <stdio.h> #include <ctype.h> #include <conio.h> #include "Customfunctions.h" void main(void) { while (1) { printf("Quelle operation voulez-vous faire ?\n"); printf("\t1 (Q)uitter\n\t2 (S)auvegarder\n\t 3 (P)oursuivre\n\t4 (M)ettre en veille\n"); char Reponse = toupper(getch()); /* Attention */ switch (Reponse) { case 1 : case 'Q' : return; case 2 : case 'S' : SaveData(); case 3 : case 'P' : GetNewData(); AnalyseNewData(); break; case 4 : case 'M' : GotoSleep(); }

35 Éléments du langage et règles d’écriture
Les instructions de contrôle L’instruction itérative while while ( exp ) inst while (Answer == 'Y' || Answer == 'y') { DoSomething(); printf("Voulez-vous poursuivre?"); Answer = getch(); }

36 Éléments du langage et règles d’écriture
Les instructions de contrôle L’instruction itérative do do inst while ( exp ); do { DoSomething(); printf("Voulez-vous poursuivre?"); Answer = getch(); } while (Answer != 'N' && Answer != 'n');

37 Éléments du langage et règles d’écriture
Les instructions de contrôle L’instruction itérative for for ( inst1; exp; inst2 ) inst3 #define N 65535 int i, Sum, Data[N]; for ( i = 0, Sum = 0; i < N; i++) { Sum += Data[i]; }

38 Éléments du langage et règles d’écriture
Les instructions de contrôle Les commandes de terminaison sont : break termine l’instruction do, for, switch ou while le plus près continue passe à la prochaine itération des instructions do, for ou while goto passe directement à l’étiquette spécifiée

39 Éléments du langage et règles d’écriture
La déclaration d’étiquettes La fonction goto doit indiquer une étiquette existante dans le programme. Les étiquettes sont définies par le deux points Les étiquettes doivent obligatoirement être mises devant une instruction pour être valide Malgré le fait qu’elle est très puissante, l’instruction goto n’est pratiquement jamais recommandée. identificateur : Erreur007:

40 Éléments du langage et règles d’écriture
La déclaration de nouveaux types Types synonymes avec typedef En fait, ceci ne permet pas la déclaration d’un nouveau type mais plutôt d’un synonyme pratique ou pertinent pour l’usager Types énumérés avec enum Type consistant à contraindre l’affectation de variable à un ensemble de constantes appelées énumérateurs typedef declaration-de-type synonyme; typedef unsigned int uint; typedef unsigned char uchar; typedef uchar Pixel; enum [ type ] { liste-enumerations } [ variable ] ; typedef enum [ type ] { liste-enumerations } [ type ] ; enum JourSemaine { Lundi, Mardi, Mercredi, Jeudi, Vendredi } AujourdHui; typedef enum { Rouge, Vert, Bleu } CouleursPrimairesAdditif; typedef enum CouleursPrimaireSoustractif { Jaune, Magenta, Cyan };

41 Éléments du langage et règles d’écriture
La déclaration de nouveaux types Types structurés avec struct Nouveau type permettant de contenir plusieurs types existants (sous-types) simultanément pour une même variable. Tous les sous-types sont disponibles simultanément. La taille du nouveau type correspond au minimum à la somme des tailles de chacun des sous-types. struct [ type ] { liste-membres } [ variable ] ; typedef struct [ tag ] { liste-membres } type ; struct Individu { char Nom[64]; int Age; float Taille; } Nicolas, Yan, Philippe; typedef struct refNode { float Data; refNode *Next; } LinkedListNode;

42 Éléments du langage et règles d’écriture
La déclaration de nouveaux types Types unis avec union Nouveau type permettant de contenir différents types existants (sous-types) pour une même zone mémoire. Puisque tous les sous-types partagent la même zone mémoire, un seul de ces sous-types est disponible à la fois. La taille du nouveau type correspond au minimum à la taille du plus grand sous-type. union [ type ] { liste-membres } [ variable ] ; typedef union [ tag ] { liste-membres } type ; union Data { float LowPrecision; double HighPrecision; } Temperature; typedef union { char Answer; float Value; } QuestionResult;

43 Éléments du langage et règles d’écriture
La déclaration de variables La déclaration d’une seule variable : La déclaration d’un tableau unidimensionnel : La déclaration d’un tableau multidimensionnel : on reviendra plus tard sur les classes de mémorisation [ classe_de_mémorisation ] type identificateur ; static int Sum; type identificateur[ taille du tableau ] ; float Vector[256]; type identificateur[dim1][dim2][dim_n]... ; unsigned char ColorImage[640][480][3];

44 Éléments du langage et règles d’écriture
La déclaration de variables La déclaration d’un pointeur : On peut déclarer plusieurs variables simultanément : Attention à la déclaration simultanée de pointeurs! [ classe_de_mémorisation ] type * identificateur ; int *PtrData; type ident1, ident2, ident3; float n, Sum, Mean, StdDev, Data[256];

45 Éléments du langage et règles d’écriture
La déclaration de variables La déclaration d’une variable constante : Une variable constante doit être initialisée à même sa déclaration et ne peut jamais être modifiée. [ classe_de_mémorisation ] const type identificateur = valeur; const float PI = ;

46 Éléments du langage et règles d’écriture
L’initialisation de variables Selon la norme ANSI, le langage C n’initialisent aucun type automatiquement lors de l’instanciation de variables Malgré tout, les compilateurs modernes comme VisualC++ initialisent à 0 uniquement les variables provenant des types de base char, int, float et double les pointeurs, tableaux et types personnalisés ne sont pas initialisés malgré tout, faites attention aux optimisateurs de code

47 Éléments du langage et règles d’écriture
L’initialisation de variables Les variables provenant des types de base peuvent être explicitement initialisées lors de leur déclaration Pour les variables standard : Pour les variables de type union Pour les variables de type struct [ classe_de_mémorisation ] type identificateur = exp; int IPos = 10, INeg = -10; void *RefPtr = NULL; [ classe_de_mémorisation ] type identificateur = { exp }; typedef union { float NbrReel; int NbrEntier; } Nombre; Nombre N1 = { f }, N2 = { 101 }; // les bons types ont été assignés [ classe_de_mémorisation ] type identificateur = { exp1, exp2, ... }; typedef struct { char Nom[256]; int Age; float Taille; } Individu; Individu Pianiste = { "Julius Katchen", 43, 1.80 };

48 Éléments du langage et règles d’écriture
L’initialisation de variables Pour un tableau unidimensionnel : Pour un tableau multidimensionnel : type identificateur[ [taille du tableau] ] = { exp1, exp2, exp3, ... }; float ResultatsLabGPA665[3] = { 89.0f, 97.0f, 100.0f }; float ResultatsExamGPA665[] = { 78.0f, 84.5f }; /* intéressant */ type ident[dim 1][dim n]... = { ... { exp1, ... }, { expn, ... }, ... }; int Input[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };

49 Éléments du langage et règles d’écriture
Éléments de langage particuliers aux pointeurs Opérateurs d’adressage et de déréférence On utilise & pour obtenir l’adresse d’une variable ou d’une fonction préalablement allouée On utiliser * (la déréférence) pour accéder au contenu « pointé » par une adresse int Nombre1, Nombre2; int *PtrNbr; Nombre1 = 666; PtrNbr = &Nombre1; Nombre2 = *PtrNbr;

50 Éléments du langage et règles d’écriture
Éléments de langage particuliers aux types composés Accès aux membres des types composés union ou struct On utilise l’opérateur d’indirection . (point) pour accéder à un membre particulier Pour les accès via un pointeur, on peut utiliser : une combinaison du point et de la déréférence l’opérateur d’indirection -> typedef struct { char Nom[256]; int Age; float Taille; } Individu; Individu Employe1, Employe2, *RefEmp1, *RefEmp2; Ref1Emp = &Employe1; Ref2Emp = &Employe2 RefEmp1->Age = 2 * (*RefEmp2).Age;

51 Éléments du langage et règles d’écriture
Éléments de langage particuliers aux tableaux Accès aux éléments d’un tableau On utilise l’opérateur de déréférence avec décalage [ ] pour accéder à un élément spécifique d’un tableau On peut aussi utiliser l’opérateur de déréférence avec l’arithmétique des pointeurs float NotesExam[] = { 84.0f, 68.0f, 90.5f }; float PonderationsExam[] = { 0.30f, 0.30f, 0.40f }; float ResultatFinal = 0.0f; for (int i = 0; i < 3; i++) { /* Attention a la declaration de i */ ResultatFinal += NotesExam[i] * PonderationsExam[i]; }

52 Éléments du langage et règles d’écriture
Éléments de langage particuliers typedef char String[256]; typedef union { String Nom; int NoRef; } IDRef; struct Individu { IDRef ID; int Age; String SignesDistinctifs; } Professeur, ChargeDeCours, ChargeDeLab; Individu *Ref[3] = { &Professeur, &ChargeDeCours, &ChargeDeLab }; strcpy(Professeur.ID.Nom, "Mohamed Cheriet"); ChargeDeCours.ID.NoRef = 12345; strcpy(ChargeDeLab.ID.Nom, "Yan Levasseur"); ChargeDeLab.Age = 25; strcpy(ChargeDeLab.SignesDistinctifs, "Intellectuellement superieur"); (*Ref[0]).Age = Ref[2]->Age << 1;

53 Éléments du langage et règles d’écriture
Les fonctions Les fonctions sont des entités permettant de regrouper plusieurs instructions et expressions en un sous-ensemble disjoint, autonome et fonctionnel. Entre autre, elles aident à la lisibilité, la modularité et la réutilisabilité du code /* Syntaxe moderne */ [ classe-mémorisation ] type-retour identificateur( [ type1 param1, ... ] ) { exp1 exp2 ... [ return [ ( ] [ exp-retour ] [ ) ] ; ] } On revient de façon très détaillée sur les fonctions plus loin dans les acétates float Square(float Data) { return Data * Data; }

54 Éléments du langage et règles d’écriture
Les instructions au préprocesseur Ces instructions servent à donner des instructions préalables au compilateur. Elles sont faciles à reconnaître car elles sont tous précédés de # Inclusion de fichiers avec #include. Permet de faire référence au contenu d’un ou plusieurs autres fichiers externes. #include "nom_de_fichier" /* recherche locale + catalogue système */ #include <nom_de_fichier> /* recherche catalogue système uniquement */ #include "mes_propres_fonctions.h" #include <math.h> void main(void) { printf("Le racine carre de 9 est : %lf", sqrt(9.0)); printf("La racine cubique de 27 est : %lf", cubicroot(27.0)); }

55 Éléments du langage et règles d’écriture
Les instructions au préprocesseur Les commandes de compilation conditionnelle sont : #if équivalent du if #elif équivalent à un else if #else équivalent au else #endif indique la terminaison du bloc if #ifdef si une étiquette est définie #ifndef si une étiquette n’est pas définie #if exp1 inst1 #elif exp2 inst2 #else inst3 #endif

56 Éléments du langage et règles d’écriture
Les instructions au préprocesseur #define et #undef permettent de déclarer de nouvelles définitions ou d’annuler des définitions existantes. Ces définitions permettent au programmeur de nommer des éléments du programme comme des constantes et des étiquettes de référence. #define identificateur expression #undef identificateur Constantes.h #ifndef _CONSTANTES_H #define _CONSTANTES_H #define PI #endif Programme.c #include "Constantes.h" void main(void) { printf("Le nombre pi est : %0.6lf", PI); }

57 Éléments du langage et règles d’écriture
Les instructions au préprocesseur #define permet aussi de définir des macro. Les macros ressemblent à des fonctions sans définition de type. #define nom_macro(param1, param2, ...) expression #define Max(Val1, Val2) ((Val1) > (Val2) ? (Val1) : (Val2)) void main(void) { int N1 = 100, N2 = 500; printf("Le maximum entre les nombres %d et %d est : %d", N1, N2, Max(N1, N2)); }

58 Éléments du langage et règles d’écriture
Les instructions au préprocesseur Pourquoi éviter les macros ? En fait, l’utilisation du #define correspond à un copier/coller avant la compilation. #define Max(Val1, Val2) ((Val1) > (Val2) ? (Val1) : (Val2)) #define Square(Val) a*a void main(void) { int A, *B; A = Max('a', B); /* Compile malgré les types incompatibles */ int i = 1, j = 2; A = Max(++i,++j); /* On s’attend à 3 alors qu’on obtient 4 */ double X = 3, Y; Y = Square(X + 1); /* On s’attend à 16 alors qu’on obtient 7 */ }

59 Principales fonctions provenant des bibliothèques standard
stdio.h fopen ouverture d’un fichier fclose fermeture d’un fichier ferror vérifie si il y a eu une erreur avec un fichier feof vérifie si un fichier est arrivé à la fin fgetpos détermine un indicateur pour la position courante d’un fichier fsetpos positionne un fichier selon un indicateur de position ftell retourne la position d’un fichier fseek positionne un fichier selon un décalage spécifié fread lit des données provenant d’un fichier à partir de la position courante fwrite écrit des données dans un fichier

60 Principales fonctions provenant des bibliothèques standard
stdio.h fgets lit une chaîne de caractères provenant d’un fichier gets lit une chaîne de caractères provenant de l’entrée standard fputs écrit une chaîne de caractères dans un fichier puts écrit une chaîne de caractères à la sortie standard fgetc lit un caractère provenant d’un fichier getc lit un caractère provenant de l’entrée standard fputc écrit un caractère dans un fichier putc écrit un caractère à la sortie standard stdin constante de redirection vers l’entrée standard stdout constante de redirection vers la sortie standard stderr constante de redirection vers la sortie d’erreur standard

61 Principales fonctions provenant des bibliothèques standard
stdio.h printf* écrit une chaîne de caractères mise en forme vers la sortie standard sprintf écrit une chaîne de caractères mise en forme dans une variable de type chaîne de caractères fprintf écrit une chaîne de caractères mise en forme dans un fichier scanf* lit une chaîne de caractères mise en forme provenant de l’entrée standard sscanf lit une chaîne de caractères mise en forme provenant d’une variable de type chaîne de caractères fscanf lit une chaîne de caractères mise en forme provenant d’un fichier

62 Principales fonctions provenant des bibliothèques standard
stdlib.h NULL déclaration du pointeur NULL malloc allocation d’un bloc de mémoire calloc allocation d’un bloc de mémoire initialisé à zéro realloc réallocation d’un bloc de mémoire free libération d’un bloc de mémoire alloué system exécute une commande du système d’exploitation exit termine l’exécution du programme en cours atoi conversion d’une chaîne de caractères en valeur numérique entière atof conversion d’une chaîne de caractères en valeur numérique réelle

63 Principales fonctions provenant des bibliothèques standard
memory.h memcpy copie un bloc mémoire d’un endroit à un autre memcmp compare deux blocs mémoire caractère par caractère memset initialise chaque octet d’un bloc mémoire par la valeur d’un octet spécifié

64 Principales fonctions provenant des bibliothèques standard
math.h cos retourne le cosinus d’un angle en radian acos retourne, en radian, l’arcosinus d’un nombre cosh retourne le cosinus hyperbolique d’un angle en radian sin retourne le sinus d’un angle en radian asin retourne, en radian, l’arcsinus d’un nombre sinh retourne le sinus hyperbolique d’un angle en radian tan retourne, la tangente d’un angle en radian atan retourne, en radian, la tangente d’un nombre atan2 retourne, en radian, la tangente d’un nombre tanh retourne la tangente hyperbolique d’un angle en radian

65 Principales fonctions provenant des bibliothèques standard
math.h pow calcule un nombre à la puissance d’un autre exp calcule l’exponentiel d’un nombre (en) log calcule le logarithme naturelle d’un nombre log10 calcule le logarithme en base 10 d’un nombre sqrt calcule la racine carrée d’un nombre abs calcule la valeur absolue d’un nombre entier fabs calcule la valeur absolue d’un nombre réel div effectue une division entière floor retourne l’arrondi inférieur d’un nombre ceil retourne l’arrondi supérieur d’un nombre srand détermine un point de départ pour la génération d’un nombre pseudo aléatoire rand génère un nombre pseudo aléatoire

66 Principales fonctions provenant des bibliothèques standard
string.h strlen retourne la longueur d’une chaîne de caractères strset initialise une chaîne de caractères à un caractère spécifique strcpy copie une chaîne de caractères strncpy copie n caractères provenant d’une chaîne de caractères strcmp compare deux chaînes de caractères strncmp compare n caractères provenant de deux chaînes de caractères strcat concatène deux chaînes de caractères strncat concatène n caractères d’une chaîne de caractères dans une autre

67 Principales fonctions provenant des bibliothèques standard
string.h strchr recherche un caractère spécifique dans une chaîne de caractères strstr recherche une chaîne de caractères spécifique à l’intérieur d’une chaîne de caractères strtok recherche la prochaine occurrence d’un « token » dans une chaîne de caractères

68 Principales fonctions provenant des bibliothèques standard
ctype.h isdigit identifie si un caractère est numérique isalpha identifie si un caractère est alphabétique islower identifie si un caractère est en minuscule isupper identifie si un caractère est en majuscule tolower convertie un caractère à un caractère minuscule toupper convertie un caractère à un caractère majuscule

69

70 Portée et durée de vie des variables
Gestion d’une variable par le compilateur C Les 2 points essentiels suivants : le domaine de validité d’une variable (couramment appelé le « scope », la portée ou la visibilité) la durée de vie de la variable (c’est-à-dire la durée pendant laquelle la variable existe vraiment) sont déterminés par : l’emplacement de la définition de la variable la classe de mémorisation utilisée à sa déclaration (« storage class »)

71 Portée et durée de vie des variables
La notion de variable globale et locale est directement liée à l’emplacement de la déclaration Par définition, une variable est visible par toutes les fonctions et les expressions de code qui suit sa déclaration si ces dernières sont à un niveau de bloc égal ou inférieur. Les variables globales : Elles sont déclarées hors de toute fonction Elles sont donc connues, valides et utilisables dans tout le fichier où elle est définie; cela à partir de l’endroit de sa déclaration. Selon sa classe de mémorisation, elle peut même s’étendre à d’autres fichiers. Les variables locales : Elles sont définies à l’intérieur d’un bloc ou d’une fonction. Par définition, elles sont donc visibles, valides et utilisables uniquement pour ces blocs ou fonctions.

72 Portée et durée de vie des variables
Description des classes de mémorisation Chaque variable possède une seule classe de mémorisation parmi les suivantes : auto static extern register Les variables globales ne peuvent être que des classes extern ou static

73 Portée et durée de vie des variables
Description des classes de mémorisation pour les variables locales : auto (pour « automatic ») est la classe de mémorisation par défaut. Le bloc dans laquelle elles sont définie détermine leur visibilité et leur durée de vie. static Cette classe de mémorisation indique que la portée d’une variable est la même que pour une variable auto mais que sa durée de vie correspond à celle du programme. [ auto ] type identificateur ; auto int Sum; float Average; static type identificateur ; static int Flag;

74 Portée et durée de vie des variables
Description des classes de mémorisation pour les variables locales (suite) : extern Cette classe de mémorisation est particulière car elle n’entraîne pas d’allocation mémoire. Elle fait plutôt référence à une autre variable existante du même nom ailleurs dans le programme. register Ces variables obéissent aux même règles que celles de type auto. La différence vient du fait que le compilateur tente de la placé à même les registres du CPU plutôt que dans la mémoire de travail. extern type identificateur ; extern int VersionNoSofware; register type identificateur ; register int i, j, k, l;

75 Portée et durée de vie des variables
Description des classes de mémorisation pour les variables globales : extern Est la classe de mémorisation par défaut. Leur portée est au moins le fichier où elle sont déclarées et peut être étendue à plusieurs fichiers. Leur durée de vie sont celle du programme. static Leur durée de vie est la même que pour les variables globales extern. Leur portée inclue le fichier de leur déclaration mais ne peut être étendue à d’autres fichiers. extern type identificateur ; extern int GlobalInformation; static type identificateur ; static int RegionalInformation;

76 Portée et durée de vie des variables
Résumé sur la portée et la durée de vie des variables :

77 Portée et durée de vie des variables
#include <stdio.h> int VarGlobale1 = 1; /* Variable globale */ void main(void) { int VarLocaleMain = 2; /* Variable locale au main */ if (VarLocaleMain >= 0) { int VarLocalBlocIf = 321; /* Variable locale */ } /* visible dans le bloc if */ printf("%d, %d, %d\n", VarGlobale1, VarLocaleMain, Fonction(54321)); } int VarGlobale2 = 123; /* Variable globale invisible par le main */ int Fonction(int Val) int VarLocaleFonction = Val; /* Variable locale à la fonction */ return VarLocaleFonction * VarGlobale1 * VarGlobale2; Attention cet exemple ne compile pas… la déclaration de fonction est absente

78

79 Rappel général sur les pointeurs
Un pointeur est une variable dont le contenu a pour valeur une adresse en mémoire. Le type du pointeur correspond au type de la variable pointée. En fait, ce typage ne sert au compilateur qu’à souligner au programmeur les erreurs potentielles lorsqu’elles surviennent. Tous les pointeurs sans exception ont 32 bits (en considérant un environnement 32 bits tel que Windows) Attention à l’opérateur * puisqu’il est le seul élément syntaxique du langage C à avoir 3 sens différents selon le contexte : La multiplication dans une expression mathématique La déclaration d’un pointeur dans une déclaration de variable La déréférence d’un pointeur pour accéder au contenu de l’adresse mémoire référencée

80 Représentation schématique de la mémoire
Afin de bien comprendre l’état des pointeurs il est pratique de schématiser l’état de la mémoire. Une astuce efficace consiste à observer l’évolution d’un programme ligne par ligne en faisant le suivi de toutes les variables par la représentation par case. Pour chaque allocation ou libération d’une variable, on crée ou détruit une case représentant la variable à suivre. Ensuite, pour chaque affectation, comparaison ou manipulation de ces variables, on s’assure que les types sont compatibles et que chacune des instructions correspondent bien à ce qui est souhaité. Variable standard Variable de type constant On assigne les adresses arbitrairement à titre de référence.

81 Représentation schématique des pointeurs
Que pensez-vous de ce petit programme? void main(void) { int V1, *P1; /* 01 */ int *P2, V2; /* 02 */ /* 03 */ P1 = &V1; /* 04 */ P2 = &V2; /* 05 */ /* 06 */ V1 = 100; /* 07 */ V2 = *P1; /* 08 */ *P2 = 2.0 * *P1; /* 09 */ /* 10 */ int V3 = P1; /* 11 */ /* Attention! Est-ce que ça compile? */ int *P3 = V1; /* 12 */ /* Attention! Est-ce que ça compile? */ } Faire l’exercice par la technique de représentation par cases

82 Pointeurs multiples Partant du fait qu’un pointeur est une variable dont le contenu indique l’endroit en mémoire du contenu d’une autre variable de n’importe quel type. Qu’arrive-t-il si nous désirons utiliser un pointeur indiquant un autre pointeur? On déclare les pointeurs multiples par l’utilisation de plusieurs *. En fait, on utilise autant d’astérisque que d’indirection nécessaire. [ Classe_de_memorisation ] type ***...* identificateur ; int Value; int *PtrLevel1, **PtrLevel2, ***PtrLevel3; PtrLevel1 = &Value; PtrLevel2 = &PtrLevel1; PtrLevel3 = &PtrLevel2 Value = 10; *PtrLevel1 = 100; **PtrLevel2 = 1000; ***PtrLevel3 = 10000; Faire l’exercice par la technique de représentation par cases

83 Pointeurs et structures
On revient sur les opérateurs d’indirection pour s’assurer de bien les maîtriser. #define STRING_MAX 256 typedef struct { int NoCivique; /* No civique */ char Rue[STRING_MAX]; /* Nom de la rue */ char Ville[STRING_MAX]; /* Nom de la ville */ } Adresse; char Nom[STRING_MAX]; /* Nom de l’individu */ int Age; /* Age en annee de l’individu */ int *RefNoCoursGPA; /* Numero du cours suivi en GPA */ Adresse *RefAd; /* Reference vers une adresse */ } Individu; /* Declaration et initialisation */ Adresse Residence1 = { 1100, "Notre-Dame Ouest", "Montreal" }; Adresse Residence2 = { 666, "Rue de la fin du monde", "St-Isidor de l’Apocalypse" }; int CoursInteressantDeGPA = 665; Individu Justin = { "Justin B. Sil", 24, &CoursInteressantDeGPA, &Residence1 }; Individu Jay = { "Jay Latrouille", 23, &CoursInteressantDeGPA, &Residence2 }; Individu *Personne = &Jay; /* Quelques instructions */ int NoCiviqueQuelconque = Jay.RefAd->NoCivique; /* ou (*Jay.RefAd).NoCivique */ int NoCoursQuelconque = *Justin.RefNoCoursGPA; /* est-ce correct sans parathèse ? */ char NomVille[STRING_MAX], *PtrCible = NomVille; char *PtrSource = Personne->RefAd->Ville; /* ou (*(*Personne).RefAd).Ville */ int i = 0; while ((*PtrCible++ = *PtrSource++) != '\0' && i++ < STRING_MAX);

84 Rappel général sur les tableaux
Un tableau est un ensemble de variable de même type. Un tableau est toujours constitué de deux parties : l’espace des données qui est toujours contiguë en mémoire (que ce soit pour un tableau unidimensionnel ou multidimensionnel) une référence de type pointeur indiquant toujours le début du tableau int Tableau[5] = { 10, 100, 1000, 10000, };

85 Rappel général sur les tableaux
Contrairement aux allocations dynamiques que nous verrons plus loin, les allocations statiques de tableaux ne génèrent pas d’allocation mémoire pour le pointeur de référence. Ce pointeur est constant et ne peut être modifié. Les tableaux réservent le nombre d’octets suivant : taille tableau = sizeof(type) * nombre d’éléments Quels sont les tailles des tableaux suivants? unsigned short int Data1[5]; int Data2[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; double Data3[10][10]; typedef struct { char Nom[256]; char Age; float Poids; int NoReference; } Individu; Individu Pierre[1], Jean[100], Jacques[5][10][2];

86 Relation étroite entre pointeurs et tableaux
Puisque tous les tableaux sont référencés par un pointeur, il est souvent pratique et pertinent d’affecter l’adresse de départ d’un tableau à un pointeur. Les tableaux sont toujours passés aux fonctions par leur adresse de départ. Il est impossible de passer un tableau par son contenu à une fonction. Il est aussi impossible qu’une fonction retourne le contenu d’un tableau, elle ne peut que retourner l’adresse du tableau Dans le but d’accélérer certains processus, il est parfois efficace d’exécuter certaines opérations sur un tableau à l’aide des pointeurs et de ne pas utiliser les opérateurs [ ] pour chaque accès au tableau.

87 Arithmétique sur les pointeurs
Les opérateurs d’addition, de soustraction, d’incrémentation ou de comparaison sont permis sur les pointeurs. Ils permettent le calcul de décalage entre une adresse de base et une adresse souhaitée. Tous ces calculs sont automatiquement gérés par le compilateur en fonction de la taille du type de référence du pointeur. #define TABLE_SIZE 5 const float CND_TO_USD = 1.2f float CNDCost[TABLE_SIZE] = { 10.25f, 2.12f, f, 32.77f, 0.12f }; short int Quantity[TABLE_SIZE] = { 3, 12, 2, 8, 193 }; float GlobalUSDCost; unsigned short int i; /* Cout global en dollard americain */ GlobalUSDCost = 0.0f; for (i = 0; i < TABLE_SIZE; i++) { GlobalUSDCost += CNDCost[i] * CND_TO_USD * Quantity[i]; } i = 0; short int *CurrentQuantity = Quantity, *PostLastQuantity = Quantity + TABLE_SIZE; while (CurrentQuantity < PostLastQuantity) { GlobalUSDCost += *(CNDCost + i++) * CND_TO_USD * *CurrentQuantity++;

88 Arithmétique sur les pointeurs
Que se passe-t-il exactement avec ces lignes de code? pour faire le suivi, on retire les lignes qui ne compilent pas et celles qui causent un débordement de mémoire int A[] = { 1, 2, 3, 4, 5 }; int a = 0x100; int *pA = A, *pa = &a, **ppA = &pA; a = A; /* 01 */ a = *A; /* 02 */ a = A[0] * 0; /* 03 */ a = pA; /* 04 */ a = *pA; /* 05 */ a = pA[0] + 0; /* 06 */ a = pa; /* 07 */ a = *pa * 0; /* 08 */ a = pa[0]; /* 09 */ *pa = *pA; /* 10 */ *(pA + 3) = *(pa + 3); /* 11 */ A[3] = a[3]; /* 12 */ a = A[5]; /* 13 */ A[1] = A[2]; /* 14 */ *(pA – 1) = a; /* 15 */ A = pA[2]; /* 16 */ a *= *A * *(A + *(pA + 1)); /* 17 */ a = --*pA++; /* 18 */ (A + 2)[2] = (*pa + 2) * *(pA – 1); /* 19 */ *(ppA[0] + 3) = (*ppA + 2)[0]; /* 20 */ Passer chacune des lignes en validant avec les étudiants si elles compilent et s’exécutent sans problème. La technique des cases est recommandée. Compile Déb mem A[0] A[1] A[2] A[3] A[4] a pA pa ppA Init OUI NON &A &a &pA 1 NON 2 OUI NON &A &a &pA 3 OUI NON &A &a &pA 4 NON 5 OUI NON &A &a &pA 6 OUI NON &A &a &pA 7 NON 8 OUI NON &A &a &pA 9 OUI NON &A &a &pA 10 OUI NON &A &a &pA 11 OUI OUI 12 NON 13 OUI OUI 14 OUI NON &A &a &pA 15 OUI OUI 16 NON 17 OUI NON &A &a &pA 18 OUI NON &A[1] &a &pA 19 OUI NON &A[1] &a &pA 20 OUI NON &A[1] &a &pA

89 Particularité des chaînes de caractères
Le langage C et même le langage C++ n’offrent aucun type de base correspondant à une chaîne de caractères (string). (par contre, il existe plusieurs implémentations de classes performantes pour en faire la gestion en C++ – voir la bibliothèque STL à titre d’exemple) La façon de gérer les chaînes de caractères est simplement par l’usage d’un tableau de type char. Par convention, on utilise le caractère '\0' (correspondant à la valeur 0) pour identifier la fin d’une chaîne de caractères. Ainsi, la longueur d’une chaîne de caractères ne correspond pas nécessairement à la place qu’elle prend en mémoire. La déclaration d’une chaîne de caractères constante et toutes les fonctions des bibliothèques standard respectent cette convention. C’est pourquoi il est important de la suivre. Faire un exercice sur les chaînes de caractères en impliquant les étudiants directement au tableau : - La longueur d’une chaîne de caractères - Copier une chaîne dans une autre CopyString(S1 S2) while ((*S1++ = *S2++) != ‘\0’); char Discipline[] = "Gymnastique"; char Prenom[256] = "Kassandra";

90 Gestion statique et dynamique de la mémoire
Les compilateurs basés sur le langage C gèrent automatiquement toutes les allocations mémoire provenant des déclarations standard. Ils gèrent à la fois les allocations, les initialisations (lorsque pratiquées) et les libérations de la mémoire au moment opportun. Puisque toutes ces opérations sur la mémoire sont connues au moment de la compilation, le programme, lorsqu’il est compilé et généré, contient déjà toutes les instruction de gestion de la mémoire (gestion fixe et immuable) C’est ce mécanisme qu’on appel la gestion statique de la mémoire.

91 Gestion statique et dynamique de la mémoire
Avec les fonctions malloc et free, il est possible de faire soi même la gestion de la mémoire au moment opportun – c’est ce qu’on appel la gestion dynamique de la mémoire. Il est essentiel de libérer soi même la mémoire réservée. Sans quoi votre programme présentera des fuites de mémoire (« memory leak »). Aucune allocation dynamique ne fait l’initialisation du contenu des variables créées. void * malloc(int taille-en-octet); void free(void * bloc-memoire); int *VariableQuelconque = (int*) malloc(sizeof(int)); ... *VariableQuelconque = 10; free(VariableQuelconque); La réponse se trouve à la page suivante.

92 Gestion statique et dynamique de la mémoire
Alors pourquoi utiliser la gestion dynamique de la mémoire? Cette liberté supplémentaire, malgré le fait qu’elle complexifie la lecture du code, permet de créer des programmes : plus flexibles; plus performants; plus modulaires; mieux structurés.

93 Gestion statique et dynamique de la mémoire
L’un des exemples les plus flagrant est l’instanciation d’un tableau : Les allocations statiques de tableaux nécessitent de connaître la taille du tableau au moment de la compilation (« compile time »). Puisqu’il arrive souvent qu’on ne connaisse pas la taille réelle requise pour un tableau au moment de la compilation, il est courrant de déterminer la taille de ce dernier égal au pire des cas possibles. Ainsi, cette pratique engendre souvent des contraintes importantes : mémoire réservée inutilement qui peut ne jamais servir (ou très rarement) le tableau alloué peut ne pas pouvoir accueillir toutes les données requises pour une application => le programme est limité et ne peut réaliser toutes les tâches auxquelles il est destinées Les allocations dynamiques permettent d’allouer un tableau de la taille voulue à l’instant voulu (au « run time »). Les possibilités sont vastes et permettent d’allouer exactement la mémoire requise et de gérer celle-ci de façon optimale.

94 Gestion statique et dynamique de la mémoire
On se sert souvent des allocations dynamiques pour allouer des tableaux dont la taille est indéfinie au moment de la compilation. Il existe une différence notable entre un tableau alloué statiquement et dynamiquement : la référence du tableau nécessaire à l’exécution du programme. int TableauStatique[3] = { 0, 1, 2 }; int *TableauDynamique = (int*) malloc(sizeof(int) * 3);

95 Gestion statique et dynamique de la mémoire
Puisque le contenu de tout les tableaux statique est contiguë en mémoire (qu’il soit unidimensionnel ou multidimensionnel), c’est le compilateur qui gère les décalages selon l’adresse de départ pour chaque accès mémoire. Pour les allocations dynamiques de tableaux multidimensionnels, le compilateur ne peut calculer les décalages souhaités étant donné qu’il ne connaît pas la taille nominale de chacune des dimensions au moment de la compilation. Il existe alors deux solutions qui se présentent : la gestion explicite des décalages par le programmeur; la déclaration de plusieurs tableaux en cascade. Prendre le temps de bien expliquer les deux solutions possibles : par décalage et en cascade

96 Gestion statique et dynamique de la mémoire
const int TABLE_SIZE_X = 2, TABLE_SIZE_Y = 3; float StaticMatrix[TABLE_SIZE_X][TABLE_SIZE_Y]; float *DynMatrixByOffset; float **DynMatrixByCascade; int i, x, y; /* Allocation dynamique par gestion de décalage */ DynMatrixByOffset = (float*) malloc(sizeof(float) * TABLE_SIZE_X * TABLE_SIZE_Y); /* Allocation dynamique par cascade */ DynMatrixByCascade = (float**) malloc(sizeof(float*) * TABLE_SIZE_X); for (i = 0; i < TABLE_SIZE_X; i++) DynMatrixByCascade[i] = (float*) malloc(sizeof(float) * TABLE_SIZE_Y); /* Initialisation de tous les elements de chaque matrices a 0, 1 ou 2 */ for (x = 0; x < TABLE_SIZE_X; x++) { for (y = 0; y < TABLE_SIZE_Y; y++) { StaticMatrix[x][y] = 0; DynMatrixByOffset[x + y * TABLE_SIZE_X] = 1; /* gestion explicite des décalages */ DynMatrixByCascade[x][y] = 2; } /* Liberation de la matrice dynamique par gestion de décalage */ free(DynMatrixByOffset); /* Liberation de la matrice dynamique par cascade */ free(DynMatrixByCascade[i]); free(DynMatrixByCascade);

97 Gestion statique et dynamique de la mémoire

98

99 Paramètres d’une fonction :
En général Paramètres d’une fonction : on appel la signature la liste des types qui sont passés à une fonction tous les types peuvent être passés à une fonction à titre d’argument mais : les tableaux doivent être passés par leur adresse attention aux structures qui sont copiés bit à bit il est possible de spécifier l’absence de paramètre par : l’usage du mot clé void en laissant l’intérieure des parenthèses vide on appel fonctions ellipsis celles dont le nombre d’argument est variable (comme printf et scanf – nécessite ... à la fin de la liste d’arguments au moment de la déclaration) Fonction1(); /* retourne : int parametres : aucun */ void Fonction2(void); /* retourne : rien parametres : aucun */ void* Fonction3(void*); /* retourne : pointeur void – parametres : pointeur void */ float Fonction4(int, int); /* retourne : float parametres : 2 int */

100 En général Type de retour :
une fonction peut retourner n’importe quel type mais : les tableaux sont toujours retournés par leur adresse attention aux types structurés qui sont copié bit-à-bit si aucun type de retour n’est spécifié, le type de retour par défaut est l’entier – int l’usage du mot clé void comme type de retour indique que la fonction ne retourne aucune valeur

101 En général Terminer une fonction
l’instruction return permet la terminaison d’une fonction à n’importe quel endroit return peut retourner une valeur ou non dépendamment du type de retour spécifié lors de la déclaration l’instruction return est facultative pour terminer une fonction ne retournant aucune valeur. return; /* l’utilisation du return a la fin d’une fonction est facultatif */ return Resultat; /* lorsqu’on retourne une valeur, on peut la mettre ou non entre ... */ return(Result); /* parenthèse */

102 Portée des fonctions De façon semblable à la déclaration des variables, l’accessibilité ou la visibilité des fonctions est déterminée par : l’emplacement de la déclaration la classe de mémorisation utilisée à sa déclaration (« storage class ») L’emplacement de la déclaration d’une fonction détermine le point à partir duquel celle-ci peut être utilisée. Comme certains types de données, une fonction peut être déclarée avant d’être définie. On utilise souvent les « header files » (*.h) pour déclarer les fonctions.

103 Portée des fonctions #include <stdio.h>
struct Individu; /* Individu est declare avant sa definition */ int Age(Individu *I, int AnneeCourante); /* Prototype de fonction déclaré bien avant */ struct Individu { char Prenom[32]; char Nom[32]; int AnneeNaissance; }; void main(void) { char* Nom(Individu*, char*); /* Prototype de fonction déclaré à l’interne */ /* Le nom des variables est facultatif */ Individu Scientifique = { "Albert", "Einstein", 1879}; char Temp[256]; printf("Monsieur %s a %d an(s)", Nom(&Scientifique, Temp), Age(&Scientifique, 2006)); } int Age(Individu *I, int AnneeCourante) return AnneeCourante - I->AnneeNaissance; char* Nom(Individu *I, char *CC) sprintf(CC, "%s %s", I->Prenom, I->Nom); return CC; Monsieur Albert Einstein a 127 an(s) - aucune entrée - Entrée Sortie

104 Description des classes de mémorisation des fonctions :
Portée des fonctions Description des classes de mémorisation des fonctions : extern (par défaut) La fonction est une entité globale et peut être utilisée partout où elle est référencée. static L’utilisation de la fonction est limitée dans le module où elle a été définie. [ extern ] type-retour identificateur( [ type1 param1, ... ] ) ... extern int NombreDeCaractere(char *ChaineCaracteres); Attention au mot clé static qui a un sens complètement différent en C++ [ static ] type-retour identificateur( [ type1 param1, ... ] ) ... static void InverserChaineCaracteres(char *ChaineCaracteres);

105 Mécanismes d’appel d’une fonction
Lorsqu’une fonction est appelée, un mécanisme est mis en action afin de sauvegarder momentanément plusieurs informations nécessaires au déroulement du programme. Ces informations servent au microprocesseur à faire le suivi adéquat du programme. On utilise la pile du système pour y enregistrer temporairement les informations nécessaires. Tous les appels de fonction gérés par le compilateur fonctionnent de la même façon, peu importe leur nature. On nomme : fonction appelante celle qui appel fonction appelée celle qui est appelée Expliquer qu’est-ce que le « stack » du système Pile du système => « Memory stack »

106 Mécanismes d’appel d’une fonction
Pour chaque appel de fonction : Un ensemble d’information sur l’état courant du programme, nommé enregistrement d’activation, est mis sur la pile. On y retrouve principalement : L’adresse de retour de la fonction appelante Le résultat de la fonction appelée Tous les paramètres de la fonction appelée Toutes les variables locales de la fonction appelée L’état courant des registres de la fonction appelante qui sont utilisés par la fonction appelée Trois registres sont systématiquement utilisés : ESP – stack pointer EBP – Base pointer EIP – Instruction Pointer Enregistrement d’activation = « stack frame » ou « activation record » Il est important de spécifier que l’exemple présenté ici (la façon qu’a le compilateur de générer le code assembleur pour l’appel d’une fonction) n’est qu’un exemple et qu’il représente en fait les fonctions de type _cdecl sous Intel x86.

107 Mécanismes d’appel d’une fonction
Les étapes du mécanisme d’appel d’une fonction sont : Que manque-t-il?

108 Mécanismes d’appel d’une fonction
La valeur de retour est passée à titre de premier paramètre de la fonction. c’est-à-dire, comme dernier élément empilé sur la pile Par convention, ce paramètre possède toujours 4 octets (32 bits). Qu’arrive-t-il si la valeur de retour spécifiée ne correspond pas aux 4 octets utilisés? Si la valeur de retour est plus petite (comme un char), on utilise tout de même les 4 octets. Si la valeur de retour est plus grande (comme un double), on porte en mémoire haute la valeur de la variable et on utilise les 4 octets réservés comme pointeurs pour indiquer cette l’adresse de cette position. Enregistrement d’activation = « stack frame » ou « activation record » Il est important de spécifier ici que la façon qu’a le compilateur de générer le code assembleur pour l’appel d’une fonction présentée ici n’est qu’un exemple – plus précisément, cet exemple représente les fonctions de type _cdecl avec Intel x86.

109 Mécanismes d’appel d’une fonction
Un exemple assembleur serait plus que pertinent...

110 Généralité du langage C
int i, n, Sum; n = 1234; Sum = 0; for (i = 0; i < n; i++) Sum += i; Comment est exécuté un programme

111 Le passage des paramètres à une fonction
Il existe trois méthodes de passage de paramètres à une fonction : Passage par valeur (« call by value ») les valeurs des paramètres d'appel sont recopiés dans la fonction appelée ainsi la fonction appelée travaille avec une copie des paramètres en fait, c'est le seul mode de passage de paramètres réellement existant en C Cette notion est particulièrement importante. Il faut s’assurer de passer suffisamment de temps sur cette acétate et l’exemple de la page suivante.

112 Le passage des paramètres à une fonction
… trois méthodes de passage de paramètres (2ième): Passage par référence (« call by reference ») dans ce mode de passage de paramètres, la fonction appelée travail avec les références des paramètres d'appel il existe deux avantages importants de ce mode de transmission il évite des copies coûteuses des paramètres de tailles importantes il permet la modification de variables externes à la fonction fondamentalement, ce type de passage est inexistant en C (contrairement au C++ avec ses références) c’est l’utilisation des pointeurs qui permet, indirectement, d’obtenir un comportement qui s’approche du passage par référence Cette notion est particulièrement importante. Il faut s’assurer de passer suffisamment de temps sur cette acétate et l’exemple de la page suivante.

113 Le passage des paramètres à une fonction
… trois méthodes de passage de paramètres (3ième): Passage par nom (« call by name ») c'est le mode de transmission utilisé par le préprocesseur avec les arguments des macros dans ce mode, les paramètres de l'appel seront textuellement substitués aux diverses occurrences des paramètres formels (sans validation aucune – ni même la vérification de compatibilité des types) Cette notion est particulièrement importante. Il faut s’assurer de passer suffisamment de temps sur cette acétate et l’exemple de la page suivante.

114 Le passage des paramètres à une fonction
#include <stdio.h> void IncrementAndShow(int V1, int V2) { V1++; V2++; printf("Valeur des variables dans IncAndShow : \t%d\t%d\n", V1, V2); } void Swap(int *V1, int *V2) int VTemp = *V2; *V2 = *V1; *V1 = VTemp; #define Show(V1, V2) printf("Valeur des variables dans Show : \t%d\t%d\n", V1, V2) void main(void) int V1 = 5, V2 = 10; printf("Valeur des variables dans main (1) : \t%d\t%d\n", V1, V2); IncrementAndShow(V1, V2); printf("Valeur des variables dans main (2) : \t%d\t%d\n", V1, V2); Swap(&V1, &V2); printf("Valeur des variables dans main (3) : \t%d\t%d\n", V1, V2); Show(--V1, V2++); printf("Valeur des variables dans main (4) : \t%d\t%d\n", V1, V2); Valeur des variables dans main (1) : Valeur des variables dans IncAndShow : Valeur des variables dans main (2) : Valeur des variables dans main (3) : Valeur des variables dans Show : Valeur des variables dans main (4) : - aucune entrée - Entrée Sortie

115 Fonctions récursives Il existe deux types de solution pour résoudre des problèmes nécessitant plusieurs calculs similaires et répétitifs : les solutions itératives et récursives. Dans les deux cas, un mécanisme différent est mis en place afin d’exécuter plusieurs fois la même séquence d’instruction. Ces mécanismes se traduisent par : des boucles de contrôle pour les solutions itératives  implique des solutions linéaires (des solutions non linéaires sont possibles avec des artifices) des fonctions qui s’appellent elles même  implique des solutions linéaires et non linéaires

116 Fonctions récursives D’un point de vue théorique, lorsqu’une solution récursive est bien structurée, elle est un moyen puissant et unique de résoudre des problèmes. Par définition, la récursivité est une technique de traitement d’une tâche T par le traitement d’une tâche T’ semblable à l’approche descendante la différence réside dans le fait que la tâche T’ est de même nature que la tâche T D’un point de vue applicatif, il est important de se rappeler que : T’  T mais que T’ < T Est-ce qu’une fonction récursive est meilleur qu’une solution itérative ? NON – d’un point de vue théorique ce n’est pas toujours vrai et d’un point de vue applicatif c’est rarement vrai (à cause des conditions de performance qu’on verra plus loin)

117 La question importante : Comment arrêter une fonction récursive?
Fonctions récursives La question importante : Comment arrêter une fonction récursive? Les cas limites sont des éléments essentiels de toutes fonctions récursives : ce sont les conditions qui cessent les appels récursifs et permettent de terminer la fonction ce sont des tests d’arrêt qui sont liés aux parties non récursives de la fonction Il existe deux types de récursivité : la récursivité directe correspond au cas où la fonction F appel directement la fonction F la récursivité indirecte correspond au cas où une fonction F appel F1, qui appel F2, qui appel F3, ..., qui appel Fn, qui enfin appel la fonction F Le cas limite est un concept important sur lequel il faut élaborer. Quel est-il pour l’exemple de la page précédente?

118 Un premier exemple : La recherche d’un mot dans le dictionnaire
Fonctions récursives Un premier exemple : La recherche d’un mot dans le dictionnaire la solution itérative, simple mais peu efficace, correspond à parcourir dans l’ordre, du premier vers le dernier, tous les éléments du dictionnaire jusqu’à ce que le mot recherché soit identifié une solution plus efficace et plus élégante consiste à faire ce que nous faisons intuitivement : une recherche binaire (« binary search ») Comment procède-t-on pour résoudre ce problème? Les étudiants doivent proposer leur méthode de résolution du problème – On oriente la discussion vers une solution non informatique

119 Pseudo code d’un algorithme de recherche binaire :
Fonctions récursives Pseudo code d’un algorithme de recherche binaire : Le tableau de données doit être trié sinon la recherche dichotomique n’est pas applicable. A[0]  A[1]  A[2]  A[3] ...  A[n] Fonction-Recherche-binaire (Dictionnaire, Mot) SI Dictionnaire est réduit à une seule page ALORS parcourir la page pour localiser Mot SINON Trouver la partie du Dictionnaire séparé par le milieu où se trouve Mot SI Mot se trouve dans la première partie du Dictionnaire Fonction-Recherche-Binaire( première partie de Dictionnaire, Mot) Sinon Fonction-Recherche-Binaire( deuxième partie de Dictionnaire, Mot)

120 Comment trouver une solution récursive?
Fonctions récursives Comment trouver une solution récursive? Réponses clés à trouver pour construire une solution récursive : Est-ce que le problème peut être définie en terme de sous problème de même nature? Est-ce que chaque appel récursif diminue la taille du problème? Quel est le cas limite du problème? Puisque la taille du problème diminue, est on assuré d’atteindre le cas limite? La technique présentée ici est importante.

121 Fonctions récursives Aussi, plusieurs algorithmes itératifs peuvent facilement être modifier en algorithmes récursifs Un exemple simple de transformation algorithmique : le parcours d’une boucle void AfficheTousLesNombres2Chiffres_Iteratif(int APartirDuNombre) { for (int i = APartirDuNombre; i < 100; i++) { printf("%d\n", i); } void AfficheTousLesNombres2Chiffres_Recursif(int APartirDuNombre) { if (APartirDuNombre < 100) { printf("%d\n", APartirDuNombre); AfficheTousLesNombres_Recursif(APartirDuNombre + 1); } La technique présentée ici est importante.

122 Fonctions récursives Un exemple un peu plus étayé de transformation algorithmique : le parcours d’une double boucle void InitDynamicArrayByCascade_Iterative(int **Array, int SizeX, int SizeY, int Value) { for (int iY = 0; iY < SizeY; iY++) { for (int iX = 0; iX < SizeX; iX++) { Array[iX][iY] = Value; } void InitDynamicArrayByCascade_Recursive(int **Array, int SizeX, int SizeY, int Value) { if (SizeY > 0) { if (SizeX > 0) { Array[SizeY - 1][SizeX - 1] = Value; InitDynamicArrayByCascade_Recursive(Array, SizeX - 1, SizeY, Value); } InitDynamicArrayByCascade_Recursive(Array, SizeX, SizeY – 1, Value); La technique présentée ici est importante.

123 Fonctions récursives La méthode de représentation suivante permet de décrire simplement un problème récursif par l’identification systématique de toutes les tâches possibles à chaque « itération » : l’idée consiste à décrire tous les cas possibles du problème il est important d’exprimer les tâches à faire et la nature récursive des cas concernés il est intéressant de voir que les cas limites apparaissent clairement avec cette méthode

124 Un autre exemple : le calcul de la factorielle.
Fonctions récursives Un autre exemple : le calcul de la factorielle. La définition mathématique de la factorielle est connue et simple : Faire la démonstration pour arriver à la définition récursive (factoriel en fonction de factoriel) : - n! = 1 * 2 * 3 * … * (n-1) * n = n * (n-1) * (n-2) * … * 2 * 1 = n * [(n-1) * (n-2) * … * 2 * 1] = n * (n-1)!

125 Suite de l’exemple : le calcul de la factorielle
Fonctions récursives Suite de l’exemple : le calcul de la factorielle Fonction-Factoriel (Nombre) SI Nombre < 0 retourne erreur Si Nombre = 0 retourne 1 SINON retourne Nombre * Fonction-Factoriel(Nombre – 1) int Factoriel(int N) { if (N < 0) { /* Parametre d’entree invalide */ return -1; /* -1 represente erreur */ } else if (N == 0) { return 1; /* Cas limite */ } else { return N * Factoriel(N – 1); /* Appel recursif */ }

126 Fonctions récursives Puisque chaque appel de fonction possède son propre espace de variable, chaque appel récursif possède le sien. Ceci implique un parcours descendant jusqu’au dernier appel récursif pour ensuite revenir au premier appel. La technique de représentation par boîtes est très efficace pour illustrer le comportement d’un programme récursif. Elle aide à : déverminer les programmes récursifs (parfois difficiles à suivre) illustrer le comportement d’un algorithme

127 Fonctions récursives Les étapes à suivre pour réaliser la technique de représentation par boîtes : pour chaque appel récursif (parcours descendant), on crée une boîte qui contient l’appel de fonction incluant les valeurs de chaque paramètre l’environnement local du sous programme associé les instructions faites avant l’appel récursif une flèche initiant le nouvel appel récursif pour chaque dernier appel récursif, on revient à l’appel de fonction appelante en créant une boîte contenant : les instructions faites après l’appel récursif un flèche indiquant le retour de fonction avec la valeur retournée s’il y a lieu

128 Exemple de représentation par boîte :
Fonctions récursives Exemple de représentation par boîte : 3! = 6 - aucune entrée - Entrée Sortie int main(void) { printf("%d! = %d\n", 3, Factoriel(3)); }

129 Fonctions récursives Un exemple : écrire une chaîne de caractères à l’envers (1ier) void Writebackward1(char *String, int Length) { if (Length > 0) { printf("%c", String[Length - 1]); Writebackward1(String, Length – 1); } Writebackward1("allo", 4); olla - aucune entrée - Entrée Sortie

130 Fonctions récursives Un exemple : écrire une chaîne de caractères à l’envers (2ième) void Writebackward2(char *String) { if (String[0] != '\0') { Writebackward2(String + 1); /* équivalent à WB2(&String[0] */ printf("%c", String[0]); } Writebackward2("allo"); olla - aucune entrée - Entrée Sortie Demander aux étudiants laquelle des fonctions Writebackward est la meilleure.

131 Un exemple : impression selon un ordre particulier
Fonctions récursives Un exemple : impression selon un ordre particulier void ImpressionEtrange(int *Data, int N) { if (N > 0) { ImpressionEtrange(Data + 1, N – 1); printf("%d \n", *Data); ) } int Data[3] = { 1, 2, 3 }; ImpressionEtrange(Data, 3); 3 2 1 - aucune entrée - Entrée Sortie

132 Fonctions récursives suite de l’exemple à la page suivante ...

133 Fonctions récursives ... fin de l’exemple de la page précédente

134 Fonctions récursives Comme le montre indirectement l’exemple précédent, il peut être très pratique d’utiliser l’affichage de l’état courant du programme avant et après chaque appel récursif. Cette pratique est performante pour aider à la compréhension d’un algorithme et surtout au déverminage. 1 : 1 : 1 : 2 : 3 : 2 : 3 : 2 : 3 : - aucune entrée - Entrée Sortie void FonctionQuelconque(int *Data, int N) { if (N > 0) { printf("1 : %4d %4d \n", N, *Data); FonctionQuelconque(Data + 1, N – 1); printf("2 : %4d %4d \n", N, *Data); printf("3 : %4d %4d \n", N, *Data); } int Data[3] = { 1, 2, 3 }; FonctionQuelconque(Data, 3);

135 Fonctions récursives Comme il arrive souvent en algorithmie, il est intéressant de faire certaines optimisations qui augmentent significativement le rendement des algorithmes réveloppés. Les fonctions récursives permettent certaines optimisations intéressantes.

136 Prenons pour exemple la fonction de puissance.
Fonctions récursives Prenons pour exemple la fonction de puissance. X^Y = X * X * X * X * ... * X (y fois) = X * (X * X * X * ... * X) = X * X^(Y-1) Une discussion sur la division et de la possibilité qu’elle soit par 0 dans cet exemple peut être intéressant

137 Fonctions récursives La fonction de puissance (suite) : version récursive Version itérative float PowerRec(float X, int Y) { if (Y == 0) { return 1.0f; } else if (Y < 0) { return 1.0f / PowerRec(X, -Y); } else { return X * PowerRec(X, Y – 1); } float PowerIter(float X, int Y) { float Result = 1.0f; for (int i = abs(Y); i > 0; i--) { Result *= X; } return (Y > 0) ? (Result) : (1.0f / Result);

138 Fonctions récursives La fonction de puissance (suite) : version récursive optimisée Une simple manipulation mathématique nous permet de démontrer : float PowerRecOptimized(float X, int Y) { if (Y == 0) { return 1.0f; } else if (Y < 0) { return 1.0f / PowerRecOptimized(X, -Y); } else { if (Odd(Y)) { return X * Sqr(PowerRecOptimized(X, Y / 2)); return Sqr(PowerRecOptimized(X, Y / 2)); } La fonction Odd est la fonction impaire qui peut se faire par : int Odd(int Value) { return Value % 2; } Demander aux étudiants de la faire.

139 Fonctions récursives Laquelle de ces trois fonctions est la plus performante et pourquoi? L’exemple des fonctions de puissance montre de façon convaincante l’amélioration que certaines optimisations peuvent faire. Ce tableau montre un indice de performance pour l’appel des trois fonctions montrées avec comme valeur d’exposant Fonction Nombre de parcours de boucle PowerRec 10000 appels récursifs PowerIter 10000 itérations PowerRecOptimized 14 appels récursifs (répartit en 5 appels impairs et 9 pairs) 10000 – 5000 – 2500 – 1250 – 625(624) – 312 – 156 – 78 – 39(38) – 19(18) – 9(8) – 4 – 2 – 1(0)

140 On revient sur l’exemple de recherche dichotomique.
Fonctions récursives On revient sur l’exemple de recherche dichotomique. Fonction-Recherche-binaire (Dictionnaire, Mot) SI Dictionnaire est réduit à une seule page ALORS parcourir la page pour localiser Mot SINON Trouver la partie du Dictionnaire séparé par le milieu où se trouve Mot SI Mot se trouve dans la première partie du Dictionnaire Fonction-Recherche-Binaire( première partie de Dictionnaire, Mot) Sinon Fonction-Recherche-Binaire( deuxième partie de Dictionnaire, Mot) Individu* RechercheIndividu(Individu Individus[], char Nom[], int Premier, int Dernier) { if (Premier > Dernier) { return NULL; } else { int Milieu = (Premier + Dernier) / 2; int ResultatComparaison = strcmp(Individus[Milieu].Nom, Nom); if (ResultatComparaison == 0) { return &Individus[Milieu]; } else if (ResultatComparaison > 0) { return RechercheIndividu(Individus, Nom, Premier, Milieu – 1); return RechercheIndividu(Individus, Nom, Milieu + 1, Dernier); }

141 Fonctions récursives void main(void) {
Individu ListePersonnes[9], *PersonneCible; strcpy(ListePersonnes[0].Nom, "Aleksia"); ListePersonnes[0].Age = 31; strcpy(ListePersonnes[1].Nom, "Frédérick"); ListePersonnes[1].Age = 2; strcpy(ListePersonnes[2].Nom, "Karolanne"); ListePersonnes[2].Age = 14; strcpy(ListePersonnes[3].Nom, "Kassandra"); ListePersonnes[3].Age = 10; strcpy(ListePersonnes[4].Nom, "Kristian"); ListePersonnes[4].Age = 34; strcpy(ListePersonnes[5].Nom, "Kzavier"); ListePersonnes[5].Age = 49; strcpy(ListePersonnes[6].Nom, "Michaël"); ListePersonnes[6].Age = 12; strcpy(ListePersonnes[7].Nom, "Nikolas"); ListePersonnes[7].Age = 30; strcpy(ListePersonnes[8].Nom, "Patrick"); ListePersonnes[8].Age = 32; PersonneCible = RechercheIndividu(ListePersonnes, "Frédérick", 0, 8); PersonneCible = RechercheIndividu(ListePersonnes, "Kroutchev", 0, 8); }

142 Encore un exemple : la suite de Fonacci
Fonctions récursives Encore un exemple : la suite de Fonacci Le rythme de reproduction des lapins est un phénomènes pouvant être représenté approximativement par cette suite. Quelle est l’évolution du nombre de lapin dans le temps en faisant les suppositions suivantes : les lapins ne meurent jamais un lapin atteint sa maturité sexuelle 2 mois après sa naissance (c’est-à-dire au début du 3ième mois de vie) au début de chaque mois, chaque paire mâle/femelle sexuellement mûre donne naissance exactement à une paire mâle/femelle on commence par une paire mâle/femelle naissants

143 Suite de Fibonacci (suite)
Fonctions récursives Suite de Fibonacci (suite) Mois Non mûre 1ière semaine Non mûre 2ième semaine Mûre Total 1 2 3 4 5 6 8 7 13 On présente la série de Fibonacci par la somme des deux nombres précédents

144 Suite de Fibonacci (suite)
Fonctions récursives Suite de Fibonacci (suite) unsigned int FibonacciRec(unsigned int N) { if (N == 0) { return 0; } else if (N == 1) { return 1; } else { return FibonacciRec(N - 1) + FibonacciRec(N – 2); }

145 Fonctions récursives Suite de Fibonacci (suite)
Cette première version récursive, quoique mathématiquement élégante et juste, est si peu performante qu’elle devient pratiquement non fonctionnelle. Pour Fib(N), cette solution donne : Fib(N+1) – 1 additions 2 Fib(N+1) – 2 appels récursifs void main(void) { FibonacciRec(5); /* 14 appels récursifs */ } void main(void) { FibonacciRec(7); /* 40 appels récursifs */ } void main(void) { FibonacciRec(4); /* 8 appels récursifs */ } void main(void) { FibonacciRec(6); /* 24 appels récursifs */ } void main(void) { FibonacciRec(3); /* 4 appels récursifs */ } void main(void) { FibonacciRec(0); /* 0 appel récursif */ } void main(void) { FibonacciRec(1); /* 0 appel récursif */ } void main(void) { FibonacciRec(2); /* 2 appels récursifs */ } F’(N) = F’(N - 2) + F’(N - 1) + 2 => correspond au nombre d’appels récursif de F(N) Préciser le : « pratiquement non fonctionnelle »

146 Fonctions récursives Une simple optimisation est assez facile :
Ces solutions donnent : N – 1 appels récursifs pour la version réc. optimisée N itération pour la version itérative unsigned int FRO(unsigned int N, unsigned int Previous, unsigned int Current) { if (N <= 1) { return Current; } else { return FRO(N – 1, Current, Current + Previous); } unsigned int FibonacciRecOptimized(unsigned int N) return (N == 0) ? (N) : (FRO(N, 0, 1)); unsigned int FibonacciIter(unsigned int N) { unsigned int R1 = 0, R2 = 1, R = 0, i; for (i = 1; i <= N; i++) { R = R1 + R2; R2 = R1; R1 = R; } return R;

147 Plusieurs fonction graphique utilisent la récursivité :
Fonctions récursives Plusieurs fonction graphique utilisent la récursivité : Remplissage d’une région bornée Manipulations de polygones Génération de fractals Affichage de géométries particulières ...

148 Que fait la fonction suivante :
Fonctions récursives Que fait la fonction suivante : void CoolDrawing1(int X, int Y, int R) { DrawCircle(X, Y, R); /* fonction dessinant un cercle centré à (X, Y) et de rayon R */ if (R > 2) { CoolDrawing1(X + R / 2, Y, R / 2); CoolDrawing1(X – R / 2, Y, R / 2); }

149 Comment tracer cette figure (lignes et flèches) ?
Fonctions récursives Comment tracer cette figure (lignes et flèches) ? void CoolDrawing2(int PreviousLength, int Length, int MaxLength) { if (Length <= MaxLength) { DrawLineFromCurrentPositionAndDirection(Length); TurnCursorToLeft(90); DrawArrowFromCurrentPositionAndDirection(Length); CoolDrawing2(Length, PreviousLength + Length, Maxlength); } CoolDrawing2(0, 1, 21); Spiral de Fibonacci

150 Impact du mécanisme d’appel des fonctions sur les fonctions récursives
Puisque le mécanisme d’appel des fonctions s’applique également sur les fonctions récursives ou non, regardons quel est son impact sur l’efficacité d’une fonction. Prenons pour exemple la fonction suivante (servant à afficher un nombre en format binaire) : avec l’appel de fonction suivant : void WriteBinary(int N) { if (N <= 1) { printf("%d", N); } else { WriteBinary(N / 2); printf("%d", N % 2); } ... WriteBinary(6);

151 Impact du mécanisme d’appel des fonctions sur les fonctions récursives

152 Impact du mécanisme d’appel des fonctions sur les fonctions récursives
Le mécanisme d’appel des fonctions génère deux impacts importants sur les fonctions récursives. En effet, lors de chaque appel : une série d’instructions supplémentaires est générées. Ces instructions rendent l’exécution de la fonction moins performante qu’une solution itérative équivalente. un enregistrement d’activation est empilé sur la pile. Cet empilement requiert une quantité de mémoire non négligeable pour les fonctions récursives ayant un nombre d’appel élevé. Il est relativement fréquent de voir une fonction mal conçue à ce niveau générer une erreur fatal du programme.

153 Pointeurs de fonctions
En plus d’avoir des pointeurs faisant référence à des variables de divers types, le langage C permet la définition de pointeurs de fonction. Ces pointeurs servent à identifier l’adresse d’entrée de la première instruction d’une fonction (point d’entrée). On utilise souvent les pointeurs de fonction pour passer une fonction en argument à une autre fonction. La déclaration d’un pointeur de fonction ressemble à la déclaration d’un pointeur de type et à la déclaration d’une fonction à la fois.

154 Pointeurs de fonctions
La déclaration d’un pointeur de fonction se fait par les parenthèses qui permettent de déterminer la variable pointeur: On vient de déclarer quatre pointeurs de fonction : pFonction1 fait référence à une fonction ne retournant rien et ayant aucun paramètre d’entrée pFonction2 fait référence à une fonction retournant un entier et ayant deux entiers comme signature pFonction3 fait référence à une fonction retournant un pointeur indéterminé et un pointeur indéterminé comme signature pFonction4 fait référence à une fonction retournant un pointeur de fonction (ne retournant rien et n’ayant aucun paramètre d’entrée) et ayant un pointeur de fonction (de même nature que pFonction2) et un entier comme signature. [ classe-mémorisation ] type-retour (*identificateur)( [ type1 param1, ... ] ); void (*pFonction1)(void); int (*pFonction2)(int, int); void* (*pFonction3)(void*) (void (*)(void)) (*pFonction4)((int (*)(int, int)), int);

155 Pointeurs de fonctions
Il faut faire attention à l’ambiguïté des déclarations d’un pointeur de fonction et d’une fonction retournant un pointeur. On vient de déclarer deux types complètement différents : pFonction fait référence à un pointeur de fonction retournant un entier et ayant un entier comme paramètre d’entrée Fonction fait référence à une fonction retournant un pointeur d’entier et ayant un entier comme paramètre d’entrée On assigne la valeur d’un pointeur de fonction en spécifiant l’adresse d’une fonction par : On l’appel simplement comme une fonction standard par le pointeur : int (*pFonction)(int); int *Fonction(int); int Fonction(int N); ... int (*pFonction)(int) = &Fonction; /* l’opérateur d’adressage (&) est facultatif */ int A = pFonction(N);

156 Pointeurs de fonctions
Un premier exemple : int Resultat(int V1, int V2, int (*Compare)(int, int)) { return Compare(V1, V2); } int Max(int V1, int V2) return (V1 >= V2) ? (V1) : (V2); int Min(int V1, int V2) return (V1 <= V2) ? (V1) : (V2); void main(void) printf("Min de 1 et 2 = %d\n", Resultat(1, 2, &Min)); printf("Max de 1 et 2 = %d\n", Resultat(1, 2, &Max)); Min de 1 et 2 = 1 Max de 1 et 2 = 2 - Entrée Sortie

157 Pointeurs de fonctions
Un deuxième exemple : int BrowseData(int *Data, int N, int (*ToDo)(int, int)) { int Resultat = *Data; for (int i = 0; i < N; i++) Resultat = ToDo(Resultat, Data[i]); return Resultat; } int Max(int V1, int V2) return (V1 >= V2) ? (V1) : (V2); int Min(int V1, int V2) return (V1 <= V2) ? (V1) : (V2); int Display(int V1, int V2) return printf(" %d ", V2); void main(void) int Data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -9, -8, -7, -6, -5, -4, -3, -2, -1 }; printf("Min de toutes les données = %d\n", BrowseData(Data, 19, &Min)); printf("Max de toutes les données = %d\n", BrowseData(Data, 19, &Max)); printf("Affichage de toutes les données :\n"); BrowseData(Data, 19, &Display); Min de toutes les donnÚes = -9 Max de toutes les donnÚes = 9 Affichage de toutes les données : - Entrée Sortie

158 Fonctions ellipsis Le C permet de déclarer des fonctions dont le nombre de paramètres est variable (les fonctions printf et scanf en sont de parfait exemple). Cette pratique est habituellement à proscrire en langage C++. L’un des points forts du langage C++ est de faire la vérification systématique de la compatibilité de tous les types, or les fonctions ellipsis ne permettent pas au compilateur de faire cette vérification de type. Malgré tout, cette technique est puissante et peut être très utile en langage C.

159 Fonctions ellipsis Cette technique est rendue possible grâce au fait que toutes les variables passées à une fonction se retrouvent tous empilées sur la pile lors de l’appel de fonction. Avec la position initiale des paramètres en mémoire, le type et le nombre, il est assez aisé de les récupérer pour les utiliser. Toutes les fonctions ellipsis possède au moins un paramètre avant la déclaration ellipsis à proprement dite (...). Ce paramètre permet de connaître l’adresse de départ de tout les paramètres de la fonction. Il sert souvent aussi pour déterminer le type et/ou le nombre de paramètre. Faire le lien avec le premier paramètre de printf Il est possible de ne pas utiliser le paramètre obligatoire pour connaître les données entrantes. Si par exemple on veut imprimer une série de données qui sont positives. On peut utiliser une valeur négative pour indiquer la fin des paramètres ellipsis.

160 Fonctions ellipsis L’implémentation correcte de ce type de fonction requiert une grande précaution car plusieurs règles doivent être respectées : il existe au moins un argument définit avant la déclaration ellipsis (...) il faut faire attention aux « type promotions » pratiqués par les compilateurs le corps d’une fonction ellipsis doit être cohérent entre sa déclarations (incluant son comportement – extraction des données) et son utilisation les macros va_arg, va_start, va_end servent à l’extraction des paramètres Type promotion sous VC : - float -> double - char ou short -> int (long) - le copie constructeur n’est pas appelé même s’il existe -> c’est plutôt une copie bit à bit de l’objet qui est fait (notion C++)

161 Un premier exemple (sans les macros) :
Fonctions ellipsis Un premier exemple (sans les macros) : void AfficheIntData(int N, ...) { for (int i = 0; i < N; i++) { printf("%d ", *(&N + (1 + i))); } printf("\n"); void AfficheDoubleData(int N, ...) printf("%0.1lf ", *(double*)((char*)&N + sizeof(int) + sizeof(double) * i)); void main(void) AfficheIntData(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); AfficheDoubleData(5, 1.9, 2.8, 3.7, 4.6, 5.5); - Entrée Sortie

162 Un deuxième exemple (avec les macros) :
Fonctions ellipsis Un deuxième exemple (avec les macros) : void AfficheIntData(int N, ...) { va_list ParametreCourant; va_start(ParametreCourant, N); for (int i = 0; i < N; i++) printf("%d ", va_arg(ParametreCourant, int)); va_end(ParametreCourant); printf("\n"); } void AfficheDoubleData(int N, ...) for (int i = 0; i < N; i++) printf("%0.1lf ", va_arg(ParametreCourant, double)); void main(void) AfficheIntData(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); AfficheDoubleData(5, 1.9, 2.8, 3.7, 4.6, 5.5); - Entrée Sortie


Télécharger ppt "Convention sur les acétates"

Présentations similaires


Annonces Google