Вопросы к собеседованию Flashcards

1
Q

Как реализовано ООП в go?

A

В go нет классической реализация ООП, так как он не объектно-ориентированный язык. При этом в go есть свои приближения к этой реализации.

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

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

Инкапсуляция
Инкапсуляция – сокрытие поведения объекта внутри него. Объекту «водитель» не нужно знать, что происходит в объекте «машина», чтобы она ехала.

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

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

Полиморфизм в go реализован с помощью интерфейсов.
Основная идея заключается в том, что мы можем объявить интерфейсы (контракты на определённое поведение) для наших типов. При этом, для типов мы должны реализовать методы, удовлетворяющие этим интерфейсам. Таким образом, мы сможем работать со всем набором типов, у которых реализовали интерфейсы, как с единым интерфейсным типом.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Что такое пакеты в go?

A

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Что такое глобальная переменная?

A

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Что такое фигурные скобки с не объявленным оператором в go функции?

A

В go функции действительно можно объявить{} без оператора, ограничив область видимости куска кода в рамках этой функции.

func main() {
	solder := "Bill"
{
	solder := "Den"

	fmt.Println(solder)
}

fmt.Println(solder) }
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

В go есть оператор switch case, можно ли выполнить несколько условий в одном объявленном операторе?

A

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

func main() {
	animals := []string{"bear", "bear", "rabbit", "wolf"}
for _, animal := range animals {
	switch animal {

	case "rabbit":
		fmt.Println(animal, "is so weak!")
		fallthrough
		case "bear", "wolf":
			fmt.Println(animal, "is so strong!")
		}
	}
}
bear is so strong!
bear is so strong!
rabbit is so weak!
rabbit is so strong!
wolf is so strong!
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Что представляют из себя строки в go?

A

В Go строка в действительности является срезом (slice) байтов, доступным только для чтения.

type _string struct {
elements *byte // underlying bytes
len int // number of bytes
}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Как можно оперировать строками?

A

Строки в go можно складывать(конкатенировать). Для многих операций есть стандартные пакеты, к примеру strings, fmt. Кроме того, надо понимать, что все варианты конкатенации имеют свою производительность.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Что будет если сложить строки?

A

Ранее мы говорили о том что, строки - это массивы байт. Из этого следует, что при работе со строками (конкатенация и тд) мы будем получать новые строки

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Как определить количество символов для строки? или Какие есть нюансы при итерации по строке?

A

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

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

Еще одним способом определения длинны строки является функция RuneCountInString пакета utf8.

func main() {
	str := "世界, 你好!"
fmt.Printf("len bytes: %d\n", len(str))
fmt.Printf("len runes: %d\n", len([]rune(str)))   fmt.Printf("len runes: %d\n", utf8.RuneCountInString(str)) }

В Go присутствует синтаксический сахар при обходе строки. Если использовать конструкцию for range, строка автоматически будет преобразована в []rune, то есть обход будет по Юникод символам

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Какие численные типы есть в go?

A

int/int8/int16/int32/int64;

uint/uint8/uint16/uint32/uint64;

float32/float64;

complex64/complex128;

rune(int32).

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

Чем отличается int от uint?

A

int содержит диапазон от отрицательных значений до положительных, тогда как uint - это диапазон от 0 в строну увеличения положительных значений.

Пример: int64 это диапазон от –9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 , uint64 от 0 до 18 446 744 073 709 551 615.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Что такое обычный int и какие есть нюансы его реализации?

A

В зависимости от того какая архитектура платформы, на которой мы стартуем, компилятор преобразует int в int32 для 32 разрядной архитектуры и в int64 для 64 разрядной архитектуры.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Как преобразовать строку в int и наоборот? Можно ли сделать int(string) и string(int) соответственно?

A

Преобразование типов между int и string указанным синтаксисом невозможно. Для преобразования необходимо использовать функции из пакета strconv стандартной библиотеки go.

При этом для преобразования строк в/из int и int64 используются разные функции, strconv.Atoi и strconv.Itoa для int, strconv.ParseInt и strconv.FormatInt соответственно.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Сколько в памяти занимают реализации int32 и int64?

A

В предыдущем вопросе мы отвечали сколько места в памяти занимают эти типы. Таким образом с помощью 4 или 8 байт можно закодировать разные по диапазону значения.

Для int64 это диапазон от –9 223 372 036 854 775 808 до 9 223 372 036 854 775 807, для int32 от –2 147 483 648 до 2 147 483 647.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Какой результат получим если разделить int на 0 и float на 0?

A

Это вопрос с подвохом. Деление int на 0 в go невозможно и вызовет ошибку компилятора. Тогда как деление float на 0 дает в своем результате бесконечность.

func main() {
	f := 500.0
fmt.Printf("float: %v\n", f/0) } float: +Inf
func main() {
    i := 500
fmt.Printf("int: %v\n", i/0) } division by zero
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Что такое константы и можно ли их изменять?

A

