Structures, Classes et Interfaces

Structures

Une structure est un ensemble d'éléments de n'importe quel type (sauf le type void). Une structure regroupe donc des données logiques de différents types entre elles.

Déclaration de la Structure

Le type de données structure est défini par la description suivante :

struct structure_name
  {
   elements_description
  };

Le nom de la structure ne peut pas être utilisé comme un identifiant (nom d'une variable ou d'une fonction). Il est à noter que dans une structure MQL5, les éléments se suivent les uns après les autres, sans alignement. En C++, cet ordre est réalisé par le compilateur avec l'instruction suivante :

#pragma pack(1)

Si vous souhaitez réaliser un autre alignement dans la structure, utilisez des membres auxiliaires appelés "fillers" et ayant la bonne taille.

Exemple :

struct trade_settings
  {
   uchar  slippage;     // valeur de la taille admissible du slippage 1 octet
   char   reserved1;    // saute 1 octet
   short  reserved2;    // saute 2 octets
   int    reserved4;    // 4 autres octets sont sautés pour assurer l'alignement sur 8 octets
   double take;         // valeur du prix de fixation du profit
   double stop;         // valeur du prix du stop de protection
  };

Cette description des structures alignées n'est nécessaire que pour le transfert à des fonctions de dll importées.

Attention : cette exemple illustre des données conçue de façon incorrecte. Il serait mieux de déclarer en premier les données les plus grandes : le take et stop de type double, et ensuite de déclarer le membre du slippage de type uchar. Dans ce cas, la représentation interne des données sera toujours la même, indépendamment de la valeur spécifiée dans l'instruction #pragma pack().

Si une structure contient des variables de type string et/ou un tableau dynamique, le compilateur crée automatiquement un constructeur implicite. Ce constructeur réinitialise tous les membres de la structure de type string et initialise correctement les tableaux dynamiques.

Structures Simples

Les structures qui ne contiennent pas de chaînes de caractères, d'objets de classe, de pointeurs ou de tableaux dynamiques sont appelées des structures simples. Les variables de structures simples, de même que leurs tableaux, peuvent être passées en paramètres aux fonctions de DLL importées.

La copie de structures simples n'est autorisée que dans deux cas :

  • Si les objets appartiennent à la même structure
  • si les objets sont connectés par un héritage, c'est à dire qu'une structure est dérivée d'une autre.

Pour fournir un exemple, développons la structure CustomMqlTick avec un contenu identique à la structure de base MqlTick. Le compilateur ne permet pas de copier la valeur de l'objet MqlTick dans un objet de type CustomMqlTick. La conversion directe vers le type nécessaire cause également une erreur de compilation :

      //--- la copie de structures simples de différents types est interdite
      my_tick1=last_tick;               // le compilateur retourne une erreur ici
     
      //--- la conversion de structures de différents types les unes vers les autres est interdite également
      my_tick1=(CustomMqlTick)last_tick;// le compilateur retourne une erreur ici

Il ne reste donc qu'une seule option - la copie de la valeur des éléments de la structure une par une. Il est toujours autorisé de copier les valeurs du même type de CustomMqlTick.

      CustomMqlTick my_tick1,my_tick2;
      //--- il est autorisé de copier les objets du même type de CustomMqlTick de la façon suivante
      my_tick2=my_tick1;
     
      //--- crée un tableau à partir des objets de la structure simple CustomMqlTick
      CustomMqlTick arr[2];
      arr[0]=my_tick1;
      arr[1]=my_tick2;

La fonction ArrayPrint() est appelée pour vérification en affichant la valeur du tableau arr[] dans le journal.

//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- développe la structure similaire à la structure intégrée MqlTick
   struct CustomMqlTick
     {
      datetime          time;          // Dernière heure de mise à jour du prix
      double            bid;           // Prix Bid courant
      double            ask;           // Prix Ask courant
      double            last;          // Prix courant de la dernière transaction (Last)
      ulong             volume;        // Volume du prix Last courant
      long              time_msc;      // Dernière mise à jour du prix Last en millisecondes
      uint              flags;         // Flags du tick    
     };
   //--- récupère la valeur du dernier tick
   MqlTick last_tick;
   CustomMqlTick my_tick1,my_tick2;
//--- tentative de copier les données de MqlTick vers CustomMqlTick
   if(SymbolInfoTick(Symbol(),last_tick))
     {
      //--- la copie de structures simples sans aucun rapport est interdite
      //1. my_tick1=last_tick;               // le compilateur retourne une erreur ici
     
      //--- il est également interdit de convertir entre elles des structures sans rapport
      //2. my_tick1=(CustomMqlTick)last_tick;// le compilateur retourne une erreur ici
     
      //--- il faut donc copier les membres de la structure un par un     
      my_tick1.time=last_tick.time;
      my_tick1.bid=last_tick.bid;
      my_tick1.ask=last_tick.ask;
      my_tick1.volume=last_tick.volume;
      my_tick1.time_msc=last_tick.time_msc;
      my_tick1.flags=last_tick.flags;
     
      //--- il est autorisé de copier les objets du même type de CustomMqlTick de la façon suivante
      my_tick2=my_tick1;
     
      //--- crée un tableau à partir des objets de la structure simple CustomMqlTick
      CustomMqlTick arr[2];
      arr[0]=my_tick1;
      arr[1]=my_tick2;
      ArrayPrint(arr);
//--- exemple d'affichage des valeurs du tableau contenant les objets de type CustomMqlTick type
      /*
                       [time]   [bid]   [ask]   [last] [volume]    [time_msc] [flags]
      [0] 2017.05.29 15:04:37 1.11854 1.11863 +0.00000  1450000 1496070277157       2
      [1] 2017.05.29 15:04:37 1.11854 1.11863 +0.00000  1450000 1496070277157       2           
      */
     }
   else
      Print("Echec de SymbolInfoTick(), erreur = ",GetLastError());
  }

Le deuxième exemple montre la copie de structures simples par héritage. Supposons que nous avons la structure de base Animal, à partir de laquelle les deux structures Cat et Dog sont dérivées. Nous pouvons copier les objets Animal et Cat, ainsi que les objets Animal et Dog les uns vers les autres, mais nous ne pouvons pas copier Cat et Dog l'un vers l'autre, bien qu'ils soient tous les deux descendants de la structure Animal.

//--- structure pour décrire les chiens
struct Dog: Animal
  {
   bool              hunting;       // race de chien de chasse
  };
//--- structure pour décrire les chats
struct Cat: Animal
  {
   bool              home;          // race domestique
  };
//--- crée les objets des structures filles
   Dog dog;
   Cat cat;
