5. Funkcje własne.

5.5. Przekazywanie danych do funkcji własnej

Mówiąc o przekazaniu danych do funkcji własnej, należy rozróżniać 2 etapy: (1) to jak wartość może być przekazana na miejsce argumentu w nagłówku funkcji oraz (2) to jak funkcja przyjmuje tę wartość do swojego formalnego parametru (rys. 1).

Rys. 1. Argumenty i formalne parametry własnej funkcji.


Argumenty

Na miejscu argumentu można zapisać:

  • zmienną (kod 1),
  • liczbę (kod 2) lub inne typy wartości,
  • element tablicy (kod 3),
  • stałą zdefiniowaną w MQL4 (kod 4),
  • działanie matematyczne (kod 5),
  • inną funkcję, standardową lub własną (kod 6),
  • w przypadku jeśli argumentem będzie jakieś rozbudowane działanie, to lepiej go zapisać wewnątrz okrągłych nawiasów w celu poprawienia czytelności kodu (kod 7),
  • całą tablicę (kod 10).
Kod 1
// --- Wariant 1
double r = 1.0;
double h = 2.5;

double v = CylinderVolume( r , h );  // argumenty jako zmienne

// --- Wariant 2
double r = 10.0 - 9.0;  // r = 1.0
double h = 5.0 / 2.0;   // h = 2.5

double v = CylinderVolume( r , h );  // argumenty jako zmienne

Kod 2
double v = CylinderVolume( 1.0 , 2.5 );  // argumenty jako wartości liczbowe

Kod 3
double array[3];

array[0] = 2.5;
array[1] = -33.7;
array[2] = 1.0;

double v = CylinderVolume( array[2] , array[0] );  // argumenty jako elementy tablicy

Kod 4
double v = CylinderVolume( M_LOG2E , M_PI_2 );  // argumenty jako stałe matematyczne

Tutaj M_LOG2E to stała log2(e) = 1.44269504088896340736, a M_PI_2 to stała π/2 = 1.57079632679489661923 .


Kod 5
//--- Wariant 1
double v = CylinderVolume( 10.3 - 9.3 , 5.0 / 2.0 ); /* argumenty jako
                                          działania matematyczne liczb */

//--- Wariant 2
double a = 10.3;
double b = 9.3;
double c = 5.0;
double d = 2.0;

double v = CylinderVolume( a - b , c / d );  /* argumenty jako
                              działania matematyczne zmiennych */

//--- Wariant 3
double a = 10.3;
double d = 2.0;

double v = CylinderVolume( a - 9.3 , 5.0 / d );  /* argumenty jako
                           działania matematyczne liczb i zmiennych */

W każdym z tych wariantów pierwszy argument to działanie matematyczne 10.3 - 9.3 co daje wynik 1.0 i ta cyfra zostanie przekazana do pierwszego formalnego parametru. Drugi argument to działanie matematyczne 5.0 / 2.0 co daje wynik 2.5 i ta cyfra zostanie przekazana do drugiego formalnego parametru.

Kod 6
double v = CylinderVolume( MathAbs(9.3 - 10.3) , MathSqrt(6.25) ); // argumenty jako funkcje

Na pierwszy rzut oka ten zapis może być trudny do zrozumienia, ale już wyjaśniam. Tutaj pierwszy argument to MathAbs(9.3 - 10.3), a drugi argument to MathSqrt(6.25). MathAbs to nazwa funkcji standardowej, która oblicza wartość bezwzględną argumentu zapisanego w jej nagłówku. W jej nagłówku zapisano działanie matematyczne 9.3 - 10.3 co daje -1.0. Wartość bezwzględna tej liczby to 1.0 i właśnie ona będzie pierwszym formalnym parametrem funkcji własnej CylinderVolume(). MathSqrt to nazwa funkcji standardowej, która oblicza pierwiastek kwadratowy argumentu zapisanego w jej nagłówku. W jej nagłówku zapisano liczbę 6.25. Jej pierwiastek kwadratowy to 2.5 i właśnie ta liczba stanie się drugim formalnym parametrem dla CylinderVolume().