Константы - это неизменяемые переменные, изменить константу нельзя.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

Что такое iota?

A

iota - идентификатор, который позволяет создавать последовательные не типизированные целочисленные константы. Значением iota является индекс ConstSpec. Не смотря на то, что первым индексом является 0, значение первой константы можно задать отличным от 0, что в свою очередь повлияет на значения последующих констант.

18
Q

Что такое слайс и чем он отличается от массива?

A

Cлайс - это структура go, которая включает в себя ссылку на базовый массив, а также две переменные len(length) и cap(capacity).

len это длина слайса - то количество элементов, которое в нём сейчас находится.

cap - это ёмкость слайса - то количество элементов, которые мы можем записать в слайс сверх len без его дальнейшего расширения.

Array - это последовательно выделенная область памяти. Частью типа array является его размер, который в том числе является не изменяемым.

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}
19
Q

Как работает базовая функция append для go?

A

Функция принимает на вход слайс и переменное количество элементов для добавления в слайс. Append расширяет слайс за пределы его len, возвращая при этом новый слайс.

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

func append(slice []Type, elems …Type) []Type

20
Q

Какой размер массива выделяется под слайс при его расширении за рамки его емкости?

A

Если отвечать на вопрос поверхностно, то можно сказать, что базовый массив расширяется в два раза от нашей capacity.

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

Если развернуть ответ полностью, то это будет звучать примерно так:

если требуемая cap больше чем вдвое исходной cap, то новая cap будет равна требуемой;

если это условие не выполнено, а также len текущего слайса меньше 1024, то новая cap будет в два раза больше базовой cap;

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

if cap > doublecap {
		newcap = cap
	} else {
		if old.cap < 1024 {
			newcap = doublecap
		} else {
			// Check 0 < newcap to detect overflow
			// and prevent an infinite loop.
			for 0 < newcap && newcap < cap {
				newcap += newcap / 4
			}
			// Set newcap to the requested cap when
			// the newcap calculation overflowed.
			if newcap <= 0 {
				newcap = cap
			}
		}
	}
21
Q

Как реализована map(карта) go? *Ответ требует дополнительной информации

A

Сама map в go - это структура, реализующая операции хеширования. При этом, так же как и любую структуру, содержащую ссылки на области памяти,map необходимо инициализировать. map ссылается на такие элементы как bucket (в переводе на русский “ведра”). Каждый bucket содержит в себе:

8 экстра бит, с помощью которых осуществляется доступ до значений в этом bucket;

ссылку на следующий коллизионный bucket;

8 пар ключ-значение, уложенных в массив.

22
Q

Почему нельзя брать ссылку на значение, хранящееся по ключу в map? *Дописать

A

map поддерживает процедуру эвакуации. Значения, хранящиеся в определённой ячейки памяти в текущий момент времени, в следующий момент времени уже могут там не храниться.

23
Q

Что такое эвакуация, и в каком случае она будет происходить?

A

Эвакуация - это процесс когда map переносит свои значения из одной области памяти в другую. Это происходит из-за того что число значений в каждом отдельном bucket максимально равно 8.

В тот момент времени, когда среднее количество значений в bucket составляет 6.5, go понимает, что размер map не удовлетворяет необходимому. Начинается процесс расширения map.

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

24
Q

Какие есть особенности синтаксиса получения и записи значений в map?

A

Получить значение из map, которую мы предварительно не аллоцировали нельзя, приложение упадет в панику.

Если ключ не найден в map в ответ мы получим дефолтное значение для типа значений map. То есть, для строки - это будет пустая строка, для int - 0 и так далее. Для того, чтобы точно понять, что в map действительно есть значение, хранящееся по переданному ключу, необходимо использовать специальный синтаксис. А именно, возвращать не только само значение, но и булевую переменную, которая показывает удалось-ли получить значение по ключу.

25
Q

Как происходит поиск по ключу в map?

A

вычисляется хэш от ключа;

с помощью значения хэша и размера bucket вычисляется используемый для хранения bucket;

вычисляется дополнительный хэш - это первые 8 бит уже полученного хэша;

в полученном bucket последовательно сравнивается каждый из 8 его дополнительных хэшей с дополнительным хэшем ключа;

если дополнительные хэши совпали, то получаем ссылку на значение и возвращаем его;

если дополнительные хэши не совпали, и в bucket больше нет дополнительных хэшей, алгоритм переходит в следующий bucket, ссылка на который хранится в текущем;

если в текущем bucket нет ссылки на следующий bucket, а значение так и не найдено, возвращается дефолтное значение.

26
Q

Что такое интерфейсы в go?

A

Интерфейс можно рассматривать, как некое соглашение (контракт), что тот или иной объект будет реализовывать указанное в интерфейсе поведение.

Интерфейсный тип в Go — это своего рода определение. Он определяет и описывает конкретные методы, которые должны быть у какого-то другого типа.

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

27
Q

Дайте пример реализации интерфейсов.

A

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

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

