Перейти к основному содержимому

Целочисленные типы в Go (int, uint)

В Go существует множество типов для представления целых чисел. Они различаются по трем основным параметрам:

  1. Размер в памяти (от 1 до 8 байт).
  2. Знаковость (могут ли они быть отрицательными).
  3. Архитектурная зависимость (зависит ли их размер от платформы).

Давайте разберем их на группы.

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 придерживаются этого правила по нескольким причинам:

  1. Идиоматичность и простота: Это стандартный выбор в Go. Все стандартные функции, такие как len(), возвращают int. Использование int везде делает код однородным.
  2. Безопасность: Смешивание int и uint в арифметических операциях может привести к ошибкам. Go не позволяет их смешивать без явного приведения типов, что делает код громоздким и может скрыть ошибки.
    • Например, uint(0) - 1 не станет -1, а превратится в максимальное значение uint (4294967295 или больше), что является частой причиной багов.
  3. Производительность: 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 или типы с фиксированным размером?

Несмотря на общее правило, для этих типов есть свои четкие сценарии:

ТипКогда использоватьПримеры
uint1. Когда значение по своей природе не может быть отрицательным (например, счетчики).
2. При работе с битовыми масками и побитовыми операциями, где знаковый бит может помешать.
var userID uint32
const ReadMask uint8 = 1 << 0
int32, uint64 и т.д.1. Для экономии памяти, когда у вас огромные массивы данных.
2. Когда размер данных критичен для совместимости: при работе с бинарными файлами, сетевыми протоколами или базами данных, где нужно гарантировать одинаковый размер на всех платформах.
pixels [1_000_000]uint8 (экономия памяти)
func writePacket(data int32) (сетевой протокол)

Итог

Критерийintuint
Основное правилоИспользуйте по умолчанию для всех целых чисел.Используйте, только если есть веская причина.
ПредназначениеИндексы, счетчики, общие вычисления.Значения, которые логически не могут быть отрицательными.
ДиапазонВключает отрицательные числа.Только 0 и положительные числа.
Потенциальная проблемаНа 64-битной системе занимает 8 байт, что может быть избыточно.Вычитание uint(0) - 1 приводит к "переполнению", а не к отрицательному числу.