- Types Integer
- Types Réels (double, float)
- Nombre complexe (complex)
- Type String
- Structures, Classes et Interfaces
- Tableau Dynamique d'Objets
- Matrices et vecteurs
- Conformation des types
- Type Void et Constante NULL
- Types des données
- Pointeurs d'Objets
- Références : Modificateur & et Mot-Clé this
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
|
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
|
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
|
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;
|
La fonction ArrayPrint() est appelée pour vérification en affichant la valeur du tableau arr[] dans le journal.
//+------------------------------------------------------------------+
|
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
|
Code d'exemple complet :
//--- structure de base pour décrire les animaux
|
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
|
'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
|
'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 :
//+------------------------------------------------------------------+
|
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
|
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.
Déclarons maintenant la même structure avec un alignement sur 4 octets, et exécutons le code.
//+------------------------------------------------------------------+
|
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).
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.
//+------------------------------------------------------------------+
|
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'.
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.
//+------------------------------------------------------------------+
|
Bien que le contenu de la structure n'ait pas changé, modifier l'ordre des membres a augmenté sa taille.
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
|
Créons la classe fille Children comprenant un membre de type 'short' (sizeof(short)=2).
struct Children pack(2) : Parent
|
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
|
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 |
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' |
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
|
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.
//+------------------------------------------------------------------+
|
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 :
//+------------------------------------------------------------------+
|
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 :
//+------------------------------------------------------------------+
|
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 :
//+------------------------------------------------------------------+
|
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.
//+------------------------------------------------------------------+
|
Dans ce cas, la classe CPerson a 3 constructeurs :
- Un constructeur par défaut explicite, permettant de créer un tableau d'instances de cette classe ;
- 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é ;
- 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.
//+------------------------------------------------------------------+
|
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
|
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 :
//+------------------------------------------------------------------+
|
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
|
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
|
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.
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
|
Le message du compilateur :
attempting to reference deleted function 'double B::GetValue()'
|
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
|
Pendant la compilation, nous obtenons les messages d'erreur :
attempting to reference deleted function 'void A::SetValue(int)'
|
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 |
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' |
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
|
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
|
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 :
- tableaux dynamiques
- chaînes de caractères
- pointeurs d'objets et de fonctions
- objets de classe
- objets structure ayant des constructeurs et des destructeurs
- objets structure ayant des membres des points 1-5
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().
//+------------------------------------------------------------------+
|
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
|
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