Slice the concept

Slice represents a variable-length sequence of elements of the same type. Slice is usually written as []T, where elements are of type T, somewhat similar to generics in Java, which accept variables of unknown types.

Slice is a lightweight data structure that can be used to access some or all of an array’s elements. The bottom layer of slice is an array. Slice has three properties: pointer, length, and capacity.

Pointers: are addresses to each element;

Length: the number of elements in slice;

Capacity: the size of the slice;

Slice the details

  • Go the built-inlen()andcap()The slice () function returns the length and size of slice. An underlying array can correspond to multiple slices, which can refer to any location in the array and overlap elements with each other.
  • It is worth noting that if a reference to Slice exceeds the capacity of the referenced object, cap(s), the program will go down; If the reference exceeds the length of the referenced object, len(s), then the final slice will be longer than the original slice.
  • Note that the string substring operation is similar to the slice operation of the byte slice ([]byte). Both operations are written as x[m:n], return a subsequence of the original byte, and are referenced the same way. Both operations consume constant time.
  • Unlike arrays, slices cannot be compared directly, so == cannot be used to compare whether two slices are the same. The standard library provides highly optimized functionsbytes.EqualTo compare two bytes of slice. But for other types of slice, we need to write our own function to compare.
func equal(x,y []string) bool{ if len(x) ! = len(y){ return false } for i := range x { if x[i] ! = y[i]{ return false } } return true }Copy the code
  • Why, you might wonder, can’t Slice be used directly= =Operator to compare. There are two reasons for this,

First, array elements are direct, whereas slice elements are indirect, and it is possible that slice can contain itself. There are ways to deal with this, but there is no simple, efficient, intuitive way.

Second, slice elements are not straightforward, and if the underlying array elements change, the same slice will have different elements at different times. Since the hash table only makes shallow copies of elements, this requires that the keys of the hash table remain constant throughout its life. Because Slice requires depth comparisons, slice cannot be used as a map key. For Pointers and channels of reference type, the operator == checks for reference equality, that is, whether they point to the same element. It might be useful to have a similar slice equality comparison facility, and to solve the slice as map key problem. Because slice covers so many cases, the safest approach is not to compare Slice directly.

  • The only thing slice allows is to compare to nil:
summmer := []int if summer == nil{/**... * /}Copy the code
  • Slice has a zero value of nil. A slice with a nil value has no corresponding underlying array and is 0 in length and capacity. But there are non-nil slice lengths and sizes that are 0, such as []int{} or make([]int,3)[3:]
  • For any type, if their value is nil, then it can be expressed like this:
 var s []int
 s = nil
 ​
 s = []int(nil)
 s = []int{}
Copy the code
  • This can be used if you need to check if a slice is emptylen(s) == 0, cannot be useds == nilBecause there iss ! = nilSlice may also be empty.
  • The built-in make function creates a slice with a specified element type, length, and size. Where the capacity parameter can be omitted, in which case the length of slice is equal to the capacity.
Make ([] T, len) make ([] T, len, cap) / / equivalent to make ([] T, cap) [: len]Copy the code

In the above code, make creates an unnamed array and returns a slice of it; This array is only accessible through slice. In the first line above, the returned slice references the entire array. In the second line, slice references only the first len of the array.

Append function

The Go language’s built-in function append() can be used to append elements to slice.

 var runes []rune
 for _,r := range "hello world"{
     runes = append(runes,r)
 } 
 fmt.Printf("%q\n,runes")//"['H' 'e' 'l' 'l' 'o' 'w' 'o' 'r' 'l' 'd']"
Copy the code

The append () function is important to understand how slice works, so we implement appendInt().

AppendInt (x []int, y int) []int{var z []int zlen := len(x)+1 if zlen <= cap(x){ Z = x[:zlen]}else{// Slice is out of space, assign it a new underlying array // To achieve linear complexity of allocation, Zcap := zcap if zcap < 2*len(x){zcap = 2*len(x)} z = make([]int,zlen,cap) copy(z,x)} z[len(x)] = y return z }Copy the code

Each appendInt call must check that Slice still has enough capacity to store new elements in the array. If the slice is large enough, it defines a new slice(still referencing the original underlying array), copies the new element to the new location, and returns the new slice. The input parameter Slice x and the function return value Slice Z have the same underlying array.

If slice is not large enough to hold the growing element, the appendInt function must create a new underlying array with enough capacity to hold the new element, copy the element from Slice X into the array, and append the new element Y to the array. The return value slice Z will reference a different underlying array from the input parameter Slice X.

The copy function is used to copy elements for two slices that have the same type. The first argument of copy is the target slice and the second argument is the source slice.

For efficiency reasons, the newly created array will be a bit larger than the minimum size needed to actually hold Slice X and slice Y. Reducing the number of memory allocations by doubling the size of the array each time it is expanded also ensures that the time it takes to append an element is fixed.

 func main(){
     var x,y []int
     for i := 0; i < 10; i++{
         y = appendInt(x,i)
         fmt.Printf("%d cap=%d\t%v\n",i,cap(y),y)
         x = y
     }
 }
Copy the code

Slice in place modification

Slice in-place modifications are a handy way to simplify code and reduce error opportunities in real development.

Package main import "FMT" //nonmpty returns a new slice whose elements are non-empty strings // During the function call, Func nonEmpty (strings []string) []string{I := 0 for _,s := range strings{if s! Func nonempty(strings []string) []string{I := 0 for _,s := range strings{if s! = ""{ string[i] = s i++ } } return string[:i] }Copy the code

A closer look at the code shows that the input [] String and the returned [] String have the same underlying array.

\