English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Les bases de la programmation orientée objet

Les bases de la programmation orientée objet

MetaTrader 5Exemples | 22 décembre 2021, 16:58
316 1
Dmitry Fedoseev
Dmitry Fedoseev

 

Introduction

Nous supposons que quiconque a essayé de commencer à apprendre la programmation orientée-objet (POO), a en premier lieu rencontré des termes comme polymorphisme, Types, surcharge et héritage. Peut-être que quelqu'un a regardé des classes toutes faites et essayé de comprendre où se trouvent réellement ces polymorphismes ou encapsulations... Cela pourrait très probablement s’inscrire à la fin du processus d'apprentissage de la POO.

En fait, tout est beaucoup plus simple que cela en donne l’air. Pour utiliser la POO, vous n'avez pas besoin de savoir ce que signifient ces mots - vous pouvez simplement utiliser les fonctionnalités de POO, sans même savoir comment elles s'appellent. J'espère néanmoins que tous ceux qui liront cet article apprendront non seulement à utiliser la POO, mais qu'ils comprendront également la signification de ces mots.

 

Création de bibliothèques de fonctions

La première et la plus simple des applications de la POO consiste à créer vos propres bibliothèques de fonctions fréquemment utilisées. Bien sûr, vous pouvez simplement stocker ces fonctions dans un fichier d'inclusion (mqh). Lorsque vous avez bien besoin d'une fonction, incluez simplement un fichier et appelez cette fonction. Cependant, si vous avez fait de la programmation assez longtemps, vous allez collecter une énorme quantité de fonctions, de sorte qu'il serait difficile de se souvenir de leurs noms et de leur objectif.

Vous pouvez collecter des fonctions dans différents fichiers, en les divisant en catégories en fonction de leur objectif. Par exemple, des fonctions de travail avec des tableaux, des fonctions de travail avec une chaîne, des fonctions de calcul d'ordres, etc. Dans la dernière phrase, le mot « catégorie » peut être remplacé par le mot « classes ». Le sens reste le même, mais examinons ce sujet de plus près - Programmation Orientée Objet.

Ainsi, les fonctions peuvent être divisées en classes : classe de fonctions pour travailler avec des tableaux, classe de fonctions pour travailler avec des chaînes, classe de fonctions pour compter les ordres, etc. Le mot « classe » nous rapproche du sujet de la POO car c'est son concept fondamental. Vous pouvez rechercher dans divers ouvrages de référence, dictionnaires et encyclopédies (par exemple Wikipedia) ce qu'est la « classe de programmation ».

Dans la programmation orientée objet, une classe est une construction utilisée comme modèle pour créer des instances d'elle-même.

Peut-être que la première impression serait à peu près la même que celle que reflètent les mots « polymorphisme », « encapsulation », etc. Pour le moment, par le concept « classe », nous entendrons un ensemble de fonctions et de variables. Dans le cas de l'utilisation de la classe pour créer une bibliothèque - un ensemble de fonctions et de variables regroupées par type de données traitées ou par type d'objets traités : tableaux, chaînes, ordres.

 

Un programme dans le programme

Il y a eu (et il y aura) beaucoup de questions similaires sur le Forum - comment appeler un script d'un Expert Advisor ? Tout en évitant d'utiliser des outils tiers, cette tâche est accomplie en plaçant le code du script dans le code de l'Expert Advisor. En fait, ce n'est pas une tâche difficile, mais un script peut utiliser les mêmes noms de variables et de fonctions qu'EA, vous devrez donc ajuster le code du script. Les changements ne sont pas compliqués, mais probablement importants en volume.

Ce serait formidable d'appeler simplement ce script en tant que programme indépendant séparé ! Ceci est possible si vous programmez le script en tant que classe, puis utilisez cette classe. La quantité de travail ne sera augmentée que de quelques lignes de code. Dans ce cas, une classe combinera des fonctions non pas par type de données traitées, mais selon leur finalité. Par exemple : une classe pour supprimer des ordres en attente, une classe pour ouvrir une position ou passer un ordre, une classe pour travailler avec des objets graphiques, etc.

Une caractéristique importante de la classe est qu'elle se distingue de l'espace dans lequel elle se trouve. La classe est comme un programme s'exécutant dans un système d'exploitation : plusieurs programmes peuvent s'exécuter simultanément, mais seuls, indépendamment les uns des autres. Par conséquent, la classe peut être appelée « un programme dans le programme », car elle se distingue de l'espace dans lequel elle se trouve.

 

Aspect et perception d'une classe

La création de classe commence par le mot classe, suivi du nom de la classe, puis le code entier de la classe est placé entre accolades :

class CName 
  {
   // Here is the entire code of the class
  };
Attention ! N'oubliez pas de mettre un point-virgule après l'accolade fermante.

 

Visible et caché (encapsulation)

Si vous prenez n'importe quel programme, nous savons qu'il comprend une variété de fonctions. Ces fonctions peuvent être divisées en deux types : principales et auxiliaires. Les fonctions principales sont des fonctions dont un programme est réellement composé. Ces fonctions peuvent nécessiter de nombreuses autres fonctions que l'utilisateur n'a pas besoin de connaître. Par exemple, dans le terminal client pour ouvrir une position, le trader doit ouvrir la boîte de dialogue Nouvel ordre, saisir le volume, les valeurs ​de Stop Loss et Take Profit, puis cliquer sur « Acheter » ou « Vendre ».

Mais que se passe-t-il vraiment entre le clic sur le bouton et l'ouverture d'une position - seuls les développeurs de terminaux peuvent le dire avec certitude. On peut supposer que le terminal fait beaucoup d'actions : vérifie la position du volume, vérifie les valeurs ​de Stop Loss et de Take Profit, vérifie la connexion réseau, etc. De très nombreuses procédures sont cachées ou, en d'autres termes, encapsulées. De même, dans une classe, vous pouvez diviser le code en morceaux (fonctions et variables) - certains d'entre eux seront disponibles lors de l'utilisation d'une classe, et d'autres seront cachés.

Les niveaux d'encapsulation sont définis à l'aide des mots clés suivants : private, protected et public. La différence entre protected et private sera examinée un peu plus tard, mais nous allons d’abord aborder les mots-clés private et public. Ainsi, un modèle de classe simple prend la forme suivante :

class CName 
  {
private:
   // Variables and functions available only inside the class
public:
   // Variables and functions available outside the class
  };
C'est suffisant pour tirer profit de la POO. Au lieu d'écrire votre code directement dans l'Expert Advisor (Script ou Indicateur), créez d'abord une classe puis écrivez tout dans cette classe. Ensuite, nous examinerons la différence entre les sections private et public en fonction d’un exemple pratique.

 

Exemple de création d'une bibliothèque

Le modèle de classe présenté ci-dessus peut être utilisé pour créer une bibliothèque de fonctions. Créons une classe pour travailler avec des tableaux. Les tâches les plus courantes pouvant survenir lors de l'utilisation d'un tableau sont l'ajout d'un nouvel élément au tableau et l'ajout d'un nouvel élément unique, à condition que l'élément avec la valeur donnée n'existe pas dans le tableau.

