Info
Content

Part 2: Working with Data Structures

CHAPTER 6: Slicing and Dicing Using Arrays and Slices

В Go, массив (array) это пронумерованная последовательность элементов одного типа указанной длины
После создания массив не может изменить длину

Для описания массива используется следующий синтаксис

var array_name [size_of_array]data_type
? cat main.go
package main

import "fmt"

func main() {
    var nums [5]int
    fmt.Println(nums)
}
? /usr/local/go/bin/go run main.go
[0 0 0 0 0]

Массив содержит точно указанное кол-во элементов (в примере выше указано 5, значит 6 элементов в этот конкретный массив не влезет)
Счет элементов в массиве начинается с 0, то есть первый элемент в примере выше доступен по индексу 0, а последний по индексу 4

? cat main.go
package main

import "fmt"

func main() {
    var nums [5]int = [5]int{1, 2, 3, 4, 5}
    fmt.Println(nums)
    fmt.Println(nums[0])
    fmt.Println(nums[4])
}
? /usr/local/go/bin/go run main.go
[1 2 3 4 5]
1
5

Можно сразу инициализировать его значениями

nums := [5]int{1, 2, 3, 4, 5}

При такой инициализации можно опустить длину массива поставив вместо нее многоточие

? cat main.go
package main

import "fmt"

func main() {
    nums := [...]int{1, 2, 3, 4, 5}
    fmt.Println(nums)
    fmt.Println(nums[0])
    fmt.Println(nums[4])
}
? /usr/local/go/bin/go run main.go
[1 2 3 4 5]
1
5

Получить длину массива можно с помощью функции len()

    nums := [...]int{1, 2, 3, 4, 5}
    fmt.Println(nums)
    fmt.Println(len(nums))
? /usr/local/go/bin/go run main.go
[1 2 3 4 5]
5

Показанные выше массивы - одномерные
Далее рассмотрим многомерные массивы

Двухмерный массив:

? cat main.go
package main

import "fmt"
import "strconv"

func main() {
    var table [5][6]string
    for row := 0; row < 5; row++ {
        for column := 0; column < 6; column++ {
            table[row][column] = strconv.Itoa(row) + "," + strconv.Itoa(column)
        }
    }
    for row := 0; row < 5; row++ {
        fmt.Println(table[row])
    }
}
? /usr/local/go/bin/go run main.go
[0,0 0,1 0,2 0,3 0,4 0,5]
[1,0 1,1 1,2 1,3 1,4 1,5]
[2,0 2,1 2,2 2,3 2,4 2,5]
[3,0 3,1 3,2 3,3 3,4 3,5]
[4,0 4,1 4,2 4,3 4,4 4,5]

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

Трехмерный массив:

? cat main.go
package main

import "fmt"
import "strconv"

func main() {
    var cube [4][3][3]string
    for row := 0; row < 4; row++ {
        for column := 0; column < 3; column++ {
            for depth := 0; depth < 3; depth++ {
                cube[row][column][depth] = strconv.Itoa(row) +
                    strconv.Itoa(column) + strconv.Itoa(depth)
            }
        }
    }
    for row := 0; row < 4; row++ {
        for column := 0; column < 3; column++ {
            fmt.Println(cube[row][column])
        }
        fmt.Println()
    }
}
? /usr/local/go/bin/go run main.go
[000 001 002]
[010 011 012]
[020 021 022]

[100 101 102]
[110 111 112]
[120 121 122]

[200 201 202]
[210 211 212]
[220 221 222]

[300 301 302]
[310 311 312]
[320 321 322]

Screenshot_2021_02_02-12_49_03-2022-12-19-at-03-3d-array.png


Слайсы это тоже самое что и массивы, но в отличие от массивов они могут изменять свой размер

Создать слайс можно с помощью функции make()

? cat main.go
package main

import "fmt"

func main() {
    s := make([]int, 5)
    fmt.Println(s)
}
? /usr/local/go/bin/go run main.go
[0 0 0 0 0]

Очень похоже на массив
Но технически слайс это вид (view) на нижележащий массив

У слайса есть хедер который содержит три поля:

  • ptr - указатель на область памяти нижележащего массива
  • len - длина слайса
  • cap - (capacity) максимальный размер массива

Screenshot_2021_02_02-12_49_03-2022-12-19-at-0slice-header.png

Функции len() и cap() покажут длину и вместительность слайса

? cat main.go
package main

import "fmt"

func main() {
    s := make([]int, 5)
    fmt.Println(s)
    fmt.Println(len(s))
    fmt.Println(cap(s))
}
? /usr/local/go/bin/go run main.go
[0 0 0 0 0]
5
5

В примере выше у нас кол-во элементов и вместительность равны
Но можно явно задать эти значения при создании слайса
Нужно передать в функцию make() нужные аргументы

s := make([]int, 5, 10)
? /usr/local/go/bin/go run main.go
[0 0 0 0 0]
5
10
? cat main.go
package main

import "fmt"
import "reflect"

