Estruturas, Classes e Interfaces

Estruturas

Uma estrutura é um conjunto de elementos de qualquer tipo (exceto o tipo void). Portanto, a estrutura combina dados logicamente relacionados de diferentes tipos.

Declaração da Estrutura

O tipo de dados estrutura é determinado pela seguinte descrição:

struct structure_name
  {
   elements_description
  };

O nome da estrutura não pode ser usado como um identificador (nome de uma variável ou função). Deve-se notar que em estruturas MQL5, os elementos seguem um ao outro diretamente, sem alinhamento. Em C++ tal ordem é feita pelo compilador usando a seguinte instrução:

#pragma pack(1)

Se você quiser ter outro alinhamento na estrutura, use membros auxiliares, "fillers" (preenchedores) para o tamanho certo.

Exemplo:

struct trade_settings
  {
   uchar  slippage;     // valor do tamanho do slippage admissível - 1 byte
   char   reserved1;    // pula 1 byte
   short  reserved2;    // pula 2 bytes
   int    reserved4;    // outros 4 bytes são pulados. garantir um alinhamento de 8 bytes de limite
   double take;         // valores do preço de fixação de lucro
   double stop;         // valor do preço de stop de proteção
  };

Tal descrição de estruturas alinhadas é necessário somente para transferência de dados para funções de dll importadas.

Atenção: Este exemplo ilustra dados incorretamente esquematizados. Seria melhor primeiro declarar o take e stop de grandes volumes de dados do tipo double, e então declarar o membro slippage do tipo uchar. Neste caso, a representação interna de dados será sempre a mesma, independentemente do valor especificado no #pragma pack().

Se a estrutura contém variáveis do tipo string e/ou objeto de um array dinâmico, o compilador atribui um construtor implícito para tal estrutura. Este construtor redefine todos os membros de tipo string da estrutura e corretamente inicializa objetos do array dinâmico.

Estruturas Simples

As estruturas contendo cadeias de caracteres, objetos de classe, ponteiros e objetos de matrizes dinâmicas são chamadas de estruturas simples. As variáveis ​​de estruturas simples e suas matrizes podem ser transferidas ​​como parâmetros para as funções importadas a partir de DLL.

Copiar e colar estruturas simples é permitido apenas em dois casos:

  • se os objetos pertencem ao mesmo tipo de estrutura
  • se os objetos são ligados por uma linha herança, isto é, uma estrutura é um descendente de uma outra estrutura.

Mostraremos isso com ajuda de exemplos, criamos uma estrutura personalizada CustomMqlTick idêntica em composição à estrutura construída MqlTick. O compilador não permitirá tentativas de copiar e colar os valores do objeto MqlTick no objeto de tipo CustomMqlTick. A conversão direta para o tipo desejado também causará um erro de compilação:

      //--- copiar estruturas simples de diferentes tipos é restrito
      my_tick1=last_tick;               // aqui o compilador gerará um erro
     
      //--- combinar estruturas de diferente tipo também é restrito
      my_tick1=(CustomMqlTick)last_tick;// aqui o compilador gerará um erro

Portanto, resta só uma opção, isto é, copiar e colar os valores dos membros da estrutura elemento por elemento. Mas, ao fazer isto, é permitido copiar e colar os valores dos objetos do mesmo tipo CustomMqlTick.

      CustomMqlTick my_tick1,my_tick2;
      //--- também é possível copiar e colar objetos da mesma estrutura CustomMqlTick
      my_tick2=my_tick1;
     
      //--- criamos uma matriz a partir de objetos da estrutura simples CustomMqlTick e registramos nela os valores
      CustomMqlTick arr[2];
      arr[0]=my_tick1;
      arr[1]=my_tick2;

Como um teste é chamada a função ArrayPrint() para exibir os valores da matriz arr[], no diário.

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- criamos a mesma estrutura como a construída por MqlTick
   struct CustomMqlTick
     {
      datetime          time;          // Tempo da última atualização dos preços
      double            bid;           // Preço atual Bid
      double            ask;           // Preço atual Ask
      double            last;          // Preço atual da última transação (Last)
      ulong             volume;        // Volume para o preço atual Last
      long              time_msc;      // Hora da última atualização dos preços em milissegundos
      uint              flags;         // Sinalizadores de ticks     
     };
   //--- obtemos os valores do último ticks
   MqlTick last_tick;
   CustomMqlTick my_tick1,my_tick2;
