Область видимости переменных

Название урока

Области видимости (Scope): локальная, вложенная и глобальная

Краткое описание

Изучаем фундаментальное понятие области видимости переменных в Go. Разбираем, как фигурные скобки {} создают отдельные области видимости, как работают вложенные области, чем отличается локальная область от глобальной, и почему можно объявить переменные с одинаковым именем в разных областях.

Основные концепции

1. Область видимости (Scope)

Определение: область видимости — это участок кода, в котором переменная существует и может быть использована.

Правило: фигурные скобки {} определяют границы области видимости.

package main

import "fmt"

func main() {
    // Область видимости функции main
    var volume int = 123
    fmt.Println("volumeMain =", volume)  // ✅ Работает
}
// Здесь volume больше не существует

2. Вложенные области видимости

Внутри одной области можно создать другую с помощью {}.

func main() {
    var volume int = 123
    fmt.Println("volumeMain =", volume)  // 123
    
    {
        // Новая вложенная область
        var volume int = 234
        fmt.Println("volumeInner =", volume)  // 234
    }
    
    fmt.Println("volumeMain =", volume)  // 123 (не изменилось)
}

Вывод:

volumeMain = 123
volumeInner = 234
volumeMain = 123

Ключевой момент: это две разные переменные с одинаковым именем, но в разных областях видимости.

3. Переопределение переменной во вложенной области

Если во вложенной области не объявлять новую переменную, используется внешняя.

func main() {
    var volume int = 123
    fmt.Println("volumeMain =", volume)  // 123
    
    {
        // Не объявляем новую, используем внешнюю
        volume = 234  // Изменяем внешнюю переменную
        fmt.Println("volumeInner =", volume)  // 234
    }
    
    fmt.Println("volumeMain =", volume)  // 234 (изменилось!)
}

Разница:

  • var volume int = 234новая переменная во вложенной области
  • volume = 234изменение внешней переменной

4. Глубокая вложенность

Можно создавать любое количество уровней вложенности.

func main() {
    var volume int = 123
    fmt.Println("Level 1:", volume)  // 123
    
    {
        var volume int = 234
        fmt.Println("Level 2:", volume)  // 234
        
        {
            var volume int = 345
            fmt.Println("Level 3:", volume)  // 345
        }
        
        fmt.Println("Level 2:", volume)  // 234
    }
    
    fmt.Println("Level 1:", volume)  // 123
}

Приоритет: внутренняя переменная перекрывает внешнюю с тем же именем.

5. Несколько областей на одном уровне

На одном уровне может быть несколько независимых областей.

func main() {
    var volume int = 123
    
    {
        // Первая область
        var volume int = 234
        fmt.Println("Area 1:", volume)  // 234
    }
    
    {
        // Второая область
        var volume int = 456
        fmt.Println("Area 2:", volume)  // 456
    }
    
    fmt.Println("Main:", volume)  // 123
}

Важно: эти области независимы друг от друга.

6. Ошибка: две переменные в одной области

Нельзя объявить две переменные с одинаковым именем в одной области видимости.

func main() {
    var volume int = 123
    var volume int = 456  // ❌ ОШИБКА: volume redeclared in this block
}

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

7. Локальная область видимости

Локальная переменная — объявлена внутри функции.

func main() {
    var value int = 987  // Локальная переменная функции main
    fmt.Println(value)   // ✅ Доступна внутри main
}

func foo() {
    // fmt.Println(value)  // ❌ ОШИБКА: value не определена здесь
}

Область действия: только внутри функции, где объявлена.

8. Глобальная область видимости

Глобальная переменная — объявлена на уровне пакета (вне функций).

package main

import "fmt"

var globalVolume int = 10  // Глобальная переменная

func main() {
    fmt.Println("main:", globalVolume)  // ✅ Доступна в main
    foo()
}

func foo() {
    fmt.Println("foo:", globalVolume)   // ✅ Доступна в foo
}

Вывод:

main: 10
foo: 10

Область действия: доступна во всех функциях текущего пакета.

9. Глобальная vs Локальная

package main

import "fmt"

var volume int = 10  // Глобальная

func main() {
    var volume int = 123  // Локальная (перекрывает глобальную)
    fmt.Println("main:", volume)     // 123 (локальная)
    
    foo()
}

func foo() {
    fmt.Println("foo:", volume)      // 10 (глобальная)
}

Вывод:

main: 123
foo: 10

