Share This
Связаться со мной
Крути в низ
Categories
//Готовимся к интервью по Golang: массивы, слайсы и строки

Готовимся к интервью по Golang: массивы, слайсы и строки

Статья начинает серию материалов по подготовке к интервью на backend-разработчика на языке Go. В данном тексте рассматриваются особенности таких структур данных, как массивы, слайсы и строки, нюансы их использования и немного задач для самостоятельной тренировки.

gotovimsja k intervju po golang massivy slajsy i stroki 05fb6c9 - Готовимся к интервью по Golang: массивы, слайсы и строки

В статье я хотел бы рассмотреть массивы, слайсы и строки в Golang и их особенности. Я стараюсь приводить примеры вопросов и заданий, которые могут встретиться вам на собеседовании на должность backend-разработчика, где предполагается знание языка Go. Практически все это вы сможете найти в других источниках, но в статье я постарался собрать в одном месте и отсеять то, что, на мой взгляд, является второстепенным, чтобы уменьшить количество материала и обратить внимание читателя на более основные и важные моменты. Для более детального изучения вы сможете воспользоваться ссылками на дополнительные материалы, приведенные в статье.

Массивы (arrays)

Сразу хочу сказать, что вопросы по массивам встречаются на интервью редко. Однако их понимание необходимо, потому что они являются базой для слайсов и строк. Массив в Go не отличается по своей сути от массивов в общепринятом смысле – это структура данных, которая содержит от 0 до N элементов определенного (заданного) типа. Количество элементов массива указывается при его создании.

Особенности массива:

  • память под массив выделяется в процессе создания.
  • размер массива поменять невозможно.
  • два массива разной размерности, но с элементами одного типа – это два разных массива (разных типа). Следовательно, их невозможно сравнить между собой с помощью операторов == и !=
  • по умолчанию все элементы массива инициализируются нулевыми значениями заданного типа.

Примеры:

примеры объявления массива

         var nonInitedArray [5]int var initedArrayWithLen [5]int = [5]int{5, 4, 3, 2, 1} initedArrayWithoutLen := [...]int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}     

Ссылка на playground.

