
Aprendendo MQL5 do iniciante ao profissional (Parte IV): Sobre Arrays, Funções e Variáveis Globais do Terminal
Introdução
Este artigo é a continuação do ciclo para iniciantes. Nos artigos anteriores, foram detalhados os métodos de descrição de dados armazenados em nossos programas. Até o momento, o leitor deve saber:
- que os dados podem ser armazenados em variáveis ou constantes;
- que a linguagem MQL5 é uma linguagem de tipagem rígida, o que significa que cada fragmento de dados no programa tem seu tipo específico, necessário para o compilador alocar a memória corretamente e evitar alguns erros lógicos;
- que os tipos de dados podem ser simples (básicos) ou complexos (personalizados);
- que para utilizar dados em um programa em MQL5, é necessário declarar pelo menos uma função;
- que quaisquer blocos de código podem ser movidos para arquivos separados e, posteriormente, esses arquivos podem ser incluídos no projeto usando a diretiva de pré-processador #include.
Neste artigo, abordarei três tópicos bastante amplos:
- Arrays de dados, que completam a história principal sobre dados dentro do programa.
- Variáveis globais do terminal, que permitem a troca de dados simples entre diferentes programas MQL5.
- Além disso, serão analisados alguns detalhes do funcionamento de funções e sua interação com variáveis.
Informações básicas sobre arrays
Um array é uma variável que contém uma sequência de dados de um único tipo.
Para descrever um array, é necessário definir seu tipo e o nome da variável, separados por colchetes. Nos colchetes, é possível indicar a quantidade de elementos nessa sequência.
int myArray[2]; // Describes an array of integers containing two elements
Exemplo 1. Descrição de um array estático.
Sequências em programas na linguagem MQL5 são frequentemente descritas. Isso inclui todos os preços históricos, o tempo de início de cada vela, os volumes etc. Enfim, os arrays são uma boa escolha sempre que há conjuntos de dados.
A numeração dos elementos dentro de um array em MQL5 sempre começa com 0. Portanto, o número do último elemento em um array sempre será igual à quantidade de elementos menos um (lastElement = size 1).
Para acessar qualquer elemento de um array, basta especificar o número desse elemento entre colchetes:
// Fill the array with values: myArray[0] = 3; myArray[1] = 315; // Output the last element of this array to the log: Print(myArray[1]); // 315
Exemplo 2. Uso dos elementos de um array.
E, claro, qualquer array pode ser inicializado durante sua descrição, exatamente como uma estrutura, usando chaves:
double anotherArray[2] = {4.0, 5.2}; // A two-element array is initialized with two values Print( DoubleToString(anotherArray[0],2) ); // Output 4.00
Exemplo 3. Inicialização de um array durante a descrição.
Arrays multidimensionais
Um array pode conter outros arrays. Esses arrays aninhados são chamados de "multidimensionais".
Um exemplo simples e ilustrativo de arrays multidimensionais pode ser encontrado nas páginas dos livros. Os caracteres se organizam em linhas — primeira dimensão; as linhas formam parágrafos — segunda dimensão; e a página, composta por um conjunto de parágrafos, representa a terceira dimensão.
Figura 1. Caracteres reunidos em uma linha — um array unidimensional.
Figura 2. Linhas reunidas em parágrafos — um array bidimensional.
Figura 3. Parágrafos reunidos em páginas — um array tridimensional.
Para descrever tais arrays em MQL5, basta adicionar colchetes para cada nova dimensão. Os colchetes dos "contêineres externos" são posicionados mais à esquerda que os dos "contêineres internos". Por exemplo, os arrays ilustrados nas figuras 1-3 poderiam ser descritos e utilizados da seguinte maneira:
char stringArray[21]; // One-dimensional array char paragraphArray[2][22]; // Two-dimensional array char pageArray[3][2][22]; // Three-dimensional array // Filling a two-dimensional array paragraphArray[0][0]='T'; paragraphArray[0][1]='h'; // … paragraphArray[1][20]='n'; paragraphArray[1][21]='.'; // Access to an arbitrary element of a two-dimensional array Print(CharToString(paragraphArray[1][3])); // Will print "a" (why?)
Exemplo 4. Descrição de arrays multidimensionais para as figuras 1-3.
O número total de dimensões em um array não deve exceder 4. O número máximo de elementos em qualquer dimensão é 2.147.483.647.
A inicialização de arrays multidimensionais durante sua descrição é tão simples quanto a de arrays unidimensionais. Basta listar os elementos de cada array dentro de chaves:
int arrayToInitialize [2][5] = { {1,2,3,4,5}, {6,7,8,9,10} }
Exemplo 5. Inicialização de arrays multidimensionais.
Arrays dinâmicos
Nem todos os arrays permitem saber antecipadamente quantos elementos conterão. Por exemplo, arrays que armazenam o histórico do terminal ou listas de transações podem mudar com o tempo. Assim, além dos arrays estáticos descritos nas seções anteriores, o MQL5 permite criar arrays dinâmicos, que podem alterar o número de elementos durante a execução do programa. Esses arrays são descritos da mesma forma que os estáticos, mas os colchetes permanecem vazios, sem indicação do número de elementos:
int dinamicArray [];
Exemplo 6. Descrição de arrays dinâmicos
Um array descrito dessa forma não contém elementos, tem comprimento igual a 0 e, portanto, é impossível acessar qualquer elemento nele. Se o programa tentar fazê-lo, ocorrerá um erro crítico, encerrando sua execução imediatamente. Portanto, antes de trabalhar com um array dinâmico, é necessário definir seu tamanho utilizando a função especial ArrayResize:
ArrayResize(dinamicArray, 1); // The first parameter is the array and the second is the new size ArrayResize(dinamicArray,1, 100); // The third parameter is the reserved (excess) size
Exemplo 7. Alteração do tamanho de um array dinâmico
No manual, é possível verificar que essa função aceita até três parâmetros. No entanto, o terceiro parâmetro tem um valor padrão, podendo ser omitido, como fiz na primeira linha do meu exemplo.
O primeiro parâmetro desta função será, obrigatoriamente, o array que estamos modificando. O segundo é o novo tamanho do mesmo. Creio que isso seja simples de entender. O terceiro parâmetro é o "tamanho reservado".
O "tamanho reservado" é usado quando se sabe o tamanho máximo possível do array. Por exemplo, na nossa tarefa, o array não pode ter mais de 100 valores, mas a quantidade exata é desconhecida. podemos usar o parâmetro reserve_size nesta função e defini-lo como 100, conforme mostrado no exemplo 7, na segunda linha. Nesse caso, ao ser chamada, a função reservará espaço excedente na memória para 100 elementos, embora o tamanho real do array continue sendo o indicado no segundo parâmetro (1 elemento).
Por que tanta complexidade? Por que simplesmente não adicionar elementos conforme necessário?
A resposta simples é: para aumentar a eficiência da nossa programação.
Uma explicação mais detalhada pode ser longa. Porém, resumidamente, toda vez que usamos a função ArrayResize sem o terceiro parâmetro, nosso programa solicita memória adicional ao sistema operacional. Essa alocação de memória é um processo relativamente lento (do ponto de vista do processador) e, independentemente de ser necessário alocar memória para apenas um elemento ou para muitos ao mesmo tempo. Quanto menos frequentemente nosso programa precisar fazer isso, melhor. Ou seja, é melhor reservar um grande espaço de uma vez e depois preenchê-lo, do que alocar pouco espaço e então expandi-lo. No entanto, é importante considerar que a memória RAM é um recurso limitado, e por isso será sempre necessário buscar um equilíbrio entre a velocidade de execução e o tamanho dos dados.
Se você conhece os limites dos seus arrays, é melhor informar isso explicitamente ao programa, descrevendo arrays estáticos ou reservando memória usando o terceiro parâmetro da função ArrayResize. Caso contrário, o array será dinâmico por padrão, e o terceiro parâmetro não é obrigatório, embora possa ser usado. Se o tamanho real do array exceder o tamanho reservado, o MQL5 apenas alocará a memória necessária.
Uma vez que o array tenha um novo tamanho, como no exemplo 7, é possível modificar os dados nele:
dinamicArray[0] = 3; // Now our array contains exactly one element (see example 7), its index is 0
Exemplo 8. Uso de um array modificado
Quando trabalhamos com arrays dinâmicos, a tarefa mais comum é adicionar dados ao final do array, em vez de alterar algo no meio (embora isso também aconteça). Como o programa não sabe, em um dado momento, quantos elementos há no array, é necessário usar uma função especial para obter essa informação. Essa função é chamada de ArraySize. Ela aceita um parâmetro — o próprio array — e retorna um valor inteiro correspondente ao número de elementos nele contidos. Uma vez conhecido o tamanho exato do array dinâmico (obtido com essa função), adicionar um elemento torna-se bastante simples:
int size, // Number of elements in the array lastElemet; // Index of the last element char stringArray[]; // Our example dynamic array. // Immediately description its size is 0 (array cannot contain elements) // add an element to the end. size = ArraySize(stringArray); // Find the current size of the array size++; // Array size should increase by 1 ArrayResize(stringArray, size, 2); // Resize the array. In our example, the array will have no more than two elements. lastElemet = size — 1; // Numbering starts from 0, so the number of the last element is 1 less than the size of the array stringArray[lastElement] = `H`; // Write the value // Now add one more element. The sequence of actions is absolutely the same. size = ArraySize(stringArray); // Find the current size if the array size++; // Array size should increase by 1 ArrayResize(stringArray, size, 2); // Resize the array. In our example, the array will have no more than two elements. lastElemet = size — 1; // Numbering starts from 0, so the number of the last element is 1 less than the size of the array stringArray[lastElement] = `i`; // Write the value // Note that when adding the second element in this way, only on line changes: // the one that writes the actual value to a specific cell. // // This means that the solution can be written in a shorter form. For example, by creating a separate custom function for it.
Exemplo 9. Adição de um elemento no final de um array dinâmico.
As funções ArraySize e ArrayResize são constantemente utilizadas ao trabalhar com arrays dinâmicos, geralmente em combinação, como no exemplo 9. Recomendo que você explore outras funções, menos frequentes, mas igualmente úteis, por conta própria.
Para concluir esta seção, quero destacar que é possível criar arrays dinâmicos multidimensionais em MQL5, mas apenas o primeiro índice pode ser indefinido.
int a [][12]; // It's ok // int b [][]; // Compilation error: only the first index can be dynamic
Exemplo 10. Array dinâmico multidimensional.
Se for absolutamente necessário que vários índices sejam dinâmicos, é possível criar uma estrutura cujo único campo seja um array dinâmico e, em seguida, criar um array dessas estruturas.
struct DinArray // Structure containing a dynamic array { int a []; }; DinArray dinamicTwoDimensions []; // Dynamic array of structures ArrayResize( dinamicTwoDimensions, 1 ); // Set size of the outer dimension ArrayResize( dinamicTwoDimensions[0].a, 1 ); // Set size of the internal dimension dinamicTwoDimensions[0].a[0] = 12; // Use cell to write data
Exemplo 11. Array com dois índices dinâmicos.
Existem outros métodos para resolver essa questão. Por exemplo, você pode criar sua própria classe ou usar as que já estão disponíveis na biblioteca padrão. Contudo, deixarei o tema de classes para artigos futuros.
Arrays de séries
Os preços de abertura, fechamento, máximos e mínimos, volumes (tanto de ticks quanto reais), spreads, tempos de início das velas e valores de indicadores para cada vela em MQL5 são chamados de séries ou séries temporais.
O programador em MQL5 não tem acesso direto a essas sequências, mas a linguagem fornece um conjunto de funções predefinidas específicas (listadas na tabela 1) para copiar esses dados em quaisquer variáveis dentro do nosso programa.
Se precisarmos, por exemplo, dos preços de fechamento, primeiro devemos criar um array onde esses preços serão armazenados e, em seguida, chamar a função CopyClose, passando o array criado como último parâmetro. A função copiará a série padrão para nossa variável, e esses dados poderão ser utilizados normalmente, por meio de acesso pelos índices entre colchetes.
Porém, o trabalho com séries temporais é um pouco diferente do trabalho com todos os outros arrays. Isso se deve a razões históricas.
Na memória, as séries são armazenadas da mesma forma que outros dados: das mais antigas para as mais recentes. No entanto, as funções de manipulação de séries da tabela 1 enumeram os elementos das séries temporais da direita para a esquerda. Para todas essas funções, a vela de índice zero será a mais à direita, ou seja, a atual, aquela que ainda não foi concluída. Porém, os "arrays comuns" não têm essa noção, de modo que, para eles, essa vela será a última. Essa diferença pode gerar confusão...
Vamos tentar esclarecer isso com a ajuda de ilustrações.
Figura 4. A direção da enumeração em arrays comuns (seta verde) e em séries (seta azul).
Figura 5. Cópia de séries para arrays comuns.
A Figura 4 mostra como a direção da enumeração difere entre as séries e os arrays comuns.
A Figura 5 representa esquematicamente o processo de cópia de séries para arrays comuns, usando, por exemplo, a função CopyRates ou outra similar (veja a Tabela 1). A ordem física dos elementos na memória é a mesma tanto para arrays comuns quanto para séries, mas a numeração muda: após a cópia, o primeiro elemento da série torna-se o último no array comum.
Às vezes, programar mantendo esses detalhes em mente pode ser inconveniente. Para lidar com essa dificuldade, existem dois caminhos:
- A função embutida ArraySetAsSeries permite alterar a direção da enumeração para qualquer array dinâmico. Ela aceita dois parâmetros: o próprio array e um indicador de "seriedade" (true/false). Se o seu algoritmo pressupõe a cópia de dados que sempre começam pela última vela, geralmente é possível configurar o array de destino como uma série, garantindo que as funções padrão e seu algoritmo utilizem os mesmos índices.
- Se o seu algoritmo precisa copiar pequenos fragmentos de dados de locais arbitrários no gráfico, especialmente se o número de elementos for conhecido em cada etapa do algoritmo (por exemplo, ao obter os preços de fechamento de três barras: a primeira fechada — índice 1 na série — e as duas seguintes, com índices 2 e 3), é melhor não tentar modificar a lógica. O mais prático é aceitar que a numeração segue direções diferentes e programar com maior atenção. Uma solução alternativa é criar uma função específica para verificar os valores necessários e tentar aplicá-la a qualquer expressão que os envolva.
No exemplo a seguir, procurei ilustrar tudo o que foi explicado anteriormente usando código.
datetime lastBarTime; // We'll try to write the last candle's time into this variable datetime lastTimeValues[]; // The array will store the time of the last two candles. // It's dynamic so that it can be made into a time series to test indices // Get the start time of the current candlestick using the iTime function lastBarTime = iTime ( Symbol(), // Use the current symbol PERIOD_CURRENT, // For the current timeframe 0 // Current candlestick ); Print("Start time of the 0 bar is ", lastBarTime); // Get the start time of the last two candlesticks using the CopyTime function CopyTime ( Symbol(), // Use the current symbol PERIOD_CURRENT, // For the current timeframe 0, // Start with position 0 2, // Take two values lastTimeValues // Write them to array lastTimeValues ("regular") array ); Print("No series"); ArrayPrint(lastTimeValues,_Digits,"; "); // Print the entire array to log. The separator between elements is a semicolon ArraySetAsSeries(lastTimeValues,true); // Convert the array into a time series Print("Series"); ArrayPrint(lastTimeValues,_Digits,"; "); // Print the entire array again. Note the order of the data /* Script output: 2024.08.01 09:43:27.000 PrintArraySeries (EURUSD,H4) Start time of the 0 bar is 2024.08.01 08:00:00 2024.08.01 09:43:27.051 PrintArraySeries (EURUSD,H4) No series 2024.08.01 09:43:27.061 PrintArraySeries (EURUSD,H4) 2024.08.01 04:00:00; 2024.08.01 08:00:00 2024.08.01 09:43:27.061 PrintArraySeries (EURUSD,H4) Series 2024.08.01 09:43:27.061 PrintArraySeries (EURUSD,H4) 2024.08.01 08:00:00; 2024.08.01 04:00:00 */
Exemplo 12. Verificação de funções para trabalhar com séries temporais.
Tabela 1. Lista de funções para acessar séries temporais. Para todas essas funções, a enumeração de elementos começa da direita — da última vela (incompleta).
Função | Ação |
---|---|
CopyBuffer | Obtém os dados do buffer especificado de um indicador no array. |
CopyRates | Obtém dados históricos no formato da estrutura MqlRates para o símbolo e período especificados. |
CopySeries | Obtém várias séries temporais sincronizadas para o símbolo/período especificados na quantidade indicada. A lista dos arrays preenchidos é passada no final, e sua ordem deve corresponder aos campos da estrutura MqlRates. |
CopyTime | Obtém os dados históricos sobre os horários de abertura dos candles para o símbolo e período especificados no array. |
CopyOpen | Obtém os dados históricos sobre os preços de abertura dos candles para o símbolo e período especificados no array. |
CopyHigh | Obtém os dados históricos sobre os preços máximos dos candles para o símbolo e período especificados no array. |
CopyLow | Obtém os dados históricos sobre os preços mínimos dos candles para o símbolo e período especificados no array. |
CopyClose | Obtém os dados históricos sobre os preços de fechamento dos candles para o símbolo e período especificados no array. |
CopyTickVolume | Obtém os dados históricos sobre os volumes de ticks para o símbolo e período especificados no array. |
CopyRealVolume | Obtém os dados históricos sobre os volumes reais de negociação para o símbolo e período especificados no array. |
CopySpread | Obtém os dados históricos sobre os spreads para o símbolo e período especificados no array. |
CopyTicks | Obtém os ticks no formato MqlTick no array. |
CopyTicksRange | Obtém os ticks no intervalo de datas especificado no array. |
iBarShift | Retorna o índice do candle na série que contém o horário especificado. |
iClose | Retorna o valor do preço de fechamento do candle (especificado pelo parâmetro shift) no gráfico correspondente. |
iHigh | Retorna o valor do preço máximo do candle (especificado pelo parâmetro shift) no gráfico correspondente. |
iHighest | Retorna o índice do maior valor encontrado (deslocamento em relação ao candle atual) no gráfico correspondente. |
iLow | Retorna o valor do preço mínimo do candle (especificado pelo parâmetro shift) no gráfico correspondente. |
iLowest | Retorna o índice do menor valor encontrado (deslocamento em relação ao candle atual) no gráfico correspondente. |
iOpen | Retorna o valor do preço de abertura do candle (especificado pelo parâmetro shift) no gráfico correspondente. |
iTime | Retorna o valor do horário de abertura do candle (especificado pelo parâmetro shift) no gráfico correspondente. |
iTickVolume | Retorna o valor do volume de ticks do candle (especificado pelo parâmetro shift) no gráfico correspondente. |
iRealVolume | Retorna o valor do volume real do candle (especificado pelo parâmetro shift) no gráfico correspondente. |
iSpread | Retorna o valor do spread do candle (especificado pelo parâmetro shift) no gráfico correspondente. |
Criação de funções (em detalhes)
Qualquer função em um programa MQL5 é criada seguindo um modelo único, que foi brevemente mencionado no primeiro artigo da série:
ResultType Function_Name(TypeOfParameter1 nameOfParameter1, TypeOfParameter2 nameOfParameter2 …) { // Description of the result variable and other local variables ResultType result; // … //--- // Main actions are performed here //--- return resut; }
Exemplo 13. Modelo de descrição de função.
TipoResultado (e TiposParametros) pode ser qualquer tipo de dado permitido. Isso inclui int, double, o nome de uma classe, uma enumeração ou qualquer outra coisa que você conheça.
Uma função pode não ter parâmetros ou um resultado explícito. Nesse caso, em vez do tipo de resultado ou dentro dos parênteses (onde estaria a lista de parâmetros), utiliza-se a palavra void. Literalmente, void significa "espaço vazio" ou "lacuna".
É evidente que, se o TipoResultado for void, não será necessário retornar nenhum dado. Assim, a última linha dentro das chaves (return result) não precisa ser incluída, nem é necessário descrever uma variável de resultado.
Algumas regras simples adicionais:
- O NomeFuncao e os nomes dos parâmetros devem seguir as convenções de identificação.
- O operador return retorna apenas um valor. Não mais do que isso. No entanto, existem maneiras de contornar isso, que serão abordadas mais adiante.
- Uma função não pode ser descrita dentro de outra função, apenas fora, fora de todas as funções.
- É possível descrever várias funções com o mesmo nome, desde que elas tenham um número diferente de parâmetros, tipos de parâmetros diferentes e/ou tipos diferentes de resultados. O importante é que seja possível identificar claramente qual função deve ser usada em cada caso. Se você conseguir distinguir e explicar essas diferenças para alguém sem conhecimento prévio do seu código, o compilador também conseguirá.
Aqui estão alguns exemplos ilustrando como funções podem ser descritas:
//+------------------------------------------------------------------+ //| Example 1 | //| Comments are often used to describe what a function does, | //| what date it needs and why. For example, like this: | | //| | //| | //| The function returns difference between two integers. | //| | //| Parameters: | //| int a is a minuend | //| int b is a subtrahend | //| Return value: | //| difference between a and b | //+------------------------------------------------------------------+ int diff(int a, int b) { // The action is very simple, we do not create a variable for the result. return (a-b); } //+------------------------------------------------------------------+ //| Example 1a | //| The function returns the difference between two real numbers. | //| | //| Function name is as in the previous example, but parameter type | //| differs | //| | //| Parameters: | //| double a is a minuendе | //| double b is a subtrahend | //| Return value: | //| difference between a and b | //+------------------------------------------------------------------+ double diff(double a, double b) { return (a-b); } //+------------------------------------------------------------------+ //| Example 2 | //| Illustrates the use of "void". | //| Calls (uses) the diff function | //+------------------------------------------------------------------+ void test() { // You can do whatever you want. // For example, use the function from Example 1. Print(diff(3,4)); // the result is -1 // Since when calling the diff function, integer parameters were // passed in parentheses, the result is also int. // Now let's try to call the same function with double precision parameters Print(diff(3.0,4.0)); // the result is -1.0 // Since the function is declared as "void", the "return" statement is not needed } //+------------------------------------------------------------------+ //| Example 3 | //| The function has no parameters to process. We could use | //| empty parentheses as in the previous example or explicitly use | //| the word "void" | //| Return value: | //| string nameForReturn is some name , always the same | | //+------------------------------------------------------------------+ string name(void) { string nameForReturn="Needed Name"; return nameForReturn; }
Exemplo 14. Exemplos de descrição de funções de acordo com o modelo.
É importante entender que a utilização de uma função consiste em duas etapas: descrição e utilização. Quando descrevemos uma função, esta ainda não executa qualquer ação. Trata-se apenas de um algoritmo formal, um esquema de ações. Por exemplo, a função diff do exemplo acima poderia ser descrita da seguinte forma:
- Receber dois números inteiros (quaisquer valores, desconhecidos previamente).
- Dentro do algoritmo, atribuir o nome a a um deles e b ao outro.
- Subtrair o número b do número a.
- Retornar o resultado do cálculo (qualquer valor obtido, desconhecido previamente) para quem solicitou.
A expressão "qualquer valor" pode ser substituída por "parâmetro formal". As funções são criadas precisamente para executar ações formais com dados arbitrários.
Os parâmetros usados na descrição de funções são chamados de "parâmetros formais".
No exemplo 14, a função diff contém dois parâmetros formais, ao passo que as outras funções não têm quaisquer parâmetros. Em geral, uma função pode ter muitos parâmetros formais (até 63).
Para obter um resultado específico, é necessário chamar a função em questão. Por exemplo, a função test do exemplo 14 chamou as funções Print e diff. Neste momento, são utilizados valores concretos, conhecidos como valores reais no momento da chamada: o conteúdo de variáveis ou constantes, literais (como no exemplo) ou os resultados de outras funções...
Os parâmetros que passamos para uma função no momento da chamada são chamados de "parâmetros reais".
Para chamar qualquer função, você deve especificar seu nome e listar os parâmetros reais entre colchetes. Os parâmetros reais devem corresponder aos parâmetros formais em termos de tipo e quantidade.
Assim, no exemplo 14, a função test usa exatamente dois números inteiros ou exatamente dois números de ponto flutuante para chamar a função diff. Caso eu tivesse cometido um erro e tentado passar um ou três parâmetros, isso resultaria em um erro de compilação.
Escopo das variáveis
Ao declarar variáveis, é essencial considerar onde elas são declaradas.
-
Se uma variável for declarada dentro de uma função (incluindo os parâmetros formais dessa função), outras funções não poderão "ver" ou utilizá-la. Geralmente, essa variável "nasce" quando a função é chamada e "morre" quando a função termina sua execução. Essa variável é chamada de variável local.
De modo geral, pode-se dizer que o escopo de uma variável é determinado pela "entidade" contínua em nosso código. Por exemplo, se uma variável for declarada dentro de chaves ({}), ela será visível apenas dentro dessas chaves, que formam um bloco, mas não fora dele. Os parâmetros formais de uma função pertencem à "entidade" da função e, portanto, só serão visíveis dentro dela. E por aí fora. Da mesma forma, a duração de uma variável é igual à duração da "entidade" à qual ela pertence. Por exemplo, uma variável declarada dentro de uma função é criada quando essa função é chamada e destruída quando a função termina.
void OnStart() { //--- Local variable inside a function is visible to all blocks of that function, but not beyond it int myString = "This is local string"; // Curly braces describe a block inside a function { int k=4; // Block local variable - visible only inside curly braces Print(k); // It's ok Print (myString); } // Print(k); // Compilation error. The variable k does not exist outside the curly brace block. }
Exemplo 15. Variável local dentro de um bloco de chaves
- Se uma variável for declarada fora da descrição de quaisquer funções, ela pode ser usada por todas as funções do programa. Nesse caso, a duração dessa variável equivale à duração do programa. Tal variável é chamada de variável global.
int globalVariable = 345; void OnStart() { //--- Print (globalVariable); // It's ok }
Exemplo 16. Variável global é visível para todas as funções do programa.
- É possível ter uma variável local com o mesmo nome de uma variável global. Nesse caso, entretanto, a variável local "oculta" a variável global dentro da função em questão.
int globalVariable=5; void OnStart() { int globalVariable=10; // The variable is described according to all the rules, including the type. // If the type were not declared, this expression would change the global variable //--- Print(globalVariable); // The result is 10 - that is, the value of the local variable Print(::globalVariable); // The result is 5. To print the value of a global variable, not the local one, // we use two colons before the name }
Exemplo 17. Nomes "sobrepostos" de variáveis locais e globais. A local oculta a global.
Variáveis estáticas
Há um caso especial ao descrever variáveis locais.
Como mencionado anteriormente, variáveis locais perdem seus valores após a conclusão da função. Esse é o comportamento normalmente esperado. Contudo, há situações em que é necessário preservar o valor de uma variável local mesmo após a execução da função.
Por exemplo, organizar um contador de chamadas para uma função. A tarefa mais comum dos traders é organizar uma função para verificar o início de uma nova vela. Para isso, é necessário obter o valor do tempo atual a cada tick e compará-lo com o valor anterior conhecido. É possível, claro, criar uma variável global para cada um desses contadores. No entanto, cada variável global aumenta a probabilidade de erro, pois esses contadores são necessários apenas para uma função, e não é desejável alterá-los ou mesmo "vê-los" para as demais.
Em situações em que uma variável local precisa ter a mesma duração que uma variável global, utilizam-se variáveis estáticas. Elas são descritas exatamente como as variáveis comuns, mas com a palavra-chave static antes da declaração, como demonstrado na função HowManyCalls no exemplo a seguir:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- HowManyCalls(); HowManyCalls(); HowManyCalls(); } //+------------------------------------------------------------------+ //| The function counts the number of requests | //+------------------------------------------------------------------+ void HowManyCalls() { //--- Variable description. The variable is local, but its lifetime is long. static int counter=0; // Since 'static' keyword is used, the variable is initialized only // before he first function call // (more precisely, before the OnInit function call) //--- Main actions counter++; // During program execution, the value will be stored till the end //--- Operation result Print( IntegerToString(counter)+" calls"); } // Script output: // 1 calls // 2 calls // 3 calls
Exemplo 18. Uso de variável estática.
O exemplo contém duas funções: HowManyCalls, que usa uma variável estática para contar quantas vezes foi chamada e exibe o resultado no log, e OnStart, que chama HowManyCalls três vezes consecutivas.
Passagem de parâmetros para a função por valor e por referência
Por padrão, uma função usa apenas cópias dos dados que lhe são passados como parâmetros (os programadores dizem que, nesse caso, os dados são passados "por valor"). Dessa forma, mesmo que algo seja alterado na variável-parâmetro dentro da função, os dados originais não serão afetados.
Se quisermos que a função altere os dados originais, o parâmetro formal que pode ser modificado deve ser declarado com o símbolo especial & (e comercial). Esse método de passagem é chamado de "passagem por referência".
Para ilustrar como uma função pode alterar dados externos, criaremos um novo arquivo de script contendo o seguinte código:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart(void) { //--- Declare and initialize two local variables int first = 3; int second = 77; //--- Print their values BEFORE all changes Print("Before swap: first = " + first + " second = " + second); //--- Use the Swap function, which takes data by reference Swap(first,second); //--- See what happened Print("After swap: first = " + first + " second = " + second); //--- //--- Apply the CheckLocal function to the received data //--- This function takes parameters by value CheckLocal(first,second); //--- Print the result again Print("After CheckLocal: first = " + first + " second = " + second); } //+------------------------------------------------------------------+ //| Swaps the values of two integer variables | //| Data is passed by reference, so the originals will be modified | //+------------------------------------------------------------------+ void Swap(int &a, int& b) // It can be done in any way, both positions are correct { int temp; //--- temp = a; a = b; b = temp; } //+------------------------------------------------------------------+ //| Takes parameters by value, that is why changes happen | //| only locally | //+------------------------------------------------------------------+ void CheckLocal(int a, int b) { a = 5; b = 10; } // Script output: // Before swap: first = 3 second = 77 // After swap: first = 77 second = 3 // After CheckLocal: first = 77 second = 3
Exemplo 19. Passagem de parâmetros por referência.
O código descreve três funções curtas: OnStart, Swap e CheckLocal.
A função CheckLocal recebe dados por valor e, portanto, trabalha com cópias. A função Swap recebe dois parâmetros por referência, ou seja, trabalha diretamente com os originais. A função OnStart declara duas variáveis locais, exibe seus valores, chama as funções Swap e CheckLocal, e apresenta os resultados no log após cada interação. Vale destacar que a função Swap alterou os dados passados para ela, enquanto CheckLocal não conseguiu fazer isso.
É importante ressaltar que todas as variáveis de tipos complexos (como enumerações, estruturas, objetos, etc., além de qualquer array) devem sempre ser passadas por referência.
Caso se tente passar tais variáveis por valor, o compilador gerará um erro.
Para resumir, seguem as principais regras de interação entre variáveis e funções:
- As variáveis globais da linguagem MQL5 podem ser usadas diretamente dentro de qualquer função, incluindo a possibilidade de alterar seus valores.
- As variáveis locais estão acessíveis apenas dentro do bloco onde foram descritas.
- Se um parâmetro formal for descrito como "passagem por valor", a função não poderá modificar os dados originais, mesmo que altere o valor da variável-parâmetro internamente. No entanto, os dados passados "por referência" podem ser modificados no local original.
- Se uma variável global e uma local tiverem o mesmo nome, a variável local terá prioridade (em outras palavras, a variável local oculta a global).
- O tempo de vida das variáveis globais é igual ao tempo de vida do programa, enquanto o das variáveis locais corresponde ao tempo de vida do bloco onde foram descritas.
Valores padrão para parâmetros formais de função
Os parâmetros formais podem ter valores padrão atribuídos.
Por exemplo, ao criar uma função de registro de logs, pode ser necessário destacar as mensagens desta função das demais mensagens do terminal. A forma mais simples de fazer isso é adicionar um prefixo ao início e um sufixo ao final da mensagem original. A string principal sempre deve ser especificada, caso contrário, a função perde seu propósito. No entanto, os "complementos" podem ser padrões, embora sejam configuráveis.
Abaixo, está um código simples que ilustra essa ideia:
//+------------------------------------------------------------------+ //| Add a prefix and suffix to a sting | //+------------------------------------------------------------------+ string MakeMessage( string mainString, string prefix="=== ", string suffix=" ===" ) { return (prefix + mainString + suffix); }
Exemplo 20. Descrição de função com parâmetros formais definidos como padrão
Ao chamar essa função, um ou ambos os parâmetros com valores padrão podem ser omitidos. Caso não sejam especificados, a função usará os valores definidos na descrição. Por exemplo:
Print ( MakeMessage("My first string") ); // Default prefix and suffix Print ( MakeMessage("My second string", "~~ ") ); // Prefix changed, suffix remains unchanged Print ( MakeMessage("My third string", "~~ ", " ~~") ); // Both actual parameter have been changed // Script output: // === My first string === // ~~ My first string === // ~~ My first string ~~
Exemplo 21. Parâmetros efetivos com valores padrão podem ser omitidos
Os parâmetros com valores padrão devem ser colocados em sequência e descritos após todos os outros parâmetros que não possuem valores padrão.
Como fazer uma função retornar vários resultados
Como mencionado, o operador return pode retornar apenas um resultado. Além disso, uma função não pode retornar arrays. Mas e se for necessário? Por exemplo, como calcular simultaneamente o horário e o valor do preço de fechamento de uma vela ou obter uma lista de instrumentos disponíveis? Antes de continuar, pense em uma solução e, em seguida, compare-a com as alternativas a seguir.
Para resolver esse problema, algumas abordagens podem ser utilizadas:
- Criar um tipo de dado complexo (como uma estrutura) e retornar uma variável desse tipo.
- Usar a passagem de parâmetros por referência. Embora isso não permita retornar os dados diretamente com o operador return, é possível alterar qualquer valor e utilizá-lo posteriormente.
- Usar variáveis globais (não recomendado). Esse método é semelhante ao anterior, mas é potencialmente mais arriscado para o código. As variáveis globais devem ser usadas minimamente, apenas quando absolutamente necessário. Contudo, se for imprescindível, esta abordagem também pode ser tentada.
Modificadores de variáveis globais: input e extern
No uso de variáveis globais, também existem "casos especiais". Incluem-se aqui:
- A descrição de parâmetros de entrada da nossa aplicação usando o modificador input.
- O uso do modificador extern.
Parâmetros de entrada (variáveis input)
Cada parâmetro de entrada de um programa escrito em MQL5 é descrito como uma variável global (fora de todas as funções) e marcado pela palavra-chave input, que é colocada no início da descrição.
input string smart = "The smartest decision"; // The window will contain this description
Ejemplo 22. Descrição de parâmetros de entrada
Normalmente, no lado esquerdo da janela de propriedades, o nome da variável é exibido. No entanto, se na mesma linha onde a variável foi descrita houver um comentário, como no exemplo 22, será exibido o comentário em vez do nome da variável.
Nos programas escritos em MQL5, as variáveis designadas como input podem apenas ser lidas, não sendo possível gravar nelas. Os valores dessas variáveis só podem ser definidos durante a descrição (no código) ou na janela de diálogo de propriedades do programa.
Se for criado um EA (Expert Advisor) ou indicador, os valores dessas variáveis podem ser otimizados no testador de estratégias. Contudo, se você deseja excluir certos parâmetros da otimização, adicione a letra s ao início da palavra input ou utilize o modificador static:
input double price =1.0456; // optimize sinput int points =15; // NOT optimize static input int unoptimizedVariable =100; // NOT optimize
Exemplo 23. Uso do modificador sinput para excluir a variável da otimização no testador
Se o usuário tiver que selecionar valores a partir de uma lista em um campo de entrada, será necessário criar uma enumeração para cada campo. Comentários em linha para os elementos das enumerações também funcionam; ou seja, em vez de nomes como POINT_PRICE_CLOSE, é possível exibir "Preço de fechamento do ponto" em qualquer idioma. Infelizmente, não é possível alterar o idioma do texto do nome do campo (comentário) de forma simples. Para cada idioma utilizado, será necessário compilar um arquivo separado. Por isso, muitos programadores experientes preferem usar um idioma universal, como o inglês.
Os parâmetros podem ser agrupados visualmente para facilitar seu uso. Para definir o nome de um grupo, é utilizado um comentário especial:
input group "Group Name"
Exemplo 24. Título do grupo de parâmetros
Aqui está um exemplo completo que ilustra todas essas possibilidades:
#property script_show_inputs // Enumeration. Allows you to create a list box. enum ENUM_DIRECTION { // All inline comments next to lines describing parameters, // will be displayed instead of the names in the parameters window DIRECTION_UP = 1, // Up DIRECTION_DN = -1, // Down DIRECTION_FL = 0 // Unknown }; input group "Will be optimized" input int onlyExampleName = 10; input ENUM_DIRECTION direction = DIRECTION_FL; // Possible directions list input group "Will not be optimized" sinput string something = "Something good"; static input double doNotOptimizedMagickVariable = 1.618; // Some magic //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- } //+------------------------------------------------------------------+
Ejemplo 25. Diferentes formas de descrever parâmetros de entrada
Figura 6. Diálogo de parâmetros. As cores destacam os grupos (grupo de input). As setas verdes indicam os valores dos comentários substituídos pelos nomes das variáveis.
A figura 6 mostra o diálogo de parâmetros gerado a partir do código do exemplo 25. Em diferentes computadores, ele pode parecer ligeiramente diferente, mas, em qualquer caso, os cabeçalhos dos grupos (destacados em azul na figura) estarão evidentes. Também é possível observar que os parâmetros sem comentários utilizam os nomes das variáveis. Já os parâmetros com comentários usam esses comentários como rótulos, como indicado pelas setas verdes no exemplo.
Por fim, nem todos os iniciantes compreendem os ícones à esquerda que indicam o tipo de dado de cada parâmetro. No parâmetro chamado "Possible directions list", por exemplo, o tipo de dado é uma enumeração, e seu ícone () sugere uma lista. Os dados para este campo só podem ser escolhidos a partir de uma lista limitada de opções, exatamente o que se desejava. Os demais ícones também são intuitivos, e acredito que não será difícil compreendê-los.
O nome de qualquer parâmetro não deve exceder 63 caracteres (o que é muito, na prática os nomes geralmente são bem mais curtos).
O tamanho do valor de um parâmetro de stringnão pode ultrapassar 254 caracteres, e quanto mais longo for o nome desse parâmetro, menor será o conteúdo possível, já que eles são armazenados na memória como uma única string.
É importante lembrar essa limitação, especialmente se o endereço de uma página web importante para o programa for especificado. Às vezes, os endereços são realmente longos, e se este for o seu caso, tente passar esse endereço de outra forma, por exemplo, incorporando-o no código como uma variável global, em vez de como um parâmetro. Certamente, existem soluções melhores, como usar arquivos ou "unir" o endereço a partir de vários fragmentos, mas o principal é lembrar do número 254 para os valores dos parâmetros...
Outro caso especial envolve as variáveis "externas".
Ao desenvolver programas grandes divididos em vários arquivos, às vezes uma variável global é definida em um arquivo, mas é necessário acessá-la a partir de outros arquivos. Contudo, pode haver motivos para não querer incluir arquivos usando a diretiva #include. O MetaEditor trata cada arquivo individualmente e, nesse caso, não consegue ajudar diretamente durante a digitação.
Essa situação ocorre frequentemente com parâmetros de entrada (descritos na seção anterior).
Aqui, a palavra-chave extern (externa) é útil.
extern bool testComplete;
Ejemplo 26. Descrição de variáveis externas
Essas variáveis não podem ser inicializadas no arquivo em que são declaradas. Durante a compilação, o endereço de memória dessa variável provavelmente será substituído pelo da "verdadeira" variável global com o mesmo nome, caso o compilador a encontre. No entanto, funções podem acessar esses dados "formais" livremente, inclusive modificá-los, e o IDE não enfrentará dificuldades ao realizar autocompletar.
Variáveis globais do terminal
Tanto as variáveis locais quanto as globais descritas anteriormente estão disponíveis apenas para o programa em execução. Nenhum outro programa pode acessá-las. No entanto, existem situações em que os programas precisam trocar informações entre si ou garantir que os valores das variáveis sejam preservados após o término da sessão.
Um exemplo é um indicador simples que exibe a quantidade de fundos na moeda do depósito necessária para abrir uma posição. Parece simples. Ao consultar o índice da documentação, descobrimos que existe uma função especial em MQL5 chamada OrderCalcMargin, que calcula o valor necessário. Tentamos usá-la e... ficamos desapontados. Isso porque indicadores não podem usar funções de negociação. Isso é proibido fisicamente, no nível do compilador. A OrderCalcMargin é justamente uma função de negociação....
Portanto, será necessário recorrer a métodos alternativos. Uma das opções é criar um script ou serviço que calcule os valores necessários e os grave em variáveis do terminal. Depois, nosso indicador apenas lerá esses dados, em vez de calculá-los. Esse truque é viável porque, diferentemente dos indicadores, scripts e serviços têm permissão para realizar negociações (consulte a tabela na primeira parte da série).
Vamos ver como esse intercâmbio poderia ser implementado. Primeiro, criamos um arquivo de script usando o assistente. Chamaremos esse arquivo de "CalculateMargin.mq5".
Para acessar variáveis do terminal, existe um conjunto de funções predefinidas cujos nomes começam com o prefixo GlobalVariable.
Usando essas funções e a função OrderCalcMargin, criamos um novo script que torna disponíveis os dados necessários para os indicadores:
//+------------------------------------------------------------------+ //| CalculateMargin.mq5 | //| Oleg Fedorov (aka certain) | //| mailto:coder.fedorov@gmail.com | //+------------------------------------------------------------------+ #property copyright "Oleg Fedorov (aka certain)" #property link "mailto:coder.fedorov@gmail.com" #property version "1.00" #property script_show_inputs //--- Script input parameters input double requiredAmount = 1; // Number of lots //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Description of local variables string symbolName = Symbol(); // Name of the current symbol string terminalVariableName; // Terminal variable name double marginBuy, marginSell; // Margin values (buy and sell) double currentPrice = iClose(symbolName,PERIOD_CURRENT,0); // Current price to calculate margin bool okCalcBuy, okCalcSell; // Indication of success when calculating margin up or down //--- Main operations // Calculate Buy margin okCalcBuy = OrderCalcMargin( ORDER_TYPE_BUY, // Order type symbolName, // Symbol name requiredAmount, // Required volume in lots currentPrice, // Order open price marginBuy // Result (by reference) ); // Calculate Sell margin okCalcSell = OrderCalcMargin( ORDER_TYPE_SELL, // Sometimes different amounts are needed for opening up and down symbolName, requiredAmount, currentPrice, marginSell ); //--- Operation result // Create a terminal variable name for Buy details terminalVariableName = symbolName + "BuyAmount"; // Write the data. If the global terminal variable does not exist, it will be created. GlobalVariableSet ( terminalVariableName, // Where to write marginBuy // What to write ); // Now we create another name - for the Sell details terminalVariableName = symbolName + "SellAmount"; // Write data for Sell. If there was no variable with the name stored in terminalVariableName, // create one GlobalVariableSet(terminalVariableName,marginSell); } //+------------------------------------------------------------------+
Ejemplo 31. Script para calcular os fundos na moeda do depósito necessários para comprar ou vender 1 lote e salvar esses dados em variáveis globais do terminal
Neste exemplo, utilizamos a função padrão GlobalVariableSet para gravar dados nas variáveis do terminal. O uso dessas funções é bastante direto. Apenas um detalhe: o comprimento do nome de uma variável global do terminal não pode exceder 63 caracteres.
Se executarmos esse script em qualquer gráfico, os resultados não serão imediatamente visíveis. No entanto, é possível verificar o que foi gravado pressionando a tecla <F3> ou selecionando no menu do terminal "Ferramentas -> Variáveis Globais".
Figura 7. Menu de variáveis do terminal.
Após selecionar essa opção, uma janela aparecerá com a lista de todas as variáveis do terminal:
Figura 8. Janela com a lista de variáveis globais do terminal
Na figura 8, é mostrado que o script foi executado apenas no par EURUSD, por isso há apenas duas variáveis visíveis: os valores necessários para compra e venda, que neste caso coincidem.
Agora, criaremos um indicador que utilizará esses dados e observaremos como as funções padrão usam os princípios descritos anteriormente para trabalhar com variáveis.
Chamaremos o arquivo do indicador de "GlobalVars.mq5". O trabalho principal desse indicador será feito na função OnInit, executada uma vez, logo após o início do programa. Também será adicionada a função OnDeinit, que remove os comentários quando o indicador é retirado do gráfico. A função OnCalculate, que é obrigatória para todos os indicadores e executada a cada tick, também estará presente, mas não será utilizada neste caso.
//+------------------------------------------------------------------+ //| GlobalVars.mq5 | //| Oleg Fedorov (aka certain) | //| mailto:coder.fedorov@gmail.com | //+------------------------------------------------------------------+ #property copyright "Oleg Fedorov (aka certain)" #property link "mailto:coder.fedorov@gmail.com" #property version "1.00" #property indicator_chart_window //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Description of local variables string symbolName = Symbol(); // Symbol name string terminalVariableName; // Name of global terminal value double buyMarginValue, sellMarginValue; // Buy and Sell value bool okCalcBuy; // Indication that everything is OK when calling one of the variants of the GlobalVariableGet function //--- Main operations // Create a terminal variable name for Buy details terminalVariableName = symbolName + "BuyAmount"; // Use the first method to get the value of a global variable. // To get the result, the parameter is passed by reference okCalcBuy = GlobalVariableGet(terminalVariableName, buyMarginValue); // Change the name of the terminal variable - for Sell details terminalVariableName = symbolName + "SellAmount"; // Second way to get the result: return value sellMarginValue = GlobalVariableGet(terminalVariableName); //--- Output the result as a comment on the chart Comment( "Buy margin is " + DoubleToString(buyMarginValue) // Buy margin value, the second parameter // of the DoubleToString function is omitted +"\n" // Line break +"Sell margin is " + DoubleToString(sellMarginValue,2) // Margin value for sale, indicated the number of // decimal places ); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| The function will be called when the program terminates. | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Очищаем комментарии Comment(""); } //+------------------------------------------------------------------+ //| Custom indicator iteration function (not used here) | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- //--- return value of prev_calculated for the next call return(rates_total); } //+------------------------------------------------------------------+
Ejemplo 32. Uso de variáveis globais do terminal no indicador.
No indicador demonstrativo apresentado, basta ler as variáveis do terminal uma vez e encerrar o trabalho. Por isso, o código principal foi colocado na função OnInit. Embora o exemplo pareça grande e complexo, ele é bastante legível, especialmente porque a maior parte do código consiste em comentários. Descreverei em palavras o que acontece na função OnInit:
- No primeiro bloco, declaramos todas as variáveis necessárias para o restante do código.
- Em seguida, geramos o nome para a variável global do terminal.
- Lemos o valor da variável global do terminal correspondente e o armazenamos em uma variável local.
- Depois, geramos o nome da segunda variável e lemos seu valor.
- Por último, exibimos uma mensagem para o usuário como um comentário no canto superior esquerdo do gráfico (veja a figura 9).
Observe que a função GlobalVariableGet pode ser usada de duas maneiras: retornando o valor diretamente ou usando um parâmetro passado por referência. Já a função DoubleToString possui um parâmetro com valor padrão. Caso você digite o exemplo manualmente no MetaEditor, sem copiá-lo, o editor destacará essas nuances.
Figura 9. Resultado do trabalho do indicador
Ao formatar os resultados, usei diferentes formas de chamar a função DoubleToString, o que resultou em pequenas diferenças nos comentários exibidos. Na linha superior, omiti o segundo parâmetro, que define o número de casas decimais (o padrão é 8). Na linha inferior, especifiquei esse valor explicitamente, configurando duas casas decimais.
É importante aplicá-lo ao mesmo gráfico onde o script foi executado, e somente após o script, para garantir que as variáveis globais do terminal já existam. Caso contrário, o indicador apresentará erros e os comentários não serão exibidos.
Para gravar variáveis do terminal, utilizamos a função GlobalVariableSet, e para lê-las, a função GlobalVariableGet. Embora essas sejam as funções mais usadas, recomendo explorar as demais funções relacionadas na documentação, pois algumas podem ser muito úteis.
Considerações finais
Vamos recapitular os tópicos abordados hoje. Se algum deles ainda não estiver claro, volte à seção correspondente do artigo e analise-o com atenção, pois este material é a base para todo o restante do aprendizado (com exceção das variáveis globais do terminal, que muitas vezes podem ser evitadas, embora geralmente não causem dificuldades). Eis o que vimos:
- Arrays:
- Podem ser estáticos ou dinâmicos.
- Podem ser unidimensionais ou multidimensionais.
- Arrays estáticos podem ser inicializados com literais (em chaves {}).
- Para trabalhar com arrays dinâmicos, é necessário usar funções padrão para alterar seus tamanhos e verificar seus tamanhos atuais.
- Variáveis em relação às funções:
- Variáveis locais têm vida curta, exceto as estáticas (que utilizam o modificador static).
- Variáveis globais têm vida longa e podem ser modificadas com os modificadores extern e input.
- Parâmetros de funções:
- Podem ser passados por referência.
- Podem ser passados por valor.
- Variáveis globais do terminal: Diferem de variáveis globais do programa porque permitem a troca de dados entre programas diferentes. Há um conjunto especial de funções para utilizá-las.
Se você entende todos esses tópicos e nenhum deles causa confusão, já não pode ser considerado um iniciante — você possui uma base sólida para trabalhar. O próximo passo é aprender como usar os operadores principais e compreender as peculiaridades da criação de indicadores e EAs (Expert Advisors) na linguagem MQL5 em comparação com outras linguagens. Assim, você poderá começar a desenvolver programas úteis. É claro que, para alcançar o nível de "profissional", será necessário explorar outras dezenas de tópicos, incluindo programação orientada a objetos, mas todos esses assuntos se basearão na fundação que, após este artigo, já está parcialmente construída.
E que a documentação esteja sempre com você…
Artigos anteriores da série:
- Aprendendo MQL5 do iniciante ao profissional (Parte I): Comecemos a programar
- Aprendendo MQL5 do iniciante ao profissional (Parte II): Tipos de dados básicos e uso de variáveis
- Aprendendo MQL5 do iniciante ao profissional (Parte III): Tipos de dados complexos e arquivos inclusos
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/15357





- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso