1 Wprowadzenie do typów danych w języku Go

W języku Go typy danych stanowią podstawę programowania, określając formę danych, które mogą przechowywać zmienne. Podstawowe typy danych dostarczane przez język Go są głównie podzielone na następujące kategorie:

Typ danych Opis Zużycie pamięci
bool Typ logiczny, używany do przechowywania wartości true lub false 1 bajt
int, uint Liczby całkowite ze znakiem i bez znaku, domyślny rozmiar zależy od platformy systemowej 4 lub 8 bajtów
int8, uint8 8-bitowe liczby całkowite ze znakiem i bez znaku 1 bajt
int16, uint16 16-bitowe liczby całkowite ze znakiem i bez znaku 2 bajty
int32, uint32 32-bitowe liczby całkowite ze znakiem i bez znaku 4 bajty
int64, uint64 64-bitowe liczby całkowite ze znakiem i bez znaku 8 bajtów
float32 Liczba zmiennoprzecinkowa 32-bitowa 4 bajty
float64 Liczba zmiennoprzecinkowa 64-bitowa 8 bajtów
complex64 Liczba zespolona z 32-bitową częścią rzeczywistą i urojoną 8 bajtów
complex128 Liczba zespolona z 64-bitową częścią rzeczywistą i urojoną 16 bajtów
byte Podobne do uint8 1 bajt
rune Podobne do int32, reprezentuje punkt kodowy Unicode 4 bajty
string Typ łańcuchowy Zależy od długości łańcucha
error Interfejs błędu, używany do zwracania informacji o błędzie Bez ustalonego rozmiaru

Te typy można wybierać w zależności od różnych potrzeb, takich jak obliczenia numeryczne, przetwarzanie tekstów czy sterowanie logiczne.

2 Typy danych całkowitoliczbowych

2.1 Przegląd typów całkowitoliczbowych

W języku Go istnieje wiele wbudowanych typów całkowitoliczbowych, sklasyfikowanych następująco:

  • Liczby całkowite ze znakiem: int8, int16, int32 (lub rune), int64 oraz int
  • Liczby całkowite bez znaku: uint8 (lub byte), uint16, uint32, uint64 oraz uint

Rozmiary int i uint wynoszą 4 bajty w systemach 32-bitowych i 8 bajtów w systemach 64-bitowych. Zakresy wartości typów danych całkowitoliczbowych są przedstawione w poniższej tabeli:

Typ Zakres wartości
int8 -128 do 127
uint8 0 do 255
int16 -32768 do 32767
uint16 0 do 65535
int32 -2147483648 do 2147483647
uint32 0 do 4294967295
int64 -9223372036854775808 do 9223372036854775807
uint64 0 do 18446744073709551615

2.2 Użycie zmiennych całkowitoliczbowych

Podstawowa składnia deklarowania zmiennej całkowitoliczbowej jest następująca:

var nazwa_zmiennej typ_danych = wartosc_poczatkowa

Przykładowy kod:

package main
import "fmt"

func main() {
    var a int = 10 // Zmienna całkowita ze znakiem
    var b uint = 20 // Zmienna całkowita bez znaku
    var c int8 = -128 // Najmniejsza wartość int8
    fmt.Println(a, b, c)
}

2.3 Operacje na liczbach całkowitych

Język Go obsługuje standardowe operatory arytmetyczne, takie jak dodawanie (+), odejmowanie (-), mnożenie (*), dzielenie (/), i reszta z dzielenia (%), a także operatory bitowe, takie jak bitowe AND (&), OR (|), XOR (^), przesunięcie w lewo (<<), i przesunięcie w prawo (>>).

package main
import "fmt"

func main() {
    x := 10
    y := 3

    // Operacje arytmetyczne
    fmt.Println(x + y) // Dodawanie
    fmt.Println(x - y) // Odejmowanie
    fmt.Println(x * y) // Mnożenie
    fmt.Println(x / y) // Dzielenie
    fmt.Println(x % y) // Reszta z dzielenia

    // Operacje bitowe
    fmt.Println(x & y)  // Bitowe AND
    fmt.Println(x | y)  // Bitowe OR
    fmt.Println(x ^ y)  // Bitowe XOR
    fmt.Println(x << 1) // Przesunięcie w lewo o 1
    fmt.Println(x >> 1) // Przesunięcie w prawo o 1
}

3 Typy danych zmiennoprzecinkowe

3.1 Przegląd typów danych zmiennoprzecinkowych

W języku Go typy zmiennoprzecinkowe obejmują float32 i float64, odpowiadające 32-bitowym i 64-bitowym danym zmiennoprzecinkowym. Ogólnie zaleca się używanie float64, ponieważ zapewnia on większy zakres i precyzję.

  • float32 ma około 23 istotne bity, co daje około 7 miejsc po przecinku.
  • float64 ma około 52 istotne bity, co daje około 16 miejsc po przecinku.

3.2 Korzystanie z zmiennych zmiennoprzecinkowych

Zmienne zmiennoprzecinkowe można deklarować poprzez bezpośrednie podanie literałów lub używając słowa kluczowego var:

package main
import "fmt"

func main() {
    var f1 float32 = 3.14 // Wyraźne określenie typu float32
    f2 := 3.14            // Automatycznie wywnioskowany jako typ float64
    fmt.Println(f1, f2)
}

3.3 Arytmetyka zmiennoprzecinkowa i problemy

Arytmetyka zmiennoprzecinkowa może prowadzić do utraty precyzji. Wykonywanie operacji o bardzo dużej precyzji jest powszechnym problemem, zwłaszcza przy odejmowaniu dwóch bardzo bliskich liczb.

package main
import "fmt"

func main() {
    f1 := .1
    f2 := .2
    f3 := f1 + f2
    fmt.Println(f3) // Wynik może nie być oczekiwanym .3 z powodu problemów z precyzją

    // Naprawa problemów z precyzją za pomocą sformatowanego wyjścia
    fmt.Printf("%.1f\n", f3) // Wynik poprawiony do .3
}

4 Typ danych boolowski

4.1 Przegląd typu danych boolowskiego

Boolowski jest najprostszym typem danych i może przyjmować tylko dwie wartości: true (prawda) i false (fałsz). Odgrywa bardzo ważną rolę w instrukcjach warunkowych i strukturach kontrolnych pętli.

4.2 Korzystanie z zmiennych boolowskich

Deklaracja i stosowanie zmiennych boolowskich:

package main
import "fmt"

func main() {
    var sukces bool = true
    var niepowodzenie bool = false
    fmt.Println("Operacja udana:", sukces)
    fmt.Println("Operacja zakończona niepowodzeniem:", niepowodzenie)
}

Wartości boolowskie są często używane w instrukcjach warunkowych:

package main
import "fmt"

func main() {
    a := 10
    b := 20
    fmt.Println("a == b:", a == b) // false
    fmt.Println("a < b:", a < b)   // true
}

5 Typ danych łańcuchów znaków

5.1 Przegląd łańcuchów znaków

Łańcuch znaków jest zbiorem znaków. W języku Go łańcuchy znaków są niemutowalne. Każdy łańcuch składa się z dwóch części: wskaźnika do podstawowej tablicy bajtów i długości. Łańcuchy znaków mogą zawierać dowolne dane, włącznie z bajtami.

5.2 Korzystanie z zmiennych łańcuchów znaków

Zmienne łańcuchów znaków są zazwyczaj deklarowane za pomocą podwójnych cudzysłowów " do tworzenia, ale można też używać odwrotnych apostrofów ` do tworzenia wieloliniowych ciągów znaków:

package main
import "fmt"

func main() {
    var s1 string = "witaj"
    s2 := "świecie"
    s3 := `To jest 
    ciąg znaków 
    wieloliniowy`
    fmt.Println(s1)
    fmt.Println(s2)
    fmt.Println(s3)
}

Po utworzeniu ciągu znaków jego zawartość nie może być zmieniona. Następująca operacja jest nielegalna i spowoduje błąd kompilacji:

s := "witaj"
s[] = 'W` // Błąd kompilacji: zawartość ciągu znaków jest niemutowalna

5.3 Operacje na łańcuchach znaków

Łańcuchy znaków są bardzo powszechne i ważne w programowaniu. Język Go zapewnia bogaty zestaw wbudowanych funkcji do manipulacji łańcuchami znaków. Oto kilka często używanych operacji.

5.3.1 Konkatenacja łańcuchów znaków

W języku Go można użyć operatora plus (+) do konkatenacji łańcuchów znaków, co jest najprostszą metodą. Dodatkowo, podczas częstego łączenia wielu łańcuchów znaków zaleca się użycie strings.Builder dla lepszej wydajności.

package main

import (
    "fmt"
    "strings"
)

func main() {
    // Łączenie łańcuchów znaków za pomocą +
    hello := "Cześć, "
    world := "Świecie!"
    wynik := hello + world
    fmt.Println(wynik) // Wynik: Cześć, Świecie!

    // Łączenie łańcuchów znaków za pomocą strings.Builder
    var sb strings.Builder
    sb.WriteString("Cześć, ")
    sb.WriteString("Świecie!")
    fmt.Println(sb.String()) // Wynik: Cześć, Świecie!
}

5.3.2 Podział ciągów znaków

Podział ciągów znaków można wykonać przy użyciu funkcji strings.Split, która dzieli ciąg znaków na fragmenty na podstawie określonego separatora.

package main

import (
    "fmt"
    "strings"
)

func main() {
    // Zdefiniowanie ciągu znaków
    zdanie := "Go to język programowania typu open source"

    // Podział ciągu znaków po spacji
    slowa := strings.Split(zdanie, " ")
    for _, slowo := range slowa {
        fmt.Printf("%s\n", slowo)
    }
    // Wynik:
    // Go
    // to
    // język
    // programowania
    // typu
    // open
    // source
}

5.3.3 Dostęp do indeksu

W Go, ciąg znaków to niezmienne ciąg bajtów. Możesz użyć indeksowania do uzyskania dostępu do określonych bajtów w ciągu znaków. Jednak należy zauważyć, że ponieważ ciąg znaków może zawierać znaki wielobajtowe (takie jak znaki kodowane w UTF-8), bezpośrednie indeksowanie może nie zwrócić oczekiwanego pojedynczego znaku.

package main

import "fmt"

func main() {
    s := "Cześć, 世界"
    for i := 0; i < len(s); i++ {
        fmt.Printf("%d: %x\n", i, s[i])
    }
    // Uwaga: zostanie wyświetlona szesnastkowa reprezentacja bajtów, a nie znaków
}

Aby iterować przez ciąg znaków po znakach, można użyć pętli range.

package main

import "fmt"

func main() {
    s := "Cześć, 世界"
    for index, wartoscRune := range s {
        fmt.Printf("%d: %U '%c'\n", index, wartoscRune, wartoscRune)
    }
    // Wynik: indeks, kodowanie Unicode i sam znak dla każdego znaku
}

5.3.4 Pobieranie długości

Funkcja len może uzyskać długość ciągu znaków, czyli długość podstawowego ciągu bajtów. Dla ciągów UTF-8, jeśli chcesz uzyskać liczbę znaków (runes), powinieneś użyć funkcji utf8.RuneCountInString. To pozwala poprawnie obsługiwać znaki wielobajtowe.

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    s := "Cześć, 世界"
    fmt.Println("Długość bajtów:", len(s)) // Wyświetl długość bajtów
    fmt.Println("Długość znaków:", utf8.RuneCountInString(s)) // Wyświetl długość znaków
}

Z powyższego przykładu wynika, że język Go dostarcza bogate funkcje biblioteki do operacji na ciągach znaków, co ułatwia wykonanie różnych zadań związanych z przetwarzaniem ciągów znaków. Podczas pisania kodu ważne jest zwrócenie uwagi na różnicę między bajtami a znakami, zwłaszcza przy pracy z zestawami znaków niewykorzystujących kodowania ASCII.