суббота, 26 января 2019 г.

Эффективный Go: инициализация

Хотя она не выглядит внешне очень отличающейся от инициализации в C или C++, инициализация в Go более мощная. Сложные структуры могут быть построены во время инициализации, а проблемы упорядочения среди инициализированных объектов, даже среди разных пакетов, обрабатываются правильно.

Константы

Константы в Go - это просто константы. Они создаются во время компиляции, даже если они определены как локальные в функциях, и могут быть только числами, символами (рунами), строками или булевыми типами. Из-за ограничения времени компиляции выражения, которые определяют их должны быть константными выражениями, оценивающимися компилятором. Например,

1 << 3
является константным выражением, а math.Sin(math.Pi/4) нет, потому что вызов функции для math.Sin случится во время выполнения.

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

type ByteSize float64

const (
    // игнорируем первое значение, 
    // производя присваивание к пустому идентификатору
    _           = iota
    KB ByteSize = 1 << (10 * iota)
    MB
    GB
    TB
    PB
    EB
    ZB
    YB
)

Возможность прикрепить метод, такой как String, к любому определенному пользователем типу позволяет произвольным значениям форматировать себя автоматически для печати. Хотя вы увидите, что это чаще всего применяется к структурам, этот метод также полезен для скалярных типов, таких как типы с плавающей точкой, такие как ByteSize.

func (b ByteSize) String() string {
    switch {
    case b >= YB:
        return fmt.Sprintf("%.2fYB", b/YB)
    case b >= ZB:
        return fmt.Sprintf("%.2fZB", b/ZB)
    case b >= EB:
        return fmt.Sprintf("%.2fEB", b/EB)
    case b >= PB:
        return fmt.Sprintf("%.2fPB", b/PB)
    case b >= TB:
        return fmt.Sprintf("%.2fTB", b/TB)
    case b >= GB:
        return fmt.Sprintf("%.2fGB", b/GB)
    case b >= MB:
        return fmt.Sprintf("%.2fMB", b/MB)
    case b >= KB:
        return fmt.Sprintf("%.2fKB", b/KB)
    }
    return fmt.Sprintf("%.2fB", b)
}

Выражение YB печатается как 1.00YB, в то время как ByteSize(1e13)печатается как 9.09TB.

Использование здесь Sprintf для реализации в ByteSize метода String безопасно (избегает бесконечного повторения) не из-за преобразования, а потому что он вызывает Sprintf с %f, который не является строковым форматом: Sprintf будет вызывать только метод String, когда ему нужна строка, и %f ожидает значение с плавающей точкой.

Переменные

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

var (
    home   = os.Getenv("HOME")
    user   = os.Getenv("USER")
    gopath = os.Getenv("GOPATH")
)

Функция init

Наконец, каждый исходный файл может определить свою собственную функцию init для установки любого необходимого состояния. (На самом деле каждый файл может иметь несколько init.) init вызывается после того как все объявления переменных в пакете были оценены их инициализаторами, и они оцениваются только после того, как все импортированные пакеты были инициализированы.

Помимо инициализаций, которые не могут быть выражены как объявления, обычное использование функций init - это проверка или исправление правильности состояния программы до начала реального выполнения.

func init() {
    if user == "" {
        log.Fatal("$USER not set")
    }
    if home == "" {
        home = "/home/" + user
    }
    if gopath == "" {
        gopath = home + "/go"
    }
    // gopath может быть переписан 
    // --gopath флагом в командной строке.
    flag.StringVar(&gopath, "gopath", gopath, 
                   "override default GOPATH")
}


Читайте также:


Комментариев нет:

Отправить комментарий