Прежде чем писать настоящие программы, нужно понять, как Go работает с данными. Данные в Go — это значения определённых типов: числа, строки, логические значения и многое другое. Чтобы с ними работать, мы используем переменные — именованные области памяти, в которых хранятся значения.


🔤 Типы данных в Go

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

🔢 Числовые типы

Целые числа:

  • int, int8, int16, int32, int64
  • uint, uint8 (или byte), uint16, uint32, uint64

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

  • float32, float64

Комплексные числа:

  • complex64, complex128
var a int = 10
var b uint8 = 255
var c float64 = 3.14
var d complex64 = 2 + 3i

💡 В чём отличия int от uint?

int может хранить как положительные, так и отрицательные числа (например, -10, 0, 42), а uint — только положительные (unsigned integer - беззнаковое число), но с удвоенным верхним пределом. Используй uint, если заранее знаешь, что переменная не будет принимать отрицательные >значения, например, счётчик или индекс.

🔍 Что означают числа в типах int8, uint32 и т. д.?

Число в конце типа указывает на размер в битах, который выделяется для хранения значения:

int8 — 8 бит → диапазон от -128 до 127

uint8 — 8 бит → от 0 до 255

int16 — 16 бит → от -32 768 до 32 767

int32 — 32 бита → от -2,147,483,648 до 2,147,483,647

uint32 — от 0 до 4,294,967,295

int64, uint64 — используются для очень больших чисел

Чем больше битов — тем больше диапазон значений, но и выше потребление памяти. Обычно достаточно использовать int, если нет особых требований.

🔡 Строки

var message string = "Привет, Go!"

Строки — это неизменяемые последовательности байт. В Go они представлены в кодировке UTF-8.

✅ Булевы значения

var isReady bool = true

bool может принимать только два значения: true или false.

🕳 Специальный тип rune

rune — это псевдоним для int32, используется для представления символов Unicode:

var symbol rune = 'Ж'

📝 Объявление переменных

Переменные в Go можно объявлять несколькими способами.

Способ 1: Использование var

var x int = 10
var name string = "Go"
var active bool // значение по умолчанию: false

Можно опустить указание типа, если значение явно задано:

var city = "Москва" // Go сам выведет тип string

Способ 2: Краткая форма :=

count := 5
language := "Go"
success := true

💡 := — это синтаксический сахар, удобный для быстрой инициализации переменных. Он:

  • Используется только внутри функций.
  • Требует обязательной инициализации (значения не может не быть).
  • Go автоматически определяет тип на основе значения справа.
pi := 3.1415        // float64
name := "Гоша"       // string
ok := false         // bool

💡 Под капотом x := 10 эквивалентно var x int = 10.


🛠 Константы

Константы объявляются с помощью ключевого слова const и не могут изменяться после определения:

const pi float64 = 3.14159
const appName = "MyApp"

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

const maxRetries = 3 // Go выведет тип int

🧪 Пример

package main

import "fmt"

const (
    gravity  = 9.81 // ускорение свободного падения, м/с²
    distance = 100   // расстояние, м
)

func main() {
    time := float64(distance) / gravity
    fmt.Printf("Время падения: %.2f секунд\n", time)
}

📦 Значения по умолчанию

Если переменная объявлена через var, но не инициализирована, она получает значение по умолчанию:

ТипЗначение по умолчанию
int0
float640.0
string"" (пустая строка)
boolfalse
pointernil

🧪 Пример

package main

import "fmt"

func main() {
    var age int = 28
    name := "Анна"
    score := 95.5
    var passed bool = true

    fmt.Println("Имя:", name)
    fmt.Println("Возраст:", age)
    fmt.Println("Оценка:", score)
    fmt.Println("Сдала экзамен?", passed)
}

Результат:

Имя: Анна
Возраст: 28
Оценка: 95.5
Сдала экзамен? true

🔄 Преобразование типов

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

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

fmt.Println(f) // 42.0

💡 Будьте осторожны: преобразование может привести к потере данных (например, при приведении float64 к int дробная часть отбрасывается).


🚫 Типичные ошибки

Ошибка: Использование := вне функции.

package main

count := 10 // Ошибка: := можно использовать только внутри функций

Исправление:

var count = 10 // Используйте var для глобальных переменных

Ошибка: Попытка сложения переменных разных типов.

