Указатель, по сути, хранит адрес каких то данных (переменной, структуры, слайса и т.д.).
Иными словами он "указывает" на область данных.
Чтобы понять как им пользоваться - сначала нужно понять для чего он вообще используется.
Представьте что у вас есть структура, которая содержит много разных полей, в которых содержится большой объём данных.
Например:
type BigStruct struct {
field1 int
filed2 string
field3 uint
field4 []byte
...
field50 string
}
Предположим, что после создания этой структуры и заполнения всех её полей она занимает в памяти 300мб.
Если вы сделаете функцию, которая будет принимать такую структуру как агрумент, например вот так
func Report(s BigStruct)
то при каждом вызове этой функции вся структура (300мб) каждый раз будут копироваться.
Пример:
s := BigStruct{}
// заполняем поля
Report(s)
Чтобы избежать такой мега нагрузки - можно передавать не копию данных, а указатель, т.е. адрес в памяти, где хранится сама структура.
Для этого нужно объявить агрумент функции как указатель, т.е. ставим *.
func Report(s *BigStruct)
А код уже будет выглядеть вот так.
s := BigStruct{}
// заполняем поля
Report(&s) // тут добавился & - берём адрес структуры, а не саму структуру
Или второй вариант
// создаём переменную s сразу с типом указатель на BigStruct
s := &BigStruct{}
// заполняем поля
Report(s) // поскольку s уже является указателем - & тут не нужен
В общем * используется:
- когда нужно объявить переменную
var s *BigStruct
- когда нужно прочитать/записать значение, которое храниться по адресу указателя
var i *int
i = new(int)
*i = 10 // пишем значение
fmt.Printf("i: %v\n", i)
fmt.Printf("*i: %v\n", *i)
Вывод будет примерно таким
i: 0xc0000160d8 (это адрес памяти, где лежит значение переменной i)
*i: 10 (а это её значение)
& (амперсанд) используется когда нужно получить адрес переменный.
Еще один вариант применения - если нужно иметь возможность модифицировать данные у параметра функции. Если нужны примеры - дайте знать, я напишу.