1 Podstawy funkcji

W programowaniu funkcja to fragment kodu, który wykonuje określone zadanie i może mieć parametry wejściowe oraz wartości zwracane. W języku Go funkcje są szeroko wykorzystywane do organizowania i ponownego wykorzystywania kodu. Skuteczne wykorzystanie funkcji może sprawić, że kod będzie bardziej zwięzły, łatwy do czytania i łatwy w utrzymaniu.

Funkcje stanowią kluczowy element języka Go, a zrozumienie sposobu deklaracji i definiowania funkcji jest kluczowe dla pisania wydajnego i czytelnego kodu.

2 Definiowanie funkcji

2.1 Deklaracja funkcji

W języku Go ogólna postać deklaracji funkcji jest następująca:

func nazwaFunkcji(parametry) typZwracany {
    // Ciało funkcji
}

Rozłóżmy te składniki:

  1. Słowo kluczowe func służy do deklaracji funkcji.
  2. nazwaFunkcji to nazwa funkcji, zgodna z konwencją nazewnictwa języka Go. Funkcja z wielką literą na początku jest eksportowana, co oznacza, że jest widoczna poza pakietem; funkcja z małą literą na początku nie jest eksportowana i może być używana tylko w tym samym pakiecie.
  3. parametry to lista parametrów, które funkcja otrzymuje, oddzielone przecinkami. Należy określić typ każdego parametru, a jeśli wiele parametrów ma ten sam typ, można określić typ tylko raz.
  4. typZwracany to typ wartości zwracanej przez funkcję. Jeśli funkcja nie zwraca wartości, ten fragment można pominięć. Jeśli funkcja zwraca wiele wartości, należy je umieścić w nawiasach.

Na przykład możemy zadeklarować prostą funkcję obliczającą sumę dwóch liczb całkowitych:

func Dodaj(a int, b int) int {
    return a + b
}

W tym przykładzie nazwa funkcji to Dodaj, przyjmuje ona dwa parametry typu int (a i b) i zwraca ich sumę o typie int.

2.2 Lista Parametrów

Lista parametrów określa, jakie parametry przyjmuje funkcja i jaki jest typ każdego parametru. Parametry są specjalnym rodzajem zmiennej używanej do przekazywania danych do funkcji.

func Przywitaj(imie string, wiek int) {
    fmt.Printf("Cześć, %s! Masz %d lat.\n", imie, wiek)
}

W funkcji Przywitaj, imie i wiek są parametrami. Typ imie to string, a typ wiek to int. Podczas wywoływania tej funkcji należy podać rzeczywiste wartości dla tych parametrów.

2.3 Typy Zwracane

Funkcje mogą zwracać obliczone wyniki, a typ zwracany określa typ danych zwracanych przez funkcję. Funkcje mogą nie zwracać wartości lub mogą zwracać jedną lub więcej wartości.

Jeśli funkcja ma wiele wartości zwracanych, ich typy powinny być umieszczone w nawiasach podczas deklaracji:

func Podziel(dzielna, dzielnik float64) (float64, error) {
   if dzielnik == 0 {
      return 0, errors.New("nie można dzielić przez zero")
   }
   return dzielna / dzielnik, nil
}

Funkcja Podziel zwraca tutaj dwie wartości: iloraz oraz komunikat błędu. Jeśli dzielnik wynosi zero, zwracany jest błąd.

2.4 Parametry Zmiennoliczbowe

W języku Go, gdy nie jesteśmy pewni, ile parametrów poda wywołujący przy definiowaniu funkcji, możemy użyć parametrów zmiennoliczbowych. Parametry zmiennoliczbowe oznacza się za pomocą wielokropka ..., co oznacza, że funkcja może przyjmować dowolną liczbę parametrów. Jest to bardzo przydatne przy pracy z niepewną ilością danych albo implementacji określonych typów funkcji, takich jak formatowanie, podsumowywanie lub iteracyjne funkcje.

Scenariusze zastosowań