Правило: локальная переменная перекрывает глобальную внутри своей области.

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

Пример 1: Области в реальном коде (предварительный взгляд)

func main() {
    var x int = 10
    
    // Будущий цикл for создаст свою область
    // for ... {
    //     var y int = 20  // Область цикла
    // }
    
    // Будущее условие if создаст свою область
    // if ... {
    //     var z int = 30  // Область условия
    // }
    
    fmt.Println(x)  // ✅ x доступна
    // fmt.Println(y)  // ❌ y не существует вне цикла
    // fmt.Println(z)  // ❌ z не существует вне условия
}

Вывод: каждая конструкция (if, for, switch) создаёт свою область видимости.

Пример 2: Временные вычисления

func main() {
    var result int
    
    {
        // Временная область для промежуточных вычислений
        var temp1 int = 10
        var temp2 int = 20
        result = temp1 + temp2
    }
    // temp1 и temp2 здесь больше не существуют
    
    fmt.Println("Result:", result)  // 30
}

Применение: изоляция временных переменных.

Пример 3: Обработка ошибок (из кода)

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    var x, y, k int8
    
    fmt.Print("Введите x y k: ")
    n, err := fmt.Scanln(&x, &y, &k)
    
    fmt.Printf("n = %d\n", n)
    
    if err != nil {
        fmt.Printf("err = %v\n", err)
        // Локальные переменные для обработки ошибки
        reader := bufio.NewReader(os.Stdin)
        reader.ReadString('\n')
    }
    // reader здесь недоступна
    
    fmt.Printf("x = %d\n", x)
    fmt.Printf("y = %d\n", y)
    fmt.Printf("k = %d\n", k)
}

Важные моменты

1. Фигурные скобки = граница области

{
    // Отдельная область видимости
}

2. Внутренняя переменная перекрывает внешнюю

var x int = 10
{
    var x int = 20  // Перекрывает внешнюю
    fmt.Println(x)  // 20
}
fmt.Println(x)      // 10

3. Имя должно быть уникальным в пределах одной области

var x int = 10
var x int = 20  // ❌ ОШИБКА

4. Глобальные переменные доступны во всех функциях пакета

var global int = 10

func main() { /* может использовать global */ }
func foo()  { /* может использовать global */ }

5. Локальные переменные существуют только внутри функции

func main() {
    var local int = 10
}
// local здесь не существует

6. Избегайте одинаковых имён

Хотя технически возможно, не рекомендуется использовать одинаковые имена в разных областях — это ухудшает читаемость.

// ❌ Плохо (запутанно)
var volume int = 10
{
    var volume int = 20
    {
        var volume int = 30
    }
}

// ✅ Хорошо (понятно)
var mainVolume int = 10
{
    var innerVolume int = 20
}

7. Области видимости будут везде

В будущих темах (if, for, switch, функции) каждая конструкция создаёт свою область видимости.

Best Practices

1. Используйте понятные имена

// ✅ Хорошо
var totalPrice int = 100
{
    var discountedPrice int = 80
}

// ❌ Плохо
var price int = 100
{
    var price int = 80  // Запутанно!
}

2. Ограничивайте область видимости

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

// ✅ Хорошо
func main() {
    {
        var temp int = calculate()  // Используется только здесь
        process(temp)
    }
    // temp больше не нужна и не существует
}

3. Не злоупотребляйте глобальными переменными

Глобальные переменные усложняют отладку и тестирование.

// ❌ Плохо (слишком много глобальных)
var a, b, c, d, e int

// ✅ Хорошо (локальные где возможно)
func main() {
    var a, b, c int
    // ...
}

4. Группируйте связанные области

func main() {
    // Блок инициализации
    {
        var config = loadConfig()
        initApp(config)
    }
    
    // Блок основной логики
    {
        var data = loadData()
        processData(data)
    }
}

Что запомнить

  • Фигурные скобки {} создают область видимости
  • Переменная существует только внутри своей области
  • Вложенные области могут иметь переменные с тем же именем
  • Внутренняя переменная перекрывает внешнюю
  • Глобальные переменные — вне функций, доступны везде в пакете
  • Локальные переменные — внутри функций, доступны только там
  • Нельзя объявить две переменные с одним именем в одной области
  • Избегайте одинаковых имён — это ухудшает читаемость
  • Каждая конструкция (if, for, функция) создаёт свою область
  • Объявляйте переменные в самой узкой нужной области

Полезные ссылки