func main() {
    s := make([]int, 5, 10)
    a := []int{1, 2, 3}
    a2 := [3]int{1, 2, 3}
    fmt.Println(reflect.TypeOf(s), len(s), cap(s))
    fmt.Println(reflect.TypeOf(a), len(a), cap(a))
    fmt.Println(reflect.TypeOf(a2), len(a2), cap(a2))
}
? /usr/local/go/bin/go run main.go
[]int 5 10
[]int 3 3
[3]int 3 3

Добавить элементы в слайс можно с помощью функции append()

? cat -n main.go
     1	package main
     2
     3	import "fmt"
     4	import "reflect"
     5
     6	func main() {
     7	    s := make([]int, 5, 10)
     8	    a := []int{1, 2, 3}
     9	    a2 := [3]int{1, 2, 3}
    10	    a3 := [...]int{1, 2, 3}
    11	    fmt.Println(reflect.TypeOf(s), len(s), cap(s))
    12	    fmt.Println(reflect.TypeOf(a), len(a), cap(a))
    13	    fmt.Println(reflect.TypeOf(a2), len(a2), cap(a2))
    14	    fmt.Println(reflect.TypeOf(a3), len(a3), cap(a3))
    15	    fmt.Println()
    16	    s = append(s, 2, 3, 4)
    17	    a = append(a, 2, 3, 4)
    18	    a2 = append(a2, 2, 3, 4)
    19	    a3 = append(a3, 2, 3, 4)
    20	    fmt.Println(reflect.TypeOf(s), len(s), cap(s))
    21	    fmt.Println(reflect.TypeOf(a), len(a), cap(a))
    22	    fmt.Println(reflect.TypeOf(a2), len(a2), cap(a2))
    23	    fmt.Println(reflect.TypeOf(a3), len(a3), cap(a3))
    24	}
? /usr/local/go/bin/go run main.go
# command-line-arguments
./main.go:18:16: first argument to append must be slice; have [3]int
./main.go:19:16: first argument to append must be slice; have [3]int

В примере выше видно что a2 и a3 не являются слайсами
Потому что многоточие в квадратных скобках подменяют собой кол-во элементов из фигурных скобок
А когда у массива в квадратных скобках пусто - это слайс

Корректный пример работы функции append():

? cat main.go
package main

import "fmt"
import "reflect"

func main() {
    s := make([]int, 2, 5)
    fmt.Println("type, len, cap:", reflect.TypeOf(s), len(s), cap(s))
    fmt.Println("0 item mem addr:", &s[0])
    fmt.Println(s)
    fmt.Println()

    s = append(s, 2, 3, 4)
    fmt.Println("type, len, cap:", reflect.TypeOf(s), len(s), cap(s))
    fmt.Println("0 item mem addr:", &s[0])
    fmt.Println(s)
    fmt.Println()

    s = append(s, 2, 3, 4)
    fmt.Println("type, len, cap:", reflect.TypeOf(s), len(s), cap(s))
    fmt.Println("0 item mem addr:", &s[0])
    fmt.Println(s)
}
? /usr/local/go/bin/go run main.go
type, len, cap: []int 2 5
0 item mem addr: 0xc000018180
[0 0]

type, len, cap: []int 5 5
0 item mem addr: 0xc000018180
[0 0 2 3 4]

type, len, cap: []int 8 10
0 item mem addr: 0xc00001e050
[0 0 2 3 4 2 3 4]

Как видим при превышении вместимости слайса, его вместимость увеличилась
Это произошло при помощи копирования старого нижележащего массива в новый с большей емкостью (то что нижележащий массив изменился видно по адресу памяти первого элемента слайса)

Заранее указать большую вместимость слайса может быть нужно если ты знаешь что придется добавить какое-то количество значений в слайс и хочешь избежать оверхеда на пересоздание нижележащего массива

Screenshot_2021_02_02-12_49_03-2022-12-19-at-04tg.png

Замеры:

(yc-test:argocd) vandud@macbook: golang-study ? cat main.go
package main

import "fmt"
import "time"

func main() {
    tstart := time.Now().Unix()
    s := make([]int, 1)
    prevcap := 1
    for i := 0; i < 500000000; i++ {
        s = append(s, i)
        if cap(s) != prevcap {
            prevcap = cap(s)
            fmt.Println(prevcap)
        }
    }
    tend := time.Now().Unix()
    fmt.Println(tend - tstart)
}
? /usr/local/go/bin/go run main.go
2
4
8
16
32
64
128
256
512
1024
1280
1696
2304
...
376559616
470700032
588375040
32

То есть работа такой программы заняла 32 секунды

А если мы в функцию make() заранее зададим вместимость

s := make([]int, 1, 500000000)

То получим x2 по скорости

? /usr/local/go/bin/go run main.go
500000000
625000448
15

CHAPTER 7: Defining the Blueprints of Your Data Using Structs

CHAPTER 8: Establishing Relationships Using Maps

CHAPTER 9: Encoding and Decoding Data Using JSON

CHAPTER 10: Defining Method Signatures Using Interfaces

No Comments
Back to top