DevGang
Авторизоваться

Указатели в Golang

Это будет очень короткая статья, в которой я расскажу об указателях в языке Go.

Что такое указатели

Указатель - это переменная, которая хранит адрес другой переменной, это обычная переменная, только вместо хранения значения, например, целочисленного значения или строкового значения, вместо этого он сохраняет местоположение адреса памяти этого значения. Под адресом памяти я подразумеваю фактическое расположение переменной в памяти нашего компьютера, и в основном это шестнадцатеричный формат, начинающийся с «0x».

Зачем использовать указатели

Как правило, использование указателей в нужном месте может значительно повысить производительность и эффективность программы. Указатели могут помочь максимизировать пространство в программе: рассмотрим ситуацию, когда вам нужно передать большой объект в функцию, когда вы передаете объект напрямую, компилятор создает копию этого объекта перед его передачей, но с указателями вы можете вместо этого передать адрес этого объекта и использовать его непосредственно в функции, не создавая копию. Кроме того, потому что мы можем получить прямой доступ к ячейке памяти переменной, чтение и обновление выполняются значительно быстрее.
Также с помощью указателей мы можем читать переменные из другой области видимости в программе (при необходимости).

Синтаксис указателя в Golang

Указатели имеют тот же синтаксис в других языках программирования, таких как C, C++ и Golang.
В указателях используются 2 оператора:
1. Оператор (&), известный как оператор адреса
2. Оператор (*), известный как оператор разыменования

Давайте рассмотрим их

Чтобы объявить переменную в Golang, мы делаем var name string, объявляя переменную «name», мы можем присвоить значение переменной, выполнив name = "sammy"

Но в случае указателя мы объявляем переменную-указатель, подобную этой var name *string, используя оператор (*).

Примечание: все переменные-указатели имеют нулевое значение nil, поэтому, если мы попробуем это:

var name *string
var age *int
fmt.Println(name) // output - nil
fmt.Println(age) // output - nil

Теперь, когда мы объявили указатель, давайте присвоим ему значение, для этого мы используем оператор адреса (&) &variable_name в полном примере ниже:

var pname *string
var my_name = "Sam"
// assign the address of my_name to the pname pointer variable
pname = &my_name
fmt.Println(pname) // output - 0xc000010250 (in my case)

Вывод «pname» выше — это фактический адрес памяти переменной «my_name».
Чтобы получить значение адреса любой переменной, просто добавьте оператор адреса (&) в качестве префикса, fmt.Println(&variable_name)

Поскольку в golang есть разные методы объявления переменных, мы можем сделать это и с указателями.

age := 12
p_age := &age // pointer to age variable
fmt.Println(p_age) // output - 0xc0000b8000
// OR
var user_age *int = &age
fmt.Println(user_age) // output - 0xc0000b8000
// OR
p := new(int) // pointer variable of int type
age := 23
p = &name // assign address value of age to p

Теперь мы можем объявить указатель, присвоить ему значение и распечатать адрес, но как нам получить доступ к значению самого указателя? то есть значение, которое хранится в адресе памяти.
Мы делаем это, добавляя оператор разыменования (*) в качестве префикса к переменной-указателю fmt.Println(*pointer_variable), да, тот же самый оператор, который мы использовали для объявления переменной-указателя. Взгляните на следующий пример.

color := "purple"
p_color := &color
// to access the value stored at p_color
fmt.Println(*p_color) // output - purple

Подводя итог всему:

// declaring a normal varible
var x int = 10
// declaration a pointer
var p *int
// assigning value to the pointer 
p = &x
fmt.Println("Value stored in x = ", x) 
fmt.Println("Address of x = ", &x) 
fmt.Println("Value of variable p = ", p)
fmt.Println("Variable value in p = ", *p)
//Output
Value stored in x =  10
Address of x =  0xc000018030
Value of variable p =  0xc000018030
Variable value in p =  10

Применение указателей

Давайте подумаем, как мы можем писать более эффективные программы с указателями. Во-первых, давайте взглянем на эту простую функцию ниже, которая принимает 2 числа и возвращает их сумму:

func main() {
 result := add(2, 3)
 fmt.Println(result)
}
func add(a, b int) int {
 return a + b
}

Компилятор создаст копию «a» и «b» для использования внутри функции, теперь рассмотрим сценарий, в котором вам нужно передать большие данные, такие как значение байтов большого файла, создав новую переменную для использования в функции. может занять некоторое время (иногда едва заметное), но с помощью указателей мы можем просто указать адрес этой переменной и выполнить над ней операцию функции. Взгляните на другую версию вышеуказанной функции:

func main() {
 a := 3
 b := 2
 result := add(&a, &b)
 fmt.Println(*result)
}
func add(a, b *int) *int {
 sum := *a + *b
 return &sum
}

Здесь мы передали адреса переменных, поэтому компилятору не нужно будет делать копию этой переменной при их использовании, нам просто нужно получить доступ к переменной с помощью оператора разыменования для выполнения над ними операций (Примечание: вы не можете выполнять операции прямо по адресу). Кроме того, я вернул адрес переменной «sum» из функции вместо самой переменной, это также эффективно, если функции нужно вернуть большие данные.

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

type User struct {
 Name string
 Age  int
}
func main() {
 data := User{
  Name: "sam",
  Age:  21,
 }
 updateAge(&data)
 fmt.Println(data)
}
func updateAge(userData *User) {
 userData.Age += 1
}
// output
{sam 22}

Функция «updateAge» напрямую обновляет переданную ей структуру User, обращаясь к адресу.
Более эффективным способом написания этого мог бы быть следующий приемник метода:

type User struct {
 Name string
 Age  int
}
func main() {
 data := User{
  Name: "sam",
  Age:  21,
 }
 data.UpdateAge()
 fmt.Println(data)
}
func (u *User) UpdateAge() {
 u.Age += 1
}
Generally, pointers are useful when:

we have to move a large amount of data around e.g into or out of a function
we want a method to modify its receiver
Lastly, I want to mention that it is possible to have a pointer pointing to another pointer, for example:

x := 21
p_x := &x
p_px := &p_x //hold the address of the pointer p_x
fmt.Println(x, p_x, p_px)
fmt.Println("value in p_x: ", *p_x)
fmt.Println("value in p_px: ", **p_px)
// output 
21 0xc000018030 0xc00000e028
value in p_x:  21
value in p_px:  21
Consider exploring pointers in your Golang programs.


Как правило, указатели полезны, когда:

  1. нам нужно переместить большой объем данных, например, в функцию или из нее
  2. мы хотим, чтобы метод модифицировал получателя

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

x := 21
p_x := &x
p_px := &p_x //hold the address of the pointer p_x
fmt.Println(x, p_x, p_px)
fmt.Println("value in p_x: ", *p_x)
fmt.Println("value in p_px: ", **p_px)
// output 
21 0xc000018030 0xc00000e028
value in p_x:  21
value in p_px:  21
#Golang
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

Vladimir Shaitan - Видео блог о frontend разработке и не только

Посмотреть