D/Funkcje
Z Wikibooks, biblioteki wolnych podręczników.
Spis treści |
[edytuj] Funkcje
Funkcje są bardzo istotną częścią programowania. Występują w prawie wszystkich współczesnych językach programowania. Dzięki funkcjom nie musimy przepisywać tego samego kodu kilkukrotnie a nasze programy stają się czytelniejsze. Ponadto wbudowane w język funkcje sprawiają, że nie musisz pisać np. algorytmu pierwiastkującego - wystarczy, że wywołasz odpowiednią funkcję. Z funkcji zacząłeś korzystać pisząc swój pierwszy program - linijka printfln("Hello World"); to właśnie wywołanie funkcji.
[edytuj] Pojęcie i definicja funkcji
Funkcja jest elementem programu wykonującym pewne operacje. Może przyjmować argumenty oraz zwracać wynik. Funkcję, która nie zwraca żadnego wyniku, nazywamy procedurą.
Definicja funkcji w języku D wygląda następująco:
typ_zwracany nazwa_funkcji(typ_argumentu nazwa_argumentu, typ_argumentu nazwa_argumentu, ...)
{
//instrukcje
}
Użycie deklaracji funkcji w języku nie jest wymagane, ponieważ kompilator ignoruje kolejnosć ich definicji. Jest to dość znaczne usprawnienie w stosunku do języka C.
Aby wywołać funkcję podajemy jej nazwę i argumenty umieszczone w nawiasach. Jak każdą instrukcję, wywołanie funkcji kończymy średnikiem:
nazwa_funkcji(argument1, argument2, ...);
[edytuj] Typ zwracany
Funkcje mogą również zwracać wyniki - przykładowo funkcja real sqrt(real liczba) zawarta w module std.math zwraca wartość typu real, czyli po prostu liczbę rzeczywistą. Jeżeli nie chcemy, aby funkcja zwracała wartość, zamiast typu zwracanego podajemy void:
// Funkcja zwracająca wartość typu int
int funkcja()
{
// coś robi
return 2;
}
// Funkcja niezwracająca wartości
void funkcja
{
//coś robi;
}
W powyższym przykładzie pojawiło się słowo kluczowe return. Instrukcja return powoduje natychmiastowe wyjście z funkcji i zwrócenie wyniku (o ile funkcja zwraca jakiś wynik). Jeżeli funkcja zwraca wynik, instrukcja return wraz z podaniem wyniku jest obowiązkowa. Oczywiście wynikiem może być również zmienna, tablica lub wskaźnik.
Jeżeli za słowem return znajdą się jakieś instrukcje, nie zostaną one wykonane. W procedurach słowo return ma zastosowanie, kiedy w pewnej sytuacji procedura musi zakończyć się w odpowiednim miejscu:
void podzielIWypiszWynik(real a, real b)
{
if(b == 0)
{
writefln("Dzielenie przez zero!");
return; // jeżeli b=0, to nie wykonujemy dzielenia, tylko informujemy użytkownika i opuszczamy procedurę.
}
writefln("Wynik: %f", a/b);
}
[edytuj] Argumenty funkcji
Argumenty funkcji to jej dane wejściowe. Przykładowo pisząc: writefln("Hello World"); przekazujemy funkcji writefln jeden argument - ciąg znaków "Hello World".
Argumenty mogą być formalne i aktualne. Argument formalny jest to argument wywołany ze środka funkcji (za pomocą jego nazwy podanej w definicji tej funkcji). Argument aktualny to argument wywoływany z zewnątrz tej funkcji podczas jej wywoływania. Przykład:
// funkcja
void funkcja (int argument_formalny)
{
writefln("Argument: %d", argument_formalny); // Argument jest formalny, ponieważ do jego wywołania jest używana nazwa podana w definicji funkcji.
}
// wywołanie
int argument_aktualny = 5;
funkcja(argument_aktualny); // Argument jest aktualny, ponieważ do wywołania zmiennej używany jest aktualny identyfikator
funkcja(31); // Ten argument również jest aktualny.
[edytuj] Sposoby przekazywania parametrów
Język D umożliwia przekazywanie parametrów funkcjom na klika różnych sposobów:
- Wejściowy (in) - Ten sposób przekazywania parametrów funkcjom jest domyślny, więc słowo kluczowe in nie jest wymagane. Posiada następujące cechy:
- Przy wejściu do funkcji wartość jest kopiowana z parametru aktualnego do parametru formalnego.
- Przy wyjściu z funkcji wartość parametru nie jest zapisywana w parametrze aktualnym (i zostaje utracona).
- Wyjściowy (out) - Ten sposób często jest wykorzystywany, gdy chcemy zapisać wynik w kilku zmiennych i typ zwracany funkcji nie wystarcza. Parametr wyjściowy musi być zmienną. Posiada następujące cechy:
- Przy wejściu do funkcji wartość parametru aktualnego nie jest kopiowana do parametru formalnego. Parametr formalny zostaje zainicjowany wartością domyślną.
- Przy wyjściu z funkcji wartość parametru formalnego jest zapisywana w parametrze aktualnym.
- Wejściowo-wyjściowy (inout) - Kombinacja powyższych sposobów. Posiada następujące cechy:
- Przy wejściu do funkcji wartość jest kopiowana z parametru aktualnego do parametru formalnego.
- Przy wyjściu z funkcji wartość parametru formalnego jest zapisywana w parametrze aktualnym.
Dla początkujących programistów sposoby przekazywania parametrów mogą okazać się trudne do zrozumienia. Poniższy przykład ilustruje ich działanie w programie:
import std.stdio;
void main()
{
int x = 1, y = 2, z = 3; // Inicjacja wszystkich zmiennych
funkcja(x, y, z);
writefln("%d %d %d", x, y, z); // Wypisze: 1 1 4
}
void funkcja(in int a, out int b, inout int c) // parametr a jest wejściowy, b - wyjściowy, c - wejściowo-wyjściowy
{
// Wejściowy parametr a otrzymuje wartość skopiowaną z parametru aktualnego x. Tak więc a=1.
// Wyjściowy parametr b nie przyjmuje wartości parametru aktualnego y, ale jest inicjowany wartością domyślną. Tak więc b=0.
// Wejściowo-wyjściowy parametr c otrzymuje wartość skopiowaną z parametru aktualnego z. Tak więc c=3.
a += 1;
b += 1;
c += 1;
writefln("%d %d %d", a, b, c); // Wypisze: 2 1 4
// Przy wyjściu z funkcji wartość wejściowego parametru a nie jest zapisywana w x, tak więc x nie zmienia się. x=1.
// Wartość parametru wyjściowego b jest zapisywana w y, a więc y=1.
// Wartość parametru wejściowo-wyjściowego c również zostaje zapisana w zmiennej z, a więc z=4.
}
[edytuj] Parametry domyślne
Parametry funkcji mogą mieć też ustawione wartości domyślne. Zostaną one wykorzystane, jeżeli nie podamy parametru w wywołaniu. Ustawiamy je po prostu przypisując wartości parametrów formalnych w definicji funkcji.
void funkcja(int a = 1)
{
writefln("%d", a);
}
Jeżeli w wywołaniu funkcji podamy wartość argumentu a, zostanie ona podstawiona. W przeciwnym wypadku zostanie wykorzystana wartość domyślna parametru, czyli - w tym wypadku 1.
[edytuj] Zagnieżdżanie funkcji
W języku D istnieje możliwość zagnieżdżania funkcji, to znaczy umieszczania jednej funkcji wewnątrz drugiej. Przydaje się to na przykład do tworzenia funkcji pomocniczych, które wykorzystywane są tylko wewnątrz jednej funkcji. Funkcje można wywoływać:
Przykład zagnieżdżania funkcji:
void A()
{
void B()
{
void C()
{
A(); // OK
B(); // OK
C(); // OK
}
A(); // OK
B(); // OK
C(); // OK
}
A(); // OK
B(); // OK
C(); // Błąd, funkcja C nie jest zdefiniowana w funkcji A
}
Każda funkcja może być wywołana przez samą siebie, dowolną funkcję podrzędną lub bezpośrednio nadrzędną funkcję. Oznacza to, że w powyższym przykładzie z posiomu funkcji C możemy wywołać wszystkie trzy (A i B są funkcjami nadrzędnymi, C aktualnie wykonywaną), z poziomu funkcji B również (A - funkcja nadrzędna, B - aktualnie wykonywana, C leży bezpośrednio w B), natomiast z A tylko pierwsze dwie (bo C nie leży bezpośrednio w funkcji A, tylko w funkcji B).
[edytuj] Inlinowanie
Inlinowanie jest technika umożliwiająca wstawienie kodu funkcji w miejsce jej wystąpienia zamiast wskaźnika do tej funkcji w pamięci. Stosuje się ją najczęściej do krótkich i często występujących funkcji w celu zwiększenia wydajności programu.
W przeciwieństwie do C i C++, w języku D kompilator automatycznie decyduje, kiedy "inlinować" funkcję lub metodę.
[edytuj] Constant folding
Constant folding to wykorzystywana przez kompilator technika upraszczająca stałe wyrażenia jeszcze na poziomie kompilacji. Przykładowo:
int x = 2; int a = 4*x*8/2;
zostanie jeszcze w czasie kompilacji zamienione na:
int x = 2; int a = 16*x; // x nie jest stałą, więc nie zostanie zamienione.
[edytuj] Wykonanie w czasie kompilacji
Niektóre funkcje mogą zostać wykonane jeszcze w czasie kompilacji - przydaje się to przy obliczaniu pewnych stałych wartości. Aby funkcja mogła zostać wykonana w czasie kompilacji, wszystkie jej parametry muszą być stałe. Funkcja wykonywana w czasie kompilacji nie może wykorzystywać klas, plików, operacji wejścia/wyjścia. Może natomiast wkorzystywać inne funkcje, które nie naruszają powyższych zasad.
Przykład:
int sqr(int a) { return a*a; }
int main() {
static x = sqr(5); // wykona się w trakcie kompilacji
int x = sqr(5); // w trakcie wykonania
writefln(sqr(5)); // w trakcie wykonania
writefln(eval!(sqr(5))); // w trakcie kompilacji
const y = 6;
const x2 = sqr(y); // w trakcie kompilacji
int y2 = 6;
const x3 = sqr(y2); // w trakcie wykonania
}
template eval(E) { alias E eval; }
| Do zrobienia: Pokazać proste wskaźniki do funkcji i delegaty |