Jhnvlglmlbrt / wb-L1

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Ответы на вопросы L1

  1. Какой самый эффективный способ конкатенации строк?

    Так как строки представляют собой неизменяемый слайс байтов, при конкатенации двух строк происходит выделение новой памяти. Это может негативно сказаться на потреблении памяти, если нужно соединить много строк. Поэтому в таких случаях следует использовать strings.Builder.

    var b strings.Builder
    b.Grow(100)
    for i := 0; i<100; i++ {
        fmt.Fprint(&b, "a")
    }
    fmt.Println(b.String()) // aaaaaaaa....
  2. Что такое интерфейсы, как они применяются в Go?

    Интерфейсы — это инструменты для определения наборов действий и поведения. Интерфейсы — это в первую очередь контракты. Они позволяют объектам опираться на абстракции, а не фактические реализации других объектов. При этом для компоновки различных поведений можно группировать несколько интерфейсов. В общем смысле — это набор методов, представляющих стандартное поведение для различных типов данных.

    Как устроен Duck-typing в Go?

    Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, утка и есть.

    Если структура содержит в себе все методы, что объявлены в интерфейсе, и их сигнатуры совпадают — она автоматически удовлетворяет интерфейс.

    Такой подход позволяет полиморфно (полиморфизм — способность функции обрабатывать данные разных типов) работать с объектами, которые не связаны в иерархии наследования. Достаточно, чтобы все эти объекты поддерживали необходимый набор методов.

    Интерфейсный тип

    В Go интерфейсный тип выглядит вот так:

    type iface struct {
        tab  *itab
        data unsafe.Pointer
    }

    Где tab — это указатель на Interface Table или itable — структуру, которая хранит некоторые метаданные о типе и список методов, используемых для удовлетворения интерфейса, а data указывает на реальную область памяти, в которой лежат данные изначального объекта (статическим типом).

    Компилятор генерирует метаданные для каждого статического типа, в которых, помимо прочего, хранится список методов, реализованных для данного типа. Аналогично генерируются метаданные со списком методов для каждого интерфейса. Теперь, во время исполнения программы, runtime Go может вычислить itable на лету (late binding) для каждой конкретной пары. Этот itable кешируется, поэтому просчёт происходит только один раз.

    Зная это, становится очевидно, почему Go ловит несоответствия типов на этапе компиляции, но кастинг к интерфейсу — во время исполнения.

    Что важно помнить — переменная интерфейсного типа может принимать nil. Но так как объект интерфейса в Go содержит два поля: tab и data — по правилам Go, интерфейс может быть равен nil только если оба этих поля не определены (faq):

    var (
        builder  *strings.Builder
        stringer fmt.Stringer
    )
    
    fmt.Println(builder, stringer) // nil nil
    fmt.Println(stringer == nil) // true
    fmt.Println(builder == nil) // true
    
    stringer = builder
    
    fmt.Println(builder, stringer) // nil nil
    fmt.Println(stringer == nil) // false (!!!)
    fmt.Println(builder == nil) // true

    Пустой interface{}

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

    На какой стороне описывать интерфейс — на передающей или принимающей?

    Многое зависит от конкретного случая, но по умолчанию описывать интерфейсы следует на принимающей стороне — таким образом, ваш код будет меньше зависеть от какого-то другого кода/пакета/реализации.

    Другими словами, если нам в каком-то месте требуется «что-то что умеет себя закрывать», или — умеет метод Close() error, или (другими словами) удовлетворят интерфейсу:

    type something interface {
        Close() error
    }

    То он (интерфейс) должен быть описан на принимающей стороне. Так принимающая сторона не будет ничего знать о том, что именно в неё может «прилететь», но точно знает поведение этого «чего-то». Таким образом реализуется инверсия зависимости, и код становится проще переиспользовать/тестировать.

    Ссылка: Вопросы и ответы для собеседования Go-разработчика

  3. Чем отличаются RWMutex от Mutex?

    Mutex позволяет блокировать доступ к общим ресурсам с помощью методов Lock и Unlock. У RWMutex есть еще два метода RLock и RUnlock, которые можно использовать, если общий ресурс нуждается в чтении, таким образом другие операции RLock/RUnlock не заблокируются, а операции Lock/Unlock заблокируются.

  4. Чем отличаются буферизированные и не буферизированные каналы?

    Чтение или запись данных в небуферизированный канал блокирует горутину и контроль передается свободной горутине. Буферизированный канал создается указанием размера буфера, в этом случае горутина не блокируется до тех пор, пока буфер не будет заполнен.

  5. Какой размер у структуры struct{}{}?

    Пустая структура занимает 0 байт.

    Ссылка: The empty struct

  6. Есть ли в Go перегрузка методов или операторов?

    Нет.

  7. В какой последовательности будут выведены элементы map[int]int? Пример:

    m[0]=1
    m[1]=124
    m[2]=281

    В случайной. Порядок вывода при итерации по мапе не гарантирован.

    Ссылка: Go maps in action

  8. В чем разница make и new?

    Функция new(T) выделяет «нулевую» память для нового элемента типа T и возвращает его адрес, значение типа *T. В терминологии Go он возвращает указатель на только что выделенное нулевое значение типа T. Вот три различных способа создания указателя p, указывающего на нулевое значение bytes.Buffer, каждый из которых эквивалентен:

    var buf bytes.Buffer
    p := &buf
    
    p := &bytes.Buffer{}
    
    p := new(bytes.Buffer)

    Функция make() — это специальная встроенная функция, которая используется для инициализации слайсов, мап и каналов. make() можно использовать только для инициализации слайсов, мап и каналов, и что, в отличие от функции new(), make() не возвращает указатель.

    Слайсы, мапы и каналы также можно инициализировать с помощью составных выражений. В качестве примеров ниже приведены два разных (но эквивалентных) способа инициализации мапы m:

    m := make(map[string]bool, 0)
    
    m := map[string]bool{}

    Также можно инициализировать мапу и заполнить ее значениями:

    m := map[string]bool{
    "java": false,
    "go": true,
    }

    Ссылка: new() vs make()

  9. Сколько существует способов задать переменную типа slice или map?

    // slice - 6
    letters := []string{}
    letters := []string{"a", "b", "c", "d"}
    var letters []string
    letters := make([]string, 0)
    letters := make([]string, 0, 10)
    letters := new([]string)
    
    // map - 5
    var m map[string]string
    m := map[string]string{
        "123": "456",
        "test": "hello",
    }
    m := make(map[string]string)
    m := make(map[string]string, 10)
    m := new(map[string]string)
  10. Что выведет данная программа и почему?

    func update(p *int) {
        b := 2
        p = &b
    }
    
    func main() {
        var (
            a = 1
            p = &a
        )
        fmt.Println(*p)
        update(p)
        fmt.Println(*p)
    }

    По-умолчанию в go аргументы в функцию передаются по значению. Если необходимо передать указатель, нужно это явно указать. Однако в go нет ссылок (невозможно создать две переменные с одним адресом). В примере выше, несмотря на то, что в функцию update передается указатель, переменная в главной функции не изменяется. p внутри функции update является локальной переменной, и изменения касаются только этой локальной переменной. Оригинальный указатель в main остается неизменным.

    Таким образом программа выведет:

    1
    1
    

    Т.к. update принимает указатель и мы передаём &a, то нужно разыменовать его внутри функции - *p = b, а не передавать адрес p. Либо передавать в update двойной указатель -

    update(p **int)
    *p = &b 
    update(*p)
    
  11. Что выведет данная программа и почему?

    func main() {
        wg := sync.WaitGroup{}
        for i := 0; i < 5; i++ {
            wg.Add(1)
            go func(wg sync.WaitGroup, i int) {
                fmt.Println(i)
                wg.Done()
            }(wg, i)
        }
        wg.Wait()
        fmt.Println("exit")
    }

    Будет дедлок. Так как мы передаем структуру WaitGroup в функцию по значению, она копируется, и вызов wg.Done() внутри горутины не изменит счетчик WaitGroup в главной функции. Следовательно, главная горутина навсегда зависнет на wg.Wait()

    Для того, чтобы исправить эту ошибку, необходимо передавать в функцию структуру WaitGroup по указателю, либо не передавать его и использовать через замыкание.

  12. Что выведет данная программа и почему?

    func main() {
        n := 0
        if true {
            n := 1
            n++
        }
        fmt.Println(n)
    }

    Программа выведет 0. Конструкция if создает новую область видимости, переменная n создается в ней и инкрементируется. При этом переменная n конструкции if не затрагивается, так как она находится в другой области видимости.

  13. Что выведет данная программа и почему?

    func someAction(v []int8, b int8) {
        v[0] = 100
        v = append(v, b)
    }
    
    func main() {
        var a = []int8{1, 2, 3, 4, 5}
        someAction(a, 6)
        fmt.Println(a)
    }

    Программа выведет: [100 2 3 4 5]

    Слайс – это структура, которая содержит в себе длину, емкость и указатель на массив. Передавая слайс в функцию по значению, он копируется, но поля остаются теми же. Поэтому мы можем изменить значения элементов. При использовании append, т.к. вместимости слайса не хватает, то происходит выделение нового блока памяти для среза, и изменения касаются только этого нового среза, не влияя на оригинальный срез, который был передан в функцию.

  14. Что выведет данная программа и почему?

    func main() {
        slice := []string{"a", "a"}
        func(slice []string) {
            slice = append(slice, "a")
            slice[0] = "b"
            slice[1] = "b"
            fmt.Print(slice)
        }(slice)
        fmt.Print(slice)
    }

    Программа выведет: [b b a][a a]

    В данной программе анонимной функции передается через параметры слайс, то есть он копируется. Меняя этот слайс мы не меняем слайс в главной функции.

About


Languages

Language:Go 100.0%