fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
HOT / 7 minut

Golang #3 – funkcje, struktury oraz własne typy


24.07.2019

Przedstawiam trzeci odcinek tutoriala GO. Poprzednie znajdziesz pod tym linkiem!

Funkcje

Deklaracja funkcji zaczyna się od słowa kluczowego „func”, po którym występuje nazwa funkcji. Najczęściej. :) Są wyjątki od tej reguły, ale o tym za chwilę. Funkcja main() jest doskonałym przykładem funkcji, która nie przyjmuje żadnych parametrów oraz nic nie zwraca. Przyjrzyjmy się więc funkcji main() oraz funkcji, która zarówno zwraca wynik, jak i przyjmuje parametry wejściowe.

func favoriteNumber(i int) string {
	return fmt.Sprintf("my favorite number is %d", i)
}

func main() {
	fmt.Printf("Here is what the function returns: %s", favoriteNumber(13))
}

Jak widać, parametry wejściowe są podawane w nawiasach, natomiast typ zwracany jest umieszczony po owym nawiasie, przed klamrą otwierającą. Spójrzmy teraz na parametry.

To, co jest na wejściu funkcji

Kolejne parametry funkcji są wymieniane po przecinkach, tak jak pokazano poniżej.

func twoFavoriteNumbers(i int, j int) string {
	return fmt.Sprintf("my favorite numbers are %d and %d", i, j)
}

I tutaj możemy skorzystać z ułatwienia: jeśli dwie zmienne (lub więcej) są tego samego typu oraz następują po sobie, to wystarczy podać typ tylko raz.

func favoriteNumbers(a1, a2, a3, a4, a5 int) string {
}

Jeśli podamy po nazwie, lecz przed typem zmiennej wielokropek ..., to otrzymamy tablicę elementów danego typu. Dokładniejsze omówienie map oraz tablic zostawmy sobie na później.

func favoriteNumbers(numbers... int) string {
	return fmt.Sprintf("my favorite numbers are: %+v", numbers)
}

func main() {
	fmt.Printf("Here is what the function returns: %s\n", favoriteNumbers(7, 13))
	fmt.Printf("Here is what the function returns: %s", favoriteNumbers(1,2,3,4,5,6,7,8))
}

Co możemy podać jako parametr funkcji? Praktycznie wszystko, nawet wskaźnik do innej funkcji. Możliwe, że brzmi to dziwnie, jednak czasami taką technikę się stosuje. Golang podczas kompilacji rozpozna, czy dana funkcja pasuje do deklaracji z funkcji `runAnotherFunction`, i jeśli definicje nie będą zgodne, to zgłosi błąd.

func runAnotherFunction(f func()string) string {
	return fmt.Sprintf("the function returned: %s", f())
}

func hello()string {
	return "Hello"
}

func world()string {
	return "World!"
}

func anotherFunction(int i)string {
	return "I do not fit:( "
}

func main() {
	fmt.Println(runAnotherFunction(hello))
	fmt.Println(runAnotherFunction(world))
	//fmt.Println(runAnotherFunction(anotherFunction)) // won't compile
}

Omawiając parametry funkcji, należy powiedzieć o jeszcze jednej rzeczy. Otóż nie trzeba podawać nazwy parametru, lecz można zostawić tylko typ.

func funcWithoutNameInParams(string)string {
	return "I have no idea how to get the parameter's value"
}

func main() {
	fmt.Println(funcWithoutNameInParams("it does not matter"))
}

Korzysta się z tej możliwości, gdy – przykładowo – implementuje się interfejs, w którym nie wszystkie parametry są nam potrzebne. Nie otrzymujemy wtedy ostrzeżenia, że dana zmienna jest nieużywana.

Return, czyli to, co funkcja zwraca

Wartości zwracane w Go mają kilka ciekawych właściwości. Przede wszystkim może być ich więcej niż jedna. Rozdziela się je wtedy przecinkami oraz zamyka w nawiasach.

func multipleReturnValues() (int, string) {
	return 4321, "I am second!"
}

func main() {
	first, second := multipleReturnValues()

	fmt.Printf("Hello, the multipleReturnValues() returned number %d and text '%s'", first, second)
}

Inną, nie wszędzie spotykaną cechą są tak zwane named return values, czyli nazwane wartości zwracane. Na początku wykonywania funkcji tworzone są zmienne o zdefiniowanych przez nas typach i nazwach, które możemy modyfikować. Dzięki temu na samym końcu nie musimy po słówku „return” podawać nic – funkcja zwróci właśnie te wartości jako wynik funkcji.

func namedReturnValues() (myDigit int, coolString string) {
	myDigit = 123
	coolString = "here I am!"
	
	return
}