//--- peut être copié de l'ancêtre vers le descendant (Animal ==> Dog)
   dog=some_animal;
   dog.swim=true;    // les chiens ne peuvent pas nager
//--- vous ne pouvez pas copier les objets des structures filles (Dog != Cat)
   cat=dog;        // le compilateur retourne une erreur

Code d'exemple complet :

//--- structure de base pour décrire les animaux
struct Animal
  {
   int               head;          // nombre de têtes
   int               legs;          // nombre de jambes
   int               wings;         // nombre d'ailes
   bool              tail;          // queue
   bool              fly;           // voler
   bool              swim;          // nager  
   bool              run;           // courir
  };
//--- structure pour décrire les chiens
struct Dog: Animal
  {
   bool              hunting;       // race de chien de chasse
  };
//--- structure pour décrire les chats
struct Cat: Animal
  {
   bool              home;          // race domestique
  };
//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- crée et décrit un objet du type de base Animal
   Animal some_animal;
   some_animal.head=1;
   some_animal.legs=4;
   some_animal.wings=0;
   some_animal.tail=true;
   some_animal.fly=false;
   some_animal.swim=false;
   some_animal.run=true;
//--- crée les objets des types enfants
   Dog dog;
   Cat cat;
//--- peut être copié de l'ancêtre vers le descendant (Animal ==> Dog)
   dog=some_animal;
   dog.swim=true;    // les chiens peuvent nager
//--- vous ne pouvez pas copier les objets des structures filles (Dog != Cat)
   //cat=dog;        // le compilateur retourne une erreur ici
//--- il faut donc copier les éléments un par un
   cat.head=dog.head;
   cat.legs=dog.legs;
   cat.wings=dog.wings;
   cat.tail=dog.tail;
   cat.fly=dog.fly;
   cat.swim=false;   // les chats ne peuvent pas nager
//--- il est possible de copier les valeurs du descendant vers l'ancêtre
   Animal elephant;
   elephant=cat;
   elephant.run=false;// les éléphants ne peuvent pas courir
   elephant.swim=true;// les éléphants peuvent nager
//--- crée un tableau
   Animal animals[4];
   animals[0]=some_animal;
   animals[1]=dog;  
   animals[2]=cat;
   animals[3]=elephant;
//--- affichage
   ArrayPrint(animals);
//--- résultat de l'exécution
/*
      [tête] [jambes] [ailes] [queue] [vole] [nage] [court]
   [0]      1      4       0   true false  false  true
   [1]      1      4       0   true false   true  true
   [2]      1      4       0   true false  false false
   [3]      1      4       0   true false   true false
*/  
  }

Une autre façon de copier les types simples est d'utiliser une union. Les objets des structures doivent être membres de la même union — voir l'exemple dans la section union.

Accès aux Membres d'une Structure

Le nom d'une structure devient un nouveau type de données, vous pouvez donc déclarer des variables de ce type. La structure ne peut être déclarée qu'une seule fois dans un projet. Les membres de la structures sont disponibles en utilisant l'opérateur point (.).

Exemple :

struct trade_settings
  {
   double take;         // valeur du prix de fixation du profit
   double stop;         // valeur du prix du stop de protection
   uchar  slippage;     // valeur du slippage autorisé
  };
//--- crée et initialise une variable de type trade_settings
trade_settings my_set={0.0,0.0,5};  
if (input_TP>0) my_set.take=input_TP;

'pack' pour aligner les champs d'une structure et d'une classe #

L'attribut spécial pack permet de définir l'alignement de champs d'une structure ou d'une classe.

 pack([n])

où n est l'une des valeurs suivantes : 1, 2, 4, 8 ou 16. Il peut être omis.

Exemple :

   struct pack(sizeof(long)) MyStruct
     {
      // les membres seront alignés sur 8 octets
     };
ou
   struct MyStruct pack(sizeof(long))
     {
      // les membres seront alignés sur 8 octets
     };

'pack(1)' est appliqué par défaut pour les structures. Cela signifie que les membres de la structure sont situés les uns parès les autres en mémoire, et que la taille de la structure est égale à la somme de la taille de ses membres.

Exemple :

//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- structure simple sans alignement
   struct Simple_Structure
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(int)=4
      double            d; // sizeof(double)=8
     };
   //--- déclare l'instance d'une structure simple
   Simple_Structure s;  
//--- affiche la taille de chaque membre de la structure  
   Print("sizeof(s.c)=",sizeof(s.c));
   Print("sizeof(s.s)=",sizeof(s.s));
   Print("sizeof(s.i)=",sizeof(s.i));
   Print("sizeof(s.d)=",sizeof(s.d));
//--- s'assure que la taille de la structure POD est égale à la somme de la taille de ses membres
   Print("sizeof(simple_structure)=",sizeof(simple_structure));
/*
  Résultat :
   sizeof(s.c)=1
   sizeof(s.s)=2
   sizeof(s.i)=4
   sizeof(s.d)=8
   sizeof(simple_structure)=15 
*/    
  }

Il peut être nécessaire d'aligner les champs de la structure lors de l'echange de données avec les bibliothèques tierces (*.DLL) où ce type d'alignement est appliqué.

Utilisons quelques exemples pour montrer comment l'alignement fonctionne. Nous appliquerons une structure constituée de 4 membres sans alignement.

//--- structure simple sans alignement
   struct Simple_Structure pack() // aucune taille n'est spécifiée, l'alignement sur 1 octet doit être effectué
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(int)=4
      double            d; // sizeof(double)=8
     };
//--- -- déclare l'instance d'une structure simple  
   Simple_Structure s;

Les champs de la structure doivent être situé en mémoire les uns après les autres selon l'ordre de déclaration et la taille du type. La taille de la structure est 15, et le décalage des champs de la structure dans les tableaux est non défini.

simple_structure_alignment

Déclarons maintenant la même structure avec un alignement sur 4 octets, et exécutons le code.

//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- structure simple avec un alignement sur 4 octets
   struct Simple_Structure pack(4)
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(int)=4
      double            d; // sizeof(double)=8
     };
   //--- déclare l'instance d'une structure simple
   Simple_Structure s;  
//--- affiche la taille de chaque membre de la structure  
   Print("sizeof(s.c)=",sizeof(s.c));
   Print("sizeof(s.s)=",sizeof(s.s));
   Print("sizeof(s.i)=",sizeof(s.i));
   Print("sizeof(s.d)=",sizeof(s.d));
//--- assurons nous que la taille de la structure POD est maintenant égale à la somme de la taille de ses membres
   Print("sizeof(simple_structure)=",sizeof(simple_structure));