Nommons la fonction qui ajoute un élément au tableau comme AddToEnd(), et la fonction qui ajoute un élément unique au tableau comme AddToEndIfNotExists(). Dans la fonction AddToEndIfNotExists(), nous devrons d'abord vérifier si l'élément ajouté existe déjà dans le tableau, et sinon - utiliser la fonction AddToEnd(). La fonction, qui vérifie si un élément existe déjà dans le tableau, sera considérée comme auxiliaire, nous la placerons donc dans la section private et toutes les autres fonctions - dans la section public. En conséquence, nous obtiendrons la classe suivante :

class CLibArray 
  {
private:
   // Check if an element with required value exists in array
   int Find(int &aArray[],int aValue) 
     {
      for(int i=0; i<ArraySize(aArray); i++) 
        {
         if(aArray[i]==aValue) 
           {
            return(i); // Element exists, return index of element
           }
        }
      return(-1);  // No such element, return -1
     }
public:
   // Add to end of array
   void AddToEnd(int &aArray[],int aValue) 
     {
      int m_size=ArraySize(aArray);
      ArrayResize(aArray,m_size+1);
      aArray[m_size]=aValue;
     }
   // Add to end of array if there is no such value in array already
   void AddToEndIfNotExistss(int &aArray[],int aValue) 
     {
      if(Find(aArray,aValue)==-1) 
        {
         AddToEnd(aArray,aValue);
        }
     }
  };
 

Classe de chargement

Pour utiliser une classe, elle doit être chargée. Si une classe se trouve dans un fichier séparé, vous devez inclure ce fichier

#include <OOP_CLibArray_1.mqh>

puis chargez cette classe. Le chargement de classe est similaire à la déclaration de variable :

CLibArray ar;

Vient d'abord le nom de la classe, puis le nom d'un pointeur pour faire référence à cette instance. Après le chargement, la classe devient un objet. Pour utiliser n'importe quelle fonction d'un objet, écrivez le nom du pointeur, le point puis le nom de la fonction. Après avoir tapé le point, une liste déroulante de fonctions de classe s'ouvrira (Figure 1).

Figure 1. Liste de fonctions
Figure 1. Liste de fonctions

Grâce à la liste déroulante, il n'est pas nécessaire de vous souvenir des noms des fonctions - vous pouvez naviguer dans la liste des noms et vous souvenir du but de la fonction. C'est le plus grand avantage de l'utilisation de classes pour créer des bibliothèques par opposition à la simple collecte de fonctions dans des fichiers.

Dans le cas de la fonction de collecte, lorsque vous tapez quelques lettres initiales du nom de la fonction, la liste déroulante affichera toutes les fonctions de toutes les bibliothèques incluses, et lorsque vous utilisez des classes, uniquement les fonctions liées à la classe spécifiée. Notez également que la fonction Find() n'est pas répertoriée - c'est la différence entre les sections private et public. La fonction est écrite dans la section private et n'est donc pas disponible.

 

Création d'une bibliothèque universelle pour différents types de données (surcharge)

À ce stade, notre bibliothèque comprend des fonctions qui ne travaillent qu'avec des tableaux de type int. En plus de tableaux de type int, nous pouvons avoir besoin d’appliquer les fonctions de bibliothèques aux tableaux des types suivants : uint, long, ulong, etc. Pour les tableaux d'autres types de données, nous devons écrire leurs propres fonctions. Cependant, vous n'avez pas besoin de donner d'autres noms à ces fonctions - la fonction correcte sera sélectionnée automatiquement en fonction du type de paramètre passé ou de l'ensemble de paramètres (dans cet exemple, en fonction du type de paramètres). Nous compléterons la classe par des fonctions permettant de travailler avec des tableaux de type long :

class CLibArray 
  {
private:
   // Для int. Check if an element with required value exists in array
   int Find(int &aArray[],int aValue)
     {
      for(int i=0; i<ArraySize(aArray); i++) 
        {
         if(aArray[i]==aValue) 
           {
            return(i); // Element exists, return index of element
           }
        }
      return(-1); // No such element, return -1
     }
   // For long. Check if an element with required value exists in array
   int Find(long &aArray[],long aValue) 
     {
      for(int i=0; i<ArraySize(aArray); i++) 
        {
         if(aArray[i]==aValue) 
           {
            return(i); // Element exists, return index of element
           }
        }
      return(-1); // No such element, return -1
     }
public:
   // For int. Add to end of array
   void AddToEnd(int &aArray[],int aValue) 
     {
      int m_size=ArraySize(aArray);
      ArrayResize(aArray,m_size+1);
      aArray[m_size]=aValue;
     }
   // For long. Add to end of array
   void AddToEnd(long &aArray[],long aValue) 
     {
      int m_size=ArraySize(aArray);
      ArrayResize(aArray,m_size+1);
      aArray[m_size]=aValue;
     }
   // For int. Add to end of array if there is no such value in array already
   void AddToEndIfNotExistss(int &aArray[],int aValue) 
     {
      if(Find(aArray,aValue)==-1) 
        {
         AddToEnd(aArray,aValue);
        }
     }
   // For long. Add to end of array if there is no such value in array already
   void AddToEndIfNotExistss(long &aArray[],long aValue) 
     {
      if(Find(aArray,aValue)==-1) 
        {
         AddToEnd(aArray,aValue);
        }
     }
  };
Maintenant, en utilisant le même nom, nous avons des fonctionnalités différentes. Ces fonctions sont appelées surchargées, car un nom est chargé avec plus d'une fonctionnalité, c'est-à-dire surchargé.

Vous pouvez trouver cet exemple dans le fichier OOP_CLibArray_1.mqh en pièce jointe de cet article.

 

Une autre méthode de notation de classe

Dans les exemples ci-dessus, toutes les fonctions ont été écrites à l'intérieur de la classe. Si vous avez beaucoup de fonctions et que chacune d'entre elles est assez massive, une telle notation peut ne pas être très confortable. Dans de tels cas, vous pouvez placer des fonctions en dehors de la classe. A l'intérieur de la classe, vous n'écrivez que des noms de fonctions avec des paramètres, et les fonctions sont entièrement décrites en dehors de la classe. De plus, vous devez indiquer que la fonction appartient à une classe spécifique : écrivez d'abord le nom de la classe, puis mettez deux deux-points et le nom de la fonction :

class CLibArray 
  {
private:
   int               Find(int  &aArray[],int  aValue);
   int               Find(long &aArray[],long aValue);
public:
   void              AddToEnd(int  &aArray[],int  aValue);
   void              AddToEnd(long &aArray[],long aValue);
   void              AddToEndIfNotExistss(int  &aArray[],int  aValue);
   void              AddToEndIfNotExistss(long &aArray[],long aValue);
  };
//---
int CLibArray::Find(int &aArray[],int aValue) 
  {
   for(int i=0; i<ArraySize(aArray); i++) 
     {
      if(aArray[i]==aValue) 
        {
         return(i);
        }
     }
   return(-1);
  }