Допустим, участок делится на 2 геометрические фигуры: квадрат и прямоугольный треугольник. Для каждой фигуры мы можем создать тип. Описать интерфейс Squarer, условием реализации которого будет метод расчета площади. Написать для каждого типа метод расчета площади, который будет реализовывать объявленный интерфейс Squarer.

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

type Squarer interface {
GetSquare() int
}

type Foursquare struct {
a int
}

func (obj Foursquare) GetSquare() int {
	return obj.a * obj.a
}
type Triangle struct {
	a int // катет
	b int // катет
	c int // гипотенуза
}
func (obj Triangle) GetSquare() int {
	return obj.a * obj.b / 2
}
func sumSquare(s []Squarer) int {
	square := 0
for i := range s {
	square += s[i].GetSquare()
}

return square }

func main() {

figures := []Squarer{Foursquare{a: 3}, Foursquare{a: 2}, Triangle{a: 2, b: 3}}

fmt.Println(sumSquare(figures)) }
28
Q

Что такое пустой интерфейс?

A

Пустой интерфейсный тип не описывает методы. У него нет правил. И поэтому любой объект удовлетворяет пустому интерфейсу.

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

func main() {
	var (
		inter interface{}
		param *int
	)
fmt. Printf("%T %v %v\n", inter, inter, inter == nil)
fmt. Printf("%T %v %v\n", param, param, param == nil)

inter = param

fmt.Printf("%T %v %v\n", inter, inter, inter == nil) }

true

  • int true
  • int false
29
Q

Как преобразовать интерфейс к другому типу?

A

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

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

var (
v string
ok bool
)

v, ok = i.(T)
30
Q

Как определить тип интерфейса?

A

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

func do(i interface{}) {

	switch v := i.(type) {
	case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
	case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	}
}
func main() {
	do(21)
	do("hello")
	do(true)
}
31
Q

Зачем используется ключевое слово defer в go?

A

defer добавляет переданную после него функцию в стэк. При возврате внешней функции, вызываются все, добавленные в стэк вызовы. Поскольку стэк работает по принципу LIFO (last in first out), значения стэка возвращаются в порядке от последнего к первому.

Таким образом функции c deferбудут вызываться в обратной последовательности от их объявления во внешней функции. На этот вопрос любят давать практические задачи.

func main() {
	fmt.Println("counting")
for i := 1; i < 4; i++ {
	defer fmt.Println(i)
}
	fmt.Println("done")
}counting
done
3
2
1
32
Q

Как передаются значения в функции, перед которыми указано ключевое слово defer?

A

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

func main() {
	nums := 1 << 5 // 32
defer fmt.Println(nums)

nums = nums >> 1 //16

fmt.Println("done") }

32

33
Q

Если в функции есть return, обязательно ли она вернет то, что указано в return?

A

Нет, можно использовать особенность defer, который запоминает состояние переменных, в зависимости от места, где написан

34
Q

В чем преимущество Go перед другими языками?

A

1) В отличие от других языков, которые зарождались в качестве академических экспериментов, в Go код спроектирован прагматично. Каждая из его возможностей и выбор синтаксиса разработаны с учетом удобства для программиста.
2) Go оптимизирован под конкурентность и отлично поддается масштабированию.
3) Go зачастую признается более удобочитаемым, чем его альтернативы, ввиду единого стандартного формата кода.
4) Автоматическая сборка мусора в нем заметно эффективнее, чем в Java или Python, так как выполняется параллельно с программой.

35
Q

Какую форму преобразования типов поддерживает Go? Преобразуйте целое число в число с плавающей запятой.

A

Go поддерживает явные преобразования типов, соответствуя требованиям строгой типизации. Go строго типизированный язык
i := 55 //int

j := 67.8 //float64

sum := i + int(j) //j преобразуется в int

36
Q

Что такое горутина? Как ее остановить?

A

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

Для остановки горутины можно использовать канал.

quit := make(chan struct{})
go func() {
    for {
        select {
        case
37
Q

Что такое замыкания функций? *дописать

A

Замыкание - это функция, которая ссылается к переменным вне ее тела. Функция имеет доступ к связанным переменным, а также может присваивать им значения; в этом смысле функция “связана” с этими переменными.
Функция имеет доступ к связанным переменным, а также может присваивать им значения; в этом смысле функция “связана” с этими переменными.

package main

import “fmt”

func adder() func(int) int {
  sum := 0
  return func(x int) int {
    sum += x
    return sum
  }
}
func main() {
  pos, neg := adder(), adder()
  for i := 0; i < 10; i++ {
    fmt.Println(
      pos(i),
      neg(-2*i),
    )
  }
}
38
Q

Что такое Указатель? написать ответ

A

Указатель — это местоположение в памяти, где хранится значение. Значение указателя — это адрес переменной.

39
Q

Алгоритмы. Дизайн паттерны. Написать

A
40
Q

Что такое руна

A

Руна - это числовой тип, аналогичный int32. Используется для представления Юникода.