/*
  Résultat :
   sizeof(s.c)=1
   sizeof(s.s)=2
   sizeof(s.i)=4
   sizeof(s.d)=8
   sizeof(simple_structure)=16 // la taille de la structure a changé
*/    
  }

La taille de la structure a changé afin que tous les membres de 4 octets ou plus ont un décalage d'un multiple de 4 depuis le début de la structure. Les petits membres sont alignés sur la limite de leur propre taille (par exemple, 2 pour 'short'). Cela ressemble à ça (l'octet ajouté est affiché en gris).

simple_structure_alignment_pack

Dans ce cas, 1 octet est ajouté après le membre s.c, afin que le champ s.s (sizeof(short)==2) ait une limite de 2 octets (alignement pour le type 'short').

Le décalage depuis le début de la structure dans le tableau est également aligné sur 4 octets, c'est à dire que les adresses des éléments a[0], a[1] et a[n] sont des multiples de 4 octets pour Simple_Structure arr[].

Considérons 2 structures supplémentaires constituée de types similaires avec un alignement de 4 octets mais avec un ordre différent des membres. Dans la première structure, les membres sont ordonnés par la taille croissante du type.

//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- structure simple alignée sur 4 octets
   struct CharShortInt pack(4)
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(double)=4
     };
//--- -- déclare l'instance d'une structure simple  
   CharShortInt ch_sh_in;
//--- affiche la taille de chaque membre de la structure  
   Print("sizeof(ch_sh_in.c)=",sizeof(ch_sh_in.c));
   Print("sizeof(ch_sh_in.s)=",sizeof(ch_sh_in.s));
   Print("sizeof(ch_sh_in.i)=",sizeof(ch_sh_in.i));
 
//--- s'assure que la taille de la structure POD est égale à la somme de la taille de ses membres
   Print("sizeof(CharShortInt)=",sizeof(CharShortInt));
/*
  Résultat :
   sizeof(ch_sh_in.c)=1
   sizeof(ch_sh_in.s)=2
   sizeof(ch_sh_in.i)=4
   sizeof(CharShortInt)=8
*/   
  }

Comme nous pouvons le voir, la taille de la structure est 8 et est faite de 2 blocs de 4 octets. Le premier bloc contient les champs avec les types 'char' et 'short', et le second contient le champ avec le type 'int'.

charshortint

Transformons maintenant la 1ère structure dans la 2ème, qui ne diffère que par l'ordre des champs, en déplacant le membre de type 'short' à la fin.

//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- structure simple alignée sur 4 octets
   struct CharIntShort pack(4)
     {
      char              c; // sizeof(char)=1
      int               i; // sizeof(double)=4
      short             s; // sizeof(short)=2
     };
//--- -- déclare l'instance d'une structure simple  
   CharIntShort ch_in_sh;
//--- affiche la taille de chaque membre de la structure  
   Print("sizeof(ch_in_sh.c)=",sizeof(ch_in_sh.c));
   Print("sizeof(ch_in_sh.i)=",sizeof(ch_in_sh.i));
   Print("sizeof(ch_in_sh.s)=",sizeof(ch_in_sh.s));
//--- s'assure que la taille de la structure POD est égale à la somme de la taille de ses membres
   Print("sizeof(CharIntShort)=",sizeof(CharIntShort));
/*
  Résultat :
   sizeof(ch_in_sh.c)=1
   sizeof(ch_in_sh.i)=4
   sizeof(ch_in_sh.s)=2
   sizeof(CharIntShort)=12
*/   
  }

Bien que le contenu de la structure n'ait pas changé, modifier l'ordre des membres a augmenté sa taille.

charintshort

Il faut également tenir compte de l'alignement lors de l'héritage. Démontrons-le avec une structure simple Parent ayant un seul membre de type 'char'. La taille de la structure sans alignement est 1.

   struct Parent
     {
      char              c;    // sizeof(char)=1
     };

Créons la classe fille Children comprenant un membre de type 'short' (sizeof(short)=2).

   struct Children pack(2) : Parent
     {
      short             s;   // sizeof(short)=2
     };

Avec un alignement sur 2 octets, la taille de la structure est égale à 4, bien que la taille de ses membres soit 3. Dans cet exemple, 2 octets sont alloués sur la classe Parent, afin que l'accès au champ 'short' de la classe fille soit aligné sur 2 octets.

La connaissance de l'allocation mémoire pour les membres de la structure est nécessaire si une application MQL5 interagit avec des données tierces en écrivant/lisant les fichiers ou les flux.

Le répertoire MQL5\Include\WinAPI de la Bibliothèque Standard contient les fonctions pour travailler avec les fonctions WinAPI. Ces fonctions appliquent les structures avec un alignement spécifié pour les cas où il est requis de travailler avec WinAPI.  

offsetof est une commande spéciale directement reliée à l'attribut pack. Cela nous permet d'obtenir le décalage d'un membre à partir du début de la structure.

//--- déclare la variable de type Children
   Children child;  
//--- détecte les décalages depuis le début de la structure
   Print("offsetof(Children,c)=",offsetof(Children,c));
   Print("offsetof(Children,s)=",offsetof(Children,s));  
/*
  Résultat :
   offsetof(Children,c)=0
   offsetof(Children,s)=2
*/   

Spécificateur 'final' #

L'utilisation du spécificateur 'final' dans la déclaration d'une structure interdit tout héritage ultérieur de cette structure. Si une structure ne requiert aucune modification ultérieurement, ou que les modifications ne sont pas autorisées pour des raisons de sécurité, déclarez cette structure avec le spécificateur 'final'. De plus, tous les membres de la structure seront également considérés final de façon implicite.

struct settings final
  {
  //--- Corps de la structure
  };
 
struct trade_settings : public settings
  {
  //--- Corps de la structure
  };

Si vous tentez d'hériter d'une structure ayant le modificateur 'final' comme montré dans l'exemple ci-dessus, le compilateur retournera une erreur :

cannot inherit from 'settings' as it has been declared as 'final'
see declaration of 'settings'

Classes #

Les classes sont différentes des structures :

  • le mot-clé class est utilisé dans la déclaration ;
  • par défaut, tous les membres de la classe sont considérés comme privés, sauf mention contraire. Les données membres de la structure sont considérées comme publiques par défaut, sauf mention contraire ;
  • les instances de classe ont toujours une table des fonctions virtuelles, même s'il n'y a aucune fonction virtuelle déclarée dans la classe. Les structures ne peuvent pas avoir de fonctions virtuelles ;
  • l'opérateur new peut être appliqués aux classes ; cet opérateur ne peut pas être appliqué aux structures ;
  • les classes ne peuvent dériver que d'autres classes, les structures ne peuvent dériver que d'autres structures.

