6. Zmienne globalne i lokalne.

6.6. Zmienne statyczne

Z poprzednich rozdziałów tej lekcji dowiedzieliśmy się, że każda funkcja oraz operator tworzy w programie swój obszar lokalny. Jeśli w tym obszarze zostanie utworzona zmienna, to dla programu będzie ona widoczna tylko w granicach tego obszaru, a czas jej życia będzie ograniczony czasem istnienia tego obszaru.

W MQL4 istnieje możliwość "przedłużenia" życia lokalnej zmiennej tak, aby nie znikała ona z RAM wraz ze zniknięciem swojego obszaru. W tym celu przy inicjalizacji takiej zmiennej przed jej typem należy zapisać modyfikator static. Zmienna z takim modyfikatorem inicjalizowana jest tylko raz, umieszczana na poziomie globalnym programu i istnieje dopóki działa cały program, a nie tylko konkretny obszar lokalny. Mimo tego, że znajduje się ona na poziomie globalnym, dla programu widoczna jest ona tylko w swoim obszarze, poza którym nie jest dostępna.

A teraz na poniższych przykładach omówimy te właściwości statycznej zmiennej.

Kod 1
#property strict

void OnStart()
  {
   for(int i = 0; i < 5; i++)
     {
      static int Variable_1 = 0;  // 1) inicjalizacja statycznej zmiennej
             int Variable_2 = 0;  // 2) inicjalizacja zmiennej

      Variable_1++;  // 3) zwiększenie o 1 (inkrementacja)
      Variable_2++;  // 4) zwiększenie o 1 (inkrementacja)

      Print("Variable_1 = ", Variable_1, ". Variable_2 = ", Variable_2);  // 5
     }
  }

W powyższym kodzie 1 w ciele operatora for na początku dokonuje się inicjalizacji dwóch zmiennych typu int: Variable_1 i Variable_2 z zerowymi wartościami. Jak widać do pierwszej z nich zastosowano modyfikator static. Następnie wartość każdej zmiennej jest powiększana o 1 za pomocą operatora inkrementacji ++. Na końcu Print() wyświetla te wartości w dzienniku logów terminala (rys. 1).

Rys. 1. Wynik działania skryptu.


Pętla for ma 5 iteracji, dlatego w logach widzimy 5 wyników obliczenia obu zmiennych. Widać, że na każdej nowej iteracji wartość Variable_1 jest zwiększana o 1, a wartość Variable_2 nadal jest = 1. Wyjaśnijmy to zjawisko.


Pierwsza iteracja (i = 0)

Tutaj program pierwszy raz zobaczy instrukcje inicjalizacji zmiennych Variable_1 i Variable_2 (1, 2). Pierwszą zmienną umieści na poziomie globalnym pamięci programu ze względu na obecność static, a drugą umieści w pamięci obszaru lokalnego. Każdej z nich od razu zostanie przypisana wartość 0. W 3-ej i 4-ej instrukcjach do każdej z tych zmiennych doda 1 (Variable_1 = 0 + 1 = 1, Variable_2 = 0 + 1 = 1), a funkcja Print() wyświetli te informacje. Pod koniec tej iteracji z obszaru lokalnego program usunie z pamięci Variable_2, a Variable_1 pozostanie na poziomie globalnym z wartością 1. Koniec iteracji.

Druga iteracja (i = 1)

Teraz program znowu zobaczy instrukcje inicjalizacji zmiennych, ale Variable_1 nie będzie tworzona, ponieważ program widzi, że na poziomie globalnym taka zmienna jest dostępna i nie zostanie jej przypisane 0. Następnie program dokona inicjalizacji nowej zmiennej o nazwie Variable_2, której od razu przypisze wartość 0. W 3-ej i 4-ej instrukcji do każdej z nich zostanie dodane 1 i teraz bardzo ważna rzecz do zapamiętania - Variable_1 znajduje się na poziomie globalnym i po poprzedniej iteracji ma wartość 1. Dlatego po inkrementacji jej wartość wyniesie 2 (1 + 1 = 2), a wartość Variable_2 będzie równa 1 (0 + 1 = 1). Po wyświetleniu danych Variable_1 pozostanie na poziomie globalnym już z wartością 2, a Variable_2 zostanie usunięta. Koniec iteracji.

Na każdej nowej iteracji będzie się działo to samo. Dopiero po zakończeniu działania całego programu globalna zmienna zostanie usunięta z pamięci RAM.


