Целочисленные типы в Go (int, uint)
В Go существует множество типов для представления целых чисел. Они различаются по трем основным параметрам:
- Размер в памяти (от 1 до 8 байт).
- Знаковость (могут ли они быть отрицательными).
- Архитектурная зависимость (зависит ли их размер от платформы).
Давайте разберем их на группы.
1. Знаковые целые числа (int) — могут быть отрицательными
Эти типы могут хранить положительные числа, отрицательные числа и ноль. Их название не содержит префикс u.
Аналогия: Температура на улице. Она может быть +25, -15 или 0 градусов. Для таких значений нужен знаковый тип.
int8: от -128 до 127 (1 байт)int16: от -32 768 до 32 767 (2 байта)int32: от -2 147 483 648 до 2 147 483 647 (4 байта)int64: от –9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 (8 байт)
// Температура может быть отрицательной, поэтому int8 — хороший выбор
var temperature int8 = -15
2. Беззнаковые целые числа (uint) — только положительные
Эти типы могут хранить только положительные числа и ноль. Их название начинается с префикса u (от англ. unsigned — беззнаковый). Поскольку им не нужно хранить знак, их максимальное положительное значение в два раза больше, чем у соответствующего знакового типа.
Аналогия: Количество яблок в корзине. Вы не можете иметь "-5 яблок", количество всегда 0 или больше.
uint8: от 0 до 255 (1 байт)uint16: от 0 до 65 535 (2 байта)uint32: от 0 до 4 294 967 295 (4 байта)uint64: от 0 до 18 446 744 073 709 551 615 (8 байт)
// Количество студентов в группе не может быть отрицательным
var studentCount uint16 = 300
// var invalidCount uint8 = -1 // Ошибка компиляции: constant -1 overflows uint8
3. Синонимы: byte и rune
В Go есть два специальных синонима (альтернативных имени) для удобства и семантической ясности.
byte: Это просто синоним дляuint8. Используется, когда вы работаете с сырыми двоичными данными, файлами или сетевыми протоколами.rune: Это синоним дляint32. Используется для представления одного Unicode-символа. Go работает с текстом в кодировке UTF-8, и один символ (например, '🚀' или 'Я') может занимать до 4 байт, поэтому для него нуженint32.
var firstLetter rune = 'А' // 'А' — это Unicode-символ (руна)
var rawData byte = 255 // Один байт данных
// firstLetter имеет тип int32, а rawData - uint8
fmt.Printf("Тип 'А': %T, Тип 255: %T\n", firstLetter, rawData)
// Вывод: Тип 'А': int32, Тип 255: uint8
4. int и uint: В чем разница и что выбрать?
Это самые важные и часто используемые типы. Их ключевая особенность — размер зависит от архитектуры компьютера:
- На 32-битной системе (например,
x86):int— этоint32, аuint— этоuint32. - На 64-битной системе (например,
amd64,arm64):int— этоint64, аuint— этоuint64.
Сегодня почти все современные системы 64-битные. Эти типы выбраны так, чтобы быть наиболее эффективными для процессора.
Главное правило: По умолчанию всегда используйте int
Большинство разработчиков Go придерживаются этого правила по нескольким причинам:
- Идиоматичность и простота: Это стандартный выбор в Go. Все стандартные функции, такие как
len(), возвращаютint. Использованиеintвезде делает код однородным. - Безопасность: Смешивание
intиuintв арифметических операциях может привести к ошибкам. Go не позволяет их смешивать без явного приведения типов, что делает код громоздким и может скрыть ошибки.- Например,
uint(0) - 1не станет-1, а превратится в максимальное значениеuint(4294967295 или больше), что является частой причиной багов.
- Например,
- Производительность:
intимеет наиболее естественный и быстрый размер для целевой платформы.
// Идиоматичный и безопасный код
var items []string = []string{"a", "b", "c"}
// len(items) возвращает int, и наш индекс i тоже int. Все совпадает.
for i := 0; i < len(items); i++ {
fmt.Println(items[i])
}
Когда же стоит использовать uint или типы с фиксированным размером?
Несмотря на общее правило, для этих типов есть свои четкие сценарии:
| Тип | Когда использовать | Примеры |
|---|---|---|
uint | 1. Когда значение по своей природе не может быть отрицательным (например, счетчики). 2. При работе с битовыми масками и побитовыми операциями, где знаковый бит может помешать. | var userID uint32const ReadMask uint8 = 1 << 0 |
int32, uint64 и т.д. | 1. Для экономии памяти, когда у вас огромные массивы данных. 2. Когда размер данных критичен для совместимости: при работе с бинарными файлами, сетевыми протоколами или базами данных, где нужно гарантировать одинаковый размер на всех платформах. | pixels [1_000_000]uint8 (экономия памяти)func writePacket(data int32) (сетевой протокол) |
Итог
| Критерий | int | uint |
|---|---|---|
| Основное правило | Используйте по умолчанию для всех целых чисел. | Используйте, только если есть веская причина. |
| Предназначение | Индексы, счетчики, общие вычисления. | Значения, которые логически не могут быть отрицательными. |
| Диапазон | Включает отрицательные числа. | Только 0 и положительные числа. |
| Потенциальная проблема | На 64-битной системе занимает 8 байт, что может быть избыточно. | Вычитание uint(0) - 1 приводит к "переполнению", а не к отрицательному числу. |