Основные типы данных в Go

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

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

Общие сведения

Чтобы лучше разобраться в типах данных, просто представьте различные типы данных, которые мы используем в повседневной жизни. Это числа: они бывают целыми (…, -1, 0, 1,…) и иррациональными (π).

Обычно в математике мы можем складывать числа разных типов и получать какой-то результат. Мы можем сложить 5 и π, например:

5 + π

В таком случае для учета иррационального числа можно оставить в качестве ответа уравнение; или же можно округлить π до десятичного числа, а затем сложить их:

5 + π = 5 + 3.14 = 8.14

Но если попытаться оценить числа относительно других типов данных – например, слов, — это будет бессмысленно. Как бы вы решили следующее уравнение?

shark + 8

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

Целые числа

Как и в математике, целые числа в программировании могут быть положительными, отрицательными или 0 (…, -1, 0, 1,…). В Go целое число называется int. Как и в случае с другими языками программирования, использовать запятые в четырехзначных или более числах нельзя (то есть 1,000 нужно писать как 1000).

Чтобы вывести целое число, просто введите:

fmt.Println(-459)
-459

Или же можно объявить переменную, которая в этом случае является символом числа, например так:

var absoluteZero int = -459
fmt.Println(absoluteZero)
-459

В Go можно делать вычисления с целыми числами. В следующем блоке кода мы используем оператор присваивания := для объявления и создания экземпляра переменной sum:

sum := 116 - 68
fmt.Println(sum)
48

Как показывает вывод, математический оператор — вычел целое число 68 из 116, в результате чего получилось 48. Подробнее об объявлении переменных мы поговорим в специальном разделе этого мануала.

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

Числа с плавающей точкой

Числа с плавающей точкой (float) используются для представления действительных чисел, которые не могут быть выражены как целые числа. Действительные числа включают все рациональные и иррациональные числа, и поэтому числа с плавающей точкой могут содержать дробную часть: например, 9.0 или -116.42. Проще говоря, число с плавающей точкой в программе Go – это число, которое содержит десятичную точку.

Как и в случае с целыми числами, число с плавающей точкой можно вывести на экран так:

fmt.Println(-459.67)
-459.67

Также можно объявить переменную и присвоить ей такое число:

absoluteZero := -459.67
fmt.Println(absoluteZero)
-459.67

Как и с целыми числами, с числами с плавающей точкой можно проводить вычисления:

var sum = 564.0 + 365.24
fmt.Println(sum)
929.24

Работая с целыми числами и числами с плавающей точкой важно помнить, что 3 ≠ 3,0, так как 3 – это целое число, а 3,0 – число с плавающей точкой.

Другие числовые типы

В дополнение к целым числам и числам с плавающей точкой Go имеет еще два типа числовых данных, которые отличаются характером их размеров (статическим или динамическим). Первые – это независимые от архитектуры типы, чей размер данных в битах не изменяется независимо от того, на какой машине выполняется код.

Большинство системных архитектур сегодня 32-битные или 64-битные. Например, вы можете разрабатывать программу для современного ноутбука Windows, на котором работает 64-битная операционная система. Однако если вы что-то разрабатываете для других устройств (например, для часов для фитнеса), вы можете работать с 32-битной архитектурой. Если вы используете независимый от архитектуры тип (такой как int32), то тип будет иметь постоянный размер независимо от того, для какой архитектуры вы компилируете код.

Второй тип является зависимым от архитектуры. В этом типе размер может варьироваться в зависимости от архитектуры, на которой собрана программа. Например, если вы используете тип int, при компиляции Go для 32-битной архитектуры размер типа данных будет 32 бита. Если программа скомпилирована для 64-битной архитектуры, переменная будет иметь размер 64 бита.

Кроме того, целочисленные типы данных также бывают двух основных типов: целые числа со знаком и беззначные числа. Тип int8 является целым числом со знаком и может иметь значение от -128 до 127. Тип uint8 является беззначным целым числом и может иметь только положительное значение от 0 до 255.

Диапазоны в Go основаны на размере бита. Для двоичных данных 8 битов могут представлять в общей сложности 256 различных значений. Поскольку тип int должен поддерживать как положительные, так и отрицательные значения, 8-битное целое число (int8) будет иметь диапазон от -128 до 127, что в общей сложности составит 256 уникальных возможных значений.