var i int = 42
var f float64 = 3.14
result := i + f // Ошибка: типы несовместимы

Исправление:

result := float64(i) + f // Приведение int к float64
fmt.Println(result) // 45.14

🚀 Практические примеры работы с типами

Система управления температурой

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

package main

import (
    "fmt"
    "math"
)

// Константы для температурных пределов
const (
    MinSafeTemp     = -10.0  // Минимальная безопасная температура
    MaxSafeTemp     = 35.0   // Максимальная безопасная температура
    CriticalTempDiff = 5.0   // Критическая разница температур
)

func main() {
    // Текущие показания датчиков (float64 для точности)
    currentTemp := 24.7
    previousTemp := 22.3
    
    // Номер датчика и счётчик измерений (int)
    sensorID := 1001
    measurementCount := 0
    
    // Статус системы (bool)
    isSystemActive := true
    alertSent := false
    
    fmt.Printf("🌡️  Система мониторинга температуры\n")
    fmt.Printf("Датчик #%d активен: %t\n", sensorID, isSystemActive)
    
    if isSystemActive {
        measurementCount++
        
        // Анализ температуры
        tempDiff := math.Abs(currentTemp - previousTemp)
        
        fmt.Printf("\n📊 Показания датчика:\n")
        fmt.Printf("   Текущая температура: %.1f°C\n", currentTemp)
        fmt.Printf("   Предыдущая: %.1f°C\n", previousTemp)
        fmt.Printf("   Изменение: %.1f°C\n", tempDiff)
        
        // Проверка безопасных пределов
        if currentTemp < MinSafeTemp || currentTemp > MaxSafeTemp {
            fmt.Printf("⚠️  ВНИМАНИЕ: Температура за пределами нормы!\n")
            alertSent = true
        }
        
        // Проверка резких изменений
        if tempDiff > CriticalTempDiff {
            fmt.Printf("🚨 КРИТИЧНО: Резкое изменение температуры!\n")
            alertSent = true
        }
        
        if !alertSent {
            fmt.Printf("✅ Температура в норме\n")
        }
        
        fmt.Printf("\n📈 Статистика: измерение #%d\n", measurementCount)
    }
}

Конвертер валют с безопасным преобразованием типов

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

package main

import (
    "fmt"
    "math"
)

func convertCurrency(amount float64, rate float64, precision int) {
    // Конвертируем валюту
    converted := amount * rate
    
    // Округляем до нужной точности (копейки/центы)
    multiplier := math.Pow(10, float64(precision))
    rounded := math.Round(converted * multiplier) / multiplier
    
    fmt.Printf("💰 %.2f → %.2f (курс: %.4f)\n", amount, rounded, rate)
    
    // Безопасное преобразование в копейки для точных расчётов
    kopecks := int64(rounded * 100) // Преобразуем в копейки
    rubles := kopecks / 100         // Целые рубли
    remainder := kopecks % 100      // Оставшиеся копейки
    
    fmt.Printf("💸 В копейках: %d (рублей: %d, копеек: %d)\n", 
        kopecks, rubles, remainder)
}

func main() {
    fmt.Println("🏦 Конвертер валют")
    
    // Исходная сумма в долларах
    dollars := 125.75
    
    // Курс доллара к рублю
    usdToRub := 93.2156
    
    convertCurrency(dollars, usdToRub, 2)
}

Обработка текста с Unicode символами (rune)

Демонстрирует важность типа rune при работе с многоязычным текстом:

package main

import (
    "fmt"
    "unicode/utf8"
)

func analyzeText(text string) {
    fmt.Printf("📝 Анализ текста: \"%s\"\n", text)
    
    // Количество байт в строке
    byteCount := len(text)
    
    // Количество символов Unicode (rune)
    runeCount := utf8.RuneCountInString(text)
    
    fmt.Printf("📊 Статистика:\n")
    fmt.Printf("   Байт: %d\n", byteCount)
    fmt.Printf("   Символов: %d\n", runeCount)
    
    if byteCount != runeCount {
        fmt.Printf("   💡 Текст содержит многобайтовые символы\n")
    }
    
    // Детальный анализ каждого символа
    fmt.Printf("\n🔍 Разбор по символам:\n")
    for i, r := range text {
        // r имеет тип rune (int32)
        byteSize := utf8.RuneLen(r)
        fmt.Printf("   Позиция %d: '%c' (код: %d, байт: %d)\n", 
            i, r, int(r), byteSize)
    }
}

