Шаг 43.
Язык программирования Go.
Указатели

    На этом шаге рассмотрим понятие указателя в Go.

    Переменная представляет собой небольшой блок памяти, содержащий значение. Переменные, созданные с помощью объявлений, идентифицируются по имени, например х, но многие переменные идентифицируются только с помощью выражений, таких как x[i] или x.f. Все эти выражения считывают значение переменной, за исключением ситуаций, когда они находятся слева от оператора присваивания; в этом случае переменной присваивается новое значение.

    Значение указателя представляет собой адрес переменной. Таким образом, указатель — это местоположение в памяти, где хранится значение. Не всякое значение имеет адрес, но его имеет любая переменная. С помощью указателя можно считывать или изменять значение переменной косвенно, не используя ее имя, если оно у нее есть.

    Если переменная объявлена как var х int, выражение ("адрес х") дает указатель на целочисленную переменную, т.е. значение типа *int, который произносится как "указатель на int". Если это значение называется р, мы говорим "р указывает на х" или, что то же самое, что "р содержит адрес х". Переменная, на которую указывает р, записывается как . Выражение дает значение этой переменной, но поскольку выражение обозначает переменную, оно может использоваться и в левой части присваивания, и в этом случае присваивание обновляет данную переменную.

х := 1
р := &х // р имеет тип *int и указывает на х
fmt.Println(*p) // "1" 
*р = 2 // Эквивалентно присваиванию х = 2 
fmt.Println(x) // "2"

    Каждый компонент переменной составного типа — поле структуры или элемент массива — также является переменной, а значит, имеет свой адрес.

    Переменные иногда описываются как адресуемые значения. Выражения, которые описывают переменные, являются единственными выражениями, к которым может быть применен оператор получения адреса &.

    Нулевое значение указателя любого типа равно nil. Проверка р ! = nil истинна, если р указывает на переменную. Указатели можно сравнивать; два указателя равны тогда и только тогда, когда они указывают на одну и ту же переменную или когда они оба равны nil.

var х, у int
fmt.Println(&x == &х, &x == &y, &x == nil) // true false false

    Функция совершенно безопасно может вернуть адрес локальной переменной. Например, в приведенном ниже коде локальная переменная v, созданная этим конкретным вызовом f, будет существовать даже после возврата из функции, и указатель р будет по-прежнему указывать на нее:

var р = f() func f() *int { v := 1 return &v
}

    Все вызовы f возвращают различные значения:

fmt.Println(f() == f()) // false

    Поскольку указатель содержит адрес переменной, передача указателя в функцию в качестве аргумента делает возможным обновление переменной, косвенно переданной в функцию. Например, приведенная ниже функция увеличивает переменную, на которую указывает ее аргумент, и возвращает новое значение переменной, так что оно может использоваться в выражении:

func incr(p *int) int {
*p++ // Увеличивает значение, на которое указывает р;
// не изменяет значение р return *р
}

v := 1
incr(&v) // Побочное действие: v теперь равно 2 
fmt.Println(incr(&v)) // 3 (и v становится равным 3)  

    Всякий раз, получая адрес переменной или копируя указатель, мы создаем новые псевдонимы, или способы идентификации одной и той же переменной. Например, — это псевдоним для v. Псевдонимы-указатели полезны, потому что позволяют нам получить доступ к переменной без использования ее имени, но чтобы найти все инструкции, которые обращаются к переменной, необходимо знать все ее псевдонимы. Это не просто указатели, которые создают псевдонимы; псевдонимы также создаются, когда мы копируем значения других ссылочных типов (наподобие срезов, отображений и каналов), а также структуры, массивы и интерфейсы, которые содержат эти типы.

    На следующем шаге рассмотрим массивы в Go.


Предыдущий шаг Содержание Следующий шаг