//---
int CLibArray::Find(long &aArray[],long aValue) 
  {
   for(int i=0; i<ArraySize(aArray); i++) 
     {
      if(aArray[i]==aValue) 
        {
         return(i);
        }
     }
   return(-1);
  }
//---
void CLibArray::AddToEnd(int &aArray[],int aValue) 
  {
   int m_size=ArraySize(aArray);
   ArrayResize(aArray,m_size+1);
   aArray[m_size]=aValue;
  }
//---
void CLibArray::AddToEnd(long &aArray[],long aValue) 
  {
   int m_size=ArraySize(aArray);
   ArrayResize(aArray,m_size+1);
   aArray[m_size]=aValue;
  }
//---
void CLibArray::AddToEndIfNotExistss(int &aArray[],int aValue) 
  {
   if(Find(aArray,aValue)==-1) 
     {
      AddToEnd(aArray,aValue);
     }
  }
//---
void CLibArray::AddToEndIfNotExistss(long &aArray[],long aValue) 
  {
   if(Find(aArray,aValue)==-1) 
     {
      AddToEnd(aArray,aValue);
     }
  }

Avec une telle notation, vous pouvez obtenir une image complète de la composition de la classe et examiner de plus près les fonctions individuelles si nécessaire.

Vous pouvez trouver cet exemple dans le fichier OOP_CLibArray_2.mqh en pièce jointe de cet article.

 

Déclarer des variables dans la classe

Continuons à considérer l'exemple mentionné précédemment. Il y a une différence entre coder directement dans le fichier et à l'intérieur de la classe. Directement dans le fichier, vous pouvez affecter des variables avec des valeurs ​selon que vous les déclarez :

int Var = 123;

Si vous déclarez une variable de façon non appropriée, cela ne marchera pas - les valeurs ​devraient être affectées lors de l'exécution d'une fonction d'une classe. Donc, tout d'abord, vous devez passer des paramètres à la classe (c'est-à-dire préparer la classe à travailler). Nommons cette fonction comme Init().

Considérez cela sur base d’un exemple pratique.

 

Exemple de conversion de script en classe

Supposons qu'il existe un script qui supprime les ordres en attente (voir le fichier OOP_sDeleteOrders_1.mq5 en pièce jointe).

// Include file to use the CTrade class from standard delivery
#include <Trade/Trade.mqh>

// External parameters

// Select symbol. true  - delete orders for all symbols, 
//                false - only for symbol of chart, where the script is running
input bool AllSymbol=false;

// Select types of orders to delete
input bool BuyStop       = false;
input bool SellStop      = false;
input bool BuyLimit      = false;
input bool SellLimit     = false;
input bool BuyStopLimit  = false;
input bool SellStopLimit = false;

// Load the CTrade class
CTrade Trade;
//---
void OnStart()
  {
// Variable to check function result
   bool Ret=true;
// Loop by all orders in terminal
   for(int i=0; i<OrdersTotal(); i++)
     {
      ulong Ticket=OrderGetTicket(i); // Select order and get its ticket
                                      // Successfully selected
      if(Ticket>0)
        {
         long Type=OrderGetInteger(ORDER_TYPE);
         // Check order type
         if(Type == ORDER_TYPE_BUY_STOP && !BuyStop) continue;
         if(Type == ORDER_TYPE_SELL_STOP && !SellStop) continue;
         if(Type == ORDER_TYPE_BUY_LIMIT && !BuyLimit) continue;
         if(Type == ORDER_TYPE_SELL_LIMIT && !SellLimit) continue;
         if(Type == ORDER_TYPE_BUY_STOP_LIMIT && !BuyStopLimit) continue;
         if(Type == ORDER_TYPE_SELL_STOP_LIMIT && !SellStopLimit) continue;
         // Check symbol
         if(!AllSymbol && Symbol()!=OrderGetString(ORDER_SYMBOL)) continue;
         // Delete
         if(!Trade.OrderDelete(Ticket))
           {
            Ret=false; // Failed to delete
           }
        }
      // Failed to select order, unknown result,
      // function ended up with error
      else
        {
         Ret=false;
         Print("Error selecting order");
        }
     }

   if(Ret)
     {
      Alert("Script ended successfully");
     }
   else    
     {
      Alert("Script ended up with error, see details. in Journal");
     }
  }

Le script a des paramètres externes qui permettent d'activer différents types d’ordres et de sélectionner le symbole pour lequel les ordres seront supprimés (tous les symboles ou symbole du graphique sur lequel le script est en cours d'exécution).

