1. The introduction

Slicing in the GO language provides a convenient and efficient way to process typed data sequences. This article focuses on slice data structure, transfer in functions and methods, slice length and capacity, slice copy.

2. Definition of slice

Slice is a data structure that describes a contiguous piece of array space separated from the slice variable itself. Slicing is not an array, but rather describes a contiguous array region

A slice is a data structure describing a contiguous section of an array stored separately from the slice variable itself. A slice is not an array. A slice describes a piece of an array.

Slicing consists of a three-field data structure that contains the metadata that the Go language needs to manipulate the underlying array. As shown in figure 1

The relevant data structure is as follows. The three fields are the pointer to the underlying array, the number of elements accessed by the slice (i.e. length), and the number of elements allowed to grow by the slice (i.e. capacity).

type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}
Copy the code

3. Slice passing in the function

The slice itself is a three-element data structure, and the slice passed in the function is a copy of the sliceHeader. Case1: The function passes slices without changing the length of slices

func AddOneToEachElement(slice []byte) {
    for i := range slice {
        slice[i]++
    }
}
func main(a) {
    buffer := [100]int{0}
    slice := buffer[10:20]
    for i := 0; i < len(slice); i++ {
        slice[i] = byte(i)
    }
    fmt.Println("before", slice)
    AddOneToEachElement(slice)
    fmt.Println("after", slice)
}
Copy the code

The following output is displayed:

before [0 1 2 3 4 5 6 7 8 9]
after [1 2 3 4 5 6 7 8 9 10]
Copy the code

Even though the slice header is passed to the function by value, the original slice data structure and the copy of the slice data structure passed to the function describe the same piece of data space. So when the result of the function returns, the modified data is also visible in the original slice. The underlying data structure is shown in Figure 2 below:

Case2: Changes the length of the pass slice in the function

func SubtractOneFromLength(slice []byte) []byte {
    slice = slice[1 : len(slice)- 1]
    slice[0] = 2
    return slice
}

func main(a) {
    buffer := [256]byte{0}
    slice := buffer[10:20]
    for i := 0; i < len(slice); i++ {
        slice[i] = byte(i)
    }
    fmt.Println("Before: len(slice) =".len(slice))
    newSlice := SubtractOneFromLength(slice)
    fmt.Println("After: len(slice) =".len(slice))
    fmt.Println("After: len(newSlice) =".len(newSlice))
    fmt.Printf("After slice %+v\n", slice)
    fmt.Printf("After newSlice %+v\n", newSlice)
}
Copy the code

The following output is displayed:

Before: len(slice) = 10
After:  len(slice) = 10
After:  len(newSlice) = 8
After slice [0 2 2 3 4 5 6 7 8 9]
After newSlice [2 2 3 4 5 6 7 8]
Copy the code

As we can see, the contents of a slice variable can be changed in a function, but the sliceHeader cannot. The length stored in the SliceHeader cannot be changed by a function. The SliceHeader passed to the function is a copy of the original slice, and if we want to modify the contents of the SliceHeader, we must include this slice in the return value.

4. Section transfer in the method

We know that methods in the GO language can accept values or Pointers. So what happens when you pass a pointer to a slice or you slice the head. Here is an example of case1: passing the slice pointer

type path []byte

func (p *path) TruncateAtFinalSlash(a) {
    i := bytes.LastIndex(*p, []byte("/"))
    if i >= 0 {
        *p = (*p)[0:i]
    }
}

func main(a) {
    pathName := path("/usr/bin/tso") // Conversion from string to path.
    pathName.TruncateAtFinalSlash()
    fmt.Printf("%s\n", pathName)
}
Copy the code

The following output is displayed:

/usr/bin
Copy the code

As can be seen from the result of the run, pass a pointer to sliceHeader, using this method to achieve the purpose of modifying the sliceheader, slice the content described in the method case2: pass the value

type path []byte

func (p path) ToUpper(a) {
    for i, b := range p {
        if 'a' <= b && b <= 'z' {
            p[i] = b + 'A' - 'a'}}}func main(a) {
    pathName := path("/usr/bin/tso")
    pathName.ToUpper()
    fmt.Printf("%s\n", pathName)
}
Copy the code

The following output is displayed:

/USR/BIN/TSO
Copy the code

This method accepts values and passes the Slice header as a copy of the original slice variable. Without destroying the underlying pointing content of the original slice, the two slice heads point to the same memory region. Therefore, the original section can also see the modified content

5. Slice capacity

Based on the understanding of the slice head structure, the third variable of the slice is the capacity of the slice, which describes the maximum element length that the current slice can contain. When the slice capacity does not change, multiple copies of the slice head share the same memory region at the bottom. When the size of the slice changes, the new underlying array is separated from the original array.

6. Slice growth

One advantage of using slices over arrays is that you can increase the size of slices as needed, and the built-in Append function of the Go language handles the internal logic for increasing the length. If the slice capacity is less than 1024, the capacity is increased by multiple. If it is greater than 1024, the required capacity is grown at a 25% step

7. Refer to reading

  • go action
  • slice.go
  • blog.golang.org/slices
  • draveness.me/golang/