//--- tentamos colar e copiar os dados a partir do MqlTick no CustomMqlTick
   if(SymbolInfoTick(Symbol(),last_tick))
     {
      //--- copiar e colar estruturas simples não aparentadas é restrito
      //1. my_tick1=last_tick;               // aqui o compilador gerará um erro
     
      //--- combinar estruturas não aparentadas também é restrito
      //2. my_tick1=(CustomMqlTick)last_tick;// aqui o compilador gerará um erro
     
      //--- por isso copiamos e colamos os membros da estrutura elemento por elemento     
      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;
     
      //--- também é possível copiar e colar objetos da mesma estrutura CustomMqlTick
      my_tick2=my_tick1;
     
      //--- criamos uma matriz a partir de objetos da estrutura simples CustomMqlTick e registramos nela os valores
      CustomMqlTick arr[2];
      arr[0]=my_tick1;
      arr[1]=my_tick2;
      ArrayPrint(arr);
//--- exemplo de exibição de valores de matriz contendo objetos do tipo CustomMqlTick
      /*
                       [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("SymbolInfoTick() failed, error = ",GetLastError());
  }

O segundo exemplo mostra a possibilidade de copiar e colar estruturas simples segundo linha de herança. Assumamos que temos uma estrutura básica Animal, da qual são geradas - para herança - as estruturas Cat e Dog. Nós podemos copiar e colar entre si mesmos os objetos Animal e Cat (ou Animal e Dog), no entanto não podemos copiar e colar entre sim mesmos Cat e Dog, embora ambos sejam descendentes da estrutura Animal.

//--- estrutura para descrever cães
struct Dog: Animal
  {
   bool              hunting;       // raça de caça
  };
//--- estrutura para descrição de gatos
struct Cat: Animal
  {
   bool              home;          // raça domestica
  };
//--- criamos os objetos das subclasses
   Dog dog;
   Cat cat;
//--- é possível copiar no ancestral e colar no descendente (Animal ==> Dog)
   dog=some_animal;
   dog.swim=true;    // cães sabem nadar
//--- é impossível copiar e colar objetos de subestruturas (Dog != Cat)
   cat=dog;        // aqui o compilador gerará um erro

Código completo de exemplo:

//--- estrutura básica para descrever animais
struct Animal
  {
   int               head;          // número de cabeças
   int               legs;          // número de patas
   int               wings;         // número de assas
   bool              tail;          // presença de cauda
   bool              fly;           // voa
   bool              swim;          // nada
   bool              run;           // corre
  };
//--- estrutura para descrever cães
struct Dog: Animal
  {
   bool              hunting;       // raça de caça
  };
//--- estrutura para descrição de gatos
struct Cat: Animal
  {
   bool              home;          // raça domestica
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- criamos um objeto do tipo básico Animal e descrevemo-lo
   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;
//--- criamos os objetos dos subtipos
   Dog dog;
   Cat cat;
//--- é possível copiar no ancestral e colar no descendente (Animal ==> Dog)
   dog=some_animal;
   dog.swim=true;    // cães sabem nadar
//--- é impossível copiar e colar objetos de subestruturas (Dog != Cat)
   //cat=dog;        // aqui o compilador gerará um erro
//--- por isso é possível copiar e colar apenas elemento por elemento
   cat.head=dog.head;
   cat.legs=dog.legs;
   cat.wings=dog.wings;
   cat.tail=dog.tail;
   cat.fly=dog.fly;
   cat.swim=false;   // gatos não sabem nadar
//--- é possível copiar valores no descendente e colá-los no ancestral
   Animal elephant;
   elephant=cat;
   elephant.run=false;// elefantes não sabem correr
   elephant.swim=true;// elefantes nadam
//--- criamos uma matriz
   Animal animals[4];
   animals[0]=some_animal;
   animals[1]=dog;  
   animals[2]=cat;
   animals[3]=elephant;
//--- imprimimos
   ArrayPrint(animals);
//--- resultado da execução
/*
       [head] [legs] [wings] [tail] [fly] [swim] [run]
   [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
*/  
  }

Outro método para copiar e colar tipos simples consiste em utilizar associações, para fazer isto, os objetos destas estruturas devem ser membros da mesma associação — veja o exemplo em union.

Acesso a Membros de Estrutura

A estrutura é um novo tipo de dados permitindo declarar variáveis deste tipo. A estrutura pode ser declarado somente um vez dentro de um projeto. Os membros de estrutura são acessados usando aoperação ponto (.).

Exemplo:

struct trade_settings
  {
   double take;         // valor do preço de fixação do lucro
   double stop;         // valor do preço stop de proteção
   uchar  slippage;     // valor do slippage admissível
  };
//--- cria e inicializa uma variável do tipo trade_settings
trade_settings my_set={0.0,0.0,5};  
if (input_TP>0) my_set.take=input_TP;

pack para alinhamento de campos de estruturas e classes

O atributo especial pack permite definir o alinhamento dos campos de estrutura ou classe.

 pack([n])

onde n  é um dos seguintes valores 1,2,4,8 ou 16. Pode estar ausente.

Exemplo:

   struct pack(sizeof(long)) MyStruct
     {
      // membros da estrutura serão alinhados a 8 bytes
     };
ou
   struct MyStruct pack(sizeof(long))
     {
      // membros da estrutura serão alinhados a 8 bytes
     };

Por padrão, pack(1) é usado para estruturas. Isso significa que na memória os membros da estrutura estão localizados um após o outro e o tamanho da estrutura é igual à soma dos tamanhos de seus membros.

Exemplo:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- estrutura simples sem alinhamento
   struct Simple_Structure
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(int)=4
      double            d; // sizeof(double)=8
     };
   //--- declaramos uma instância de uma estrutura simples   
   Simple_Structure s;  
//--- exibimos o tamanho de cada membro da estrutura  
   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));
//--- verificamos de que o tamanho da estrutura POD é igual à soma dos tamanhos de seus membros
   Print("sizeof(simple_structure)=",sizeof(simple_structure));
/*
  Resultado:
   sizeof(s.c)=1
   sizeof(s.s)=2
   sizeof(s.i)=4
   sizeof(s.d)=8
   sizeof(simple_structure)=15 
*/    
  }

