Skip to content

devmax-pro/tasks-l-one

Repository files navigation

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

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

Наиболее эффективный способ для конкатенации - использовать тип strings.Builder. Этот тип предоставляет более оптимизированные по выделению памяти методы для построения строк.

A Builder is used to efficiently build a string using Write methods. It minimizes memory copying. The zero value is ready to use.

package main

import (
"fmt"
"strings"
)

func main() {
var sb strings.Builder

sb.WriteString("Hello, ")
sb.WriteString("world!")
sb.WriteString(" How are you?")

result := sb.String() // Получить итоговую строку

fmt.Println(result)
}

2. Что такое интерфейсы, как они применяются в Go?

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

type Animal interface {
	Speak() string
}

type Dog struct {}

func (d Dog) Speak() string {
	return "Woof!"
}

type Cat struct {}

func (c Cat) Speak() string {
	return "Meow!"
}

func main() {
	animals := []Animal{Dog{}, Cat{}}
	for _, animal := range animals {
		fmt.Println(animal.Speak())
	}
}

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

sync.Mutex и sync.RWMutex служат для управления доступом к общим ресурсам, но они используются в различных сценариях и работают немного по-разному.

Mutex: sync.Mutex предоставляет базовый механизм блокировки, который гарантирует, что только одна горутина может получить доступ к ресурсам в данный момент времени. Если один горутина захватила Mutex, любая другая горутина, будет блокироваться до тех пор, пока он не будет освобождён.

import "sync"

var mutex sync.Mutex
var sharedResource int

func updateResource() {
    mutex.Lock()   // Блокировка доступа к sharedResource
    sharedResource = sharedResource + 1
    mutex.Unlock() // Освобождение доступа
}

RWMutex: sync.RWMutex предоставляет более сложный механизм блокировки для ситуаций, когда у вас есть множество горутин, которые только читают данные, и меньшее число горутин, которые обновляют данные. RWMutex имеет два типа блокировок: блокировку для чтения (RLock) и блокировку для записи (Lock).

  • RLock (Read Lock): Блокирует ресурс для чтения. Множество горутин могут одновременно удерживать блокировку для чтения, при условии, что никакие горутины не удерживают блокировку для записи. Это улучшает производительность в сценариях с большим количеством операций чтения и меньшим количеством операций записи.
  • Lock (Write Lock): Блокирует ресурс для записи. Только одна горутина может удерживать блокировку для записи, и никакие другие не могут удерживать ни блокировку для чтения, ни блокировку для записи.
import "sync"

var rwMutex sync.RWMutex
var sharedResource int

func readResource() int {
    rwMutex.RLock()    // Блокировка для чтения
    value := sharedResource
    rwMutex.RUnlock()  // Освобождение
    return value
}

func writeResource(value int) {
    rwMutex.Lock()     // Блокировка для записи
    sharedResource = value
    rwMutex.Unlock()   // Освобождение
}

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

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

h := make(chan int) // Создание не буферизированного канала
go func() {
    // Эта операция блокируется, пока значение не будет получено.
    ch <- 42
}()
// После того как значение будет получено, горутина продолжит работу.
value := <-ch
fmt.Println(value)

Буферизированные каналы имеют внутреннюю очередь определённого размера. Операция отправки в буферизированный канал блокируется только тогда, когда буфер заполнен. Аналогично, операция получения блокируется только когда буфер пуст.

h := make(chan int, 2) // Создание буферизированного канала с размером буфера 2

// Операция отправки не блокируется, потому что есть свободное место в буфере.
ch <- 42
ch <- 27

// Третья операция отправки будет блокироваться, пока не освободится место в буфере.
// go func() {
//      ch <- 100
// }()

value := <-ch // Получение значения из канала
fmt.Println(value) // Выводит 42

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

Cтруктура struct{}{} не содержит никаких полей. Она называется пустой структурой и имеет размер 0 байт. Это может быть проверено с помощью функции unsafe.Sizeof, которая возвращает размер в байтах значения, переданного в качестве аргумента.

package main

import (
 "fmt"
 "unsafe"
)

func main() {
 var emptyStruct struct{}
 fmt.Println(unsafe.Sizeof(emptyStruct)) // Выведет 0
}

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