Kod 7
//--- Wariant 1
double a = 10.3;
double d = 2.0;

double v = CylinderVolume( ( 55.5 / (47.5 + MathAbs(2.3 - a) ) ), ( MathSqrt(19.36) - 1.9 ) );

//--- Wariant 2
double a = 10.3;
double d = 2.0;

double w1 = MathAbs(2.3 - a); // w1 = MathAbs(2.3 - 10.3) = MathAbs(-8.0) = 8.0
double w2 = 47.5 + w1;        // w2 = 47.5 + 8.0 = 55.5
double r  = 55.5 / w2;        // r  = 55.5 / 55.5 = 1.0

double z1 = MathSqrt(19.36);  // z1 = 4.4
double h  = z1 - 1.9;         // h  = 4.4 - 1.9 = 2.5

double v = CylinderVolume( r , h );

W wariancie 1 pierwszy argument to rozbudowane działanie matematyczne 55.5 / (47.5 + MathAbs(2.3 - a) ), granice którego są ograniczone dwoma nawiasami (zaznaczono na żółto). Drugi argument to zapis MathSqrt(19.36) - 1.9, który też znajduje się wewnątrz nawiasów (zaznaczono na niebiesko) (rys 2).

Rys. 2. Granice argumentów.


W wariancie 2 obliczenia zostały wykonane przed wywołaniem funkcji, wartości przypisane zmiennym r i h, które potem zostały zapisane w nagłówku funkcji.


Formalne parametry

Formalne parametry - to lista zmiennych zapisanych w nagłówku funkcji w jej opisie (rys 1). W tym miejscu nie można zapisywać żadnych działań matematycznych. Nie mogą to też być stałe, tylko nazwy zmiennych. Istnieją dwa sposoby przekazania argumentu do formalnego parametru:

  • kopiowanie wartości,
  • przekazanie wartości za pomocą wskaźnika.

Aby pokazać różnicę między tymi sposobami, najpierw należało by krótko wyjaśnić jak działa pamięć RAM. W momencie, kiedy program tworzy zmienną, to tak naprawdę w pamięci RAM rezerwuje on określoną przestrzeń dla wartości jaką ta zmienna będzie przechowywać. Z rozdziału 3.7. Typy zmiennych: typy liczb zmiennoprzecinkowych wiemy, że typ double zajmuje w pamięci 8 bajtów. Szufladka o takiej długości zostanie zarezerwowana dla zmiennej r (rys. 3), gdzie zostanie zapisana liczba 1.0. Przy tym sama zmienna r staje się "nośnikiem" adresu tej szufladki (adres_1). Kiedy program korzysta ze zmiennej to tak naprawdę bierze od niej adres, idzie do RAM, szuka szufladki z tym adresem i pobiera z niej wartość. To samo się dzieje w przypadku zmiennej h.

Rys. 3. Utworzenie zmiennych w RAM oraz kopiowanie argumentów do formalnych parametrów.


W momencie wywołania funkcji CylinderVolume() argument r przekazuje liczbę 1.0 do formalnego parametru radius. Następnie, dla radius program tworzy nową szufladkę w pamięci RAM (adres_3), gdzie zapisuje 1.0, tj. następuje kopiowanie wartości. To samo się dzieje przy kopiowaniu 2.5 z h do height. Kiedy funkcja realizuje swojego zadania wartości zmiennych radius i height mogą się zmieniać i jeśli tak się dzieje, wtedy zmieniają się ich wartości w szufladkach adres_3 i adres_4, a nie w szufladkach adres_1 i adres_2. Przy takim zapisie formalnych parametrów jak na rys. 3, funkcja własna nie jest w stanie zmienić oryginalnych wartości ani r ani h. Po zakończeniu działania funkcji parametry radius i height są niszczone, a wartości w szufladkach adres_3 i adres_4 są usuwane z pamięci RAM.


Drugim sposobem udostępnienia wartości do funkcji własnej jest przekazanie ich za pomocą wskaźnika. Aby to uczynić, w opisie funkcji między typem parametru a jego nazwą należy zapisać symbol & (kod 8, zaznaczono na źółto). Znak ampersandu to operator uzyskiwania adresu w pamięci.

