Массивы и слайсы

Что такое слайс?

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

1
2
3
4
5
type slice struct {
array unsafe.Pointer
len int
cap int
}

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

Слайсом можно манипулировать с помощью различных операций и функций. Например, можно получать подслайсы с помощью оператора [:], добавлять элементы в конец слайса с помощью функции append, копировать элементы из одного слайса в другой с помощью функции copy, сортировать элементы слайса с помощью функции sort, и так далее.

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

Массив и слайс в golang - это структуры данных, которые могут хранить элементы одного типа. Однако, между ними есть несколько отличий:

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

Как работает append?

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

Например:

1
2
3
s := []int{1, 2, 3} // создаем слайс из трех элементов
s = append(s, 4, 5) // добавляем два элемента в конец слайса
fmt.Println(s) // выводит [1 2 3 4 5]

Функция append может также принимать другой слайс в качестве аргумента, если он заключен в оператор … Это позволяет объединять два слайса в один.

Например:

1
2
3
4
s1 := []int{1, 2, 3} // создаем первый слайс
s2 := []int{4, 5, 6} // создаем второй слайс
s3 := append(s1, s2...) // добавляем второй слайс в конец первого слайса
fmt.Println(s3) // выводит [1 2 3 4 5 6]

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

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

До какого размера можно увеличивать слайс?

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

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

Например:

1
2
3
4
5
6
7
8
9
10
s := make([]int, 0, 5) // создаем слайс с длиной 0 и вместимостью 5
fmt.Println(len(s), cap(s)) // выводит 0 5
s = append(s, 1, 2, 3, 4, 5) // добавляем 5 элементов в слайс
fmt.Println(len(s), cap(s)) // выводит 5 5
s = append(s, 6) // добавляем еще один элемент в слайс
fmt.Println(len(s), cap(s)) // выводит 6 10 - вместимость увеличилась вдвое
s = append(s, 7, 8, 9, 10) // добавляем еще 4 элемента в слайс
fmt.Println(len(s), cap(s)) // выводит 10 10
s = append(s, 11) // добавляем еще один элемент в слайс
fmt.Println(len(s), cap(s)) // выводит 11 20 - вместимость увеличилась вдвое

Берем от слайса слайс, куда будет указывать его указатель?

Если мы берем от слайса слайс, то его указатель будет указывать на тот же массив, что и указатель исходного слайса, но с другим смещением. Например, если мы имеем слайс s, который ссылается на массив [1, 2, 3, 4, 5], и мы берем от него подслайс s[1:3], то его указатель будет указывать на тот же массив, но с элемента 2. То есть, подслайс будет содержать элементы [2, 3] из исходного массива.

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

В чем разница между слайсом указателей и слайсом значений с точки зрения вызова функции?

Разница между слайсом указателей и слайсом значений с точки зрения вызова функции заключается в том, как они передаются и изменяются внутри функции.

Слайс указателей - это слайс, который содержит указатели на элементы другого типа, например:

1
var sp []*int // слайс указателей на int

Слайс значений - это слайс, который содержит элементы другого типа, например:

1
var sv []int // слайс значений int

Когда мы передаем слайс указателей в функцию, мы копируем только заголовок слайса, который содержит указатель на первый элемент, длину и вместимость.

Однако, сами элементы слайса, на которые указывают указатели, не копируются. Это означает, что если мы изменяем элементы слайса в функции, то это отразится на исходном слайсе, так как они ссылаются на одни и те же значения. Например:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
x := 1
y := 2
z := 3
sp := []*int{&x, &y, &z} // создаем слайс указателей
fmt.Println(*sp[0], *sp[1], *sp[2]) // выводит 1 2 3
modifySlicePointers(sp) // передаем слайс в функцию
fmt.Println(*sp[0], *sp[1], *sp[2]) // выводит 10 20 30
}

func modifySlicePointers(sp []*int) {
*sp[0] = 10 // изменяем значение, на которое указывает первый элемент слайса
*sp[1] = 20 // изменяем значение, на которое указывает второй элемент слайса
*sp[2] = 30 // изменяем значение, на которое указывает третий элемент слайса
}