Parametry zmiennoliczbowe są powszechnie wykorzystywane w następujących scenariuszach:

  1. Łączenie ciągów znaków: np. funkcje fmt.Sprintf i strings.Join.
  2. Przetwarzanie tablicy/slice'a: podczas operowania na tablicach lub slice'ach o zmiennej długości, jak obliczanie sumy lub łączenie wielu slice'ów.
  3. Rejestrowanie i obsługa błędów: podczas obsługi wielu błędów lub zapisywania wielu informacji dziennika, np. log.Printf lub funkcje podsumowujące błędy niestandardowe.
  4. Funkcje pomocnicze: używane w interfejsach API lub bibliotekach, aby zapewnić użytkownikom bardziej elastyczny sposób wywoływania funkcji.

Korzystanie z parametrów zmiennych

Oto prosty przykład korzystania z parametrów zmiennych:

// Zdefiniuj funkcję, która przyjmuje zmienną liczbę parametrów całkowitych i zwraca ich sumę
func Sum(nums ...int) int {
    total := 0
    for _, num := range nums {
        total += num
    }
    return total
}

func main() {
    // Możesz przekazywać dowolną liczbę parametrów
    fmt.Println(Sum(1, 2))          // Wynik: 3
    fmt.Println(Sum(1, 2, 3, 4))    // Wynik: 10
    
    // Możesz także przekazać slice i użyć wielokropka po slicu
    numbers := []int{1, 2, 3, 4, 5}
    fmt.Println(Sum(numbers...))    // Wynik: 15
}

W funkcji Sum, nums jest slice'em liczb całkowitych zawierającym wszystkie parametry przekazane do funkcji Sum. Możemy użyć pętli range do iteracji po tym slicu i obliczenia sumy.

Warto zauważyć, że parametry zmiennych wewnątrz funkcji są w rzeczywistości slicem, dlatego można używać wszystkich operacji związanych ze slicami. Gdy potrzebujesz przekazać slice jako parametr zmienny do funkcji, po prostu dodaj wielokropek ... po slicu.

Korzystanie z parametrów zmiennych może uczynić wywołania funkcji bardziej elastycznymi i zwięzłymi, ale również wpłynie nieco na wydajność, ponieważ korzystanie z parametrów zmiennych wiąże się z tworzeniem sliców i alokacją pamięci. Dlatego należy ostrożnie postępować, gdy istnieją surowe wymagania wydajnościowe.

Wskazówka: Szczegółowe wyjaśnienie dotyczące sliców zostanie podane w późniejszych rozdziałach. Jeśli masz doświadczenie z innymi językami programowania, możesz tymczasowo myśleć o nich jako tablicach.

3 Wywoływanie funkcji

3.1 Podstawowe wywołania funkcji

Wywołanie funkcji oznacza wykonanie kodu funkcji. W Go wywołanie zdefiniowanej funkcji jest bardzo proste, po prostu użyj nazwy funkcji i przekaż odpowiednie parametry. Na przykład:

result := add(3, 4)
fmt.Println(result)  // Wynik: 7

W tym przykładzie funkcja add jest wywoływana, a następnie przekazywane są jej dwa liczby całkowite, a zwrócony wynik jest przypisywany do zmiennej result.

3.2 Przekazywanie parametrów

Podczas przekazywania parametrów do funkcji, Go domyślnie używa przekazywania przez wartość, co oznacza przekazanie kopii wartości parametru, a oryginalne dane nie zostaną zmienione. Jednak jeśli chcesz, aby funkcja modyfikowała zmienne zewnętrzne lub z przyczyn wydajnościowych, można użyć przekazywania przez referencję, na przykład za pomocą wskaźników lub przekazywania dużych struktur.

Oto przykłady przekazywania przez wartość i przez referencję:

// Przykład przekazywania przez wartość
func double(val int) {
    val *= 2
}

// Przykład przekazywania przez referencję
func doublePtr(val *int) {
    *val *= 2
}

func main() {
    value := 3
    double(value)
    fmt.Println(value)  // Wynik: 3, wartość pozostaje niezmieniona

    doublePtr(&value)
    fmt.Println(value)  // Wynik: 6, wartość podwojona
}

W powyższym przykładzie funkcja double próbuje podwoić przekazaną wartość val, ale podwaja tylko jej kopię, pozostawiając oryginalną zmienną value niezmienioną. Jednak funkcja doublePtr zmienia wartość oryginalnej zmiennej, odbierając wskaźnik do zmiennej całkowitoliczbowej jako parametr.