Пример задачи

         package main  import "fmt"  func foo(a [5]int) { 	a[3] = 10 }  func bar(a *[5]int) { 	a[3] = 10 }  func main() { 	a := [...]int{1, 2, 3, 4, 5}  	fmt.Printf("%#vn", a)  	foo(a) 	fmt.Printf("%#vn", a) // что выведет?  	bar(&a) 	fmt.Printf("%#vn", a) // что выведет? }      

Ссылка на playground.

Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека Go разработчика» Интересно, перейти к каналу

Срезы (slices)

Срез можно рассматривать как динамический массив. Это значит, что вы можете изменять его размер.

Срез представляет собой структуру, в которой содержится указатель на начало области памяти (массива), длина слайса (length) и объем области памяти (capacity)

В коде Golang slice определен как структура с указателем на массив, длиной и емкостью: https://github.com/golang/go/blob/master/src/runtime/slice.go#L15

Важной особенностью такого представления является то, что на одну и ту же область памяти с данными (массив) может ссылаться несколько слайсов. Например, как показано на рисунке:

gotovimsja k intervju po golang massivy slajsy i stroki 8e236db - Готовимся к интервью по Golang: массивы, слайсы и строки

Два среза (Drinks и Menu) указывают на перекрывающиеся области памяти

Такое может получиться в результате операции re-slicing’а. Для этого используется запись: newSlice = originalSlice[firstElementIndex:lastElementIndex]. Обратите внимание, что lasElementIndex не включается в новый слайс (т. е. в слайсе будут элементы от firstElementIndex до lastElementIndex-1 включительно): пример на playground.

Если не указывать firstElementIndex, то он будет равен первому элементу, если не указывать lastElementIndex, то он будет равен длине слайса: пример на playground.

Особенности среза:

  • нулевое значение слайса: nil и к нему можно применять функцию append для добавления элементов (пример на playground).
  • при создании с помощью make можно указать capacity (третьим аргументом).
  • особенности работы append: при достаточном capacity увеличивается length слайса, если места не хватает – происходит перевыделение памяти и копирование данных. Новый слайс указывает на новую область памяти с новой длиной (length) и обьемом (capacity). Обычно говорят, что capacity увеличивается в 2 раза (на 100%), но это верно пока количество элементов в слайсе менее 512. После этого увеличение размера плавно уменьшается до 25% (пример на playground). Логику работы append можно посмотреть в коде golang.
  • стоит учитывать, что слайс хотя и является структурой, но содержит внутри ссылку и поэтому при рейслайсинге или передаче слайса в функцию и изменении данных слайса в новом слайсе или внутри функции они будут изменены и в оригинальном слайсе, так как указывают на одну область памяти. Чтобы избежать такого поведения, можно воспользоваться функцией copy – она скопирует данные в новый слайс (пример на playground).
  • однако относительно предыдущего пункта стоит учитывать, что если функция append расширила область памяти (не хватило capacity и была выделена дополнительная память), то старый слайс будет указывать на старую область, а новый – на новую. Тогда изменения в одном из них не приведут к изменениям в другом (пример на playground). Но стоит помнить, что не всегда при append происходит перевыделение памяти.
  • слайсы (как и массивы) – одномерные. Для создания двумерного слайса нужно создать слайс слайсов (пример на playground).
  • cлайсы можно сравнивать только с nil. В остальных случаях можно использовать reflect.DeepEqual (пример на playground).

Примеры работы со срезами

  • Создать слайс можно разными способами (см. пример), при использование make можно указать capacity (обратите внимание на значения по умолчанию):

Примеры создания слайса

         a := []string{"Pizza", "Cheese", "Tea", "Water", "Milk", "Burger", "Salad", "Pasta"} b := []int{} // слайс из 0 элементов var c []int  // пусто слайс, значения nil по умолчанию d := make([]int, 5, 20) e := make([]int, 5)     

Ссылка на playground.

  • Для добавления элемента(-ов) в слайс используется функция append:

Пример использования append

         menu := []string{"Pizza", "Cheese", "Tea", "Water", "Milk", "Burger", "Salad", "Pasta"} addMenu := []string{"Toast", "Boiled Eggs", "Omlet"}  fmt.Printf("Menu: %vn", menu)  menu = append(menu, "Coffee") fmt.Printf("Menu: %vn", menu)  menu = append(menu, addMenu...) fmt.Printf("Menu: %vn", menu)     

Ссылка на playground.

  • И можно итерировать по слайсу с помощью range.

Пример итерирования по элементами слайса

         menu := []string{"Pizza", "Cheese", "Tea", "Water", "Milk", "Burger", "Salad", "Pasta"}  for idx, dishTitle := range menu { 	fmt.Printf("%d. %s, ", idx, dishTitle) }     

Ссылка на playground.

Примеры задач

Задачи на понимание внутреннего устройства слайса

         func bar(a []int) { 	for i := 0; i < len(a); i += 2 { 		a[i], a[i+1] = a[i+1], a[i] 	} }  func main() { 	a := []int{1, 2, 3, 4, 5, 6} 	fmt.Printf("a[1]=%dn", a[1])  	foo(a) 	fmt.Printf("a[1]=%dn", a[1]) // что выведет?  	bar(a) 	fmt.Printf("a=%vn", a) // печатает весь слайс, что здесь выведет? }     

Ссылка на playground.

Задачи на понимание особенностей работы append и reslicing

         func foo(a []int) { 	a = append(a, 7) 	a[1] = 7 }  func bar(a *[]int) { 	*a = append(*a, 7) }  func main() { 	a := []int{1, 2, 3, 4, 5, 6} 	fmt.Printf("a[1]=%dn", a[1])  	b := a[1:3] 	b[0] = 10 	fmt.Printf("1. a[1]=%dn", a[1]) // что выведет?  	b = append(b, a...) 	b[0] = 100 	fmt.Printf("2. a[1]=%dn", a[1]) // что выведет?  	foo(a) 	fmt.Printf("3. a[1]=%dn", a[1]) // что выведет?  	bar(&a) 	fmt.Printf("4. a=%vn", a) // что выведет? }      

Ссылка на playground.

Дополнительные материалы

  • A tour of Go: Slices
  • A Comprehensive Guide of Arrays and Slices in Golang (and their differences) (или перевод на habr)
  • SliceTricks

Строки

Строка представляет собой слайс байтов и является неизменяемой. Это значит, что вы не можете поменять отдельный байт в строке после ее объявления. Однако стоит сказать, что строка может быть представлена в различной кодировке и один символ не обязательно соответствует одному байту (это зависит от символа и используемой кодировки).

Особенности строк:

  • строка содержит неизменяемые байты, ее длина и сами байты не могут быть изменены после объявления.
  • доступ по индексу – это доступ к байту, а не к символу (так как символ может занимать более одного байта).
  • исходный код в Go использует кодировку UTF-8, поэтому строки обычно представлены в этой кодировке (если значения строк заданы в тексте программы).
  • rune – специальный тип в Go, который представляет символ в формате UTF-8.
  • для итерации по runes (рунам) можно использовать оператор range
  • для работы с UTF-8 можно использовать пакет unicode/utf8 из стандартной библиотеки.

Примеры работы со строками

Примеры работы со строкой

         func main() { 	s1 := "hello, world!" 	s2 := `Hello, "World"!` 	s3 := `Long string Next line` 	s4 := "Привет, Мир!"  	fmt.Println(s1) 	fmt.Println(s2) 	fmt.Println(s3)  	for idx, ch := range s4 { 		fmt.Printf("%d=%c ", idx, ch) 	} 	fmt.Println()  }      

Ссылка на playground.

Примеры работы с рунами с помощью unicode/utf8

         func main() { 	s := "Привет, Мир!"  	cnt := utf8.RuneCountInString(s) 	runeIdx := 0 	for i := 0; i < cnt; i++ { 		r, siz := utf8.DecodeRuneInString(s[runeIdx:]) 		fmt.Printf("%c", r) 		runeIdx += siz 	} }     

Ссылка на playground.

Примеры задач

Пример задачи на строки

         func main() { 	s1 := "Hello, World!" 	s2 := "Привет, Мир!"  	// Что выведет? 	for i := 0; i < len(s1); i++ { 		fmt.Printf("%c", s1[i]) 	} 	fmt.Println()  	// Что выведет? 	for i := 0; i < len(s2); i++ { 		fmt.Printf("%c", s2[i]) 	} 	fmt.Println()  	// Что выведет? 	/*s1[len(s1)-1] = '.' 	fmt.Println(s1)*/  	// Что выведет? 	s1 = s1[0:5] 	fmt.Println(s1)  	// Что выведет? 	s2 = s2[0:6] 	fmt.Println(s2) }     

Пример на playground.

Дополнительные материалы

  • Strings, bytes, runes and characters in Go
  • Go Walkthrough: bytes + strings packages (или перевод на habr)

***

Материалы по теме

  • 📜 Шпаргалка по Go: slices, maps, channels
  • 🏃 Горутины: что такое и как работают
  • 🏎 К чему приводит обилие конкурентности в кодовой базе Go?

  • 0 views
  • 0 Comment

Leave a Reply

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

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

Связаться со мной
Close