Welcome to new things

[Technical] [Electronic work] [Gadget] [Game] memo writing

Notes on things I got stuck on in Go language (slice, map, string)

I started Golang.

Golang, like C, has a simple syntax and data structure, but it also incorporates advanced concepts to increase productivity.

And for that reason, Golang has some slightly tricky syntax.

However, without knowing them, I made judgments based on my preconceptions and self-serving assumptions in other languages, and got into a bit of trouble.

Here, I would like to note down some of the things that I personally got into or misunderstood when I started Golang, as a reminder by topic.

Here is a note on what a "slice (slice), map (map), and string (string)" is.

Introduction.

Golang has slices, maps, and strings, which are like arrays, maps, and strings in other languages.

Because it is often seen in other languages, I used it casually, assuming that it was a common one.

However, Golang has the concept of pointers and entities.

As far as exchanging values with pointers, the entities are the same, so there is not much to worry about, However, if you pass a value as an assignment, you need to know the structure of a slice and a map and how they work.

What is a slice map string?

What is a slice map string? In a nutshell

Go language built-in variable-length array, map, and string library

It is.

Breaking down Golang's data structures to their smallest units, there are "basic types" such as integers and decimals, "arrays" and "structs," and slices, maps, and strings are not stand-alone data structures that exist in parallel with them, but are represented by a combination of these data structures.

That is similar to C. For example, if you want a variable-length array, map, or string in C, you will have to implement it yourself or use a library.

In today's programming, variable-length arrays, maps, and strings are used so frequently that it would be inconvenient for users to prepare them on their own in a library like the C language. Golang implements them using basic types and incorporates them as a language feature from the beginning, bringing variable-length arrays, maps, and strings down to the Golang syntax level.

slice

Slices are variable-length arrays and are based on arrays.

The entity of a slice is a structure, which contains the following data: "pointer to the array," "number of elements in the slice," and "length of the array.

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

An array of length cap is prepared as a storage area separately from the slice structure, and array is a pointer to the array.

To begin with, arrays in Golang are fixed-length and cannot be made longer later.

In order to make a fixed-length item look like a variable-length one, in a slice, Allocate a longer array in advance, and when the number of elements in the slice increases, increase the value of the number of elements in the slice and let the new elements use it.

If the number of elements exceeds the allocated length, a new, longer array is prepared and the values of the original array are copied into it,New slice (slice structure) pointing to a new arrayCreate a

Assignment of slices

As mentioned above, the entity of a slice is a structure. Therefore, the assignment from slice to slice is the same as a structure assignment, and the entire value of the structure is copied.

The fact that a slice is a structure can be seen by looking at the memory size of the slice.

It is 24 bytes in total: 8 bytes of the array pointer, 8 bytes of the array length int, and 8 bytes of the number of elements int in the slice.

An array, on the other hand, refers to the entire array, so the size of the array is the capacity of the array.

a := []int{0}
b := []int{0, 1, 2, 3}
c := [...]int{0}
d := [...]int{0, 1, 2, 3}
fmt.Println(unsafe.Sizeof(a))  // 24
fmt.Println(unsafe.Sizeof(b))  // 24
fmt.Println(unsafe.Sizeof(c))  // 8
fmt.Println(unsafe.Sizeof(d))  // 32

How far does the change in the value of an element in the slice extend?

The values of the elements of a slice are stored in an array, pointed to by the array pointer held by the slice.

When a slice is assigned to another slice, a copy of the slice structure occurs, so the array pointer value will be the same,point to the same arrayThis will be the case.

So, if you change the value of an element in the destination slice, the value in the source slice will also change.

a := []int{0}
b := a
b[0] = 1
fmt.Println(a) // 1
fmt.Println(b) // 1

However, the story gets complicated when array reassignment occurs, such as when values are added to a slice and do not fully fit into the array.

As mentioned earlier, when array reassignment occurs, the original slice remains intact and the new slice points to the new array area.

a := make([]string, 1, 2) // Allocation capacity2
a[0] = "A"
b := a
fmt.Println(a) // [A]
fmt.Println(b) // [A]

// a and b refer to the same sequence
b[0] = "B"
fmt.Println(a) // [B]
fmt.Println(b) // [B]

