Вызов по значению и вызов по ссылке — это два разных способа передачи аргументов функции.

Вызов по значению

Давайте разберемся, что такое вызов по значению.

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

func callByValue(a, b int) (int, int) {
  temp := a
  a = b
  b = temp
  return a, b
}

Вызов по ссылке

Давайте разберемся, что такое вызов по ссылке.

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

Стоит отметить, что «вызов по ссылке» может иметь разные вариации, например, «передача по ссылке» и «передача по указателю». При «переходе по ссылке» ссылка на аргумент передается напрямую, а при «переходе по указателю» передается указатель на аргумент. Эти варианты похожи друг на друга и часто используются взаимозаменяемо.

func callByRef(c, d *int) (int, int) {
 temp := *c
 *c = *d
 *d = temp
 return *c, *d
}

Полный код

package main

import (
 "fmt"
)

func main() {
 fmt.Println("\n=========================")
 fmt.Println("Call By Value Code Starts")
 fmt.Println("=========================")
 x, y := 10, 20

 fmt.Printf("Before swap: x = %d, y = %d\n", x, y)
 x1, y1 := callByValue(x, y)
 fmt.Printf("In function the changed values of x is: %d & y is: %d \n", x1, y1)
 fmt.Printf("After swap: x = %d, y = %d\n", x, y)
 fmt.Println("\n=========================")
 fmt.Println("Call By Value Code Ends")
 fmt.Println("=========================")

 fmt.Println("\n=============================")
 fmt.Println("Call By Reference Code Starts")
 fmt.Println("=============================")
 u, v := 10, 20

 fmt.Printf("Before swap: u = %d, v = %d\n", u, v)
 u1, v1 := callByRef(&u, &v)
 fmt.Printf("In function the changed values of x is: %d & y is: %d \n", u1, v1)
 fmt.Printf("After swap: u = %d, v = %d\n", u, v)
 fmt.Println("\n=============================")
 fmt.Println("Call By Value Reference Ends")
 fmt.Println("=============================")
}

func callByValue(a, b int) (int, int) {
 temp := a
 a = b
 b = temp
 return a, b
}

func callByRef(c, d *int) (int, int) {
 temp := *c
 *c = *d
 *d = temp
 return *c, *d
}

Результат

=========================
Call By Value Code Starts
=========================

Before swap: x = 10, y = 20
In function the changed values of x is: 20 & y is: 10 
After swap: x = 10, y = 20

=========================
Call By Value Code Ends
=========================

=============================
Call By Reference Code Starts
=============================

Before swap: u = 10, v = 20
In function the changed values of x is: 20 & y is: 10 
After swap: u = 20, v = 10

=============================
Call By Value Reference Ends
=============================

Теперь давайте разберемся, почему нас смущает передача по ссылке в go.

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

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

Передача по ссылке. В этом механизме функции передается ссылка или указатель на исходное значение аргумента. Затем функция может использовать эту ссылку или указатель для изменения исходного значения аргумента. Обычно это делается в языках, поддерживающих указатели или ссылки, таких как C++ или Java.

Вызов по ссылке: в этом механизме функции передается адрес исходного значения аргумента. Затем функция может использовать этот адрес для изменения исходного значения аргумента. Это конкретная реализация передачи по ссылке, которая используется в некоторых языках, таких как Pascal.

В Go нет понятия «ссылочных переменных», как в некоторых других языках программирования, таких как C++ или Java.

В Go все значения передаются по значению. Когда вы передаете переменную функции или присваиваете ее другой переменной, создается копия ее значения. Это означает, что изменение значения копии не влияет на исходную переменную.

Однако в Go есть указатели — переменные, в которых хранится адрес памяти другой переменной. Вы можете думать об указателе как о «ссылке» на другую переменную, но важно отметить, что указатели сами по себе являются переменными, а не ссылками.

В Go вы можете объявить указатель, используя символ * перед именем типа, например:

var x int = 42
var p *int = &xgo

p здесь — указатель на целое число, и он инициализируется адресом переменной x. Затем вы можете использовать оператор * для доступа к значению, на которое указывает p, например:

fmt.Println(*p) // 42

Вы также можете изменить значение, на которое указывает p, разыменовав его с помощью оператора * и присвоив новое значение, например:

*p = 123
fmt.Println(x) // 123

Таким образом, хотя в Go нет «ссылочных переменных» в том же смысле, что и в некоторых других языках, вы можете использовать указатели для достижения аналогичной функциональности.

См. приведенный выше раздел Вызов по ссылке.

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

Интересно, что Go карта и каналы являются справочными типами.

Ссылочный тип — это тип, значение которого представлено адресом памяти. Когда вы присваиваете ссылочный тип переменной или передаете ее в качестве аргумента функции, вы фактически передаете ссылку на область памяти, где хранится значение, а не копию самого значения.

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

package main

import (
 "fmt"
)

func main() {
 m := make(map[string]int)
 m["a"] = 1
 m["b"] = 2
 fmt.Println(m) // map[a:1 b:2]
 m2 := m
 m2["c"] = 3
 fmt.Println(m2) // map[a:1 b:2 c:3]
 fmt.Println(m)  // map[a:1 b:2 c:3]
}

В этом примере m — это карта, содержащая две пары ключ-значение. Затем мы назначаем m новой переменной m2 и изменяем m2, добавляя новую пару "ключ-значение". Поскольку m и m2 относятся к одной и той же базовой структуре данных, изменение m2 также влияет на m.

package main

import (
 "fmt"
)

func main() {
 m := make(map[string]int)
 m["a"] = 1
 m["b"] = 2
 fmt.Println(m) // map[a:1 b:2]
 m2 := m
 m2["c"] = 3
 fmt.Println(m2) // map[a:1 b:2 c:3]
 fmt.Println(m)  // map[a:1 b:2 c:3]
}

В этом примере мы создаем канал ch типа int с помощью функции make. Затем мы запускаем процедуру go, которая отправляет значение 42 в канал ch. Мы используем оператор канала ⇽, чтобы получить значение из канала и распечатать его.

Аналогичным образом мы создаем еще одну переменную anotherCh и назначаем ее ch. Это означает, что anotherCh теперь относится к тому же каналу, что и ch.

Мы запускаем еще одну процедуру go, которая отправляет значение 100 в другой канал. Затем мы используем оператор канала ⇽, чтобы получить значение от ch (не другойCh) и распечатать его. Поскольку и ch, и anotherCh относятся к одному и тому же каналу, значение, отправляемое в anotherCh, принимается из ch.

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

fmt.Println(anotherCh) //0xc000020180
fmt.Println(ch) //0xc000020180

Без оператора стрелки (⇽) он печатает адреса переменных и, к удивлению, они одинаковы.