1. Что такое переменная в Go: имя, тип и значение
Когда вы только начинаете программировать, легко думать о переменной как о “коробочке для числа”. Это неплохое начало, но в Go у этой “коробочки” есть паспорт: имя, тип и текущее значение. И самое важное: тип фиксируется при объявлении и дальше не “переобувается” на ходу. Это дисциплинирует код и делает его более предсказуемым (и да, иногда бесит — но чаще спасает от странных багов).
Представим переменную как бейджик на конференции: на бейджике написано имя (как обращаться), “роль” (тип) и текущее “состояние” (значение). Если на бейджике написано int, вы не сможете внезапно вписать туда строку "привет" — охрана компилятора не пропустит.
Объявление и присваивание: почему это разные операции
На практике новички чаще всего путаются не в синтаксисе, а в смысле: чем отличается “создать переменную” от “поменять значение в уже созданной переменной”. В Go эти действия разделены очень чётко. Это как разница между “завести нового персонажа в игре” и “поменять ему одежду”: имя персонажа появляется при создании, а дальше вы уже обновляете его параметры.
В Go есть три формы, которые мы будем использовать каждый день:
- var — объявление переменной (создаём имя в текущей области видимости).
- := — короткое объявление переменной (тоже создаём имя, обычно внутри функций).
- = — присваивание (имя уже должно существовать, мы меняем значение).
Соберём это в табличку — так мозгу проще “зацепиться”:
| Запись | Что делает | Когда использовать |
|---|---|---|
|
объявляет x типа int, даёт значение по умолчанию | когда хотим явно указать тип или объявить заранее |
|
объявляет x, тип выводится из 10 | когда есть стартовое значение и тип очевиден |
|
объявляет x (тип выводится), короткая форма | обычно внутри main и других функций |
|
присваивает 10 в уже существующий x | когда x уже объявлен |
И вот ключевая фраза дня: var и := создают переменную, а = — только меняет значение.
2. Объявление через var: два варианта
var выглядит чуть более “официально”, чем :=. Иногда это даже приятно: код кажется серьёзнее, как будто вы надели пиджак на переменную. Но главное — var хорошо показывает тип и полезен, когда вы хотите объявить переменную заранее, а заполнить её позже (например, после fmt.Scan или после проверки условия).
var name Type: объявили, значение по умолчанию
Когда вы пишете var count int, вы создаёте переменную count типа int. Если вы не дали ей стартовое значение, Go всё равно не оставит её “с мусором”, а поставит значение по умолчанию (для int это 0). Мы детально разберём значения по умолчанию отдельно, но уже сейчас важно: программа не будет читать “рандом из памяти”.
Минимальный пример, чтобы прочувствовать разницу между объявлением и присваиванием:
package main
import "fmt"
func main() {
var count int // объявили
count = 3 // присвоили
fmt.Println(count) // 3
}
Тут var count int создаёт переменную, а строка count = 3 лишь меняет её значение.
var name = expr: объявили и сразу инициализировали
Если вы пишете var x = 10, Go смотрит на правую часть и выводит тип сам. Это называется вывод типа (type inference). Например, если справа строка — тип будет string. В официальных материалах Go это часто объясняют на очень понятном примере: var str = "Hello, 世界" означает то же самое, что и var str string = "Hello, 世界".
Вот небольшой пример:
package main
import "fmt"
func main() {
var name = "Gopher"
var age = 10
fmt.Println(name, age) // Gopher 10
}
Здесь name становится string, а age — int, потому что Go сделал вывод типа по правой части.
4. := и = в повседневном коде
Короткое объявление := внутри функции
:= — это “быстрое объявление”. Оно почти всегда используется внутри main, потому что там мы пишем основной сценарий программы, и хочется меньше церемоний. На практике := делает код компактным и читабельным: вы сразу видите и имя, и начальное значение. Это как сказать “вот переменная, и вот её старт”.
Пример:
package main
import "fmt"
func main() {
x := 10 // объявили + инициализировали
x = x + 5 // обновили уже существующую
fmt.Println(x) // 15
}
Обратите внимание на ритм: первая строка создаёт переменную, дальше мы используем = для обновления. Это очень типичный стиль Go-кода.
Есть важное правило, которое пока запомним как мантру: если переменная уже существует и вы хотите её изменить — пишите =. Если вы пишете :=, вы, скорее всего, создаёте новую переменную (а это иногда не то, что вы хотели).
Присваивание =: меняем значение существующей переменной
Оператор = не создаёт переменную. Он требует, чтобы имя уже было объявлено в текущей области видимости (или во внешней области, которая видна). Поэтому x = 10 без предыдущего объявления x приведёт к ошибке компиляции — и это нормально: компилятор честно говорит “я не знаю, что такое x”.
Пример “правильной” последовательности:
package main
import "fmt"
func main() {
var total int
total = 100
total = total - 30
fmt.Println(total) // 70
}
Здесь total создаётся через var, а затем дважды обновляется через =.
Кстати, из-за строгости типов Go не любит “смешивать” разные числовые типы автоматически: если переменные разных типов, компилятор попросит вас быть конкретнее. Это часть философии Go: лучше запретить неоднозначность заранее, чем ловить странные эффекты потом.
5. Область видимости блоков {}: где переменная “видна”
Теперь самое вкусное (и самое частое “почему оно не работает?!” у новичков). В Go фигурные скобки {} задают область видимости: участок кода, внутри которого имена переменных существуют и доступны. Можно думать о блоке как о комнате: вы можете принести в неё вещи (переменные), пользоваться ими внутри, но когда вы вышли из комнаты — вещи, созданные внутри, остаются там.
Схематично это выглядит так:
main {
x // живёт во всём main
if {
y // живёт только внутри if
}
// тут y уже не существует
}
Это не “придирка Go”, а защита от хаоса: если переменная нужна только внутри маленького блока, лучше не давать ей жить дольше — так меньше шансов случайно использовать её не там.
Вложенные блоки if
Самый простой пример: переменная объявлена внутри if — снаружи она недоступна.
package main
import "fmt"
func main() {
x := 10
if x > 0 {
y := x * 2
fmt.Println("inside:", y) // inside: 20
}
fmt.Println("outside:", x) // outside: 10
}
Здесь y существует только внутри блока if. Это удобно: y — “временный расчёт”, он не нужен дальше.
Если попытаться написать fmt.Println(y) после if, компилятор скажет, что y не определён. И это хороший момент: компилятор буквально спасает вас от использования переменной, которая уже “умерла”.
Переменные цикла for
С циклом for похожая история: всё, что объявлено в заголовке цикла, обычно живёт внутри цикла.
package main
import "fmt"
func main() {
for i := 1; i <= 3; i++ {
fmt.Println(i) // 1, потом 2, потом 3
}
}
Переменная i создана в рамках цикла. После завершения цикла она уже не нужна, и Go не даёт ей “висеть” в main. Это делает код чище: меньше “лишних” имён, которые можно случайно использовать потом.
Когда объявлять переменную снаружи, а когда внутри
Здесь полезна очень практичная логика. Если значение нужно после блока — переменную объявляют снаружи, а внутри блока делают присваивание. Если значение нужно только для внутреннего вычисления — объявляют прямо внутри блока, чтобы оно не мешало остальной программе.
Посмотрим разницу на одном примере: допустим, вы хотите вычислить скидку, но использовать её и после if.
package main
import "fmt"
func main() {
total := 120
discount := 0 // объявили заранее
if total >= 100 {
discount = 10 // присваивание, НЕ объявление
}
fmt.Println("discount:", discount) // discount: 10
}
Здесь discount объявлена до if, потому что нам важно напечатать её после условия. Внутри if мы не создаём новую переменную, а обновляем существующую.
6. Мини‑приложение “BudgetBuddy”: переменные и области видимости
Давайте напишем маленькое консольное приложение. Оно будет простым, но полезным: считаем бюджет и траты. Мы не используем функции, коллекции и сложные структуры — только то, что уже умеем: ввод, переменные, if, for, вывод. Это будет как мини-касса или трекер расходов уровня “мама, я программист”.
Версия 1: читаем бюджет и одну покупку
Сначала сделаем совсем простую версию: пользователь вводит бюджет и одну покупку, а программа печатает, сколько осталось.
package main
import "fmt"
func main() {
var budget int
var purchase int
fmt.Scan(&budget, &purchase)
rest := budget - purchase
fmt.Println(rest) // например: 700
}
Обратите внимание на стиль. Вводим значения в переменные, потом считаем rest уже через :=, потому что это промежуточный результат и он очевиден по типу.
Версия 2: цикл по N покупкам и зачем снаружи нужен spent
Теперь сделаем что-нибудь полезнее: пусть пользователь вводит число покупок n, а затем вводит n сумм. Нам нужен накопитель spent, который живёт вне цикла, иначе после цикла мы не сможем его использовать.
package main
import "fmt"
func main() {
var budget, n int
fmt.Scan(&budget, &n)
spent := 0
for i := 0; i < n; i++ {
var x int
fmt.Scan(&x)
spent = spent + x
}
fmt.Println(budget - spent)
}
Здесь spent объявлен снаружи цикла, потому что он нужен после цикла. А вот x — внутренняя переменная одной итерации: прочитали одну покупку, добавили в сумму, забыли. Это хороший пример “узкой” области видимости: меньше переменных живёт долго — меньше путаницы.
Версия 3: проверка бюджета через if
Добавим условие: если потратили больше бюджета — печатаем предупреждение. Заметьте, что spent живёт во всём main, а значит доступен и после for, и внутри if.
package main
import "fmt"
func main() {
var budget, n int
fmt.Scan(&budget, &n)
spent := 0
for i := 0; i < n; i++ {
var x int
fmt.Scan(&x)
spent = spent + x
}
if spent > budget {
fmt.Println("over budget")
}
fmt.Println("spent:", spent)
}
Тут у нас два важных эффекта. Во‑первых, spent объявлен ровно там, где он “начинается” как смысл — перед циклом. Во‑вторых, переменная x не расползается по функции: она нужна на 2–3 строки, вот и живёт 2–3 строки.
7. Типичные ошибки при объявлении и использовании переменных
Ошибка №1: использовать = для имени, которого ещё не существует.
Это одна из самых частых ситуаций: человек пишет sum = 0, потому что “ну я же присваиваю ноль”, а sum до этого нигде не объявлял. В Go так нельзя: сначала объявляем (var sum int или sum := 0), а уже потом меняем (sum = sum + x). Хорошая привычка — приучить себя видеть первую встречу с переменной: она должна быть через var или :=.
Ошибка №2: ожидать, что переменная из if или for будет доступна после блока.
Психологически это очень понятно: вы только что вычислили y, она такая красивая, и хочется использовать её дальше. Но если y объявлена внутри {}, то после закрывающей скобки её как будто “удалили”. Решение здесь простое по смыслу: если значение нужно после блока, объявите переменную до блока, а внутри сделайте присваивание.
Ошибка №3: объявлять переменные слишком рано и слишком “широко”.
Новичок иногда заводит в начале main десять переменных “на всякий случай”. Потом половина не используется, половина используется через 40 строк, и вы уже не помните, кто есть кто. В Go намного приятнее объявлять переменные ближе к месту использования: это уменьшает область видимости и делает код легче для чтения (и для вашего будущего “я”, который через неделю будет смотреть на код как на чужой).
Ошибка №4: объявить переменную и не использовать её.
Go относится к этому строго: если в функции есть переменная, которая нигде не используется, компилятор обычно ругается. Это поначалу раздражает (“да я просто хотел на будущее!”), но быстро становится вашим союзником: он не даёт держать в коде мусор и “забытые задумки”, которые путают читателя. Если переменная реально не нужна — удаляем. Если нужна позже — пишем код так, чтобы она действительно использовалась.
Ошибка №5: путать “объявление” и “обновление” при чтении кода.
Иногда программа “не работает” просто потому, что вы думаете, что строка создаёт новую переменную, а она всего лишь меняет старую (или наоборот). Помогает привычка читать код как историю: “вот тут переменная появилась”, “вот тут ей поменяли значение”, “вот тут мы её используем”. Если вы научитесь видеть эти три этапа, 80% проблем с переменными уйдут без магии и шаманства.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