Oczywiście w takich wypadkach można po słówku „return” podać wprost to, co chcemy zwrócić, i wszystko zadziała, jak należy. W oficjalnym tourze znajdziemy informację, że powinno się używać tego mechanizmu tylko przy małych funkcjach. Zauważyłem jednak, że i w takich sytuacjach rzadko się korzysta z tej możliwości, ponieważ wiele osób uznaje ten sposób pisania funkcji za trochę mylący. Dlatego preferują (w tym również ja) podawanie explicit wartości zwracanych. Jak wszystko – ma to swoje plusy i minusy. Jeśli uważasz, że zwiększa to czytelność kodu – droga wolna!

Zamierzam nagrać tutorial (po polsku) o Go. Jeśli jesteś zainteresowany, to tutaj podaję link: Go wideotutorial

Struktury

Składnię deklaracji struktury definiuje się podobnie jak w innych językach programowania. Krótko mówiąc – struktura jest to jakaś dana plus informacja o ewentualnym jej zachowaniu. Aby stworzyć strukturę, należy stworzyć nowy typ – strukturalny. Poniżej znajduje się przykład jednej z nich:

type Order struct {
	ID int
	User user.User
}

Pola w strukturze podaje się w klamrach, zaczynając od nazwy pola, po czym wskazuje się typ. Kropka jest operatorem (podobnie jak np. w Javie), który pozwala nam odwołać się do pól.
Ważna informacja: Nie zawsze mamy dostęp do wszystkich pól oraz metod w strukturze. Opowiem o tym więcej przy okazji pakietów oraz widoczności.

Nową instancję struktury tworzymy tak jak zwykłe zmienne, tylko że po nazwie struktury dodajemy klamry. W tych klamrach możemy (ale nie musimy) podać wartości dla naszej struktury. W przeciwnym wypadku zostaną one wypełnione wartościami domyślnymi (dla string będzie to pusty tekst, a dla wartości liczbowych – 0 i tak dalej).
Podajemy wartości pól analogicznie jak przy tworzeniu struktury – z tą różnicą, że zamiast typu wpisujemy interesującą nas wartość oraz oddzielamy nazwę pola od owej wartości dwukropkiem. Na końcu stawiamy zawsze przecinek.

order := Order{
    ID: 123,
}

Nie musimy podawać wszystkich wartości oraz nie musimy tego robić w kolejności, w jakiej definiowaliśmy pola. Co ciekawe, jeśli będziemy podawać te wartości właśnie w takiej kolejności, to możemy pominąć typ.

order := Order{123}

Należy pamiętać, że musisz podać wszystkie pola dostępne w tej strukturze. Osobiście nie przepadam za tym rozwiązaniem, ale o gustach się nie dyskutuje. :)

Zachowanie (metodę) dodajemy do struktury oddzielnie, czyli poza opisem struktury.

Dla przykładu – klasa Order w Javie będzie wyglądać tak:

class Order {
  int ID;
  String firstName;
  String LastName;

  int getID() {
    return this.ID;
  }
}

natomiast w Go zapiszemy tak:

type Order struct {
	ID        int
	firstName string
	lastName  string
}

func (o Order) GetID() int {
	return o.ID
}

Konwencja jest następująca: na początku używamy słowa kluczowgo func, później w nawiasach podajemy obiekt naszej klasy, a następnie musimy podać funkcję, tak jak jest to opisane na początku tego artykułu.

func (o Order) FullName() string {
	return o.firstName + " " + o.lastName
}

To by było na razie wszystko. Jeśli masz jakieś pytania – poniżej jest sekcja z komentarzami. :) Widzimy się już wkrótce!

0 0 votes
Article Rating
7 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Marcin
Marcin
4 years ago

func (o Order) FullName() int {
return o.firstName + ” ” + o.lastName
}

Ta funkcja na pewno zwraca int?

Mateusz Szczyrzyca
4 years ago

Nie wiem tylko, czy ten getter w Go na podstawie kodu javowego, to dobry pomysł (wiem co chciałeś nim pokazać) – ktoś z Javy na to spojrzy i jeszcze pomyśli, że w Go tak się to robi z automatu. :-)

Tym niemniej fajny kurs Barek robisz, keep going, bo warto! Będę śledził kolejne części. :)

Pozdrawiam!

Atommy
Atommy
4 years ago

Go nie rozróżnia funkcji od procedur?

Atommy
Atommy
4 years ago

Używam przyjętego słownika. Jest różnica między funkcją/metodą/procedurą. Testowałeś kiedyś procedurę?

Kurs Gita

Zaawansowany frontend

Szkolenie z Testów

Szkolenie z baz danych

Książka

Zobacz również