Вопросы к собеседованию Flashcards
Как реализовано ООП в go?
В go нет классической реализация ООП, так как он не объектно-ориентированный язык. При этом в go есть свои приближения к этой реализации.
Наследование.
Наследование (англ. inheritance) — концепция объектно-ориентированного программирования, согласно которой абстрактный тип данных может наследовать данные и функциональность некоторого существующего типа, способствуя повторному использованию компонентов программного обеспечения.
У нас есть структуры - это специальные типы, в которые мы можем включать другие типы, в том числе такие же структуры. При этом методы дочерних структур родительская структура также будет наследовать.
Инкапсуляция
Инкапсуляция – сокрытие поведения объекта внутри него. Объекту «водитель» не нужно знать, что происходит в объекте «машина», чтобы она ехала.
Инкапсуляция в go - это возможность задавать переменным, функциям и методам первую букву названия в верхнем или нижнем регистре. Соответственно нижний регистр будет значить, что переменная, функция или метод доступна только в рамках пакета. Тогда как верхний регистр даст доступ к переменной, функции или методу за рамками пакета.
Полиморфизм
Полиморфизм — это способность объекта использовать методы производного класса, который не существует на момент создания базового.
Способность функции работать с данными разных типов.
Полиморфизм в go реализован с помощью интерфейсов.
Основная идея заключается в том, что мы можем объявить интерфейсы (контракты на определённое поведение) для наших типов. При этом, для типов мы должны реализовать методы, удовлетворяющие этим интерфейсам. Таким образом, мы сможем работать со всем набором типов, у которых реализовали интерфейсы, как с единым интерфейсным типом.
Что такое пакеты в go?
Пакет - это механизм переиспользования кода, при котором go файлы помещаются в общую директорию. В начале каждого такого файла объявляется зарезервированное слово package, а после него прописывается имя пакета. В рамках пакета все функции и глобальные переменные, объявленные как в верхнем, так и в нижнем регистре, видят друг друга.
Что такое глобальная переменная?
Глобальная переменная - это переменная уровня пакета, то есть объявленная вне функции. Глобальная переменная также может быть доступна за рамками пакета, конечно только в том случае, если ее наименование начинается в верхнем регистре.
Что такое фигурные скобки с не объявленным оператором в go функции?
В go функции действительно можно объявить{} без оператора, ограничив область видимости куска кода в рамках этой функции.
func main() { solder := "Bill"
{ solder := "Den" fmt.Println(solder) } fmt.Println(solder) }
В go есть оператор switch case, можно ли выполнить несколько условий в одном объявленном операторе?
Такое возможно благодаря ключевому слову 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!
Что представляют из себя строки в go?
В Go строка в действительности является срезом (slice) байтов, доступным только для чтения.
type _string struct {
elements *byte // underlying bytes
len int // number of bytes
}
Как можно оперировать строками?
Строки в go можно складывать(конкатенировать). Для многих операций есть стандартные пакеты, к примеру strings, fmt. Кроме того, надо понимать, что все варианты конкатенации имеют свою производительность.
Что будет если сложить строки?
Ранее мы говорили о том что, строки - это массивы байт. Из этого следует, что при работе со строками (конкатенация и тд) мы будем получать новые строки
Как определить количество символов для строки? или Какие есть нюансы при итерации по строке?
Исходя из того же знания, что строка это массив байт, взяв базовую функцию 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, то есть обход будет по Юникод символам
Какие численные типы есть в go?
int/int8/int16/int32/int64;
uint/uint8/uint16/uint32/uint64;
float32/float64;
complex64/complex128;
rune(int32).
Чем отличается int от uint?
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.
Что такое обычный int и какие есть нюансы его реализации?
В зависимости от того какая архитектура платформы, на которой мы стартуем, компилятор преобразует int в int32 для 32 разрядной архитектуры и в int64 для 64 разрядной архитектуры.
Как преобразовать строку в int и наоборот? Можно ли сделать int(string) и string(int) соответственно?
Преобразование типов между int и string указанным синтаксисом невозможно. Для преобразования необходимо использовать функции из пакета strconv стандартной библиотеки go.
При этом для преобразования строк в/из int и int64 используются разные функции, strconv.Atoi и strconv.Itoa для int, strconv.ParseInt и strconv.FormatInt соответственно.
Сколько в памяти занимают реализации int32 и int64?
В предыдущем вопросе мы отвечали сколько места в памяти занимают эти типы. Таким образом с помощью 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.
Какой результат получим если разделить int на 0 и float на 0?
Это вопрос с подвохом. Деление 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
Что такое константы и можно ли их изменять?
Константы - это неизменяемые переменные, изменить константу нельзя.
Что такое iota?
iota - идентификатор, который позволяет создавать последовательные не типизированные целочисленные константы. Значением iota является индекс ConstSpec. Не смотря на то, что первым индексом является 0, значение первой константы можно задать отличным от 0, что в свою очередь повлияет на значения последующих констант.
Что такое слайс и чем он отличается от массива?
Cлайс - это структура go, которая включает в себя ссылку на базовый массив, а также две переменные len(length) и cap(capacity).
len это длина слайса - то количество элементов, которое в нём сейчас находится.
cap - это ёмкость слайса - то количество элементов, которые мы можем записать в слайс сверх len без его дальнейшего расширения.
Array - это последовательно выделенная область памяти. Частью типа array является его размер, который в том числе является не изменяемым.
type slice struct { array unsafe.Pointer len int cap int }
Как работает базовая функция append для go?
Функция принимает на вход слайс и переменное количество элементов для добавления в слайс. Append расширяет слайс за пределы его len, возвращая при этом новый слайс.
Если количество элементов, которые мы добавляем в слайс, не будет превышать cap, вернется новый слайс, который ссылается на тот же базовый массив, что и предыдущий слайс. Если количество добавляемых элементов превысит cap, то вернется новый слайс, базовым для которого будет новый массив.
func append(slice []Type, elems …Type) []Type
Какой размер массива выделяется под слайс при его расширении за рамки его емкости?
Если отвечать на вопрос поверхностно, то можно сказать, что базовый массив расширяется в два раза от нашей 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 } } }
Как реализована map(карта) go? *Ответ требует дополнительной информации
Сама map в go - это структура, реализующая операции хеширования. При этом, так же как и любую структуру, содержащую ссылки на области памяти,map необходимо инициализировать. map ссылается на такие элементы как bucket (в переводе на русский “ведра”). Каждый bucket содержит в себе:
8 экстра бит, с помощью которых осуществляется доступ до значений в этом bucket;
ссылку на следующий коллизионный bucket;
8 пар ключ-значение, уложенных в массив.
Почему нельзя брать ссылку на значение, хранящееся по ключу в map? *Дописать
map поддерживает процедуру эвакуации. Значения, хранящиеся в определённой ячейки памяти в текущий момент времени, в следующий момент времени уже могут там не храниться.
Что такое эвакуация, и в каком случае она будет происходить?
Эвакуация - это процесс когда map переносит свои значения из одной области памяти в другую. Это происходит из-за того что число значений в каждом отдельном bucket максимально равно 8.
В тот момент времени, когда среднее количество значений в bucket составляет 6.5, go понимает, что размер map не удовлетворяет необходимому. Начинается процесс расширения map.
Следует отметить, что сам процесс эвакуации может происходить некоторое время, на протяжение которого новые и старые данные будут связаны
Какие есть особенности синтаксиса получения и записи значений в map?
Получить значение из map, которую мы предварительно не аллоцировали нельзя, приложение упадет в панику.
Если ключ не найден в map в ответ мы получим дефолтное значение для типа значений map. То есть, для строки - это будет пустая строка, для int - 0 и так далее. Для того, чтобы точно понять, что в map действительно есть значение, хранящееся по переданному ключу, необходимо использовать специальный синтаксис. А именно, возвращать не только само значение, но и булевую переменную, которая показывает удалось-ли получить значение по ключу.