D/Funkcje

Z Wikibooks, biblioteki wolnych podręczników.

< D

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 Do zrobienia:
Pokazać proste wskaźniki do funkcji i delegaty