Struktury w Golang

W języku Go, struktury, zwane także struct, stanowią jeden z fundamentalnych elementów pozwalających na kreowanie własnych, złożonych typów danych.

W niniejszym artykule zagłębimy się w świat struktur, prezentując ich istotę, sposób implementacji i zastosowania w programowaniu w Go, a wszystko to poparte praktycznymi przykładami.

Zaczynajmy!

Wprowadzenie

Struktura to nic innego jak zbiór powiązanych ze sobą danych, gdzie każde pole może być określonego typu. Struktury umożliwiają tworzenie niestandardowych, złożonych rekordów poprzez grupowanie różnych elementów. W skład struktury mogą wchodzić zarówno typy wbudowane, jak i typy definiowane przez użytkownika (przy czym sama struktura jest typem definiowanym przez użytkownika).

Struktury w Go charakteryzują się zmiennością, co oznacza, że ich zawartość może być modyfikowana w trakcie działania programu.

Struktury znacząco podnoszą jakość kodu poprzez możliwość tworzenia i przekazywania złożonych zbiorów danych pomiędzy różnymi modułami. Zamiast przekazywać do funkcji szereg pojedynczych parametrów, możemy zgrupować je w strukturę i operować na niej jako na pojedynczym obiekcie.

Struktury deklaruje się, używając słów kluczowych type i struct. Ich składnię przypomina nieco klasy w Javie, zbiór pól ujęty jest w nawiasy klamrowe. Każde pole ma przypisany konkretny typ i nazwę. Szczegóły implementacyjne omówimy w dalszej części artykułu.

Dla osób mających doświadczenie z programowaniem obiektowym (OOP), struktura może przypominać klasę, z tą różnicą, że nie obsługuje dziedziczenia.

Definiowanie Struktur

Po zapoznaniu się z definicją i zastosowaniem struktur, przyszedł czas na naukę ich deklarowania. Podstawowy schemat struktury prezentuje się następująco:

type nazwa_struktury struct {
    pole1 typ_danych_pola1
    pole2 typ_danych_pola2
}

W powyższym przykładzie, słowa kluczowe „type” i „struct” są elementami składni języka Go. Wewnątrz struktury definiujemy pola wraz z ich typami danych.

Spójrzmy na konkretny przykład:

package main

import (
	"fmt"
)

type Użytkownik struct {
	imie        string
	wiek         int
	saldoBankowe float32
}

func main() {
	var uzytkownik Użytkownik
	fmt.Println(uzytkownik)
}

W tym przykładzie, tworzymy strukturę o nazwie „Użytkownik”, która składa się z trzech pól: imienia (string), wieku (int) i salda bankowego (float32). W funkcji main(), deklarujemy zmienną „uzytkownik” typu „Użytkownik” i wyświetlamy jej zawartość. Ponieważ struktura nie została jeszcze zainicjalizowana, otrzymujemy tzw. wartość zerową, która jest domyślną wartością dla każdego z pól.

{ 0 0}

Inicjalizacja Struktur

W poprzednim rozdziale nauczyliśmy się deklarować struktury, teraz zajmiemy się przypisywaniem im konkretnych wartości. Poniższy kod ilustruje, jak tego dokonać:

package main

import (
	"fmt"
)

type Użytkownik struct {
	imie        string
	wiek         int
	saldoBankowe float32
}

func main() {
	// Inicjalizacja z nazwami pól
	uzytkownik1 := Użytkownik{
		imie:        "Jan",
		wiek:         30,
		saldoBankowe: 1500.0,
	}
	
	// Inicjalizacja bez nazw pól
	uzytkownik2 := Użytkownik{"Anna", 25, 2500.0}
	
	fmt.Println(uzytkownik1)
	fmt.Println(uzytkownik2)
}

Powyższy kod przedstawia dwa sposoby inicjalizacji struktury: z wykorzystaniem nazw pól oraz bez nich. Wynik działania programu będzie następujący:

{Jan 30 1500}
 {Anna 25 2500}

W przypadku inicjalizacji z pominięciem niektórych pól, przyjmują one domyślną wartość zerową:

uzytkownik1 := Użytkownik{
    imie:        "Jan",
    wiek:         30,
}

 // Wynik - {Jan 30 0.0 }

Istnieje również inna metoda tworzenia struktur za pomocą słowa kluczowego `new`. Szczegóły poznamy w następnej sekcji.

Dostęp do pól struktury

Skoro potrafimy już tworzyć i inicjalizować struktury, sprawdźmy, jak uzyskać dostęp do ich pól. W Go wykorzystuje się do tego operator kropki. Na przykładzie wcześniejszego kodu, spróbujmy wyświetlić imię i wiek użytkownika:

package main

import (
	"fmt"
)

type Użytkownik struct {
	imie        string
	wiek         int
	saldoBankowe float32
}

func main() {
	// Inicjalizacja z nazwami pól
	uzytkownik := Użytkownik{
		imie:        "Jan",
		wiek:         30,
		saldoBankowe: 1500.0,
	}

	fmt.Println(uzytkownik.imie)
	fmt.Println(uzytkownik.wiek)
	fmt.Println(uzytkownik.saldoBankowe)
}

Używając składni `nazwa_struktury.nazwa_pola`, uzyskujemy dostęp do konkretnych pól struktury. Wynik działania powyższego kodu to:

Jan
 30
 1500

Jak wspomniano wcześniej, struktury możemy tworzyć również przy pomocy słowa kluczowego `new`. Zobaczmy jak to działa:

uzytkownik := new(Użytkownik)
 uzytkownik.imie = "Jan"
 uzytkownik.wiek = 30
 uzytkownik.saldoBankowe = 1500.0

 fmt.Println(uzytkownik)

 // Wynik - &{Jan 30 1500}

Słowo kluczowe `new` zwraca wskaźnik do nowo utworzonej struktury. W Go nie ma potrzeby jawnego wyłuskiwania wskaźnika, ale użycie `fmt.Println(*uzytkownik)` dałoby identyczny rezultat.

Zagnieżdżone Struktury

W Go, struktury mogą zawierać w sobie inne struktury. Oznacza to, że możemy tworzyć struktury zagnieżdżone.

package main

import (
	"fmt"
)

type Użytkownik struct {
	imie        string
	wiek         int
	saldoBankowe float32
	detaleRoli DetaleRoli
}

type DetaleRoli struct {
	stanowisko string
	zespół     string
}

func main() {
	detaleRoliDlaJana := DetaleRoli{
		stanowisko: "Programista",
		zespół:     "IT",
	}
	uzytkownik := Użytkownik{
		imie:        "Jan",
		wiek:         30,
		saldoBankowe: 1500.0,
		detaleRoli: detaleRoliDlaJana,
	}

	fmt.Println(uzytkownik)
}

W powyższym przykładzie, struktura „DetaleRoli” jest częścią struktury „Użytkownik”. Wyjście programu będzie następujące:

{Jan 30 1500 {Programista IT}}

Dostęp do zagnieżdżonych pól uzyskujemy, korzystając z tego samego operatora kropki: `uzytkownik.detaleRoli.stanowisko`.

Porównywanie Struktur

Dwie struktury są sobie równe, gdy wszystkie odpowiadające im pola są równe (zarówno wbudowane, jak i zdefiniowane przez użytkownika). Nie wszystkie typy danych są jednak porównywalne. (na przykład mapa nie jest porównywalna). Sprawdźmy to na przykładzie.

package main

import (
	"fmt"
)

type Użytkownik struct {
	imie        string
	wiek         int
	saldoBankowe float32
}

func main() {
	uzytkownik1 := Użytkownik{
		imie:        "Jan",
		wiek:         30,
		saldoBankowe: 1500.0,
	}
	uzytkownik2 := Użytkownik{
		imie:        "Jan",
		wiek:         30,
		saldoBankowe: 1500.0,
	}
	uzytkownik3 := Użytkownik{
		imie:        "Anna",
		wiek:         25,
		saldoBankowe: 2500.0,
	}

	if uzytkownik1 == uzytkownik2 {
		fmt.Println("uzytkownik1 i uzytkownik2 są równe")
	} else {
		fmt.Println("uzytkownik1 i uzytkownik2 nie są równe")
	}

	if uzytkownik1 == uzytkownik3 {
		fmt.Println("uzytkownik1 i uzytkownik3 są równe")
	} else {
		fmt.Println("uzytkownik1 i uzytkownik3 nie są równe")
	}
}

Puste i zerowe wartości struktur są sobie równe. Kolejność pól nie ma znaczenia, liczy się wyłącznie zgodność wartości poszczególnych pól. Wynik powyższego kodu będzie następujący:

uzytkownik1 i uzytkownik2 są równe
uzytkownik1 i uzytkownik3 nie są równe

Podsumowanie

Wspaniale!

Teraz posiadasz wiedzę niezbędną do korzystania ze struktur w języku Go. Przeanalizowaliśmy podstawowe zagadnienia, takie jak deklarowanie, inicjalizowanie i dostęp do pól struktur. Dowiedzieliśmy się również, jak porównywać struktury oraz jak zaimplementować struktury zagnieżdżone. Poniżej kilka linków do dalszej nauki:

W temacie struktur jest jeszcze wiele do odkrycia, jednak to doskonały punkt wyjścia. Mam nadzieję, że ten artykuł wzbogacił Twoją wiedzę!

Eksploruj i ucz się dalej!


newsblog.pl