Convertissez ce script en classe appelée COrderDelete. Dans la section private, déclarons les mêmes variables que celles déclarées dans le script en tant que paramètres externes, mais préfixons les noms de variables avec « m_ » (du mot « member », c'est-à-dire membre de la classe). L'ajout de préfixe n’est ​​pas obligatoire, mais c'est très pratique car cela permet de distinguer facilement les variables. Ainsi, nous pouvons savoir avec certitude que nous avons affaire à des variables limitées par l'espace de classe. De plus, vous ne recevrez pas d'avertissement du compilateur indiquant que la déclaration de variable masque la variable déclarée à la portée globale.

L'utilisation des mêmes noms de variables à portée globale, dans la définition de classe, dans le corps de la fonction n'est pas une erreur, mais rend le programme difficile à comprendre, c'est pourquoi dans de tels cas, le compilateur émet un avertissement. Pour affecter des variables avec des valeurs, ​​écrivez la fonction Init() avec les paramètres correspondant à ces variables (et aux paramètres externes du script). Si vous utilisez cette classe, vous devez d'abord appeler la fonction Init() et lui passer des paramètres externes. Le reste du code du script reste inchangé. L’unique exception - au lieu d'utiliser directement les paramètres externes, vous devez utiliser des variables déclarées dans la classe.

On obtient donc la classe suivante :

#include <Trade/Trade.mqh> 

class COrderDelete 
  {

private:
   // Variables for parameters
   bool              m_AllSymbol;
   bool              m_BuyStop;
   bool              m_SellStop;
   bool              m_BuyLimit;
   bool              m_SellLimit;
   bool              m_BuyStopLimit;
   bool              m_SellStopLimit;
   // Load the CTrade class
   CTrade            m_Trade;
public:
   // Function to set parameters
   void Init(bool aAllSymbol,bool aBuyStop,bool aSellStop,bool aBuyLimit,bool aSellLimit,bool aBuyStopLimit,bool aSellStopLimit) 
     {
      // Set parameters
      m_AllSymbol    =aAllSymbol;
      m_BuyStop      =aBuyStop;
      m_SellStop     =aSellStop;
      m_BuyLimit     =aBuyLimit;
      m_SellLimit    =aSellLimit;
      m_BuyStopLimit =aBuyStopLimit;
      m_SellStopLimit=aSellStopLimit;
     }
   Main function to delete orders
   bool Delete() 
     {
      // Variable to check function result
      bool m_Ret=true;
      // Loop by all orders in terminal
      for(int i=0; i<OrdersTotal(); i++) 
        {
         // Select order and get its ticket
         ulong m_Ticket=OrderGetTicket(i);
         // Successfully selected
         if(m_Ticket>0) 
           {
            long m_Type=OrderGetInteger(ORDER_TYPE);
            // Check order type
            if(m_Type == ORDER_TYPE_BUY_STOP && !m_BuyStop) continue;
            if(m_Type == ORDER_TYPE_SELL_STOP && !m_SellStop) continue;
            if(m_Type == ORDER_TYPE_BUY_LIMIT && !m_BuyLimit) continue;
            if(m_Type == ORDER_TYPE_SELL_LIMIT && !m_SellLimit) continue;
            if(m_Type == ORDER_TYPE_BUY_STOP_LIMIT && !m_BuyStopLimit) continue;
            if(m_Type == ORDER_TYPE_SELL_STOP_LIMIT && !m_SellStopLimit) continue;
            // Check symbol/s61>
            if(!m_AllSymbol && Symbol()!=OrderGetString(ORDER_SYMBOL)) continue;
            // Delete
            if(!m_Trade.OrderDelete(m_Ticket)) 
              {
               m_Ret=false; // Failed to delete
              }
           }
         // Failed to select order, unknown result,
         // function ended up with error
         else 
           {
            m_Ret=false;
            Print("Error selecting order");
           }
        }
      // Return function result
      return(m_Ret);
     }
  };
Vous pouvez trouver un exemple de cette classe dans le fichier OOP_CDeleteOrder_1.mqh en pièce jointe de cet article. Le script utilisant cette classe est réduit au minimum (paramètres externes, charge de classe, appel des méthodes Init() et Delete()) :
// External parameters

// Select symbol. true  - delete orders for all symbols, 
//                false - only for symbol of chart, where the script is running
input bool AllSymbol=false;

// Select types of orders to delete
input bool BuyStop       = false;
input bool SellStop      = false;
input bool BuyLimit      = false;
input bool SellLimit     = false;
input bool BuyStopLimit  = false;
input bool SellStopLimit = false;

// Include file with class
#include <OOP_CDeleteOrder_1.mqh> 

// Load class
COrderDelete od;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart() 
  {
// Pass external parameters to the class
   od.Init(AllSymbol,BuyStop,SellStop,BuyLimit,SellLimit,BuyStopLimit,SellStopLimit);
// Delete orders
   bool Ret=od.Delete();
// Process result of deleting
   if(Ret) 
     { 
       Alert("Script ended successfully"); 
     }
   else    
     { 
       Alert("Script ended up with error, see details in Journal"); 
     }
  }

Vous pouvez trouver un exemple de ce script dans le fichier OOP_sDeleteOrders_2.mq5 en pièce jointe de cet article. La plupart du script traite les résultats de la fonction Delete(), notifiant ainsi les résultats du script.

Maintenant, toutes les fonctions de base du script sont conçues comme une classe située dans un fichier séparé, vous pouvez donc utiliser cette classe depuis n'importe quel autre programme (Expert Advisor ou Script), c'est-à-dire appeler ce script depuis Expert Advisor.

 

Un peu d'automatique (constructeur et destructeur)

Le fonctionnement du programme peut être divisé en trois phases : le lancement du programme, le processus de travail et l'achèvement de son travail. L'importance de cette séparation est évidente : lorsque le programme démarre, il se prépare (par exemple, charge et définit les paramètres avec lesquels travailler), lorsque le programme se termine, il doit effectuer un « nettoyage » (par exemple, supprimer les objets graphiques du graphique).

Pour séparer ces étapes, les Expert Advisors et les indicateurs ont des fonctions particulières : OnInit() (s’exécute au démarrage) et OnDeinit() (s’exécute à l’arrêt). Les classes ont des caractéristiques similaires : vous pouvez ajouter des fonctions qui seront automatiquement exécutées lorsque la classe est chargée et lorsqu'elle est déchargée. Ces fonctions sont appelées Constructeur et Destructeur. Ajouter un constructeur à la classe signifie ajouter une fonction avec exactement le nom comme nom de classe. Pour ajouter un destructeur - faites tout comme pour le constructeur, mais le nom de la fonction commence par un tilde "~".

Un script qui montre le constructeur et le destructeur :

// Class
class CName 
  {
public:
   // Constructor
                     CName() { Alert("Constructor"); }
   // Destructor
                    ~CName() { Alert("Destructor"); }

   void Sleep() { Sleep(3000); }
  };

// Load class
CName cname;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart() 
  {
// Pause
   cname.Sleep();
  }

Cette classe n'a en fait qu'une seule fonction Sleep() qui fait une pause de 3 secondes. Lorsque vous exécutez le script, une fenêtre d'alerte avec le message « Constructeur » apparaît, puis après une pause de trois secondes, une fenêtre d'alerte avec le message « Destructeur » s'affiche. Ceci malgré le fait que les fonctions CName() et ~CName() n'ont jamais été appelées explicitement.

Vous pouvez trouver cet exemple dans le fichier OOP_sConstDestr_1.mq5 en pièce jointe de cet article.

 

Passer des paramètres au constructeur

Dans l'exemple où nous avons converti le script en classe, vous pouvez toujours réduire la quantité de code d'une ligne - supprimez l'appel de la fonction Init(). Les paramètres peuvent être passés au constructeur lors du chargement de la classe. Ajoutez un constructeur à la classe :

COrderDelete(bool aAllSymbol     = false,
             bool aBuyStop       = false,
             bool aSellStop      = false,
             bool aBuyLimit      = false,
             bool aSellLimit     = false,
             bool aBuyStopLimit  = false,
             bool aSellStopLimit=false) 
  {
   Init(aAllSymbol,aBuyStop,aSellStop,aBuyLimit,aSellLimit,aBuyStopLimit,aSellStopLimit);
  }

La fonction Init() reste telle qu'elle était, mais elle est appelée depuis le constructeur. Tous les paramètres du constructeur sont facultatifs, de sorte que la classe peut être utilisée comme avant : chargez la classe sans paramètres et appelez la fonction Init().

Après avoir créé un constructeur, il existe une autre façon d'utiliser cette classe. Lorsque la classe est chargée, vous pouvez lui passer des paramètres sans avoir besoin d'appeler la fonction Init() :

COrderDelete od(AllSymbol,BuyStop,SellStop,BuyLimit,SellLimit,BuyStopLimit,SellStopLimit);

La fonction Init() a été laissée dans la section public pour permettre la réinitialisation de la classe. Lorsque vous utilisez le programme (Expert Advisor), dans un cas, vous devrez peut-être supprimer uniquement les ordres Stop, dans l'autre - uniquement les ordres Limit. Pour ce faire, vous pouvez appeler la fonction Init() avec différents paramètres afin que la fonction Delete() supprime un autre ensemble d’ordres.

Vous pouvez trouver cet exemple dans les fichiers OOP_CDeleteOrder_2.mqh et OOP_sDeleteOrders_3.mq5 en pièce jointe de cet article.

 

Utilisation de plusieurs instances d'une classe

Comme cela a été mentionné dans la section précédente, la même classe peut effectuer différentes actions en fonction des paramètres définis lors de l'initialisation. Si vous savez à quoi servira votre classe, vous pouvez omettre la réinitialisation de la classe. Pour ce faire, vous devez charger quelques instances de classe avec différents paramètres.

Par exemple, il est connu que lorsque notre EA est en cours d'exécution, dans certains cas, nous devons supprimer les ordres BuyStop et BuyLimit, tandis que dans d'autres cas, il s’agira des ordres SellStop et SellLimit. Dans ce cas, vous pouvez charger deux instances de la classe.

Pour supprimer les ordres BuyStop et BuyLimit :

COrderDelete DeleteBuy(false,true,false,true,false,false,false);

Pour supprimer les ordres SellStop et SellLimit :

COrderDelete DeleteSell(false,false,true,false,true,false,false);

Maintenant, lorsque vous souhaitez supprimer les ordres en attente d'achat, utilisez une instance d'une classe :

DeleteBuy.Delete();

Lorsque vous souhaitez supprimer les ordres de vente en attente - autre instance :

DeleteSell.Delete();

 

Tableau d'objets

Vous ne savez peut-être pas toujours avec certitude de combien d'instances de classe vous aurez besoin lorsque votre programme est en cours d'exécution. Dans ce cas, vous pouvez créer un tableau d'instances de classe (objets). Jetons un coup d'œil à cet exemple de classe avec constructeur et destructeur. En modifiant légèrement la classe, passons le paramètre au constructeur, afin que nous puissions surveiller chaque instance de la classe :

// Class
class CName 
  {
private:
   int               m_arg; // Variable for the instance

public:
   // Constructor
   CName(int aArg) 
     {
      m_arg=aArg;
      Alert("Constructor "+IntegerToString(m_arg));
     }
   // Destructor
  ~CName() 
     { 
      Alert("Destructor "+IntegerToString(m_arg)); 
     }
   //---
   void Sleep() 
     { 
      Sleep(3000); 
     }
  };
Utilisons cette classe. Vous pouvez déclarer un tableau d'une certaine taille, par exemple dix éléments :
CName* cname[10];

Voir une différence par rapport à la déclaration habituelle du tableau de variables - un astérisque «*». Un astérisque indique que le pointeur dynamique est utilisé contrairement au pointeur automatique précédemment utilisé.

Vous pouvez utiliser un tableau dynamique (sans taille pré-allouée, ne confondez pas tableau dynamique avec pointeur dynamique) :

CName* cname[];

Dans ce cas, cela nécessitera une graduation (effectuée dans n'importe quelle fonction, dans des scripts - dans la fonction OnStart()) :

ArrayResize(cname,10);

Parcourons maintenant tous les éléments du tableau et chargeons l'instance de classe dans chacun d'eux. Pour ce faire, utilisez le mot-clé new :

ArrayResize(cname,10);
for(int i=0; i<10; i++) 
  {
   cname[i]=new CName(i);
  }
Pause :
cname[0].Sleep();

Vérification de script. Exécutez-le et notez qu'il y a dix constructeurs, mais aucun destructeur. Si vous utilisez des pointeurs dynamiques, les classes ne sont pas déchargées automatiquement lorsque le programme est terminé. En plus de cela, sur l'onglet « Experts », vous pouvez noter des messages sur les fuites de mémoire. Vous devez supprimer les objets manuellement :

for(int i=0; i<10; i++) 
  {
   delete(cname[i]);
  }

Maintenant, à la fin du script, il y a dix destructeurs en cours d'exécution et aucun message d'erreur.

Vous pouvez trouver cet exemple dans le fichier OOP_sConstDestr_2.mq5 en pièce jointe de cet article.

 

Utilisation de la POO pour modifier la logique du programme (fonctions virtuelles, polymorphisme)

Polymorphisme - c'est peut-être la fonctionnalité POO la plus intéressante et la plus significative, qui vous permet de contrôler la logique de votre programme. Il utilise une classe de base avec des fonctions virtuelles et plusieurs classes enfants. Une classe peut prendre plusieurs formes définies par des classes filles.

Prenons un exemple simple - comparaison de deux valeurs. Il peut y avoir cinq versions de comparaison : supérieure à (>), inférieure à (<), supérieure ou égale à (>=), inférieure ou égale à (<=), égale à (==).

Création d’une classe de base avec une fonction virtuelle. Fonction virtuelle - est exactement la même fonction normale, mais sa déclaration commence par le mot virtuel :

class CCheckVariant 
  {
public:
   virtual bool CheckVariant(int Var1,int Var2) 
     {
      return(false);
     }
  };

La fonction virtuelle n'a pas de code. C'est une sorte de connecteur qui acceptera divers dispositifs. Selon le type de dispositif, il effectuera différentes actions.

Créez cinq classes enfants :

//+------------------------------------------------------------------+
//|   >                                                              |
//+------------------------------------------------------------------+

class CVariant1: public CCheckVariant
  {
   bool CheckVariant(int Var1,int Var2)
     {
      return(Var1>Var2);
     }
  };
//+------------------------------------------------------------------+
//|   <                                                              |
//+------------------------------------------------------------------+
class CVariant2: public CCheckVariant
  {
   bool CheckVariant(int Var1,int Var2)
     {
      return(Var1<Var2);
     }
  };
//+------------------------------------------------------------------+
//|   >=                                                             |
//+------------------------------------------------------------------+
class CVariant3: public CCheckVariant
  {
   bool CheckVariant(int Var1,int Var2)
     {
      return(Var1>=Var2);
     }
  };
//+------------------------------------------------------------------+
//|   <=                                                             |
//+------------------------------------------------------------------+
class CVariant4: public CCheckVariant
  {
   bool CheckVariant(int Var1,int Var2)
     {
      return(Var1<=Var2);
     }
  };
//+------------------------------------------------------------------+
//|   ==                                                             |
//+------------------------------------------------------------------+
class CVariant5: public CCheckVariant
  {
   bool CheckVariant(int Var1,int Var2)
     {
      return(Var1==Var2);
     }
  };

Avant d'utiliser cette classe, elle doit être chargée. Si vous savez quelle classe enfant doit être utilisée, vous pouvez déclarer un pointeur avec le type de cet enfant. Par exemple, si vous souhaitez vérifier la condition « > » :

CVariant1 var; // Load class to check the ">" condition

Si, comme dans notre cas, on ne connaît pas à l'avance le type de fils, un pointeur vers une classe est déclaré avec le type de la classe de base. Mais dans ce cas, le pointeur dynamique est utilisé.

CCheckVariant* var;

Cet enfant doit être chargé en utilisant le mot-clé new. Charger l'enfant en fonction de la variante sélectionnée :

// Number of variant
int Variant=5; 
// Depending on variant number one of five children classes will be used
switch(Variant) 
  {
    case 1: 
       var = new CVariant1;
       break;
    case 2: 
       var = new CVariant2;
       break;
    case 3: 
       var = new CVariant3;
       break;
    case 4: 
       var = new CVariant4;
       break; 
    case 5: 
       var = new CVariant5;
       break; 
 }

Vérifier les conditions :

bool rv = var.CheckVariant(1,2);

Le résultat de la comparaison de deux valeurs ​dépendra de la classe enfant, même si le code qui vérifie les conditions est identique pour tous les cas.

Vous pouvez trouver cet exemple dans le fichier OOP_sVariant_1.mq5 en pièce jointe de cet article.

 

En savoir plus sur l'encapsulation (privée, protégée, publique)

Pour le moment, c'est assez clair avec la section public - elle contient des fonctions et des variables qui doivent être visibles pour l'utilisateur de la classe (par utilisateur, nous impliquons un programmeur, écrivant des programmes en utilisant une classe prête à l'emploi.) Du point de vue de l'utilisateur de classe, il n'y a pas de différence entre les sections protected et private - les fonctions et les variables de ces sections ne sont pas disponibles pour l'utilisateur :

//+------------------------------------------------------------------+
//|   Class with the protected keyword                               |
//+------------------------------------------------------------------+
class CName1
  {
protected:
   int ProtectedFunc(int aArg)
     {
      return(aArg);
     }
public:
   int PublicFunction(int aArg)
     {
      return(ProtectedFunc(aArg));
     }
  };
//+------------------------------------------------------------------+
//|   Class with the private keyword                                 |
//+------------------------------------------------------------------+
class CName2
  {
private:
   int PrivateFunc(int aArg)
     {
      return(aArg);
     }
public:
   int PublicFunction(int aArg)
     {
      return(PrivateFunc(aArg));
     }
  };

CName1 c1; // Load class with the protected keyword
CName2 c2; // Load class with the private keyword
Dans cet exemple, il y a deux classes : CName1 et CName2. Chaque classe a deux fonctions : l'une est située dans la section public, l'autre - dans la section protected (pour la classe CName1) ou dans la section private (pour la classe CName2). Les deux classes n'ont qu'une seule fonction de la section public dans la liste déroulante des fonctions (Figures 2 et 3).

Figure 2. Fonctions de la classe CName1
Figure 2. Fonctions de la classe CName1

Figure 3. Fonctions de la classe CName2
Figure 3. Fonctions de la classe CName2

Vous pouvez trouver cet exemple dans le fichier OOP_sProtPriv_1.mq5 en pièce jointe de cet article.

Les sections private et protected déterminent la visibilité de la fonction de classe de base pour ses classes enfants :

//+------------------------------------------------------------------+
//|   Base class                                                     |
//+------------------------------------------------------------------+
class CBase
  {
protected:
   string ProtectedFunc()
     {
      return("CBase ProtectedFunc");
     }
private:
   string PrivateFunc()
     {
      return("CBase PrivateFunc");
     }
public:
   virtual string PublicFunction()
     {
      return("");
     }
  };
//+------------------------------------------------------------------+
//|   Child class                                                    |
//+------------------------------------------------------------------+

class Class: public CBase
  {
public:
   string PublicFunction()
     {
      // With this line everything compiles correctly
      return(ProtectedFunc());
      // If you will uncomment this line and comment the previous one, there will be a compiler error
      // return(PrivateFunc()); 
     }
  };

Dans cet exemple, nous avons une classe de base appelée CBase et une classe enfant appelée Class. Essayez d'appeler la fonction de classe de base située dans les sections protected et private de la classe enfant. Si vous appelez la fonction à partir de la section protected, tout se compile et s'exécute. Si vous appelez la fonction à partir de la section private, une erreur du compilateur s'affiche (impossible d'appeler la fonction membre privée). C'est-à-dire que la fonction de la section private n'est pas visible pour les classes enfants.

La section protected protège les fonctions uniquement des utilisateurs de classe, et la section private protège également les fonctions des classes enfants. La visibilité des fonctions de la classe de base (situées dans différentes sections) à partir des classes enfants est illustrée à la figure 4.

Figure 4. Visibilité des fonctions de base dans la classe enfant
Figure 4. Visibilité des fonctions de base dans la classe enfant
Flèches bleues - les fonctions sont disponibles ; grises - non disponibles.

Vous pouvez trouver cet exemple dans le fichier OOP_sProtPriv_2.mq5 en pièce jointe de cet article.

 

Fonction virtuelle par défaut et héritage

Toutes les fonctions virtuelles de la classe de base ne doivent pas avoir les fonctions correspondantes dans les classes enfants. Si une classe enfant a le même nom de fonction - elle utilisera cette même fonction, sinon - elle exécutera le code de la fonction virtuelle de la classe de base. Considérez cela, par exemple.

//+------------------------------------------------------------------+
//|   Base Class                                                     |
//+------------------------------------------------------------------+
class CBase
  {
public:
   virtual string Function()
     {
      string str="";
      str="Function ";
      str=str+"of base ";
      str=str+"class";
      return(str);
     }
  };
//+------------------------------------------------------------------+
//|   Child class 1                                                  |
//+------------------------------------------------------------------+
class Class1: public CBase
  {
public:
   string Function()
     {
      string str="";
      str="Function ";
      str=str+"of child ";
      return(str);
     }
  };
//+------------------------------------------------------------------+
//|   Child class 2                                                  |
//+------------------------------------------------------------------+
class Class2: public CBase
  {

  };

Class1 c1; // Load class 1
Class2 c2; // Load class 2
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
   Alert("1: "+c1.Function()); // Running function from Class1
   Alert("2: "+c2.Function()); // Running function from CBase
  }

Malgré le fait que la classe Class2 n'a pas de fonctions, il est toujours possible d'appeler la fonction Function() à partir de celle-ci. Cela exécutera la fonction de la classe CBase. La classe Class1 exécutera sa propre fonction :

void OnStart() 
   {
    Alert("1: " + c1.Function()); // Running function from Class1
    Alert("2: " + c2.Function()); // Running function from CBase
   }

Du point de vue de l'utilisateur de la classe, lors de l'utilisation d'une classe enfant, toutes les fonctions de la classe de base de la section public seront disponibles. C'est ce qu'on appelle Héritage. Si la fonction de la classe de base est déclarée virtuelle, elle sera remplacée par la fonction de la classe enfant si une classe enfant a une fonction avec ce nom (Figure 5).

Figure 5. Accès aux fonctions par les utilisateurs de la classe
Figure 5. Accès aux fonctions par les utilisateurs de la classe

Excepté le cas où la classe enfant n'a pas de fonctions correspondant aux fonctions virtuelles de la classe de base, la classe enfant peut avoir des fonctions « supplémentaires » (fonctions sans fonctions virtuelles du même nom à l'intérieur de la classe de base). Si vous chargez une classe en utilisant un pointeur vers le type de classe enfant, ces fonctions seront disponibles. Si vous chargez une classe à l'aide d'un pointeur vers le type de classe de base, ces fonctions ne seront pas disponibles (Figure 6).

