Спецификация языка программирования Go полезна.
Пламенный привет посетителям этой страницы, пришедшим из социальных сетей, да и всем остальным тоже!
В апреле 2021-го года наблюдал удивительное явление: обильный поток посетителей из 4-х социальных сетей. В связи с этим настоятельно рекомендую всем неоднократно и регулярно посещать сайт rtbsm.ru — там в общих чертах изложена Российская Теннисная Балльная Система Марии (Шараповой).
Приглашаю всех полюбоваться на Фото и Видео красавицы Марии — надеюсь, что Вы поделитесь адресом сайта rtbsm.ru с друзьями и знакомыми.
Главная проблема — известить Марию, чтобы она лично как можно скорее заявила на весь мир о РТБСМ.
Спецификация языка программирования Go может пригодиться при освоении языка Go, хотя проще, пожалуй, учебники языка программирования Go.
Привожу информацию со страницы https://go.dev/ref/spec :
Спецификация языка программирования Go
Языковая версия go1.23 (13 июня 2024 г.)
Введение ¶
Это справочное руководство по языку программирования Go. Версию до Go1.18, без дженериков, можно найти здесь (). Для получения дополнительной информации и других документов см. go.dev ().
Go — это язык общего назначения, разработанный с учетом системного программирования. Он строго типизирован, имеет сборщик мусора и явно поддерживает параллельное программирование. Программы создаются из пакетов , свойства которых позволяют эффективно управлять зависимостями.
Синтаксис компактен и прост для анализа, что позволяет легко проводить анализ с помощью автоматических инструментов, таких как интегрированные среды разработки.
Обозначение ¶
Синтаксис задан с использованием варианта расширенной формы Бэкуса-Наура (EBNF):
Синтаксис = {Производство}. Производство = имя_производства "=" [Выражение] "." . Выражение = Термин { "|" Термин } . Термин = Фактор { Фактор } . Фактор = имя_производства | токен [ токен "…" ] | Группа | Параметр | Повторение . Группа = "(" Выражение ")" . Параметр = "[" Выражение "]" . Повторение = "{" Выражение "}" .Продукция — это выражения, составленные из терминов и следующих операторов в порядке возрастания приоритета:
| чередование () группировка [] вариант (0 или 1 раз) {} повторение (от 0 до n раз)Для идентификации лексических (терминальных) токенов используются строчные производственные имена. Нетерминальные токены в CamelCase. Лексические токены заключаются в двойные кавычки
""
или обратные кавычки``
.Форма
a … b
представляет набор символов отa
доb
в качестве альтернатив. Горизонтальное многоточие…
также используется в других местах спецификации для неформального обозначения различных перечислений или фрагментов кода, которые не уточняются дополнительно. Символ…
(в отличие от трех символов...
) не является токеном языка Go.Ссылка в форме [ Go 1.xx ] указывает, что описанная языковая функция (или какой-либо ее аспект) была изменена или добавлена в версии языка 1.xx и, таким образом, требует как минимум этой версии языка для сборки. Подробности см. в связанном разделе в приложении .
Представление исходного кода ¶
Исходный код — текст Unicode, закодированный в UTF-8 . Текст не канонизирован, поэтому одна акцентированная кодовая точка отличается от того же символа, созданного путем объединения акцента и буквы; они рассматриваются как две кодовые точки. Для простоты в этом документе будет использоваться неквалифицированный термин символ для обозначения кодовой точки Unicode в исходном тексте.
Каждая кодовая точка индивидуальна; например, заглавные и строчные буквы — это разные символы.
Ограничение реализации: для совместимости с другими инструментами компилятор может запретить использование символа NUL (U+0000) в исходном тексте.
Ограничение реализации: Для совместимости с другими инструментами компилятор может игнорировать метку порядка байтов в кодировке UTF-8 (U+FEFF), если это первая кодовая точка Unicode в исходном тексте. Метка порядка байтов может быть запрещена в любом другом месте исходного текста.
Персонажи ¶
Для обозначения определенных категорий символов Unicode используются следующие термины:
newline = /* кодовая точка Unicode U+000A */ . unicode_char = /* произвольная кодовая точка Unicode, кроме символа новой строки */ . unicode_letter = /* кодовая точка Unicode, относящаяся к категории «Буква» */ . unicode_digit = /* кодовая точка Unicode, относящаяся к категории «Число, десятичная цифра» */ .В стандарте Unicode 8.0 , раздел 4.5 «Общая категория» определяет набор категорий символов. Go рассматривает все символы в любой из категорий букв Lu, Ll, Lt, Lm или Lo как буквы Unicode, а символы в категории чисел Nd — как цифры Unicode.
Буквы и цифры ¶
Символ подчеркивания
_
(U+005F) считается строчной буквой.буква = буква_юникода | "_" . десятичная_цифра = "0" … "9" . двоичная_цифра = "0" | "1" . восьмеричная_цифра = "0" … "7" . шестнадцатеричная_цифра = "0" … "9" | "A" … "F" | "a" … "f" .Лексические элементы ¶
Комментарии ¶
Комментарии служат документацией программы. Существуют две формы:
- Строчные комментарии начинаются с последовательности символов
//
и заканчиваются в конце строки.- Общие комментарии начинаются с последовательности символов
/*
и заканчиваются первой последующей последовательностью символов*/
.Комментарий не может начинаться внутри руны или строкового литерала или внутри комментария. Общий комментарий, не содержащий новых строк, действует как пробел. Любой другой комментарий действует как новая строка.
Токены ¶
Токены формируют словарь языка Go. Существует четыре класса: идентификаторы , ключевые слова , операторы и знаки препинания , а также литералы . Пробелы , образованные из пробелов (U+0020), горизонтальных табуляций (U+0009), возвратов каретки (U+000D) и новых строк (U+000A), игнорируются, за исключением случаев, когда они разделяют токены, которые в противном случае были бы объединены в один токен. Кроме того, новая строка или конец файла могут вызвать вставку точки с запятой . При разбиении ввода на токены следующим токеном является самая длинная последовательность символов, образующая допустимый токен.
Точки с запятой ¶
Формальный синтаксис использует точки с запятой
";"
в качестве терминаторов в ряде производств. Программы Go могут опускать большинство этих точек с запятой, используя следующие два правила:
- Когда ввод разбивается на токены, точка с запятой автоматически вставляется в поток токенов сразу после последнего токена строки, если этот токен
- идентификатор
- целое число , число с плавающей точкой , мнимое число , руническое число или строковый литерал
- одно из ключевых слов
break
,continue
,fallthrough
, илиreturn
- один из операторов и знаков препинания
++
,--
,)
,]
, или}
- Чтобы сложные операторы могли занимать одну строку, точку с запятой перед закрывающим тегом
")"
или можно опустить"}"
.Для отражения идиоматического использования в примерах кода в этом документе точки с запятой опускаются с использованием этих правил.
Идентификаторы ¶
Идентификаторы именуют программные сущности, такие как переменные и типы. Идентификатор — это последовательность из одной или нескольких букв и цифр. Первый символ в идентификаторе должен быть буквой.
идентификатор = буква { буква | цифра_юникода } .а _x9 ЭтаПеременнаяЭкспортируется αβНекоторые идентификаторы объявлены заранее .
Ключевые слова ¶
Следующие ключевые слова зарезервированы и не могут использоваться в качестве идентификаторов.
break default func интерфейс выбор случай отсрочки перехода к карте структуры chan else goto пакетный переключатель константа проваливается, если тип диапазона продолжить для импорта return varОператоры и знаки препинания ¶
Следующие последовательности символов представляют операторы (включая операторы присваивания ) и знаки препинания [ Go 1.18 ]:
+ & += &= && == != ( ) - | -= |= || < <= [ ] * ^ *= ^= <- > >= { } / << /= <<= ++ = := , ; % >> %= >>= -- ! ... . : &^ &^= ~Целочисленные литералы ¶
Целочисленный литерал — это последовательность цифр, представляющая целочисленную константу . Необязательный префикс задает недесятичную базу:
0b
or0B
для двоичной,0
,0o
, или0O
для восьмеричной, и0x
или0X
для шестнадцатеричной [ Go 1.13 ]. Одиночный символ0
считается десятичным нулем. В шестнадцатеричных литералах буквыa
отf
до представляют значенияA
отF
10 до 15.Для удобства чтения после базового префикса или между последовательными цифрами может стоять символ подчеркивания
_
; такие подчеркивания не изменяют значение литерала.int_lit = decimal_lit | binary_lit | octal_lit | hex_lit . decimal_lit = "0" | ( "1" … "9" ) [ [ "_" ] decimal_digits ] . binary_lit = "0" ( "b" | "B" ) [ "_" ] binary_digits . octal_lit = "0" [ "o" | "O" ] [ "_" ] octal_digits . hex_lit = "0" ( "x" | "X" ) [ "_" ] hex_digits . десятичные_цифры = десятичная_цифра { [ "_" ] десятичная_цифра } . двоичные_цифры = двоичная_цифра { [ "_" ] двоичная_цифра } . восьмеричные_цифры = восьмеричная_цифра { [ "_" ] восьмеричная_цифра } . шестнадцатеричные_цифры = шестнадцатеричная_цифра { [ " _ " ] шестнадцатеричная_цифра } .42 4_2 0600 0_600 0o600 0O600 // второй символ — заглавная буква «О» 0xBadFace 0xПлохое_Лицо 0x_67_7a_2f_cc_40_c6 170141183460469231731687303715884105727 170_141183_460469_231731_687303_715884_105727 _42 // идентификатор, а не целочисленный литерал 42_ // недопустимо: _ должен разделять последовательные цифры 4__2 // недопустимо: только один _ за раз 0_xBadFace // недопустимо: _ должен разделять последовательные цифрыЛитералы с плавающей точкой ¶
Литерал с плавающей точкой — это десятичное или шестнадцатеричное представление константы с плавающей точкой .
Десятичный литерал с плавающей точкой состоит из целой части (десятичных цифр), десятичной точки, дробной части (десятичных цифр) и экспоненциальной части (
e
илиE
с последующим необязательным знаком и десятичными цифрами). Одна из целой части или дробной части может быть опущена; одна из десятичной точки или экспоненциальной части может быть опущена. Значение экспоненты exp масштабирует мантиссу (целую и дробную части) на 10 exp .Шестнадцатеричный литерал с плавающей точкой состоит из префикса
0x
или0X
, целой части (шестнадцатеричные цифры), точки основания, дробной части (шестнадцатеричные цифры) и экспоненциальной части (p
илиP
с последующими необязательным знаком и десятичными цифрами). Одна из целой части или дробной части может быть опущена; точка основания также может быть опущена, но экспоненциальная часть обязательна. (Этот синтаксис соответствует синтаксису, приведенному в IEEE 754-2008 §5.12.3.) Значение экспоненты exp масштабирует мантиссу (целую и дробную части) на 2 exp [ Go 1.13 ].Для удобства чтения после базового префикса или между последовательными цифрами может стоять символ подчеркивания
_
; такие подчеркивания не изменяют буквальное значение.float_lit = decimal_float_lit | hex_float_lit . decimal_float_lit = десятичные_цифры "." [ десятичные_цифры ] [ десятичная_экспонента ] | десятичные_цифры десятичная_экспонента | "." десятичные_цифры [ десятичная_экспонента ] . десятичная_экспонента = ( "e" | "E" ) [ "+" | "-" ] десятичные_цифры . hex_float_lit = "0" ( "x" | "X" ) hex_mantissa hex_exponent . hex_mantissa = [ "_" ] hex_digits "." [ hex_digits ] | [ "_" ] шестнадцатеричные_цифры | "." hex_digits . hex_exponent = ( "p" | "P" ) [ "+" | "-" ] decimal_digits .0. 72.40 072.40 // == 72.40 2.71828 1.е+0 6.67428e-11 1E6 .25 .12345E+5 1_5. // == 15.0 0.15e+0_2 // == 15.0 0x1p-2 // == 0.25 0x2.p10 // == 2048.0 0x1.Fp+0 // == 1.9375 0X.8p-0 // == 0.5 0X_1FFFP-16 // == 0.1249847412109375 0x15e-2 // == 0x15e - 2 (вычитание целых чисел) 0x.p1 // неверно: мантисса не имеет цифр 1p-2 // недопустимо: для экспоненты p требуется шестнадцатеричная мантисса 0x1.5e-2 // неверно: шестнадцатеричная мантисса требует p-экспоненты 1_.5 // недопустимо: _ должен разделять последовательные цифры 1._5 // недопустимо: _ должен разделять последовательные цифры 1.5_e1 // недопустимо: _ должен разделять последовательные цифры 1.5e_1 // недопустимо: _ должен разделять последовательные цифры 1.5e1_ // недопустимо: _ должен разделять последовательные цифрыМнимые литералы ¶
Мнимый литерал представляет мнимую часть комплексной константы . Он состоит из целого числа или литерала с плавающей точкой , за которым следует строчная буква
i
. Значение мнимого литерала — это значение соответствующего целого числа или литерала с плавающей точкой, умноженное на мнимую единицу i [ Перейти 1.13 ]мнимые_числа = ( десятичные_числа | целые_числа | плавающие_числа ) "i" .Для обеспечения обратной совместимости целая часть мнимого литерала, состоящая исключительно из десятичных цифр (и, возможно, символов подчеркивания), считается десятичным целым числом, даже если она начинается с ведущей точки
0
.0я 0123i // == 123i для обратной совместимости 0o123i // == 0o123 * 1i == 83i 0xabci // == 0xabc * 1i == 2748i 0.я 2.71828i 1.е+0и 6.67428e-11i 1E6i .25i .12345E+5i 0x1p-2i // == 0x1p-2 * 1i == 0.25iРунические литералы ¶
Рунный литерал представляет собой руническую константу , целочисленное значение, идентифицирующее кодовую точку Unicode. Рунный литерал выражается одним или несколькими символами, заключенными в одинарные кавычки, как в
'x'
или'\n'
. Внутри кавычек может быть любой символ, кроме новой строки и неэкранированной одинарной кавычки. Одинарный кавычковый символ представляет значение Unicode самого символа, в то время как многосимвольные последовательности, начинающиеся с обратной косой черты, кодируют значения в различных форматах.Самая простая форма представляет собой один символ в кавычках; поскольку исходный текст Go представляет собой символы Unicode, закодированные в UTF-8, несколько байтов в кодировке UTF-8 могут представлять одно целочисленное значение. Например, литерал
'a'
содержит один байт, представляющий литералa
, Unicode U+0061, значение0x61
, в то время как'ä'
содержит два байта (0xc3
0xa4
), представляющие литералa
-dieresis, U+00E4, значение0xe4
.Несколько экранирований обратной косой черты позволяют кодировать произвольные значения как текст ASCII. Существует четыре способа представления целочисленного значения в виде числовой константы:
\x
с указанием ровно двух шестнадцатеричных цифр;\u
с указанием ровно четырех шестнадцатеричных цифр;\U
с указанием ровно восьми шестнадцатеричных цифр и простой обратной косой черты\
с указанием ровно трех восьмеричных цифр. В каждом случае значение литерала — это значение, представленное цифрами в соответствующей базе.Хотя все эти представления приводят к целому числу, у них разные допустимые диапазоны. Восьмеричные экранированные символы должны представлять значение от 0 до 255 включительно. Шестнадцатеричные экранированные символы удовлетворяют этому условию по построению. Экранированные символы
\u
и\U
представляют кодовые точки Unicode, поэтому внутри них некоторые значения являются недопустимыми, в частности, те, что выше0x10FFFF
, и суррогатные половины.После обратной косой черты некоторые односимвольные экранированные последовательности представляют специальные значения:
\a U+0007 оповещение или звонок \b U+0008 возврат на одну позицию \f U+000C подача формы \n U+000A перевод строки или новая строка \r U+000D возврат каретки \t U+0009 горизонтальная табуляция \v U+000B вертикальная табуляция \\ U+005C обратная косая черта \' U+0027 одинарная кавычка (допустимый экран только внутри рунических литералов) \" U+0022 двойная кавычка (допустимо экранирование только внутри строковых литералов)Нераспознанный символ после обратной косой черты в руническом литерале является недопустимым.
rune_lit = "'" ( unicode_value | byte_value ) "'" . unicode_value = unicode_char | little_u_value | big_u_value | escaped_char . byte_value = octal_byte_value | hex_byte_value . octal_byte_value = `\` octal_digit octal_digit octal_digit . hex_byte_value = `\` "x" hex_digit hex_digit . little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit hex_digit . big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit . escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .'а' 'д' '本' '\т' '\000' '\007' '\377' '\x07' '\xff' '\u12e4' '\U00101234' '\'' // рунический литерал, содержащий символ одинарной кавычки 'aa' // неправильно: слишком много символов '\k' // недопустимо: k не распознается после обратной косой черты '\xa' // неправильно: слишком мало шестнадцатеричных цифр '\0' // неверно: слишком мало восьмеричных цифр '\400' // недопустимо: восьмеричное значение больше 255 '\uDFFF' // незаконно: суррогатная половина '\U00110000' // недопустимо: недопустимая кодовая точка UnicodeСтроковые литералы ¶
Строковый литерал представляет собой строковую константу, полученную путем конкатенации последовательности символов. Существует две формы: необработанные строковые литералы и интерпретированные строковые литералы.
Необработанные строковые литералы — это последовательности символов между обратными кавычками, как в
`foo`
. Внутри кавычек может быть любой символ, кроме обратной кавычки. Значение необработанного строкового литерала — это строка, состоящая из неинтерпретируемых (неявно закодированных в UTF-8) символов между кавычками; в частности, обратные косые черты не имеют специального значения, а строка может содержать символы новой строки. Символы возврата каретки (‘\r’) внутри необработанных строковых литералов отбрасываются из необработанного строкового значения.Интерпретируемые строковые литералы — это последовательности символов между двойными кавычками, как в
"bar"
. Внутри кавычек может появляться любой символ, кроме новой строки и неэкранированной двойной кавычки. Текст между кавычками формирует значение литерала, при этом экранированные символы обратной косой черты интерпретируются так же, как и в рунических литералах (за исключением того, что\'
это недопустимо и\"
допустимо), с теми же ограничениями. Трехзначные восьмеричные (\
nnn ) и двузначные шестнадцатеричные (\x
nn ) экранированные символы представляют отдельные байты результирующей строки; все остальные экранированные символы представляют (возможно, многобайтовую) кодировку UTF-8 отдельных символов . Таким образом, внутри строкового литерала\377
и\xFF
представляют один байт значения0xFF
=255, тогда какÿ
,\u00FF
,\U000000FF
и\xc3\xbf
представляют два байта0xc3
0xbf
кодировки UTF-8 символа U+00FF.string_lit = raw_string_lit | interpretation_string_lit . raw_string_lit = "`" { unicode_char | newline } "`" . interpreted_string_lit = `"` { unicode_value | byte_value } `"` .`abc` // то же, что и "abc" `\n \n` // то же самое, что и "\\n\n\\n" "\н" "\"" // то же, что и `"` "Привет, мир!\n" "日本語" "\u65e5本\U00008a9e" "\xff\u00FF" "\uD800" // незаконно: суррогатная половина "\U00110000" // недопустимо: недопустимая кодовая точка UnicodeВсе эти примеры представляют одну и ту же строку:
"日本語" // текст ввода UTF-8 `日本語` // вводимый текст UTF-8 как необработанный литерал "\u65e5\u672c\u8a9e" // явные кодовые точки Unicode "\U000065e5\U0000672c\U00008a9e" // явные кодовые точки Unicode "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" // явные байты UTF-8Если исходный код представляет символ в виде двух кодовых точек, например, в виде комбинированной формы, включающей ударение и букву, результатом будет ошибка при помещении в рунический литерал (это не одна кодовая точка), а при помещении в строковый литерал результат будет выглядеть как две кодовые точки.
Константы ¶
Существуют булевы константы , рунические константы , целочисленные константы , константы с плавающей точкой , комплексные константы и строковые константы . Рунические, целочисленные, с плавающей точкой и комплексные константы вместе называются числовыми константами .
Константное значение представлено руной , целым числом , числом с плавающей точкой , мнимым числом или строковым литералом, идентификатором, обозначающим константу, константным выражением , преобразованием с результатом, являющимся константой, или результирующим значением некоторых встроенных функций, таких как
min
илиmax
примененных к константным аргументам,unsafe.Sizeof
примененных к определенным значениям или примененных к некоторым выражениям , и примененных к комплексной константе и примененных к числовым константам. Булевы значения истинности представлены предопределенными константами и . Предопределенный идентификатор iotacap
обозначает целочисленную константу.len
real
imag
complex
true
false
В общем случае комплексные константы являются формой константного выражения и обсуждаются в этом разделе.
Числовые константы представляют точные значения произвольной точности и не переполняются. Следовательно, не существует констант, обозначающих отрицательный ноль, бесконечность и нечисловые значения IEEE 754.
Константы могут быть типизированными или нетипизированными . Литеральные константы,
true
, , и некоторые константные выраженияfalse
, содержащие только нетипизированные константные операнды , являются нетипизированными.iota
Константе может быть присвоен тип явно с помощью объявления константы или преобразования , или неявно при использовании в объявлении переменной или операторе присваивания или в качестве операнда в выражении . Если значение константы не может быть представлено как значение соответствующего типа, это является ошибкой . Если тип является параметром типа, константа преобразуется в неконстантное значение параметра типа.
Нетипизированная константа имеет тип по умолчанию , который является типом, к которому константа неявно преобразуется в контекстах, где требуется типизированное значение, например, в кратком объявлении переменной , например,
i := 0
где нет явного типа. Тип по умолчанию нетипизированной константы —bool
,rune
,int
,float64
,complex128
илиstring
соответственно, в зависимости от того, является ли она логической, рунической, целочисленной, с плавающей точкой, комплексной или строковой константой.Ограничение реализации: Хотя числовые константы имеют произвольную точность в языке, компилятор может реализовать их с использованием внутреннего представления с ограниченной точностью. При этом каждая реализация должна:
- Представлять целочисленные константы длиной не менее 256 бит.
- Представлять константы с плавающей точкой, включая части комплексной константы, с мантиссой длиной не менее 256 бит и знаковой двоичной экспонентой длиной не менее 16 бит.
- Выдайте ошибку, если не удается точно представить целочисленную константу.
- Выдайте ошибку, если из-за переполнения невозможно представить число с плавающей точкой или комплексную константу.
- Округлите до ближайшей представимой константы, если невозможно представить константу с плавающей точкой или комплексную константу из-за ограничений точности.
Эти требования применяются как к литеральным константам, так и к результатам вычисления константных выражений .
Переменные ¶
Переменная — это место хранения для хранения значения . Набор допустимых значений определяется типом переменной .
Объявление переменной или, для параметров и результатов функции, сигнатура объявления функции или функционального литерала резервирует память для именованной переменной. Вызов встроенной функции
new
или получение адреса составного литерала выделяет память для переменной во время выполнения. Такая анонимная переменная ссылается через (возможно, неявную) косвенную ссылку указателя .Структурированные переменные типа array , slice и struct имеют элементы и поля, к которым можно обращаться индивидуально. Каждый такой элемент действует как переменная.
Статический тип (или просто тип ) переменной — это тип, указанный в ее объявлении, тип, указанный в вызове
new
или составном литерале, или тип элемента структурированной переменной. Переменные типа интерфейса также имеют отдельный динамический тип , который является (не интерфейсным) типом значения, назначенного переменной во время выполнения (если только значение не является предварительно объявленным идентификаторомnil
, который не имеет типа). Динамический тип может изменяться во время выполнения, но значения, сохраненные в переменных интерфейса, всегда можно назначить статическому типу переменной.var x interface{} // x равен nil и имеет статический тип interface{} var v *T // v имеет значение nil, статический тип *T x = 42 // x имеет значение 42 и динамический тип int x = v // x имеет значение (*T)(nil) и динамический тип *TЗначение переменной извлекается путем ссылки на переменную в выражении ; это самое последнее значение , назначенное переменной. Если переменной еще не назначено значение, ее значением является нулевое значение для ее типа.
Типы ¶
Тип определяет набор значений вместе с операциями и методами, специфичными для этих значений. Тип может быть обозначен именем типа , если оно у него есть, за которым должны следовать аргументы типа, если тип является универсальным. Тип также может быть указан с помощью литерала типа , который составляет тип из существующих типов.
Type = TypeName [ TypeArgs ] | TypeLit | "(" Type ")" . TypeName = identifier | QualifiedIdent . TypeArgs = "[" TypeList [ "," ] "]" . TypeList = Type { "," Type } . TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType | SliceType | MapType | ChannelType .Язык заранее объявляет определенные имена типов. Другие вводятся с помощью объявлений типов или списков параметров типов . Составные типы — массивы, структуры, указатели, функции, интерфейсы, срезы, карты и типы каналов — могут быть созданы с использованием литералов типов.
Предопределенные типы, определенные типы и параметры типа называются именованными типами . Псевдоним обозначает именованный тип, если тип, указанный в объявлении псевдонима, является именованным типом.
Булевы типы ¶
Булев тип представляет собой набор булевых значений истинности, обозначенных предопределенными константами
true
иfalse
. Предопределенный булев тип — этоbool
; это определенный тип .Числовые типы ¶
Целочисленный , с плавающей точкой или комплексный тип представляет собой набор целых, с плавающей точкой или комплексных значений соответственно. Они совместно называются числовыми типами . Предопределенные архитектурно-независимые числовые типы:
uint8 — набор всех беззнаковых 8-битных целых чисел (от 0 до 255) uint16 — набор всех беззнаковых 16-битных целых чисел (от 0 до 65535) uint32 набор всех беззнаковых 32-битных целых чисел (от 0 до 4294967295) uint64 — набор всех беззнаковых 64-битных целых чисел (от 0 до 18446744073709551615) int8 — множество всех знаковых 8-битных целых чисел (от -128 до 127) int16 — набор всех 16-битных целых чисел со знаком (от -32768 до 32767) int32 — набор всех 32-битных целых чисел со знаком (от -2147483648 до 2147483647) int64 — набор всех 64-битных целых чисел со знаком (от -9223372036854775808 до 9223372036854775807) float32 набор всех 32-битных чисел с плавающей точкой IEEE 754 float64 — набор всех 64-битных чисел с плавающей точкой IEEE 754 complex64 множество всех комплексных чисел с float32 действительной и мнимой частями complex128 множество всех комплексных чисел с float64 действительной и мнимой частями байтовый псевдоним для uint8 псевдоним руны для int32Значение n- битного целого числа имеет ширину n бит и представлено с использованием арифметики дополнения до двух .
Существует также набор предопределенных целочисленных типов с размерами, зависящими от реализации:
uint 32 или 64 бита int того же размера, что и uint uintptr — беззнаковое целое число, достаточно большое для хранения неинтерпретируемых битов значения указателяЧтобы избежать проблем с переносимостью, все числовые типы являются определенными типами и, таким образом, различны, за исключением
byte
, который является псевдонимом дляuint8
, иrune
, который является псевдонимом дляint32
. Явные преобразования требуются, когда различные числовые типы смешиваются в выражении или назначении. Например,int32
иint
не являются одним и тем же типом, даже если они могут иметь одинаковый размер на определенной архитектуре.Типы строк ¶
Строковый тип представляет собой набор строковых значений. Строковое значение — это (возможно, пустая) последовательность байтов. Количество байтов называется длиной строки и никогда не бывает отрицательным. Строки неизменяемы: после создания изменить содержимое строки невозможно. Предопределенный строковый тип —
string
; это определенный тип .Длину строки
s
можно узнать с помощью встроенной функцииlen
. Длина является константой времени компиляции, если строка является константой. Доступ к байтам строки можно получить по целочисленным индексам от 0 доlen(s)-1
. Недопустимо брать адрес такого элемента; еслиs[i]
этоi
байт строки,&s[i]
то это недопустимо.Типы массивов ¶
Массив — это пронумерованная последовательность элементов одного типа, называемая типом элемента. Количество элементов называется длиной массива и никогда не бывает отрицательным.
ArrayType = "[" ArrayLength "]" ElementType . ArrayLength = Выражение . ElementType = Тип .Длина является частью типа массива; она должна вычисляться как неотрицательная константа , представляемая значением типа
int
. Длину массиваa
можно узнать с помощью встроенной функцииlen
. К элементам можно обращаться по целочисленным индексам от 0 доlen(a)-1
. Типы массивов всегда одномерны, но могут быть составлены для формирования многомерных типов.[32]байт [2*N] структура { x, y int32 } [1000]*float64 [3][5]инт [2][2][2]float64 // то же самое, что и [2]([2]([2]float64))Тип массива
T
не может иметь элемент типаT
или типа, содержащегоT
в качестве компонента, напрямую или косвенно, если содержащие типы являются только типами массива или структуры.// недопустимые типы массивов тип ( T1 [10]T1 // тип элемента T1 — T1 T2 [10]struct{ f T2 } // T2 содержит T2 как компонент структуры T3 [10]T4 // T3 содержит T3 как компонент структуры в T4 T4 struct{ f T3 } // T4 содержит T4 как компонент массива T3 в структуре ) // допустимые типы массивов тип ( T5 [10]*T5 // T5 содержит T5 как компонент указателя T6 [10]func() T6 // T6 содержит T6 как компонент типа функции T7 [10]struct{ f []T7 } // T7 содержит T7 как компонент среза в структуре )Типы срезов ¶
Срез — это дескриптор непрерывного сегмента базового массива , который обеспечивает доступ к пронумерованной последовательности элементов этого массива. Тип среза обозначает набор всех срезов массивов его типа элемента. Количество элементов называется длиной среза и никогда не бывает отрицательным. Значение неинициализированного среза равно
nil
.SliceType = "[" "]" ElementType .Длина среза
s
может быть обнаружена встроенной функциейlen
; в отличие от массивов она может изменяться во время выполнения. Элементы могут быть адресованы целочисленными индексами от 0 доlen(s)-1
. Индекс среза заданного элемента может быть меньше индекса того же элемента в базовом массиве.Срез, будучи инициализированным, всегда связан с базовым массивом, который содержит его элементы. Поэтому срез делит хранилище со своим массивом и с другими срезами того же массива; в отличие от этого, отдельные массивы всегда представляют отдельное хранилище.
Массив, лежащий в основе среза, может выходить за пределы конца среза. Емкость является мерой этой протяженности: это сумма длины среза и длины массива за срезом; срез длиной до этой емкости может быть создан путем нарезания нового среза из исходного среза. Емкость среза
a
может быть обнаружена с помощью встроенной функцииcap(a)
.Новое, инициализированное значение среза для заданного типа элемента
T
может быть создано с помощью встроенной функцииmake
, которая принимает тип среза и параметры, указывающие длину и, опционально, емкость. Срез, созданный с помощью ,make
всегда выделяет новый, скрытый массив, на который ссылается возвращаемое значение среза. То есть, выполнениесделать([]T, длина, емкость)создает тот же срез, что и выделение массива и его срез , поэтому эти два выражения эквивалентны:
сделать([]целое, 50, 100) новый([100]int)[0:50]Как и массивы, срезы всегда одномерны, но могут быть составлены для создания объектов более высокой размерности. В массивах массивов внутренние массивы по конструкции всегда имеют одинаковую длину; однако в срезах срезов (или массивах срезов) внутренние длины могут динамически меняться. Более того, внутренние срезы должны быть инициализированы индивидуально.
Типы конструкций ¶
Структура — это последовательность именованных элементов, называемых полями, каждое из которых имеет имя и тип. Имена полей могут быть указаны явно (IdentifierList) или неявно (EmbeddedField). В пределах структуры непустые имена полей должны быть уникальными .
StructType = "struct" "{" { FieldDecl ";" } "}" . FieldDecl = ( Тип списка идентификаторов | Встроенное поле ) [ Тег ] . Встроенное поле = [ "*" ] Имя типа [ Аргументы типа ] . Тег = string_lit .// Пустая структура. структура {} // Структура с 6 полями. структура { x, y целые у float32 _ float32 // заполнение А *[]целое F функция() }Поле, объявленное с типом, но без явного имени поля, называется встроенным полем . Встроенное поле должно быть указано как имя типа
T
или как указатель на имя неинтерфейсного типа*T
иT
само по себе не может быть типом указателя. Неквалифицированное имя типа действует как имя поля.// Структура с четырьмя встроенными полями типов T1, *T2, P.T3 и *P.T4 структура { T1 // имя поля T1 *T2 // имя поля T2 P.T3 // имя поля T3 *P.T4 // имя поля T4 x, y int // имена полей x и y }Следующее объявление является недопустимым, поскольку имена полей должны быть уникальными в структурном типе:
структура { T // конфликтует со встроенными полями *T и *PT *T // конфликтует со встроенным полем T и *PT *PT // конфликтует со встроенным полем T и *T }Поле или метод
f
встроенного поля в структуреx
называется продвинутым, еслиx.f
является допустимым селектором , обозначающим это поле или методf
.Продвинутые поля действуют как обычные поля структуры, за исключением того, что их нельзя использовать в качестве имен полей в составных литералах структуры.
При наличии типа структуры
S
и именованного типаT
продвигаемые методы включаются в набор методов структуры следующим образом:
- Если
S
содержит встроенное полеT
, то наборы методовS
и*S
оба включают продвигаемые методы с приемникомT
. Набор методов*S
также включает продвигаемые методы с приемником*T
.- Если
S
содержит встроенное поле*T
, то наборы методовS
и*S
оба включают продвигаемые методы с получателемT
или*T
.За объявлением поля может следовать необязательный строковый литерал тег , который становится атрибутом для всех полей в соответствующем объявлении поля. Пустая строка тега эквивалентна отсутствующему тегу. Теги становятся видимыми через интерфейс отражения и принимают участие в идентификации типа для структур, но в противном случае игнорируются.
структура { x, y float64 "" // пустая строка тега похожа на отсутствующий тег имя строки "любая строка разрешена как тег" _ [4]byte "ceci не имеет поля структуры" } // Структура, соответствующая буферу протокола TimeStamp. // Строки тегов определяют номера полей буфера протокола; // они следуют соглашению, изложенному в пакете reflect. структура { микросекунда uint64 `protobuf:"1"` serverIP6 uint64 `protobuf:"2"` }Тип структуры
T
не может содержать поле типаT
или типа, содержащегоT
в качестве компонента, напрямую или косвенно, если содержащие типы являются только типами массива или структуры.// недопустимые типы структур тип ( T1 struct{ T1 } // T1 содержит поле T1 T2 struct{ f [10]T2 } // T2 содержит T2 как компонент массива T3 struct{ T4 } // T3 содержит T3 как компонент массива в struct T4 T4 struct{ f [10]T3 } // T4 содержит T4 как компонент структуры T3 в массиве ) // допустимые типы структур тип ( T5 struct{ f *T5 } // T5 содержит T5 как компонент указателя T6 struct{ f func() T6 } // T6 содержит T6 как компонент типа функции T7 struct{ f [10][]T7 } // T7 содержит T7 как компонент среза в массиве )Типы указателей ¶
Тип указателя обозначает набор всех указателей на переменные данного типа, называемый базовым типом указателя. Значение неинициализированного указателя равно
nil
.Тип Указателя = "*" Базовый Тип . БазовыйТип = Тип .*Точка *[4]целоеТипы функций ¶
Тип функции обозначает множество всех функций с одинаковыми типами параметров и результатов. Значение неинициализированной переменной типа функции равно
nil
.FunctionType = "func" Сигнатура . Сигнатура = Параметры [ Результат ]. Результат = Параметры | Тип . Параметры = "(" [ СписокПараметров [ "," ] ] ")" . СписокПараметров = ParameterDecl { "," ParameterDecl } . ParameterDecl = [ СписокИдентификаторов ] [ "..." ] Тип .В списке параметров или результатов имена (IdentifierList) должны либо присутствовать все, либо отсутствовать. Если присутствуют, каждое имя обозначает один элемент (параметр или результат) указанного типа, и все непустые имена в сигнатуре должны быть уникальными . Если отсутствуют, каждый тип обозначает один элемент этого типа. Списки параметров и результатов всегда заключаются в скобки, за исключением того, что если есть ровно один неименованный результат, он может быть записан как тип без скобок.
Последний входящий параметр в сигнатуре функции может иметь тип с префиксом
...
. Функция с таким параметром называется вариативной и может быть вызвана с нулевым или большим количеством аргументов для этого параметра.функция() функ(x целое) целое func(a, _ int, z float32) bool func(a, b int, z float32) (логическое) func(строка префикса, значения ...целое) func(a, b int, z float64, opt ...interface{}) (успех bool) функция(целое, целое, float64) (float64, *[]целое) функция(n int) функция(p *T)Типы интерфейсов ¶
Тип интерфейса определяет набор типов . Переменная типа интерфейса может хранить значение любого типа, который находится в наборе типов интерфейса. Говорят, что такой тип реализует интерфейс . Значение неинициализированной переменной типа интерфейса равно
nil
.InterfaceType = "interface" "{" { InterfaceElem ";" } "}" . InterfaceElem = MethodElem | TypeElem . MethodElem = MethodName Сигнатура . MethodName = идентификатор . TypeElem = TypeTerm { "|" TypeTerm } . TypeTerm = Тип | UnderlyingType . UnderlyingType = "~" Тип .Тип интерфейса определяется списком элементов интерфейса . Элемент интерфейса — это либо метод , либо элемент типа , где элемент типа — это объединение одного или нескольких терминов типа . Термин типа — это либо один тип, либо один базовый тип.
Базовые интерфейсы ¶
В своей самой базовой форме интерфейс определяет (возможно, пустой) список методов. Набор типов, определяемый таким интерфейсом, представляет собой набор типов, которые реализуют все эти методы, а соответствующий набор методов состоит исключительно из методов, определяемых интерфейсом. Интерфейсы, наборы типов которых могут быть определены полностью списком методов, называются базовыми интерфейсами.
// Простой файловый интерфейс. интерфейс { Читать([]байт) (целое, ошибка) Запись([]байт) (целое, ошибка) Ошибка Close() }Имя каждого явно указанного метода должно быть уникальным и непустым .
интерфейс { Строка() строка String() string // недопустимо: строка не уникальна _(x int) // недопустимо: метод должен иметь непустое имя }Интерфейс может реализовываться несколькими типами. Например, если два типа
S1
иS2
имеют набор методовfunc (p T) Read(p []byte) (n int, err ошибка) func (p T) Write(p []byte) (n int, err ошибка) ошибка функции (p T) Close()(где
T
означает либо ,S1
либоS2
), тоFile
интерфейс реализуется как , такS1
иS2
, независимо от того, какие другие методыS1
иS2
могут иметь или совместно использовать.Каждый тип, являющийся членом набора типов интерфейса, реализует этот интерфейс. Любой заданный тип может реализовывать несколько различных интерфейсов. Например, все типы реализуют пустой интерфейс , который обозначает набор всех (не интерфейсных) типов:
интерфейс{}Для удобства предопределенный тип
any
является псевдонимом для пустого интерфейса. [ Go 1.18 ]Аналогично рассмотрим эту спецификацию интерфейса, которая появляется в объявлении типа для определения интерфейса с именем
Locker
:Тип интерфейса Locker { Замок() Разблокировать() }Если
S1
иS2
также реализоватьфунк (п Т) Блокировка() { … } func (p T) Разблокировать() { … }они реализуют
Locker
интерфейс, а такжеFile
интерфейс.Встроенные интерфейсы ¶
В несколько более общей форме интерфейс
T
может использовать (возможно, квалифицированное) имя типа интерфейсаE
в качестве элемента интерфейса. Это называется встраиванием интерфейсаE
вT
[ Go 1.14 ]. Набор типовT
— это пересечение наборов типов, определенныхT
явно объявленными методами , и наборов типовT
встроенных интерфейсов . Другими словами, набор типовT
— это набор всех типов, которые реализуют все явно объявленные методы ,T
а также все методыE
[ Go 1.18 ].Тип интерфейса читателя { Read(p []byte) (n int, err ошибка) Ошибка Close() } Интерфейс Writer типа { Write(p []byte) (n int, err ошибка) Ошибка Close() } // Методы ReadWriter — Read, Write и Close. тип интерфейса ReadWriter { Reader // включает методы Reader в набор методов ReadWriter Writer // включает методы Writer в набор методов ReadWriter }При встраивании интерфейсов методы с одинаковыми именами должны иметь идентичные сигнатуры.
тип интерфейса ReadCloser { Reader // включает методы Reader в набор методов ReadCloser Close() // неверно: сигнатуры Reader.Close и Close различны }Общие интерфейсы ¶
В своей наиболее общей форме элемент интерфейса может также быть произвольным термином типа
T
или термином формы,~T
определяющей базовый типT
, или объединением терминов [ Go 1.18 ]. Вместе со спецификациями методов эти элементы позволяют точно определить набор типов интерфейса следующим образом:t1|t2|…|tn
- Набор типов пустого интерфейса представляет собой набор всех неинтерфейсных типов.
- Набор типов непустого интерфейса представляет собой пересечение наборов типов его элементов интерфейса.
- Набор типов спецификации метода — это набор всех неинтерфейсных типов, наборы методов которых включают этот метод.
- Набор типов термина неинтерфейсного типа — это набор, состоящий только из этого типа.
- Набор типов терма формы
~T
— это набор всех типов, базовым типом которых являетсяT
.- Типовой набор объединения терминов представляет собой объединение типовых наборов терминов.
t1|t2|…|tn
Квантификация «множество всех неинтерфейсных типов» относится не только ко всем (неинтерфейсным) типам, объявленным в рассматриваемой программе, но и ко всем возможным типам во всех возможных программах, и, следовательно, является бесконечной. Аналогично, учитывая множество всех неинтерфейсных типов, которые реализуют определенный метод, пересечение множеств методов этих типов будет содержать именно этот метод, даже если все типы в рассматриваемой программе всегда спаривают этот метод с другим методом.
По своей конструкции набор типов интерфейса никогда не содержит тип интерфейса.
// Интерфейс, представляющий только тип int. интерфейс { инт } // Интерфейс, представляющий все типы с базовым типом int. интерфейс { ~инт } // Интерфейс, представляющий все типы с базовым типом int, реализующие метод String. интерфейс { ~инт Строка() строка } // Интерфейс, представляющий пустой набор типов: не существует типа, который был бы одновременно целым числом и строкой. интерфейс { инт нить }В термине формы
~T
базовый типT
должен быть самим собой иT
не может быть интерфейсом.тип MyInt int интерфейс { ~[]byte // базовый тип []byte — это он сам ~MyInt // недопустимо: базовый тип MyInt не является MyInt ~error // недопустимо: ошибка — это интерфейс }Элементы объединения обозначают объединения наборов типов:
// Интерфейс Float представляет все типы с плавающей точкой // (включая любые именованные типы, базовые типы которых // либо float32, либо float64). тип Float интерфейс { ~float32 | ~float64 }Тип
T
в термине формыT
или~T
не может быть параметром типа , а наборы типов всех неинтерфейсных терминов должны быть попарно непересекающимися (попарное пересечение наборов типов должно быть пустым). Дан параметр типаP
:интерфейс { P // неверно: P — параметр типа int | ~P // неверно: P — параметр типа ~int | MyInt // недопустимо: наборы типов для ~int и MyInt не являются непересекающимися (~int включает MyInt) float32 | Float // перекрывающиеся наборы типов, но Float — это интерфейс }Ограничение реализации: объединение (с более чем одним термином) не может содержать предварительно объявленный идентификатор
comparable
или интерфейсы, которые определяют методы, или встраиватьcomparable
интерфейсы, которые определяют методы.Интерфейсы, которые не являются базовыми , могут использоваться только как ограничения типа или как элементы других интерфейсов, используемых как ограничения. Они не могут быть типами значений или переменных, или компонентами других, не интерфейсных типов.
var x Float // недопустимо: Float не является базовым интерфейсом var x interface{} = Float(nil) // недопустимо тип Плавающая структура { f Плавающий // недопустимый }Тип интерфейса
T
не может встраивать элемент типа, который является, содержит или встраиваетT
, прямо или косвенно.// нелегально: Bad не может встраиваться тип Плохой интерфейс { Плохой } // незаконно: Bad1 не может встраивать себя с помощью Bad2 тип интерфейса Bad1 { Плохо2 } тип интерфейса Bad2 { Плохо1 } // недопустимо: Bad3 не может встраивать объединение, содержащее Bad3 тип интерфейса Bad3 { ~целое | ~строка | Bad3 } // недопустимо: Bad4 не может встраивать массив, содержащий Bad4 как тип элемента тип интерфейса Bad4 { [10]Плохо4 }Реализация интерфейса ¶
Тип
T
реализует интерфейс,I
если
T
не является интерфейсом и является элементом типа set ofI
; илиT
— это интерфейс, а набор типовT
— это подмножество набора типовI
.Значение типа
T
реализует интерфейс, еслиT
реализует интерфейс.Типы карт ¶
Карта — это неупорядоченная группа элементов одного типа, называемого типом элемента, индексированная набором уникальных ключей другого типа, называемого типом ключа. Значение неинициализированной карты —
nil
.MapType = "map" "[" KeyType "]" ElementType . KeyType = Type .Операторы сравнения
==
и!=
должны быть полностью определены для операндов типа ключа; таким образом, тип ключа не должен быть функцией, картой или срезом. Если тип ключа является типом интерфейса, эти операторы сравнения должны быть определены для динамических значений ключа; сбой вызовет панику во время выполнения .карта[строка]целое map[*T]struct{ x, y float64 } карта[строка]интерфейс{}Число элементов карты называется ее длиной. Для карты
m
ее можно обнаружить с помощью встроенной функцииlen
и она может изменяться во время выполнения. Элементы могут добавляться во время выполнения с помощью назначений и извлекаться с помощью выражений индекса ; их можно удалять с помощьюdelete
встроеннойclear
функции и .Новое пустое значение карты создается с помощью встроенной функции
make
, которая принимает тип карты и необязательную подсказку о емкости в качестве аргументов:сделать(карта[строка]целое) сделать(карта[строка]целое, 100)Начальная емкость не ограничивает ее размер: карты растут, чтобы вместить количество хранящихся в них элементов, за исключением
nil
карт.nil
Карта эквивалентна пустой карте, за исключением того, что никакие элементы не могут быть добавлены.Типы каналов ¶
Канал обеспечивает механизм для одновременного выполнения функций для связи путем отправки и получения значений указанного типа элемента. Значение неинициализированного канала равно
nil
.ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .Необязательный оператор указывает направление
<-
канала , отправку или получение . Если направление указано, канал направленный , в противном случае он двунаправленный . Канал может быть ограничен только отправкой или только получением путем назначения или явного преобразования .chan T // может использоваться для отправки и получения значений типа T chan<- float64 // можно использовать только для отправки float64 <-chan int // можно использовать только для приема целых чиселОператор
<-
ассоциируется с самым левымchan
возможным:chan<- chan int // то же самое, что chan<- (chan int) chan<- <-chan int // то же самое, что chan<- (<-chan int) <-chan <-chan int // то же, что и <-chan (<-chan int) чан (<-чан цел.)Новое инициализированное значение канала можно создать с помощью встроенной функции
make
, которая принимает тип канала и необязательную емкость в качестве аргументов:сделать(chan int, 100)Емкость, в количестве элементов, задает размер буфера в канале. Если емкость равна нулю или отсутствует, канал не буферизован, и связь успешна только тогда, когда готовы и отправитель, и получатель. В противном случае канал буферизован, и связь успешна без блокировки, если буфер не полон (отправляет) или не пуст (принимает). Канал
nil
никогда не готов к связи.Канал может быть закрыт с помощью встроенной функции
close
. Форма многозначного назначения оператора получения сообщает, было ли полученное значение отправлено до закрытия канала.Один канал может использоваться в операторах отправки , операциях получения и вызовах встроенных функций
cap
иlen
любым количеством goroutines без дальнейшей синхронизации. Каналы действуют как очереди «первым пришел — первым вышел». Например, если одна goroutine отправляет значения по каналу, а вторая goroutine их получает, значения принимаются в порядке отправки.Свойства типов и значений ¶
Базовые типы ¶
Каждый тип
T
имеет базовый тип : ЕслиT
это один из предопределенных булевых, числовых или строковых типов или литерал типа, то соответствующий базовый тип — этоT
он сам. В противном случаеT
базовый тип — это базовый тип типа, на которыйT
ссылается в своем объявлении. Для параметра типа, который является базовым типом его ограничения типа , которое всегда является интерфейсом.тип ( А1 = строка А2 = А1 ) тип ( струна B1 В2 В1 В3 []В1 В4 В3 ) функция f[P любой](x P) { … }Базовый тип
string
,A1
,A2
,B1
, иB2
—string
. Базовый тип[]B1
,B3
, иB4
— .[]B1
Базовый типP
.interface{}
Основные типы ¶
Каждый неинтерфейсный тип
T
имеет базовый тип , который совпадает с базовым типомT
.Интерфейс
T
имеет основной тип, если выполняется одно из следующих условий:
- Существует один тип
U
, который является базовым типом всех типов в наборе типовT
; или- набор типов
T
содержит только типы каналов с идентичным типом элементаE
, и все направленные каналы имеют одинаковое направление.Никакие другие интерфейсы не имеют основного типа.
Основной тип интерфейса, в зависимости от выполняемого условия, может быть:
- тип
U
; или- тип ,
chan E
еслиT
содержит только двунаправленные каналы, или типchan<- E
или<-chan E
в зависимости от направления присутствующих направленных каналов.По определению, основной тип никогда не является определенным типом , параметром типа или типом интерфейса .
Примеры интерфейсов с основными типами:
тип Цельсия float32 тип Кельвин float32 интерфейс{ целое } // целое интерфейс{ Цельсий|Кельвин } // float32 интерфейс{ ~канал целочисленный } // канал целочисленный интерфейс{ ~канал целочисленный|~канал<- целочисленный } // канал<- целочисленный интерфейс{ ~[]*данные; String() строка } // []*данныеПримеры интерфейсов без основных типов:
interface{} // нет единого базового типа interface{ Celsius|float64 } // нет единого базового типа interface{ chan int | chan<- string } // каналы имеют разные типы элементов interface{ <-chan int | chan<- int } // направленные каналы имеют разные направленияНекоторые операции ( выражения среза и ) полагаются на немного более свободную форму основных типов, которые принимают байтовые срезы и строки. В частности, если есть ровно два типа
append
иcopy
[]byte
,string
которые являются базовыми типами всех типов в наборе типов интерфейсаT
, то основной типT
называетсяbytestring
.Примеры интерфейсов с
bytestring
основными типами:interface{ int } // int (то же самое, что и обычный основной тип) интерфейс{ []байт | строка } // байтовая строка интерфейс{ ~[]байт | myString } // байтовая строкаОбратите внимание, что
bytestring
это не настоящий тип; его нельзя использовать для объявления переменных или составления других типов. Он существует исключительно для описания поведения некоторых операций, которые считывают последовательность байтов, которая может быть байтовым срезом или строкой.Тип идентичности ¶
Два типа либо идентичны , либо различны .
Именованный тип всегда отличается от любого другого типа. В противном случае два типа идентичны, если их базовые литералы типов структурно эквивалентны; то есть они имеют одинаковую литеральную структуру и соответствующие компоненты имеют идентичные типы. Подробно:
- Два типа массивов идентичны, если они имеют идентичные типы элементов и одинаковую длину массива.
- Два типа среза идентичны, если они имеют идентичные типы элементов.
- Два типа структур идентичны, если они имеют одинаковую последовательность полей, и если соответствующие поля имеют одинаковые имена, идентичные типы и идентичные теги. Неэкспортированные имена полей из разных пакетов всегда различны.
- Два типа указателей идентичны, если они имеют идентичные базовые типы.
- Два типа функций идентичны, если они имеют одинаковое количество параметров и значений результата, соответствующие типы параметров и результатов идентичны, и либо обе функции являются вариативными, либо ни одна из них не является таковой. Имена параметров и результатов не обязательно должны совпадать.
- Два типа интерфейса идентичны, если они определяют один и тот же набор типов.
- Два типа карт идентичны, если они имеют идентичные ключи и типы элементов.
- Два типа каналов идентичны, если они имеют идентичные типы элементов и одинаковое направление.
- Два экземпляра типа идентичны, если их определенные типы и все аргументы типа идентичны.
Учитывая заявления
тип ( A0 = []строка А1 = А0 A2 = структура {a, b целое число} A3 = целое A4 = функция(A3, float64) *A0 A5 = func(x int, _ float64) *[]строка Б0 А0 B1 []строка B2 структура { a, b целое } B3 структура { a, c int } B4 функция(целое, float64) *B0 B5 функция(x целое, y плавающее64) *A1 С0 = В0 D0[P1, P2 любой] структура { x P1; y P2 } E0 = D0[целое, строка] )Эти типы идентичны:
A0, A1 и []строка A2 и структура {a, b int} A3 и инт A4, func(int, float64) *[]string и A5 В0 и С0 D0[целое, строка] и E0 []целое и []целое структура {a, b *B5} и структура {a, b *B5} func(x int, y float64) *[]string, func(int, float64) (результат *[]string) и A5
B0
иB1
отличаются тем, что они являются новыми типами, созданными различными определениями типов ;func(int, float64) *B0
иfunc(x int, y float64) *[]string
отличаются тем, чтоB0
отличается от[]string
; иP1
иP2
отличаются тем, что они являются разными параметрами типа.D0[int, string]
иstruct{ x int; y string }
отличаются тем, что первый является инстанцированным определенным типом, а второй — литералом типа (но их по-прежнему можно назначать ).Назначаемость ¶
Значение
x
типаV
может быть присвоено переменной типа (« можно присвоить ») , если выполняется одно из следующих условий:T
x
T
V
иT
идентичны.V
иT
имеют идентичные базовые типы , но не являются параметрами типа и по крайней мере один изV
илиT
не является именованным типом .V
иT
являются типами каналов с идентичными типами элементов,V
является двунаправленным каналом и по крайней мере один изV
илиT
не является именованным типом .T
является типом интерфейса, но не параметром типа, иx
реализуетT
.x
— это предварительно объявленный идентификаторnil
,T
представляющий собой указатель, функцию, срез, карту, канал или тип интерфейса, но не параметр типа.x
— нетипизированная константа , представляемая значением типаT
.Кроме того, если
x
типV
илиT
является параметрами типа,x
может быть назначен переменной типа,T
если выполняется одно из следующих условий:
x
— это предварительно объявленный идентификаторnil
,T
параметр типа, которыйx
может быть назначен каждому типу вT
наборе типов.V
не является именованным типом ,T
является параметром типа иx
может быть назначен каждому типу вT
наборе типов.V
является параметром типа иT
не является именованным типом, и значения каждого типа вV
наборе типов могут быть назначеныT
.Репрезентативность ¶
Константа может быть представленазначением типа , где не является параметром типа , если выполняется одно из следующих условий:
x
T
T
x
находится в наборе значений , определяемыхT
.T
является типом с плавающей точкой иx
может быть округлен доT
точности без переполнения. Округление использует правила округления до четного IEEE 754, но с отрицательным нулем IEEE, дополнительно упрощенным до нуля без знака. Обратите внимание, что постоянные значения никогда не приводят к отрицательному нулю IEEE, NaN или бесконечности.T
является сложным типом, а компоненты иx
могут быть представлены значениями типа компонента ( или ).real(x)
imag(x)
T
float32
float64
Если
T
это параметр типа,x
то он может быть представлен значением типаT
, еслиx
он может быть представлен значением каждого типа изT
набора типов .x T x можно представить значением T, поскольку Байт 97 «a» находится в наборе значений байта 97 руна руна — это псевдоним для int32, а 97 входит в набор 32-битных целых чисел "foo" строка "foo" находится в наборе строковых значений 1024 int16 1024 входит в набор 16-битных целых чисел 42.0 байт 42 находится в наборе беззнаковых 8-битных целых чисел 1e10 uint64 10000000000 входит в набор беззнаковых 64-битных целых чисел 2.718281828459045 float32 2.718281828459045 округляется до 2.7182817, что входит в набор значений float32 -1e-1000 float64 -1e-1000 округляется до IEEE -0.0, что далее упрощается до 0.0 0i int 0 — целое число (42 + 0i) float32 42.0 (с нулевой мнимой частью) входит в набор значений float32x T x не может быть представлен значением T, потому что 0 bool 0 не входит в набор булевых значений строка 'a' 'a' — это руна, ее нет в наборе строковых значений 1024 байта 1024 не входит в набор беззнаковых 8-битных целых чисел -1 uint16 -1 не входит в набор беззнаковых 16-битных целых чисел 1.1 int 1.1 не является целым числом 42i float32 (0 + 42i) не входит в набор значений float32 1e1000 float64 1e1000 переполняется до IEEE +Inf после округленияНаборы методов ¶
Набор методов типа определяет методы, которые могут быть вызваны для операнда этого типа. Каждый тип имеет (возможно пустой) набор методов, связанный с ним:
- Набор методов определенного типа
T
состоит из всех методов, объявленных с типом приемникаT
.- Набор методов указателя на определенный тип
T
(гдеT
не является ни указателем, ни интерфейсом) — это набор всех методов, объявленных с помощью приемника*T
илиT
.- Набор методов типа интерфейса представляет собой пересечение наборов методов каждого типа в наборе типов интерфейса (результирующий набор методов обычно представляет собой просто набор объявленных методов в интерфейсе).
Дополнительные правила применяются к структурам (и указателям на структуры), содержащим встроенные поля, как описано в разделе о типах структур . Любой другой тип имеет пустой набор методов.
В наборе методов каждый метод должен иметь уникальное непустое имя метода .
Блоки ¶
Блок — это возможно пустая последовательность объявлений и операторов внутри соответствующих фигурных скобок.
Блок = "{" StatementList "}" . StatementList = { Statement ";" } .Помимо явных блоков в исходном коде существуют неявные блоки:
- Блок universe охватывает весь исходный текст Go.
- Каждый пакет имеет блок пакета, содержащий весь исходный текст Go для этого пакета.
- Каждый файл имеет файловый блок , содержащий весь исходный текст Go в этом файле.
- Каждый оператор «if» , «for» и «switch» считается находящимся в своем собственном неявном блоке.
- Каждое предложение в операторе «switch» или «select» действует как неявный блок.
Блоки вложены и влияют на область действия .
Декларации и область действия ¶
Декларация связывает непустой идентификатор с константой , типом , параметром типа , переменной , функцией , меткой или пакетом . Каждый идентификатор в программе должен быть объявлен . Ни один идентификатор не может быть объявлен дважды в одном и том же блоке, и ни один идентификатор не может быть объявлен как в файле, так и в блоке пакета.
Пустой идентификатор может использоваться как любой другой идентификатор в объявлении, но он не вводит привязку и, таким образом, не объявляется. В блоке пакета идентификатор
init
может использоваться только для объявленийinit
функций , и, как и пустой идентификатор, он не вводит новую привязку.Объявление = ConstDecl | TypeDecl | VarDecl . TopLevelDecl = Объявление | FunctionDecl | MethodDecl .Область действия объявленного идентификатора — это область исходного текста, в которой идентификатор обозначает указанную константу, тип, переменную, функцию, метку или пакет.
Go имеет лексическую область видимости с помощью блоков :
- Областью действия предварительно объявленного идентификатора является блок юниверса.
- Областью действия идентификатора, обозначающего константу, тип, переменную или функцию (но не метод), объявленную на верхнем уровне (вне какой-либо функции), является блок пакета.
- Областью действия имени импортируемого пакета является блок файла, содержащий декларацию импорта.
- Областью действия идентификатора, обозначающего приемник метода, параметр функции или переменную результата, является тело функции.
- Область действия идентификатора, обозначающего параметр типа функции или объявленного получателем метода, начинается после имени функции и заканчивается в конце тела функции.
- Область действия идентификатора, обозначающего параметр типа, начинается после имени типа и заканчивается в конце TypeSpec.
- Область действия идентификатора константы или переменной, объявленного внутри функции, начинается в конце ConstSpec или VarSpec (ShortVarDecl для коротких объявлений переменных) и заканчивается в конце самого внутреннего содержащего блока.
- Область действия идентификатора типа, объявленного внутри функции, начинается с идентификатора в TypeSpec и заканчивается в конце самого внутреннего содержащего блока.
Идентификатор, объявленный в блоке, может быть повторно объявлен во внутреннем блоке. Пока идентификатор внутреннего объявления находится в области действия, он обозначает сущность, объявленную внутренним объявлением.
Предложение пакета не является декларацией; имя пакета не появляется ни в одной области действия. Его цель — идентифицировать файлы, принадлежащие одному и тому же пакету , и указать имя пакета по умолчанию для деклараций импорта.
Области действия меток ¶
Метки объявляются помеченными операторами и используются в операторах «break» , «continue» и «goto» . Незаконно определять метку, которая никогда не используется. В отличие от других идентификаторов, метки не имеют области действия блока и не конфликтуют с идентификаторами, которые не являются метками. Областью действия метки является тело функции, в которой она объявлена, и исключает тело любой вложенной функции.
Пустой идентификатор ¶
Пустой идентификатор представлен символом подчеркивания
_
. Он служит анонимным заполнителем вместо обычного (непустого) идентификатора и имеет особое значение в объявлениях , как операнд и в операторах присваивания .Предварительно объявленные идентификаторы ¶
Следующие идентификаторы неявно объявлены в блоке universe [ Go 1.18 ] [ Go 1.21 ]:
Типы: любой логический байт, сопоставимый complex64 complex128 ошибка float32 float64 int int8 int16 int32 int64 руническая строка uint uint8 uint16 uint32 uint64 uintptr Константы: правда ложь йота Нулевое значение: ноль Функции: добавить cap очистить закрыть сложный копировать удалить imag len сделать max min новый паника печать println реальный восстановитьЭкспортированные идентификаторы ¶
Идентификатор может быть экспортирован для разрешения доступа к нему из другого пакета. Идентификатор экспортируется, если оба:
- первый символ имени идентификатора — заглавная буква Unicode (категория символов Unicode Lu); и
- идентификатор объявлен в блоке пакета или является именем поля или именем метода .
Все остальные идентификаторы не экспортируются.
Уникальность идентификаторов ¶
При наличии набора идентификаторов идентификатор называется уникальным , если он отличается от любого другого в наборе. Два идентификатора различаются, если они пишутся по-разному или если они появляются в разных пакетах и не экспортируются . В противном случае они одинаковы.
Постоянные заявления ¶
Объявление константы связывает список идентификаторов (имен констант) со значениями списка константных выражений . Количество идентификаторов должно быть равно количеству выражений, а n- й идентификатор слева связан со значением n- го выражения справа.
ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) . ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] . IdentifierList = идентификатор { "," идентификатор } . ExpressionList = Выражение { "," Выражение } .Если тип присутствует, все константы принимают указанный тип, и выражения должны быть назначаемы этому типу, который не должен быть параметром типа. Если тип опущен, константы принимают индивидуальные типы соответствующих выражений. Если значения выражений являются нетипизированными константами , объявленные константы остаются нетипизированными, а идентификаторы констант обозначают значения констант. Например, если выражение является литералом с плавающей точкой, идентификатор константы обозначает константу с плавающей точкой, даже если дробная часть литерала равна нулю.
константа Пи float64 = 3.14159265358979323846 const zero = 0.0 // нетипизированная константа с плавающей точкой константа ( размер int64 = 1024 eof = -1 // нетипизированная целочисленная константа ) const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", нетипизированные целые числа и строковые константы константа u, v float32 = 0, 3 // u = 0.0, v = 3.0В
const
списке объявлений, заключенном в скобки, список выражений может быть опущен из любого, кроме первого ConstSpec. Такой пустой список эквивалентен текстовой подстановке первого предшествующего непустого списка выражений и его типа, если таковой имеется. Таким образом, пропуск списка выражений эквивалентен повторению предыдущего списка. Количество идентификаторов должно быть равно количеству выражений в предыдущем списке. Вместе с генераторомiota
констант этот механизм позволяет легковесное объявление последовательных значений:константа ( Воскресенье = йота Понедельник Вторник Среда Четверг Пятница Partyday numberOfDays // эта константа не экспортируется )Йота ¶
В объявлении константы предопределенный идентификатор
iota
представляет последовательные нетипизированные целочисленные константы . Его значение — это индекс соответствующего ConstSpec в объявлении константы, начиная с нуля. Его можно использовать для построения набора связанных констант:константа ( c0 = йота // c0 == 0 c1 = йота // c1 == 1 c2 = йота // c2 == 2 ) константа ( a = 1 << йота // a == 1 (йота == 0) b = 1 << йота // b == 2 (йота == 1) c = 3 // c == 3 (йота == 2, не используется) d = 1 << йота // d == 8 (йота == 3) ) константа ( u = iota * 42 // u == 0 (нетипизированная целочисленная константа) v float64 = йота * 42 // v == 42.0 (константа float64) w = йота * 42 // w == 84 (нетипизированная целочисленная константа) ) константа x = йота // x == 0 константа y = йота // y == 0По определению, многократное использование
iota
в одном и том же ConstSpec имеет одно и то же значение:константа ( бит0, маска0 = 1 << йота, 1<<йота - 1 // бит0 == 1, маска0 == 0 (йота == 0) бит1, маска1 // бит1 == 2, маска1 == 1 (йота == 1) _, _ // (йота == 2, не используется) бит3, маска3 // бит3 == 8, маска3 == 7 (йота == 3) )В этом последнем примере используется неявное повторение последнего непустого списка выражений.
Тип декларации ¶
Объявление типа связывает идентификатор, имя типа , с типом . Объявления типов бывают двух видов: объявления псевдонимов и определения типов.
TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) . TypeSpec = AliasDecl | TypeDef .Декларации псевдонимов ¶
Объявление псевдонима привязывает идентификатор к заданному типу [ Go 1.9 ].
AliasDecl = идентификатор "=" Тип .В рамках идентификатора он служит псевдонимом типа .
тип ( nodeList = []*Node // nodeList и []*Node — идентичные типы Polar = polar // Polar и polar обозначают идентичные типы )Определения типов ¶
Определение типа создает новый, отличный тип с тем же базовым типом и операциями, что и заданный тип, и привязывает к нему идентификатор, имя типа .
TypeDef = идентификатор [ ПараметрыТипа ] Тип .Новый тип называется определенным типом . Он отличается от любого другого типа, включая тип, из которого он создан.
тип ( Point struct{ x, y float64 } // Point и struct{ x, y float64 } — это разные типы полярная точка // полярная и точка обозначают разные типы ) тип структуры TreeNode { левый, правый *TreeNode ценность любого } тип Блок интерфейса { BlockSize() целое число Шифровать(источник, dst []байт) Расшифровать(src, dst []byte) }Определенный тип может иметь методы , связанные с ним. Он не наследует никаких методов, связанных с данным типом, но набор методов типа интерфейса или элементов составного типа остается неизменным:
// Мьютекс — это тип данных с двумя методами: Lock и Unlock. тип Mutex struct { /* Поля Mutex */ } func (m *Mutex) Lock() { /* Реализация блокировки */ } func (m *Mutex) Unlock() { /* Реализация разблокировки */ } // NewMutex имеет тот же состав, что и Mutex, но его набор методов пуст. тип NewMutex Мьютекс // Набор методов базового типа PtrMutex *Mutex остается неизменным, // но набор методов PtrMutex пуст. тип PtrMutex *Mutex // Набор методов *PrintableMutex содержит методы // Блокировка и разблокировка привязаны к встроенному полю Mutex. тип PrintableMutex структура { Мьютекс } // MyBlock — это тип интерфейса, имеющий тот же набор методов, что и Block. тип MyBlock БлокОпределения типов могут использоваться для определения различных логических, числовых или строковых типов и связывания с ними методов:
тип TimeZone int константа ( EST TimeZone = -(5 + йота) КСТ МСТ Тихоокеанское стандартное время ) функция (tz TimeZone) String() строка { return fmt.Sprintf("GMT%+dh", tz) }Если определение типа указывает параметры типа , имя типа обозначает универсальный тип . Универсальные типы должны быть инстанцированы при их использовании.
тип Список[T любой] структура { следующий *Список[T] значение Т }В определении типа заданный тип не может быть параметром типа.
тип T[P любой] P // недопустимо: P — параметр типа функция f[T любая]() { тип LT // недопустимо: T — параметр типа, объявленный охватывающей функцией }Универсальный тип также может иметь методы, связанные с ним. В этом случае получатели методов должны объявить то же количество параметров типа, что и в определении универсального типа.
// Метод Len возвращает количество элементов в связанном списке l. функция (l *List[T]) Len() int { … }Тип объявления параметров ¶
Список параметров типа объявляет параметры типа обобщенной функции или объявления типа. Список параметров типа выглядит как обычный список параметров функции, за исключением того, что все имена параметров типа должны присутствовать, а список заключен в квадратные скобки, а не в круглые [ Go 1.18 ].
TypeParameters = "[" TypeParamList [ "," ] "]" . TypeParamList = TypeParamDecl { "," TypeParamDecl } . TypeParamDecl = IdentifierList TypeConstraint .Все непустые имена в списке должны быть уникальными. Каждое имя объявляет параметр типа, который является новым и другим именованным типом , который действует как заполнитель для (пока) неизвестного типа в объявлении. Параметр типа заменяется аргументом типа при создании экземпляра универсальной функции или типа.
[P любой] [S-интерфейс{ ~[]байт|строка}] [С ~[]Э, Э любой] [P Ограничение[int]] [_ любой]Так же, как каждый обычный параметр функции имеет тип параметра, каждый параметр типа имеет соответствующий (мета-)тип, который называется его ограничением типа .
Неоднозначность синтаксического анализа возникает, когда список параметров типа для универсального типа объявляет один параметр типа
P
с ограничением,C
таким образом, что текстP C
образует допустимое выражение:тип Т[П *С] … тип Т[П (С)] … тип Т[П *С|Q] … …В этих редких случаях список параметров типа неотличим от выражения, а объявление типа анализируется как объявление типа массива. Чтобы устранить неоднозначность, вставьте ограничение в интерфейс или используйте завершающую запятую:
тип T[P интерфейс{*C}] … тип Т[П *С,] …Параметры типа также могут быть объявлены спецификацией приемника объявления метода , связанного с универсальным типом.
В списке параметров типа универсального типа
T
ограничение типа не может (напрямую или косвенно через список параметров типа другого универсального типа) ссылаться наT
.тип T1[P T1[P]] … // недопустимо: T1 ссылается на себя тип T2[P interface{ T2[int] }] … // неверно: T2 ссылается на себя тип T3[P interface{ m(T3[int])}] … // неверно: T3 ссылается на себя тип T4[P T5[P]] … // недопустимо: T4 ссылается на T5 и тип T5[P T4[P]] … // T5 относится к T4 type T6[P int] struct{ f *T6[P] } // ok: ссылка на T6 отсутствует в списке параметров типаОграничения типа ¶
Ограничение типа — это интерфейс , который определяет набор допустимых аргументов типа для соответствующего параметра типа и управляет операциями, поддерживаемыми значениями этого параметра типа [ Go 1.18 ].
ОграничениеТипа = ЭлементТипа .Если ограничение представляет собой литерал интерфейса в форме,
interface{E}
гдеE
— встроенный элемент типа (не метод), в списке параметров типа включениеinterface{ … }
может быть опущено для удобства:[T []P] // = [T интерфейс{[]P}] [T ~целое] // = [T интерфейс{~целое}] [T целое|строка] // = [T интерфейс{целое|строка}] Ограничение типа ~int // неверно: ~int отсутствует в списке параметров типаПредварительно объявленный тип интерфейса
comparable
обозначает набор всех неинтерфейсных типов, которые строго сопоставимы [ Go 1.18 ].Несмотря на то, что интерфейсы, не являющиеся параметрами типа , сопоставимы , они не являются строго сопоставимыми и, следовательно, не реализуют
comparable
. Однако они удовлетворяютcomparable
.int // реализует сопоставимость (int строго сопоставим) []byte // не реализует сравнение (срезы нельзя сравнивать) interface{} // не реализует сопоставимость (см. выше) interface{ ~int | ~string } // только параметр типа: реализует сопоставимость (типы int, string строго сопоставимы) interface{compatible} // только параметр типа: реализует сопоставимый (сравнимый реализует себя) interface{ ~int | ~[]byte } // только параметр типа: не реализует сопоставимость (срезы не сопоставимы) interface{ ~struct{ any } } // только параметр типа: не реализует сопоставимость (поле any не является строго сопоставимым)Интерфейс
comparable
и интерфейсы, которые (прямо или косвенно) встраиваются,comparable
могут использоваться только как ограничения типа. Они не могут быть типами значений или переменных, или компонентами других, не интерфейсных типов.Удовлетворение ограничения типа ¶
Аргумент типа
T
удовлетворяет ограничению типаC
, еслиT
является элементом набора типов, определенногоC
; т. е. еслиT
реализуетC
. В качестве исключения строго сопоставимое ограничение типа может также удовлетворяться сопоставимым (не обязательно строго сопоставимым) аргументом типа [ Go 1.20 ]. Точнее:Тип T удовлетворяет ограничению,
C
если
T
орудияC
; илиC
можно записать в видеinterface{ comparable; E }
, гдеE
— базовый интерфейс , аT
— сравнимый и реализующийE
.тип аргумент тип ограничение // удовлетворение ограничения int interface{ ~int } // удовлетворено: int реализует interface{ ~int } строка сопоставима // удовлетворено: строка реализует сопоставимость (строка строго сопоставима) []байт сопоставим // не удовлетворено: срезы не сопоставимы любой интерфейс { сопоставимый; int } // не удовлетворен: any не реализует интерфейс { int } любой сопоставимый // удовлетворен: любой сопоставим и реализует базовый интерфейс любой struct{f any} сравнима // удовлетворена: struct{f any} сравнима и реализует базовый интерфейс any любой интерфейс { сопоставимый; m() } // не удовлетворен: любой не реализует базовый интерфейс interface{ m() } interface{ m() } interface{ Compare; m() } // удовлетворено: interface{ m() } сопоставим и реализует базовый интерфейс interface{ m() }Из-за исключения в правиле удовлетворения ограничений сравнение операндов типа параметра типа может привести к панике во время выполнения (даже несмотря на то, что сравнимые параметры типа всегда строго сравнимы).
Объявления переменных ¶
Объявление переменной создает одну или несколько переменных , связывает с ними соответствующие идентификаторы и присваивает каждой из них тип и начальное значение.
VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) . VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .var i int вар U, V, W float64 вар к = 0 переменная x, y float32 = -1, -2 вар ( я инт u, v, s = 2,0, 3,0, "бар" ) var re, im = complexSqrt(-1) var _, found = entry[name] // поиск по карте; интересует только «found»Если задан список выражений, переменные инициализируются выражениями, следуя правилам для операторов присваивания . В противном случае каждая переменная инициализируется своим нулевым значением .
Если тип присутствует, каждой переменной присваивается этот тип. В противном случае каждой переменной присваивается тип соответствующего значения инициализации в назначении. Если это значение является нетипизированной константой, оно сначала неявно преобразуется в свой тип по умолчанию ; если это нетипизированное логическое значение, оно сначала неявно преобразуется в тип
bool
. Предварительно объявленное значениеnil
не может использоваться для инициализации переменной без явного типа.var d = math.Sin(0.5) // d равно float64 var i = 42 // i — целое число var t, ok = x.(T) // t это T, ok это bool var n = nil // недопустимоОграничение реализации: компилятор может сделать незаконным объявление переменной внутри тела функции , если эта переменная никогда не используется.
Короткие объявления переменных ¶
Краткое объявление переменной использует следующий синтаксис:
ShortVarDecl = СписокИдентификаторов ":=" СписокВыражений .Это сокращение для обычного объявления переменной с выражениями инициализатора, но без типов:
"var" СписокИдентификаторов "=" СписокВыражений .я, j := 0, 10 f := func() int { return 7 } ch := make(chan int) r, w, _ := os.Pipe() // os.Pipe() возвращает связанную пару файлов и ошибку, если таковая имеется _, y, _ := coord(p) // coord() возвращает три значения; интересует только координата yВ отличие от обычных объявлений переменных, краткое объявление переменных может переобъявлять переменные при условии, что они были изначально объявлены ранее в том же блоке (или в списках параметров, если блок является телом функции) с тем же типом, и по крайней мере одна из непустых переменных является новой. Как следствие, переобъявление может появляться только в кратком объявлении с несколькими переменными. Переобъявление не вводит новую переменную; оно просто присваивает новое значение исходному. Непустые имена переменных в левой части
:=
должны быть уникальными .поле1, смещение := nextField(str, 0) field2, offset := nextField(str, offset) // переопределяет offset x, y, x := 1, 2, 3 // неправильно: x повторяется слева от :=Короткие объявления переменных могут появляться только внутри функций. В некоторых контекстах, таких как инициализаторы для операторов «if» , «for» или «switch» , их можно использовать для объявления локальных временных переменных.
Объявления функций ¶
Объявление функции связывает идентификатор, имя функции , с функцией.
FunctionDecl = "func" ИмяФункции [ ПараметрыТипа ] Подпись [ ТелоФункции ]. ИмяФункции = идентификатор . ТелоФункции = Блок .Если в сигнатуре функции объявлены параметры результата, список операторов тела функции должен заканчиваться завершающим оператором .
func IndexRune(строка s, руна r) int { для i, c := диапазон s { если с == г { вернуть я } } // недействительно: отсутствует оператор возврата }Если в объявлении функции указаны параметры типа , имя функции обозначает обобщенную функцию . Обобщенная функция должна быть инстанцирована , прежде чем ее можно будет вызвать или использовать в качестве значения.
функция min[T ~int|~float64](x, y T) T { если х < у { вернуть х } вернуть y }Объявление функции без параметров типа может опускать тело. Такое объявление предоставляет сигнатуру для функции, реализованной вне Go, например, для процедуры сборки.
func flushICache(begin, end uintptr) // реализовано внешнеОбъявления методов ¶
Метод — это функция с получателем . Объявление метода связывает идентификатор, имя метода , с методом и связывает метод с базовым типом получателя .
MethodDecl = "func" Получатель ИмяМетода Сигнатура [ ТелоФункции ]. Получатель = Параметры .Получатель указывается с помощью дополнительного раздела параметров, предшествующего имени метода. Этот раздел параметров должен объявлять один невариативный параметр, получатель. Его тип должен быть определенным типом
T
или указателем на определенный типT
, за которым может следовать список имен параметров типа,[P1, P2, …]
заключенный в квадратные скобки.T
называется базовым типом получателя . Базовый тип получателя не может быть указателем или типом интерфейса и должен быть определен в том же пакете, что и метод. Говорят, что метод привязан к своему базовому типу получателя, а имя метода видно только внутри селекторов для типаT
или*T
.Непустой идентификатор приемника должен быть уникальным в сигнатуре метода. Если значение приемника не упоминается внутри тела метода, его идентификатор может быть опущен в объявлении. То же самое относится в целом к параметрам функций и методов .
Для базового типа непустые имена методов, связанных с ним, должны быть уникальными. Если базовый тип — это тип структуры , непустые имена методов и полей должны быть разными.
Учитывая определенный тип,
Point
объявленияфункция (p *Point) Длина() float64 { вернуть math.Sqrt(px * px + py * py) } func (p *Point) Масштаб(фактор float64) { пикс *= фактор py *= фактор }свяжите методы
Length
иScale
, с типом приемника*Point
, с базовым типомPoint
.Если базовый тип приемника является универсальным типом , спецификация приемника должна объявлять соответствующие параметры типа для используемого метода. Это делает параметры типа приемника доступными для метода. Синтаксически это объявление параметра типа выглядит как инстанцирование базового типа приемника: аргументы типа должны быть идентификаторами, обозначающими объявляемые параметры типа, по одному для каждого параметра типа базового типа приемника. Имена параметров типа не обязательно должны совпадать с соответствующими именами параметров в определении базового типа приемника, и все непустые имена параметров должны быть уникальными в разделе параметров приемника и сигнатуре метода. Ограничения параметров типа приемника подразумеваются определением базового типа приемника: соответствующие параметры типа имеют соответствующие ограничения.
тип Пара[A, B любой] структура { а А б Б } func (p Pair[A, B]) Swap() Pair[B, A] { … } // приемник объявляет A, B func (p Pair[First, _]) First() First { … } // приемник объявляет First, соответствует A в PairВыражения ¶
Выражение определяет вычисление значения путем применения операторов и функций к операндам.
Операнды ¶
Операнды обозначают элементарные значения в выражении. Операнд может быть литералом, (возможно, квалифицированным ) непустым идентификатором , обозначающим константу , переменную или функцию , или выражением в скобках.
Операнд = Литерал | ИмяОперанда [ TypeArgs ] | "(" Выражение ")" . Литерал = БазовыйЛит | СоставнойЛит | ФункцияЛит . БазовыйЛит = int_lit | float_lit | imaginary_lit | rune_lit | string_lit . ИмяОперанда = идентификатор | КвалифицированныйИдент .За именем операнда, обозначающим универсальную функцию , может следовать список аргументов типа ; результирующий операнд представляет собой инстанцированную функцию.
Пустой идентификатор может появляться в качестве операнда только в левой части оператора присваивания .
Ограничение реализации: Компилятору не нужно сообщать об ошибке, если тип операнда является параметром типа с пустым набором типов . Функции с такими параметрами типа не могут быть инстанциированы ; любая попытка приведет к ошибке на месте инстанцирования.
Квалифицированные идентификаторы ¶
Квалифицированный идентификатор — это идентификатор, квалифицированный с префиксом имени пакета. Имя пакета и идентификатор не должны быть пустыми .
QualifiedIdent = PackageName "." идентификатор .Квалифицированный идентификатор обращается к идентификатору в другом пакете, который должен быть импортирован . Идентификатор должен быть экспортирован и объявлен в блоке пакета этого пакета.
math.Sin // обозначает функцию Sin в пакете mathСоставные литералы ¶
Составные литералы создают новые составные значения каждый раз, когда они оцениваются. Они состоят из типа литерала, за которым следует список элементов, заключенный в фигурные скобки. Каждому элементу может предшествовать соответствующий ключ.
CompositeLit = LiteralType LiteralValue . LiteralType = StructType | ArrayType | "[" "..." "]" ElementType | SliceType | MapType | TypeName [ TypeArgs ] . LiteralValue = "{" [ ElementList [ "," ] ] "}" . ElementList = KeyedElement { "," KeyedElement } . KeyedElement = [ Key ":" ] Element . Key = FieldName | Expression | LiteralValue . FieldName = identifier . Element = Expression | LiteralValue .Основной тип LiteralType
T
должен быть типом структуры, массива, среза или карты (синтаксис обеспечивает это ограничение, за исключением случаев, когда тип задан как TypeName). Типы элементов и ключей должны быть назначаемыми соответствующим типам полей, элементов и ключей типаT
; дополнительное преобразование не выполняется. Ключ интерпретируется как имя поля для литералов структуры, индекс для литералов массива и среза и ключ для литералов карты. Для литералов карты все элементы должны иметь ключ. Указывать несколько элементов с одинаковым именем поля или постоянным значением ключа является ошибкой. Для непостоянных ключей карты см. раздел о порядке вычисления .Для структурных литералов применяются следующие правила:
- Ключ должен быть именем поля, объявленным в типе структуры.
- Список элементов, не содержащий ключей, должен содержать элемент для каждого поля структуры в том порядке, в котором поля объявлены.
- Если какой-либо элемент имеет ключ, то каждый элемент должен иметь ключ.
- Список элементов, содержащий ключи, не обязательно должен иметь элемент для каждого поля структуры. Пропущенные поля получают нулевое значение для этого поля.
- Литерал может не содержать список элементов; такой литерал оценивается как нулевое значение для своего типа.
- Указание элемента для неэкспортированного поля структуры, принадлежащей другому пакету, является ошибкой.
Учитывая заявления
тип Point3D структура { x, y, z float64 } тип Линия структура { p, q Point3D }можно написать
origin := Point3D{} // нулевое значение для Point3D line := Line{origin, Point3D{y: -4, z: 12.3}} // нулевое значение для line.qxДля литералов массивов и срезов применяются следующие правила:
- Каждый элемент имеет связанный с ним целочисленный индекс, обозначающий его положение в массиве.
- Элемент с ключом использует ключ в качестве своего индекса. Ключ должен быть неотрицательной константой, представляемой значением типа
int
; и если он типизирован, он должен быть целочисленного типа .- Элемент без ключа использует индекс предыдущего элемента плюс один. Если у первого элемента нет ключа, его индекс равен нулю.
При получении адреса составного литерала генерируется указатель на уникальную переменную, инициализированную значением литерала.
указатель var *Point3D = &Point3D{y: 1000}Обратите внимание, что нулевое значение для типа среза или карты не совпадает с инициализированным, но пустым значением того же типа. Следовательно, взятие адреса пустого среза или составного литерала карты не имеет того же эффекта, что и выделение нового значения среза или карты с помощью new .
p1 := &[]int{} // p1 указывает на инициализированный пустой срез со значением []int{} и длиной 0 p2 := new([]int) // p2 указывает на неинициализированный срез со значением nil и длиной 0Длина литерала массива — это длина, указанная в типе литерала. Если в литерале указано меньше элементов, чем длина, отсутствующие элементы устанавливаются в нулевое значение для типа элемента массива. Предоставление элементов со значениями индекса за пределами диапазона индекса массива является ошибкой. Нотация
...
указывает длину массива, равную максимальному индексу элемента плюс один.буфер := [10]строка{} // длина(буфер) == 10 intSet := [6]int{1, 2, 3, 5} // len(intSet) == 6 days := [...]string{"Сб", "Вс"} // len(days) == 2Литерал среза описывает весь базовый литерал массива. Таким образом, длина и емкость литерала среза — это максимальный индекс элемента плюс один. Литерал среза имеет вид
[]Т{x1, x2, … xn}и является сокращением для операции среза, применяемой к массиву:
tmp := [n]T{x1, x2, … xn} тмп[0 : н]В составном литерале типа массива, среза или карты
T
элементы или ключи карты, которые сами являются составными литералами, могут опускать соответствующий тип литерала, если он идентичен типу элемента или ключаT
. Аналогично элементы или ключи, которые являются адресами составных литералов, могут опускать ,&T
если тип элемента или ключа равен*T
.[...]Point{{1.5, -3.5}, {0, 0}} // то же самое, что [...]Point{Point{1.5, -3.5}, Point{0, 0}} [][]int{{1, 2, 3}, {4, 5}} // то же самое, что [][]int{[]int{1, 2, 3}, []int{4, 5}} [][]Точка{{{0, 1}, {1, 2}}} // то же самое, что и [][]Точка{[]Точка{Точка{0, 1}, Точка{1, 2}}} map[string]Point{"orig": {0, 0}} // то же самое, что map[string]Point{"orig": Point{0, 0}} map[Point]string{{0, 0}: "orig"} // то же самое, что map[Point]string{Point{0, 0}: "orig"} тип PPoint *Point [2]*Point{{1.5, -3.5}, {}} // то же, что и [2]*Point{&Point{1.5, -3.5}, &Point{}} [2]PPoint{{1.5, -3.5}, {}} // то же самое, что и [2]PPoint{PPoint(&Point{1.5, -3.5}), PPoint(&Point{})}Неоднозначность анализа возникает, когда составной литерал, использующий форму TypeName LiteralType, появляется как операнд между ключевым словом и открывающей фигурной скобкой блока оператора «if», «for» или «switch», и составной литерал не заключен в круглые, квадратные или фигурные скобки. В этом редком случае открывающая фигурная скобка литерала ошибочно анализируется как вводная для блока операторов. Чтобы разрешить неоднозначность, составной литерал должен появляться в круглых скобках.
если х == (Т{а,б,в}[я]) { … } если (x == T{a,b,c}[i]) { … }Примеры допустимых литералов массива, среза и карты:
// список простых чисел простые числа := []целое число{2, 3, 5, 7, 9, 2147483647} // vowels[ch] истинно, если ch — гласная гласные := [128]bool{'a': правда, 'e': правда, 'i': правда, 'o': правда, 'u': правда, 'y': правда} // массив [10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1} фильтр := [10]float32{-1, 4: -0.1, -0.1, 9: -1} // частоты в Гц для равномерно темперированного строя (A4 = 440 Гц) noteFrequency := map[string]float32{ "C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83, "G0": 24,50, "A0": 27,50, "B0": 30,87, }Функциональные литералы ¶
Функциональный литерал представляет анонимную функцию . Функциональные литералы не могут объявлять параметры типа.
FunctionLit = "func" Подпись FunctionBody .func(a, b int, z float64) bool { return a*b < int(z) }Функциональный литерал можно присвоить переменной или вызвать напрямую.
f := func(x, y int) int { return x + y } func(ch chan int) { ch <- ACK }(replyChan)Функциональные литералы являются замыканиями : они могут ссылаться на переменные, определенные в окружающей функции. Затем эти переменные совместно используются окружающей функцией и функциональным литералом, и они существуют до тех пор, пока они доступны.
Первичные выражения ¶
Первичные выражения — это операнды для унарных и бинарных выражений.
PrimaryExpr = Операнд | Преобразование | MethodExpr | Селектор PrimaryExpr | Индекс PrimaryExpr | Срез PrimaryExpr | Утверждение типа PrimaryExpr | Аргументы PrimaryExpr . Селектор = "." идентификатор . Индекс = "[" Выражение [ "," ] "]" . Срез = "[" [ Выражение ] ":" [ Выражение ] "]" | "[" [ Выражение ] ":" Выражение ":" Выражение "]" . TypeAssertion = "." "(" Тип ")" . Arguments = "(" [ ( ExpressionList | Тип [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .х 2 (с + ".txt") f(3.1415, правда) Точка{1, 2} м["фу"] с[я : j + 1] объект.цвет fp[i].x()Селекторы ¶
Для первичного выражения
x
, которое не является именем пакета , выражение селекторахфобозначает поле или метод
f
значенияx
(или иногда*x
; см. ниже). Идентификаторf
называется селектором (поля или метода) ; он не должен быть пустым идентификатором . Тип выражения селектора — это типf
. Еслиx
— имя пакета, см. раздел о квалифицированных идентификаторах .Селектор
f
может обозначать поле или методf
типаT
, или он может ссылаться на поле или методf
вложенного встроенного поля .T
Количество встроенных полей, пройденных для достижения,f
называется его глубиной вT
. Глубина поля или метода,f
объявленного в ,T
равна нулю. Глубина поля или метода,f
объявленного во встроенном полеA
в ,T
равна глубинеf
вA
плюс один.К селекторам применяются следующие правила:
- Для значения
x
типаT
или ,*T
гдеT
не является указателем или типом интерфейса,x.f
обозначает поле или метод на самой мелкой глубине,T
где есть такойf
. Если нет точно одногоf
с самой мелкой глубиной, выражение селектора является недопустимым.- Для значения
x
типаI
, гдеI
— тип интерфейса,x.f
обозначает фактический метод с именемf
динамического значения . Если в наборе методовx
нет метода с именем , выражение селектора является недопустимым.f
I
- В качестве исключения, если тип
x
является определенным типом указателя и(*x).f
является допустимым выражением селектора, обозначающим поле (но не метод),x.f
является сокращением для(*x).f
.- Во всех остальных случаях
x.f
это незаконно.- Если
x
имеет тип указателя и имеет значениеnil
иx.f
обозначает поле структуры, то присвоение или вычислениеx.f
вызывает панику во время выполнения .- Если
x
имеет тип интерфейса и имеет значениеnil
, вызов или оценка методаx.f
приводит к панике во время выполнения .Например, учитывая заявления:
тип T0 структура { х инт } функция (*T0) M0() тип T1 структура { у инт } функция (T1) M1() тип T2 структура { z инт Т1 *Т0 } функция (*T2) M2() тип Q *T2 var t T2 // с t.T0 != nil var p *T2 // где p != nil и (*p).T0 != nil вар q Q = pможно написать:
тц // тц ty // т.T1.y tx // (*t.T0).x пз // (*п).з py // (*p).T1.y пикс // (*(*p).T0).x qx // (*(*q).T0).x (*q).x — допустимый селектор поля p.M0() // ((*p).T0).M0() M0 ожидает получателя *T0 p.M1() // ((*p).T1).M1() M1 ожидает получателя T1 p.M2() // p.M2() M2 ожидает получателя *T2 t.M2() // (&t).M2() M2 ожидает получателя *T2, см. раздел Вызовыно следующее недействительно:
q.M0() // (*q).M0 допустим, но не является селектором поляМетод выражений ¶
Если
M
находится в наборе методов типаT
,T.M
то это функция, которая может быть вызвана как обычная функция с теми же аргументами, что иM
с префиксом в виде дополнительного аргумента, который является получателем метода.MethodExpr = ReceiverType "." ИмяМетода . ТипПриемника = Тип .Рассмотрим структурный тип
T
с двумя методами,Mv
, получатель которого имеет типT
, иMp
, получатель которого имеет тип*T
.тип T структура { целое число } func (tv T) Mv(a int) int { return 0 } // приемник значения func (tp *T) Mp(f float32) float32 { return 1 } // указатель-приемник вар т ТВыражение
Т.Мввыдает функцию, эквивалентную
Mv
, но с явным получателем в качестве первого аргумента; она имеет сигнатуруfunc(tv T, a int) intЭту функцию можно вызвать обычным образом с явным получателем, поэтому эти пять вызовов эквивалентны:
т.Мв(7) Т.Мв(т, 7) (Т).Мв(т, 7) f1 := T.Mv; f1(t, 7) f2 := (T).Mv; f2(t, 7)Аналогично выражение
(*Т).Мпвыдает значение функции, представляющее
Mp
сигнатуруфункция(tp *T, f float32) float32Для метода с получателем значения можно вывести функцию с явным получателем указателя, так что
(*Т).Мввыдает значение функции, представляющее
Mv
сигнатуруfunc(tv *T, a int) intТакая функция обращается к получателю, чтобы создать значение, которое будет передано в качестве получателя базовому методу; метод не перезаписывает значение, адрес которого передается в вызове функции.
Последний случай — функция-получатель значения для метода-указателя-получателя — недопустим, поскольку методы-указатели-получатели не входят в набор методов типа значения.
Значения функций, полученные из методов, вызываются с помощью синтаксиса вызова функции; получатель предоставляется в качестве первого аргумента вызова. То есть, заданный
f := T.Mv
,f
вызывается какf(t, 7)
неt.f(7)
. Чтобы создать функцию, которая связывает получателя, используйте литерал функции или значение метода .Допустимо выводить значение функции из метода типа интерфейса. Результирующая функция принимает явный приемник этого типа интерфейса.
Значения метода ¶
Если выражение
x
имеет статический типT
иM
находится в наборе методов типаT
,x.M
называется значением метода . Значение методаx.M
— это значение функции, которое может быть вызвано с теми же аргументами, что и вызов методаx.M
. Выражениеx
вычисляется и сохраняется во время вычисления значения метода; затем сохраненная копия используется в качестве приемника в любых вызовах, которые могут быть выполнены позже.тип S структура { *T } тип T инт функ (t T) M() { print(t) } т := новый(Т) с := С{Т: т} f := tM // приемник *t оценивается и сохраняется в f g := sM // приемник *(sT) оценивается и сохраняется в g *t = 42 // не влияет на сохраненные приемники в f и gТип
T
может быть интерфейсным или неинтерфейсным.Как и при обсуждении выражений методов выше, рассмотрим тип структуры
T
с двумя методами,Mv
, получатель которого имеет типT
, иMp
, получатель которого имеет тип*T
.тип T структура { целое число } func (tv T) Mv(a int) int { return 0 } // приемник значения func (tp *T) Mp(f float32) float32 { return 1 } // указатель-приемник вар т Т вар пт *Т функция makeT() TВыражение
т.Мввыдает значение функции типа
функ(целое) целоеЭти два вызова эквивалентны:
т.Мв(7) f := t.Mv; f(7)Аналогично выражение
пт.МПвыдает значение функции типа
функция(float32) float32Как и в случае с селекторами , ссылка на неинтерфейсный метод с получателем значения, использующим указатель, автоматически разыменует этот указатель:
pt.Mv
эквивалентно(*pt).Mv
.Как и в случае с вызовами методов , ссылка на неинтерфейсный метод с получателем указателя, использующим адресуемое значение, автоматически примет адрес этого значения:
t.Mp
эквивалентно(&t).Mp
.f := t.Mv; f(7) // как t.Mv(7) f := pt.Mp; f(7) // как pt.Mp(7) f := pt.Mv; f(7) // как (*pt).Mv(7) f := t.Mp; f(7) // как (&t).Mp(7) f := makeT().Mp // недопустимо: результат makeT() не адресуетсяХотя в приведенных выше примерах используются типы, не являющиеся интерфейсами, также допустимо создание значения метода из значения типа интерфейса.
var i интерфейс { M(int) } = myVal f := iM; f(7) // как iM(7)Индексные выражения ¶
Первичное выражение формы
а[х]обозначает элемент массива, указатель на массив, срез, строку или карту,
a
индексированнуюx
. Значениеx
называется индексом или ключом карты , соответственно. Применяются следующие правила:Если
a
это не параметр ни карты, ни типа:
- индекс
x
должен быть нетипизированной константой или его основной тип должен быть целым числом- константный индекс должен быть неотрицательным и представляться значением типа
int
- константный индекс, который не типизирован, имеет тип
int
- индекс
x
находится в диапазоне , если0 <= x < len(a)
, в противном случае он находится вне диапазонаДля
a
типа массиваA
:
- константный индекс должен находиться в диапазоне
- если
x
во время выполнения выходит за пределы диапазона, возникает паника во время выполненияa[x]
— это элемент массива по индексуx
, а тип —a[x]
это тип элементаA
Для
a
указателя на тип массива :
a[x]
это сокращение от(*a)[x]
Для
a
типа ломтикаS
:
- если
x
во время выполнения выходит за пределы диапазона, возникает паника во время выполненияa[x]
является элементом среза по индексуx
, а типa[x]
является типом элементаS
Для
a
строкового типа :
- константный индекс должен быть в диапазоне , если строка
a
также является константой- если
x
во время выполнения выходит за пределы диапазона, возникает паника во время выполненияa[x]
— это непостоянное значение байта по индексуx
, а типa[x]
—byte
a[x]
не может быть назначенДля
a
типа картыM
:
x
тип должен быть назначен ключевому типуM
- если карта содержит запись с ключом
x
,a[x]
является элементом карты с ключомx
, а типa[x]
является типом элементаM
- если карта содержит
nil
или не содержит такую запись,a[x]
является ли нулевым значением для типа элементаM
Для
a
типа параметра typeP
:
- Выражение индекса
a[x]
должно быть допустимым для значений всех типов вP
наборе типов.- Типы элементов всех типов в
P
наборе типов должны быть идентичны. В этом контексте тип элемента строкового типа —byte
.- Если в наборе типов есть тип карты
P
, все типы в этом наборе типов должны быть типами карты, а соответствующие ключевые типы должны быть идентичными.a[x]
— это элемент массива, среза или строки по индексуx
или элемент карты с ключомx
аргумента типа,P
с которым создается экземпляр, а типa[x]
— это тип (идентичных) типов элементов.a[x]
не может быть назначен, еслиP
набор типов включает строковые типы.В противном случае
a[x]
это незаконно.Индексное выражение на карте
a
типа,map[K]V
используемое в операторе присваивания или инициализации специальной формыv, ок = а[х] v, ок := a[x] вар v, ок = а[х]выдает дополнительное нетипизированное логическое значение. Значение
ok
is,true
если ключx
присутствует в карте, иfalse
в противном случае.Присвоение элементу
nil
карты вызывает панику во время выполнения .Выражения среза ¶
Выражения среза создают подстроку или срез из строки, массива, указателя на массив или среза. Существует два варианта: простая форма, которая указывает нижнюю и верхнюю границу, и полная форма, которая также указывает границу емкости.
Простые выражения среза
Первичное выражение
а[низкий : высокий]создает подстроку или срез. Основной тип должен
a
быть строкой, массивом, указателем на массив, срезом илиbytestring
. Индексыlow
иhigh
выбирают, какие элементы операндаa
появятся в результате. Результат имеет индексы, начинающиеся с 0, и длину, равнуюhigh
-low
. После среза массиваa
а := [5]целое{1, 2, 3, 4, 5} с := а[1:4]срез
s
имеет тип[]int
, длину 3, емкость 4 и элементыс[0] == 2 с[1] == 3 с[2] == 4Для удобства любой из индексов может быть опущен. Отсутствующий
low
индекс по умолчанию равен нулю; отсутствующийhigh
индекс по умолчанию равен длине срезанного операнда:a[2:] // то же самое, что и a[2 : len(a)] a[:3] // то же самое, что и a[0 : 3] a[:] // то же самое, что и a[0 : len(a)]Если
a
это указатель на массив,a[low : high]
то это сокращение для(*a)[low : high]
.Для массивов или строк индексы находятся в диапазоне, если
0
<=low
<=high
<=len(a)
, в противном случае они находятся вне диапазона . Для срезов верхняя граница индекса — это емкость среза,cap(a)
а не длина. Константный индекс должен быть неотрицательным и представимым значением типаint
; для массивов или константных строк константные индексы также должны находиться в диапазоне. Если оба индекса являются константами, они должны удовлетворятьlow <= high
. Если индексы находятся вне диапазона во время выполнения, возникает паника во время выполнения .За исключением нетипизированных строк , если срезанный операнд является строкой или срезом, результатом операции среза является неконстантное значение того же типа, что и операнд. Для нетипизированных строковых операндов результатом является неконстантное значение типа
string
. Если срезанный операнд является массивом, он должен быть адресуемым , а результатом операции среза является срез с тем же типом элемента, что и массив.Если срезанный операнд допустимого выражения среза является срезом
nil
, результатом является срезnil
. В противном случае, если результат является срезом, он разделяет свой базовый массив с операндом.вар а [10]целое s1 := a[3:7] // базовый массив s1 — это массив a; &s1[2] == &a[5] s2 := s1[1:4] // базовый массив s2 является базовым массивом s1, который является массивом a; &s2[1] == &a[5] s2[1] = 42 // s2[1] == s1[2] == a[5] == 42; все они ссылаются на один и тот же базовый элемент массива var s []целое s3 := s[:0] // s3 == нольПолные выражения среза
Первичное выражение
а[низкий : высокий : максимальный]создает срез того же типа, с той же длиной и элементами, что и простое выражение среза
a[low : high]
. Кроме того, он управляет емкостью результирующего среза, устанавливая его вmax - low
. Только первый индекс может быть опущен; по умолчанию он равен 0. Основной тип долженa
быть массивом, указателем на массив или срезом (но не строкой). После среза массиваa
а := [5]целое{1, 2, 3, 4, 5} т := а[1:3:5]срез
t
имеет тип[]int
, длину 2, емкость 4 и элементыт[0] == 2 т[1] == 3Что касается простых выражений среза, если
a
это указатель на массив,a[low : high : max]
это сокращение для(*a)[low : high : max]
. Если операнд среза является массивом, он должен быть адресуемым .Индексы находятся в диапазоне, если
0 <= low <= high <= max <= cap(a)
, в противном случае они находятся вне диапазона . Константный индекс должен быть неотрицательным и представимым значением типаint
; для массивов константные индексы также должны находиться в диапазоне. Если несколько индексов являются константами, присутствующие константы должны находиться в диапазоне относительно друг друга. Если индексы находятся вне диапазона во время выполнения, возникает паника во время выполнения .Утверждения типа ¶
Для выражения
x
типа интерфейса , но не параметра типа , и типаT
, первичное выражениех.(Т)утверждает, что
x
это не такnil
и что значение, хранящееся в,x
имеет типT
. Нотацияx.(T)
называется утверждением типа .Точнее, если
T
не является типом интерфейса,x.(T)
утверждает, что динамический типx
идентичен типу . В этом случае должен реализовывать тип (интерфейса) ; в противном случае утверждение типа недействительно, поскольку невозможно сохранить значение типа . Если является типом интерфейса, утверждает, что динамический тип реализует интерфейс .T
T
x
x
T
T
x.(T)
x
T
Если утверждение типа выполняется, значение выражения — это значение, хранящееся в
x
, а его тип —T
. Если утверждение типа ложно, происходит паника во время выполнения . Другими словами, даже если динамический тип известен только во время выполнения, известно, чтоx
тип находится в правильной программе.x.(T)
T
var x interface{} = 7 // x имеет динамический тип int и значение 7 i := x.(int) // i имеет тип int и значение 7 интерфейс типа I { m() } функция f(y I) { s := y.(string) // неверно: string не реализует I (отсутствует метод m) r := y.(io.Reader) // r имеет тип io.Reader, а динамический тип y должен реализовывать как I, так и io.Reader … }Утверждение типа, используемое в операторе присваивания или инициализации специальной формы
v, ок = х.(Т) v, ок := x.(T) вар v, ок = х.(Т) var v, ok interface{} = x.(T) // динамические типы v и ok — это T и boolвыдает дополнительное нетипизированное логическое значение. Значение
ok
равно ,true
если утверждение выполняется. В противном случае оно равноfalse
, а значениеv
равно нулю для типаT
. В этом случае паника во время выполнения не возникает.Звонки ¶
Дано выражение
f
с основным типомF
функции type ,f(a1, a2, … an)вызовы
f
с аргументамиa1, a2, … an
. За исключением одного особого случая, аргументы должны быть однозначными выражениями , назначаемыми типам параметровF
и вычисляемыми до вызова функции. Тип выражения — это тип результатаF
. Вызов метода аналогичен, но сам метод указывается как селектор по значению типа приемника для метода.math.Atan2(x, y) // вызов функции вар пт *Точка pt.Scale(3.5) // вызов метода с приемником ptЕсли
f
обозначает универсальную функцию, то ее необходимо создать, прежде чем ее можно будет вызвать или использовать в качестве значения функции.При вызове функции значение функции и аргументы оцениваются в обычном порядке . После оценки параметры вызова передаются по значению в функцию, и вызываемая функция начинает выполнение. Возвращаемые параметры функции передаются по значению обратно вызывающей стороне, когда функция возвращается.
Вызов
nil
значения функции приводит к панике во время выполнения .В качестве особого случая, если возвращаемые значения функции или метода
g
равны по количеству и индивидуально назначаются параметрам другой функции или методаf
, то вызов будет вызван после связывания возвращаемых значений с параметрами по порядку. Вызов не должен содержать никаких параметров, кроме вызова , и должен иметь по крайней мере одно возвращаемое значение. Если имеет конечный параметр, ему назначаются возвращаемые значения , которые остаются после назначения обычных параметров.f(g(parameters_of_g))
f
g
f
f
g
g
f
...
g
func Split(s string, pos int) (string, string) { вернуть s[0:поз], s[поз:] } функция Join(s, t строка) строка { вернуть с + т } если Join(Split(значение, len(значение)/2)) != значение { log.Panic("тест не пройден") }Вызов метода
x.m()
допустим, если набор методов (тип)x
содержитm
, а список аргументов может быть назначен списку параметровm
. Еслиx
адресуем и набор методов содержит , является сокращением для :&x
m
x.m()
(&x).m()
вар п Точка стр.Масштаб(3.5)Не существует отдельного типа метода и не существует литералов метода.
Передача аргументов
...
параметрам ¶Если
f
является вариативным с конечным параметромp
типа...T
, то внутриf
типаp
эквивалентно типу[]T
. Еслиf
вызывается без фактических аргументов дляp
, переданное значениеp
равноnil
. В противном случае переданное значение является новым срезом типа[]T
с новым базовым массивом, последовательные элементы которого являются фактическими аргументами, которые все должны быть назначеныT
. Длина и емкость среза, таким образом, являются числом аргументов, привязанных к , и могут различаться дляp
каждого места вызова.Учитывая функцию и вызовы
func Greeting(префиксная строка, кто ...строка) Приветствие("никто") Приветствие("привет:", "Джо", "Анна", "Эйлин")внутри
Greeting
,who
будет иметь значениеnil
и в первом вызове, и[]string{"Joe", "Anna", "Eileen"}
во втором.Если последний аргумент может быть назначен типу среза
[]T
и за ним следует...
, он передается без изменений как значение параметра...T
. В этом случае новый срез не создается.Учитывая срез
s
и призывs := []string{"Джеймс", "Жасмин"} Приветствие("до свидания:", с...)внутри будет иметь то же значение, что и
Greeting
с тем же базовым массивом.who
s
Инстанцирования ¶
Обобщенная функция или тип инстанцируется путем замены аргументов типа на параметры типа [ Go 1.18 ]. Инстанцирование происходит в два этапа:
- Каждый аргумент типа заменяется на соответствующий ему параметр типа в обобщенном объявлении. Эта замена происходит по всей функции или объявлению типа, включая сам список параметров типа и любые типы в этом списке.
- После подстановки каждый аргумент типа должен удовлетворять ограничению (при необходимости инстанцированному) соответствующего параметра типа. В противном случае инстанцирование не удается .
Создание экземпляра типа приводит к созданию нового неуниверсального именованного типа ; создание экземпляра функции приводит к созданию новой неуниверсальной функции.
список параметров типа аргументы типа после подстановки [P any] int int удовлетворяет любому [S ~[]E, E любой] []int, int []int удовлетворяет ~[]int, int удовлетворяет любой [P io.Writer] строка недопустима: строка не удовлетворяет io.Writer [P сопоставимый] любой любой удовлетворяет (но не реализует) сопоставимыйПри использовании обобщенной функции аргументы типа могут быть указаны явно или могут быть частично или полностью выведены из контекста, в котором используется функция. При условии, что они могут быть выведены, списки аргументов типа могут быть полностью опущены, если функция:
- вызванный с обычными аргументами,
- присвоено переменной с известным типом
- передано как аргумент другой функции, или
- в результате вернули .
Во всех остальных случаях должен присутствовать (возможно частичный) список аргументов типа. Если список аргументов типа отсутствует или частичный, все отсутствующие аргументы типа должны быть выведены из контекста, в котором используется функция.
// sum возвращает сумму (конкатенацию для строк) своих аргументов. func sum[T ~int | ~float64 | ~string](x... T) T { … } x := sum // неверно: тип x неизвестен intSum := sum[int] // intSum имеет тип func(x... int) int a := intSum(2, 3) // a имеет значение 5 типа int b := sum[float64](2.0, 3) // b имеет значение 5.0 типа float64 c := sum(b, -1) // c имеет значение 4.0 типа float64 тип sumFunc func(x... строка) строка var f sumFunc = sum // то же, что var f sumFunc = sum[string] f = сумма // то же самое, что f = сумма[строка]Частичный список аргументов типа не может быть пустым; по крайней мере первый аргумент должен присутствовать. Список является префиксом полного списка аргументов типа, оставляя остальные аргументы для вывода. Грубо говоря, аргументы типа могут быть опущены «справа налево».
функция применить[S ~[]E, E любой](s S, f функция(E) E) S { … } f0 := apply[] // недопустимо: список аргументов типа не может быть пустым f1 := apply[[]int] // аргумент типа для S указан явно, аргумент типа для E выведен f2 := apply[[]string, string] // оба аргумента типа указаны явно var байты []байт r := apply(bytes, func(byte) byte { … }) // оба аргумента типа выведены из аргументов функцииДля универсального типа все аргументы типа всегда должны быть указаны явно.
Вывод типа ¶
Использование обобщенной функции может опускать некоторые или все аргументы типа, если они могут быть выведены из контекста, в котором используется функция, включая ограничения параметров типа функции. Вывод типа успешен, если он может вывести отсутствующие аргументы типа, и инстанцирование успешно с выведенными аргументами типа. В противном случае вывод типа не удается, и программа недействительна.
Вывод типа использует отношения типов между парами типов для вывода: например, аргумент функции должен быть назначаемым соответствующему параметру функции; это устанавливает связь между типом аргумента и типом параметра. Если любой из этих двух типов содержит параметры типа, вывод типа ищет аргументы типа для замены параметров типа таким образом, чтобы отношение назначаемости было удовлетворено. Аналогично, вывод типа использует тот факт, что аргумент типа должен удовлетворять ограничению своего соответствующего параметра типа.
Каждая такая пара сопоставленных типов соответствует уравнению типа, содержащему один или несколько параметров типа, из одной или, возможно, нескольких универсальных функций. Вывод отсутствующих аргументов типа означает решение результирующего набора уравнений типа для соответствующих параметров типа.
Например, дано
// dedup возвращает копию фрагмента аргумента, из которой удалены все повторяющиеся записи. функция дедупликации[S ~[]E, E сопоставимо](S) S { … } тип Срез []целое var s Срез s = dedup(s) // то же самое, что s = dedup[Slice, int](s)переменная
s
типаSlice
должна быть присваиваемой типу параметра функцииS
, чтобы программа была допустимой. Чтобы уменьшить сложность, вывод типа игнорирует направленность присваиваний, поэтому отношение типа междуSlice
иS
может быть выражено через (симметричное) уравнение типа (или, если на то пошло), где in указывает, что типы LHS и RHS должны соответствовать правилам присваиваемости (подробнее см. в разделе об унификации типов ). Аналогично, параметр типа должен удовлетворять своему ограничению . Это можно выразить как , где означает « удовлетворяет ограничению ». Эти наблюдения приводят к набору из двух уравненийSlice ≡A S
S ≡A Slice
A
≡A
S
~[]E
S ≡C ~[]E
X ≡C Y
X
Y
Срез ≡ A S (1) С ≡ С ~[]Э (2)который теперь может быть решен для параметров типа
S
иE
. Из (1) компилятор может вывести, что аргумент типа дляS
являетсяSlice
. Аналогично, поскольку базовый типSlice
является[]int
и[]int
должен соответствовать[]E
ограничению, компилятор может вывести, чтоE
должно бытьint
. Таким образом, для этих двух уравнений вывод типа выводитS ➞ Слайс E ➞ цел.При наличии набора уравнений типа параметры типа для решения являются параметрами типа функций, которые необходимо инстанцировать и для которых не указаны явные аргументы типа. Эти параметры типа называются связанными параметрами типа. Например, в
dedup
приведенном выше примере параметры типаS
иE
связаны сdedup
. Аргументом вызова универсальной функции может быть сама универсальная функция. Параметры типа этой функции включены в набор связанных параметров типа. Типы аргументов функции могут содержать параметры типа из других функций (например, универсальная функция, охватывающая вызов функции). Эти параметры типа также могут появляться в уравнениях типа, но они не связаны в этом контексте. Уравнения типа всегда решаются только для связанных параметров типа.Вывод типов поддерживает вызовы универсальных функций и назначения универсальных функций (явно типизированным функциям) переменным. Это включает передачу универсальных функций в качестве аргументов другим (возможно, также универсальным) функциям и возврат универсальных функций в качестве результатов. Вывод типов работает с набором уравнений, специфичных для каждого из этих случаев. Уравнения следующие (списки аргументов типов опущены для ясности):
Для вызова функции , где или аргумент функции является обобщенной функцией: Каждая пара соответствующих аргументов функции и параметров, где не является нетипизированной константой, дает уравнение . Если является нетипизированной константой , а является параметром связанного типа , пара собирается отдельно от уравнений типа.
f(a0, a1, …)
f
ai
(ai, pi)
ai
typeof(pi) ≡A typeof(ai)
ai
cj
typeof(pi)
Pk
(cj, Pk)
Для присвоения
v = f
универсальной функцииf
(неуниверсальной) переменнойv
типа функции: .
typeof(v) ≡A typeof(f)
Для оператора возврата,
return …, f, …
гдеf
— это универсальная функция, возвращаемая в качестве результата в (неуниверсальную) результирующую переменнуюr
типа функции: .
typeof(r) ≡A typeof(f)
Кроме того, каждый параметр типа и соответствующее ограничение типа дают уравнение типа .
Pk
Ck
Pk ≡C Ck
Вывод типа отдает приоритет информации о типе, полученной из типизированных операндов, перед рассмотрением нетипизированных констант. Таким образом, вывод происходит в два этапа:
Уравнения типов решаются для связанных параметров типов с использованием унификации типов . Если унификация не удалась, вывод типа не удаётся.
Для каждого связанного параметра типа , для которого еще не выведен аргумент типа и для которого собраны одна или несколько пар с тем же параметром типа, определите тип константы во всех этих парах таким же образом, как для константных выражений . Аргумент типа для является типом по умолчанию для определенного типа константы. Если тип константы не может быть определен из-за конфликтующих типов констант, вывод типа завершается неудачей.
Pk
(cj, Pk)
cj
Pk
Если после этих двух этапов не все аргументы типа были найдены, вывод типа завершается неудачей.
Если обе фазы прошли успешно, вывод типа определяет аргумент типа для каждого связанного параметра типа:
П к ➞ А кАргумент типа может быть составным типом, содержащим другие параметры связанного типа в качестве типов элементов (или даже быть просто другим параметром связанного типа). В процессе повторного упрощения параметры связанного типа в каждом аргументе типа заменяются соответствующими аргументами типа для этих параметров типа до тех пор, пока каждый аргумент типа не освободится от параметров связанного типа.
Ak
Pk
Если аргументы типа содержат циклические ссылки на себя через связанные параметры типа, упрощение и, следовательно, вывод типа терпят неудачу. В противном случае вывод типа проходит успешно.
Унификация типов ¶
Вывод типов решает уравнения типов посредством унификации типов . Унификация типов рекурсивно сравнивает типы LHS и RHS уравнения, где один или оба типа могут быть или содержать связанные параметры типа, и ищет аргументы типа для этих параметров типа таким образом, чтобы LHS и RHS совпадали (становились идентичными или совместимыми по назначению, в зависимости от контекста). Для этого вывод типов поддерживает карту связанных параметров типа с выведенными аргументами типа; эта карта просматривается и обновляется во время унификации типов. Изначально связанные параметры типа известны, но карта пуста. Во время унификации типов, если
A
выводится новый аргумент типа, соответствующее отображениеP ➞ A
из параметра типа в аргумент добавляется к карте. И наоборот, при сравнении типов известный аргумент типа (аргумент типа, для которого уже существует запись карты) занимает место соответствующего ему параметра типа. По мере прогрессирования вывода типов карта заполняется все больше и больше, пока все уравнения не будут рассмотрены или пока унификация не завершится неудачей. Вывод типа считается успешным, если ни один из шагов унификации не завершается неудачей и в карте есть запись для каждого параметра типа.Например, если задано уравнение типа с параметром связанного типа
P
[10]структура{ элемент P, список []P } ≡ A [10]структура{ элемент строка; список []строка }Вывод типа начинается с пустой карты. Сначала унификация сравнивает структуру верхнего уровня типов LHS и RHS. Оба являются массивами одинаковой длины; они унифицируются, если типы элементов унифицируются. Оба типа элементов являются структурами; они унифицируются, если у них одинаковое количество полей с одинаковыми именами и если типы полей унифицируются. Аргумент типа для
P
пока не известен (нет записи карты), поэтому унификацияP
сstring
добавляет отображениеP ➞ string
к карте. Унификация типов поляlist
требует унификации[]P
и[]string
и, таким образом,P
иstring
. Поскольку аргумент типа дляP
на данный момент известен (есть запись карты дляP
), его аргумент типаstring
занимает местоP
. И посколькуstring
идентиченstring
, этот шаг унификации также завершается успешно. Унификация LHS и RHS уравнения теперь завершена. Вывод типа завершается успешно, поскольку есть только одно уравнение типа, ни один шаг унификации не был неудачным, и карта полностью заполнена.Унификация использует комбинацию точной и свободной унификации в зависимости от того, должны ли два типа быть идентичными , совместимыми по назначению или только структурно равными. Соответствующие правила унификации типов подробно изложены в Приложении .
Для уравнения вида , где и являются типами, участвующими в присваивании (включая передачу параметров и операторы возврата), структуры типов верхнего уровня могут быть унифицированы слабо, но типы элементов должны быть унифицированы точно, в соответствии с правилами присваивания.
X ≡A Y
X
Y
Для уравнения вида , где — параметр типа и соответствующее ему ограничение, правила унификации немного сложнее:
P ≡C C
P
C
- Если
C
имеет базовый типcore(C)
иP
имеет известный аргумент типаA
,core(C)
иA
должен унифицироваться слабо. ЕслиP
не имеет известного аргумента типа иC
содержит ровно один термин типаT
, который не является базовым типом (тильда), унификация добавляет отображениеP ➞ T
к карте.- Если
C
не имеет основного типа иP
имеет известный аргумент типаA
,A
то должны быть все методыC
, если таковые имеются, и соответствующие типы методов должны точно унифицироваться.При решении уравнений типа из ограничений типа решение одного уравнения может вывести дополнительные аргументы типа, которые, в свою очередь, могут позволить решить другие уравнения, зависящие от этих аргументов типа. Вывод типа повторяет унификацию типа до тех пор, пока выводятся новые аргументы типа.
Операторы ¶
Операторы объединяют операнды в выражения.
Выражение = UnaryExpr | Выражение binary_op Выражение . UnaryExpr = PrimaryExpr | unary_op UnaryExpr . двоичный_оп = "||" | "&&" | rel_op | добавить_оп | мул_оп . rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" . add_op = "+" | "-" | "|" | "^" . mul_op = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" . unary_op = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .Сравнения обсуждаются в другом месте . Для других бинарных операторов типы операндов должны быть идентичны , если только операция не включает сдвиги или нетипизированные константы . Для операций, включающих только константы, см. раздел о константных выражениях .
За исключением операций сдвига, если один операнд является нетипизированной константой , а другой — нет, константа неявно преобразуется в тип другого операнда.
Правый операнд в выражении сдвига должен иметь целочисленный тип [ Go 1.13 ] или быть нетипизированной константой , представляемой значением типа
uint
. Если левый операнд неконстантного выражения сдвига является нетипизированной константой, он сначала неявно преобразуется в тип, который он бы принял, если бы выражение сдвига было заменено только его левым операндом.var a [1024]байт var s uint = 33 // Результаты следующих примеров приведены для 64-битных целых чисел. var i = 1<<s // 1 имеет тип int var j int32 = 1<<s // 1 имеет тип int32; j == 0 var k = uint64(1<<s) // 1 имеет тип uint64; к == 1<<33 var m int = 1.0<<s // 1.0 имеет тип int; m == 1<<33 var n = 1.0<<s == j // 1.0 имеет тип int32; n == true var o = 1<<s == 2<<s // 1 и 2 имеют тип int; o == false var p = 1<<s == 1<<33 // 1 имеет тип int; p == true var u = 1.0<<s // недопустимо: 1.0 имеет тип float64, сдвиг невозможен var u1 = 1.0<<s != 0 // недопустимо: 1.0 имеет тип float64, сдвиг невозможен var u2 = 1<<s != 1.0 // неверно: 1 имеет тип float64, сдвиг невозможен var v1 float32 = 1<<s // неверно: 1 имеет тип float32, сдвиг невозможен var v2 = string(1<<s) // неверно: 1 преобразуется в строку, сдвиг невозможен var w int64 = 1.0<<33 // 1.0<<33 — это выражение постоянного сдвига; w == 1<<33 var x = a[1.0<<s] // паника: 1.0 имеет тип int, но 1<<33 выходит за границы массива var b = make([]byte, 1.0<<s) // 1.0 имеет тип int; len(b) == 1<<33 // Результаты следующих примеров приведены для 32-битных целых чисел, // что означает, что сдвиги будут переполнены. var mm int = 1.0<<s // 1.0 имеет тип int; mm == 0 var oo = 1<<s == 2<<s // 1 и 2 имеют тип int; oo == true var pp = 1<<s == 1<<33 // неверно: 1 имеет тип int, но 1<<33 переполняет int var xx = a[1.0<<s] // 1.0 имеет тип int; xx == a[0] var bb = make([]byte, 1.0<<s) // 1.0 имеет тип int; len(bb) == 0Приоритет операторов ¶
Унарные операторы имеют наивысший приоритет. Поскольку операторы
++
и--
образуют операторы, а не выражения, они выходят за рамки иерархии операторов. Как следствие, оператор*p++
такой же, как и(*p)++
.Существует пять уровней приоритета для бинарных операторов. Сильнее всего связывают операторы умножения, за ними следуют операторы сложения, операторы сравнения
&&
(логическое И) и, наконец,||
(логическое ИЛИ):Приоритет оператора 5 * / % << >> & &^ 4 + - | ^ 3 == != < <= > >= 2 && 1 ||Бинарные операторы одинакового приоритета ассоциируются слева направо. Например,
x / y * z
то же самое, что и(x / y) * z
.+х // х 42 + а - б // (42 + а) - б 23 + 3*x[i] // 23 + (3 * x[i]) х <= f() // х <= f() ^а >> б // (^а) >> б ф() || г() // ф() || г() x == y+1 && <-chanInt > 0 // (x == (y+1)) && ((<-chanInt) > 0)Арифметические операторы ¶
Арифметические операторы применяются к числовым значениям и возвращают результат того же типа, что и первый операнд. Четыре стандартных арифметических оператора (
+
,-
,*
,/
) применяются к целым , плавающим и комплексным типам;+
также применяются к строкам . Побитовые логические операторы и операторы сдвига применяются только к целым числам.+ сумма целых чисел, чисел с плавающей точкой, комплексных значений, строк - разность целых чисел, чисел с плавающей точкой, комплексных значений * произведение целых чисел, чисел с плавающей точкой, комплексных значений / частное целых чисел, чисел с плавающей точкой, комплексных значений % остаток целых чисел & побитовое И целые числа | побитовое ИЛИ целых чисел ^ побитовое XOR целых чисел &^ бит очистить (И НЕ) целые числа << сдвиг влево целое число << целое число >= 0 >> сдвиг вправо целое число >> целое число >= 0Если тип операнда является параметром типа , оператор должен применяться к каждому типу в этом наборе типов. Операнды представлены как значения аргумента типа, с которым создается экземпляр параметра типа , и операция вычисляется с точностью этого аргумента типа. Например, если задана функция:
функция dotProduct[F ~float32|~float64](v1, v2 []F) F { вар s F для i, x := диапазон v1 { у := v2[i] с += х * у } вернуть с }произведение
x * y
и сложениеs += x * y
вычисляются с точностьюfloat32
илиfloat64
, соответственно, в зависимости от аргумента типа дляF
.Целочисленные операторы ¶
Для двух целых значений
x
иy
целое частноеq = x / y
и остатокr = x % y
удовлетворяют следующим соотношениям:x = q*y + r и |r| < |y|с
x / y
усечением в сторону нуля ( «усеченное деление» ).хуx / уx % у 5 3 1 2 -5 3 -1 -2 5 -3 -1 2 -5 -3 1 -2Единственным исключением из этого правила является то, что если делимое
x
является самым отрицательным значением для типа intx
, то частноеq = x / -1
равноx
(и ) из-за переполнения целого числаr = 0
в дополнительном коде :х, д int8 -128 int16 -32768 int32 -2147483648 int64 -9223372036854775808Если делитель является константой , он не должен быть равен нулю. Если делитель равен нулю во время выполнения, происходит паника во время выполнения . Если делимое неотрицательно, а делитель является постоянной степенью 2, деление можно заменить сдвигом вправо, а вычисление остатка можно заменить побитовой операцией И:
хх / 4 х % 4 х >> 2 х и 3 11 2 3 2 3 -11 -2 -3 -3 1Операторы сдвига сдвигают левый операнд на число сдвига, указанное правым операндом, которое должно быть неотрицательным. Если число сдвига отрицательно во время выполнения, происходит паника во время выполнения . Операторы сдвига реализуют арифметические сдвиги, если левый операнд является целым числом со знаком, и логические сдвиги, если это целое число без знака. Верхнего предела для числа сдвига нет. Сдвиги ведут себя так, как будто левый операнд сдвигается
n
на 1 раз для числа сдвигаn
. В результатеx << 1
то же самое, чтоx*2
иx >> 1
то же самое, что иx/2
, но усеченное в сторону отрицательной бесконечности.Для целочисленных операндов унарные операторы
+
,-
и^
определяются следующим образом:+х это 0 + х -x отрицание равно 0 - x ^x побитовое дополнение — это m ^ x, где m = «все биты установлены в 1» для беззнакового x и m = -1 для знакового xЦелочисленное переполнение ¶
Для беззнаковых целых значений операции
+
,-
,*
, и<<
вычисляются по модулю 2 n , где n — разрядность типа беззнакового целого числа. Грубо говоря, эти беззнаковые целые операции отбрасывают старшие биты при переполнении, и программы могут полагаться на «переход».Для знаковых целых чисел операции
+
,-
,*
,/
и<<
могут законно переполняться, а результирующее значение существует и детерминировано определяется представлением знакового целого числа, операцией и ее операндами. Переполнение не вызывает панику во время выполнения . Компилятор может не оптимизировать код, предполагая, что переполнение не происходит. Например, он может не предполагать, что этоx < x + 1
всегда верно.Операторы с плавающей точкой ¶
Для чисел с плавающей точкой и комплексных чисел
+x
то же самое, что иx
, а-x
является отрицаниемx
. Результат деления на ноль числа с плавающей точкой или комплексного числа не определен за пределами стандарта IEEE 754; возникновение паники во время выполнения зависит от реализации.Реализация может объединить несколько операций с плавающей точкой в одну объединенную операцию, возможно, между операторами, и создать результат, который отличается от значения, полученного путем выполнения и округления инструкций по отдельности. Явное преобразование типа с плавающей точкой округляет до точности целевого типа, предотвращая объединение, которое отменило бы это округление.
Например, некоторые архитектуры предоставляют инструкцию «объединенного умножения и сложения» (FMA), которая вычисляет
x*y + z
без округления промежуточного результатаx*y
. Эти примеры показывают, когда реализация Go может использовать эту инструкцию:// FMA разрешен для вычисления r, поскольку x*y явно не округлен: г = х*у + z г = z; г += х*у т = х*у; г = т + z *р = х*у; г = *р + z г = х*у + float64(z) // FMA запрещен для вычисления r, поскольку он пропустит округление x*y: r = float64(x*y) + z г = z; г += float64(x*y) t = float64(x*y); r = t + zКонкатенация строк ¶
Строки можно объединять с помощью
+
оператора или+=
оператора присваивания:с := "привет" + строка(с) s += "и до свидания"Сложение строк создает новую строку путем конкатенации операндов.
Операторы сравнения ¶
Операторы сравнения сравнивают два операнда и возвращают нетипизированное логическое значение.
== равно != не равно < меньше <= меньше или равно > больше >= больше или равноПри любом сравнении первый операнд должен иметь тип второго операнда, или наоборот.
Операторы равенства
==
и!=
применяются к операндам сопоставимых типов. Операторы упорядочения<
,<=
,>
, и>=
применяются к операндам упорядоченных типов. Эти термины и результат сравнений определяются следующим образом:
- Булевы типы сравнимы. Два булевых значения равны, если они являются либо обоими,
true
либо обоимиfalse
.- Целочисленные типы сравнимы и упорядочены. Два целочисленных значения сравниваются обычным способом.
- Типы с плавающей точкой сравнимы и упорядочены. Два значения с плавающей точкой сравниваются, как определено стандартом IEEE 754.
- Комплексные типы сравнимы. Два комплексных значения
u
иv
равны, если обаreal(u) == real(v)
иimag(u) == imag(v)
.- Строковые типы сравнимы и упорядочены. Два строковых значения сравниваются лексически побайтово.
- Типы указателей сравнимы. Два значения указателя равны, если они указывают на одну и ту же переменную или если оба имеют значение
nil
. Указатели на различные переменные нулевого размера могут быть равными или не равными.- Типы каналов сопоставимы. Два значения каналов равны, если они были созданы одним и тем же вызовом
make
или если оба имеют значениеnil
.- Типы интерфейсов, которые не являются параметрами типа, сравнимы. Два значения интерфейса равны, если они имеют идентичные динамические типы и равные динамические значения или если оба имеют значение
nil
.- Значение
x
неинтерфейсного типаX
и значениеt
интерфейсного типаT
можно сравнивать, если типX
сопоставим иX
реализуетT
. Они равны, еслиt
динамический тип идентиченX
, аt
динамическое значение равноx
.- Типы структур сравнимы, если все типы их полей сравнимы. Два значения структур равны, если их соответствующие непустые значения полей равны. Поля сравниваются в исходном порядке, и сравнение останавливается, как только два значения полей различаются (или все поля были сравнены).
- Типы массивов сравнимы, если сравнимы типы их элементов массива. Два значения массива равны, если равны соответствующие им значения элементов. Элементы сравниваются в порядке возрастания индекса, и сравнение останавливается, как только два значения элементов различаются (или все элементы были сравнены).
- Параметры типа сопоставимы, если они строго сопоставимы (см. ниже).
Сравнение двух значений интерфейса с идентичными динамическими типами вызывает панику во время выполнения , если этот тип не сопоставим. Это поведение применимо не только к прямым сравнениям значений интерфейса, но и при сравнении массивов значений интерфейса или структур с полями, имеющими значения интерфейса.
Типы среза, карты и функции не сопоставимы. Однако, как особый случай, значение среза, карты или функции может быть сравнено с предопределенным идентификатором
nil
. Сравнение значений указателя, канала и интерфейса сnil
также разрешено и следует из общих правил, приведенных выше.const c = 3 < 4 // c — нетипизированная булева константа true тип MyBool bool переменная x, y целое вар ( // Результат сравнения — нетипизированное логическое значение. // Применяются обычные правила назначения. b3 = x == y // b3 имеет тип bool b4 bool = x == y // b4 имеет тип bool b5 MyBool = x == y // b5 имеет тип MyBool )Тип строго сопоставим, если он сопоставим и не является типом интерфейса и не состоит из типов интерфейса. В частности:
- Типы данных «Булев», «Числовой», «Строка», «Указатель» и «Канал» строго сопоставимы.
- Типы структур строго сопоставимы, если все типы их полей строго сопоставимы.
- Типы массивов строго сопоставимы, если типы их элементов массива строго сопоставимы.
- Параметры типов строго сопоставимы, если все типы в их наборе типов строго сопоставимы.
Логические операторы ¶
Логические операторы применяются к булевым значениям и возвращают результат того же типа, что и операнды. Оценивается левый операнд, а затем правый, если условие того требует.
&& условное И p && q — это «если p, то q, иначе ложь» || условное ИЛИ p || q — «если p, то истинно, иначе q» ! НЕ !p это "не p"Операторы адресов ¶
Для операнда
x
типаT
операция адреса&x
генерирует указатель типа*T
наx
. Операнд должен быть адресуемым , то есть либо переменной, косвенной адресацией указателя, либо операцией индексации среза; либо селектором поля адресуемого операнда структуры; либо операцией индексации массива адресуемого массива. В качестве исключения из требования адресуемостиx
может также быть (возможно, заключенным в скобки) составным литералом . Если оценкаx
вызовет панику во время выполнения , то оценка&x
тоже это сделает.Для операнда
x
типа указателя*T
косвенность указателя*x
обозначает переменную типа,T
на которую указываетx
. Еслиx
этоnil
, попытка вычисления*x
вызовет панику во время выполнения .&х &а[ф(2)] &Точка{2, 3} *р *пф(х) var x *int = ноль *x // вызывает панику во время выполнения &*x // вызывает панику во время выполненияПрием оператора ¶
Для операнда, основным типом
ch
которого является канал , значением операции приема является значение, полученное из канала . Направление канала должно разрешать операции приема, а тип операции приема — тип элемента канала. Выражение блокируется до тех пор, пока значение не станет доступным. Получение из канала блокируется навсегда. Операция приема на закрытом канале всегда может быть продолжена немедленно, возвращая нулевое значение типа элемента после получения любых ранее отправленных значений.<-ch
ch
nil
v1 := <-ч v2 = <-ч ф(<-ч) <-strobe // ждем тактового импульса и сбрасываем полученное значениеВыражение приема, используемое в операторе присваивания или инициализации специальной формы
х, ок = <-ч х, ок := <-ч вар х, ок = <-ч вар х, ок Т = <-чвыдает дополнительный нетипизированный логический результат, сообщающий, была ли связь успешной. Значение равно,
ok
еслиtrue
полученное значение было доставлено успешной операцией отправки в канал илиfalse
если это нулевое значение, сгенерированное из-за того, что канал закрыт и пуст.Конверсии ¶
Преобразование изменяет тип выражения на тип, указанный преобразованием. Преобразование может появляться буквально в источнике или может подразумеваться контекстом , в котором появляется выражение.
Явное преобразование — это выражение вида, где
T(x)
—T
тип, аx
— выражение, которое можно преобразовать в типT
.Преобразование = Тип "(" Выражение [ "," ] ")" .Если тип начинается с оператора
*
или<-
, или если тип начинается с ключевого словаfunc
и не имеет списка результатов, его необходимо заключить в скобки, когда это необходимо, чтобы избежать двусмысленности:*Point(p) // то же самое, что *(Point(p)) (*Point)(p) // p преобразуется в *Point <-chan int(c) // то же, что и <-(chan int(c)) (<-chan int)(c) // c преобразуется в <-chan int func()(x) // сигнатура функции func() x (func())(x) // x преобразуется в func() (func() int)(x) // x преобразуется в func() int func() int(x) // x преобразуется в func() int (однозначно)Константное значение может быть преобразовано в тип, если оно может быть представлено значением . В качестве особого случая целочисленная константа может быть явно преобразована в строковый тип с использованием того же правила , что и для неконстантного .
x
T
x
T
x
x
Преобразование константы в тип, который не является параметром типа, приводит к получению типизированной константы.
uint(iota) // значение йоты типа uint float32(2.718281828) // 2.718281828 типа float32 complex128(1) // 1.0 + 0.0i типа complex128 float32(0.49999999) // 0.5 типа float32 float64(-1e-1000) // 0.0 типа float64 string('x') // "x" типа string string(0x266c) // "♬" типа string myString("foo" + "bar") // "foobar" типа myString string([]byte{'a'}) // не константа: []byte{'a'} не является константой (*int)(nil) // не константа: nil не является константой, *int не является логическим, числовым или строковым типом int(1.2) // неверно: 1.2 не может быть представлено как int string(65.0) // неверно: 65.0 не является целочисленной константойПреобразование константы в параметр типа дает неконстантное значение этого типа, причем значение представлено как значение аргумента типа, с которым инстанцируется параметр типа . Например, если задана функция:
функция f[P ~float32|~float64]() { … Р(1,1) … }преобразование
P(1.1)
приводит к неконстантному значению типаP
, и значение1.1
представляется какfloat32
илиfloat64
в зависимости от аргумента типа дляf
. Соответственно, еслиf
инстанцирован сfloat32
типом, числовое значение выраженияP(1.1) + 1.2
будет вычислено с той же точностью, что и соответствующее неконстантноеfloat32
сложение.Неконстантное значение
x
можно преобразовать в типT
в любом из следующих случаев:
x
может быть назначенT
.- игнорируя структурные теги (см. ниже),
x
типы иT
не являются параметрами типа , но имеют идентичные базовые типы .- игнорируя структурные теги (см. ниже),
x
типы иT
являются типами указателей, которые не являются именованными типами , а их базовые типы указателей не являются параметрами типа, но имеют идентичные базовые типы.x
Тип иT
оба являются целочисленными или типами с плавающей точкой.x
тип иT
оба являются сложными типами.x
представляет собой целое число или фрагмент байтов или рун иT
имеет строковый тип.x
представляет собой строку иT
представляет собой фрагмент байтов или рун.x
является срезом,T
является массивом [ Go 1.20 ] или указателем на массив [ Go 1.17 ], а типы среза и массива имеют идентичные типы элементов.Кроме того, если тип
T
или является параметрами типа, его также можно преобразовать в тип, если применяется одно из следующих условий:x
V
x
T
- Оба
V
являютсяT
параметрами типа, и значение каждого типа вV
наборе типов может быть преобразовано в каждый тип вT
наборе типов.- Только
V
параметр типа и значение каждого типа вV
наборе типов могут быть преобразованы вT
.- Only
T
— это параметр типа, которыйx
может быть преобразован в любой тип изT
набора типов.Теги структур игнорируются при сравнении типов структур на предмет идентичности с целью преобразования:
тип Person структура { Имя строки Адрес *структура { Уличная строка Городская строка } } var данные *структура { Имя строки `json:"name"` Адрес *структура { Строка улицы `json:"улица"` Строка города `json:"city"` } `json:"адрес"` } var person = (*Person)(data) // игнорируя теги, базовые типы идентичныОсобые правила применяются к (неконстантным) преобразованиям между числовыми типами или в и из строкового типа. Эти преобразования могут изменить представление
x
и повлечь за собой затраты времени выполнения. Все остальные преобразования изменяют только тип, но не представлениеx
.Нет лингвистического механизма для преобразования между указателями и целыми числами. Пакет
unsafe
реализует эту функциональность при ограниченных обстоятельствах.Преобразования между числовыми типами
Для преобразования непостоянных числовых значений применяются следующие правила:
- При преобразовании между целыми типами , если значение является целым числом со знаком, оно расширяется до знака неявной бесконечной точности; в противном случае оно расширяется до нуля. Затем оно усекается, чтобы вписаться в размер типа результата. Например, если
v := uint16(0x10F0)
, тоuint32(int8(v)) == 0xFFFFFFF0
. Преобразование всегда дает допустимое значение; нет никаких признаков переполнения.- При преобразовании числа с плавающей точкой в целое число дробная часть отбрасывается (усечение в сторону нуля).
- При преобразовании целого числа или числа с плавающей точкой в тип с плавающей точкой или комплексного числа в другой комплексный тип результирующее значение округляется до точности, указанной целевым типом. Например, значение переменной
x
типаfloat32
может быть сохранено с использованием дополнительной точности, выходящей за рамки 32-битного числа IEEE 754, но float32(x) представляет результат округленияx
значения до 32-битной точности. Аналогично,x + 0.1
может использовать более 32 бит точности, ноfloat32(x + 0.1)
не делает этого.Во всех неконстантных преобразованиях, включающих значения с плавающей точкой или комплексные значения, если тип результата не может представить значение, преобразование выполняется успешно, но значение результата зависит от реализации.
Преобразования в строковый тип и из него ¶
- Преобразование среза байтов в строковый тип дает строку, последовательные байты которой являются элементами среза.
string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "привет" строка([]байт{}) // "" строка([]байт(ноль)) // "" тип байты []байт string(bytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "привет" тип myByte байт string([]myByte{'w', 'o', 'r', 'l', 'd', '!'}) // "мир!" myString([]myByte{'\xf0', '\x9f', '\x8c', '\x8d'}) // ""- Преобразование фрагмента рун в строковый тип дает строку, которая представляет собой конкатенацию отдельных значений рун, преобразованных в строки.
string([]rune{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔" строка([]руна{}) // "" строка([]руна(ноль)) // "" тип руны []руна string(runes{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔" тип myRune руна строка([]мояРуна{0x266b, 0x266c}) // "\u266b\u266c" == "♫♬" мояСтрока([]мояРуна{0x1f30e}) // "\U0001f30e" == ""- Преобразование значения строкового типа в срез типа байтов дает ненулевой срез, последовательными элементами которого являются байты строки.
[]byte("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'} []байт("") // []байт{} байты("hellø") // []байт{'h', 'e', 'l', 'l', '\xc3', '\xb8'} []myByte("мир!") // []myByte{'w', 'o', 'r', 'l', 'd', '!'} []myByte(myString("")) // []myByte{'\xf0', '\x9f', '\x8c', '\x8f'}- Преобразование значения строкового типа в срез рунического типа дает срез, содержащий отдельные кодовые точки Unicode строки.
[]rune(myString("白鵬翔")) // []rune{0x767d, 0x9d6c, 0x7fd4} []руна("") // []руна{} руны("白鵬翔") // []руна{0x767d, 0x9d6c, 0x7fd4} []мояРуна("♫♬") // []мояРуна{0x266b, 0x266c} []мояРуна(мояСтрока("")) // []мояРуна{0x1f310}- Наконец, по историческим причинам целочисленное значение может быть преобразовано в строковый тип. Эта форма преобразования дает строку, содержащую (возможно, многобайтовое) представление UTF-8 кодовой точки Unicode с заданным целочисленным значением. Значения за пределами диапазона допустимых кодовых точек Unicode преобразуются в
"\uFFFD"
.строка('а') // "а" строка(65) // "А" строка('\xf8') // "\u00f8" == "ø" == "\xc3\xb8" строка(-1) // "\ufffd" == "\xef\xbf\xbd" тип myString строка myString('\u65e5') // "\u65e5" == "日" == "\xe6\x97\xa5"Примечание: эта форма преобразования может быть в конечном итоге удалена из языка.
go vet
Инструмент помечает определенные преобразования целых чисел в строки как потенциальные ошибки. Вместо этого следует использовать библиотечные функции, такие какutf8.AppendRune
или .utf8.EncodeRune
Преобразования из среза в массив или указатель массива ¶
Преобразование среза в массив дает массив, содержащий элементы базового массива среза. Аналогично, преобразование среза в указатель массива дает указатель на базовый массив среза. В обоих случаях, если длина среза меньше длины массива, происходит паника во время выполнения .
s := make([]байт, 2, 4) a0 := [0]байт(ы) a1 := [1]byte(s[1:]) // a1[0] == s[1] a2 := [2]байт(ы) // a2[0] == s[0] a4 := [4]byte(s) // паника: len([4]byte) > len(s) s0 := (*[0]байт)(с) // s0 != ноль s1 := (*[1]байт)(s[1:]) // &s1[0] == &s[1] s2 := (*[2]байт)(с) // &s2[0] == &s[0] s4 := (*[4]byte)(s) // паника: len([4]byte) > len(s) var t []строка t0 := [0]string(t) // ok для нулевого среза t t1 := (*[0]строка)(t) // t1 == ноль t2 := (*[1]string)(t) // паника: len([1]string) > len(t) u := make([]байт, 0) u0 := (*[0]байт)(u) // u0 != нольПостоянные выражения ¶
Константные выражения могут содержать только константные операнды и оцениваются во время компиляции.
Нетипизированные логические, числовые и строковые константы могут использоваться в качестве операндов везде, где разрешено использовать операнд логического, числового или строкового типа соответственно.
Константное сравнение всегда дает нетипизированную булеву константу. Если левый операнд выражения сдвига константы является нетипизированной константой, результатом является целочисленная константа; в противном случае это константа того же типа, что и левый операнд, который должен быть целочисленного типа .
Любая другая операция с нетипизированными константами приводит к нетипизированной константе того же вида, то есть к булевой, целочисленной, с плавающей точкой, комплексной или строковой константе. Если нетипизированные операнды бинарной операции (кроме сдвига) имеют разные виды, результатом будет вид операнда, который указан далее в этом списке: целое число, руна, с плавающей точкой, комплексный. Например, нетипизированная целочисленная константа, деленная на нетипизированную комплексную константу, дает нетипизированную комплексную константу.
const a = 2 + 3.0 // a == 5.0 (нетипизированная константа с плавающей точкой) const b = 15 / 4 // b == 3 (нетипизированная целочисленная константа) const c = 15 / 4.0 // c == 3.75 (нетипизированная константа с плавающей точкой) const Θ float64 = 3/2 // Θ == 1.0 (тип float64, 3/2 — целочисленное деление) const Π float64 = 3/2. // Π == 1.5 (тип float64, 3/2. — это деление с плавающей точкой) const d = 1 << 3.0 // d == 8 (нетипизированная целочисленная константа) const e = 1.0 << 3 // e == 8 (нетипизированная целочисленная константа) const f = int32(1) << 33 // недопустимо (константа 8589934592 переполняет int32) const g = float64(2) >> 1 // недопустимо (float64(2) — типизированная константа с плавающей точкой) const h = "foo" > "bar" // h == true (нетипизированная булева константа) const j = true // j == true (нетипизированная булева константа) const k = 'w' + 1 // k == 'x' (нетипизированная руническая константа) const l = "hi" // l == "hi" (нетипизированная строковая константа) const m = string(k) // m == "x" (тип string) const Σ = 1 - 0.707i // (нетипизированная комплексная константа) const Δ = Σ + 2.0e-4 // (нетипизированная комплексная константа) const Φ = iota*1i - 1/1i // (нетипизированная комплексная константа)Применение встроенной функции
complex
к нетипизированным целым числам, рунам или константам с плавающей точкой дает нетипизированную комплексную константу.const ic = complex(0, c) // ic == 3.75i (нетипизированная комплексная константа) const iΘ = complex(0, Θ) // iΘ == 1i (тип complex128)Константные выражения всегда оцениваются точно; промежуточные значения и сами константы могут требовать точности, значительно большей, чем та, которая поддерживается любым предопределенным типом в языке. Ниже приведены допустимые объявления:
const Огромный = 1 << 100 // Огромный == 1267650600228229401496703205376 (нетипизированная целочисленная константа) const Четыре int8 = Огромный >> 98 // Четыре == 4 (тип int8)Делитель постоянной операции деления или остатка не должен быть равен нулю:
3.14 / 0.0 // неправильно: деление на нольЗначения типизированных констант всегда должны быть точно представлены значениями константного типа. Следующие константные выражения недопустимы:
uint(-1) // -1 не может быть представлено как uint int(3.14) // 3.14 не может быть представлено как int int64(Huge) // 1267650600228229401496703205376 не может быть представлено как int64 Four * 300 // операнд 300 не может быть представлен как int8 (тип Four) Четыре * 100 // произведение 400 не может быть представлено как int8 (тип Четыре)Маска, используемая унарным оператором побитового дополнения,
^
соответствует правилу для неконстант: маска состоит из единиц для беззнаковых констант и -1 для знаковых и нетипизированных констант.^1 // нетипизированная целочисленная константа, равная -2 uint8(^1) // недопустимо: то же, что и uint8(-2), -2 не может быть представлено как uint8 ^uint8(1) // типизированная константа uint8, то же самое, что и 0xFF ^ uint8(1) = uint8(0xFE) int8(^1) // то же самое, что и int8(-2) ^int8(1) // то же, что и -1 ^ int8(1) = -2Ограничение реализации: Компилятор может использовать округление при вычислении нетипизированных выражений с плавающей точкой или сложных констант; см. ограничение реализации в разделе о константах . Это округление может привести к тому, что выражение с плавающей точкой станет недействительным в целочисленном контексте, даже если оно будет целым при вычислении с использованием бесконечной точности, и наоборот.
Порядок оценки ¶
На уровне пакета зависимости инициализации определяют порядок оценки отдельных выражений инициализации в объявлениях переменных . В противном случае при оценке операндов выражения, присваивания или оператора return все вызовы функций, вызовы методов, операции получения и бинарные логические операции оцениваются в лексическом порядке слева направо.
Например, в (локальном) назначении
y[f()], ок = g(z || h(), i()+x[j()], <-c), k()вызовы функций и связь происходят в порядке
f()
,h()
(еслиz
оценивается как false),i()
,j()
,<-c
,g()
, иk()
. Однако порядок этих событий по сравнению с оценкой и индексациейx
и оценкойy
иz
не указан, за исключением случаев, когда это требуется лексически. Например,g
не может быть вызван до оценки его аргументов.а := 1 f := func() int { a++; return a } x := []int{a, f()} // x может быть [1, 2] или [2, 2]: порядок вычисления между a и f() не указан m := map[int]int{a: 1, a: 2} // m может быть {2: 1} или {2: 2}: порядок оценки между двумя назначениями карты не указан n := map[int]int{a: f()} // n может быть {2: 3} или {3: 3}: порядок оценки между ключом и значением не указанНа уровне пакета зависимости инициализации переопределяют правило «слева направо» для отдельных выражений инициализации, но не для операндов внутри каждого выражения:
переменная а, б, в = f() + v(), g(), sqr(u()) + v() функция f() int { return c } func g() int { return a } функция sqr(x int) int { return x*x } // функции u и v независимы от всех других переменных и функцийВызовы функций происходят в следующем порядке
u()
:sqr()
,v()
,f()
,v()
, иg()
.Операции с плавающей точкой в одном выражении оцениваются в соответствии с ассоциативностью операторов. Явные скобки влияют на оценку, переопределяя ассоциативность по умолчанию. В выражении
x + (y + z)
сложениеy + z
выполняется перед сложениемx
.Заявления ¶
Заявления контролируют исполнение.
Оператор = Объявление | LabeledStmt | SimpleStmt | GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt | FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt | DeferStmt . SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Назначение | ShortVarDecl .Завершающие заявления ¶
Оператор завершения прерывает обычный поток управления в блоке . Следующие операторы являются завершающими:
- Оператор «возврата» или «перехода» .
- Вызов встроенной функции
panic
.- Блок , в котором список операторов заканчивается завершающим оператором.
- Утверждение «если», в котором:
- присутствует ветвь «else», и
- обе ветви являются завершающими операторами.
- Выражение «for», в котором:
- нет операторов «break», ссылающихся на оператор «for», и
- условие цикла отсутствует, и
- В операторе «for» не используется предложение диапазона.
- «Переключающее» утверждение , в котором:
- нет операторов «break», ссылающихся на оператор «switch»,
- есть случай по умолчанию, и
- списки операторов в каждом случае, включая оператор по умолчанию, заканчиваются завершающим оператором или, возможно, оператором с меткой «fallthrough» .
- «Выборочное» утверждение , в котором:
- нет операторов «break», ссылающихся на оператор «select», и
- списки операторов в каждом случае, включая оператор по умолчанию, если он присутствует, заканчиваются завершающим оператором.
- Помеченный оператор, маркирующий завершающий оператор.
Все остальные заявления не являются завершающими.
Список операторов заканчивается завершающим оператором, если список не пуст и его последний непустой оператор является завершающим.
Пустые заявления ¶
Пустое утверждение ничего не делает.
ПустойСтмт = .Помеченные утверждения ¶
Помеченное утверждение может быть целью утверждения
goto
,break
илиcontinue
.LabeledStmt = Метка ":" Оператор . Метка = идентификатор .Ошибка: log.Panic("произошла ошибка")Выражения ¶
За исключением определенных встроенных функций, вызовы функций и методов, а также операции получения могут появляться в контексте оператора. Такие операторы могут быть заключены в скобки.
ExpressionStmt = Выражение .Следующие встроенные функции не допускаются в контексте оператора:
добавить кап сложное изображение len сделать новый реальный небезопасный.Добавить небезопасный.Выровнять небезопасный.Смещение небезопасного.Размер небезопасного.Срез небезопасный.Данные среза небезопасный.Строка небезопасный.Данные строкич(х+у) f.Закрыть() <-ч (<-ч) len("foo") // недопустимо, если len — встроенная функцияОтправить заявления ¶
Оператор send отправляет значение по каналу. Основной тип выражения канала должен быть каналом , направление канала должно разрешать операции отправки, а тип отправляемого значения должен быть назначаемым типу элемента канала.
SendStmt = Канал "<-" Выражение . Канал = Выражение .И канал, и выражение значения оцениваются до начала коммуникации. Коммуникация блокируется до тех пор, пока не будет продолжена отправка. Отправка по небуферизованному каналу может быть продолжена, если получатель готов. Отправка по буферизованному каналу может быть продолжена, если в буфере есть место. Отправка по закрытому каналу продолжается, вызывая панику во время выполнения . Отправка по
nil
каналу блокируется навсегда.ch <- 3 // отправить значение 3 на канал chЗаявления IncDec ¶
Операторы «++» и «—» увеличивают или уменьшают свои операнды на нетипизированную константу
1
. Как и в случае с присваиванием, операнд должен быть адресуемым или выражением индекса карты.IncDecStmt = Выражение ( "++" | "--" ) .Следующие операторы присваивания семантически эквивалентны:
IncDec заявление Назначение х++ х += 1 х-- х -= 1Утверждения о назначении ¶
Присваивание заменяет текущее значение, хранящееся в переменной , новым значением, указанным выражением . Оператор присваивания может присвоить одно значение одной переменной или несколько значений соответствующему количеству переменных.
Назначение = СписокВыражений assign_op СписокВыражений . Assign_op = [ add_op | mul_op ] "=" .Каждый операнд с левой стороны должен быть адресуемым , выражением индекса карты или (
=
только для назначений) пустым идентификатором . Операнды могут быть заключены в скобки.х = 1 *п = ф() а[я] = 23 (k) = <-ch // то же самое, что: k = <-chОперация присваивания
x
op,=
y
где op — это бинарный арифметический оператор , эквивалентнаx
=
x
op(y)
, но вычисляетсяx
только один раз. Конструкция op=
— это один токен. В операциях присваивания как левый, так и правый списки выражений должны содержать ровно одно однозначное выражение, а левое выражение не должно быть пустым идентификатором.а[я] <<= 2 я &^= 1<<nПрисвоение кортежа присваивает отдельные элементы многозначной операции списку переменных. Существует две формы. В первой правый операнд — это одно многозначное выражение, такое как вызов функции, операция канала или карты или утверждение типа . Количество операндов в левой части должно соответствовать количеству значений. Например, если
f
— это функция, возвращающая два значения,х, у = f()присваивает первое значение ,
x
а второе —y
. Во второй форме количество операндов слева должно быть равно количеству выражений справа, каждое из которых должно быть однозначным, а n -е выражение справа присваивается n -му операнду слева:один, два, три = '一', '二', '三'Пустой идентификатор позволяет игнорировать значения правой части в присваивании:
_ = x // вычислить x, но игнорировать его x, _ = f() // вычислить f(), но игнорировать второе результирующее значениеПрисваивание происходит в два этапа. Во-первых, операнды индексных выражений и косвенных указаний (включая неявные косвенные указания в селекторах ) слева и выражения справа вычисляются в обычном порядке . Во-вторых, присваивания выполняются в порядке слева направо.
a, b = b, a // поменять местами a и b х := []целое{1, 2, 3} я := 0 i, x[i] = 1, 2 // установить i = 1, x[0] = 2 я = 0 x[i], i = 2, 1 // установить x[0] = 2, i = 1 x[0], x[0] = 1, 2 // устанавливаем x[0] = 1, затем x[0] = 2 (так что x[0] == 2 в конце) x[1], x[3] = 4, 5 // устанавливаем x[1] = 4, затем устанавливаем панику x[3] = 5. тип Point struct { x, y int } вар п *Точка x[2], px = 6, 7 // устанавливаем x[2] = 6, затем устанавливаем панику px = 7 я = 2 х = []целое{3, 5, 7} для i, x[i] = диапазон x { // установить i, x[2] = 0, x[0] перерыв } // после этого цикла i == 0 и x равен []int{3, 5, 3}В присваиваниях каждое значение должно быть присваиваемым типу операнда, которому оно присваивается, со следующими особыми случаями:
- Пустому идентификатору может быть присвоено любое типизированное значение.
- Если нетипизированная константа присваивается переменной типа интерфейса или пустому идентификатору, константа сначала неявно преобразуется в свой тип по умолчанию .
- Если переменной типа интерфейса или пустому идентификатору присваивается нетипизированное логическое значение, оно сначала неявно преобразуется в тип
bool
.Если утверждения ¶
Операторы «If» определяют условное выполнение двух ветвей в соответствии со значением логического выражения. Если выражение оценивается как истинное, выполняется ветвь «if», в противном случае, если она присутствует, выполняется ветвь «else».
IfStmt = "if" [ SimpleStmt ";" ] Блок выражения [ "else" ( IfStmt | Block ) ] .если х > макс { х = макс }Выражению может предшествовать простой оператор, который выполняется до вычисления выражения.
если х := f(); х < у { вернуть х } иначе если х > z { возвращение z } еще { вернуть y }Переключение операторов ¶
Операторы «Switch» обеспечивают многоканальное выполнение. Выражение или тип сравниваются с «cases» внутри «switch», чтобы определить, какую ветвь выполнять.
SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .Существует две формы: переключатели выражений и переключатели типов. В переключателе выражений случаи содержат выражения, которые сравниваются со значением выражения переключателя. В переключателе типов случаи содержат типы, которые сравниваются с типом специально аннотированного выражения переключателя. Выражение переключателя вычисляется ровно один раз в операторе switch.
Переключатели выражений ¶
В выражении switch вычисляется выражение switch, а выражения case, которые не обязательно должны быть константами, вычисляются слева направо и сверху вниз; первое выражение, которое равно выражению switch, запускает выполнение операторов связанного case; остальные case пропускаются. Если ни один case не соответствует и есть case «default», выполняются его операторы. Может быть максимум один case default, и он может появляться в любом месте оператора «switch». Отсутствующее выражение switch эквивалентно логическому значению
true
.ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" . ExprCaseClause = ExprSwitchCase ":" StatementList . ExprSwitchCase = "case" ExpressionList | "default" .Если выражение switch вычисляется как нетипизированная константа, оно сначала неявно преобразуется в свой тип по умолчанию . Предварительно объявленное нетипизированное значение
nil
не может использоваться как выражение switch. Тип выражения switch должен быть сопоставимым .Если выражение case не типизировано, оно сначала неявно преобразуется в тип выражения switch. Для каждого (возможно преобразованного) выражения case
x
и значенияt
выражения switchx == t
должно быть допустимое сравнение .Другими словами, выражение switch рассматривается так, как если бы оно использовалось для объявления и инициализации временной переменной
t
без явного типа; это то значение,t
по которому каждое выражение casex
проверяется на равенство.В предложении case или default последний непустой оператор может быть (возможно, помеченным ) оператором «fallthrough» , чтобы указать, что управление должно перейти от конца этого предложения к первому оператору следующего предложения. В противном случае управление переходит к концу оператора «switch». Оператор «fallthrough» может появляться как последний оператор всех, кроме последнего, предложений выражения switch.
Выражению switch может предшествовать простой оператор, который выполняется до вычисления выражения.
тег переключения { по умолчанию: s3() случай 0, 1, 2, 3: s1() случай 4, 5, 6, 7: s2() } switch x := f(); { // пропущенное выражение switch означает «истина» случай x < 0: возврат -x по умолчанию: вернуть x } выключатель { случай х < у: f1() случай х < z: f2() случай х == 4: f3() }Ограничение реализации: Компилятор может запретить множественные выражения case, вычисляющие одну и ту же константу. Например, текущие компиляторы запрещают дублирование целочисленных, плавающих или строковых констант в выражениях case.
Тип переключателей ¶
Переключатель типа сравнивает типы, а не значения. В остальном он похож на переключатель выражения. Он отмечен специальным выражением переключателя, которое имеет форму утверждения типа , использующего ключевое слово,
type
а не фактический тип:переключатель x.(тип) { // случаи }Затем кейсы сопоставляют фактические типы
T
с динамическим типом выраженияx
. Как и в случае с утверждениями типа,x
должен быть типом интерфейса , но не параметром типа , и каждый неинтерфейсный тип,T
указанный в кейсе, должен реализовывать типx
. Все типы, указанные в кейсах переключателя типа, должны быть разными .TypeSwitchStmt = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" . TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . TypeCaseClause = TypeSwitchCase ":" StatementList . TypeSwitchCase = "case" TypeList | "default" .TypeSwitchGuard может включать краткое объявление переменной . При использовании этой формы переменная объявляется в конце TypeSwitchCase в неявном блоке каждого предложения. В предложениях с case, перечисляющим только один тип, переменная имеет этот тип; в противном случае переменная имеет тип выражения в TypeSwitchGuard.
Вместо типа случай может использовать предопределенный идентификатор
nil
; этот случай выбирается, когда выражение в TypeSwitchGuard являетсяnil
значением интерфейса. Может быть максимум одинnil
случай.При наличии выражения
x
типаinterface{}
выполняется следующее переключение типа:переключатель i := x.(тип) { случай ноль: printString("x is nil") // тип i равен типу x (interface{}) случай int: printInt(i) // тип i — int случай float64: printFloat64(i) // тип i - float64 случай функция(целое) float64: printFunction(i) // тип i - func(int) float64 случай bool, строка: printString("тип - bool или string") // тип i - тип x (interface{}) по умолчанию: printString("не знаю тип") // тип i равен типу x (interface{}) }можно переписать:
v := x // x вычисляется ровно один раз если v == ноль { i := v // тип i равен типу x (interface{}) printString("x равен нулю") } иначе если i, isInt := v.(int); isInt { printInt(i) // тип i — int } иначе если i, isFloat64 := v.(float64); isFloat64 { printFloat64(i) // тип i - float64 } иначе если i, isFunc := v.(func(int) float64); isFunc { printFunction(i) // тип i - func(int) float64 } еще { _, isBool := v.(bool) _, isString := v.(строка) если isBool || isString { i := v // тип i равен типу x (interface{}) printString("тип - bool или string") } еще { i := v // тип i равен типу x (interface{}) printString("не знаю тип") } }Параметр типа или универсальный тип могут использоваться как тип в случае. Если при инстанцировании этот тип оказывается дублирующим другую запись в переключателе, выбирается первый соответствующий случай.
func f[P любой](x любой) int { переключатель x.(тип) { случай П: возврат 0 строка случая: возврат 1 случай []P: возврат 2 случай []байт: возвращение 3 по умолчанию: возвращение 4 } } var v1 = f[string]("foo") // v1 == 0 var v2 = f[байт]([]байт{}) // v2 == 2Защитному переключателю типа может предшествовать простой оператор, который выполняется до оценки защитного переключателя.
Оператор «fallthrough» не допускается в переключателе типов.
Для заявлений ¶
Оператор «for» определяет повторное выполнение блока. Существует три формы: итерация может контролироваться одним условием, предложением «for» или предложением «range».
ForStmt = "for" [ Условие | ForClause | RangeClause ] Блок . Условие = Выражение .Для утверждений с одним условием ¶
В своей простейшей форме оператор «for» определяет повторное выполнение блока, пока логическое условие оценивается как истинное. Условие оценивается перед каждой итерацией. Если условие отсутствует, оно эквивалентно логическому значению
true
.для а < б { а *= 2 }Для заявлений с
for
пунктом ¶Оператор «for» с ForClause также контролируется его условием, но дополнительно он может указывать оператор init и post , такой как оператор присваивания, инкремента или декремента. Оператор init может быть коротким объявлением переменной , но оператор post не должен.
ForClause = [ InitStmt ] ";" [ Состояние ] ";" [ Постстмт ] . InitStmt = SimpleStmt . ПостСтмт = ПростоСтмт .для i := 0; i < 10; i++ { ф(я) }Если непусто, оператор init выполняется один раз перед оценкой условия для первой итерации; оператор post выполняется после каждого выполнения блока (и только если блок был выполнен). Любой элемент ForClause может быть пустым, но точки с запятой обязательны, если только нет только условия. Если условие отсутствует, оно эквивалентно логическому значению
true
.для cond { S() } то же самое, что и для ; cond ; { S() } для { S() } то же самое, что и для true { S() }Каждая итерация имеет свою собственную отдельную объявленную переменную (или переменные) [ Go 1.22 ]. Переменная, используемая первой итерацией, объявляется оператором init. Переменная, используемая каждой последующей итерацией, объявляется неявно перед выполнением оператора post и инициализируется значением переменной предыдущей итерации в этот момент.
var печатает []func() для i := 0; i < 5; i++ { печатает = append(печатает, func() { println(i) }) я++ } для _, p := диапазон печатает { р() }отпечатки
1 3 5До [ Go 1.22 ] итерации совместно использовали один набор переменных вместо того, чтобы иметь свои собственные отдельные переменные. В этом случае пример выше выводит
6 6 6Для заявлений с
range
пунктом ¶Оператор «for» с предложением «range» выполняет итерацию по всем записям массива, среза, строки или карты, значениям, полученным по каналу, целочисленным значениям от нуля до верхнего предела [ Go 1.22 ] или значениям, переданным в функцию yield функции итератора [ Go 1.23 ]. Для каждой записи он назначает значения итерации соответствующим переменным итерации, если они присутствуют, а затем выполняет блок.
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Выражение .Выражение справа в предложении «range» называется выражением диапазона , его основным типом должен быть массив, указатель на массив, срез, строка, карта, канал, разрешающий операции приема , целое число или функция с определенной сигнатурой (см. ниже). Как и в случае с присваиванием, если присутствуют операнды слева, они должны быть адресуемыми или выражениями индекса карты; они обозначают переменные итерации. Если выражение диапазона является функцией, максимальное количество переменных итерации зависит от сигнатуры функции. Если выражение диапазона является каналом или целым числом, допускается не более одной переменной итерации; в противном случае их может быть до двух. Если последняя переменная итерации является пустым идентификатором , предложение диапазона эквивалентно тому же предложению без этого идентификатора.
Выражение диапазона
x
вычисляется до начала цикла, за одним исключением: если присутствует не более одной переменной итерации иx
илиlen(x)
является константой , выражение диапазона не вычисляется.Вызовы функций слева оцениваются один раз за итерацию. Для каждой итерации значения итерации производятся следующим образом, если присутствуют соответствующие переменные итерации:
Выражение диапазона 1-е значение 2-е значение массив или срез a [n]E, *[n]E или []E индекс i int a[i] E строка s тип строки индекс i int см. ниже руна карта m карта[K]V ключ k K m[k] V канал c chan E, <-chan E элемент e E целочисленное значение n целочисленного типа или нетипизированное значение int, которое я вижу ниже функция, 0 значений f func(func() bool) функция, 1 значение f func(func(V) bool) значение v V функция, 2 значения f func(func(K, V) bool) ключ k K v V
- Для массива, указателя на массив или значения среза
a
значения итераций индекса производятся в порядке возрастания, начиная с индекса элемента 0. Если присутствует не более одной переменной итерации, цикл диапазона производит значения итераций от 0 доlen(a)-1
и не индексирует сам массив или срез. Дляnil
среза количество итераций равно 0.- Для строкового значения предложение «range» перебирает кодовые точки Unicode в строке, начиная с индекса байта 0. При последующих итерациях значение индекса будет индексом первого байта последовательных кодовых точек UTF-8 в строке, а второе значение типа
rune
будет значением соответствующей кодовой точки. Если итерация встречает недопустимую последовательность UTF-8, вторым значением будет0xFFFD
, заменяющий символ Unicode, а следующая итерация переместит строку на один байт.- Порядок итерации по картам не указан и не гарантируется, что он будет одинаковым от одной итерации к другой. Если запись карты, которая еще не была достигнута, удаляется во время итерации, соответствующее значение итерации не будет создано. Если запись карты создается во время итерации, эта запись может быть создана во время итерации или может быть пропущена. Выбор может меняться для каждой созданной записи и от одной итерации к другой. Если карта —
nil
, количество итераций равно 0.- Для каналов, полученные значения итерации являются последовательными значениями, отправляемыми по каналу до тех пор, пока канал не будет закрыт . Если канал —
nil
, выражение диапазона блокируется навсегда.- Для целочисленного значения
n
, гдеn
is имеет целочисленный тип или нетипизированную целочисленную константу , значения итерации от 0 доn-1
производятся в порядке возрастания. Еслиn
is имеет целочисленный тип, значения итерации имеют тот же тип. В противном случае типn
определяется так, как если бы он был назначен переменной итерации. В частности: если переменная итерации уже существует, тип значений итерации — это тип переменной итерации, который должен быть целочисленным типом. В противном случае, если переменная итерации объявлена предложением «range» или отсутствует, тип значений итерации — это тип по умолчанию дляn
. Еслиn
<= 0, цикл не выполняет никаких итераций.- Для функции
f
итерация продолжается путем вызоваf
с новой синтезированнойyield
функцией в качестве аргумента. Еслиyield
вызывается доf
возвратов, аргументыyield
становятся значениями итерации для выполнения тела цикла один раз. После каждой успешной итерации циклаyield
возвращает true и может быть вызван снова для продолжения цикла. Пока тело цикла не завершится, предложение «range» будет продолжать генерировать значения итерации таким образом для каждогоyield
вызова до тех пор, покаf
не завершится. Если тело цикла завершается (например, операторомbreak
),yield
возвращает false и не должен вызываться снова.Переменные итерации могут быть объявлены предложением «range» с использованием формы краткого объявления переменных (
:=
). В этом случае их областью действия является блок оператора «for», и каждая итерация имеет свои собственные новые переменные [ Go 1.22 ] (см. также операторы «for» с ForClause ). Переменные имеют типы соответствующих им значений итерации.Если переменные итерации явно не объявлены предложением «range», они должны существовать заранее. В этом случае значения итерации присваиваются соответствующим переменным, как в операторе присваивания .
var testdata *struct { а *[7]целое } для i, _ := диапазон testdata.a { // testdata.a никогда не оценивается; len(testdata.a) является константой // i варьируется от 0 до 6 ф(я) } var a [10]строка для i, s := диапазон a { // тип i - int // тип s — строка // с == а[я] г(я, с) } var ключевая строка var val interface{} // тип элемента m может быть назначен val m := map[string]int{"пн":0, "вт":1, "ср":2, "чт":3, "пт":4, "сб":5, "вс":6} для ключа, значение = диапазон m { h(ключ, значение) } // ключ == последний ключ карты, обнаруженный в итерации // значение == карта[ключ] var ch chan Работа = производитель() для w := диапазон ch { doWork(w) } // очистить канал для диапазона ch {} // вызов f(0), f(1), ... f(9) для i := диапазон 10 { // тип i - int (тип по умолчанию для нетипизированной константы 10) ф(я) } // неверно: 256 не может быть присвоено uint8 var u uint8 для u = диапазон 256 { } // неверно: 1e3 — константа с плавающей точкой для диапазона 1e3 { } // fibo генерирует последовательность Фибоначчи fibo := func(yield func(x int) bool) { ф0, ф1 := 0, 1 для доходности (f0) { ф0, ф1 = ф1, ф0+ф1 } } // вывести числа Фибоначчи ниже 1000: для x := диапазон фибо { если х >= 1000 { перерыв } fmt.Printf("%d ", x) } // вывод: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 // поддержка итераций для рекурсивной древовидной структуры данных тип Дерево[K cmp.Ordered, V любой] структура { левое, правое *Дерево[K, V] клавиша К значение V } func (t *Tree[K, V]) walk(yield func(key K, val V) bool) bool { return t == nil || t.left.walk(yield) && yield(t.key, t.value) && t.right.walk(yield) } func (t *Tree[K, V]) Walk(yield func(key K, val V) bool) { t.walk(выход) } // обход дерева t в порядке возрастания var t Дерево[строка, целое] для k, v := диапазон t.Walk { // процесс k, v }Перейти к заявлениям ¶
Оператор «go» запускает выполнение вызова функции как независимого параллельного потока управления, или горутины , в том же адресном пространстве.
GoStmt = "go" Выражение .Выражение должно быть вызовом функции или метода; его нельзя заключать в скобки. Вызовы встроенных функций ограничены, как и для операторов выражений .
Значение функции и параметры оцениваются как обычно в вызывающей goroutine, но в отличие от обычного вызова, выполнение программы не ждет завершения вызванной функции. Вместо этого функция начинает выполняться независимо в новой goroutine. Когда функция завершается, ее goroutine также завершается. Если функция имеет какие-либо возвращаемые значения, они отбрасываются после завершения функции.
перейти на сервер() go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)Выберите утверждения ¶
Оператор «select» выбирает, какой из набора возможных операций отправки или получения будет выполнен. Он похож на оператор «switch» , но все случаи относятся к операциям связи.
SelectStmt = "select" "{" { CommClause } "}" . CommClause = CommCase ":" StatementList . CommCase = "case" ( SendStmt | RecvStmt ) | "default" . RecvStmt = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr . RecvExpr = Выражение .Случай с RecvStmt может присваивать результат RecvExpr одной или двум переменным, которые могут быть объявлены с помощью краткого объявления переменной . RecvExpr должен быть (возможно, заключенным в скобки) операцией получения. Может быть максимум один случай по умолчанию, и он может появляться в любом месте списка случаев.
Выполнение оператора «select» происходит в несколько этапов:
- Для всех случаев в операторе операнды каналов операций приема и выражения каналов и правых сторон операторов отправки оцениваются ровно один раз в порядке источника при вводе оператора «select». Результатом является набор каналов для получения или отправки, а также соответствующие значения для отправки. Любые побочные эффекты в этой оценке будут возникать независимо от того, какая (если таковая имеется) операция связи выбрана для продолжения. Выражения в левой части RecvStmt с коротким объявлением или назначением переменной еще не оцениваются.
- Если одно или несколько сообщений могут продолжиться, то одно из них, которое может продолжиться, выбирается с помощью равномерного псевдослучайного выбора. В противном случае, если есть случай по умолчанию, выбирается этот случай. Если случая по умолчанию нет, оператор «select» блокируется до тех пор, пока хотя бы одно из сообщений не сможет продолжиться.
- Если выбранный случай не является случаем по умолчанию, выполняется соответствующая операция связи.
- Если выбранный случай представляет собой RecvStmt с коротким объявлением переменной или присваиванием, то выражения в левой части оцениваются и полученное значение (или значения) присваиваются.
- Выполняется список заявлений выбранного случая.
Поскольку связь по
nil
каналам никогда не может быть продолжена, выбор только сnil
каналами и без случая по умолчанию блокируется навсегда.var a []целое переменная c, c1, c2, c3, c4 канал int вар i1, i2 целые выбирать { случай i1 = <-c1: print("получено ", i1, " от c1\n") случай c2 <- i2: print("отправлено ", i2, " в c2\n") case i3, ok := (<-c3): // то же самое, что: i3, ok := <-c3 если ок { print("получено ", i3, " от c3\n") } еще { print("c3 закрыт\n") } случай а[f()] = <-c4: // то же самое, что: // случай t := <-c4 // а[ф()] = т по умолчанию: print("нет связи\n") } для { // отправить случайную последовательность бит в c выбирать { case c <- 0: // примечание: нет оператора, нет перехода, нет сворачивания случаев случай с <- 1: } } выберите {} // заблокировать навсегдаВозврат заявлений ¶
Оператор «return» в функции
F
завершает выполнениеF
и опционально предоставляет одно или несколько значений результата. Любые функции, отложенные с помощью ,F
выполняются доF
возврата к вызывающему.ReturnStmt = "return" [ ExpressionList ] .В функции без типа результата оператор «return» не должен указывать никаких значений результата.
функция noResult() { возвращаться }Существует три способа возврата значений из функции с типом результата:
- Возвращаемое значение или значения могут быть явно перечислены в операторе «return». Каждое выражение должно быть однозначным и присваиваться соответствующему элементу типа результата функции.
функция simpleF() int { возврат 2 } func complexF1() (re float64, im float64) { возврат -7.0, -4.0 }- Список выражений в операторе «return» может быть одним вызовом многозначной функции. Эффект такой, как если бы каждое возвращаемое этой функцией значение было назначено временной переменной с типом соответствующего значения, за которым следует оператор «return», перечисляющий эти переменные, и в этот момент применяются правила предыдущего случая.
func complexF2() (re float64, im float64) { вернуть комплексF1() }- Список выражений может быть пустым, если тип результата функции определяет имена для ее параметров результата . Параметры результата действуют как обычные локальные переменные, и функция может присваивать им значения по мере необходимости. Оператор «return» возвращает значения этих переменных.
func complexF3() (re float64, im float64) { ре = 7,0 им = 4.0 возвращаться } func (devnull) Write(p []byte) (n int, _ error) { n = len(p) возвращаться }Независимо от того, как они объявлены, все результирующие значения инициализируются нулевыми значениями для своего типа при входе в функцию. Оператор «return», который указывает результаты, устанавливает результирующие параметры до выполнения любых отложенных функций.
Ограничение реализации: компилятор может запретить пустой список выражений в операторе «return», если в области действия возврата находится другая сущность (константа, тип или переменная) с тем же именем в качестве параметра результата .
func f(n int) (res int, err ошибка) { если _, ошибка := f(n-1); ошибка != ноль { return // недопустимый оператор возврата: err затенен } возвращаться }Заявления о перерывах ¶
Оператор «break» завершает выполнение самого внутреннего оператора «for» , «switch» или «select» внутри той же функции.
BreakStmt = "break" [ Метка ].Если есть метка, то она должна быть меткой охватывающего оператора «for», «switch» или «select», и именно на ней завершается выполнение.
Внешний контур: для i = 0; i < n; i++ { для j = 0; j < m; j++ { переключатель а[i][j] { случай ноль: состояние = Ошибка перерыв OuterLoop предмет дела: состояние = Найдено перерыв OuterLoop } } }Продолжить заявления ¶
Оператор «continue» начинает следующую итерацию самого внутреннего охватывающего цикла «for», перемещая управление в конец блока цикла. Цикл «for» должен находиться внутри той же функции.
ContinueStmt = "продолжить" [ Метка ].Если есть метка, то это должна быть метка окружающего оператора «for», и именно он выполняет ее.
RowLoop: для y, строка := диапазон строк { для x, данные := диапазон строк { если данные == endOfRow { продолжить RowLoop } строка[x] = данные + смещение(x, y) } }Операторы Goto ¶
Оператор «goto» передает управление оператору с соответствующей меткой внутри той же функции.
GotoStmt = Метка "goto" .перейти к ошибкеВыполнение оператора «goto» не должно приводить к попаданию в область видимости каких-либо переменных , которые еще не находились в области видимости в точке goto. Например, этот пример:
перейти на L // ПЛОХО в := 3 Л:является ошибочным, поскольку переход к метке
L
пропускает созданиеv
.Оператор «goto» вне блока не может перейти к метке внутри этого блока. Например, этот пример:
если n%2 == 1 { перейти на L1 } для n > 0 { ф() н-- Л1: ф() н-- }ошибочно, поскольку метка
L1
находится внутри блока оператора «for», аgoto
не.Провалы заявлений ¶
Оператор «fallthrough» передает управление первому оператору следующего предложения case в выражении «switch» statement . Он может использоваться только как последний непустой оператор в таком предложении.
FallthroughStmt = "провал".Отсрочка заявлений ¶
Оператор «defer» вызывает функцию, выполнение которой откладывается до момента возврата из окружающей функции, либо потому, что окружающая функция выполнила оператор return , достигла конца своего тела функции , либо потому, что соответствующая горутина находится в состоянии паники .
DeferStmt = "отложить" Выражение .Выражение должно быть вызовом функции или метода; его нельзя заключать в скобки. Вызовы встроенных функций ограничены, как и для операторов выражений .
Каждый раз, когда выполняется оператор «defer», значение функции и параметры вызова оцениваются как обычно и сохраняются заново, но фактическая функция не вызывается. Вместо этого отложенные функции вызываются непосредственно перед возвратом окружающей функции, в обратном порядке, в котором они были отложены. То есть, если окружающая функция возвращается через явный оператор return , отложенные функции выполняются после того, как все параметры результата установлены этим оператором return , но до того, как функция возвращается к своему вызывающему. Если значение отложенной функции оценивается как
nil
, выполнение паникует при вызове функции, а не при выполнении оператора «defer».Например, если отложенная функция является литералом функции , а окружающая функция имеет именованные параметры результата , которые находятся в области действия литерала, отложенная функция может получить доступ к параметрам результата и изменить их до того, как они будут возвращены. Если отложенная функция имеет какие-либо возвращаемые значения, они отбрасываются после завершения функции. (См. также раздел об обработке паник .)
замок(л) defer unlock(l) // разблокировка происходит до возврата окружающей функции // выводит 3 2 1 0 до возврата окружающей функции для я := 0; я <= 3; я++ { отложить fmt.Print(i) } // f возвращает 42 func f() (результат int) { отложить функцию() { // результат доступен после того, как ему было присвоено значение 6 оператором return результат *= 7 }() возвращение 6 }Встроенные функции ¶
Встроенные функции объявляются заранее . Они вызываются как и любые другие функции, но некоторые из них принимают тип вместо выражения в качестве первого аргумента.
Встроенные функции не имеют стандартных типов Go, поэтому они могут появляться только в выражениях вызова ; их нельзя использовать в качестве значений функций.
Добавление и копирование срезов ¶
Встроенные функции
append
иcopy
помогают в общих операциях среза. Для обеих функций результат не зависит от того, перекрывается ли память, на которую ссылаются аргументы.Функция с переменным числом аргументов добавляет
append
ноль или более значенийx
к срезуs
и возвращает результирующий срез того же типа, что иs
. Основной тип долженs
быть срезом типа[]E
. Значенияx
передаются параметру типа...E
, и применяются соответствующие правила передачи параметров . В качестве особого случая, если основной типs
—[]byte
,append
также принимает второй аргумент с основным типом,bytestring
за которым следует...
. Эта форма добавляет байты байтового среза или строки.append(s S, x ...E) S // основной тип S - []EЕсли емкость
s
недостаточна для размещения дополнительных значений,append
выделяет новый, достаточно большой базовый массив, который вмещает как существующие элементы среза, так и дополнительные значения. В противном случаеappend
повторно использует базовый массив.s0 := []целое{0, 0} s1 := append(s0, 2) // добавить один элемент s1 is []int{0, 0, 2} s2 := append(s1, 3, 5, 7) // добавить несколько элементов s2 is []int{0, 0, 2, 3, 5, 7} s3 := append(s2, s0...) // добавить срез s3 is []int{0, 0, 2, 3, 5, 7, 0, 0} s4 := append(s3[3:6], s3[2:]...) // добавить перекрывающийся срез s4 is []int{3, 5, 7, 2, 3, 5, 7, 0, 0} var t []интерфейс{} t = append(t, 42, 3.1415, "foo") // t — это []интерфейс{}{42, 3.1415, "foo"} var b []байт b = append(b, "bar"...) // добавить содержимое строки b is []byte{'b', 'a', 'r' }Функция
copy
копирует элементы среза из источникаsrc
в место назначенияdst
и возвращает количество скопированных элементов. Основные типы обоих аргументов должны быть срезами с идентичным типом элемента. Количество скопированных элементов равно минимумуlen(src)
иlen(dst)
. В качестве особого случая, если основной тип места назначения —[]byte
,copy
также принимает исходный аргумент с основным типомbytestring
. Эта форма копирует байты из байтового среза или строки в байтовый срез.копия(dst, src []T) целое copy(dst []byte, src string) intПримеры:
вар а = [...] int{0, 1, 2, 3, 4, 5, 6, 7} var s = make([]int, 6) var b = make([]байт, 5) n1 := copy(s, a[0:]) // n1 == 6, s — []int{0, 1, 2, 3, 4, 5} n2 := copy(s, s[2:]) // n2 == 4, s равно []int{2, 3, 4, 5, 4, 5} n3 := copy(b, "Привет, мир!") // n3 == 5, b is []byte("Привет")Прозрачный ¶
Встроенная функция
clear
принимает аргумент типа параметра map , slice или type и удаляет или обнуляет все элементы [ Go 1.21 ].Вызов Тип аргумента Результат clear(m) map[K]T удаляет все записи, в результате чего пустая карта (len(m) == 0) clear(s) []T устанавливает все элементы длиной доs
к нулевому значению T параметр типа clear(t) см. нижеЕсли тип аргумента
clear
является параметром типа , все типы в его наборе типов должны быть картами или срезами, иclear
выполняется операция, соответствующая фактическому аргументу типа.Если карта или срез — это
nil
,clear
то это пустая операция.Закрывать ¶
Для аргумента
ch
с основным типом , который является каналом , встроенная функцияclose
записывает, что больше никаких значений не будет отправлено по каналу. Это ошибка, еслиch
это канал только для приема. Отправка или закрытие закрытого канала вызывает панику во время выполнения . Закрытие канала nil также вызывает панику во время выполнения . После вызоваclose
и после получения любых ранее отправленных значений операции получения вернут нулевое значение для типа канала без блокировки. Многозначная операция получения возвращает полученное значение вместе с указанием того, закрыт ли канал.Манипулирование комплексными числами ¶
Три функции собирают и разбирают комплексные числа. Встроенная функция
complex
создает комплексное значение из действительной и мнимой части с плавающей точкой, аreal
иimag
извлекают действительную и мнимую части комплексного значения.комплексный(действительнаяЧасть, мнимаяЧасть floatT) комплексныйT real(complexT) floatT imag(complexT) floatTТип аргументов и возвращаемого значения соответствуют. Для
complex
два аргумента должны иметь одинаковый тип с плавающей точкой , а возвращаемый тип — комплексный тип с соответствующими компонентами с плавающей точкой:complex64
дляfloat32
аргументов иcomplex128
дляfloat64
аргументов. Если один из аргументов вычисляется как нетипизированная константа, он сначала неявно преобразуется в тип другого аргумента. Если оба аргумента вычисляются как нетипизированные константы, они должны быть некомплексными числами или их мнимые части должны быть равны нулю, а возвращаемое значение функции — нетипизированная комплексная константа.Для
real
иimag
аргумент должен быть комплексного типа, а возвращаемый тип — соответствующий тип с плавающей точкой:float32
дляcomplex64
аргумента иfloat64
дляcomplex128
аргумента. Если аргумент вычисляется как нетипизированная константа, он должен быть числом, а возвращаемое значение функции — нетипизированная константа с плавающей точкой.Функции
real
иimag
вместе образуют обратную функцию , поэтомуcomplex
для значенияz
комплексного типаZ
.z == Z(complex(real(z), imag(z)))
Если все операнды этих функций являются константами, возвращаемое значение также является константой.
var a = комплекс(2, -2) // комплекс128 const b = complex(1.0, -1.4) // нетипизированная комплексная константа 1 - 1.4i x := float32(math.Cos(math.Pi/2)) // float32 var c64 = комплекс(5, -x) // комплекс64 var s int = complex(1, 0) // нетипизированная комплексная константа 1 + 0i может быть преобразована в int _ = complex(1, 2<<s) // неверно: 2 предполагает тип с плавающей точкой, сдвиг невозможен var rl = real(c64) // float32 var im = imag(a) // float64 const c = imag(b) // нетипизированная константа -1.4 _ = imag(3 << s) // неверно: 3 предполагает сложный тип, сдвиг невозможенАргументы типа параметра типа не допускаются.
Удаление элементов карты ¶
Встроенная функция
delete
удаляет элемент с ключомk
из картыm
. Значениеk
должно быть назначаемым типу ключаm
.delete(m, k) // удалить элемент m[k] из карты mЕсли тип
m
является параметром типа , все типы в этом наборе типов должны быть картами, и все они должны иметь идентичные типы ключей.Если карта
m
существуетnil
или элементm[k]
не существует,delete
то это пустая операция.Длина и вместимость ¶
Встроенные функции
len
иcap
принимают аргументы различных типов и возвращают результат типаint
. Реализация гарантирует, что результат всегда вписывается вint
.Вызов Тип аргумента Результат len(s) тип строки длина строки в байтах [n]T, *[n]T длина массива (== n) []Длина среза T map[K]T длина карты (количество определенных ключей) chan T количество элементов, поставленных в очередь в буфере канала параметр типа см. ниже cap(s) [n]T, *[n]T длина массива (== n) []Емкость среза T емкость буфера канала T параметр типа см. нижеЕсли тип аргумента является параметром типа
P
, вызовlen(e)
(илиcap(e)
соответственно) должен быть допустимым для каждого типа вP
наборе типов . Результатом является длина (или емкость, соответственно) аргумента, тип которого соответствует аргументу типа, с которымP
был создан экземпляр .Емкость среза — это количество элементов, для которых выделено место в базовом массиве. В любой момент времени выполняется следующее соотношение:
0 <= длина(ы) <= размер(ы)Длина среза
nil
, карты или канала равна 0. Емкостьnil
среза или канала равна 0.Выражение
len(s)
является константой, еслиs
является строковой константой. Выраженияlen(s)
иcap(s)
являются константами, если типs
является массивом или указателем на массив и выражениеs
не содержит приемы каналов или (неконстантные) вызовы функций ; в этом случаеs
не вычисляется. В противном случае вызовыlen
иcap
не являются константами иs
вычисляется.константа ( c1 = imag(2i) // imag(2i) = 2.0 — константа c2 = len([10]float64{2}) // [10]float64{2} не содержит вызовов функций c3 = len([10]float64{c1}) // [10]float64{c1} не содержит вызовов функций c4 = len([10]float64{imag(2i)}) // imag(2i) — константа, и вызов функции не выполняется c5 = len([10]float64{imag(z)}) // недопустимо: imag(z) — это (неконстантный) вызов функции ) вар z комплекс128Создание срезов, карт и каналов ¶
Встроенная функция
make
принимает типT
, за которым может следовать список выражений, специфичный для типа. Основной тип долженT
быть срезом, картой или каналом. Она возвращает значение типаT
(не*T
). Память инициализируется, как описано в разделе начальных значений .Вызов Тип ядра Результат make(T, n) slice срез типа T длиной n и емкостью n make(T, n, m) срез срез типа T длиной n и емкостью m make(T) map карта типа T make(T, n) map map типа T с начальным пространством для приблизительно n элементов make(T) канал небуферизованный канал типа T make(T, n) канал буферизованный канал типа T, размер буфера nКаждый из аргументов размера
n
иm
должен иметь целочисленный тип , иметь набор типов , содержащий только целочисленные типы, или быть нетипизированной константой . Аргумент константного размера должен быть неотрицательным и представимым значением типаint
; если это нетипизированная константа, ей присваивается типint
. Если указаныn
иm
и являются константами, тоn
должен быть не большеm
. Для срезов и каналов, еслиn
отрицательно или больше, чемm
во время выполнения, возникает паника во время выполнения .s := make([]int, 10, 100) // срез с len(s) == 10, cap(s) == 100 s := make([]int, 1e3) // срез с len(s) == cap(s) == 1000 s := make([]int, 1<<63) // неверно: len(s) не может быть представлено значением типа int s := make([]int, 10, 0) // неверно: len(s) > cap(s) c := make(chan int, 10) // канал с размером буфера 10 m := make(map[string]int, 100) // карта с начальным пространством примерно для 100 элементовВызов
make
с указанием типа и размера картыn
создаст карту с начальным пространством для храненияn
элементов карты. Точное поведение зависит от реализации.Мин и макс ¶
Встроенные функции
min
иmax
вычисляют наименьшее — или наибольшее, соответственно — значение фиксированного числа аргументов упорядоченных типов . Должен быть хотя бы один аргумент [ Go 1.21 ].Применяются те же правила типов, что и для операторов : для упорядоченных аргументов
x
иy
,min(x, y)
допустимо, еслиx + y
допустимо, а типmin(x, y)
является типомx + y
(и аналогично дляmax
). Если все аргументы являются константами, результат является константой.переменная x, y целое м := мин(х) // м == х m := min(x, y) // m — меньшее из x и y m := max(x, y, 10) // m больше x и y, но не меньше 10 c := max(1, 2.0, 10) // c == 10.0 (тип с плавающей точкой) f := max(0, float32(x)) // тип f - float32 var s []строка _ = min(s...) // недопустимо: аргументы среза не допускаются t := max("", "foo", "bar") // t == "foo" (строковый тип)Для числовых аргументов, предполагая, что все NaN равны,
min
а такжеmax
являются коммутативными и ассоциативными:мин(х, у) == мин(у, х) min(x, y, z) == min(min(x, y), z) == min(x, min(y, z))Для аргументов с плавающей точкой (отрицательный ноль, NaN и бесконечность) применяются следующие правила:
xy мин(x, y) макс(x, y) -0.0 0.0 -0.0 0.0 // отрицательный ноль меньше (неотрицательного) нуля -Inf y -Inf y // отрицательная бесконечность меньше любого другого числа +Inf yy +Inf // положительная бесконечность больше любого другого числа NaN y NaN NaN // если какой-либо аргумент — NaN, результат — NaNДля строковых аргументов результатом
min
является первый аргумент с наименьшим (или дляmax
наибольшим) значением, сравниваемый лексически побайтово:min(x, y) == если x <= y тогда x иначе y min(x, y, z) == min(min(x, y), z)Распределение ¶
Встроенная функция
new
принимает типT
, выделяет хранилище для переменной этого типа во время выполнения и возвращает значение типа,*T
указывающее на него. Переменная инициализируется, как описано в разделе начальных значений .тритон)Например
тип S struct { a int; b float64 } новости)выделяет хранилище для переменной типа
S
, инициализирует ее (a=0
,b=0.0
) и возвращает значение типа ,*S
содержащее адрес местоположения.Как справиться с паникой ¶
Две встроенные функции
panic
иrecover
помогают сообщать о паниках во время выполнения и программно-определяемых состояниях ошибок и обрабатывать их.функция паники(интерфейс{}) интерфейс funcrecover(){}При выполнении функции
F
явный вызовpanic
или паника времени выполнения завершают выполнениеF
. Все функции , отложенные на ,F
затем выполняются как обычно. ЗатемF
запускаются все отложенные функции, запущенные вызывающей функцией , и так далее до любой отложенной функцией верхнего уровня в выполняемой goroutine. В этот момент программа завершается, и сообщается об ошибке, включая значение аргумента дляpanic
. Такая последовательность завершения называется panicking .паника(42) паника("недоступен") паника(Ошибка("невозможно проанализировать"))Функция
recover
позволяет программе управлять поведением паникующей горутины. Предположим, что функцияG
откладывает функциюD
, которая вызывает ,recover
и в функции той же горутины, в которойG
выполняется , происходит паника. Когда выполнение отложенных функций достигаетD
, возвращаемым значениемD
вызова ‘srecover
будет значение, переданное вызовуpanic
. ЕслиD
возвращается нормально, без начала новогоpanic
, паникующая последовательность останавливается. В этом случае состояние функций, вызванных междуG
и вызовом ,panic
отбрасывается, и возобновляется нормальное выполнение. Затем запускаются все функции, отложенные до , иG
выполнение завершается возвратом к вызывающей стороне.D
G
Возвращаемое значение —
recover
этоnil
когда goroutine не паникует илиrecover
не была вызвана напрямую отложенной функцией. И наоборот, если goroutine паникует иrecover
была вызвана напрямую отложенной функцией, возвращаемое значениеrecover
гарантированно не будетnil
. Чтобы гарантировать это, вызовpanic
соnil
значением интерфейса (или нетипизированнымnil
) вызывает панику во время выполнения .Функция
protect
в примере ниже вызывает аргумент функцииg
и защищает вызывающие объекты от паник во время выполнения, вызванныхg
.функция защиты (g функция()) { отложить функцию() { log.Println("done") // Println выполняется нормально, даже если возникла паника если x := восстановить(); x != ноль { log.Printf("паника во время выполнения: %v", x) } }() log.Println("старт") г() }Самонастройка ¶
Текущие реализации предоставляют несколько встроенных функций, полезных во время начальной загрузки. Эти функции документированы для полноты, но не гарантируют, что останутся в языке. Они не возвращают результат.
Поведение функции print выводит все аргументы; форматирование аргументов зависит от реализации println похож на print, но печатает пробелы между аргументами и новую строку в концеОграничение реализации:
println
печать логических, числовых и строковых типов .Пакеты ¶
Программы Go создаются путем связывания пакетов . Пакет, в свою очередь, создается из одного или нескольких исходных файлов, которые вместе объявляют константы, типы, переменные и функции, принадлежащие пакету, и которые доступны во всех файлах того же пакета. Эти элементы могут быть экспортированы и использованы в другом пакете.
Организация исходного файла ¶
Каждый исходный файл состоит из предложения пакета, определяющего пакет, к которому он принадлежит, за которым следует возможно пустой набор объявлений импорта, объявляющих пакеты, содержимое которых он хочет использовать, за которым следует возможно пустой набор объявлений функций, типов, переменных и констант.
SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .Пакетное положение ¶
Предложение пакета начинается с каждого исходного файла и определяет пакет, к которому принадлежит файл.
PackageClause = "package" PackageName . PackageName = идентификатор .PackageName не может быть пустым идентификатором .
пакет математикаНабор файлов, разделяющих одно и то же PackageName, образует реализацию пакета. Реализация может потребовать, чтобы все исходные файлы для пакета находились в одном и том же каталоге.
Импортные декларации ¶
В объявлении импорта указано, что исходный файл, содержащий объявление, зависит от функциональности импортируемого пакета ( §Инициализация и выполнение программы ) и разрешает доступ к экспортированным идентификаторам этого пакета. Импорт называет идентификатор (PackageName), который будет использоваться для доступа, и ImportPath, который указывает пакет, который будет импортирован.
ImportDecl = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) . ImportSpec = [ "." | PackageName ] ImportPath . ImportPath = string_lit .PackageName используется в квалифицированных идентификаторах для доступа к экспортированным идентификаторам пакета в импортируемом исходном файле. Он объявляется в блоке файла . Если PackageName опущен, по умолчанию используется идентификатор, указанный в предложении пакета импортируемого пакета. Если
.
вместо имени указана явная точка ( ), все экспортированные идентификаторы пакета, объявленные в блоке пакета этого пакета , будут объявлены в блоке файла импортируемого исходного файла и должны быть доступны без квалификатора.Интерпретация ImportPath зависит от реализации, но обычно это подстрока полного имени файла скомпилированного пакета, которая может относиться к репозиторию установленных пакетов.
Ограничение реализации: компилятор может ограничить ImportPaths непустыми строками, используя только символы, принадлежащие к общим категориям Unicode L, M, N, P и S (графические символы без пробелов), а также может исключить символы
!"#$%&'()*,:;<=>?[\]^`{|}
и заменяющий символ Unicode U+FFFD.Рассмотрим скомпилированный пакет, содержащий предложение пакета
package math
, которое экспортирует функциюSin
, и установим скомпилированный пакет в файле, идентифицированном"lib/math"
. Эта таблица иллюстрирует, какSin
осуществляется доступ в файлах, которые импортируют пакет после различных типов объявления импорта.Импортная декларация Местное название Sin импорт "lib/math" math.Sin импорт m "lib/math" m.Sin импорт . "lib/math" ГрехОбъявление импорта объявляет отношение зависимости между импортирующим и импортируемым пакетом. Незаконно, чтобы пакет импортировал себя, напрямую или косвенно, или напрямую импортировал пакет без ссылки на какой-либо из его экспортированных идентификаторов. Чтобы импортировать пакет исключительно для его побочных эффектов (инициализации), используйте пустой идентификатор как явное имя пакета:
импорт _ "lib/math"Пример пакета ¶
Вот полный пакет Go, реализующий параллельное простое решето.
пакет основной импорт "фмт" // Отправляем последовательность 2, 3, 4, … на канал 'ch'. функция генерации(ch chan<- int) { для я := 2; ; я++ { ch <- i // Отправляем 'i' на канал 'ch'. } } // Копируем значения из канала 'src' в канал 'dst', // удаляем те, которые делятся на «простое». func filter(src <-chan int, dst chan<- int, prime int) { for i := range src { // Цикл по значениям, полученным от 'src'. если я%простое != 0 { dst <- i // Отправляем 'i' на канал 'dst'. } } } // Главное сито: последовательные процессы фильтрации. функция решето() { ch := make(chan int) // Создать новый канал. go generate(ch) // Запустить generate() как подпроцесс. для { простое число := <-ч fmt.Print(простое число, "\n") ch1 := make(chan int) перейти фильтр(ch, ch1, prime) ч = ч1 } } функция main() { сито() }Инициализация и выполнение программы ¶
Нулевое значение ¶
Когда память выделяется для переменной , либо через объявление или вызов
new
, либо когда создается новое значение, либо через составной литерал, либо вызовmake
, и не предоставляется явная инициализация, переменной или значению присваивается значение по умолчанию. Каждому элементу такой переменной или значения присваивается нулевое значение для его типа:false
для логических значений,0
для числовых типов,""
для строк иnil
для указателей, функций, интерфейсов, срезов, каналов и карт. Эта инициализация выполняется рекурсивно, поэтому, например, каждый элемент массива структур будет иметь обнуленные поля, если не указано значение.Эти два простых объявления эквивалентны:
var i int var i int = 0После
тип T структура { i int; f float64; next *T } т := новый(Т)справедливо следующее:
ти == 0 тф == 0.0 t.next == нольТо же самое будет верно и после
вар т ТИнициализация пакета ¶
В пакете инициализация переменных на уровне пакета происходит поэтапно, при этом на каждом этапе выбирается переменная, расположенная раньше всех в порядке объявления и не имеющая зависимостей от неинициализированных переменных.
Точнее, переменная уровня пакета считается готовой к инициализации, если она еще не инициализирована и либо не имеет выражения инициализации , либо ее выражение инициализации не имеет зависимостей от неинициализированных переменных. Инициализация выполняется путем многократной инициализации следующей переменной уровня пакета, которая является самой ранней в порядке объявления и готова к инициализации, пока не останется переменных, готовых к инициализации.
Если после завершения этого процесса какие-либо переменные все еще не инициализированы, эти переменные являются частью одного или нескольких циклов инициализации, и программа недействительна.
Несколько переменных в левой части объявления переменной, инициализированных одним (многозначным) выражением в правой части, инициализируются вместе: если инициализируется какая-либо из переменных в левой части, все эти переменные инициализируются на одном шаге.
вар х = а var a, b = f() // a и b инициализируются вместе, до инициализации xВ целях инициализации пакета пустые переменные обрабатываются так же, как и любые другие переменные в объявлениях.
Порядок объявления переменных, объявленных в нескольких файлах, определяется порядком, в котором файлы представляются компилятору: переменные, объявленные в первом файле, объявляются до любых переменных, объявленных во втором файле, и т. д. Чтобы обеспечить воспроизводимое поведение инициализации, системам сборки рекомендуется представлять компилятору несколько файлов, принадлежащих одному пакету, в лексическом порядке имен файлов.
Анализ зависимости не опирается на фактические значения переменных, а только на лексические ссылки на них в источнике, анализируемые транзитивно. Например, если
x
выражение инициализации переменной ссылается на функцию, тело которой ссылается на переменную,y
тоx
зависит отy
. В частности:
- Ссылка на переменную или функцию — это идентификатор, обозначающий эту переменную или функцию.
- Ссылка на метод
m
— это значение метода или выражение метода в формеt.m
, где (статический) типt
не является типом интерфейса, а методm
находится в наборе методовt
. Не имеет значения,t.m
вызывается ли результирующее значение функции.- Переменная, функция или метод
x
зависят от переменной,y
еслиx
выражение инициализации или тело (для функций и методов) содержит ссылку наy
или на функцию или метод, которые зависят отy
.Например, учитывая заявления
вар ( а = с + б // == 9 б = ф() // == 4 с = ф() // == 5 d = 3 // == 5 после завершения инициализации ) функция f() int { д++ вернуться д }порядок инициализации —
d
,b
,c
,a
. Обратите внимание, что порядок подвыражений в выражениях инициализации не имеет значения:a = c + b
иa = b + c
в этом примере получается тот же порядок инициализации.Анализ зависимостей выполняется для каждого пакета; рассматриваются только ссылки, ссылающиеся на переменные, функции и (не интерфейсные) методы, объявленные в текущем пакете. Если между переменными существуют другие, скрытые, зависимости данных, порядок инициализации между этими переменными не указан.
Например, учитывая заявления
var x = I(T{}).ab() // x имеет необнаруженную, скрытую зависимость от a и b var _ = sideEffect() // не связано с x, a или b вар а = б вар б = 42 интерфейс типа I { ab() []int } тип T struct{} функция (T) ab() []int { return []int{a, b} }переменная
a
будет инициализирована после ,b
ноx
инициализируется ли она доb
, междуb
иa
, или послеa
, и, таким образом, момент, в которыйsideEffect()
вызывается (до или послеx
инициализации), не указан.Переменные также могут быть инициализированы с использованием функций, имена которых
init
объявлены в блоке пакета, без аргументов и параметров результата.функция init() { … }Несколько таких функций могут быть определены для одного пакета, даже в пределах одного исходного файла. В блоке пакета
init
идентификатор может использоваться только для объявленияinit
функций, но сам идентификатор не объявляется . Таким образом,init
на функции нельзя ссылаться из любого места программы.Весь пакет инициализируется путем присвоения начальных значений всем переменным уровня пакета с последующим вызовом всех
init
функций в том порядке, в котором они указаны в исходном коде, возможно, в нескольких файлах, как это представлено компилятору.Инициализация программы ¶
Пакеты полной программы инициализируются поэтапно, по одному пакету за раз. Если пакет имеет импорт, импортированные пакеты инициализируются до инициализации самого пакета. Если несколько пакетов импортируют пакет, импортированный пакет будет инициализирован только один раз. Импорт пакетов, по своей конструкции, гарантирует, что не может быть циклических зависимостей инициализации. Точнее:
Учитывая список всех пакетов, отсортированных по пути импорта, на каждом шаге инициализируется первый неинициализированный пакет в списке, для которого все импортированные пакеты (если таковые имеются) уже инициализированы . Этот шаг повторяется до тех пор, пока все пакеты не будут инициализированы.
Инициализация пакета — инициализация переменных и вызов
init
функций — происходит в одной goroutine, последовательно, по одному пакету за раз.init
Функция может запускать другие goroutine, которые могут работать одновременно с кодом инициализации. Однако инициализация всегда упорядочивает функцииinit
: она не вызовет следующую, пока предыдущая не вернется.Выполнение программы ¶
Полная программа создается путем транзитивного связывания одного неимпортированного пакета, называемого основным пакетом , со всеми пакетами, которые он импортирует. Основной пакет должен иметь имя пакета
main
и объявлять функцию,main
которая не принимает аргументов и не возвращает значения.функция main() { … }Выполнение программы начинается с инициализации программы и последующего вызова функции
main
в packagemain
. Когда вызов этой функции возвращается, программа завершается. Она не ждетmain
завершения других (не ) goroutines.Ошибки ¶
Предварительно объявленный тип
error
определяется какинтерфейс ошибки типа { Ошибка() строка }Это обычный интерфейс для представления состояния ошибки, где значение nil не представляет ошибки. Например, функция для чтения данных из файла может быть определена:
func Read(f *File, b []byte) (n int, err ошибка)Паники во время выполнения ¶
Ошибки выполнения, такие как попытка индексировать массив за пределами границ, вызывают панику во время выполнения, эквивалентную вызову встроенной функции
panic
со значением определенного реализацией типа интерфейсаruntime.Error
. Этот тип удовлетворяет предварительно объявленному типу интерфейсаerror
. Точные значения ошибок, которые представляют различные состояния ошибок во время выполнения, не указаны.время выполнения пакета Тип интерфейса ошибки { ошибка // и возможно другие методы }Системные соображения ¶
Упаковка
unsafe
¶Встроенный пакет
unsafe
, известный компилятору и доступный через путь импорта"unsafe"
, предоставляет возможности для низкоуровневого программирования, включая операции, нарушающие систему типов. Пакет usingunsafe
должен быть проверен вручную на безопасность типов и может быть непереносимым. Пакет предоставляет следующий интерфейс:посылка небезопасна type ArbitraryType int // сокращение для произвольного типа Go; это не настоящий тип тип Указатель * ПроизвольныйТип func Alignof(переменная ПроизвольныйТип) uintptr func Offsetof(селектор ПроизвольныйТип) uintptr func Sizeof(переменная ПроизвольныйТип) uintptr тип IntegerType int // сокращение для целочисленного типа; это не настоящий тип func Add(ptr Указатель, len IntegerType) Указатель func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType func SliceData(slice []ПроизвольныйТип) *ПроизвольныйТип func String(ptr *byte, len IntegerType) строка func StringData(str string) *byteA
Pointer
— это тип указателя , ноPointer
значение не может быть разыменовано . Любой указатель или значение основного типаuintptr
можно преобразовать в тип основного типаPointer
и наоборот. Эффект преобразования междуPointer
иuintptr
определяется реализацией.вар ф float64 биты = *(*uint64)(небезопасный.Указатель(&f)) тип ptr небезопасный.Указатель биты = *(*uint64)(ptr(&f)) функция f[P ~*B, B любой](p P) uintptr { вернуть uintptr(небезопасный.Указатель(p)) } var p ptr = нольФункции
Alignof
иSizeof
принимают выражениеx
любого типа и возвращают выравнивание или размер, соответственно, гипотетической переменной,v
как если быv
она была объявлена черезvar v = x
.Функция
Offsetof
принимает (возможно, заключенный в скобки) селекторs.f
, обозначающий полеf
структуры, обозначенной какs
или*s
, и возвращает смещение поля в байтах относительно адреса структуры. Еслиf
это встроенное поле , оно должно быть доступно без косвенных ссылок указателя через поля структуры. Для структурыs
с полемf
:uintptr(небезопасный.Указатель(&s)) + небезопасный.Смещение(sf) == uintptr(небезопасный.Указатель(&s.f))Архитектуры компьютеров могут требовать, чтобы адреса памяти были выровнены ; то есть, чтобы адреса переменной были кратны фактору, выравниванию типа переменной . Функция
Alignof
принимает выражение, обозначающее переменную любого типа, и возвращает выравнивание (типа) переменной в байтах. Для переменнойx
:uintptr(небезопасный.Указатель(&x)) % небезопасный.Выравнивание(x) == 0Тип (переменная)
T
имеет переменный размер , еслиT
является параметром типа или если это массив или тип структуры, содержащий элементы или поля переменного размера. В противном случае размер является постоянным . ВызовыAlignof
,Offsetof
и являются константными выражениямиSizeof
времени компиляции типа , если их аргументы (или структура в выражении селектора для ) являются типами постоянного размера.uintptr
s
s.f
Offsetof
Функция
Add
добавляет и возвращаетlen
обновленныйptr
указательunsafe.Pointer(uintptr(ptr) + uintptr(len))
[ Go 1.17 ].len
Аргумент должен быть целочисленного типа или нетипизированной константой . Константныйlen
аргумент должен быть представлен значением типаint
; если это нетипизированная константа, то ей присваивается типint
. Правила допустимого использования поPointer
-прежнему применяются.Функция
Slice
возвращает срез, базовый массив которого начинается сptr
, а его длина и емкость равныlen
.Slice(ptr, len)
эквивалентно(*[len]ПроизвольныйТип)(небезопасный.Указатель(ptr))[:]за исключением того, что в качестве особого случая, если
ptr
равноnil
иlen
равно нулю,Slice
возвращаетсяnil
[ Go 1.17 ].Аргумент
len
должен быть целочисленного типа или нетипизированной константой . Константныйlen
аргумент должен быть неотрицательным и представимым значением типаint
; если это нетипизированная константа, ей присваивается типint
. Во время выполнения, еслиlen
отрицательно или еслиptr
равноnil
иlen
не равно нулю, возникает паника во время выполнения [ Go 1.17 ].Функция
SliceData
возвращает указатель на базовый массив аргументаslice
. Если емкость срезаcap(slice)
не равна нулю, этот указатель равен&slice[:1][0]
. Еслиslice
равенnil
, результат равенnil
. В противном случае это неnil
указатель на неуказанный адрес памяти [ Go 1.20 ].Функция
String
возвращаетstring
значение, базовые байты которого начинаются сptr
и длина которого равнаlen
. К аргументуptr
и применяются те же требованияlen
, что и в функцииSlice
. Еслиlen
равно нулю, результатом является пустая строка""
. Поскольку строки Go неизменяемы, переданные байтыString
не должны впоследствии изменяться. [ Go 1.20 ]Функция
StringData
возвращает указатель на базовые байты аргументаstr
. Для пустой строки возвращаемое значение не указано и может бытьnil
. Поскольку строки Go неизменяемы, байты, возвращаемые функцией,StringData
не должны изменяться [ Go 1.20 ].Гарантии размера и выравнивания ¶
Для числовых типов гарантируются следующие размеры:
размер шрифта в байтах байт, uint8, int8 1 uint16, int16 2 uint32, int32, float32 4 uint64, int64, float64, complex64 8 комплекс128 16Гарантируются следующие минимальные свойства выравнивания:
- Для переменной
x
любого типа:unsafe.Alignof(x)
не менее 1.- Для переменной
x
типа struct:unsafe.Alignof(x)
наибольшее из всех значенийunsafe.Alignof(x.f)
для каждого поляf
,x
но не менее 1.- Для переменной
x
типа массива:unsafe.Alignof(x)
то же самое, что и выравнивание переменной типа элемента массива.Тип структуры или массива имеет размер ноль, если он не содержит полей (или элементов, соответственно), имеющих размер больше нуля. Две различные переменные нулевого размера могут иметь один и тот же адрес в памяти.
Приложение ¶
Языковые версии ¶
Гарантия совместимости с Go 1 гарантирует, что программы, написанные по спецификации Go 1, будут продолжать компилироваться и работать правильно, без изменений, в течение всего срока действия этой спецификации. В более общем плане, по мере внесения изменений и добавления функций в язык, гарантия совместимости гарантирует, что программа Go, работающая с определенной версией языка Go, продолжит работать с любой последующей версией.
Например, возможность использовать префикс
0b
для двоичных целочисленных литералов была введена в Go 1.13, обозначенная как [ Go 1.13 ] в разделе о целочисленных литералах . Исходный код, содержащий целочисленный литерал, такой как ,0b1011
будет отклонен, если подразумеваемая или требуемая версия языка, используемая компилятором, старше Go 1.13.В следующей таблице описана минимальная версия языка, необходимая для функций, представленных после Go 1.
Перейти 1.9 ¶
- Объявление псевдонима может использоваться для объявления псевдонима для типа.
Перейти 1.13 ¶
- Целочисленные литералы могут использовать префиксы
0b
,0B
,0o
, и0O
для двоичных и восьмеричных литералов соответственно.- Шестнадцатеричные литералы с плавающей точкой можно записывать с использованием префиксов
0x
и0X
.- Мнимый суффикс
i
может использоваться с любым (двоичным, десятичным, шестнадцатеричным) целым числом или числом с плавающей точкой, а не только с десятичными литералами.- Цифры любого числового литерала могут быть разделены (сгруппированы) с помощью подчеркивания
_
.- Счетчик сдвига в операции сдвига может быть целым числом со знаком.
Перейти 1.14 ¶
- Внедрение метода более одного раза через разные встроенные интерфейсы не является ошибкой.
Перейти 1.17 ¶
- Срез может быть преобразован в указатель массива, если типы элементов среза и массива совпадают, а массив не длиннее среза.
- Встроенный пакет
unsafe
включает в себя новые функцииAdd
иSlice
.Перейти 1.18 ¶
В версии 1.18 в язык добавлены полиморфные функции и типы («дженерики»). В частности:
- В набор операторов и знаков препинания включен новый токен
~
.- Объявления функций и типов могут объявлять параметры типа .
- Типы интерфейсов могут включать произвольные типы (а не только имена типов интерфейсов), а также
~T
элементы объединений и типов.- Набор предопределенных типов включает новые типы
any
иcomparable
.Перейти 1.20 ¶
- Срез может быть преобразован в массив, если типы элементов среза и массива совпадают, а массив не длиннее среза.
- Встроенный пакет
unsafe
включает в себя новые функцииSliceData
,String
, иStringData
.- Сравнимые типы (например, обычные интерфейсы) могут удовлетворять
comparable
ограничениям, даже если аргументы типа не являются строго сравнимыми.Перейти 1.21 ¶
- Набор предопределенных функций включает новые функции
min
,max
, иclear
.- Вывод типа использует типы методов интерфейса для вывода. Он также выводит аргументы типа для универсальных функций, назначенных переменным или переданных в качестве аргументов другим (возможно универсальным) функциям.
Перейти 1.22 ¶
- В операторе «for» каждая итерация имеет свой собственный набор итеративных переменных, а не использует одни и те же переменные в каждой итерации.
- Оператор «for» с предложением «range» может выполнять итерацию по целым значениям от нуля до верхнего предела.
Перейти 1.23 ¶
- Оператор «for» с предложением «range» принимает функцию итератора в качестве выражения диапазона.
Правила унификации типов ¶
Правила унификации типов описывают, как и когда два типа объединяются. Точные детали важны для реализаций Go, влияют на специфику сообщений об ошибках (например, сообщает ли компилятор о выводе типа или другой ошибке) и могут объяснить, почему вывод типа не работает в необычных ситуациях кода. Но в целом эти правила можно игнорировать при написании кода Go: вывод типа разработан так, чтобы в основном «работать так, как ожидается», и правила унификации настраиваются соответствующим образом.
Унификация типов контролируется режимом сопоставления , который может быть точным или свободным . Поскольку унификация рекурсивно спускается по структуре составного типа, режим сопоставления, используемый для элементов типа, режим сопоставления элементов , остается тем же самым, что и режим сопоставления, за исключением случаев, когда два типа унифицированы для присваиваемости ( ): в этом случае режим сопоставления свободный на верхнем уровне, но затем меняется на точный для типов элементов, отражая тот факт, что типы не обязательно должны быть идентичными, чтобы быть присваиваемыми.
≡A
Два типа, не являющиеся связанными параметрами типа, точно унифицируются, если выполняется любое из следующих условий:
- Оба типа идентичны .
- Оба типа имеют одинаковую структуру, а типы их элементов полностью унифицированы.
- Только один тип является несвязанным параметром типа с основным типом , и этот основной тип унифицируется с другим типом в соответствии с правилами унификации (нестрогая унификация на верхнем уровне и точная унификация для типов элементов).
≡A
Если оба типа являются параметрами связанного типа, они унифицируются согласно заданным режимам соответствия, если:
- Оба параметра типа идентичны.
- Максимум один из параметров типа имеет известный аргумент типа. В этом случае параметры типа объединяются : они оба обозначают один и тот же аргумент типа. Если ни один из параметров типа еще не имеет известного аргумента типа, будущий аргумент типа, выведенный для одного из параметров типа, одновременно выводится для них обоих.
- Оба параметра типа имеют известный аргумент типа, и аргументы типа унифицируются в соответствии с заданными режимами соответствия.
Параметр одного связанного типа
P
и другой типT
унифицируются согласно заданным режимам соответствия, если:
P
не имеет известного аргумента типа. В этом случаеT
выводится как аргумент типа дляP
.P
имеет известный аргумент типаA
и унифицируется согласно заданным режимамA
сопоставленияT
, и выполняется одно из следующих условий:
- Оба
A
иT
являются типами интерфейсов: В этом случае, если обаA
иT
также являются определенными типами, они должны быть идентичны . В противном случае, если ни один из них не является определенным типом, они должны иметь одинаковое количество методов (унификацияA
иT
уже установлено, что методы совпадают).- Ни то,
A
ни другое неT
являются типами интерфейсов: в этом случае, еслиT
это определенный тип,T
заменяетA
в качестве аргумента выведенного типа дляP
.Наконец, два типа, не являющиеся связанными параметрами типа, унифицируются слабо (и в соответствии с режимом сопоставления элементов), если:
- Оба типа полностью совпадают.
- Один тип — это определенный тип , другой тип — это литерал типа, но не интерфейс, и их базовые типы унифицируются в соответствии с режимом сопоставления элементов.
- Оба типа являются интерфейсами (но не параметрами типа) с идентичными терминами типа , оба или ни один из них не встраивают предварительно объявленный тип , сопоставимый , соответствующие типы методов точно унифицируются, а набор методов одного из интерфейсов является подмножеством набора методов другого интерфейса.
- Только один тип является интерфейсом (но не параметром типа), соответствующие методы двух типов унифицируются по режиму сопоставления элементов, а набор методов интерфейса является подмножеством набора методов другого типа.
- Оба типа имеют одинаковую структуру, а их типы элементов унифицируются в соответствии с режимом сопоставления элементов.
…
!…
Приглашаю всех высказываться в Комментариях. Критику и обмен опытом одобряю и приветствую. В особо хороших комментариях сохраняю ссылку на сайт автора!
И не забывайте, пожалуйста, нажимать на кнопки социальных сетей, которые расположены под текстом каждой страницы сайта.
Продолжение тут…