Найти в Дзене
Golang-news

Нулевые значения Golang

Оглавление

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

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

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

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

Что такое нулевое значение?

Когда переменная объявляется без явно заданного начального значения, компилятор Go автоматически присваивает ей нулевое значение типа.

Это гарантирует, что каждая переменная имеет допустимое значение, независимо от того, было ли оно установлено программистом или нет.

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

Посмотрите на пример кода ниже:

-2

Мы не присваивали явное значение переменной number, но компилятор автоматически установил его равным 0, в чем вы убедитесь, запустив код.

Глагол "%v", используемый в fmt.Printf строке, отображает значение в формате по умолчанию, но мы могли бы использовать "%d" вместо этого глагол, который используется специально для целых чисел, поскольку мы уже знали тип переменной number.

Список нулевых значений для каждого базового типа данных

Ниже приведена таблица, содержащая нулевые значения для всех основных типов данных в Go:

-3

Большинство нулевых значений являются очевидными значениями по умолчанию. Например, для целых чисел имеет смысл начинать с нуля. Также имеет смысл инициализировать строки как пустые, с нулевой длиной, а boolean переменные должны иметь значение false, если явно не задано значение true.

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

Следующая цитата из официальной документации Go объясняет:

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

Использование нулевого значения по умолчанию

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

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

Код ниже использует нулевые значения в качестве неявных значений по умолчанию:

-4

В приведенном выше простом примере мы перебираем строку text, чтобы проверить, содержит ли она буквы E и Z.

Если строка содержит букву E, то contains E переменной будет присвоено значение true. Аналогично, если строка содержит букву Z, то contains Z переменной будет присвоено значение true.

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

Если строка не содержит ни одной из искомых букв, то обеим переменным будет присвоено значение false, даже если мы не присвоили явно начальное значение этим переменным. Мы просто используем нулевые значения по умолчанию.

Работа с нулевым значением срезов

Как мы видели в таблице выше, нулевое значение для срезов равно nil. Однако, когда к срезу добавляются значения, при необходимости память выделяется автоматически, что означает, что ее нулевое значение готово к использованию без какой-либо инициализации.

В приведенном ниже примере показано использование slice сразу после его объявления (без какой-либо явной инициализации):

-5

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

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

Наконец, мы соединяем строки в нашем фрагменте вместе, объединяя их в одну длинную строку, которую мы выводим на консоль.

Нулевое значение сложных типов может быть неявно инициализировано

Более сложные типы, такие как объявленные как структуры в стандартной библиотеке Go, также имеют нулевые значения.

Иногда эти типы должны быть каким-то образом явно инициализированы, например, путем вызова функции. Однако обычно лучше, если типы готовы к использованию сразу после объявления.

Код ниже содержит пример последнего:

-6

Начнем с объявления переменной buffer, которая может содержать байты произвольной длины. Затем мы записываем в него некоторый текст — в виде байтового среза.

Нам не нужно было выполнять какую-либо явную инициализацию, потому что bytes.Buffer по умолчанию инициализируется нулевое значение.

Наконец, мы копируем буфер в os.Stdout, что является относительно низкоуровневым способом записи текста в консоль.

Мы могли бы вызвать fmt.Print(buffer.String()), что, возможно, было бы более идиоматичным способом выполнения той же работы, но это сначала преобразовало бы байты в буфере в строку, а не просто распечатало бы их.

Некоторые сложные типы требуют явной инициализации

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

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

В приведенном ниже коде показан пример этого из стандартной библиотеки Go:

-7

Мы вызываем внешнюю программу, в данном случае встроенную команду Linux Bash echo, чтобы вывести сообщение на консоль.

Необходимо создать cmd переменную для хранения информации о внешней команде, которую мы хотим вызвать. Однако нам нужно вызвать exec.Command функцию, чтобы предоставить cmdэту информацию.

Первый аргумент — exec.Command это имя внешней команды, которую мы хотим вызвать. Последующие аргументы передаются самой команде. Мы знаем, что echo он будет соединять любые строки, которые ему даются, с одним пробелом перед их отображением.

Вызов Output метода фактически запускает команду и возвращает ее стандартный вывод в виде байтового фрагмента.

Если бы мы удалили строку, выполняющую явную инициализацию переменной cmd, Output метод сработал бы panic (с сообщением об ошибке "exec: no command"), поскольку ему нечего было бы запускать.

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