Типы данных определяют виды значений, которые сохраняются определенными переменными при написании программы. Типы данных также помогают определить операции, которые можно выполнять с использованием данных.
В этой статье мы рассмотрим наиболее важные типы данных в Go. Это не исчерпывающее исследование типов данных, но оно поможет вам поближе познакомиться с доступными в Go возможностями. Понимание основных типов данных поможет писать более эффективный и понятный код.
Чтобы лучше понять типы данных следует посмотреть на различные типы данных, которые мы используем в реальном мире. Например, в реальном мире мы используем числа. Это могут быть положительные числа (0, 1, 2, …), целые числа (…, -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)
Output-459
Также мы можем декларировать переменную, которая будет выступать как символ используемого или изменяемого числа, например:
var absoluteZero int = -459
fmt.Println(absoluteZero)
Output-459
В Go можно выполнять математические операции с целыми числами. В следующем блоке кода мы используем оператор назначения :=
для декларирования и инициализации переменной sum
:
sum := 116 - 68
fmt.Println(sum)
Output48
Как видно на экране результатов, математический оператор -
был использован для вычитания целого числа 68
из 116
, в результате чего получилось 48
. Дополнительную информацию о декларировании переменных можно найти в разделе Объявление типов данных для переменных.
Целые числа можно использовать в программах Go разными способами. По мере дальнейшего изучения Go у вас появится много возможностей работать с целыми числами и развивать знания об этом типе данных.
*Число с плавающей точкой или float *— это действительное число, которое нельзя выразить в форме целого числа. В состав действительных чисел входят все рациональные и иррациональные числа, и поэтому числа с плавающей точкой могут содержать дробную часть, например 9,0 или -116,42. Чтобы представить тип float в программе Go, подумайте о числе с десятичной запятой.
Мы можем вывести число с плавающей запятой так же легко, как и целое число:
fmt.Println(-459.67)
Output-459.67
Также мы можем объявить переменную, которая будет представлять число типа float:
absoluteZero := -459.67
fmt.Println(absoluteZero)
Output-459.67
В Go можно выполнять математические операции с числами с плавающей точкой, как и с целыми числами:
var sum = 564.0 + 365.24
fmt.Println(sum)
Output929.24
При работе с целыми числами и числами с плавающей точкой важно помнить, что 3 ≠ 3.0, поскольку 3 означает целое число, а 3.0 — число с плавающей точкой.
Помимо различия между целыми числами и числами с плавающей точкой, в Go имеется два типа числовых данных, которые различаются по статическому или динамическому характеру их размера. Первый тип — это архитектурно-независимый тип, и это означает, что размер данных в битах не изменяется вне зависимости от того, на какой машине выполняется код.
Сегодня большинство системных архитектур представляют собой 32-битные или 64-битные архитектуры. Например, вы можете разрабатывать приложения для современных ноутбуков с 64-битной операционной системой Windows. Однако, если вы разрабатывает приложения для таких устройств, как фитнес-браслеты, вам может потребоваться 32-битная архитектура. Если вы используете архитектурно-независимый тип, например int32
, вне зависимости от компилируемой архитектуры, у этого типа будет постоянный размер.
Второй тип относится к конкретному варианту реализации. В этом типе разрядность может отличаться в зависимости от архитектуры, на базе которой построена программа. Например, если мы используем тип int
при компиляции в Go для 32-битной архитектуры, размер типа данных будет составлять 32 бита. Если программа компилируется для 64-битной архитектуры, размер переменной будет составлять 64 бита.
Помимо разных размеров, такие типы данных, как целые числа, могут иметь два базовых типа: со знаком и без знака. int8
— это целое число со знаком, которое может иметь значение от -128 до 127. uint8
— целое число без знака, которое может иметь только положительное значение от 0 до 255.
Диапазоны зависят от размера в битах. Для двоичных данных 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
, программа определяет 32-битный размер для 32-битной архитектуры, в результате чего обработка будет значительно быстрее.
Если вы не хотите выходить за пределы определенного диапазона размеров, выбор архитектурно-независимого типа может увеличить скорость и снизить нагрузку на память. Например, если вы знаете, что ваши данные не превысят значение 100
и будут только положительными числами, выбор uint8
сделает вашу программу более эффективной, поскольку для нее будет требоваться меньше памяти.
Мы рассмотрели некоторые возможные диапазоны типов числовых данных. Теперь посмотрим, что произойдет в случае превышения этих диапазонов в нашей программе.
При попытке сохранения значения, превышающего возможности типа данных, в Go возможны переполнение или циклический переход, в зависимости от того, рассчитывается ли значение при компиляции или во время исполнения Ошибка при компиляции возникает, если программа обнаруживает ошибку при попытке сборки программы. Ошибка времени исполнения возникает во время фактического выполнения уже скомпилированной программы.
В следующем прмере мы зададим для maxUint32
максимальное значение:
package main
import "fmt"
func main() {
var maxUint32 uint32 = 4294967295 // Max uint32 size
fmt.Println(maxUint32)
}
После компиляции и запуска получим следующий результат:
Output4294967295
Если мы прибавим 1
к значению времени исполнения, произойдет циклический переход на 0
:
Output0
Теперь изменим программу, чтобы прибавить 1
к переменной при ее назначении, то есть до компиляции:
package main
import "fmt"
func main() {
var maxUint32 uint32 = 4294967295 + 1
fmt.Println(maxUint32)
}
Во время компиляции, если компилятор определяет, что значение слишком большое для заданного типа данных, он выводит сообщение об ошибке переполнения
. Это означает, что рассчитанное значение слишком большое для заданного типа данных.
Поскольку компилятор может определить переполнение, он выведет сообщение об ошибке:
Outputprog.go:6:36: constant 4294967296 overflows uint32
Понимание ограничений данных поможет избежать потенциальных ошибок программы в будущем.
Мы поговорили о числовых типах, и теперь пришло время перейти к хранению логических значений.
Тип данных boolean может иметь значение истина
или ложь
и определяется как bool
при декларировании. Логические операторы используются для представления значений истины, связанных с логическим ответвлением математики, которое информирует алгоритмы в информатике.
Значения true
и false
всегда обозначаются символами t
и f
в нижнем регистре, поскольку эти идентификаторы заранее декларированы в Go.
Многие математические операции дают ответы, соответствующие значениям «истина» или «ложь»:
Как и для чисел, значения логических операторов могут храниться в переменных:
myBool := 5 > 8
Мы можем распечатать значение логического оператора посредством вызова функции fmt.Println()
:
fmt.Println(myBool)
Поскольку 5
не больше 8
, мы получим следующий результат:
Outputfalse
Чем больше программ вы напишете на Go, тем лучше вы поймете принципы работы логических операторов и влияние оценки истинности
и ложности
разных функций или операций на выполнение программы.
Строка — это последовательность из одного или нескольких символов (буквы, числа, символы), которая может представлять собой константу или переменную. Строки существуют внутри одинарных кавычек `
или двойных кавычек "
в Go и имеют разные характеристики в зависимости от типа кавычек.
Если вы используете одинарные кавычки, вы создаете необработанный строковый литерал. Если вы используете двойные кавычки, вы создаете интерпретируемый строковый литерал.
Необработанные строковые литералы — это последовательности символов между одинарными кавычками. Каждый символ внутри кавычек будет выглядеть точно так же, как он отображается, за исключением самих символов одинарных кавычек.
a := `Say "hello" to Go!`
fmt.Println(a)
OutputSay "hello" to Go!
Специальные символы в строках обычно обозначаются обратной косой чертой. Например, в интерпретируемой строке \n
представляет новую строчку в строке. Однако внутри необработанных литералов строк обратная косая черта не имеет особого значения:
a := `Say "hello" to Go!\n`
fmt.Println(a)
Поскольку в литерале строки обратная косая черта не имеет особого значения, программа будет выводить значение \n
вместо создания новой строки:
OutputSay "hello" to Go!\n
Необработанные литералы строк также могут использоваться для создания строк, включающих несколько строчек:
a := `This string is on
multiple lines
within a single back
quote on either side.`
fmt.Println(a)
OutputThis string is on
multiple lines
within a single back
quote on either side.
В предыдущих блоках кода новые строчки буквально переносились из входных данных в результаты.
Интерпретируемые строковые литералы — это последовательность символов внутри двойных кавычек, например, "bar"
. Внутри кавычек может находиться любой символ, кроме символа новой строчки и незакрытых двойных кавычек. Для отображения двойных кавычек в интерпретируемой строке вы можете использовать обратную косую черту в качестве символа перехода:
a := "Say \"hello\" to Go!"
fmt.Println(a)
OutputSay "hello" to Go!
Практически всегда вы будете использовать интерпретируемые строковые литералы, поскольку они позволяют выполнять экранирование символов внутри строк. Дополнительную информацию о строках можно найти в статье, посвященной основам работы со строками в Go.
UTF-8 — это схема кодировки, используемая для кодировки символов переменной ширины в 1-4 байтах. Go поддерживает символы UTF-8 без специальных настроек, библиотек или пакетов. Латинские символы, такие как буква A
, могут быть представлены значением ASCII, например, числом 65. Однако при использовании специальных символов, таких как международный символ 世
, требуется UTF-8. Go использует тип псевдонима rune
для данных UTF-8.
a := "Hello, 世界"
Вы можете использовать ключевое слово range
в цикле for
для индексации любых строк в Go, в том числе строк UTF-8. Мы более подробно расскажем о циклах for
и о ключевом слове range
позднее, а сейчас важно помнить, что мы можем использовать их для подсчета количества байт в строке:
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
.
В заключение мы распечатали полную переменную a
с помощью встроенной функции len
.
Мы уже упоминали, что rune является псевдонимом int32
и может состоять из 1-4 байт. Для определения символа 世
требуется три байта, и индекс перемещается соответствующим образом при изменении диапазона в строке UTF-8. По этой причине печать i
не выполняется последовательно.
Output0: H
1: e
2: l
3: l
4: o
5: ,
6:
7: 世
10: 界
length of 'Hello, 世界': 13
Как видите, длина превышает количество проходов диапазона строки.
Вы не всегда будете использовать строки UTF-8, но теперь вы понимаете, почему они относятся к типу rune, а не int32
.
Вы узнали о разных типах данных примитивов, и теперь мы перейдем к назначению этих типов для переменных в Go.
В Go мы можем определять переменные с помощью ключевого слова var
, за которым идут имя переменной и желаемый тип данных.
В следующем примере мы декларируем переменную с именем pi
типа float64
.
В первую очередь декларируется ключевое слово var
:
var pi float64
Далее идет имя переменной pi
:
var pi float64
Последним идет тип данных 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"
, а вторая являетя целым числом, поскольку ей присвоено значение 123
.
Теперь рассмотрим более сложные типы данных — массивы.
Массив представляет собой упорядоченную последовательность элементов. Вместимость массива определяется во время создания. После определения размера массива его нельзя изменить. Поскольку массив имеет статичный размер, память для него выделяется только один раз. Это делает массивы менее гибкими, но повышает производительность вашей программы. Поэтому массивы обычно используются при оптимизации программ. Срезы, о которых мы поговорим позднее, являются более гибкими и соответствуют общепринятой концепции массивов, применяемой в других языках.
Массивы определяются посредством декларирования размера массива и типа данных с определением значений внутри фигурных скобок { }
.
Массив строк должен выглядеть следующим образом:
[3]string{"blue coral", "staghorn coral", "pillar coral"}
Мы можем сохранить массив в переменной и распечатать его:
coral := [3]string{"blue coral", "staghorn coral", "pillar coral"}
fmt.Println(coral)
Output[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)
Выводимые результаты будут выглядеть точно так, как созданный нами список:
Output[shark cuttlefish squid mantis shrimp]
Мы можем использовать ключевое слово append
для добавления элементов в срез. Следующая команда добавляет значение строки seahorse
в срез:
seaCreatures = append(seaCreatures, "seahorse")
Вы можете проверить его добавление посредством вывода значений:
fmt.Println(seaCreatures)
Output[shark cuttlefish squid mantis shrimp seahorse]
Как видите, если вам требуется управление неизвестным количеством элементов, срез будет более гибким, чем массив.
Карта — это встроенный в Go тип хэша или словаря. Карты используют пары ключей и значений для хранения данных. Это полезно в программировании для быстрого просмотра значений по индексу или (в данном случае) по ключу. Например, вам может потребоваться карта пользователей с индексацией по идентификатору пользователя. Ключ может быть идентификатором пользователя, а объект пользователя будет значением. Карта создается с помощью ключевого слова map
с типом данных ключа в квадратных скобках [ ]
, за которым идут пары значение и ключ в фигурных скобках.
map[key]value{}
Карты обычно используются для хранения связанных данных, в том числе информации из идентификаторов, и выглядят следующим образом:
map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
Обратите внимание, что помимо фигурных скобок карта содержит двоеточия. Слова слева от двоеточий являются ключами. Ключи могут относится к любому *comparable *типу в Go. Сравнимые типы — это типы примитивов, в том числе строки
, целы
е числа и т. д. Тип примитива определяется языком, а не составляется посредством сочетания других типов. Хотя допускается использование определяемых пользователем типов, во избежание ошибок программирования их лучше оставлять простыми. В словаре выше содержатся ключи: name
, animal
, color
и location
.
Слова справа от двоеточий являются значениями. Значения могут состоять из любого типа данных. Значения в словаре выше: Sammy
, shark
, blue
и ocean
.
Давайте сохраним карту внутри переменной и выведем ее:
sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
fmt.Println(sammy)
Outputmap[animal:shark color:blue location:ocean name:Sammy]
Если мы хотим изолировать цвет Sammy, мы можем использовать вызов sammy["color"]
. Распечатаем результат:
fmt.Println(sammy["color"])
Outputblue
Поскольку карты предоставляют пары ключ-значение для хранения данных, они могут стать важным элементом вашей программы Go.
Теперь вы должны лучше понимать основные типы данных, доступные для использования в Go. Каждый из этих типов данных важен при разработке проектов на языке Go.
Когда вы разберетесь с доступными типами данных в Go, вы также можете изучить конвертацию типов данных, чтобы иметь возможность изменять типы данных в зависимости от ситуации.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.