Анализ теста по Go с PHDays

—

от автора

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

В конце мая прошла конференция PHDays, на которой был тест как раз с такими задачками. К моему сожалению, я провалила этот тест, но затем разобралась что, как и почему, и хочу поделиться с вами.

Итак, 5 картинок с кодом, к каждому дается 4 варианта ответа.

Задача 1

package main  import "fmt"  func f(slice []int) {     slice = append(slice, 84) }  func main() {     s := []int{23, 42}     f(s)     fmt.Println(s) } 

Варианты ответа:

1. [23 42]
2. [23 42 84]
3. Программа не скомпилилируется
4. runtime error: slice out of range

Ответ

Если попробовать скомпилировать код, то компилятор обратит ваше внимание на строку slice = append(slice, 84) и не просто так.

Да, слайсы имеют ссылочный тип. Но если они ссылочные, что не так-то? Должно работать!

А проблема в том, как работает функция append — она не меняет старый слайс, а возвращает новый. А из функции f мы результат не получаем.

Соответственно, правильный ответ будет под номером 1: [23 42].

Чтобы получить ответ из пункта 2 ([23 42 84]), нам нужно этот результат как-то передать в main. Сделать это можно двумя способами.

Во-первых, можно вернуть новый слайс из функции. Поправим немного код.

package main  import "fmt"  func f(slice []int) []int {     return append(slice, 84) }  func main() {     s := []int{23, 42}     s = f(s)     fmt.Println(s) } 

(Go Playground)

Во-вторых, можно вместо слайса передавать указатель на слайс:

package main  import "fmt"  func f(slice *[]int) {     *slice = append(*slice, 84) }  func main() {     s := []int{23, 42}     f(&s)     fmt.Println(s) } 

(Go Playground)

Задача 2

package main  import "fmt"  func main() {     s := "bar"     {         s := "foo"         fmt.Print(s)     }     fmt.Print(s) } 

Варианты ответа:

1. foofoo
2. foobar
3. compilation error: s redeclared in this block
4. compilation error: s undefined: s

Ответ

Вы когда-нибудь видели, чтобы кто-то вот так использовал скобки? Я — нет. Однако давайте разберёмся, что тут происходит.

Имеем две области видимости: внутри скобок и за их пределами. При этом с толку сбивают две одноимённых переменных. Такое называется variable shadowing и считается плохой практикой в Go. Существуют различные линтеры, которые выдают на подобный код предупреждение (например, этот линтер, который используется вместе с go vet, см. go help vet). Собственно, поэтому сначала программа напишет foo, а затем bar. Следовательно, правильный ответ будет под номером 2.

Задача 3

package main  import (     "fmt"     "sync" )  func main() {     var wg sync.WaitGroup     for i := 0; i < 100; i++ {         wg.Add(1)         go func() {             fmt.Println(i)             wg.Done()         }()     }     wg.Wait() } 

Варианты ответа:

1. Числа от 1 до 99 по возрастанию
2. Числа от 1 до 99 в произвольном порядке
3. Вывод не определен
4. Ничего

Ответ

Проблема состоит из двух частей. Во-первых, счётчик цикла создаётся лишь один раз и затем просто изменяется на каждой итерации. Во-вторых, при захвате переменных из окружения в замыкание (анонимную функцию) они захватываются по указателю.

Т.о. захваченный счётчик цикла в каждой горутине один и тот же, и меняется во всех них разом. Поэтому вывод зависит от того, когда какая горутина успеет напечатать текст. Какой в этот момент будет счётчик, такой и напечатается. Большинство же горутин напечатает 100, т.к. такой цикл закончится гораздо быстрее, чем осуществится вывод.

Следовательно, правильный ответ будет под номером 3 — вывод не определен.

Чтобы получить цифры от 1 до 99 в произвольном порядке, есть два варианта.

Во-первых, можно сделать копию счётчика цикла (i := i) перед созданием замыкания:

package main  import (     "fmt"     "sync" )  func main() {     var wg sync.WaitGroup     for i := 0; i < 100; i++ {         wg.Add(1)         i := i         go func() {             fmt.Println(i)             wg.Done()         }()     }     wg.Wait() } 

(Go Playground)

Во-вторых, можно сделать в анонимной функции параметр i int и передавать счётчик цикла в замыкание через этот параметр (тогда он будет копироваться при этой передаче):

package main  import (     "fmt"     "sync" )  func main() {     var wg sync.WaitGroup     for i := 0; i < 100; i++ {         wg.Add(1)         go func(i int) {             fmt.Println(i)             wg.Done()         }(i)     }     wg.Wait() } 

(Go Playground)

Задача 4

package main  import "fmt"  func main() {     s := "Hello"      defer func() {         s = "World"     }()     fmt.Println(s) } 

Варианты ответа:

1. World
2. Hello
3. Hello World
4. compilation error: (func literal)() used as value

Ответ

Чтобы понять, что происходит в этом коде, надо знать особенности defer. Эта конструкция выполнится только по завершению функции. Тут нас сбивает с толку Println, которы стоит после defer. Однако, если мы попробуем упросить код и убрать всё, что может нас запутать, то получим что-то такое:

package main  import "fmt"  func main() {     s := "Hello"     fmt.Println(s)      s = "World" } 

(Go Playground)

Теперь правильный ответ очевиден, он под номером 2.

Задача 5

package main  import "fmt"  func main() {     a := []string{"a", "b", "c"}     b := a[1:2]     b[0] = "q"     fmt.Println(a) } 

Варианты ответа:

1. [a b c]
2. [a q c]
3. [b c]
4. output is undefined

Ответ

На первый взгляд хочется спросить, а зачем тут вообще эта b? Мы же a печатаем.

Вот теперь самое время вспомнить, что слайсы в Go — это ссылочный тип данных. Следовательно, на строке b := a[1:2], мы присваиваем в переменную b ссылку на область памяти из переменной a. А затем (b[0] = "q") редактируем эту самую область памяти. В итоге имеем в результате правильный ответ под номером 2.

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

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Сколько задач вы решили?

35.82% 548
23.88% 432
17.16% 323
7.46% 210
3.73% 15
11.94% Не знаю Go16

Проголосовали 134 пользователя. Воздержались 15 пользователей.

ссылка на оригинал статьи https://habr.com/ru/articles/571002/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *