Указатель хранит адрес переменной. Указатель на указатель хранит адрес другого указателя. Каждый уровень добавляет одну «звёздочку» к типу и одно разыменование для доступа к значению.
Пример:
a := 100
var b *int = &a // b хранит адрес a
var c **int = &b // c хранит адрес b
Цепочка в памяти выглядит так:
c → b → a → 100
Разыменование:
fmt.Println(a) // 100 — исходное значение
fmt.Println(b) // 0xc000014090 — адрес a
fmt.Println(*b) // 100 — значение a через b
fmt.Println(c) // 0xc00000e028 — адрес b
fmt.Println(*c) // 0xc000014090 — адрес a через c
fmt.Println(**c) // 100 — значение a через c
Каждая
* это один шаг по цепочке адресов.**c позволяет не только читать значение a, но и менять сам указатель b, то есть переключать его на другую переменную. Именно это делает двойной указатель полезным, а не просто экзотикой.🐸 Библиотека Go для собеса