O alinhamento dos campos da estrutura pode ser necessário ao trocar dados com bibliotecas de terceiros (*.DLL), nas quais é aplicado tal alinhamento.

Vamos exemplificar como funciona o alinhamento. Peguemos a estrutura de quatro membros sem alinhamento.

//--- estrutura simples sem alinhamento
   struct Simple_Structure pack() // tamanho não especificado, ele será alinhado a 1 byte
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(int)=4
      double            d; // sizeof(double)=8
     };
//--- declaramos uma instância de uma estrutura simples  
   Simple_Structure s;

Os campos da estrutura serão localizados na memória um após o outro, de acordo com a ordem de declaração e o tamanho do tipo. Tamanho da estrutura igual 15, nos arrays será indefinido o deslocamento para os campos da estrutura.

simple_structure_alignment

Agora declaramos a mesma estrutura com um alinhamento de 4 bytes e executamos o código.

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- estrutura simples com alinhamento de 4 bytes
   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
     };
   //--- declaramos uma instância de uma estrutura simples   
   Simple_Structure s;  
//--- exibimos o tamanho de cada membro da estrutura  
   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));
//--- verificamos de que o tamanho da estrutura POD agora não seja igual à soma dos tamanhos de seus membros
   Print("sizeof(simple_structure)=",sizeof(simple_structure));
/*
  Resultado:
   sizeof(s.c)=1
   sizeof(s.s)=2
   sizeof(s.i)=4
   sizeof(s.d)=8
   sizeof(simple_structure)=16 // tamanho da estrutura alterado
*/    
  }

O tamanho da estrutura foi alterado para que todos os membros de 4 bytes ou mais tivessem um deslocamento múltiplo de 4 bytes a partir do início da estrutura. Membros menores serão alinhados ao seu próprio tamanho (por exemplo, 2 para short). Veja que o byte adicionado é mostrado em cinza.

simple_structure_alignment_pack

Neste caso, após o membro s.c é adicionado 1 byte, para que o campo s.s (sizeof(short)==2) tenha um limite de 2 byte (alinhamento para o tipo short).

O deslocamento para o início da estrutura no array também será alinhado a 4 bytes, ou seja, para Simple_Structure arr[], os endereços dos elementos a[0], a[1], a[n] serão múltiplos de 4 bytes.

Consideremos mais duas estruturas que consistem no mesmo tipo com alinhamento a 4 bytes, mas cuja ordem de membros é diferente. Na primeira estrutura, os membros são organizados em ordem ascendente segundo o tamanho do tipo.

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- estrutura simples com alinhamento a 4 bytes
   struct CharShortInt pack(4)
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(double)=4
     };
//--- declaramos uma instância de uma estrutura simples  
   CharShortInt ch_sh_in;
//--- exibimos o tamanho de cada membro da estrutura  
   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));
 
//--- verificamos de que o tamanho da estrutura POD é igual à soma dos tamanhos de seus membros
   Print("sizeof(CharShortInt)=",sizeof(CharShortInt));
/*
  Resultado:
   sizeof(ch_sh_in.c)=1
   sizeof(ch_sh_in.s)=2
   sizeof(ch_sh_in.i)=4
   sizeof(CharShortInt)=8
*/   
  }

Como se pode ver, o tamanho da estrutura é 8 e consiste em dois blocos de 4 bytes cada. No primeiro bloco, são colocados os campos com tipos char e short, no segundo - o campo com tipo int.

charshortint

Agora, a partir da primeira estrutura, vamos fazer a segunda, que difere apenas na ordem dos campos: reorganizamos o tipo de membro short no final.

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- estrutura simples com alinhamento a 4 bytes
   struct CharIntShort pack(4)
     {
      char              c; // sizeof(char)=1
      int               i; // sizeof(double)=4
      short             s; // sizeof(short)=2
     };
//--- declaramos uma instância de uma estrutura simples  
   CharIntShort ch_in_sh;
//--- exibimos o tamanho de cada membro da estrutura  
   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));
//--- verificamos de que o tamanho da estrutura POD é igual à soma dos tamanhos de seus membros
   Print("sizeof(CharIntShort)=",sizeof(CharIntShort));
/*
  Resultado:
   sizeof(ch_in_sh.c)=1
   sizeof(ch_in_sh.i)=4
   sizeof(ch_in_sh.s)=2
   sizeof(CharIntShort)=12
*/   
  }

Embora a estrutura em si não tenha mudado, a alteração na ordem dos membros levou a um aumento no tamanho da própria estrutura.

charintshort

Durante a herança, também é preciso levar em conta o alinhamento. Exemplificamos uma estrutura Parent simples, que possui apenas um membro do tipo char. O tamanho dessa estrutura sem alinhamento é 1.

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

Crie uma classe filha Children com adição de um membro do tipo short (sizeof(short)=2).

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

Como resultado, ao definir o alinhamento como 2 bytes, o tamanho da estrutura será 4, embora o tamanho dos próprios membros seja 3. Já que sob a classe pai, para Parent serão alocados 2 bytes, de forma que o acesso ao campo short da classe filha seja alinhado a 2 bytes.

O conhecimento de como a memória é alocada para membros de uma estrutura é necessário se o programa MQL5 interage com dados de terceiros escrevendo/lendo no nível do arquivo ou da thread.