func main() {
    // Тестируем с разными видами текста
    texts := []string{
        "Hello",           // Только ASCII
        "Привет",          // Кириллица
        "🌟 Go язык! 🚀",   // Эмодзи и смешанные символы
    }
    
    for _, text := range texts {
        analyzeText(text)
        fmt.Println()
    }
}

Выбор правильного целочисленного типа

Пример показывает, когда и какой целочисленный тип использовать:

package main

import "fmt"

func demonstrateIntTypes() {
    // uint8 для небольших положительных значений (0-255)
    var brightness uint8 = 128 // Яркость экрана 0-255
    
    // int16 для небольших знаковых значений (-32768 до 32767)
    var temperature int16 = -15 // Температура в градусах Цельсия
    
    // uint32 для больших положительных значений
    var userID uint32 = 1_000_000 // ID пользователя
    
    // int64 для временных меток и больших значений
    var timestamp int64 = 1640995200 // Unix timestamp
    
    // int для обычных случаев (размер зависит от архитектуры)
    var itemCount int = 42 // Количество элементов
    
    fmt.Printf("🔢 Демонстрация типов:\n")
    fmt.Printf("Яркость (uint8): %d (размер: %d байт)\n", 
        brightness, 1)
    fmt.Printf("Температура (int16): %d°C (размер: %d байта)\n", 
        temperature, 2)
    fmt.Printf("ID пользователя (uint32): %d (размер: %d байта)\n", 
        userID, 4)
    fmt.Printf("Timestamp (int64): %d (размер: %d байт)\n", 
        timestamp, 8)
    fmt.Printf("Количество (int): %d\n", itemCount)
    
    // Демонстрация переполнения
    fmt.Printf("\n⚠️  Демонстрация переполнения uint8:\n")
    var counter uint8 = 254
    fmt.Printf("Было: %d\n", counter)
    counter++
    fmt.Printf("Стало: %d\n", counter) // 255
    counter++
    fmt.Printf("После переполнения: %d\n", counter) // 0 (переполнение!)
}

func main() {
    demonstrateIntTypes()
}

🧠 Проверь свои знания

  1. Назови как минимум три вида числовых типов в Go и их отличия.
  2. В чём разница между var и :=?
  3. Что произойдёт, если объявить переменную через var, но не задать ей значение?
  4. Что такое rune и зачем он нужен?
  5. Напиши программу, которая объявляет переменные разных типов и выводит их значения.
  6. Попробуй преобразовать float64 в int и объясни, что происходит с дробной частью.
  7. Почему важно выбирать правильный размер целочисленного типа?
  8. Как правильно работать с денежными суммами, чтобы избежать ошибок округления?

📌 Главное из главы

  • Go — статически типизированный язык с автоматическим выводом типов
  • Числовые типы различаются размером и знаковостью (int8-int64, uint8-uint64)
  • Строки в Go неизменяемы и используют кодировку UTF-8
  • rune критически важен для правильной работы с Unicode символами
  • := удобен для локальных переменных, var для глобальных и сложных случаев
  • Преобразование типов всегда явное — Go не делает автоматических конверсий
  • Выбор правильного типа влияет на память, производительность и корректность

🛠 Практические упражнения

Упражнение 1: Калькулятор площади

Напишите программу, которая:

  1. Объявляет переменные для длины и ширины прямоугольника (float64)
  2. Вычисляет площадь и периметр
  3. Выводит результаты с точностью до 2 знаков после запятой
  4. Преобразует площадь в квадратные сантиметры (если размеры в метрах)

Упражнение 2: Анализатор возраста

Создайте программу, которая:

  1. Принимает возраст пользователя (uint8)
  2. Определяет категорию: ребёнок (<18), взрослый (18-65), пенсионер (>65)
  3. Вычисляет, сколько дней пользователь прожил
  4. Использует константы для границ возрастных категорий

Упражнение 3: Конвертер единиц

Реализуйте конвертер, который:

  1. Переводит температуру между Цельсием, Фаренгейтом и Кельвином
  2. Использует правильные типы для каждого значения
  3. Обрабатывает случаи, когда результат может быть отрицательным
  4. Выводит результат с соответствующими единицами измерения