Les classes et les structures peuvent avoir un constructeur et un destructeur explicite. Si votre constructeur est défini explicitement, l'initialisation de l'instance d'une sructure ou d'une classe utilisant la séquence d'initialisation n'est pas possible.

Exemple :

struct trade_settings
  {
   double take;         // valeur du prix de fixation du profit
   double stop;         // valeur du prix du stop de protection
   uchar  slippage;     // valeur du slippage autorisé
   //--- Constructeur
          trade_settings() { take=0.0; stop=0.0; slippage=5; }
   //--- Destructeur
         ~trade_settings() { Print("C'est la fin"); } 
  };
//--- Le compilateur générera un message d'erreur comme quoi l'initialisation n'est pas possible
trade_settings my_set={0.0,0.0,5};  

Constructeurs et Destructeurs

Un constructeur est une fonction spéciale appelée automatiquement lors de la création de l'instance d'une structure ou d'une classe et est habituellement utilisé pour initialiser les membres de la classe. Nous parlerons par la suite de classes, mais les mêmes notions s'appliquent aux structures, sauf mention contraire. Le nom d'un constructeur doit correspondre au nom de la classe. Le constructeur n'a pas de type de retour (vous pouvez spécifier le type void).

Les membres de classe définis — chaîne de caractères, tableaux dynamiques et objets nécessitant une initialisation — seront dans tous les cas initialisés, indépendamment du fait qu'il y ait un constructeur ou pas.

Chaque classe peuvent avoir plusieurs constructeurs, différant par le nombre de paramètres et la liste d'initialisation. Un constructeur nécessitant de spécifier des paramètres est appelé un constructeur paramétré.

Un constructeur sans paramètre est appelé un constructeur par défaut. Si aucun constructeur n'est déclaré dans une classe, le compilateur crée un constructeur par défaut pendant la compilation.

//+------------------------------------------------------------------+
//| Classe pour travailler avec une date                             |
//+------------------------------------------------------------------+
class MyDateClass
  {
private:
   int               m_year;          // Année
   int               m_month;         // Mois
   int               m_day;           // Jour du mois
   int               m_hour;          // Heure
   int               m_minute;        // Minutes
   int               m_second;        // Secondes
public:
   //--- Constructeur par défaut
                     MyDateClass(void);
   //--- Constructeur paramétré
                     MyDateClass(int h,int m,int s);
  };

 

Un constructeur peut être déclaré dans la description de la classe et son corps peut ensuite être défini. Par exemple, 2 constructeurs de MyDateClass peuvent être définis de la façon suivante :

//+------------------------------------------------------------------+
//| Constructeur par défaut                                          |
//+------------------------------------------------------------------+
MyDateClass::MyDateClass(void)
  {
//---
   MqlDateTime mdt;
   datetime t=TimeCurrent(mdt);
   m_year=mdt.year;
   m_month=mdt.mon;
   m_day=mdt.day;
   m_hour=mdt.hour;
   m_minute=mdt.min;
   m_second=mdt.sec;
   Print(__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Constructeur paramétré                                           |
//+------------------------------------------------------------------+
MyDateClass::MyDateClass(int h,int m,int s)
  {
   MqlDateTime mdt;
   datetime t=TimeCurrent(mdt);
   m_year=mdt.year;
   m_month=mdt.mon;
   m_day=mdt.day;
   m_hour=h;
   m_minute=m;
   m_second=s;
   Print(__FUNCTION__);
  }

Dans le constructeur par défaut, tous les membres de la classes sont remplis avec la fonction TimeCurrent(). Dans le constructeur paramétré, seules les heures sont remplies. Les autres membres de la classe (m_year, m_month et m_day) seront initialisés automatiquement avec la date courante.

Le constructeur par défaut a une fonction spéciale lors de l'initialisation d'un tableau d'instances de cette classe. Le constructeur dont tous les paramètres ont des valeurs par défaut, n'est pas un constructeur par défaut. Voici un exemple :

//+------------------------------------------------------------------+
//| Une classe avec un constructeur par défault                      |
//+------------------------------------------------------------------+
class CFoo
  {
   datetime          m_call_time;     // Heure du dernier appel à l'objet
public:
   //--- Constructeur avec un paramètre ayant une valeur par défaut n'est pas un constructeur par défaut
                     CFoo(const datetime t=0){m_call_time=t;};
   //--- Constructeur par copie
                     CFoo(const CFoo &foo){m_call_time=foo.m_call_time;};
 
   string ToString(){return(TimeToString(m_call_time,TIME_DATE|TIME_SECONDS));};
  };
//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
// CFoo foo; // Cette variante ne peut pas être utilisée - un constructeur par défaut n'existe pas
//--- Options possibles pour créer un objet CFoo
   CFoo foo1(TimeCurrent());     // Un appel explicite à un constructeur paramétré
   CFoo foo2();                  // Un appel explicite à un constructeur paramétré avec un paramètre par défaut
   CFoo foo3=D'2009.09.09';      // Un appel implicite à un constructeur paramétré
   CFoo foo40(foo1);             // Un appel explicite à un constructeur par copie
   CFoo foo41=foo1;              // Un appel implicite à un constructeur par copie
   CFoo foo5;                    // Un appel explicite à un constructeur par défaut (s'il n'y a pas de constructeur par défaut,
                                 // alors un constructeur paramétré avec une valeur par défaut est appelé)
//--- Options possibles pour reçevoir des pointeurs de CFoo
   CFoo *pfoo6=new CFoo();       // Création dynamique d'un objet et affectation d'un pointeur sur celui-ci
   CFoo *pfoo7=new CFoo(TimeCurrent());// Autre option de création d'un objet dynamique
   CFoo *pfoo8=GetPointer(foo1); // Maintenant pfoo8 pointe sur l'objet foo1
   CFoo *pfoo9=pfoo7;            // pfoo9 et pfoo7 pointent sur le même objet
   // CFoo foo_array[3];         // Cette option ne peut pas être utilisée - un constructeur par défaut n'est pas spécifié
//--- Affiche la valeur de m_call_time
   Print("foo1.m_call_time=",foo1.ToString());
   Print("foo2.m_call_time=",foo2.ToString());
   Print("foo3.m_call_time=",foo3.ToString());
   Print("foo4.m_call_time=",foo4.ToString());
   Print("foo5.m_call_time=",foo5.ToString());
   Print("pfoo6.m_call_time=",pfoo6.ToString());
   Print("pfoo7.m_call_time=",pfoo7.ToString());
   Print("pfoo8.m_call_time=",pfoo8.ToString());
   Print("pfoo9.m_call_time=",pfoo9.ToString());
//--- Supprime les tableaux créés dynamiquement
   delete pfoo6;
   delete pfoo7;
   //delete pfoo8;  // Vous n'avez pas à supprimer pfoo8 explicitement, puisque il pointe sur l'objet foo1 créé automatiquement
   //delete pfoo9;  // Vous n'avez pas à supprimer pfoo8 explicitement puisqu'il oiune sur le même objet pfoo7
  }

Si vous décommentez ces lignes

  //CFoo foo_array[3];     // Cette variante ne peut pas être utilisée - il n'y a aucun constructeur par défaut

ou

  //CFoo foo_dyn_array[];  // Cette variante ne peut pas être utilisée - il n'y a aucun constructeur par défaut

alors le compilateur retournera une erreur pour chaque ligne : "default constructor is not defined".

Si une classe a un constructeur défini, le constructeur par défaut n'est pas généré par le compilateur. Cela signifie que si un constructeur paramétré est déclaré dans une classe, et qu'aucun constructeur n'est déclaré, vous ne pouvez pas déclarer de tableaux d'instances de cette classe. Le compilateur retournera une erreur pour ce script :

//+------------------------------------------------------------------+
//| Une classe sans constructeur par défaut                          |
//+------------------------------------------------------------------+
class CFoo
  {
   string            m_name;
public:
                     CFoo(string name) { m_name=name;}
  };
//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Génère l'erreur "default constructor is not defined" pendant la compilation
   CFoo badFoo[5];
  }

