ГОЛАНГ

Указатели в Go

Указатель - это переменная, в которой хранятся данные адреса памяти, на которые ссылается другая переменная. Указатели могут изменять данные, на которые они указывают.

Прежде чем мы начнем говорить об указателях, давайте узнаем кое-что о hexadecimal числах. Шестнадцатеричное число - это число с основанием 16. Если вы веб-разработчик, то пользуетесь ими уже давно, потому что в основном; цвета представлены в шестнадцатеричном формате. Например, белый цвет представлен как #FFFFFF, а черный - как #000000.

В Go вы можете сохранить шестнадцатеричное число в переменной, и Go предоставляет для этого буквальное выражение. Если число начинается с 0x, это шестнадцатеричное число.

Из приведенного выше примера видно, что значения, представленные в шестнадцатеричной системе, сохраняются в десятичной системе с типом данных int.

Но почему мы изучаем шестнадцатеричные числа, когда говорим об указателях. Что ж, давайте сначала поговорим об адресе памяти.

Когда вы объявляете переменную и предоставляете некоторое значение (данные), среда выполнения Go выделяет некоторую память для значения в ОЗУ и в зависимости от типа данных будет выделять определенный размер памяти для хранения этого значения.

Эта память будет иметь некоторый адрес (почтовый адрес), чтобы Go мог найти значение этой переменной по запросу. Эти адреса памяти представлены в шестнадцатеричных значениях.

☛ Как получить доступ к адресу памяти переменной?

Для доступа к значению адреса (данные), представленному переменной, Go предоставляет оператор & (амперсанд), который используется перед переменной. имя. Таким образом, &variable_name expression возвращает адрес памяти для значения (data), на которое ссылается variable_name переменная.

Мы видели это на slice уроке, когда пытались доказать, что два среза могут ссылаться на значения из одного и того же массива. В приведенном выше примере с помощью оператора & мы нашли адрес памяти переменной a, b и c.

☛ Что такое указатель?

Указатель - это переменная, которая указывает на место в памяти другой переменной (фактически на значение, на которое ссылается переменная) .

Как мы видели ранее, мы можем сохранить шестнадцатеричное значение в переменной, но внутри оно сохраняется как десятичное значение типа int. Но есть загвоздка.

Даже если вы можете сохранить адрес памяти (шестнадцатеричное число) в переменной, это не указатель или не указывает на место в памяти другой переменной. Это просто ценность, и он понятия не имеет, что эта ценность означает.

Вот тут-то и пригодится указатель. Указатель - это просто переменная, но особого вида и особого типа данных.

Указатель также сохраняет адрес памяти, но он знает, где эта память расположена в ОЗУ и как получить значение, хранящееся в этом адресе памяти. Он может выполнять с ним различные операции, например, считывать значение, хранящееся по адресу памяти, или записывать новое значение.

В отличие от сохранения шестнадцатеричного значения в переменной, имеющей тип int, указатель имеет тип данных *int, если он указывает на адрес памяти данных int и *string, если он указывает на адрес памяти string данных.

Синтаксис для создания или определения указателя: var p *Type, где Type - это тип данных, на которое он будет указывать значение (данные). Давайте создадим простой указатель.

В приведенном выше примере мы создали указатель pa, который указывает на данные типа int, но, поскольку мы не присваиваем ему никакого начального значения, его нулевое значение равно nil. Указатель имеет значение nil, потому что в настоящий момент он не указывает ни на какие данные (значение) в ОЗУ.

Итак, давайте создадим переменную типа int и укажем на нее pa.

В приведенном выше примере мы создали переменную a и присвоили начальное значение 1. Go сохранит целое число 1 где-нибудь в ОЗУ. Затем мы создали указатель pa, который может указывать на значение int.

Позже мы присвоили адрес памяти переменной a (ее значение на самом деле) указателю pa, используя выражение pa = &a. Вышеупомянутая программа также может быть написана с сокращенным форматом присвоения переменных.