В Go нет перегрузки методов или операторов, как это есть в некоторых других языках программирования, таких как C++ или Java. В Go каждый метод должен иметь уникальное имя в рамках определённого типа. Это означает, что вы не можете иметь два метода с одним и тем же именем, даже если они принимают разные параметры.

Также не поддерживается перегрузка операторов. В языке есть фиксированный набор операторов с заранее определёнными операциями для встроенных типов, и вы не можете изменить или расширить эти операции для пользовательских типов. Это также способствует простоте и предсказуемости языка.

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

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

Элементы map[int]int выводятся в случайном порядке. Это связано с тем, что Go использует хэш-таблицу для реализации map, и порядок элементов в хэш-таблице не определен.

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

В Go, make и new используются для аллокации памяти, но они служат разным целям и работают по-разному.

new: Функция new(T) используется для аллокации памяти для значения типа T, которое инициализировано нулевым значением типа T и возвращает указатель на него. Нулевое значение зависит от типа: для чисел это будет 0, для булевых значений false, для указателей, слайсов, мап, каналов и функций это будет nil. new можно использовать для любого типа данных.

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

Разница между make и new:

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

Пример использования make и new:

b := new([]int)     // b - указатель на нулевой слайс, то есть на nil
c := make([]int, 0) // c - инициализированный пустой слайс с длиной и емкостью 0

fmt.Println(b) // Выведет &[], указатель на нулевой слайс
fmt.Println(c) // Выведет [], инициализированный пустой слайс

В этом примере b является указателем на слайс, который еще не был инициализирован (он указывает на nil), в то время как c является инициализированным слайсом с длиной и емкостью, равными 0.

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

Для определения переменной типа slice или map в Go существует несколько способов. Вот основные из них:

Slice (слайс)

1. Использование литерала слайса

s := []int{1, 2, 3}

2. Создание слайса с помощью make

s := make([]int, 3) // Слайс из трех нулевых элементов (длина 3, емкость 3)

3. Создание слайса с определенной длиной и емкостью

s := make([]int, 3, 5) // Длина 3, емкость 5

4. Создание нулевого слайса (nil)

var s []int // s == nil

5. Создание пустого слайса (не nil)

s := []int{} // или s := make([]int, 0)

6. Создание слайса с помощью new (редко используется)

s := new([]int) // s - указатель на слайс, *s == nil

7. Срез существующего массива или слайса

arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4] // Слайс, содержащий элементы 2, 3, 4

Map

1. Использование литерала map

m := map[string]int{"one": 1, "two": 2}

2. Создание пустой map с помощью make (не nil)

m := make(map[string]int) // Создает пустой map

3. Создание нулевой map (nil)

var m map[string]int // m == nil

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

func update(p *int) {  
    b := 2  
    p = &b  
}  
func main() {  
    var (  
       a = 1  
       p = &a  
    )  
    fmt.Println(*p) // 1
    update(p)  
    fmt.Println(*p) // 1
}
  1. В функции main объявляется переменная a со значением 1.
  2. Создается указатель p, который указывает на переменную a.
  3. С помощью fmt.Println(*p) выводится значение, на которое указывает p, то есть 1.
  4. Вызывается функция update, в которую передается копия указателя p.
  5. В функции update переменная b иннициализируется значением 2.
  6. Внутри update переменной p присваивается адрес переменной b. Это изменение локально для функции update, так как в Go аргументы передаются по значению, и это означает, что p в update является копией исходного указателя p из main. Изменение локального p в функции update не влияет на значение p в функции main.
  7. После завершения функции update программа возвращается в функцию main, и снова вызывается fmt.Println(*p). Поскольку функция update не изменила значение a и не изменила исходный указатель p в функции main, *p все еще равно 1.

Это происходит потому, что изменения в функции update затрагивают только локальную копию указателя, а не исходное значение, на которое указывает p в функции main.

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")  
}

Вывод:

4
2
1
0
3
fatal error: all goroutines are asleep - deadlock!