// If the number of elements in b increases, but no array reassignment occurs, then a and b point to the same array
b = append(b, "B")
fmt.Println(a) // [B]
fmt.Println(b) // [B B]

a[0] = "A"
fmt.Println(a) // [A]
fmt.Println(b) // [A B]

// When the number of elements in b increases and array reassignment occurs, a and b will point to different arrays
b = append(b, "B")
fmt.Println(a) // [A]
fmt.Println(b) // [A B B]

a[0] = "C"
fmt.Println(a) // [C]
fmt.Println(b) // [A B B]

Because changes in the destination slice extend to the source slice, and changes in the slice after append() extend to the slice before append(), it is easy to mistake the slice for a pointer, but at that time it just happened to point to the same array.

map

The mechanism of a map is more complicated than that of a slice, but like a map, the entity is a structure as shown below.

type hmap struct {
    count     int
    flags     uint8
    B         uint8
    noverflow uint16
    hash0     uint32
    buckets    unsafe.Pointer
    oldbuckets unsafe.Pointer
    nevacuate  uintptr
    extra *mapextra
}

However, in the case of slices, when a slice is created, the user receives the slice structure itself, but in the case of maps, when a map is created, the map structure is first created, but not the map structure,Pointer to map structureis passed.

The fact that the map is a pointer can be seen from the memory size of the map, which is 8 bytes of pointer.

It can also be seen from the fact that the return value of the make() implementation of the map is a pointer to the hmap structure.

a := map[int]int{0: 0}  // a is a pointer to the hmap structure
fmt.Println(unsafe.Sizeof(a))  // 8
func makemap(t *maptype, hint int, h *hmap) *hmap 

How far do map assignments and value changes extend?

Maps are internally pointers, so assigning from map to map copies the map pointers and they all point to the same map structure.

This means that copying a map or passing it to a function may change the value of the map elsewhere, but unlike slices, it is guaranteed that all maps will always have the same value.

So there is no need to have the map as a pointer, and no need to pass it as a pointer when passing it to a function,There is no problem to pass a map as a map to an assignment or function argument!

func testPointer(v *map[int]int) {
    fmt.Println((*v)[0])
}

func testObject(v map[int]int) {
    fmt.Println(v[0])
}

func main() {
    m := map[int]int{}
    m[0] = 1
    m[1] = 2

    p := &m  // You don't have to be a pointer.
    fmt.Println((*p)[0])

    o := m
    fmt.Println(o[0])

    testPointer(&m)  // You don't have to give it to them with a pointer.
    testObject(m)
}

Conditions for determining the same key as the one that can be put in the key of the map.

An interesting feature of maps is that they can use not only integers and strings as keys, but also decimals, booleans, arrays, and structs.

The == operator is used to determine if the same key is used to extract an element from a key, so if the == operator is defined for a type, it can be used for the key.

Based on those specifications, the following features are available

When an array is used as a key

The == operator compares array values, so even if the key to be retrieved is not the same as the array to which the key was set, if the values in the array match, they are the same key.

m := make(map[[2]int]string, 1)
keyA := [...]int{1, 2}
m[keyA] = "TEST"

keyB := [...]int{0, 0}
keyC := [...]int{1, 2}

v, ok := m[keyB]
fmt.Println(ok, v) // false

v, ok = m[keyC]
fmt.Println(ok, v) // true TEST

structure as a key.

The "==" operator in the structure compares values, just like arrays, so even if the key to be retrieved is not the same as the one in the structure when the key is set, it will be the same key if the values in the structure match.

type myStruct struct {
    x int
}

m := make(map[myStruct]string, 1)
keyA := myStruct{x: 1}
m[keyA] = "TEST"

keyB := myStruct{x: 0}
keyC := myStruct{x: 1}

v, ok := m[keyB]
fmt.Println(ok, v) // false

v, ok = m[keyC]
fmt.Println(ok, v) // true TEST

When the pointer is used as a key

The pointer "==" operator compares pointer values, so it is not surprising that the keys are the same if the pointer values are the same, that is, if the pointers point to the same object.

type myStruct struct {
    x int
}

