Speaker A
В этом видео мы разберем слайсы в языках буквально на кусочки, и я постараюсь максимально простым и понятным языком рассказать вам, как они устроены по своей сути. Слайс — это просто тип данных, а именно последовательность с динамической длиной. Ближайшая аналогия из других языков программирования — например, динамические массивы или списки в других языках. Обычно принято опускать то, как они устроены внутри, но в Go, к сожалению, или частью так поступить не получится. В Go крайне важно понимать, как устроен слайс, и без этого понимания вы полностью не сможете писать хороший код и будете регулярно стрелять себе в ногу. К тому же слайсы очень любят на собеседованиях, и там без них вам уж точно не обойтись. Слайсы имеют очень простую структуру, и разобраться в них очень просто. Буквально к концу этого видео вы будете знать о них практически все. И, пожалуй, я заранее сделаю парочку оговорок. Во-первых, слайс на русский язык обычно переводят как срез, и в ходе своего повествования я периодически могу употреблять эти слова. То есть имейте в виду, что когда я говорю слайс или срез, я на самом деле имею в виду одно и то же. И последняя оговорка: здесь мы не будем рассматривать в подробностях синтаксис слайсов. Я уверен, что в этом вы прекрасно разберетесь и без меня, а здесь мы поговорим о их внутреннем устройстве и различных нюансах. Что ж, давайте теперь разберем план действия. Так уж вышло, что понятие слайса неразрывно связано с понятием массива, и без их четкого понимания мы не сможем двигаться дальше. Поэтому начнем мы именно с массивов. Но устроены они намного проще, чем сами слайсы, и поэтому здесь мы надолго задерживаться не будем. Далее, как я и обещал, мы разберем слайсы на винтики и в подробностях посмотрим, как они устроены внутри. Далее мы перейдем к функции append и в деталях разберемся, как происходит добавление элементов в слайс. И когда мы наконец будем понимать, как всё это работает, мы разберем различные полезные практики, а также подводные камни и опасности, которые у вас могут подстерегать при работе со слайсами. И я считаю, что когда мы изучаем что-то новое, очень полезно попробовать написать это своими руками. Поэтому для закрепления теоретической части мы напишем простенькую реализацию функции append. Ну что же, давайте начинать. Массив представляет из себя последовательность с фиксированной длиной и состоит из одного типа. Записывается он следующим образом: мы всегда обязательно указываем размер массива, и число элементов, и собственно значение этих элементов. И фиксированного размера массив редко используется в Go непосредственно, но в некоторых случаях они могут оказаться довольно удобны. Например, их очень удобно использовать для хранения каких-то последовательностей фиксированной длины. Примером такой последовательности может быть UID. Скорее всего, вы с ним уже знакомы, а если нет, то советую почитать в интернете. Мы всегда точно знаем размер UID — это 16 байт или 128 бит, и никогда из него ничего не удаляем и не добавляем. Поэтому массивы здесь выгодны и в плане производительности кода, и в плане его читаемости. То есть когда мы в коде видим массив, мы лучше понимаем, что с ним будет происходить далее, потому что круг возможных действий гораздо меньше, чем, например, со слайсами. В общем-то, это всё, что вам необходимо знать об устройстве массивов. И если они в Go используются редко, то со слайсами вы будете работать практически каждый день. Давайте теперь посмотрим и на их внутреннее устройство. Записываются они практически в точности так же, как и массивы, но с небольшими изменениями. Мы не указываем размер, что в общем-то логично, ведь мы уже говорили, что размер слайса не фиксирован, то есть он может меняться по ходу работы программы. И естественно, мы не можем его зафиксировать. Всё, что мы можем сделать, — это разве что аллоцировать память заранее, когда мы создаем этот слайс. Что такое аллокация памяти и зачем нам это нужно в случае слайса, мы посмотрим чуть позже, а пока давайте посмотрим на их внутреннее устройство. По сути, слайс — это некая структура, которая состоит из трех элементов. Первый и наиболее важный элемент — это ссылка на некий базовый массив. Именно здесь проявляется тесная связь слайса и массива. На самом деле элементы последовательности не хранятся непосредственно в слайсе, они находятся в этом самом базовом массиве, и слайс имеет только ссылку на него. Но слайс не обязательно использует этот массив целиком. Во-первых, эта ссылка указывает не просто на массив, а на первый элемент массива, который используется в слайсе. А вот последний используемый элемент определяется с помощью следующего параметра — это длина. Как видно из названия, это количество элементов, которые фактически используются в слайсе. И последний параметр — это capacity, или же емкость. Если говорить простым языком, то это количество элементов, которые влезают в базовый массив. Но естественно, с учетом того, что первый элемент слайса не обязательно совпадает с первым элементом массива. То есть, к примеру, если размер массива — это 10, но мы его используем, начиная с пятого элемента, то естественно туда влезет только пять элементов. То есть capacity в этом случае будет 5. Если вы сейчас мало что поняли, то это абсолютно нормально. Сейчас мы посмотрим на устройство слайса более наглядно, и всё станет понятнее. Допустим, у нас есть некий базовый массив, который выглядит следующим образом: у него есть имя, в его ячейках есть какие-то значения, и у них есть свои номера, то есть индексы. Допустим, мы хотим создать на его основе слайс и решили использовать ячейки с 4 по 7. Это будет выглядеть следующим образом. Во-первых, размер такого слайса будет равен трем, поскольку у нас используются ячейки с номерами 4, 5 и 6. Несмотря на то, что в нашей записи также указано число 7, эта ячейка не используется, потому что правая граница указывается не включительно. Далее мы видим, что емкость такого слайса будет равна 9, и на схеме это наглядно видно. То есть массив вмещает 9 элементов. Ну естественно, указатель у нас смотрит на элемент с номером 4, поскольку именно его мы определили как первый элемент слайса. А далее, на самом деле, массив может быть использован не только для одного слайса, а для любого их количества. К примеру, на этом же массиве мы можем определить второй слайс. Допустим, в нем будут находиться элементы с 6 по 9. В таком случае, как вы видели, его длина также будет равна трем, но емкость уже будет отличаться, поскольку мы начинаем с другого элемента, и в оставшуюся часть массива влезает уже не 9, а всего 7 элементов. Это также наглядно видно на схеме. А теперь давайте поговорим про добавление в слайс элементов. Для этого в Go уже есть встроенная функция, которая называется append. Но, как мы увидим чуть позже, можно написать даже собственную ее реализацию, и это не так уж сложно. Для того чтобы наглядно продемонстрировать работу этой функции, давайте возьмем вот такой вот простой слайс, в основе которого лежит базовый массив из шести элементов. Длина слайса равна трем, а емкость — 6, то есть нам еще есть куда расти. В данный момент слайс у нас выглядит следующим образом. Так-то вы могли заметить, что в элементах с номерами 3, 4 и 5 также есть какие-то значения. Они никак не относятся к текущему слайсу, а относятся только к базовому массиву. Таким образом, я хотел подчеркнуть тот факт, что в базовом массиве не всегда должны содержаться нулевые значения. И добавление в слайс на самом деле не добавляет какие-то новые элементы, а перезаписывает предыдущие. Сейчас мы все это наглядно увидим. Допустим, мы хотим добавить в слайс вот такой элемент. Что произойдет после вызова функции append? Первое, что мы естественным образом ожидаем, — это появление данного элемента в нашем слайсе. С точки зрения высокоуровневого использования слайса, именно это и произойдет. Но что же произойдет внутри? Во-первых, на единичку увеличится длина слайса, и во-вторых, последний элемент слайса с учетом его новой длины будет перезаписан, и в нем будет содержаться то значение, которое мы хотели добавить в слайс. И дальше, когда мы захотим добавлять новые элементы, будет происходить ровно то же самое. То есть, к примеру, мы вызвали append ещё раз.