Dans cet exemple, la classe CFoo a un constructeur paramétré - dans ce cas, le compilateur ne crée pas automatiquement un constructeur par défaut pendant la compilation. En même temps, lorsque vous déclarez un tableau d'instances, il est supposé que tous les objets sont créés et initialisés automatiquement. Pendant l'initialisation automatique d'un objet, il est nécessaire d'appeler un constructeur par défaut, mais puisque le constructeur par défault n'est pas déclaré explicitement et n'est pas généré automatiquement par le compilateur non plus, il est impossible de créer cet objet. Pour cette raison, le compilateur génère une erreur au moment de la compilation.

Il existe une syntaxe spéciale pour initialiser un objet en utilisant un constructeur. Les initialiseurs (constructions spéciales pour l'initialisation) des membres d'une struct ou d'une classe peuvent être spécifiées dans la liste d'initialisation.

Une liste d'initialisation est une liste d'initialiseurs séparés par des virgules, venant après le symbole ':', après la liste des paramètres d'un constructeur et avant le corps (se place avant une accolade ouvrante). Il y a plusieurs contraintes :

  • Les listes d'initialisation ne peuvent être utilisées que dans les constructeurs;
  • Les membres parents ne peuvent pas être initialisés dans la liste d'initialisation ;
  • La liste d'initialisation doit être suivie par la définition (implémentation) de la function.

Voici un exemple de plusieurs constructeurs initialisant les membres de la classe.

//+------------------------------------------------------------------+
//| Une classe stockant le nom d'un personnage                      |
//+------------------------------------------------------------------+
class CPerson
  {
   string            m_first_name;     // Premier nom
   string            m_second_name;    // Deuxième nom
public:
   //--- Un constructeur par défaut vide
                     CPerson() {Print(__FUNCTION__);};
   //--- Un constructeur paramétré
                     CPerson(string full_name);
   //--- Un constructeur avec une liste d'initialisation
                     CPerson(string surname,string name): m_second_name(surname), m_first_name(name) {};
   void PrintName(){PrintFormat("Name=%s Surname=%s",m_first_name,m_second_name);};
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CPerson::CPerson(string full_name)
  {
   int pos=StringFind(full_name," ");
   if(pos>=0)
     {
      m_first_name=StringSubstr(full_name,0,pos);
      m_second_name=StringSubstr(full_name,pos+1);
     }
  }
//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Génère l'erreur "default constructor is not defined" (aucun constructeur par défaut n'est défini)
   CPerson people[5];
   CPerson Tom="Tom Sawyer";                       // Tom Sawyer
   CPerson Huck("Huckleberry","Finn");             // Huckleberry Finn
   CPerson *Pooh = new CPerson("Winnie","Pooh");  // Winnie l'Ourson
   //--- Valeurs de sortie
   Tom.PrintName();
   Huck.PrintName();
   Pooh.PrintName();
   
   //--- Supprime l'objet créé dynamiquement
   delete Pooh;
  }

Dans ce cas, la classe CPerson a 3 constructeurs :

  1. Un constructeur par défaut explicite, permettant de créer un tableau d'instances de cette classe ;
  2. Un constructeur a 1 paramètre, prenant en argument le nom complet et le découpe en nom et prénom en fonction de l'espace trouvé ;
  3. Un constructeur a 2 paramètres contenant une liste d'initialisation. Initialiseurs - m_second_name(surname) et m_first_name(name).

Noter que l'initialisation utilisant une liste a remplacé une affectation. Les membres individuels doit être initialisés comme :

 class_member (une liste d'expressions)

Dans la liste d'initialisation, les membres peuvent être dans n'importe quel ordre, mais tous les membres de la classe seront initialisés suivant leur ordre dans la déclaration. Cela signifie que dans le troisième constructeur, le membre m_first_name sera d'abord initialisé, puisqu'il est déclaré en premier, et seulement après, m_second_name sera initialisé. Ceci doit être pris en compte dans les cas om l'initialisation de certains membres de la classe dépendent de valeurs d'autres membres de la classe.

Si aucun constructeur par défaut n'est déclaré dans la classe de base, et qu'au moins un constructeur avec des paramètres est déclaré, vous devriez toujours appeler l'un des constructeurs de la classe de base dans la liste d'initialisation. Il se place entre les virgules comme des membres ordinaires de la liste et sera appelé en premier pendant l'initialisation de l'objet, indépendamment de son placement dans la liste d'initialisation.

//+------------------------------------------------------------------+
//| Classe de base                                                   |
//+------------------------------------------------------------------+
class CFoo
  {
   string            m_name;
public:
   //--- Un constructeur avec une liste d'initialisation
                     CFoo(string name) : m_name(name) { Print(m_name);}
  };
//+------------------------------------------------------------------+
//| Classe dérivée de CFoo                                           |
//+------------------------------------------------------------------+
class CBar : CFoo
  {
   CFoo              m_member;      // Le membre de classe est un objet de la classe parente
public:
   //--- Un constructeur par défaut dans la liste d'initialisation appelle le constructeur d'un parent
                     CBar(): m_member(_Symbol), CFoo("CBAR") {Print(__FUNCTION__);}
  };
//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
   CBar bar;
  }

Dans cet exemple, lors de la création de l'objet bar, le constructeur par défaut CBar() sera appelé, dans lequel le constructeur du parent CFoo est d'abord appelé, et ensuite le constructeur du membre de classe m_member.

Un destructeur est une fonction spéciale, appelée automatiquement lorsqu'un objet est détruit. Le nom du destructeur est le nom de la classe avec un tilde (~). Les chaînes de caractères, les tableaux dynamiques et les objets, nécessitant une désinitialisation, seront désinitialisés quand même, indépendamment de la présence ou non du destructeur. Si un destructeur existe, ces actions seront effectuées après l'appel au destructeur.

Les destructeurs sont toujours virtuels, qu'ils soient déclarés avec le mot-clé virtual ou pas.

Définir des Méthodes de Classe

Les méthodes-fonctions de classe peuvent être définies dans ou en dehors de la déclaration de la classe. Si la méthode est définie dans la classe, alors son corps vient juste après la déclaration de la méthode.

Exemple :

class CTetrisShape
  {
protected:
   int               m_type;
   int               m_xpos;
   int               m_ypos;
   int               m_xsize;
   int               m_ysize;
   int               m_prev_turn;
   int               m_turn;
   int               m_right_border;
public:
   void              CTetrisShape();
   void              SetRightBorder(int border) { m_right_border=border; }
   void              SetYPos(int ypos)          { m_ypos=ypos;           }
   void              SetXPos(int xpos)          { m_xpos=xpos;           }
   int               GetYPos()                  { return(m_ypos);        }
   int               GetXPos()                  { return(m_xpos);        }
   int               GetYSize()                 { return(m_ysize);       }
   int               GetXSize()                 { return(m_xsize);       }
   int               GetType()                  { return(m_type);        }
   void              Left()                     { m_xpos-=SHAPE_SIZE;    }
   void              Right()                    { m_xpos+=SHAPE_SIZE;    }
   void              Rotate()                   { m_prev_turn=m_turn; if(++m_turn>3) m_turn=0; }
   virtual void      Draw()                     { return;                }
   virtual bool      CheckDown(int& pad_array[]);
   virtual bool      CheckLeft(int& side_row[]);
   virtual bool      CheckRight(int& side_row[]);
  }; 

Les fonctions de SetRightBorder(int border) à Draw() sont déclarées et définies directement dans la classe CTetrisShape.

Le constructeur CTetrisShape() et les méthodes CheckDown(int& pad_array[]), CheckLeft(int& side_row[]) et CheckRight(int& side_row[]) sont seulement déclarées dans la classe, mais pas encore définies. Les définitions de ces fonctions seront placés plus loin dans le code. Pour définir la méthode en dehors de la classe, l'opérateur de résolution de portée est utilisé, le nom de la classe est utilisé pour la portée.

Exemple :

//+------------------------------------------------------------------+
//| Constructeur de la classe de base                                |
//+------------------------------------------------------------------+
void CTetrisShape::CTetrisShape()
  {
   m_type=0;
   m_ypos=0;
   m_xpos=0;
   m_xsize=SHAPE_SIZE;
   m_ysize=SHAPE_SIZE;
   m_prev_turn=0;
   m_turn=0;
   m_right_border=0;
  }
//+------------------------------------------------------------------+
//| Vérifie si la barre et le cube peuvent descendre                 |
//+------------------------------------------------------------------+
bool CTetrisShape::CheckDown(int& pad_array[])
  {
   int i,xsize=m_xsize/SHAPE_SIZE;
//---
   for(i=0; i<xsize; i++)
     {
      if(m_ypos+m_ysize>=pad_array[i]) return(false);
     }
//---
   return(true);
  }

Modificateurs d'Accès Public, Protected et Private

Lors du développement d'une nouvelle classe, il est recommandé de restreindre l'accès aux membres depuis l'extérieur. Pour cela, il faut utiliser les mots-clés private ou protected. Dans ce cas, les données cachées ne peuvent être utilisées que depuis les méthodes-fonctions de la même classe. Si le mot-clé protected est utilisé, les données cachées peut être accédées depuis les méthodes des classes héritant de cette classe. La même méthode peut être utilisée pour restreindre l'accès aux fonctions-méthodes de la classe.

Si vous avez besoin d'ouvrir complètement l'accès aux membres et/ou méthodes d'une classe, utilisez le mot-clé public.

Exemple :

class CTetrisField
  {
private:
   int               m_score;                            // Score
   int               m_ypos;                             // Position actuelle des figures
   int               m_field[FIELD_HEIGHT][FIELD_WIDTH]; // Matrice de la zone de jeu
   int               m_rows[FIELD_HEIGHT];               // Numération des lignes de la zone de jeu
   int               m_last_row;                         // Dernière ligne vide
   CTetrisShape     *m_shape;                            // Figure du Tétris
   bool              m_bover;                            // Perdu
public:
   void              CTetrisField() { m_shape=NULL; m_bover=false; }
   void              Init();
   void              Deinit();
   void              Down();
   void              Left();
   void              Right();
   void              Rotate();
   void              Drop();
private:
   void              NewShape();
   void              CheckAndDeleteRows();
   void              LabelOver();
  }; 

Tous les membres de classe et les méthodes déclarées après le spécificateur public: (et avant le spécificateur d'accès suivant) sont disponibles dans toute occurrence de l'objet dans le programme. Dans cet exemple, ce sont les membres suivants : fonctions CTetrisField(), Init(), Deinit(), Down(), Left(), Right(), Rotate() et Drop().

Tous les membres déclarés après le spécificateur d'accès private: (et avant le spécificateur d'accès suivant) ne sont disponibles qu'aux fonctions membres de cette classe. Les spécificateurs d'accès aux élements se terminent toujours avec le symbole ':' et peuvent apparaître plusieurs fois dans la définition de la classe.

Tous les membres de classe déclarés après le spécificateur d'accès protected: (et jusqu'au prochain spécificateur d'accès) ne sont disponibles que pour les fonctions membres de cette classe et des classes descendantes. En essayant de faire référence aux membres comportant les spécificateurs private et protected de l'extérieur, nous obtenons une erreur de compilation. Exemple :

class A
  {
protected:
   //--- l'opérateur de copie n'est diposnible qu'à l'intérieur de la classe A et de ses descendants
   void operator=(const A &)
     {
     }
  };
class B
  {
   //-- objet de classe A déclaré
   A                 a;
  };
//+------------------------------------------------------------------+
//| Fonction de lancement du script                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
   //--- déclare 2 variables de type B
   B b1b2;
   //--- tente de copier l'un des objets dans l'autre
   b2=b1;
  }

