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 виконав виведення типу за правою частиною.
3. := і = у повсякденному коді
Коротке оголошення := усередині функції
:= — це «швидке оголошення». Воно майже завжди використовується всередині 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("усередині:", y) // усередині: 20
}
fmt.Println("зовні:", x) // зовні: 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("витрати перевищили бюджет")
}
fmt.Println("витрачено:", 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% проблем зі змінними підуть без магії та шаманства.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