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

Лекция 2. Переменные, константы и базовые типы Go

Цели лекции

После лекции студент должен:

  • объявлять переменные через var и :=;
  • выбирать между переменной и константой;
  • понимать zero value;
  • различать целые, вещественные и логические типы;
  • понимать различие между string, byte и rune;
  • выполнять явные преобразования типов;
  • создавать именованные типы;
  • использовать iota для связанных констант;
  • объяснять, чем типизация Go отличается от Python.

1. Переменная и её тип

Переменная — именованная область памяти, содержащая значение определённого типа. В Go тип переменной не меняется после объявления.

package main
import "fmt"
func main() {
var age int = 20
fmt.Println(age)
// age = "двадцать" // ошибка: string нельзя использовать как int
}

Python разрешает тому же имени ссылаться на другое значение:

age = 20
age = "двадцать"

В Go такое изменение типа потребовало бы отдельной переменной.

2. Объявление через var

Полная форма:

var studentName string = "Анна"
var course int = 2

Если начальное значение присутствует, тип часто можно не писать:

var city = "Казань" // string
var rating = 4.75 // float64

Можно объявить несколько переменных:

var width, height int = 10, 20

Или использовать группировку на уровне пакета:

var (
applicationName = "Task Manager"
debugMode = false
maxConnections = 100
)

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

3. Короткое объявление :=

Внутри функции чаще используется короткая форма:

name := "Иван"
age := 19
average := 4.6

Компилятор выводит тип из правой части.

:= можно использовать только внутри функций:

package main
// version := "1.0" // ошибка на уровне пакета
var version = "1.0"
func main() {
message := "Программа запущена"
println(message)
}

Повторное присваивание выполняется через =:

score := 10
score = 15

Запись score := 15 в том же блоке без новой переменной вызовет ошибку.

Смешанное короткое объявление

Если хотя бы одна переменная слева новая, := допустим:

name := "Алиса"
name, age := "Борис", 21

Здесь name получает новое значение, а age объявляется впервые. Такой синтаксис часто встречается при получении значения и ошибки:

data, err := loadData()

4. Zero value

В Go каждая объявленная переменная сразу имеет корректное значение. Если инициализатор не указан, используется нулевое значение типа.

var count int
var price float64
var enabled bool
var title string
fmt.Println(count) // 0
fmt.Println(price) // 0
fmt.Println(enabled) // false
fmt.Println(title) // пустая строка

Основные zero values:

ТипZero value
целые числа0
вещественные числа0.0
boolfalse
string""
pointer, slice, map, channel, function, interfacenil

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

5. Константы

Константа объявляется через const:

const Pi = 3.1415926535
const CourseName = "Язык программирования Go"
const MaxAttempts = 3

Константу нельзя изменить:

const TaxRate = 0.2
// TaxRate = 0.25 // ошибка компиляции

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

const SecondsPerMinute = 60
const MinutesPerHour = 60
const SecondsPerHour = SecondsPerMinute * MinutesPerHour

Результат функции во время выполнения константой быть не может:

// const StartedAt = time.Now() // ошибка
var startedAt = time.Now()

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

6. Целые типы

Знаковые типы:

  • int8, int16, int32, int64;
  • int — размер зависит от целевой архитектуры, обычно 32 или 64 бита.

Беззнаковые типы:

  • uint8, uint16, uint32, uint64;
  • uint;
  • uintptr для низкоуровневых задач.

Для большинства счётчиков, индексов и размеров внутри программы используют int:

students := 25
completed := 18
remaining := students - completed

Фиксированные типы важны при работе с бинарными форматами, протоколами и внешними API.

Переполнение

Диапазон int8 — от -128 до 127:

var value int8 = 127
// value = value + 1 // результат не помещается в int8

Компилятор обнаруживает переполнение константы, но арифметика переменных фиксированной ширины может переполняться во время выполнения. Выбор типа остаётся ответственностью программиста.

7. Вещественные числа

Go предоставляет:

  • float32;
  • float64.

По умолчанию литерал с дробной частью выводится как float64:

temperature := 21.5
var precise float64 = 3.1415926535
var compact float32 = 1.25