m := make(map[*myStruct]string, 1)
keyA := myStruct{x: 1}
m[&keyA] = "TEST"

keyB := myStruct{x: 0}
keyC := myStruct{x: 1}

v, ok := m[&keyB]
fmt.Println(ok, v) // false

v, ok = m[&keyC]
fmt.Println(ok, v) // false

copyKeyA := keyA
v, ok = m[&copyKeyA]
fmt.Println(ok, v) // false

pointerKeyA := &keyA
v, ok = m[pointerKeyA]
fmt.Println(ok, v) // true TEST

When a slice map is used as a key

Since the slice "==" operator is not defined,Slices cannot be keyed

Similarly, since the map "==" operator is not defined.Maps cannot be keyed

About nil and make() in slice map

When a slice map is declared as a variable, the structure is created with zero elements, but the array area for storing values is not created.

Comparing that variable to nil yields true.

On the other hand, an empty slice map with zero elements or a slice map with make() will create an array area to store values and a structure with zero elements.

Comparing that variable to nil yields false.

Assigning nil to a variable in a slice map creates a new structure that can be used to free up space in an existing slice map, just as when declaring a variable, but does not create an array area to store values.

// slice
var s[]int
fmt.Println(len(s))  // 0
fmt.Println(s == nil)  // true

s = []int{}
fmt.Println(len(s))  // 0
fmt.Println(s == nil)  // false

s = nil
fmt.Println(len(s))  // 0
fmt.Println(s == nil)  // true

// map
var m map[int]int
fmt.Println(len(m))  // 0
fmt.Println(m == nil)  // true

m = map[int]int{}
fmt.Println(len(m))  // 0
fmt.Println(m == nil)  // false

m = nil
fmt.Println(len(m))  // 0
fmt.Println(m == nil)  // true

Text column (string)

A string is similar to a slice, storing a pointer to an array and the length of the array, such asStructure.

type stringStruct struct {
    str unsafe.Pointer
    len int
}

However, with the Go language specification,Cannot change values or add strings later.The following is a list of the most important factors that must be taken into consideration when designing a new product.

Since the string cannot be changed later, the structure only has the array pointer and array length information, not the number of elements, as is the case with slices, which are not needed.

Since strings are structures, the size of a string variable, regardless of the length of the string, is 8 bytes of the array pointer and 8 bytes of the array length int, for a total of 16 bytes.

a := "T"
b := "TEST"
fmt.Println(unsafe.Sizeof(a))  // 16
fmt.Println(unsafe.Sizeof(b))  // 16

Difference between String and Slice

Strings are similar to slices, but the major difference is that, as mentioned above, the language specification does not allow changing the contents of strings, and the "==" operator is defined to compare values and use them as keys in maps.

String Assignment and Comparison

String assignment, like slicing, is a copy of the string structure.

So no matter how long a string is assigned to another variable,16-byte copy of string structureonly.

And even if you assign more and more strings to other variables, the contents of all string variables point to the array allocated when the first string was created, andsameIt is.

For example, as shown below, if you forcefully change the contents of the original string "a," the substitution target "b" is also changed, indicating that a and b point to the same region.

a := fmt.Sprint("abc") // Set "a" to "abc" dynamically so that the value can be changed
fmt.Println(a) // "abc"

b := a
fmt.Println(b) // "abc"

// Let a be "ABC
data := (*[3]byte)(unsafe.Pointer((*reflect.StringHeader)(unsafe.Pointer(&a)).Data))
data[0] = byte(rune('A'))
data[1] = byte(rune('B'))
data[2] = byte(rune('C'))

fmt.Println(a) // "ABC"
fmt.Println(b) // "ABC"

That is, a string does not create a new string area when it is assigned to another variable, and the value initially set is not changed,STRING does not have to be a pointer.The string is still a string, and there is no problem with drawing it around between assignments or function arguments.

However, operations between strings create a new string, so a new string area is created.

a := "test"
b := a  // a and b refer to the same sequence
c := a + b // c refers to a new sequence different from a and b

Impressions, etc.

Golang is a modern and interesting language that, like C, is made up of simple syntax and data structures, but also combines elaborate features such as slices, maps, and strings in a practical means using them.

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com