Go имеет следующие архитектурно-независимые целочисленные типы:

uint8       unsigned  8-bit integers (0 to 255)
uint16      unsigned 16-bit integers (0 to 65535)
uint32      unsigned 32-bit integers (0 to 4294967295)
uint64      unsigned 64-bit integers (0 to 18446744073709551615)
int8        signed  8-bit integers (-128 to 127)
int16       signed 16-bit integers (-32768 to 32767)
int32       signed 32-bit integers (-2147483648 to 2147483647)
int64       signed 64-bit integers (-9223372036854775808 to 9223372036854775807)

Числа с плавающей точкой и комплексные числа также бывают разных размеров:

float32     IEEE-754 32-bit floating-point numbers
float64     IEEE-754 64-bit floating-point numbers
complex64   complex numbers with float32 real and imaginary parts
complex128  complex numbers with float64 real and imaginary parts

Есть также пара типов псевдонимов, которые присваивают полезные имена определенным типам данных:

byte        alias for uint8
rune        alias for int32

Цель псевдонима byte – прояснить, когда в элементах символьных строк программа использует байты в качестве общего вычислительного измерения (в отличие от маленьких целых чисел, не связанных с таким измерением данных). Хотя byte  и uint8 после компиляции программы идентичны, byte чаще используется для представления символьных данных в числовой форме, тогда как тип uint8 в программе представлен числом.

Псевдоним rune немного отличается. Если byte и uint8 – это одни и те же данные, rune может быть одним байтом или четырьмя байтами (диапазоном, определяемым int32). Тип rune используется для представления символа Unicode, тогда как символы ASCII могут быть представлены только типом данных int32.

Кроме того, Go имеет следующие зависимые от архитектуры типы:

uint     unsigned, either 32 or 64 bits
int      signed, either 32 or 64 bits

uintptr  unsigned integer large enough to store the uninterpreted bits of a pointer value

Выбор числовых типов данных

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

Как говорилось ранее в этой статье, существуют архитектурно-независимые и архитектурно-зависимые типы. Для целочисленных данных в Go обычно используются зависимые типы, такие как int или uint, вместо int64 или uint64. Это обычно увеличивает скорость обработки для целевой архитектуры. Например, если вы используете int64 и компилируете код в 32-битной архитектуре, для обработки этих значений потребуется как минимум вдвое больше времени, так как для перемещения данных по архитектуре потребуются дополнительные циклы ЦП. Если вместо этого вы используете int, программа подстроится под архитектуру и будет значительно быстрее обрабатываться.

Если вы заранее знаете, что не будете превышать определенный диапазон размеров, то можете архитектурно-независимые типы – это позволяет увеличить скорость и уменьшить использование памяти. Например, если вы знаете, что ваши данные не превысят значение 100 и представлены только положительными числами, с помощью типа uint8 вы можете сделать программу более эффективной, так как она потребует меньше памяти.

Теперь давайте посмотрим, что произойдет, если мы превысим эти диапазоны в программе.

Переполнение и сворачивание данных

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

В следующем примере устанавливается максимальное значение maxUint32:

package main
import "fmt"
func main() {
var maxUint32 uint32 = 4294967295 // Max uint32 size
fmt.Println(maxUint32)
}

Этот код скомпилируется и запустится со следующим результатом:

4294967295

Если во время выполнения прибавить 1 к этому значению, оно свернется до 0:

0

Еще можно попробовать изменить программу, добавив 1 к переменной при ее назначении, до компиляции:

package main
import "fmt"
func main() {
var maxUint32 uint32 = 4294967295 + 1
fmt.Println(maxUint32)
}

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

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

prog.go:6:36: constant 4294967296 overflows uint32

Понимание границ данных поможет вам избежать потенциальных ошибок в вашей программе в будущем.

Булев тип данных

Булевы, или логические данные могут принимать одно из двух значений: true и false. Этот тип определяется как bool при объявлении в коде. Логические данные используются для представления значений истинности, которые связаны с логической ветвью математики (определяет алгоритмы в области компьютерных наук).

Значения true и false всегда будут объявляться строчными буквами t и f соответственно, так как это их предварительно объявленные идентификаторы в Go.