При работе программы получили deadlock, из-за того, что передаём копию WaitGroup в горутину, а не ссылку на оригинальный WaitGroup. В Go sync.WaitGroup использует внутренний счетчик, чтобы отслеживать количество ожидающих горутин. Когда передаём wg по значению, каждая горутина получает свою копию WaitGroup, и вызов Done() уменьшает счетчик в этой копии, а не в оригинальном WaitGroup, с которым вызывается Wait() в главной горутине.

Чтобы исправить эту проблему, нужно передавать wg по ссылке, а не по значению. Вот исправленный вариант кода:

func main() {
    wg := &sync.WaitGroup{} // Используем указатель на WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(wg *sync.WaitGroup, i int) { // Передаем указатель
            defer wg.Done() // Используем defer для гарантии вызова Done() даже если возникнет паника
            fmt.Println(i)
        }(wg, i) // Передаем wg как указатель
    }
    wg.Wait()
    fmt.Println("exit")
}

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

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

В данном коде выводит 0, потому что в блоке if true { ... } создается новая локальная переменная n вместо использования уже существующей переменной n, объявленной в функции main. Внешняя переменная n, объявленная в функции main до блока if, не изменяется и остается равной 0. Именно ее значение и выводится в конце программы.

Чтобы исправить эту ошибку, нужно использовать оператор присваивания = вместо := в блоке 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]
}

Вывод будет равен [100 2 3 4 5]. В Go, слайсы передаются в функции по значению, но это значение включает в себя ссылку на массив, на который ссылается переданный слайс. Все изменения, внесенные в элементы слайса внутри функции, отразятся в исходном слайсе в вызывающей функции. При выполнении операции append, если в исходном массиве достаточно места для нового элемента, append изменит исходный массив. Если нет – будет создан новый массив, и v будет теперь указывать на этот новый массив. В данном случае, поскольку массив a может вместить еще элементы, append не создаст новый массив, и изменение будет внесено в исходный массив.

Для решения данной проблемы нужно, чтобы функция someAction возвращала новый слайс, а не изменяла предоставленный:

package main  
  
import (  
    "fmt"  
)  
  
func someAction(v []int8, b int8) []int8 {  
    v[0] = 100  
    v = append(v, b)  
    return v 
}  
  
func main() {  
    var a = []int8{1, 2, 3, 4, 5}  
    a = someAction(a, 6)  
    fmt.Println(a) // Вывод: [100 2 3 4 5 6]  
}

В этом случае изменения внесены в копию слайса, а затем эта измененная копия возвращается и присваивается обратно переменной a в функции main

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]  
}

Программа выводит следующее: [b b a][a a]

  1. Создается слайс slice с двумя элементами.
  2. Затем объявляется и немедленно вызывается анонимная функция, которой в качестве аргумента передается слайс slice. Переменная slice внутри функции является копией ссылки на массив, то есть изменения в переменную slice внутри функции не отражаются на внешнем slice.
  3. Внутри анонимной функции к слайсу добавляется еще один элемент "a" с помощью append. Если в исходном слайсе было достаточно места для еще одного элемента (capacity >= 3), то изменения будут внесены в исходный массив и он будет виден и снаружи функции. Если же места не хватило, append создаст новый массив и скопирует туда элементы из старого, увеличив его capacity. В данном случае исходный слайс имел длину 2 и capacity, скорее всего, тоже 2 (так как не указано явно и Go может выделить capacity ровно под количество элементов), поэтому append выделит новый массив.
  4. Далее внутри анонимной функции элементы с индексами 0 и 1 меняют свои значения на "b". Так как возможно было создание нового массива на предыдущем шаге, изменения происходят в новом массиве, но если бы append не привел к выделению нового массива, изменения отразились бы и на внешнем слайсе.
  5. Анонимная функция выводит измененный слайс, который теперь содержит ["b", "b", "a"].
  6. После выполнения анонимной функции, основная функция main выводит исходный слайс slice, который не изменился в результате работы анонимной функции и содержит ["a", "a"], так как изменения в анонимной функции либо происходили в новом массиве (если был выделен из-за недостатка capacity), либо не затрагивали внешнюю переменную slice.

Таким образом, сначала выводится измененный в анонимной функции слайс [b b a], а затем не измененный исходный слайс [a a].

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages