Proměnné ukládají hodnoty na unikátní místa v paměti počítače, přičemž každé místo má odpovídající paměťovou adresu. Ukazatel je speciální typ proměnné, který uchovává paměťovou adresu jiné proměnné.
Základy ukazatelů
petName := "Misty"
pointer := &petName

fmt.Println(petName)  // Výstup: Misty
fmt.Println(pointer)  // Výstup: (paměťová adresa, např. 0xc000020070)
Operátor Ampersand (&): Používá se k získání paměťové adresy proměnné
Operátor Hvězdička (*): Používá se k přístupu k hodnotě uložené na paměťové adrese, na kterou ukazatel odkazuje
Typová bezpečnost a přiřazování ukazatelů
Ukazatele v Go jsou typově bezpečné. To znamená, že nelze přiřadit paměťovou adresu jednoho typu ukazateli jiného typu.
var pointer *int
petName := "Misty"
// pointer = &petName // Chyba: nelze použít &petName (typ *string) jako typ *int
Deklarace ukazatelových proměnných
Pro deklaraci ukazatelové proměnné umístěte hvězdičku (*) před typ:
var pointer *string
name := "Misty"
pointer = &name
fmt.Println(name)     // Výstup: Misty
fmt.Println(*pointer) // Výstup: Misty
Práce s nil a ukazateli
Ukazatele lze porovnat s nil. Ukazatel nil znamená, že neodkazuje na žádné místo v paměti. Typy jako slices, maps, interfaces a channels, které interně používají ukazatele, lze také porovnat s nil.
var ptr *string
fmt.Println(ptr == nil) // Výstup: true
⚠️ Pokus o dereferenci ukazatele s hodnotou nil způsobí runtime chybu:
var ptr *int
fmt.Println(*ptr) // panic: runtime error: invalid memory address or nil pointer dereference
Předávání hodnotou vs. referencí
V Go jsou proměnné předávány hodnotou. To znamená, že při předání proměnné funkci je vytvořena její kopie. Změna kopie neovlivní původní proměnnou.
Příklad: Předávání hodnotou
func counter(value int) {
  value++
}

func main() {
  value := 0
  counter(value)
  fmt.Println(value) // Výstup: 0 (nezměněno)
}
Pro úpravu původní proměnné můžete předat ukazatel:
func counterPointer(value *int) {
  *value++
}

func main() {
  value := 0
  counterPointer(&value)
  fmt.Println(value) // Výstup: 1 (změněno)
}
Ukazatele se strukturami
Stejný princip „předávání hodnotou“ platí i pro struktury. Zde je příklad:
type Point struct {
  X int
  Y int
}

func modifyPoint(p Point) {
  p.X++
  p.Y++
}

func main() {
  pt := Point{X: 1, Y: 2}
  modifyPoint(pt)
  fmt.Println(pt) // Výstup: {1 2} - původní struktura zůstává nezměněna
}
Pro úpravu původní struktury předávejte ukazatel:
func modifyPointPointer(p *Point) {
  p.X++
  p.Y++
}

func main() {
  pt := Point{X: 1, Y: 2}
  modifyPointPointer(&pt)
  fmt.Println(pt) // Výstup: {2 3}
}
Správa paměti: Stack vs. Heap
Proměnné v Go jsou obvykle alokovány v zásobníkové paměti (stack), která je efektivní, ale omezená svým rozsahem. Když funkce vrací ukazatel na lokální proměnnou, Go automaticky alokuje proměnnou na haldě (heap), aby byla dostupná i po ukončení funkce.
func getRandomName() *string {
  name := "Olivia"
  return &name
}

func main() {
  olivia := getRandomName()
  fmt.Println(*olivia) // Výstup: Olivia
}
Alokace na haldě umožňuje proměnným přetrvat mimo rozsah funkce, která je vytvořila. Paměť na haldě však může být fragmentována a vyžaduje garbage collection, kterou Go zpracovává automaticky.
Role garbage collectoru v Go
Garbage collector v Go sleduje ukazatele a hodnoty, na které odkazují, a zajišťuje, že paměť je uvolněna, když již není potřeba. Tím eliminuje riziko „visících ukazatelů“ a umožňuje bezpečné vracení ukazatelů z funkcí.
Praktické případy použití ukazatelů
Ukazatele jsou užitečné zejména v případech, jako jsou:
• Vyhýbání se nákladným kopiím velkých struktur
• Implementace propojených datových struktur, jako jsou spojové seznamy nebo stromy
• Sdílení změnitelného stavu mezi funkcemi
Klíčové body
• Použijte operátor & pro získání paměťové adresy proměnné a * pro dereferenci ukazatelů
• Ukazatele jsou typově bezpečné a nemohou odkazovat na nesourodé typy
• Předávání proměnných ukazatelem umožňuje modifikaci původní hodnoty
• Garbage collector v Go zajišťuje bezpečné a efektivní využití paměti