Figure 6. Visibilité des fonctions « Extra »
Figure 6. La visibilité de la fonction « extra » (flèche rouge) est déterminée
par type de pointeur utilisé pour charger la classe.

Vous pouvez trouver cet exemple dans le fichier OOP_sDefaultVirtual_1.mq5 en pièce jointe de cet article.

 

Un peu plus sur le chargement des classes

Lorsque vous utilisez des fonctions virtuelles et, par conséquent, une classe de base et des classes enfants, si vous savez quelle classe enfant doit être utilisée, vous pouvez utiliser un pointeur qui correspond à la classe enfant :

Class1 c1; // Load class 1
Class2 c2; // Load class 2

Si vous ne savez pas quelle classe enfant doit être utilisée, utilisez un pointeur dynamique vers le type de classe de base et chargez la classe à l'aide du mot-clé new :

CBase *c; // Dynamic pointer 
void OnStart() 
   {
      c=new Class1; // Load class
      ...

Si vous utilisez un pointeur automatique vers la classe de base

CBase c; // Automatic pointer

la classe de base sera utilisée telle quelle. Lorsque vous appelez ses fonctions virtuelles, il exécutera le code situé dans ces fonctions. Les fonctions virtuelles sont converties en fonctions ordinaires.  

 

Traitement des objets dans Fonction

Le titre de cette section se suffit à lui-même. Les pointeurs vers des objets peuvent être transmis aux fonctions, puis à l'intérieur de la fonction pour vous permettre d’appeler des fonctions d'objet. Le paramètre de fonction peut être déclaré avec le type de classe de base. Cela rend la fonction universelle. Un pointeur vers une classe ne peut être transmis à la fonction que par référence (indiqué par la marque &) :

//+------------------------------------------------------------------+
//|   Base Class                                                     |
//+------------------------------------------------------------------+
class CBase
  {
public:
   virtual string Function()
     {
      return("");
     }
  };
//+------------------------------------------------------------------+
//|   Child class 1                                                  |
//+------------------------------------------------------------------+
class Class1: public CBase
  {
public:
   string Function()
     {
      return("Class 1");
     }
  };
//+------------------------------------------------------------------+
//|   Child class 2                                                  |
//+------------------------------------------------------------------+
class Class2: public CBase
  {
public:
   string Function()
     {
      return("Class 2");
     }
  };

Class1 c1; // Load class 1
Class2 c2; // Load class 2
//+------------------------------------------------------------------+
//|   Function to process objects                                    |
//+------------------------------------------------------------------+
void Function(CBase  &c)
  {
   Alert(c.Function());
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
// Process objects using one function.
   Function(c1);
   Function(c2);
  }
Vous pouvez trouver cet exemple dans le fichier OOP_sFunc_1.mq5 en pièce jointe de cet article.

 

Fonctions et méthodes, variables et propriétés

Jusqu'à présent dans cet article, nous avons utilisé le mot « fonction ». Mais en POO au lieu du mot « fonction », les programmeurs utilisent souvent le mot « méthode ». Si vous regardez la classe de l'intérieur du point de vue d'un programmeur écrivant une classe, toutes les fonctions restent des fonctions. Si vous regardez la classe du point de vue d'un programmeur utilisant une classe prête à l'emploi, les fonctions de l'interface de classe situées dans la section public (disponibles dans la liste déroulante après avoir tapé un point) sont appelées méthodes.

En plus des méthodes, l'interface de classe peut inclure des propriétés de classe. La section public peut inclure non seulement des fonctions, mais aussi des variables (y compris des tableaux).

class CMethodsAndProperties 
   {
    public:
        int               Property1; // Property 1
        int               Property2; // Property 2
        void Function1() 
           {
            //...
            return;
           }
        void Function2() 
           {
            //...
            return;
           }
   };

Ces variables seront appelées propriétés de classe et seront également disponibles dans la liste déroulante (Figure 7).

Figure 7. Méthodes et propriétés de classe en une seule liste
Figure 7. Méthodes et propriétés de classe en une seule liste

Vous pouvez utiliser ces propriétés de la même manière que les variables :

void OnStart() 
   {
    c.Property1 = 1; // Set property 1
    c.Property2 = 2; // Set property 2

    // Read properties
    Alert("Property1 = " + IntegerToString(c.Property1) + ", Property2 = " + IntegerToString(c.Property2));
   }

Vous pouvez trouver cet exemple dans le fichier OOP_sMethodsAndProperties.mq5 en pièce jointe de cet article.

 

Structures de données

Les structures de données sont similaires aux classes, mais juste un peu plus faciles. Bien que vous puissiez le dire ainsi : les classes sont comme des structures de données, mais plutôt compliquées. La différence est que les structures de données peuvent inclure uniquement des variables. À cet égard, il n'est pas nécessaire de les diviser en sections public, private et protected. Tout le contenu de la structure se trouve déjà dans la section public. La structure des données commence par le mot struct, suivi du nom de la structure, à l'intérieur des accolades que vous déclarez des variables.

struct Str1 
   {
    int    IntVar;
    int    IntArr[];
    double DblVar[];
    double DblArr[];
   };

Pour utiliser une structure, elle doit être déclarée comme variable, mais au lieu du type de variable, utilisez le nom de la structure.

Str1 s1;

Vous pouvez également déclarer un tableau de structures :

Str1 sar1[];

Les structures peuvent inclure non seulement des variables et des tableaux, mais également d'autres structures :

struct Str2 
   {
    int    IntVar;
    int    IntArr[];
    double DblVar[];
    double DblArr[];
    Str1   Str;
   };

Dans ce cas, pour appeler la variable de la structure 1 qui fait partie de la structure 2, vous devez utiliser deux points :

s2.Str.IntVar=1;

Vous pouvez trouver cet exemple dans le fichier OOP_Struct.mq5 en pièce jointe de cet article.

Les classes peuvent inclure non seulement des variables, mais aussi des structures.

 

Conclusion

Passons en revue les principaux points de la programmation orientée objet et les moments importants à garder à l'esprit :

1. La classe est créée à l'aide du classe, suivi du nom de la classe, puis à l'intérieur des accolades se trouve le code de la classe écrit en trois sections.

class CName 
  {
private:

protected:

public:
  };

2. Les fonctions et les variables de classe peuvent être situées dans l'une des trois sections : private, protected et public. Les fonctions et variables de la section private ne sont disponibles qu'au sein de la classe. Fonctions et variables de la section protected disponibles au sein de la classe et des classes enfants. Les fonctions et variables de la section public sont accessibles à tous.

3. Les fonctions de classe peuvent être situées à l'intérieur ou à l'extérieur de la classe. Si vous placez des fonctions en dehors de la classe, vous devez identifier à quelle classe elles appartiennent en plaçant le nom de la classe et deux deux-points avant chaque nom de fonction :

void ClassName::FunctionName() { ... }

4. La classe peut être chargée à l'aide d'un pointeur automatique et dynamique. Lorsque vous utilisez un pointeur dynamique, la classe doit être chargée à l'aide du mot-clé new. Dans ce cas, vous devez supprimer un objet à l'aide du mot-clé delete lors de la fermeture de votre programme.

5. Pour dire que la classe enfant appartient à la classe de base, vous devez ajouter le nom de la classe de base après le nom d'une classe enfant.

class Class : public CBase { ... }

6. Vous ne pouvez pas affecter des variables avec des valeurs lors de l'initialisation de la classe. Vous pouvez attribuer des valeurs ​tout en exécutant une fonction, le plus souvent - le constructeur.

7. Les fonctions virtuelles sont déclarées à l'aide du mot-clé virtuel. Si la classe enfant a une fonction du même nom, elle exécute cette même fonction, sinon - exécute la fonction virtuelle de la classe de base.

8. Les pointeurs vers les classes peuvent être transmis aux fonctions. Vous pouvez déclarer des paramètres de fonction avec le type de classe de base, afin de pouvoir transmettre un pointeur vers n'importe quelle classe enfant dans la fonction.

9. La section public a non seulement des fonctions (méthodes), mais aussi des variables (propriétés).

10. Les structures peuvent inclure des tableaux et d'autres structures.

 

Liste des fichiers joints

  • OOP_CLibArray_1.mqh - fichier inclus, doit être placé dans le dossier MQL5/Include. Exemple d'utilisation d’une classe pour créer une bibliothèque. Les mots-clés protected et private. Surcharge
  • OOP_CLibArray_2.mqh - fichier inclus, doit être placé dans le dossier MQL5/Include. Exemple de placement de fonctions de classe au-delà de la classe.
  • OOP_sDeleteOrders_1.mq5 - script, doit être placé dans le dossier MQL5/Scripts. Script simple pour supprimer les ordres en attente.
  • OOP_CDeleteOrder_1.mqh - fichier inclus, doit être placé dans le dossier MQL5/Include. Exemple de conversion du script OOP_sDeleteOrders_1 en classe.
  • OOP_sDeleteOrders_2.mq5 - fichier inclus, doit être placé dans le dossier MQL5/Scripts. Exemple d'utilisation de la classe pour supprimer des ordres. Extrait du fichier OOP_CDeleteOrder_1.mqh (configuration des paramètres via la fonction Init()).
  • OOP_sConstDestr_1.mq5 - script, doit être placé dans le dossier MQL5/Scripts. Constructeur et destructeur démo.
  • OOP_CDeleteOrder_2.mqh - fichier inclus, doit être placé dans le dossier MQL5/Include. Classe qui supprime les ordres avec le constructeur et transmet les paramètres via le constructeur.
  • OOP_sDeleteOrders_3.mq5 - script, doit être placé dans le dossier MQL5/Scripts. Exemple d'utilisation de la classe pour supprimer des ordres. Extrait du fichier OOP_CDeleteOrder_2.mqh (configuration des paramètres via le constructeur).
  • OOP_sConstDestr_2.mq5 - script, doit être placé dans le dossier MQL5/Scripts. Exemple de chargement de classes dans un tableau.
  • OOP_sVariant_1.mq5 - script, doit être placé dans le dossier MQL5/Scripts. Exemple de classe de base avec des enfants. Fonction virtuelle, polymorphisme.
  • OOP_sProtPriv_1.mq5 - script, doit être placé dans le dossier MQL5/Scripts. Un exemple d'identité des mots-clés protected et private lors de l'utilisation d'une classe.
  • OOP_sProtPriv_2.mq5 - script, doit être placé dans le dossier MQL5/Scripts. Exemple d'affectation des mots-clés protected et private sur la classe enfant.
  • OOP_sDefaultVirtual_1.mq5 - script, doit être placé dans le dossier MQL5/Scripts. Exemple de classe enfant qui n'a pas de fonction correspondant à la fonction virtuelle de la classe de base.
  • OOP_sFunc_1.mq5 - script, doit être placé dans le dossier MQL5/Scripts. Exemple d'utilisation d'objets dans une fonction.
  • OOP_sMethodsAndProperties.mq5 - script, doit être placé dans le dossier MQL5/Scripts. Exemple de propriétés.
  • OOP_Struct.mq5 - script, doit être placé dans le dossier MQL5/Scripts. Exemple de structures.

Après avoir expérimenté ces fichiers, vous pouvez tous les supprimer à l'exception de OOP_CDeleteOrder_2.mqh et OOP_sDeleteOrders_3.mq5. Les fichiers OOP_CDeleteOrder_2.mqh et OOP_sDeleteOrders_3.mq5 peuvent être utiles dans la programmation pratique.

Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/351

Fichiers joints |
files_en.zip (11.04 KB)
Derniers commentaires | Aller à la discussion (1)
Lionel Niquet
Lionel Niquet | 9 janv. 2022 à 10:35
Well done ! Very nice article for beginners.
Approche orientée objet pour créer des panneaux multi-délais et multi-devises Approche orientée objet pour créer des panneaux multi-délais et multi-devises
Cet article décrit comment la programmation orientée objet peut être utilisée pour créer des panneaux multi-délais et multi-devises pour MetaTrader 5. L'objectif principal est de créer un panneau universel, qui peut être utilisé pour afficher de nombreux types de données, tels que les prix, les changements de prix, les valeurs des indicateurs ou les conditions d'achat/vente personnalisées sans avoir besoin de modifier le code du panneau lui-même.
Créez vos propres panneaux graphiques en MQL5 Créez vos propres panneaux graphiques en MQL5
La convivialité du programme MQL5 est déterminée à la fois par sa riche fonctionnalité et par une interface utilisateur graphique élaborée. La perception visuelle est parfois plus importante qu'un fonctionnement rapide et stable. Voici un guide étape par étape pour créer vous-même des panneaux d'affichage sur la base des classes de la bibliothèque standard.
Systèmes de trading simples utilisant des indicateurs de sémaphore Systèmes de trading simples utilisant des indicateurs de sémaphore
Si nous examinons en profondeur tout système de trading complexe, nous verrons qu’il est basé sur un ensemble de signaux de trading simples. Par conséquent, il n’est pas nécessaire que les développeurs novices commencent à écrire des algorithmes complexes immédiatement. Cet article fournit un exemple de système de trading qui utilise des indicateurs de sémaphore pour effectuer des transactions.
Accélération des calculs avec le réseau cloud MQL5 Accélération des calculs avec le réseau cloud MQL5
Combien de cœurs avez-vous sur votre ordinateur personnel ? Combien d'ordinateurs pouvez-vous utiliser pour optimiser une stratégie de trading ? Nous montrons ici comment utiliser le réseau cloud MQL5 pour accélérer les calculs en recevant la puissance de calcul à travers le monde d'un simple clic de souris. L'expression « Le temps, c'est de l'argent » devient de plus en plus d'actualité d'année en année, et nous ne pouvons pas nous permettre d'attendre des calculs importants pendant des dizaines d'heures, voire des jours.