A Biblioteca padrão contém funções para trabalhar com funções WinAPI, no catálogo MQL5\Include\WinAPI. Estas funções usam estruturas com um determinado alinhamento para os casos em que são necessárias para trabalhar com WinAPI.  

offsetof é um comando especial que está diretamente relacionado ao atributo pack. Ele permite obter o deslocamento do membro a partir do início da estrutura.

//--- declaramos uma variável do tipo Children
   Children child;  
//--- encontramos o deslocamento a partir do início da estrutura
   Print("offsetof(child.c)=",offsetof(child.c));
   Print("offsetof(child.s)=",offsetof(child.s));  
/*
  Resultado:
   offsetof(child.c)=0
  offsetof(child.s)=2
*/   

Modificador final

A presença do modificador final, ao declarar a estrutura, proíbe a herança a partir dela. Se a estrutura não precisar de alterações futuras ou se essas alterações não se puderem levar a cabo por causa de questões de segurança, declare-a usando o modificador final. Além disso, todos os membros da estrutura também serão implicitamente considerados como final.

struct settings final
  {
  //--- corpo da estrutura
  };
 
struct trade_settings : public settings
  {
  //--- corpo da estrutura
  };

Como no exemplo acima, ao tentar herdar a partir da estrutura, usando o modificador final, o compilador irá emitir um erro:

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

Classes

As classes diferem das estruturas no seguinte:

  • a palavra-chave class é usado na declaração;
  • por default (padrão), todos os membros da classe têm especificador de acesso private, a menos que seja indicado o contrário. Dados-membros da estrutura têm o tipo default de acesso como public, a menos que seja indicado o contrário;
  • objetos de classe sempre tem uma tabela de funções virtuais, mesmo que não existam funções virtuais declaradas na classe. Estruturas não podem ter funções virtuais;
  • o operador new pode ser aplicado a objetos de classe; o operador this não pode ser aplicado a estruturas;
  • classes pode ser herdados somente de classes, estruturas pode ser herdados somente de estruturas.

Classes e estruturas podem ter um construtor e destrutor explícitos. Se seu construtor for definido explicitamente, a inicialização de uma variável de estrutura ou classe usando a seqüência de inicialização é impossível.

Exemplo:

struct trade_settings
  {
   double take;         // valor do preço de fixação do lucro
   double stop;         // valor do preço stop de proteção
   uchar  slippage;     // valor do slippage admissível
   //--- Construtor
          trade_settings() { take=0.0; stop=0.0; slippage=5; }
   //--- Destrutor
         ~trade_settings() { Print("Este é o final"); } 
  };
//--- Compilador gerará uma mensagem de erro de que a inicialização é impossível
trade_settings my_set={0.0,0.0,5};  

Construtores e Destrutores

Um construtor é uma função especial, que é chamada automaticamente ao se criar um objeto de uma estrutura ou classe e normalmente é usado para inicializar membros da classe. Mais adiante, falaremos sobre classes, tudo que for dito se aplica também a estruturas, a menos que seja indicado o contrário. O nome de um construtor deve corresponder ao nome da classe. O construtor não tem tipo de retorno (você pode especificar o tipo void).

Membros de classe definidos — strings, arrays dinâmicos e objetos que exigem inicialização — serão inicializados em qualquer caso,independentemente de haver ou não um construtor.

Cada classe pode ter múltiplos construtores, diferindo pelo número de parâmetros e a lista de inicialização. Um construtor que exige especificação de parâmetros é chamado um construtor paramétrico.

Um construtor sem parâmetros é chamado um construtor default. Se nenhum construtor for declarado em uma classe, o compilador cria um construtor default durante a compilação.

//+------------------------------------------------------------------+
//| Uma classe para trabalhar com uma data                           |
//+------------------------------------------------------------------+
class MyDateClass
  {
private:
   int               m_year;          // Ano
   int               m_month;         // Mês
   int               m_day;           // Dia do mês
   int               m_hour;          // Hora no dia
   int               m_minute;        // Minutos
   int               m_second;        // Segundos
public:
   //--- Construtor default
                     MyDateClass(void);
   //--- Construtor paramétrico
                     MyDateClass(int h,int m,int s);
  };

 

Um construtor pode ser declarado na descrição da classe e então seu corpo pode ser definido. Por exemplo, dois construtores de MyDateClasse podem ser definidos da seguinte maneira:

//+------------------------------------------------------------------+
//| Construtor default                                               |
//+------------------------------------------------------------------+
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__);
  }
//+------------------------------------------------------------------+
//| Construtor paramétrico                                           |
//+------------------------------------------------------------------+
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__);
  }

No construtor default, todos os membros da classes são preenchidos usando a função TimeCurrent(), no construtor paramétrico somente os valores de hora são preenchidos. Outros membros da classe (m_year, m_month and m_day) serão inicializados automaticamente com a data corrente.

O construtor default tem um propósito especial ao inicializar um array de objetos de sua classe. Um construtor, o qual todos o parâmetros têm valores default, não é um construtor default. Aqui está um exemplo:

//+------------------------------------------------------------------+
//| Uma classe com um construtor default                             |
//+------------------------------------------------------------------+
class CFoo
  {
   datetime          m_call_time;     // Hora da última chamada ao objeto
public:
   //--- Um construtor com um parâmetro que tem um valor default não é um construtor default
                     CFoo(const datetime t=0){m_call_time=t;};
   //--- Um construtor copiador
                     CFoo(const CFoo &foo){m_call_time=foo.m_call_time;};
 
   string ToString(){return(TimeToString(m_call_time,TIME_DATE|TIME_SECONDS));};
  };
//+------------------------------------------------------------------+
//| Programa Script da função start (iniciar)                        |
//+------------------------------------------------------------------+
void OnStart()
  {
// CFoo foo; // Esta variação não pode ser utilizada - um construtor default não foi definido
//--- Possíveis opções para criar o objeto CFoo
   CFoo foo1(TimeCurrent());     // Uma explicita chamada de um construtor paramétrico
   CFoo foo2();                  // Uma explícita chamada de um construtor paramétrico com parâmetro default
   CFoo foo3=D'2009.09.09';      // Uma implícita chamada de um construtor paramétrico
   CFoo foo40(foo1);             // Uma explicita chamada de um construtor copiador
   CFoo foo41=foo1;              // Uma implícita chamada de um construtor copiador
   CFoo foo5;                    // Uma explícita chamada de um construtor default (se não existir construtor default,
                                 // então um construtor paramétrico com um valor default é chamado)
//--- Possíveis opções para criar o objeto CFoo
   CFoo *pfoo6=new CFoo();       // Criação dinâmica de um objeto e recepção de um ponteiro para ele
   CFoo *pfoo7=new CFoo(TimeCurrent());// Outra opções de criação dinâmica de objeto
   CFoo *pfoo8=GetPointer(foo1); // Agora pfoo8 aponta para o objeto foo1
   CFoo *pfoo9=pfoo7;            // pfoo9 e pfoo7 apontam para o mesmo objeto
   // CFoo foo_array[3];         // Esta opção não pode ser usado - um construtor default não foi especificado
//--- Mostra os valores 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());
//--- Exclui dinamicamente arrays criados
   delete pfoo6;
   delete pfoo7;
   //delete pfoo8;  // Você não precisa excluir pfoo8 explicitamente, já que ele aponta para o objeto foo1 criado automaticamente
   //delete pfoo9;  // Você não precisa excluir pfoo9 explicitamente, já que ele aponta para o mesmo objeto que pfoo7
  }

Se você descomentar estas strings

  //CFoo foo_array[3];     // Esta variante não pode ser usada - um construtor default não está definido

ou

  //CFoo foo_dyn_array[];  // Esta variante não pode ser usada - um construtor default não está definido

então o compilar retorná um erro para eles "default constructor is not defined" (construtor default não definido).

Se uma classe tiver um construtor definido pelo usuário, o construtor padrão não é gerado pelo compilador. Isso significa que se um construtor paramétrico é declarado em uma classe, mas um construtor default não é declarado, você não pode declarar arrays de objetos desta classe. O compilador retorná um erro para este script:

//+------------------------------------------------------------------+
//| Uma classe sem um construtor default                             |
//+------------------------------------------------------------------+
class CFoo
  {
   string            m_name;
public:
                     CFoo(string name) { m_name=name;}
  };
//+------------------------------------------------------------------+
//| Programa Script da função start (iniciar)                        |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Obtém o erro "default constructor is not defined" durante compilação
   CFoo badFoo[5];
  }

Neste exemplo, a classe CFoo tem um construtor paramétrico declarado - em tais casos, o compilador não cria um construtor default automaticamente durante compilação. Ao mesmo tempo quando você declara um array de objetos, presume-se que todos os objetos devam se criados e inicializados automaticamente. Durante a auto-inicialização de um objeto, é necessário chamar um construtor default, mas já que o construtor default não foi explicitamente declarado e nem automaticamente gerado pelo compilador, é impossível criar tal objeto. Por esta razão, o compilador gerou um error na etapa de compilação.

Existe uma sintaxe especial para inicializar um objeto usando um construtor. Inicializadores de membros de uma estrutura ou classe (construções especiais para inicialização) podem ser especificados na lista de inicialização.

Uma lista de inicialização é uma lista de inicializadores separados por vírgulas, que seguem depois do dois pontos (:), depois da lista de parâmetros de um construtor e precede o corpo (antes da abertura de chave). Há vários requisitos:

  • Listas de inicialização podem ser usados somente em construtores;
  • Membros paternos não podem ser inicializados na lista de inicialização;
  • A lista de inicialização deve ser seguida por uma definição (implementação) de uma função.

Aqui está um exemplo de vários construtores para inicializações de membros de uma classe.

//+------------------------------------------------------------------+
//| Uma classe para armazenar o nome de um caractere                 |
//+------------------------------------------------------------------+
class CPerson
  {
   string            m_first_name;     // Primeiro nome
   string            m_second_name;    // Segundo nome
public:
   //--- Um construtor default vazio
                     CPerson() {Print(__FUNCTION__);};
   //--- Um construtor paramétrico
                     CPerson(string full_name);
   //--- Um construtor com uma lista de inicialização
                     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);
     }
  }