В сокращенном формате Go будет интерпретировать, что мы пытаемся создать указатель, потому что мы назначаем адрес памяти переменной (с помощью оператора &) переменной, которую пытаемся создать.

При выводе значения pa он возвращает адрес памяти, на который указывает. Кроме того, тип данных pa - *int, что означает, что это указатель, указывающий на данные типа int.

Однако в нем не указывается явно, на какую переменную или данные он указывает. Но он может найти данные в этом адресе памяти.

☛ Разыменование указателя

Чтобы узнать значение (data) указателя, на которое он указывает, нам нужно использовать оператор *, также называемый оператором разыменования, который, если поместить его перед переменной-указателем ( как оператор & для получения адреса памяти), он возвращает данные в этой памяти.

☛ Изменение значения переменной с помощью указателя

Как мы видели в предыдущем примере, мы можем прочитать данные в том месте памяти, на которое указывает указатель, но мы также можем изменить ( write) значение в этой ячейке памяти.

Как видно из приведенного выше примера, синтаксис *pa считывает значение из области памяти, на которую указывает указатель, но то же выражение можно использовать для записи нового значения в той же области памяти.

Если вам интересно, почему изменилось значение переменной a. Это связано с тем, что новое значение было записано по адресу памяти, на который ссылается переменная a. Это доказывает, что указатели очень эффективны.

💡 Разница между переменной и указателем заключается в том, что переменная хранит значение по адресу памяти и указатель указывает на адрес памяти.

☛ Функция new

Go предоставляет new встроенную функцию, которая выделяет память и возвращает указатель на эту память. Синтаксис функции new следующий.

func new(Type) *Type

Первый аргумент функции new - это тип данных, а возвращаемое значение этой функции - указатель на этот тип данных. Эта функция выделит некоторую память, запишет нулевое значение Type в эту ячейку памяти и вернет указатель на эту ячейку памяти.

Ожидали ли вы, что значение (данные) в ячейке памяти, возвращаемое функцией new, будет nil? Ну, нулевое значение указателя равно nil, что означает, что указатель не указывает на какую-либо память, но когда указатель указывает его на ячейку памяти, память не может быть пустой, она должна содержать некоторые данные.

Go сохраняет нулевое значение типа данных, переданных в функцию new, и возвращает его адрес в памяти. Следовательно, если вас интересует только указатель, вы можете использовать функцию new вместо создания новой переменной, а затем указателя, который указывает на значение переменной ( как мы видели ранее).

💡 Следовательно, определение «Указатель - это переменная, которая указывает адрес в памяти другой переменной» строго неверно. «Указатель - это переменная, которая указывает адрес памяти» более точно.

☛ Передача указателя на функцию

Как и переменная, вы можете передать указатель на функцию. Это можно сделать двумя способами. Либо создайте указатель, а затем передайте его функции, либо просто передайте адрес переменной.

В приведенной выше программе мы передали указатель pa в качестве аргумента функции changeValue. Этот указатель указывает на значение переменной a. Следовательно, внутри функции мы можем записать новое значение по адресу памяти, указанному указателем pa, который также изменяет значение переменной a.

Вместо того, чтобы использовать этот длинный подход, мы можем сократить приведенный выше пример, передав вместо него адрес переменной a в качестве аргумента. Параметр changeValue функции p теперь является указателем.

💡 Два указателя, указывающие на одно и то же значение, одинаковы.

В приведенной выше программе синтаксис аргумента функции changeValue указывает Go, что мы ожидаем указатель, особенно часть *int (объявление типа) аргумента p.

Вы можете передать указатель на составной тип данных, например array, на функцию.

Мы также могли бы написать указанную выше программу, используя сокращенный синтаксис Go для доступа к данным с помощью указателя на массив.

Но передача указателя на массив в качестве параметра функции не является идиоматической для Go. Мы должны предпочесть срезы для этой функции. Как мы видели в уроке срезы, мы можем передать срез в качестве аргумента функции и эта функция может изменять значения внутри среза.

☛ Указатель арифметики

В отличие от C языка, где указатель может быть увеличен или уменьшен, Go не поддерживает арифметику указателя.