эта статьяНачалось вмой блог, если это полезно, пожалуйста, поставьте лайк и соберите его, чтобы его могли увидеть больше друзей.
Сегодня попробуйте поговорить о ссылках в Go.
Причина, по которой я хочу поговорить об этом, заключается в том, что, с одной стороны, у меня была некоторая путаница в отношении концепции раньше, и я хотел разобраться, а с другой стороны, это было потому, что многие люди сомневались в цитировании. Я часто вижу проблемы, связанные с цитированием.
Например, что такое ссылка? В чем разница между ссылкой и указателем? Существуют ли ссылочные типы в Go? Что такое передача по значению? перенос адреса? Пройти по ссылке?
Прежде чем я начал говорить об этом, я уже чувствовал, что это должно быть очень больная тема. Это может быть путаница мышления, вызванная изучением стольких языков, но без их глубокого обобщения.
предисловие
Насколько я понимаю, чтобы полностью понять ссылки, вы должны думать отдельно с точки зрения типа и переноса.
С точки зрения типов типы можно разделить на типы значений и ссылочные типы.Вообще говоря, когда мы говорим о ссылках, мы подчеркиваем типы.
С точки зрения передачи, есть передача значения, передача адреса и передача ссылки.Передача — это концепция, которая упоминается только при вызове функции и используется для обозначения взаимосвязи между фактическими и формальными параметрами.
Взаимосвязь между ссылочными типами и передачей по ссылке, я пытаюсь обобщить в одном предложении, ссылочный тип не обязательно является передачей по ссылке, но передача по ссылке должна быть ссылочным типом.
Эти несколько предложений суммированы мною после использования разных языков, надеюсь, они верны, ведь они не могут ввести в заблуждение других.
что
Когда дело доходит до ссылок, мы должны упомянуть указатели, а указатели и ссылки — общие темы в обучении программированию. Чтобы снизить порог для использования программистами, некоторые языки программирования имеют только ссылки. В некоторых языках существуют указатели, такие как C++ и Go.
Указатель означает адрес.
Когда программа работает, операционная система выделяет для каждой переменной часть памяти для хранения содержимого переменной, и эта часть памяти имеет номер, то есть адрес памяти, который является адресом переменной. Теперь процессор обычно 64-битный, поэтому длина этого адреса обычно составляет 8 байт.
Ссылка, псевдоним блока памяти.
Как правило, цитаты интерпретируются как таковые. Другими словами, ссылка относится к адресу памяти, который действительно очень лаконичен и прост для понимания. Но в Go это предложение не кажется исчерпывающим, и оно будет объяснено позже.
Помимо указателей и ссылок, есть еще одно более широкое понятие — значение. Говоря о передаче переменных, мы часто упоминаем передачу по значению, передачу по адресу и передачу по ссылке. Вообще говоря, в большинстве языков указатели и ссылки являются значениями. В узком смысле ее можно разделить на стоимостную, адресную и справочную.
Довольно запутанно, не так ли?
Я уже чувствую, как у меня выпадают волосы. На самом деле, чтобы полностью разобраться в этих понятиях, нам все равно придется начинать с сути.
значения и указатели
Давайте сначала разберемся в разнице между значениями и указателями.
Вводя указатели в предыдущем разделе, мы упомянули, что следует обратить внимание на разницу между адресом и содержимым переменных. Почему ты говоришь это?
Предположим, мы определяем переменную a типа int следующим образом:
var a int = 1
Содержимое переменной a равно 1, и содержимое переменной хранится в адресе. Как получить адрес переменной? Способ получения адреса переменной в Go такой же, как и в C/C++. код показывает, как показано ниже:
var p = &a
Получить адрес с помощью & . В то же время здесь также определена новая переменная p для хранения адреса переменной a. Тип p — это указатель на int, то есть содержимое переменной p — это адрес переменной a.
Следующий код выводит их адреса:
var a = 1
var p = &a
fmt.Printf("%p\n", p)
fmt.Printf("%p\n", &p)
Мой вывод здесь таков, что адреса переменных a и p равны 0xc000092000 и 0xc00008c010 соответственно. Распределение памяти на данный момент выглядит следующим образом:
Содержимое переменной p — это адрес a, поэтому можно сказать, что указатель — это содержимое других переменных и адрес переменной. Почему вы так долго об этом говорите, ведь при изучении языка C понятие адреса будет подчеркнуто отдельно, а в Go указатели относительно ослаблены, и их тоже относят к типам-значениям.
Суть цитирования
Как упоминалось ранее, ссылка — это псевдоним блока памяти. Буквально кажется, что содержимое переменной ссылочного типа является указателем, так что это кажется правильным. В этом случае я, естественно, подумал о том, как связать ссылки с указателями.
В C/C++ ссылка фактически является синтаксическим сахаром, реализованным компилятором.После сборки операция ссылки будет преобразована в операцию указателя. Это действительно псевдоним, у него есть ощущение препроцессинга определения, но только на уровне сборки. поделиться статьейНизкоуровневая реализация «ссылки» на C++Мне интересно прочитать статью внимательно, я просто прочитал ее примерно.
В других языках суть ссылки в том, что структура содержит указатели, например Python. Следующая структура C является базовой структурой типа списка в Python.
typedef struct {
PyObject_VAR_HEAD
PyObject **ob_item;
Py_ssize_t allocated;
} PyListObject;
Место, где переменная фактически хранит данные,**ob_item
середина. Остальные два члена структуры играют вспомогательную роль.
Теперь кажется, что есть две основные реализации ссылок. Одним из них является идея C++, Ссылка на самом деле является синтаксическим сахаром, облегчающим использование указателей, что согласуется со значением псевдонимов в нашем воображении. Второй похож на реализацию в Python, где базовая структура содержит указатели на фактическое содержимое.
Конечно, могут быть и другие реализации, но ядро должно быть одинаковым.
пройти по ссылке
Когда дело доходит до передачи по ссылке, вы должны упомянуть передачу по значению.Общее определение передачи по значению выглядит следующим образом.
При вызове функции фактический параметр передает свое содержимое формальному параметру путем копирования, а формальный параметр фактически является копией значения фактического параметра.В настоящее время любая операция над формальным параметром в функции предназначена только для копия фактического параметра и не влияет на содержимое исходного значения.
Существует особая форма передачи по значению.Если тип передаваемого параметра является указателем, мы будем называть его передачей по адресу.В языке C существует два выражения: передача по значению и передача по адресу. В глубине передача адреса в C также относится к передаче значения, потому что для типа указателя значение переменной является указателем, то есть переданное значение также является указателем. Причина, по которой язык C делает упор на передачу адресов, заключается в том, что я думаю, что основной низкоуровневый язык C уделяет больше внимания указателям.
Что такое передача по ссылке?
Определение передачи по ссылке, фактический адрес параметра передается формальному параметру в вызове функции, а операция над формальным параметром влияет на фактический параметр, ее можно рассматривать как передачу по ссылке.
Из языков, которые я использовал, PHP и C++ поддерживают передачу по ссылке.
Эталонная реализация Go
Ссылочные типы Go включают slice, map и chan Механизм реализации использует второй метод, упомянутый выше, то есть структура содержит члены-указатели. Все они могут быть инициализированы с помощью встроенной функции make.
Первоначально я хотел опубликовать базовую структуру этих ссылочных типов, но обнаружил, что это будет мешать пониманию предмета этой статьи. Мы смотрим только на структуру срезов, а именно:
// slice
type slice struct {
array unsafe.Pointer
len int
cap int
}
Структура слайса является самой простой и содержит три члена, которые представляют собой адрес базового массива слайса, длину слайса и размер емкости. Похоже ли это на базовую структуру списков Python, упомянутую ранее?
Если вы хотите понять структуру map и chan, вы можете самостоятельно прочитать исходный код go.runtime/slice.go,runtime/map.goиruntime/chan.go.
Если вы не хотите изучать исходный код, рекомендуется прочитать серию статей Рао Да о глубокой расшифровке Go, в том числеГлубокая расшифровка Slice на языке Go,Глубокая расшифровка карты языка Go,Глубоко расшифровать канал языка Go, Поскольку эти статьи очень подробные и очень длинные, их чтение может проверить ваше терпение.
Go передается по значению
Официально в Go существует только передача по значению. Оригинальный текст выглядит следующим образом:
In a function call, the function value and arguments are evaluated in the usual order. After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution. The return parameters of the function are passed by value back to the calling function when the function returns.
Суть в следующем предложении.
After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution.
Смущенный? Сначала я тоже был сбит с толку, разве в Go нет типов указателей и ссылок? Но после прочтения некоторых статей и долгих размышлений, я, наконец, понял это. Ниже я попытаюсь найти разумное объяснение официальному заявлению.
Почему говорят, что в Go нет адреса
На самом деле, этот вопрос был очень четко объяснен ранее. Указатели — это всего лишь особая форма значений. Язык C — это язык очень низкого уровня. Он часто включает некоторые операции с адресами и подчеркивает особый статус указателей. Но для Go указатели были сильно ослаблены, и команда Go может также почувствовать, что нет необходимости подчеркивать статус одних только указателей.
Почему в Go нет передачи по ссылке?
Кто-то может сказать, что в Go есть передача по ссылке, но по определению передачи по ссылке очень легко привести пример в опровержение.
package main
import "fmt"
func update(s []int) {
s[1] = 10
}
func main() {
a := []int{0, 1, 2, 3, 4}
fmt.Println(a)
update(a)
fmt.Println(a)
}
Результат выглядит следующим образом:
[0 1 2 3 4]
[0 10 2 3 4]
Операция над формальным параметром s изменяет значение фактического параметра a , который, кажется, передается по ссылке. Но я хочу сказать, что операция над формальным параметром не означает операцию над элементом формального параметра.
См. пример, приведенный в C++.
void update(int& s) {
s = 10;
printf("s address: %p\n", &s);
}
int main() {
int a = 1;
std::cout << a << std::endl;
printf("a address: %p\n", &a);
update(a);
std::cout << a << std::endl;
}
Результат выполнения следующий:
1
a address: 0x7fff5b98f21c
s address: 0x7fff5b98f21c
10
Операция над s изменяет значение a . Попробуйте тот же код в Go следующим образом:
func update(s []int) {
s[1] = 10
fmt.Printf("%p\n", &s)
}
func main() {
a := []int{0, 1, 2, 3, 4}
fmt.Println(a)
fmt.Printf("%p\n", &a)
update(a)
fmt.Println(a)
}
Результат выглядит следующим образом:
[0 1 2 3 4]
0xc00000c060
0xc000098000
[0 10 2 3 4]
К сожалению, присваивание формальным параметрам не изменяет значение фактического параметра. Исходя из этого, можно сделать вывод, что передача по срезам не является передачей по ссылке. Я предпочитаю этот метод интерпретации, который подходит для моей личной памяти и понимания, и я не знаю, есть ли что-то неправильное.
Кроме того, вводится еще один способ определить, передается ли он по ссылке.
Подтвердите, сравнив адрес формального параметра и фактический параметр.Если два адреса одинаковы, он передается по ссылке, а если они разные, он передается по ссылке. Но поскольку механизм реализации ссылок на C++ и Go отличается, его будет сложнее понять. Мы также можем выбрать запоминание только заключения.
Проверка таким образом очень проста, мы вывели адреса формальных параметров и фактических параметров в приведенных выше примерах C++ и Go, и можем сделать выводы путем сравнения.
Суммировать
В этой статье в основном анализируются ссылки в Go с точки зрения типа ссылки и переноса.
Во-первых, между ссылочными типами и передачей ссылок нет абсолютной связи, я не знаю, сколько людей думают, что ссылочные типы должны передаваться по ссылке. Далее мы обсудим механизм реализации ссылок на различные языки, включая C++, Python и Go.
В конце статьи я объяснил распространенную путаницу, почему Go использует только передачу по значению. Исходя из этого, в документе предлагаются два метода, помогающие определить, поддерживает ли язык передачу по ссылке.
Связанное Чтение
Почему make(T, args) в Golang возвращает T вместо *T?
Передача параметров в языке Go осуществляется по значению или по ссылке?
Существует ли передача параметра функции по ссылке в Golang?
Справочник по C++, лежащий в основе механизма реализации
The Go Programming Language Specification
Добро пожаловать в мой публичный аккаунт.