//+------------------------------------------------------------------+
//| Programa Script da função start (iniciar)                        |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Obtém o erro "default constructor is not defined"
   CPerson people[5];
   CPerson Tom="Tom Sawyer";                       // Tom Sawyer
   CPerson Huck("Huckleberry","Finn");             // Huckleberry Finn
   CPerson *Pooh = new CPerson("Winnie","Pooh");  // Winnie the Pooh
   //--- Valores de sáida
   Tom.PrintName();
   Huck.PrintName();
   Pooh.PrintName();
   
   //--- Apaga um objeto criado dinamicamente
   delete Pooh;
  }

Neste caso, a classe CPerson tem três construtores:

  1. Um construtor default explícito, que permite a criação de um array de objetos desta classe;
  2. Um construtor com um parâmetro, que recebe um nome completo como um parâmetro e divide com o nome e segundo nome de acordo com o espaço encontrado;
  3. Um construtor com dois parâmetros que contem uma lista de inicialização. Inicializadores - m_second_name(surname) e m_first_name(name).

Note que a inicialização usando uma lista substitui uma atribuição. Membros individuais devem ser inicializados como:

 class_member (uma lista de expressões)

Na lista de inicialização, membros podem aparecer em qualquer ordem, mas todos os membros da classe serão inicializados de acordo com a ordem de sua aparição. Isso significa que no terceiro construtor, primeiro o membro m_first_name será inicializado, já que ele aparece primeiro, e somente depois m_second_name será inicializado. Isto será tomado em conta nos casos onde a inicialização de alguns membros da classe depende dos valores em outros membros da classe.

Se um construtor default não for declarado na base base, e ao mesmo tempo um ou mais construtores com parâmetros forem declarados, você deve sempre chamar um dos construtores da classe base na lista de inicialização. Ele é passado com vírgula da mesma forma que membros comuns da lista e será chamado primeiro durante a inicialização do objeto, não importando onde ele é colocado na lista de inicialização.

//+------------------------------------------------------------------+
//| A classe base                                                    |
//+------------------------------------------------------------------+
class CFoo
  {
   string            m_name;
public:
   //--- Um construtor com uma lista de inicialização
                     CFoo(string name) : m_name(name) { Print(m_name);}
  };
//+------------------------------------------------------------------+
//| Uma classe derivada a partir de CFoo                             |
//+------------------------------------------------------------------+
class CBar : CFoo
  {
   CFoo              m_member;      // Um membro de classe é um objeto do pai
public:
   //--- O construtor default na lista de inicialização chama o construtor do pai
                     CBar(): m_member(_Symbol), CFoo("CBAR") {Print(__FUNCTION__);}
  };
//+------------------------------------------------------------------+
//| Programa Script da função start (iniciar)                        |
//+------------------------------------------------------------------+
void OnStart()
  {
   CBar bar;
  }

Neste exemplo, ao criar o objeto bar, um construtor default CBar() será chamado, no qual primeiro um construtor do pai de CFoo é chamado, e então vem um construtor para o membro de classe m_member.

Um destrutor é uma função especial que é chamado automaticamente quando um objeto de classe é destruído. O nome do destrutor é escrito como nome de classe precedido de (~). Strings, arrays dinâmicos e objetos, exigindo desinicialização, serão desinicializados de qualquer forma, não importando se o destrutor estiver presente ou ausente. Se existir um destrutor, essas ações serão executadas após chamar o destrutor.

Destrutores são sempre virtuais, não importando se eles são declarados com a palavra-chave virtual ou não.

Definindo Métodos de Classe

Métodos de funções de classe podem ser definidos tanto dentro quanto fora da declaração de classe. Se o método é definido dentro de uma classe, então seu corpo vem logo após a declaração do método.

Exemplo:

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[]);
  }; 

Funções a partir de SetRightBorder(int border) até Draw() são declarados e definidos diretamente dentro da classe CTetrisShape.

O construtor CTetrisShape() e os métodos CheckDown(int& pad_array[]), CheckLeft(int& side_row[]) e CheckRight(int& side_row[]) são declarados somente dentro da classe, mas ainda não definidos. As definições destas funções serão feitas mais adiante no código. A fim de definir o método do lado de fora da classe, o operador de resolução de scope é usado, o nome da classe é usado como o escopo.

Exemplo:

//+------------------------------------------------------------------+
//| Construtor da classe básica                                      |
//+------------------------------------------------------------------+
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;
  }
//+--------------------------------------------------------------------+
//| Verificação da capacidade de move para baixo (para a varra e cubo) |
//+--------------------------------------------------------------------+
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);
  }

Modificadores de Acesso Public, Protected e Private

Quando desenvolver um nova classe, é recomendável restringir o acesso do lado de fora aos membros da classe. São usadas palavras-chave privateouprotected para esta finalidade. Neste caso, dados escondidos podem ser acessados somente a partir de métodos-funções da mesma classe. Se a palavra-chave protected é usada, dados escondidos podem ser acessados também a partir de métodos de classes herdeiras desta classe. O mesmo método pode ser usado para restringir o acesso de métodos-funções de uma classe.

Se você precisar de acesso com abertura completa aos membros e/ou métodos de uma classe, use a palavra-chave public.

Exemplo:

class CTetrisField
  {
private:
   int               m_score;                            // Contagem
   int               m_ypos;                             // Posição corrente das figuras
   int               m_field[FIELD_HEIGHT][FIELD_WIDTH]; // Boa matriz
   int               m_rows[FIELD_HEIGHT];               // Numeração das linhas boas
   int               m_last_row;                         // Última linha livre
   CTetrisShape     *m_shape;                            // Figura Tetris
   bool              m_bover;                            // Fim de jogo
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();
  }; 

Quaisquer membros de classe e métodos declarados após o especificador public: (e antes do próximo especificador de acesso) ficam disponíveis para qualquer referência ao objeto da classe pelo programa. Neste exemplo, existem os seguintes membros: funções CTetrisField(), Init(), Deinit(), Down(), Left(), Right(), Rotate() e Drop().

Quaisquer membros de classe e métodos declarados após o especificador private: (e antes do próximo especificador de acesso) ficam disponíveis somente para as funções-membros desta classe. Especificadores de acesso a elementos sempre terminam com um dois pontos (:) e podem aparecer na definição da classe várias vezes.

O acesso aos membros de uma classe base pode ser redefinido durante a herança nas classes derivadas.

Modificador final

A presença do modificador final, ao declarar a classe, proíbe a herança a partir dela. Se a interface da classe não precisar de alterações futuras ou se essas alterações não se puderem levar a cabo por causa de questões de segurança, declare-a usando o modificador final. Além disso, todos os métodos da classe também serão implicitamente considerados como final.

class CFoo final
  {
  //--- corpo da classe
  };
 
class CBar : public CFoo
  {
  //--- corpo da classe
  };

Como no exemplo acima, ao tentar herdar a partir da classe, usando o modificador final, o compilador irá emitir um erro:

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

Uniães (union)

A união é um tipo de dados especial que compreende uma série de variáveis que compartilham o mesmo espaço de memória. Assim, a união permite interpretar a mesma sequência de bit de dois (ou mais) formas diferentes. A declaração de união é semelhante a declarar estruturas e começa com a palavra-chave union.

union LongDouble
{
  long   long_value;
  double double_value;
};

Mas ao contrário das estruturas, diferentes membros de união pertencem ao mesmo local de memória. Neste exemplo, é declarada a união LongDouble, nela o tipo long e o valor do tipo double compartilham a mesma região memória. É importante entender que é impossível fazer com que esta associação armazene ao mesmo tempo um valor inteiro long e um verdadeiro double (como acontecia na estrutura), porque as variáveis long_value e double_value são sobrepostas (na memória). Mas o programa MQL5 a qualquer momento pode processar a informação contida nesta união, como um valor inteiro (long) ou real (double). Assim, a união faz com que seja possível obter duas (ou mais) variantes de representação da mesma série de dados.

Ao declarar a união, o compilador automaticamente aloca uma área de memória suficiente para armazenar na união as maiores variáveis de acordo com o volume do tipo. Para acessar o elemento de união, usa-se a mesma sintaxe como para as estruturas, isto é, operador ponto.

union LongDouble
{
  long   long_value;
  double double_value;
};
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   LongDouble lb;
//--- obtemos o número inválido -nan(ind) e exibimo-lo
   lb.double_value=MathArcsin(2.0);
   printf("1.  double=%f                integer=%I64X",lb.double_value,lb.long_value);
//--- maior número normalizado (DBL_MAX)
   lb.long_value=0x7FEFFFFFFFFFFFFF;
   printf("2.  double=%.16e  integer=%I64X",lb.double_value,lb.long_value);
//--- menor número positivo normalizado (DBL_MIN)
   lb.long_value=0x0010000000000000;    
   printf("3.  double=%.16e  integer=%.16I64X",lb.double_value,lb.long_value);
  }
/*  Resultado da execução
    1.  double=-nan(ind)                integer=FFF8000000000000
    2.  double=1.7976931348623157e+308  integer=7FEFFFFFFFFFFFFF
    3.  double=2.2250738585072014e-308  integer=0010000000000000
*/

Como as uniões permitem que o programa interprete os mesmos dados na memória de diferentes maneiras, elas são frequentemente utilizadas em aplicações que requerem uma conversão de tipos pouco comum.

As uniões não podem participar na herança, além disso, não podem ter membros estáticos por definição. De resto union comporta-se como uma estrutura, em que todos os membros têm deslocamento zero. Ao mesmo tempo, os membros de união não podem ser dos seguintes tipos:

Como as classes, a união pode ter construtores e destruidores, bem como métodos. Por padrão, os membros da união têm o tipo de acesso public<t2, para criar um elementos fechados necessárias para usar a palavra-chave private. Todos esses recursos são mostrados no exemplo que mostra como converter a cor do tipo color, no formato ARGB, como o faz a função ColorToARGB().