Lors de la compilation du code, le message d'erreur est retourné – tentative d'appeler l'opérateur de copie distant :

attempting to reference deleted function 'void B::operator=(const B&)'   trash3.mq5   32   6

La deuxième chaîne de caractères en dessous est une description plus détaillée – l'opérateur de copie dans la classe B a été supprimé explicitement, puisque l'opérateur indisponible de copie de la classe A est appelé :

   function 'void B::operator=(const B&)' was implicitly deleted because it invokes inaccessible function 'void A::operator=(const A&)' 

L'accès aux membres de la classe de base peut être redéfini pendant l'héritage dans les classes dérivées.

Spécificateur 'delete'

Le spécificateur delete marque les fonctions membres de la classe qui ne peuvent pas être utilisées. Cela signifie que si le programme se réfère à une telle fonction explicitement ou implicitement, l'erreur de compilation est générée. Par exemple, ce spécificateur vous permet de rendre les méthodes parentes indisponibles dans une classe fille. Le même résultat peut être obtenu si nous déclarons la fonction dans la zone privée de la classe parente (déclarations dans la section private). Ici, l'utilisation de delete rend le code plus lisible et plus facile à utiliser au niveau des descendants.

class A
  {
public:
                     A(void) {value=5;};
   double            GetValue(void) {return(value);}
private:
   double            value;
  };