Вещественные числа представлены приближённо:

result := 0.1 + 0.2
fmt.Printf("%.20f\n", result)

Поэтому денежные суммы часто хранят в минимальных единицах:

priceKopecks := 19990 // 199 рублей 90 копеек

Для серьёзных финансовых расчётов используют целочисленное представление или специализированный decimal-тип.

8. Логический тип

bool принимает только true или false:

isActive := true
hasAccess := false

Числа и строки нельзя использовать как логические значения:

count := 3
// if count { } // ошибка
if count > 0 {
fmt.Println("Есть элементы")
}

В отличие от Python, пустая строка, ноль и пустая коллекция не преобразуются в false автоматически.

9. Строки

Строковый тип называется string:

name := "Мария"
message := "Привет, " + name + "!"

Интерполяции вида Python f-string в Go нет. Для форматирования используется пакет fmt:

age := 20
text := fmt.Sprintf("Студенту %s %d лет", name, age)
fmt.Println(text)

Часто используемые спецификаторы:

СпецификаторНазначение
%sстрока
%dцелое число
%fвещественное число
%.2fдва знака после точки
%tлогическое значение
%vзначение в стандартном виде
%Tтип значения

Raw string записывается в обратных кавычках и может занимать несколько строк:

query := `SELECT id, name
FROM students
WHERE active = true`

Escape-последовательности внутри raw string не обрабатываются.

10. byte, rune и Unicode

byte — псевдоним uint8, а rune — псевдоним int32.

var b byte = 'A'
var r rune = 'Я'

Одинарные кавычки обозначают rune, а не строку:

letter := 'Ж' // rune
word := "Ж" // string

Строки Go хранят байты, обычно в UTF-8. Поэтому len возвращает число байтов:

fmt.Println(len("Go")) // 2
fmt.Println(len("Привет")) // 12, а не 6

Количество Unicode-символов можно получить после преобразования в []rune:

fmt.Println(len([]rune("Привет"))) // 6

Подробная работа со строками рассматривается в лекции 6.

11. Вывод типов

Компилятор определяет тип по начальному значению:

count := 42 // int
ratio := 0.75 // float64
language := "Go" // string
available := true // bool
symbol := 'G' // rune

Тип можно проверить во время эксперимента:

fmt.Printf("%T\n", count)
fmt.Printf("%T\n", symbol)

В рабочем коде обычно не печатают типы — они известны компилятору и редактору.

12. Явные преобразования

Go не выполняет большинство числовых преобразований автоматически:

var count int = 10
var ratio float64 = 2.5
// result := count * ratio // ошибка: разные типы
result := float64(count) * ratio
fmt.Println(result)

Преобразование может потерять данные:

price := 19.95
whole := int(price)
fmt.Println(whole) // 19

Преобразование числа в строку не выполняется через string(number) в обычном смысле. Для десятичной записи используют strconv:

age := 20
text := strconv.Itoa(age)
value, err := strconv.Atoi("42")

strconv.Atoi возвращает число и ошибку. Ошибки подробно изучаются позже, но уже сейчас нельзя игнорировать возможность некорректного ввода:

value, err := strconv.Atoi("42")
if err != nil {
fmt.Println("Некорректное число:", err)
return
}
fmt.Println(value)

13. Несколько присваиваний

Go позволяет присвоить несколько значений одновременно:

x, y := 10, 20
x, y = y, x
fmt.Println(x, y) // 20 10

Функции также могут возвращать несколько значений. Это используется для пары «результат и ошибка» или «значение и признак наличия».

14. Пустой идентификатор _

Иногда одно из полученных значений не нужно:

value, _ := strconv.Atoi("42")
fmt.Println(value)

_ называется blank identifier. Значение присваивается ему и сразу отбрасывается. Игнорировать ошибки таким способом без причины нельзя: это скрывает проблемы.

Допустимый пример — сознательно ненужный индекс:

for _, name := range []string{"Анна", "Борис"} {
fmt.Println(name)
}

15. Именованные типы

Можно создать новый тип на основе существующего:

type UserID int64
type Celsius float64
var id UserID = 1001
var temperature Celsius = 21.5

UserID и int64 становятся разными типами:

var raw int64 = 1001
// id = raw // ошибка
id = UserID(raw)

Это помогает не смешивать значения с одинаковым физическим представлением, но разным смыслом: идентификатор пользователя, идентификатор заказа и обычный счётчик.

Псевдоним типа объявляется через =:

type Identifier = int64

Псевдоним не создаёт новый тип. В прикладном коде чаще нужны именованные типы, которые позволяют компилятору различать предметные значения.

16. iota

iota создаёт последовательность связанных целочисленных констант:

type Status int
const (
StatusNew Status = iota
StatusInProgress
StatusDone
)

Значения будут равны 0, 1 и 2. Начало с нуля полезно: zero value типа Status соответствует StatusNew.

Если ноль должен означать неизвестное состояние:

const (
StatusUnknown Status = iota
StatusNew
StatusInProgress
StatusDone
)

iota не создаёт полноценное перечисление с закрытым множеством вариантов. Переменная типа Status всё ещё может получить другое целое значение через явное преобразование. Корректность проверяет программа.

17. Область видимости и затенение

Переменная видна внутри блока, где объявлена:

func main() {
message := "внешнее значение"
if true {
message := "внутреннее значение"
fmt.Println(message)
}
fmt.Println(message)
}

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

result, err := load()
if err != nil {
return
}
if needReload {
result, err := loadAgain() // новые переменные внутри if
_ = result
_ = err
}

Нужно внимательно различать := и =.

18. Типичные ошибки

Объявленная, но неиспользуемая переменная

name := "Анна" // ошибка, если name дальше не используется

Компилятор не разрешает оставлять неиспользуемые локальные переменные.

Смешивание числовых типов

var a int = 10
var b int64 = 20
// fmt.Println(a + b) // ошибка
fmt.Println(int64(a) + b)

Ожидание автоматического преобразования в bool

text := "Go"
// if text { } // ошибка
if text != "" {
fmt.Println(text)
}

Потеря дробной части

average := int(4.9) // 4

Необоснованное использование глобальных переменных

Глобальное изменяемое состояние усложняет тестирование и конкурентный доступ. Переменные следует объявлять в минимально необходимой области.

19. Практический пример

package main
import "fmt"
type StudentID int64
const (
TotalCourses = 4
ExcellentGPA = 4.5
)
func main() {
var id StudentID = 1001
name := "Анна Петрова"
birthYear := 2006
currentYear := 2026
course := 2
average := 4.75
active := true
age := currentYear - birthYear
remainingCourses := TotalCourses - course
fmt.Printf("ID: %d\n", id)
fmt.Printf("Имя: %s\n", name)
fmt.Printf("Возраст: %d\n", age)
fmt.Printf("Курс: %d\n", course)
fmt.Printf("Осталось курсов: %d\n", remainingCourses)
fmt.Printf("Средний балл: %.2f\n", average)
fmt.Printf("Активен: %t\n", active)
fmt.Printf("Отличник: %t\n", average >= ExcellentGPA)
}

Пример показывает вывод типов, именованный тип, типизированные и нетипизированные константы, форматированный вывод и явные предметные имена.

20. Вопросы для самопроверки

  1. Чем var отличается от :=?
  2. Где разрешено короткое объявление?
  3. Что такое zero value?
  4. Какие значения являются zero value для int, bool и string?
  5. Почему int нельзя автоматически сложить с int64?
  6. Чем byte отличается от rune?
  7. Почему len("Привет") не равен количеству русских букв?
  8. Как преобразовать строку "42" в число?
  9. Чем именованный тип отличается от псевдонима?
  10. Для чего используется iota?
  11. Что делает идентификатор _?
  12. Почему затенение переменных может быть опасно?

Итоги

Go сочетает строгую статическую типизацию с компактным синтаксисом. Переменные можно объявлять явно через var или кратко через :=, а неизменяемые значения — через const. Каждый тип имеет zero value, числовые преобразования выполняются явно, а именованные типы помогают выражать смысл предметной области.