Когда мы передаем слайс значений в функцию, мы также копируем только заголовок слайса, который содержит указатель на первый элемент, длину и вместимость. Однако, сами элементы слайса, на которые указывает указатель, также не копируются. Это означает, что если мы изменяем элементы слайса в функции, то это также отразится на исходном слайсе, так как они ссылаются на одни и те же значения. Например:

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
sv := []int{1, 2, 3} // создаем слайс значений
fmt.Println(sv[0], sv[1], sv[2]) // выводит 1 2 3
modifySliceValues(sv) // передаем слайс в функцию
fmt.Println(sv[0], sv[1], sv[2]) // выводит 10 20 30
}

func modifySliceValues(sv []int) {
sv[0] = 10 // изменяем значение первого элемента слайса
sv[1] = 20 // изменяем значение второго элемента слайса
sv[2] = 30 // изменяем значение третьего элемента слайса
}

Стоит быть внимательным при добавлении элементов в слайс в функции с помощью append, так как мы изменяем копию структуры слайса. В этом случае, слайс в функции будет иметь другие значния len, cap и, возможно, ссылки на массив. Это означает, что добавленные элементы не будут видны в исходном слайсе. Например:

1
2
3
4
5
6
7
8
9
10
func main() {
sv := []int{1, 2, 3} // создаем слайс значений
appendSliceValues(sv) // передаем слайс в функцию
fmt.Println(sv) // выводит [1 2 3]
}

func appendSliceValues(sv []int) {
sv = append(sv, 4) // добавляем элемент в слайс
fmt.Println(sv) // выводит [1 2 3 4]
}

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

1
2
3
4
5
6
7
8
9
10
func main() {
sv := []int{1, 2, 3} // создаем слайс значений
sv = appendSliceValues(sv) // передаем слайс в функцию и присваиваем его исходному слайсу
fmt.Println(sv) // выводит [1 2 3 4]
}

func appendSliceValues(sv []int) []int {
sv = append(sv, 4) // добавляем элемент в слайс
return sv // возвращаем слайс из функции
}

Какие есть ограничения при работе со слайсом?

При работе со слайсом есть некоторые ограничения, которые нужно учитывать:

  • Слайс не может содержать элементы разных типов, только одного. Для хранения элементов разных типов нужно использовать структуры, интерфейсы или пустые интерфейсы (interface{}).
  • Слайс не может быть сравнен с другим слайсом с помощью оператора ==, только с nil. Для сравнения двух слайсов нужно использовать цикл или функцию reflect.DeepEqual.
  • Слайс не может быть использован в качестве ключа мапы, так как он не является хешируемым типом. Для использования слайса в качестве ключа мапы нужно преобразовать его в строку или другой хешируемый тип.
  • Слайс не может быть константой, так как он является ссылочным типом. Для объявления слайса нужно использовать var, := или make.
  • Слайс не может быть безопасно передан в функцию или возвращен из функции, так как он ссылается на массив, который может быть изменен в другом месте. Для безопасной передачи или возврата слайса нужно копировать его элементы в новый слайс с помощью функции copy или среза [:].

С какой скоростью идет поиск в массиве и почему?

Самый простой алгоритм поиска в массиве - это линейный поиск, который перебирает все элементы массива по порядку, пока не найдет искомый элемент или не дойдет до конца массива.

Скорость линейного поиска пропорциональна длине массива, то есть чем больше элементов в массиве, тем дольше будет идти поиск. Сложность линейного поиска в худшем случае составляет O(n), где n - это количество элементов в массиве.

Какая есть функции для создания слайса с длиной отличной от нуля?

Для создания слайса с длиной отличной от нуля в golang, можно использовать одну из следующих функций:

Функция make, которая принимает тип слайса, длину и вместимость, и возвращает слайс с заданными параметрами. Например:

1
s := make([]int, 3, 5) // создает слайс из трех целых чисел с вместимостью пять

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

1
2
s := []int{} // создает пустой слайс
s = append(s, 1, 2, 3) // добавляет три элемента в слайс

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

1
s := []int{1, 2, 3} // создает слайс из трех целых чисел

Допустима ли конкуррентная работа со слайсом?

Такая работа может быть допустима, если соблюдены определенные условия и предосторожности:

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

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

  • Использовать мьютекс sync.Mutex или sync.RWMutex для защиты слайса от одновременного доступа.
  • Использовать атомарные операции (sync/atomic) для изменения отдельных элементов слайса.
  • Использовать каналы для передачи слайса между горутинами.

Вот вам и slices

Поделиться