class Bpublic A
  {
   double            GetValue(void)=delete;
  };
//+------------------------------------------------------------------+
//| Fonction de lancement du script                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- déclare une variable de type A
   A a;
   Print("a.GetValue()="a.GetValue());
//--- essaye d'obtenir la valeur de la variable de type B
   B b;
   Print("b.GetValue()="b.GetValue()); // le compilateur affiche une erreur sur cette chaîne
  }

Le message du compilateur :

attempting to reference deleted function 'double B::GetValue()'   
   function 'double B::GetValue()' was explicitly deleted here   

Le spécificateur 'delete' permet de désactiver la conversion automatique ou le constructeur par copie, qui autrement aurait été caché dans la section private également.  Exemple :

class A
  {
public:
   void              SetValue(double v) {value=v;}
   //--- désactive l'appel de type int
   void              SetValue(int) = delete;
   //--- désactive l'opérateur de copie
   void              operator=(const A&) = delete;
private:
   double            value;
  };
//+------------------------------------------------------------------+
//| Fonction de lancement du script                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- déclare 2 variables de type A
   A a1a2;
   a1.SetValue(3);      // erreur !
   a1.SetValue(3.14);   // OK
   a2=a1;               // erreur !
  }

Pendant la compilation, nous obtenons les messages d'erreur :

attempting to reference deleted function 'void A::SetValue(int)' 
   function 'void A::SetValue(int)' was explicitly deleted here 
attempting to reference deleted function 'void A::operator=(const A&)'  
   function 'void A::operator=(const A&)' was explicitly deleted here  

Spécificateur 'final' #

L'utilisation du modificateur 'final' dans la déclaration de la classe interdit tout héritage ultérieur de cette classe. Si l'interface de la classe ne nécessite aucune autre modification, ou si les modifications ne sont pas autorisées pour des raisons de sécurité, declarez cette classe avec le spécificateur 'final'. De plus, tous les membres de la classe seront également considérés 'final' de façon implicite.

class CFoo final
  {
  //--- Corps de la classe
  };
 
class CBar : public CFoo
  {
  //--- Corps de la classe
  };

Si vous tentez d'hériter d'une classe ayant le modificateur 'final' comme montré dans l'exemple ci-dessus, le compilateur retournera une erreur :

cannot inherit from 'CFoo' as it has been declared as 'final'
see declaration of 'CFoo'

Unions (union) #

L'union est un type spécial de données, consistant en plusieurs variables partageant le même segment de mémoire. L'union fournit donc la possibilité d'interpréter la même séquence de bits de 2 (ou plus) façons différentes. La déclaration d'une union est similaire à la déclaration d'une structure et commence avec le mot-clé union.

union LongDouble
{
  long   long_value;
  double double_value;
};

Contrairement aux structures, tous les membres de l'nunion appartiennent au même segment de mémoire. Dans cet exemple, l'union de LongDouble est déclarée avec des valeurs de type long et double partageant la même zone de mémoire. Veuillez noter qu'il est impossible que l'union stocke une valeur entière de type long et une valeur réelle de type double simultanément (contrairement à une structure), puisque les variables long_value et double_value se chevauchent (en mémoire). D'un autre côté, un programme MQL5 est capable de traiter des données contenant dans l'union un entier (long) ou un réel (double) n'importe quand. L'union permet donc d'avoir 2 (ou plus) options pour représenter la même séquence de données.

Pendant la déclaration de l'union, le compilateur alloue automatiquement suffisamment d'espace mémoire pour stocker le plus grand type (en terme de volume) dans la variable. La même syntaxe est utilisée pour accéder à l'élément de l'union comme pour les structures — l'opérateur point.

union LongDouble
{
  long   long_value;
  double double_value;
};
//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   LongDouble lb;
//--- récupère et affiche le nombre invalide -nan(ind)
   lb.double_value=MathArcsin(2.0);
   printf("1.  double=%f                integer=%I64X",lb.double_value,lb.long_value);
//--- plus grande valeur normalisée (DBL_MAX)
   lb.long_value=0x7FEFFFFFFFFFFFFF;
   printf("2.  double=%.16e  integer=%I64X",lb.double_value,lb.long_value);
//--- plus petite valeur positive normalisée (DBL_MIN)
   lb.long_value=0x0010000000000000;    
   printf("3.  double=%.16e  integer=%.16I64X",lb.double_value,lb.long_value);
  }
/*  Résultat de l'exécution
    1.  double=-nan(ind)                integer=FFF8000000000000
    2.  double=1.7976931348623157e+308  integer=7FEFFFFFFFFFFFFF
    3.  double=2.2250738585072014e-308  integer=0010000000000000
*/

Puisque les unions permettent au programme d'interpréter les mêmes données en mémoire de différentes façons, elles sont souvent utilisées lorsqu'une conversion de type est requise.

Les unions peuvent être impliquées dans l'héritage, et elles ne peuvent pas non plus avoir de membres statiques en raison de leur nature. Dans tous les autres aspects, l'union se comporte comme une structure, avec tous ses membres sans offset. Les types suivants ne peuvent pas être membre d'une union :

