230 likes | 346 Views
9ième Classe (Mardi, 4 novembre) CSI2572. Les macros. Nous avons vu comment utiliser les directives #define #ifndef #endif Pour s’assurer de l’inclusion unique de certains fichiers. Il y a une autre utilitée potentielle à la directive # define.
E N D
Les macros • Nous avons vu comment utiliser les directives #define #ifndef #endif Pour s’assurer de l’inclusion unique de certains fichiers. Il y a une autre utilitée potentielle à la directive #define. Écrire des macros: #define max(a, b) (((a) > (b)) ? (a) : (b)) La syntaxe utilise un nom, suivit de parenthèses dans lesquelles se trouvent des arguments.
#define PI 3.141592 #define ERRMSG "Une erreur s'est produite.\n" #define CARRE(x) (x)*(x) Le préprocesseur examine chaque ligne de code à la recherche du nom d’une macro ; s’il la trouve, il remplace le nom de la macro par sa valeur. Si la macro a un ou plusieurs paramètres, comme CARRE ci-dessus, ils sont remplacés littéralement par leur valeur effective. Ce processus se poursuit dans une ligne jusqu’à ce qu’il n’y ait plus de noms de macros, de sorte qu’une macro peut en contenir une autre, etc. (mais sans faire de cycle !).
if ( CARRE(d) > PI) printf(ERRMSG); sera transformée ainsi par le préprocesseur : if ( (d)*(d) > 3.141592) printf("Une erreur s'est produite.\n");
Attention: #define WIDTH 80 #define LENGTH1 ( WIDTH + 10 ) #define LENGTH2 WIDTH + 10 var1 = LENGTH1 * 20; var2 = LENGTH2 * 20; var1 = ( 80 + 10 ) * 20; var2 = 80 + 10 * 20;
Attention: #define CARRE(x) x * x // ... j = CARRE(i+1); la dernière ligne deviendra : j = i+1 * i+1; qui est interprété comme i + (1*i) +1, soit 2*i+1.
Attention: #define CARRE(x) (x)*(x) // ... int i = 3, j = CARRE(i++); en sortie i vaut 5, et non 4, parce que la macro a été étendue sous la forme (i++)*(i++) et provoque deux incrémentations. Ce genre d’erreur est particulièrement ardu à repérer.
#define getrandom(min, max) \ ((rand()%(int)(((max) + 1)-(min)))+ (min))
On peut « coller » deux paramètres, ou un paramètre et un identificateur, à l’aide du symbole ##. Ainsi, si l’on écrit : #define VAR(x) variable_##x les occurrences de VAR(1) par exemple seront remplacées par variable_1.
Enfin, en plaçant un # devant un paramètre, on demande son remplacement par la chaîne de caractères de son nom : #define AFFICHE(x)\ printf("Valeur de #x = %d\n"\ , (x) ) printf("Valeur de " "index" " = %d\n" ,(index) ) Les occurrences de AFFICHE(index) seront remplacées par : printf("Valeur de index = %d\n" , (index) )
#define _Paste2(x, y) x##y #define noeud(typ) _Paste2(noeud_, typ) #define listedeclare(typ) \ class noeud(typ) { \ noeud(typ) *suivt; \ typ elm; \ public : \ noeud(typ)(typ e, noeud(typ) *suivant = 0) \ { elm = e; suivt = suivant; } \ noeud(typ) *suivant(void) { return suivt; } \ typ &contenu(void) { return elm; } \ }
#include "liste.h" #define declare(x, y) _Paste2(x, declare)(y) declare(liste, int); declare(liste, double); main(){ noeud(int) *ni = new noeud(int)(0); noeud(double) *nd = new noeud(double)(3.14); // ... }
Les fonctions template: • Nous cherchons à écrire une fonction qui marche quel que soit le type des arguments (en autant que ca a un sens). Par exemple, calculer le maximum de 2 nombres à un sens pour tous les types numériques. Solutions: • Surcharger la fonction pour tous les types possibles (int, long, short, double, char, …). Il manque encore les classes de type numérique que l'on à définit soit même. Pas très pratique! • Écrire une macro: #define max(a, b) (((a) > (b)) ? (a) : (b)) Dangereux. Savoir ce qu'on fait. • Écrire un fonction template
template <class T> T max(const T& g, const T& d) { return ((g > d) ? g : d); } • max est une fonction, qui prend 2 arguments, tous les deux de type `T` et rend le maximum des deux, basé sur la définition de l'opérateur > comme il s'applique à T. • La directive: template <class T> indique que la fonction est paramétrée par le type T. T devient ensuite un type normal dans la fonction.
int main(int, char **) { int i; int j; double a; double b; cout << max(i,j) << endl; cout << max(a,b) << endl; return 0; } • cout << max(i,j) << endl; • créée • int max(const int&, const int&) • cout << max(a,b) << endl; • créée • double max(const double&,const double&)
Les fonctions template: • Un template n’est pas un type, c’est un paramètre de construction d’un type. • Les templates sont utilisés pour construire • des fonctions • algorithmes génériques • des classes • types génériques avec implémentations communes
"fonctions templates" et classes • Les classes génériques • Définit des familles de classes • Souvent utilisées pour les conteneurs template <class T1, class T2> class pair { public: T1 first; T2 second; pair (const T1& a, const T2& b): first (a), second (b) {} };
"fonctions templates" et classes • Utilisation des classes génériques • On doit désigner explicitement les types pour lesquels on utilise le template • Exemple : • une paire de strings et de doubles int main () { pair <string, double> e («E1», 23.45); cout << e.first << « » << e.second << endl; }
"fonctions templates" et classes • Pour combiner pair et max il faut définir l’opérateur < pour des paires : template <class T1, class T2> bool operator < const pair<T1, T2>& a, const pair<T1, t2>& b) { return (a.second < b.second); }
Cas concret: • Classe Array
template<class T> class Array { private: int len_; T* data_; int check(int i) const { if (i < 0 || i >= len_) throw BoundsViol("Array", i, len_); return i; }
public: Array(int len=10): len_(len), data_(new T[len]) { } ~Array(){ delete [] data_; } int len() const { return len_;} const T& operator[](int i) const { return data_[check(i)]; } T& operator[](int i) { return data_[check(i)]; } Array(const Array<T>&); Array<T>& operator= (const Array<T>&);};
les instantiations de classes template doivent être explicitement paramêtrées: int main() { Array<int> ai; Array<float> af; Array<char*> ac; Array<String> as; Array< Array<int> > aai; } • Notez l'espace entre les deux >'s dans le dernier exemple. Sans cet espace, le compilateur verrait un >> (symbol de décalage binaire) au lieu de deux >'s.