Kod 8
double CylinderVolume(double & radius, double & height)

Przy takim zapisie program utworzy w RAM nową szufladkę dla radius (adres_3), ale zapisze tam nie liczbę jak w poprzednim przykładzie, a adres szufladki adres_1, tj. zostanie utworzony wskaźnik (rys. 4). W tym przypadku zmienna radius stanie się wskaźnikiem.

Rys. 4. Przekazanie wartości do funkcji własnej za pomocą wskaźników.


Kiedy funkcja własna będzie chciała skorzystać z liczby 1.0, na którą wskazuje radius, to funkcja będzie pracować bezpośrednio z oryginałem zapisanym w innym miejscu (adres_1). Jeśli funkcja zmieni tę liczbę na jakąkolwiek inną (niech to będzie 5.5), wtedy zostanie ona zapisana w szufladce adres_1. Kiedy funkcja zakończy swoje działanie i program wróci do OnStart(), to zmienna r będzie już miała wartość 5.5, ponieważ ona też odwołuje się do szufladki adres_1. Taki sam mechanizm będzie działać w przypadku h i height.

Jak widać, mechanizm wskaźników pozwala funkcji pracować na oryginałach, a nie na ich kopiach. Po co to wszystko? Jeśli chcemy napisać funkcję, która by zmieniała oryginalne wartości to należy używać wskaźniki. Jeśli chcemy mieć 100% pewność, że funkcja nic nie namiesza w oryginałach, to kopiujmy wartości do formalnych parametrów.


Istnieje jeszcze jeden sposób obrony oryginału przed niechcianą zmianą przez funkcję. W jej opisie parametr należy zapisać w następujący sposób: najpierw const (specyfikator dostępu), potem typ parametru, potem & (ampersand), a na końcu nazwa parametru (kod 9).

Kod 9
double CylinderVolume(const double & radius, const double & height)

Specyfikator const deklaruje parametr jako stałą. Jeśli funkcja będzie chciała zmienić wartość takiego parametru, kompilator wyświetli błąd i kompilacja się nie powiedzie.


Na początku tego rozdziału napisałem, że całe tablice też można przekazywać do funkcji własnej. W MQL4 tablicę przekazuje się tylko za pomocą wskaźnika. Aby uniknąć podmiany oryginalnych elementów tablicy, należy zastosować specyfikator const (kod 10).

Kod 10
#property strict

void OnStart()
  {
   double array[] = {1.0 , 2.5 , 3.3};  // tablica
   double v = MyFunction(array);        // wywołanie funkcji własnej
  }

//--- opis funkcji własnej
double MyFunction(const double & f_arr[])
  {
   return(0.0);
  }

Podczas wywołania funkcji własnej wystarczy w jej nagłówku zapisać tylko nazwę tablicy. W powyższym przykładzie w nagłówku MyFunction() zapisano nazwę tablicy array. Z kolei w opisie funkcji parametr został zapisany w następujący sposób: const - specyfikator, który uniemożliwi funkcji podmianę oryginalnych wartości, double - typ identyczny do typu przekazywanej tablicy, & (ampersand) - operator uzyskiwania adresu w pamięci RAM, f_arr - dowolna nazwa parametru i [] jako symbol tablicy.

Ta funkcja nie dokonuje obliczeń, po prostu zwraca 0.0. Postarałem się maksymalnie uprościć zapis, aby pokazać jak należy prawidłowo zapisać argument w nagłówku wywoływanej funkcji i parametr w jej opisie.


W funkcję tablice przekazuje się tylko za pomocą wskaźników.

Przekazanie tablic poprzez kopiowanie ich elementów było by niedopuszczalnym marnotrawstwem pamięci RAM oraz znacząco wydłużyło by działanie programu. Po drugie, przy wywoływaniu funkcji w jej nagłówku można zapisać nie więcej niż 64 argumenty, dlatego większą tablicę nie da się przekazać do funkcji inaczej jak tylko za pomocą wskaźnika. W tym przypadku wskaźnik uzyskuje adres RAM pierwszego elementu tablicy.