IFT-2000: Structures de données Éléments techniques avancés du C et du C++ Dominic Genest, 2009.

Slides:



Advertisements
Présentations similaires
GEF 243B Programmation informatique appliquée
Advertisements

Premier programme en C :
La boucle for : init7.c et init71.c
Chapitre annexe. Récursivité
Les fonctions A quoi ça sert ?
Rappels C.
GEF 243B Programmation informatique appliquée
C.
Paramètres et pointeurs
Structures et unions types énumérés Qu'est-ce qu'une structure
Les pointeurs Manipulation d'adresses et de ce qui est contenu dans ces adresses Très important, fondamental même en C mauvaise réputation : 'dur à comprendre',
Fonctions Dans un programme : certaines opérations (ou séquences d'opérations) peuvent se répéter plusieurs fois : affichage de tableau, saisie, ou même.
Points importants de la semaine Les fonctions. La portée. La passage par copie. Les tableaux.
Introduction à la programmation (420-PK2-SL) cours 12 Gestion des applications Technologie de linformation (LEA.BW)
Récursivité.
Points importants de la semaine Le paramétrage. La portée. Le passage par copie. Le passage par référence.
8PRO100 Éléments de programmation Allocation dynamique de la mémoire.
IFT-2000: Structures de Données Listes chaînées Dominic Genest, 2009.
Introduction au paradigme objet Concepts importants surcharge (overload) redéfinition (override) Définition d’une classe Définition des attributs.
Les Classes les structures en C (struct) regroupent des variables : structuration de l'analyse mais problèmes de cohérence problèmes de sécurité d'accès.
Quest-ce quune classe dallocation? Une classe dallocation détermine la portée et la durée de vie dun objet ou dune fonction.
Les pointeurs Enormément utilisé en C/C++ ! Pourquoi? A quoi ça sert?
Les pointeurs Modes d’adressage de variables. Définition d’un pointeur. Opérateurs de base. Opérations élémentaires. Pointeurs et tableaux. Pointeurs et.
IFT-2000: Structures de données Plan de cours Théorie du contrat Types abstraits Dominic Genest, 2009.
IFT-2000: Structures de données
IFT-2000: Structures de données Les graphes Dominic Genest, 2009.
Structures de données IFT Abder Alikacem Transtypage Module 1 Département d’informatique et de génie logiciel Édition Septembre 2009.
Structures de données IFT-2000
Structures de données IFT-2000
IFT 6800 Atelier en Technologies d’information
8PRO100 Éléments de programmation Les types composés.
C++ : fonctions et opérateurs
Programme de baccalauréat en informatique Programmation Orientée Objets IFT Thierry EUDE Module 6. Gestion des erreurs et des exceptions : Fonctionnement.
Structures de données IFT-2000
Procédures et fonctions
Plan cours La notion de pointeur et d’adresse mémoire.
Types de données fondamentaux
Structures des données
Le langage C Structures de données
2.1 - Historique Chapitre 2 : Introduction au langage C++
Le langage C Rappel Pointeurs & Allocation de mémoire.
9ième Classe (Mardi, 4 novembre) CSI2572. H Nous avons vu comment utiliser les directives #define #ifndef #endif Pour s’assurer de l’inclusion unique.
Les Pointeurs et les Tableaux Statiques et Tableaux Dynamiques
LIFI-Java 2004 Séance du Mercredi 22 sept. Cours 3.
La notion de type revisitée en POO
Les adresses des fonctions
SIF-1053 Architecture des ordinateurs
7ième Classe (Mardi, 24 novembre) CSI2572. Devoir 3 ?
Variables et accès en Java. Déclaration des variables final transient static private Printer hp; transient => ne doivent pas être sérialisées volatile.
Un survol du language C.
1 Structures des données. 2  Le tableau permettait de désigner sous un seul nom un ensemble de valeurs de même type, chacune d'entre elles étant repérée.
Tutorat en bio-informatique
Introduction au langage C Fonctions et Procédures
ETNA – 1ème année Guillaume Belmas –
Méthodes et outils de conception Introduction à la programmation Paramètre de retour Appel d’une fonction Portée des variables Définition Pourquoi les.
Les types composés Les enregistrements.
Les surcharges d'opérateurs
CSI 3525, Implémentation des sous-programmes, page 1 Implémentation des sous-programmes L’environnement dans les langages structurés en bloc La structure.
ISBN Chapitre 10 L'implémentation des sous- programmes.
8PRO100 Éléments de programmation Les pointeurs de caractères.
Classe 1 CSI2572 Autres modificateurs de déclaration de variables: & volatile & register & static & auto & extern & const volatile Indique au compilateur.
Cours 4 (14 octobre) Héritage. Chapitre III Héritage.
Conception de Programmes - IUT de Paris - 1ère année Conception de Programmes Objectifs et organisation du cours Introduction à la P.O.O.
Conception de Programmes - IUT de Paris - 1ère année Quelques éléments du langage C++ Les références La surcharge de fonctions Les fonctions «
8PRO107 Éléments de programmation Les adresses et les pointeurs.
Philippe Gandy - 22 septembre 2015 Basé sur les notes de cours de Daniel Morin et Roch Leclerc.
Conception de Programmes - IUT de Paris - 1ère année Les classes Introduction Déclaration d’une classe Utilisation d’une classe Définition des.
Informatique 2A Langage C 5ème séance. Déroulement de la séance 5 1 ère partie Étude des chaînes de caractères 2 ème partie Les structures 3.
Transcription de la présentation:

IFT-2000: Structures de données Éléments techniques avancés du C et du C++ Dominic Genest, 2009

const On sait que le mot-clé « const » sert à spécifier au compilateur qu’une variable ne devrait pas être modifiée. Par contre, la vraie définition du mot-clé « const » est plus complexe. Formellement, ce mot-clé qualifie le type qui le précède pour indiquer que les endroits désignés par ce type précédent ne fournissent qu’un accès en lecture seule, sauf dans le cas exceptionnel, même s’il est le plus courant, où le mot-clé const est placé au début d’une expression de type. Dans ce cas, le mot-clé « const » qualifie le type désigné uniquement par le mot-clé qui le suit immédiatement. Ces subtilités donnent lieu à des nuances importantes, notamment dans des déclarations de pointeurs. Dominic Genest, 2009

const Les deux expressions suivantes sont exactement équivalentes: int const x; const int x; Dominic Genest, 2009

« const T * » versus « T *const » versus « T const * » « const int * » ou « int const * » (les deux sont équivalents) int x=10; const int y = 25; const int *p = &x; const int *q = &y; *p=33; // Erreur de compilation! On ne peut pas écrire dans « *p » printf(‘’%d’’,*p); // Ok (lecture) p=q; //Ok, on peut écrire dans « p » lui-même (c’est différent d’une écriture dans « *p »!) « int * const » (très différent!) int x=10; int z=25; int * const p=&x; *p=33; // Ok! On a le droit d’écrire dans « *p » p=&z; // Erreur de compilation! On n’a pas le droit d’écrire dans « p ». Dominic Genest, 2009

const Un pointeur à quelque chose de « non const » ne peut pas servir à donner un accès en écriture à quelque chose qui a été déclaré « const ». Ainsi: int *p; const int x = 25; p = &x; // Erreur de compilation! Dominic Genest, 2009

Remarque sur les erreurs de compilation La plupart des compilateurs C ne donnent qu’un avertissement de compilation, et non pas une erreur, quand on enfreint une règle de « const ». Tous les compilateurs C++ donnent bel et bien une erreur de compilation quand on enfreint l’une de ces règles. En C++, on peut tricher à l’aide du mot-clé « const_cast ». Cela se fait de la façon suivante: const int x=22; const int *p = &x; *const_cast (p) = 44; Ceci est toutefois toujours évitable et devrait toujours être évité. Si on arrive à un point où on est obligé de faire ça dans notre programme, c’est signe qu’on a une erreur de conception et ça soulève souvent un problème de fond dans notre architecture. Dominic Genest, 2009

Les fonctions « const » en C++ Struct S { int x; void f() { x=5; } void h() const { printf(‘’%d\n’’,x); // Ok, x est utilisé en lecture. g(); // Ok, car g est const aussi. } void g() const { x=3; // Erreur de compilation! f(); // Erreur de compilation! } }; Dominic Genest, 2009

Les fonctions « const » en C++ Il s’agit obligatoirement d’une fonction membre (les fonctions globales « const », c’est absurde). Le mot-clé « const » est placé après la parenthèse fermante dans la déclaration de fonction. Ces fonctions n’ont pas le droit d’appeler des fonctions-membres de la même structure qui ne sont pas « const ». Dans ces fonctions, les variables-membres de la même structure sont accessibles seulement en lecture (elles sont donc « const » de leur point de vue). Dominic Genest, 2009

Pourquoi utiliser « const »? « const » fournit une garantie (sauf si on triche comme par exemple à l’aide du mot-clé « const_cast ») à l’usager de la structure que les données ne seront pas modifiées lors des appels aux fonctions « const ». « const » fournit une garantie à l’usager d’une fonction dont l’un des paramètres est un pointeur sur données « const » que ces données ne seront pas modifiées. Le gros avantage est que le non-respect de ces garanties provoque une erreur de compilation! Ainsi, cela peut éviter des erreurs importantes comme par exemple: Char x[100]; strcpy(‘’dominic’’,x); // Ceci provoque heureusement une erreur de compilation, puisque la chaîne immédiate ‘’dominic’’ est de type « const char[8] » et donc le compilateur interdira de promouvoir un « const char[8] » en « char* » (pas const). (il fallait écrire « strcpy(x,’’dominic’’); » Dominic Genest, 2009

Un autre exemple de l’avantage de « const » en C++ struct Point { float x,y; Point inverse(); }; Ici, on pourrait se demander si la fonction « inverse » va modifier les valeurs de x et de y puis retourner une copie du point, ou bien tout simplement retourner un autre point sans toucher à x et y. Nous ne pouvons pas répondre à cette question sans voir le code de la fonction « Point::inverse() ». Par contre, si la fonction avait été déclarée « const »: struct Point { float x,y; Point inverse() const; }; Alors nous aurions la réponse à la question sans même voir la documentation, car le fait que la fonction soit « const » nous garantie qu’elle ne modifiera pas les valeurs des variables membres. Dominic Genest, 2009

Pointeurs « void » D’abord, penchons-nous sur l’utilité du « type pointable » d’un pointeur. Baptisons « type pointable » le type en avant de l’étoile dans la déclaration d’un pointeur. Par exemple, si on a « int *p; », le type pointable de « p » est « int ». Les utilités du « type pointable » sont les suivantes: – Indiquer au compilateur le nombre d’octets à ajouter ou soustraire selon un nombre d’éléments dans des expressions appliquant les opérateurs ‘[]’, ‘+’ ou ‘-’ au pointeur. – Indiquer au compilateur le format et la taille des données à lire ou écrire lorsqu’on applique l’opérateur ‘*’ au pointeur. Les pointeurs déclarés « void * » sont des pointeurs pour lesquels le type pointable n’est pas spécifié. Dans ce cas, toute utilisation directe de l’opérateur [], +, -, ou * est interdite sur de tels pointeurs, et provoquera une erreur de compilation. Ces pointeurs ne servent finalement qu’à emmagasiner une adresse- mémoire, sans donner d’indication sur la façon d’interpréter les données qui se trouvent à l’adresse-mémoire qu’ils contiennent. Dominic Genest, 2009

Pointeurs « void » Les pointeurs void servent principalement dans les deux cas suivants: – Programmation de structures génériques en langage C, sans utiliser les éléments du C++ prévus à cet effet (sans utiliser les « template »). – Gestion de mémoire (par exemple, les fonctions « malloc », « free » et « realloc » manipulent des pointeurs « void »). Dominic Genest, 2009

Pointeurs à des fonctions De la même façon qu’un pointeur ordinaire peut contenir l’adresse-mémoire d’une variable, un pointeur à une fonction peut contenir l’adresse-mémoire d’une fonction. Pour déclarer un pointeur à une fonction, il faut suivre la recette suivante: 1.Écrire une déclaration de fonction (avec le type de la valeur de retour et les arguments) 2.Remplacer le nom de la fonction par le nom qu’on veut donner à notre pointeur 3.Ajouter des parenthèses autour de ce nom seul 4.Ajouter une étoile juste en avant du nom, à l’intérieur des parenthèses qu’on vient d’ajouter. 5.Supprimer le nom des arguments (mais pas leur type). Ceci est optionnel, on peut les laisser là si on veut, et cela n’impose rien de plus sur les noms des arguments des fonctions dont on veut y placer l’adresse. float ma_fonction(int a, double z) { return a+z; } int main() { float f; float (*mon_pointeur)(int, double); mon_pointeur = &ma_fonction; f = (*mon_pointeur)(55,3.7); printf(‘’%f’’,f); // Ceci affiche 58.7 return 0; } Dominic Genest, 2009

Pointeurs à des fonctions Pour récupérer les adresses de fonctions, les compilateurs C et les anciennes versions des compilateurs C++ permettaient d’omettre l’esperluette (le ‘&’), puisqu’ils devinaient que lorsqu’on indiquait le nom d’une fonction sans le suivre par des parenthèses, on voulait parler de son adresse. De la même façon, ces compilateurs permettaient de ne pas mettre l’étoile pour un appel de fonction via un pointeur à une fonction. Par contre, les nouvelles versions du C++ interdisent ces raccourcis, pour des raisons qui sortent du cadre de ce cours (il est question d’une ambiguïté à laquelle ces raccourcis pourraient mener dans certains cas d’utilisation avancée de génériques avec surchargement d’opérateurs, puis de déduction de types). Il vaut mieux donc éviter ces raccourcis et toujours employer l’esperluette et l’étoile. Dominic Genest, 2009

Les pointeurs à des fonctions Les pointeurs à des fonctions servent principalement aux systèmes de retours d’appels (« call-backs »), afin de permettre à un logiciel de réagir à un événement en fournissant l’adresse-mémoire d’une fonction qu’on doit appeler quand un événement survient (par exemple, un mouvement de souris, une touche enfoncée au clavier, un certain temps en secondes s’est écoulé). Un bon exemple d’une telle utilisation est dans la librairie SDL: Les pointeurs aux fonctions peuvent toujours être remplacés par des fonctionnalités du C++ comme les « fonctions virtuelles » ou les génériques (« templates »). Mais ils tendent à revenir d’usage dans les librairies les plus sophistiquées et les mieux conçues. Par exemple, les librairies « libsigc++ » « boost » (son module « boost::signal ») en font usage. Dominic Genest, 2009

L’opérateur conditionnel ternaire Un bout de code comme le suivant peut être simplifié grandement grâce à l’opérateur conditionnel ternaire ( ? : ): if(age>=18) printf(‘’Vous êtes majeur car vous avez %d ans.’’,age); else printf(‘’Vous êtes mineur car vous avez %d ans. »,age); On peut en effet le remplacer par: printf(age>=18 ? ‘’Vous êtes majeur car vous avez %d ans.’’ : ‘’Vous êtes mineur car vous avez %d ans.’’,age); Dominic Genest, 2009

L’opérateur conditionnel ternaire float f(float x) { if(f<10.0f) return f/2; if(f<20.0f) return f/3; return f/4; } float f(float x) { return f<10.0f ? f/2 : f<20.0f ? f/3 : f/4; } Dominic Genest, 2009

Organisation en mémoire des tableaux à plusieurs dimensions En mémoire, un tableau à plusieurs dimensions est « aplati » (forcément). En effet, un tableau à deux dimensions de N par M peut être vu comme un tableau à une dimension de N éléments dont chacun des éléments est lui-même un tableau à une dimension de M éléments. On peut avoir du mal à se rappeler si cette linéarisation se fait de gauche à droite ou de droite à gauche dans l’ordre de déclaration des dimensions. Par contre, en examinant le typage, une seule avenue est possible. Soit la déclaration « int x[2][3][4]; »: – Le type de l’expression « x » est « int[2][3][4] » – Le type de l’expression « x[0] » est « int[3][4] » – Le type de l’expression « x[0][0] » est « int[4] » – Le type de l’expression « x[0][0][0] » est « int ». x[0] x[1] x[0][0]x[0][1]x[0][2]x[1][0]x[1][1] x[1][2] Dominic Genest, 2009

Les tableaux dynamiques à plusieurs dimensions Comme pour les tableaux de chaînes de caractères, vus précédemment, l’utilisation de n’importe quel tableau à plusieurs dimensions implique un choix « dynamique » ou « statique » pour chaque dimension. Par exemple, un tableau de « int » à trois dimensions peut être déclaré de 8 façons différentes: 1.int x[2][3][4]; // 2 tableaux de 3 tableaux de 4 ints. 2.int *x[2][3]; //2 tableaux de 3 tableaux d’un nombre variable de ints. 3.int **x[2]; // 2 tableaux de nombres variables de tableaux à taille variable de ints. 4.int ***x; // Nombre variable de tableaux à tailles variables de tableaux à tailles variables de ints. 5.int (*x)[3][4]; // Nombre variable de tableaux de 3 par 4 ints. 6.int (**x)[4]; // Nombre variable de tableaux à tailles variables de 4 ints. 7.int (*x[2])[4]; // 2 tableaux d’un nombre variable de tableaux de 4 ints. 8.int *(*x)[3]; // Un nombre variables de tableaux de 3 tableaux de nombres variables de int. De telles déclarations, tout spécialement les cas #7 et #8 ci-haut, peuvent être grandement simplifiées à l’aide de déclarations « typedef ». Dominic Genest, 2009

Les trois sens du mot-clé « static » Attention! Le mot-clé « static » n’a RIEN À VOIR avec le terme « tableau statique » tel qu’employé au cours. Quand on dit « tableau statique », il s’agit, par opposition à « tableau dynamique », d’un tableau dont le cardinal est fixé à la compilation. Le mot-clé « static » n’a ABSOLUMENT RINE À VOIR avec ça! Le mot-clé « static » a trois sens complètement différents selon le contexte où il est utilisé: 1.À l’intérieur d’une fonction: Il indique que la variable ne doit être initialisée que la première fois que ce bloc de code est exécuté, et qu’elle préserve sa valeur pour le prochain appel. 2.Appliqué sur une variable ou une fonction globale: Il indique que la fonction ou la variable en question n’est disponible que pour le fichier «.c » (ou «.cpp ») en cours, et qu’elle ne sera pas rendue disponible lors de l’édition des liens (« link ») pour les autres fichiers «.c » compilés (attention, la notion de « fichier » est complexe ici: il faut tenir compte du fait que du point de vue du compilateur, un fichier «.h » inclus par « #define » est vu comme faisant partie du «.c » ou «.cpp » qui est en train de se faire compiler!) 3.À l’intérieur d’une structure (C++ seulement): S’il est employé pour qualifier une variable membre: – La variable en question sera partagée par toutes les instances de la structure. Il faut alors déclarer la variable aussi à l’extérieur de la structure, à l’aide de l’opérateur « :: », dans un fichier «.cpp ». S’il est employé pour qualifier une fonction membre: – La fonction en question pourra être appelée sans qu’on ait déclaré une instance de la structure. Cette fonction n’aura pas accès aux membres de la structure qui ne sont pas statiques eux-mêmes. Dominic Genest, 2009

« static » employé à l’intérieur d’une fonction void f() { int variable_normale = 0; static int compte = 0; // Ce zéro ne sera // utilisé qu’au tout premier appel de « f ». compte++; printf(‘’La fonction f a été appelée %d fois.\n’’,compte); variable_normale++; printf(‘’%d\n’’,variable_normale); // Ceci affiche // toujours 1 } Dominic Genest, 2009

« static » appliqué sur une variable ou une fonction globale //Allo.h void f(); static void g() { printf(‘’g’’); } void h() { printf(‘’h’’); } static void k(); extern int x; int y; //Allo.c #include ‘’Allo.h’’ int x; static int z = 3; void f() { printf(‘’f’’); k(); } static void k() { printf(‘’k’’); } //Salut.c #include ‘’Allo.h’’ static int z = 5; // Il s’agit ici d’un autre z. int main() { f(); g(); h(); x=44; z = 5; k(); // Erreur au « link »! « k » n’est disponible // que pour « Allo.c » return 0; } // Deux autres erreurs au « link » surviendront parce que // le « int y » et la fonction « h » se retrouvent en double // puisqu’ils sont déclarés directement dans « Allo.h » et // inclus par deux fichiers «.c ». Il aurait fallu déclarer y // « extern » dans « Allo.h », puis le déclarer pour vrai dans // un seul fichier «.c », comme avec la variable « x ». Allo.obj: x f h g (caché) z (caché) k (caché) Salut.obj main h y g (caché) z (caché) Dominic Genest, 2009

« static » sur une fonction membre ou variable membre (c++ seulement) struct S { int x; static int y; void f(); static void g() { x = 2; // Interdit! « g » n’a accès // qu’aux membres eux-mêmes // « static ». y = 5; // Ok. } }; int main() { S a,b; a.x = 5; b.x = 7; S::x = 5; // Interdit! Il faut savoir de // quel « S » on parle. a.y = 8; b.y = 9; // Il s’agit du même y! S::y = 10; // Il vaut mieux utiliser y de cette façon-ci. a.f(); b.f(); S::f(); // Interdit! Il faut savoir de // quel « S » on parle. S::g(); return 0; } Dominic Genest, 2009

Itérations Souvent, un algorithme peut avoir besoin d’un tableau en entrée. Dans ce cas, on peut avoir le réflexe de déclarer la fonction ainsi: – void afficher(float *x, int n) { int i; for(i=0;i<n;i++) printf(‘’%f\n’’,x[i]); } int main() { float x[5] = { 4.3f, 2.2f, 6.4f, 10.5f, -3.5f }; afficher(x,5); return 0; } Les algorithmes de la STL du C++ suivent une autre convention. Plutôt que de prendre l’adresse-mémoire du début d’un tableau puis un entier pour la taille, ceux-ci prennent l’adresse-mémoire du début du tableau, puis l’adresse-mémoire qui correspond à un élément plus loin que le dernier élément du tableau: – void afficher(float *x, float *fin) { for(;x!=fin;x++) printf(‘’%f\n’’,*x); } int main() { float x[5] = { 4.3f, 2.2f, 6.4f, 10.5f, -3.5f }; afficher(x,x+5); // Oui oui, même si x+5 dépasse le tableau, c’est la convention utilisée! En effet, l’algorithme ne // déréférencera jamais cette adresse-mémoire, donc il n’y a aucun danger. return 0; } Dominic Genest, 2009

Itérations Ceci comporte les avantages suivants: – Ça simplifie le code à l’intérieur de la fonction. – Ça optimise le code à l’intérieur de la fonction (le « *x » ci- haut, dans certains contextes, est plus rapide que « x[i] », car il y a une addition de moins à faire). – Ça généralise automatiquement l’algorithme pour des cas où on voudrait ne traiter qu’une partie du tableau. – Ça permet, en C++ à l’aide des génériques (« templates ») de traiter par exactement les mêmes algorithmes aussi bien les tableaux en C que tous les conteneurs de la librairie STL, même les plus évolués (arbres binaires, tables de hachages, etc.). – Et tous ces gains en généralité se font sans la moindre perte de performance. – Il s’agit sans doute de la convention la plus moderne. Dominic Genest, 2009