Результаты многих математических операций можно оценить как истинные или ложные:

  • Больше, чем:
    • 500 > 100 true
    • 1 > 5 false
  • Меньше, чем:
    • 200 < 400 true
    • 4 < 2 false
  • Равно:
    • 5 = 5 true
    • 500 = 400 false

Как и с числами, хранить логическое значение можно в переменной:

myBool := 5 > 8

Затем мы можем вывести логическое значение с помощью функции fmt.Println():

fmt.Println(myBool)

Поскольку 5 не больше 8, мы получим следующий вывод:

false

Строки

Строка – это последовательность одного или нескольких символов (букв, цифр, других символов), которая может быть константой или переменной. Строки в Go существуют либо внутри обратных кавычек (`), либо в двойных кавычках («), и в зависимости от этого имеют разные характеристики.

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

Необработанные строковые литералы

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

a := `Say "hello" to Go!`
fmt.Println(a)
Say "hello" to Go!

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

a := `Say "hello" to Go!\n`
fmt.Println(a)

Поскольку обратный слеш не имеет специального значения в таком строковом литерале, он фактически отображается (как \n) и не создает новой строки:

Say "hello" to Go!\n

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

a := `This string is on
multiple lines
within a single back
quote on either side.`

fmt.Println(a)
This string is on
multiple lines
within a single back
quote on either side.

Интерпретируемые строковые литералы

Интерпретируемые строковые литералы являются символьными последовательностями в двойных кавычках. Внутри кавычек может появляться любой символ, кроме новой строки и незакрытых двойных кавычек. Чтобы использовать двойные кавычки внутри интерпретируемой строки, используйте обратный слэш в качестве escape-символа, так:

a := "Say \"hello\" to Go!"
fmt.Println(a)
Say "hello" to Go!

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

Строки с символами UTF-8

UTF-8 – это схема, используемая для кодирования символов переменной ширины в один-четыре байта. Go поддерживает символы UTF-8 «из коробки», без каких-либо специальных настроек, библиотек или пакетов. Римские символы, такие как буква A, могут быть представлены значением ASCII, таким как число 65. Однако для специальных символов, таких как международный символ 世, потребуется UTF-8. Для данных UTF-8 Go использует тип-псевдоним rune.

a := "Hello, 世界"

Вы можете использовать ключевое слово range в цикле for для индексации любых строк Go, даже строк UTF-8.

Циклы for и ключевое слово range мы опишем позже более подробно в этой серии мануалов по Go. На данный момент важно знать, что их можно использовать для подсчета байтов в данной строке:

package main
import "fmt"
func main() {
a := "Hello, 世界"
for i, c := range a {
fmt.Printf("%d: %s\n", i, string(c))
}
fmt.Println("length of 'Hello, 世界': ", len(a))
}

В приведенном выше блоке кода мы объявили переменную a и присвоили ей значение Hello, 世界. Этот текст содержит символы UTF-8.

Затем мы использовали стандартный цикл for, а также ключевое слово range. В Go ключевое слово range индексирует символы в строке, возвращая за раз один символ, а также индекс байта, в котором находится символ в строке.

Используя функцию fmt.Printf, мы предоставили строку формата %d: %s\n. %d – это оператор для цифры (в данном случае – это целое число), а %s – это оператор для строки. Затем мы предоставили значения для i (это текущий индекс цикла for) и c (это текущий символ в цикле for).

В конце выводится вся длина переменной а через встроенную функцию len.

Ранее мы упоминали, что rune является псевдонимом для int32 и может состоять из одного-четырех байтов. Для определения символа 世требуется три байта, и при ранжировании индекс перемещается соответствующим образом через строку UTF-8. Потому i отображается непоследовательно.

0: H
1: e
2: l
3: l
4: o
5: ,
6:
7: 世
10: 界
length of 'Hello, 世界':  13

Как видите, строка оказалась длиннее, чем количество символов.

Объявление типов данных для переменных

Теперь, когда вы знаете основные примитивные типы данных, мы рассмотрим, как присваивать эти типы переменным в Go.

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

В следующем примере мы объявим переменную pi типа float64.

Ключевое слово var — это первое, что нужно указать:

var pi float64

За ним идет имя переменной:

var pi float64

А далее – тип данных:

var pi float64

Мы также можем указать начальное значение, например, 3.14:

var pi float64 = 3.14

Go – это статически типизированный язык. Это означает, что каждый оператор в программе проверяется во время компиляции. Это также означает, что тип данных связывается с переменной, тогда как в динамически связанных языках тип данных связан со значением.

В Go тип объявляется при объявлении переменной:

var pi float64 = 3.14
var week int = 7

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

Этим Go отличается от языков типа PHP, где тип данных связан со значением:

$s = "sammy";         // $s is automatically a string
$s = 123;             // $s is automatically an integer

В предыдущем блоке кода первая переменная $s является строкой, поскольку ей присваивается значение «sammy», а вторая переменная $s является целым числом, поскольку она имеет значение 123.

Теперь давайте рассмотрим более сложные типы данных, такие как массивы.

Массивы

Массив – это упорядоченная последовательность элементов. Емкость массива определяется во время его создания. Как только массиву был выделен размер, он больше не может быть изменен. Поскольку размер массива является статическим, он выделяет память только один раз. Поэтому массивы – не самый гибкий тип, но он повышает производительность вашей программы. Массивы обычно используются при оптимизации программ. Срезы, о которых пойдет речь ниже, являются более гибким типом и представляют собой то, что называлось бы массивами в других языках программирования.

Массивы определяются путем объявления размера массива, а затем типа данных со значениями в фигурных скобках {}.

Массив строк выглядит так:

[3]string{"blue coral", "staghorn coral", "pillar coral"}

Мы можем сохранить массив в переменной и вывести его:

coral := [3]string{"blue coral", "staghorn coral", "pillar coral"}
fmt.Println(coral)
[blue coral staghorn coral pillar coral]

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

Срезы

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

Срезы определяются путем объявления типа данных, которому предшествуют квадратные скобки [], а значения помещаются в фигурных скобках {}.

Срез целых чисел выглядит так:

[]int{-3, -2, -1, 0, 1, 2, 3}

Срез чисел с плавающей точкой выглядит так:

[]float64{3.14, 9.23, 111.11, 312.12, 1.05}

Срез строк выглядит так:

[]string{"shark", "cuttlefish", "squid", "mantis shrimp"}

Давайте определим этот срез строк как seaCreatures:

seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp"}

Мы можем вывести элементы, вызвав переменную:

fmt.Println(seaCreatures)

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

[shark cuttlefish squid mantis shrimp]

Мы можем использовать ключевое слово append, чтобы добавить элемент в срез. Следующая команда добавит строковое значение seahorse:

seaCreatures = append(seaCreatures, "seahorse")

Вы можете проверить, был ли добавлен элемент, отобразив его:

fmt.Println(seaCreatures)
[shark cuttlefish squid mantis shrimp seahorse]

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

Карты

Карта в Go представляет собой встроенный хэш или словарь. Карты используют ключи и значения для хранения данных. Карты в программировании хорошо подходят для быстрого поиска значений по индексу или, в данном случае, по ключу. Например, вы можете захотеть сохранить карту пользователей, проиндексированную по ID пользователя. Ключом будет ID, а значением будет объект пользователя. Карта строится с помощью ключевого слова map, за которым следует тип данных ключа в квадратных скобках [], а затем тип данных значения и пары значений в фигурных скобках.

map[key]value{}

Обычно этот тип используется для хранения связанных данных. Карта выглядит следующим образом:

map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}

Вы заметите, что в дополнение к фигурным скобкам в карте также есть двоеточия. Слова слева от двоеточия являются ключами. Ключи в Go могут быть любого сопоставимого типа. Сопоставимые типы — это примитивные типы, такие как строки, целые числа и т. д. Примитивный тип определяется самим языком, а не создается путем объединения каких-либо других типов. Ключи в приведенном выше примере: name, animal, color и location.

Слова справа от двоеточия являются значениями. Значения могут быть представлены любым типом данных. Значения в приведенном выше примере: Sammy, shark, blue и ocean.

Давайте сохраним карту внутри переменной и выведем ее:

sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
fmt.Println(sammy)
map[animal:shark color:blue location:ocean name:Sammy]

Вы также можете запрашивать элементы карты по отдельности:

fmt.Println(sammy["color"])
blue

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

Читайте также: Основы работы с картами в Go

Tags: ,