//+------------------------------------------------------------------+
//| União para conversão de color(BGR) no formato ARGB      |
//+------------------------------------------------------------------+
union ARGB
  {
   uchar             argb[4];
   color             clr;
   //--- construtores
                     ARGB(color col,uchar a=0){Color(col,a);};
                    ~ARGB(){};
   //--- métodos públicos
public:
   uchar   Alpha(){return(argb[3]);};
   void    Alpha(const uchar alpha){argb[3]=alpha;};
   color   Color(){ return(color(clr));};
   //--- métodos fechados
private:
   //+------------------------------------------------------------------+
   //| definição de cor e valor do canal-alfa                          |
   //+------------------------------------------------------------------+
   void    Color(color col,uchar alpha)
     {
      //--- definimos a cor no membro clr
      clr=col;
      //--- definimos o valor do componente Alpha, isto é, o nível de opacidade
      argb[3]=alpha;
      //--- mudamos de lugar os bytes do componente R e B (Red e Blue)     
      uchar t=argb[0];argb[0]=argb[2];argb[2]=t;
     };
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- o valor 0x55 significa 55/255=21.6 % (0% - totalmente transparente)
   uchar alpha=0x55; 
//--- o tipo color têm o formato 0x00BBGGRR
   color test_color=clrDarkOrange;
//--- aqui entrarão os valores de bytes a partir da união ARGB
   uchar argb[]; 
   PrintFormat("0x%.8X - assim se vê o tipo color para %s, BGR=(%s)",
               test_color,ColorToString(test_color,true),ColorToString(test_color));
//--- o tipo ARGB é apresentado como 0x00RRGGBB, mudados de lugar os componentes RR e BB
   ARGB argb_color(test_color);
//--- copiamos o array de bytes
   ArrayCopy(argb,argb_color.argb);
//--- vemos como fica no formato ARGB  
   PrintFormat("0x%.8X - presentação ARGB com alfa-canal=0x%.2x, ARGB=(%d,%d,%d,%d)",
               argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- adicionamos o valor de opacidade
   argb_color.Alpha(alpha);
//--- tentamos imprimir ARGB como tipo color
   Print("ARGB como color=(",argb_color.clr,")  alfa-canal=",argb_color.Alpha());
//--- copiamos o array de bytes
   ArrayCopy(argb,argb_color.argb);
//--- assim fica no formato ARGB
   PrintFormat("0x%.8X - presentação ARGB com alfa-canal=0x%.2x, ARGB=(%d,%d,%d,%d)",
               argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- verificamos se é gerada a função ColorToARGB()
   PrintFormat("0x%.8X - resultado ColorToARGB(%s,0x%.2x)",ColorToARGB(test_color,alpha),
               ColorToString(test_color,true),alpha);
  }
/* Resultado da execução
   0x00008CFF - assim se vê o tipocolor para clrDarkOrange, BGR=(255,140,0)
  0x00FF8C00 - presentação ARGB com alfa-canal=0x00, ARGB=(0,255,140,0)
   ARGB como color=(0,140,255)  alfa-canal=85
  0x55FF8C00 - presentação ARGB com alfa-canal=0x55, ARGB=(85,255,140,0)
   0x55FF8C00 - resultado ColorToARGB(clrDarkOrange,0x55)
*/ 

Interfaces

A interface é projetada para determinar a funcionalidade específica que a classe pode implementar. Na verdade, essa é uma classe que não pode conter nenhum membro e não pode ter um construtor e/ou destrutor. Todos os métodos declarados na interface são normalmente virtuais, mesmo sem uma definição explícita.

A interface é definida usando a palavra-chave interface, como é mostrado no exemplo a seguir:

//--- interface básica para descrever animais
interface IAnimal
  {
//--- métodos da interface padrão têm acesso público
   void Sound();  // som que produz o animal
  };
//+------------------------------------------------------------------+
//|  a classe CCat é herdada da interface IAnimal                    |
//+------------------------------------------------------------------+
class CCat : public IAnimal
  {
public:
                     CCat() { Print("Cat was born"); }
                    ~CCat() { Print("Cat is dead");  }
   //--- implementamos o método Sound da interface IAnimal
   void Sound(){ Print("meou"); }
  };
//+------------------------------------------------------------------+
//|  a classe CDog é herdada da interface IAnimal                    |
//+------------------------------------------------------------------+
class CDog : public IAnimal
  {
public:
                     CDog() { Print("Dog was born"); }
                    ~CDog() { Print("Dog is dead");  }
   //--- implementamos o método Sound da interface IAnimal
   void Sound(){ Print("guaf"); }
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- matriz do ponteiro para o objeto do tipo IAnimal
   IAnimal *animals[2];
//--- geramos descendente IAnimal e salvamos os ponteiros para eles nas suas matrizes    
   animals[0]=new CCat;
   animals[1]=new CDog;
//--- chamamos o método Sound() da interface base IAnimal para cada descendente  
   for(int i=0;i<ArraySize(animals);++i)
      animals[i].Sound();
//--- removemos objetos
   for(int i=0;i<ArraySize(animals);++i)
      delete animals[i];
//--- resultado da execução
/*
   Cat was born
   Dog was born
   meou
   guaf
   Cat is dead
   Dog is dead
*/
  }

Como as classes abstratas, não se deve criar o objeto da interface sem descendente. A interface pode ser herdada apenas a partir de outras interfaces e pode se tornar descendente para a classe. Além disso, a interface sempre tem visibilidade pública.

É impossível declarar a interface dentro da declaração da classe ou estrutura, porém é possível armazenar o ponteiro para a interface na variável do tipo void *. De um modo geral, é possível armazenar os ponteiros para objetos de qualquer classe na variável do tipo void *. Para converter um ponteiro void * para outro ponteiro para um objeto de uma classe específica, é indispensável utilizar o operador dynamic_cast. Quando a conversão é impossível, o resultado da operação dynamic_cast será NULL.

Veja Também

Programação Orientada a Objetos