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.
Na miejscu argumentu można zapisać:
// --- 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
double v = CylinderVolume( 1.0 , 2.5 ); // argumenty jako wartości liczbowe
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
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 .
//--- 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.
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().
//--- 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 - 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:
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.
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).
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).
#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.
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.