Comme les classes, l'union peut avoir des constructeurs et des destructeurs, ainsi que des méthodes. Par défaut, les membres de l'union ont un type d'accès public. Pour pouvoir créer des éléments privés, utilisz le mot-clé private. Toutes ces possibilités sont affichées dans l'exemple illustrant comment convertir une couleur du type color en ARGB comme le fait la fonction ColorToARGB().

//+------------------------------------------------------------------+
//| Union pour la conversion d'une couleur (BGR) en ARGB             |
//+------------------------------------------------------------------+
union ARGB
  {
   uchar             argb[4];
   color             clr;
   //--- constructeurs
                     ARGB(color col,uchar a=0){Color(col,a);};
                    ~ARGB(){};
   //--- méthodes publiques
public:
   uchar   Alpha(){return(argb[3]);};
   void    Alpha(const uchar alpha){argb[3]=alpha;};
   color   Color(){ return(color(clr));};
   //--- méthodes privées
private:
   //+------------------------------------------------------------------+
   //| place la valeur du canal alpha et la couleur                    |
   //+------------------------------------------------------------------+
   void    Color(color col,uchar alpha)
     {
      //--- définit la couleur du membre clr
      clr=col;
      //--- définit la valeur du composant Alpha - niveau d'opacité
      argb[3]=alpha;
      //--- intervertit les octets des composantes R et B (Rouge et Bleu)     
      uchar t=argb[0];argb[0]=argb[2];argb[2]=t;
     };
  };
//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 0x55 signifie 55/255=21.6 % (0% - transparence totale)
   uchar alpha=0x55; 
//--- le type color est représenté par 0x00BBGGRR
   color test_color=clrDarkOrange;
//--- les valeurs des octets de l'union ARGB sont acceptées ici
   uchar argb[]; 
   PrintFormat("0x%.8X - voici comment le type 'color' est représenté pour %s, BGR=(%s)",
               test_color,ColorToString(test_color,true),ColorToString(test_color));
//--- le type ARGB est représenté comme 0x00RRGGBB, les composants RR et BB sont intervertis
   ARGB argb_color(test_color);
//--- copie le tableau d'octets
   ArrayCopy(argb,argb_color.argb);
//--- voici comment il ressemble dans la représentation ARGB  
   PrintFormat("0x%.8X - représentation ARGB avec le canal alpha=0x%.2x, ARGB=(%d,%d,%d,%d)",
               argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- ajoute le niveau d'opacité
   argb_color.Alpha(alpha);
//--- essaye de définir ARGB comme type 'color'
   Print("ARGB comme color=(",argb_color.clr,")  canal alpha=",argb_color.Alpha());
//--- copie le tableau d'octets
   ArrayCopy(argb,argb_color.argb);
//--- voici comment il ressemble dans la représentation ARGB
   PrintFormat("0x%.8X - représentation ARGB avec le canal alpha=0x%.2x, ARGB=(%d,%d,%d,%d)",
               argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- vérifie avec le résultat de la fonction ColorToARGB()
   PrintFormat("0x%.8X - résultat de ColorToARGB(%s,0x%.2x)",ColorToARGB(test_color,alpha),
               ColorToString(test_color,true),alpha);
  }
/*  Résultat de l'exécution
   0x00008CFF - voici comment le type color est représenté pour clrDarkOrange, BGR=(255,140,0)
  0x00FF8C00 - représentation ARGB avec le canal alpha=0x00, ARGB=(0,255,140,0)
   ARGB comme color=(0,140,255)  canal alpha=85
   0x55FF8C00 - représentation ARGB avec le canal alpha=0x55, ARGB=(85,255,140,0)
   0x55FF8C00 - résultat de ColorToARGB(clrDarkOrange,0x55)
*/ 

Interfaces #

Une interface permet de déterminer une fonctionnalité spécifique, qu'une classe peut ensuite implémenter. En fait, une interface est une classe qui ne peut pas contenir de membre, et n'a pas de constructeur et de destructeur. Toutes les méthodes déclarées dans une interface sont purement virtuelle, même sans définition explicite.

Une interface est définie avec le mot-clé "interface". Exemple :

//--- Interface de base décrivant les animaux
interface IAnimal
  {
//--- Les méthodes de l'interface ont l'accès public par défaut
   void Sound();  // Le son produit par l'animal
  };
//+------------------------------------------------------------------+
//|  La classe CCat hérite de l'interface IAnimal                    |
//+------------------------------------------------------------------+
class CCat : public IAnimal
  {
public:
                     CCat() { Print("Le chat est né"); }
                    ~CCat() { Print("Le chat est mort");  }
   //--- Implémentation de la méthode Sound de l'interface IAnimal
   void Sound(){ Print("meou"); }
  };
//+------------------------------------------------------------------+
//|  La classe CDog hérite de l'interface IAnimal                    |
//+------------------------------------------------------------------+
class CDog : public IAnimal
  {
public:
                     CDog() { Print("Le chien est né"); }
                    ~CDog() { Print("le chien est mort");  }
   //--- Implémentation de la méthode Sound de l'interface IAnimal
   void Sound(){ Print("guaf"); }
  };
//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Un tableau de pointeurs vers des objets de type IAnimal
   IAnimal *animals[2];
//--- Création de classes filles de IAnimal et sauvegarde des pointeurs sur ces objets dans le tableau    
   animals[0]=new CCat;
   animals[1]=new CDog;
//--- Appel de la méthode Sound() de l'interface de base IAnimal pour chaque enfant  
   for(int i=0;i<ArraySize(animals);++i)
      animals[i].Sound();
//--- Suppression des objets
   for(int i=0;i<ArraySize(animals);++i)
      delete animals[i];
//--- Résultat de l'exécution
/*
  Le chat est né
  Le chien est né
   meou
   guaf
  Le chat est mort
  Le chien est mort
*/
  }

Comme pour les classes abstraites, un objet interface ne peut pas être créé sans héritage. Une interface ne peut hériter que d'autres interfaces et peut être le parent de classes. Une interface est toujours visible publiquement.

Une interface ne peut pas être déclarée dans la déclaration d'une classe ou d'une structure, mais un pointeur sur l'interface peut être sauvé dans une variable du type void *. De façon générale, un pointeur sur un objet de n'importe quelle classe peut être sauvé dans une variable du type void *. Pour convertir un pointeur void * vers un pointeur sur un objet d'une classe particulière, utilisez l'opérateur dynamic_cast. Si la conversion n'est pas possible, le résultat de l'opération dynamic_cast sera NULL.

Voir aussi

Programmation Orientée Objet