Drugą sytuację omówimy na przykładzie programu strategii automatycznej (ang. expert adviser, EA). Najprostszy EA składa się z 3 głównych funkcji - OnInit(), OnTick() i OnDeinit(). Funkcja OnInit() uruchamiana jest tylko raz w momencie uruchomienia EA na wykresie notowań. OnDeinit() też uruchomia się tylko raz w chwili zakończenia działania programu. Wszystkie potrzebne działania będą odbywać się w funkcji głównej OnTick(), która uruchomia się wielokrotnie, za każdym razem gdy przychodzi nowy tick. W ciele tej funkcji zapiszemy algorytm, kiedy przy pojawieniu nowej świecy na wykresie notowań w logach terminala będzie pojawiać się stosowna informacja. W tym celu przygotujmy funkcję własną o nazwie IsNewBar() (kod 2).

Kod 2
#property strict
//---
int OnInit()
  {
   return(INIT_SUCCEEDED);
  }
//---
void OnTick()
  {
   if(IsNewBar(_Symbol, PERIOD_CURRENT) == true)  // 1
      Print("Pojawiła się nowa świeca! Aktualny czas serwera = ", TimeCurrent());  // 2
  }
//---
void OnDeinit(const int reason)
  {
  }

//===== Funkcja własna =====
bool IsNewBar(string f_Symbol,      // symbol
              ENUM_TIMEFRAMES f_TF) // timeframe
  {
   //---
   static datetime f_LastBar    = 0;                         // 3
          datetime f_CurrentBar = iTime(f_Symbol, f_TF, 0);  // 4
   //---
   if(f_LastBar != f_CurrentBar)  // 5
     {
      f_LastBar = f_CurrentBar;   // 6
      return(true);               // 7
     }
   else return(false);            // 8
  }

W nagłówku operatora warunkowego if (1) umieszczono funkcję IsNewBar(), do której przekazano 2 argumenty - nazwa symbolu _Symbol oraz przedział czasowy PERIOD_CURRENT aktualnego wykresu notowań. Jeśli funkcja zwróci true, to Print() wyświetli odpowiedni komunikat oraz dokładny czas serwera (TimeCurrent()), kiedy przyszedł pierwszy tick nowej świecy (2).

Skupmy się teraz na algorytmie działania IsNewBar(). Najpierw tworzymy statyczną zmienną typu datetime o nazwie f_LastBar z domyślną wartością 0 (3). Następnie tworzona jest zmienna f_CurrentBar, której od razu przypisuje się czas utworzenia bieżącej świecy za pomocą funkcji standartowej iTime() (4). Następnie te dwie wartości są porównywane if(f_LastBar != f_CurrentBar) (5) i jeśli prawdą jest to, że nie są one równe (!= ) to znaczy, że pojawiła się nowa świeca. Dalej w ciele operatora if statycznej zmiennej przypisywana jest wartość czasu utworzenia nowej świecy (6) i za pomocą return funkcja zwraca false (7). To będzie koniec działania funkcji po czym zmienna f_CurrentBar jest usuwana z pamięci RAM, a statyczna zmienna f_LastBar zostanie na poziomie globalnym programu.

Na kolejnym tick-u, kiedy w OnTick() znowu zostanie wywołana IsNewBar(), znowu utworzy się f_CurrentBar, której od razu zostanie przypisany czas utworzenia bieżącej świecy (4). Następnie zostanie ona porównana ze zmienną statyczną (5) i jeśli to będzie 2-gi lub kolejny tick tej samej świecy to będą one tożsame. Wynik porównania w if (5) będzie fałsz, dlatego działanie przejdzie do else, gdzie funkcja zwróci false (8).

Krótko mówiąc, na 1-szym tick-u każdej świecy funkcja własna IsNewBar() zwraca true, a na każdym kolejnym tick-u tej samej świecy - false. Opracowanie takiej funkcji własnej nie było by możliwe bez zastosowania zmiennej statycznej.


Proponuję uruchomić tę strategię automatyczną w swoim MetaTrader 4 na 1-minutowym wykresie i na własne oczy zobaczyć jak to będzie działać (rys. 2).

Rys. 2. Wynik działania strategii automatycznej.


Potem z kodu funkcji własnej usuń static i znowu uruchom w MT4. W tym przypadku na każdym nowym tick-u w logach terminala będzie pojawiać się komunikat o nowej świece, choć będzie to ta sama świeca.