【go语言】数组和切片

一、数组

1.1 什么是数组

       数组是一组数:数组需要是相同类型的数据的集合;数组是需要定义大小的;数组一旦定义了大小是不可以改变的。

1.2 数组的声明

  • 数组和其他变量定义没有什么区别,唯一的就是这个是一组数,需要给定一个大小,比如:[6] int,[12] string。
  • 数组是一个相同类型数据的有序集合,通过下标来取出对应的数据。
  • 数组有以下几个特点:
    • 长度必须是确定的,如果不确定,就不是数组,大小不可以改变
    • 元素必须是相同类型,不能是多个类型混合(any 也是类型,可以存放任意类型的数据)
    • 数组中元素的类型,可以是我们学的所有类型:int、string、float、bool、array、slice、map
    • 长度不一样,数组的类型就不一样
package main
import "fmt"func main() {// array数组定义,变量// 数组也是一个数据类型// 数组的定义:  [数组的大小size]变量的类型 ,// 我们定义了一组这个类型的数组集合,大小为size,最多可以保存size个数var arr1 [5]int// [0,0,0,0,0]// 给数组赋值,下标index,所有的数组下标都是从0开始的。arr1[0] = 100arr1[1] = 200arr1[2] = 300arr1[3] = 400arr1[4] = 500// 打印数组fmt.Println(arr1)// 取出数组中的某个元素fmt.Println(arr1[1])// 数组中的常用方法 len()获取数组的长度  cap() 获取数组的容量fmt.Println("数组的长度:", len(arr1))fmt.Println("数组的容量:", cap(arr1))// 修改数组的值,index 1 代表的第二个数据了arr1[1] = 10fmt.Println(arr1)fmt.Println(arr1[1])
}

1.3 初始化数组的几种方式

package main
import "fmt"// 数组的赋值初始化
func main() {// 在定义数组的时候就直接初始化var arr1 = [5]int{1, 2, 3, 4, 5}fmt.Println(arr1)// 快速初始化 :=arr2 := [5]int{1, 2, 3, 4, 5}fmt.Println(arr2)// 比较特殊的点// 数据如果来自用户,我不知道用户给我多少个数据,数组// ... 代表数组的长度// Go的编译器会自动根据数组的长度来给 ... 赋值,自动推导长度// 注意点:这里的数组不是无限长的,也是固定的大小,大小取决于数组元素个数。var arr3 = [...]int{1, 2, 3, 4, 5, 5, 6, 7, 8, 8, 8}fmt.Println(len(arr3))fmt.Println(arr3)// 数组默认值,我只想给其中的某几个index位置赋值。// {index:值}var arr4 [10]intarr4 = [10]int{1: 100, 5: 500}fmt.Println(arr4) // [0 100 0 0 0 500 0 0 0 0]}

1.4 遍历数组元素

package mainimport "fmt"/*
1、直接通过下标获取元素 arr[index]2、 0-len i++ 可以使用for循环来结合数组下标进行遍历3、for range:范围   (new)
*/
func main() {var arr1 = [5]int{1, 2, 3, 4, 5}fmt.Println(arr1[0])fmt.Println(arr1[1])fmt.Println(arr1[2])fmt.Println(arr1[3])fmt.Println(arr1[4])// 错误:index 5 out of bounds [0:5] 数组下标越界// 数组的长度只有5,你要取出6个元素,不可能取出//fmt.Println(arr1[5])fmt.Println("------------------")// 获取数组的长度  len()// 下标从0开始,不能<=for i := 0; i < len(arr1); i++ {fmt.Println(arr1[i])}fmt.Println("------------------")// goland 快捷方式 数组.for,未来循环数组、切片很多时候都使用for    range// for 下标,下标对应的值  range 目标数组切片// 就是将数组进行自动迭代。返回两个值 index、value// 注意点,如果只接收一个值,这个时候返回的是数组的下标// 注意点,如果只接收两个值,这个时候返回的是数组的下标和下标对应的值for _, value := range arr1 {fmt.Println(value)}}

1.5 数组是值类型

1.5.1 介绍

       在 go 语言中,数值是值类型。这意味着,当你将一个数组赋值给另一个数组时,go 会创建一个副本,而不是引用同一块内存。这是与引用类型(比如切片、slice、映射 map、通道 channel)的区别之一。

package mainimport "fmt"func main() {arr1 := [3]int{1, 2, 3} // 创建一个数组arr2 := arr1              // 将 arr1 赋值给 arr2arr2[0] = 100             // 修改 arr2 的第一个元素fmt.Println("arr1:", arr1) // 输出 arr1: [1 2 3]fmt.Println("arr2:", arr2) // 输出 arr2: [100 2 3]
}

       在上面的例子中,arr1arr2 是两个独立的数组,它们分别存储在不同的内存位置。当你修改 arr2 时,arr1 不会受到影响,因为它们是各自的副本。

1.5.2 为什么数组是值类型

  • 数组在 Go 语言中具有固定的大小(即数组的长度是类型的一部分),这导致数组的值会在赋值时进行拷贝。赋值操作会复制整个数组的内容,而不是传递数组的引用。

1.5.3 和切片的区别

       切片slice)是 Go 中的引用类型,它并不存储数据本身,而是引用底层数组。当你将一个切片赋值给另一个切片时,它们共享底层数组的相同数据。

package mainimport "fmt"func main() {slice1 := []int{1, 2, 3} // 创建一个切片slice2 := slice1          // slice2 引用 slice1 底层的数组slice2[0] = 100           // 修改 slice2 的第一个元素fmt.Println("slice1:", slice1) // 输出 slice1: [100 2 3]fmt.Println("slice2:", slice2) // 输出 slice2: [100 2 3]
}

       在这个例子中,修改 slice2 会影响 slice1,因为它们共享相同的底层数组。

  • 数组是值类型:赋值时会复制整个数组的内容。
  • 切片是引用类型:赋值时两个切片会共享相同的底层数组

1.6 数组的比较

       在 go 语言中,数组是可以直接进行比较的,但是有一些限制。具体来说,go 语言允许你比较两个数组是否相等,只要他们的类型、长度和元素的值都相同。比较时,会逐一比较数组的元素,如果所有的元素都相同,数组就视为相等。

1.6.1 允许比较

  • 类型要求一致:两个数组必须具有相同的类型(包括相同的元素类型和相同的长度)。
  • 逐个元素比较:Go 会逐个元素进行比较,如果数组的所有元素都相等,则认为这两个数组相等。
package mainimport "fmt"func main() {arr1 := [3]int{1, 2, 3}arr2 := [3]int{1, 2, 3}arr3 := [3]int{3, 2, 1}arr4 := [4]int{1, 2, 3, 4}fmt.Println(arr1 == arr2) // 输出: truefmt.Println(arr1 == arr3) // 输出: falsefmt.Println(arr1 == arr4) // 输出: 编译错误: 数组长度不同,不能比较
}
  1. arr1 == arr2 返回 true,因为它们的长度相同且每个元素的值都相同。
  2. arr1 == arr3 返回 false,因为数组的元素顺序不同。
  3. arr1 == arr4 会导致编译错误,因为这两个数组的长度不同,Go 不允许直接比较长度不同的数组。

1.6.2 不允许比较

  • 长度不同的数组:如上面的 arr1 和 arr4,它们的长度不同,因此无法进行比较。
  • 切片不能直接比较:切片是引用类型,不能直接用 == 进行比较。如果需要比较切片的内容,可以使用 reflect.DeepEqual 或者手动逐个比较切片元素。

        切片不能直接使用 == 比较,但你可以使用 reflect.DeepEqual 来比较切片内容。

package mainimport ("fmt""reflect"
)func main() {slice1 := []int{1, 2, 3}slice2 := []int{1, 2, 3}slice3 := []int{3, 2, 1}fmt.Println(reflect.DeepEqual(slice1, slice2)) // 输出: truefmt.Println(reflect.DeepEqual(slice1, slice3)) // 输出: false
}
  • 在 Go 语言中,数组是可以直接比较的,前提是数组的长度和元素类型必须相同。
  • 切片不能直接进行比较,如果需要比较两个切片的内容,可以使用 reflect.DeepEqual 函数。

二、切片

2.1 介绍

       在 go 语言中,切片是对数组的抽象。go 数组的长度是不可改变的,在特定场景中这样的集合就不太适用,go 中就提供了一种灵活、功能强悍的内置类型——切片(动态数组)。与数组相比,切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大

       切片是一种方便、灵活且强大的包装器,切片本身没有任何数据,他们只是对现有数组的引用。切片与数组相比,不需要设定长度,在 [ ] 中不用设定值,相对来说比较自由。

从概念来说,slice 像一个结构体,这个结构体包含了三种元素:

  • 指针:指向数组中 slice 指定的开始位置
  • 长度:即 slice 的长度
  • 最大长度:也就是 slice 开始位置到数组的最后位置的长度
package mainimport "fmt"// 定义切片
func main() {arr := [4]int{1, 2, 3, 4} // 定长fmt.Println(arr)var s1 []int // 变长,长度是可变的fmt.Println(s1)// 切片的空判断,初始的切片中,默认是 nilif s1 == nil {fmt.Println("切片是空的")}s2 := []int{1, 2, 3, 4} // 切片 变长fmt.Println(s2)fmt.Printf("%T,%T\n", arr, s2) // [4]int,[]intfmt.Println(s2[1])
}

2.2 切片的初始化

       在 go 语言中,切片(slice)可以通过几种不同的方式来初始化。这里总结了常见的几种方式:

2.2.1 使用 make 函数

       make 函数是 go 中用来创建切片的标准方式,可以指定切片的长度和容量。

slice := make([]int, 5)  // 创建一个长度为 5 的切片,初始值为零值 [0, 0, 0, 0, 0]

       可以指定切片的长度容量,如果只指定长度,容量默认为长度;如果指定了容量,切片的容量就会扩展到指定的大小。

slice := make([]int, 5, 10)  // 创建一个长度为 5,容量为 10 的切片

2.2.2 通过字面量初始化

       切片也可以通过字面量(literal)来初始化,直接给出一个初始值。这种方式不需要明确指定长度,Go 会根据提供的元素自动计算出长度。

slice := []int{1, 2, 3, 4, 5}  // 创建并初始化一个切片

2.2.3 通过 nil 初始化(默认值)

       当切片没有显式初始化时,它默认是 nil,它的长度和容量都是 0。

var slice []int  // 创建一个 nil 切片,长度和容量为 0
fmt.Println(slice == nil)  // 输出: true

2.2.4 通过数组创建切片

你也可以从一个数组中创建切片,利用数组的部分或全部元素。

arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4]  // 创建一个切片,包含 arr 数组的第二到第四个元素 [2, 3, 4]

       在这里,切片 slice 是从数组 arr 中的一部分创建的,切片的长度和容量是基于数组的子集来决定的。

2.2.5 通过 copy 函数拷贝切片

通过 copy 函数,你可以从一个已有的切片创建一个新的切片。

slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, len(slice1))
copy(slice2, slice1)  // 将 slice1 的内容拷贝到 slice2

2.2.6 通过扩容创建切片 

如果切片的容量不足以容纳新的元素,可以通过 append 函数来动态扩容切片。

slice := []int{1, 2}
slice = append(slice, 3, 4)  // 在原切片基础上追加元素
  • make 函数:可以创建指定长度和容量的切片。
  • 字面量初始化:直接使用 []type{} 创建并初始化切片。
  • nil 初始化:默认情况下,未显式初始化的切片为 nil,长度和容量为 0。
  • 从数组创建切片:可以通过数组的部分或全部元素创建切片。
  • copy 函数:可以从现有的切片复制内容到新的切片。
  • append 函数:可以通过动态扩展切片的容量来添加元素。

2.3 切片的数据访问

2.3.1 切片的基础数据访问

       切片的元素可以通过索引进行访问,索引从 0 开始,类似于数组。在切片中,访问超出范围的索引会引发 panic 错误。

package mainimport "fmt"func main() {// 初始化一个切片slice := []int{10, 20, 30, 40, 50}// 通过索引访问切片元素fmt.Println(slice[0])  // 输出: 10fmt.Println(slice[2])  // 输出: 30// 修改切片中的元素slice[1] = 100fmt.Println(slice)  // 输出: [10 100 30 40 50]// 访问越界的索引会导致 panic// fmt.Println(slice[10]) // 运行时错误: panic: runtime error: index out of range
}

2.3.2 切片的切片操作

Go 的切片支持通过 slice[low:high] 语法进行切片操作,生成一个新的切片。这个操作包括:

  • 低索引low):指定切片的起始位置(包括该位置),默认是 0。
  • 高索引high):指定切片的结束位置(不包括该位置)。
  • 容量:新切片的容量是原切片从 low 到末尾的部分。

[start, end]

  1. 如果只有 start 没有 end,就表示从 start 开始到结尾的所有数据
  2. 如果没有 start 有 end,表示从0到 end 之前的所有数据
  3. 如果有 start 没有 end,表示从 start 开始到结尾的所有数据
  4. 如果有 start 有 end,表示全部数据
package mainimport "fmt"func main() {// 初始化一个切片slice := []int{10, 20, 30, 40, 50}// 获取从索引 1 到 3(不包括 3)部分的切片subSlice := slice[1:3]  fmt.Println(subSlice)  // 输出: [20 30]// 如果不指定低索引,则默认从 0 开始subSlice2 := slice[:3]  fmt.Println(subSlice2)  // 输出: [10 20 30]// 如果不指定高索引,则默认到切片的末尾subSlice3 := slice[2:]  fmt.Println(subSlice3)  // 输出: [30 40 50]// 获取完整的切片fullSlice := slice[:]  fmt.Println(fullSlice)  // 输出: [10 20 30 40 50]
}

2.3.3 切片的容量和长度

  • 长度(Length)len(slice) 返回切片的长度,即当前切片中元素的个数。
  • 容量(Capacity)cap(slice) 返回切片的容量,即切片在当前数组中的总容量。容量通常是切片分配的底层数组大小。
package mainimport "fmt"func main() {slice := []int{10, 20, 30, 40, 50}fmt.Println("Length:", len(slice))  // 输出: Length: 5fmt.Println("Capacity:", cap(slice)) // 输出: Capacity: 5// 扩展切片slice = append(slice, 60)fmt.Println("New Length:", len(slice))  // 输出: New Length: 6fmt.Println("New Capacity:", cap(slice)) // 输出: New Capacity: 10
}

2.3.4 使用 append 添加元素

  append 是 Go 切片的一个内建函数,它用于向切片末尾添加元素,并且在需要时自动扩容。如果追加的数据超过切片的容量,Go 会创建一个新的底层数组并将原数据和新数据复制过去。

package mainimport "fmt"func main() {slice := []int{10, 20, 30}// 向切片添加单个元素slice = append(slice, 40)fmt.Println(slice)  // 输出: [10 20 30 40]// 向切片添加多个元素slice = append(slice, 50, 60)fmt.Println(slice)  // 输出: [10 20 30 40 50 60]// 向切片添加一个切片slice2 := []int{70, 80}slice = append(slice, slice2...)fmt.Println(slice)  // 输出: [10 20 30 40 50 60 70 80]
}

2.3.5 切片的复制 

       切片可以通过 copy 函数将一个切片的内容复制到另一个切片中。需要注意,copy 会复制源切片的元素到目标切片,但目标切片的长度不一定和源切片一样大,复制的元素数量会受到目标切片长度的限制。

package mainimport "fmt"func main() {slice1 := []int{10, 20, 30, 40, 50}slice2 := make([]int, 3)// 将 slice1 的前 3 个元素复制到 slice2copy(slice2, slice1)fmt.Println(slice2)  // 输出: [10 20 30]// 如果目标切片更大,剩余的元素保持零值slice3 := make([]int, 7)copy(slice3, slice1)fmt.Println(slice3)  // 输出: [10 20 30 40 50 0 0]
}

2.3.6 使用 for 进行遍历

package mainimport "fmt"func main() {s1 := make([]int, 0, 5)fmt.Println(s1)// 切片扩容,append()s1 = append(s1, 1, 2)fmt.Println(s1)// 问题:容量只有5个,那能放超过5个的吗? 可以,切片是会自动扩容的。s1 = append(s1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7)fmt.Println(s1)// 切片扩容之引入另一个切片。// new : 解构   slice.. ,解出这个切片中的所有元素。s2 := []int{100, 200, 300, 400}// slice = append(slice, anotherSlice...)// ... 可变参数 ...xxx// [...] 根据长度变化数组的大小定义// anotherSlice... , slice...解构,可以直接获取到slice中的所有元素// s2... = {100,200,300,400}s1 = append(s1, s2...)// 遍历切片for i := 0; i < len(s1); i++ {fmt.Println(s1[i])}for i := range s1 {fmt.Println(s1[i])}
}

2.4  切片的元素删除

       在 Go 语言中,切片本身不提供直接删除元素的方法,但是可以通过切片的切片操作和 append 函数来间接实现删除元素的功能。

2.4.1 删除切片中的元素

如果你需要删除切片中的某个元素(比如删除指定位置的元素),可以通过以下两种方法来完成:

  • 通过切片拼接:你可以使用 append 函数和切片的切片操作,将切片分为两部分,排除要删除的元素,然后重新拼接这两部分。
package mainimport "fmt"func main() {// 初始化一个切片slice := []int{10, 20, 30, 40, 50}// 打印原始切片fmt.Println("Original Slice:", slice)// 删除索引为 2 的元素(即元素 30)i := 2slice = append(slice[:i], slice[i+1:]...)// 打印修改后的切片fmt.Println("Modified Slice:", slice)
}

2.4.2 删除多个元素

       如果你需要删除多个元素,可以按照相同的方式,通过切片拼接来删除多个索引的元素。你只需进行多个切片操作。

package mainimport "fmt"func main() {// 初始化一个切片slice := []int{10, 20, 30, 40, 50}// 打印原始切片fmt.Println("Original Slice:", slice)// 删除索引为 1 和 3 的元素slice = append(slice[:1], slice[2:3]...)slice = append(slice[:2], slice[3:]...)// 打印修改后的切片fmt.Println("Modified Slice:", slice)
}

2.4.3 删除切片中的特定元素值

如果你要删除某个具体的元素值(而不是指定索引),可以遍历切片并删除所有匹配的元素。

package mainimport "fmt"func main() {// 初始化一个切片slice := []int{10, 20, 30, 40, 30, 50}// 打印原始切片fmt.Println("Original Slice:", slice)// 删除值为 30 的所有元素target := 30newSlice := []int{}for _, v := range slice {if v != target {newSlice = append(newSlice, v)}}// 打印修改后的切片fmt.Println("Modified Slice:", newSlice)
}
  • 删除指定索引的元素:通过切片操作和 append 来拼接删除元素前后的部分。
  • 删除多个元素:通过多次拼接来删除多个索引的元素。
  • 删除指定值的元素:遍历切片并将不匹配的元素添加到新的切片中。

2.5 切片的底层原理

2.5.1 为什么要理解切片的底层原理

       go 中的 slice 在函数参数传递的时候是值传递还是引用传递:值传递,效果上呈现出了引用的效果(不完全是)。

  1. 切片本身是引用类型: 切片包含三个部分:指向底层数组的指针、切片的长度和切片的容量。切片本身并不存储数据,而是通过指针指向底层的数组。因此,切片的元素存储在底层数组中,而切片本身是一个结构体,包含了这个数组的一个视图。

  2. 切片作为函数参数传递时

    • 当切片作为参数传递给函数时,传递的是 切片的引用,即切片指向底层数组的指针。这样,在函数内部对切片的修改会影响原始切片。
    • 然而,切片本身的结构(例如,切片的长度、容量)是可以在函数中修改的,但这不会改变传递到函数中的切片的原始引用。也就是说,如果函数修改了切片的长度或容量,这些变化不会影响原始切片的长度和容量。
package mainimport "fmt"func modifySlice(s []int) {// 修改切片的内容s[0] = 99
}func main() {slice := []int{1, 2, 3, 4}fmt.Println("Before:", slice)modifySlice(slice)fmt.Println("After:", slice)  // 由于切片是引用类型,内容会被修改
}
Before: [1 2 3 4]
After: [99 2 3 4]

但是,如果我们修改切片的长度,则情况会有所不同:

package mainimport "fmt"func modifySliceLength(s []int) {s = append(s, 5)  // 改变切片的长度fmt.Println("Inside function:", s)
}func main() {slice := []int{1, 2, 3}fmt.Println("Before:", slice)modifySliceLength(slice)fmt.Println("After:", slice)  // 原始切片的长度没有变化
}
Before: [1 2 3]
Inside function: [1 2 3 5]
After: [1 2 3]

       在这个例子中,modifySliceLength 函数通过 append 操作改变了切片的长度。虽然在函数内部,切片的长度和内容都发生了变化,但由于 append 操作可能导致切片重新分配底层数组,函数外部的原始切片 slice 的长度并没有改变。

  • 切片是引用类型,因此传递给函数时是 引用传递,修改切片的内容会影响原始切片。
  • 切片的 长度 和 容量 是在切片的结构体中存储的,函数内对它们的修改不会影响原始切片,除非通过重新赋值或 append 等操作导致切片重新分配新的底层数组。

2.5.2 切片的底层原理

       在 Go 语言中,切片(slice)是一个非常重要的类型,它提供了一种灵活的方式来操作数组的部分内容。切片本身是对数组的一个抽象,它具有一些非常重要的特性。为了理解切片的底层原理,我们需要了解切片的内部结构及其与底层数组的关系。

2.5.2.1 切片的结构

切片本质上是一个结构体,包含以下三个部分:

  1. 指向底层数组的指针(ptr: 切片内部有一个指针指向底层的数组。这使得切片可以动态地调整大小,而不需要重新分配数组。

  2. 切片的长度(len: 这是切片中元素的数量,表示当前切片中包含的元素个数。

  3. 切片的容量(cap: 切片的容量表示从切片的开始位置到底层数组的末尾可用的元素数量。容量决定了切片在不重新分配的情况下,最多可以容纳多少个元素。

2.5.2.2 切片与底层数组的关系

       切片是对底层数组的一部分的引用,它并不直接存储数据。切片通过指针指向底层数组的一段区间,切片的长度和容量是基于该区间确定的。

  • 切片的指针:切片指向底层数组的某个位置(不是从数组的开头开始,可能是中间的某个位置)。
  • 切片的长度:切片当前使用的元素数量。
  • 切片的容量:从切片的起始位置到底层数组末尾的元素数量。
2.5.2.3 切片的底层存储

       当切片是通过数组或其他切片创建时,它会直接指向底层数组,底层数组是存储实际数据的地方。切片的大小(即元素个数)是可以调整的,但它们共享同一个底层数组。

       例如,创建一个切片时,Go 会根据你提供的长度和容量分配底层数组。如果你通过切片扩展(例如使用 append)使得切片的容量不足,Go 会分配一个新的更大的数组,并将数据复制到新数组中。

2.5.2.4 切片的扩容

       当我们使用 append 函数向切片添加元素时,Go 会检查切片的容量。如果当前容量足够,append 会在原有的数组上直接增加元素;如果容量不够,Go 会分配一个新的更大的数组,复制原始数组的内容,并将新元素加入。

2.5.2.5 扩容策略

       Go 使用一个增长的策略来扩展切片的容量。通常情况下,当切片容量不足时,Go 会将容量加倍(或者增长 1.25 倍左右)。这种扩容策略使得 append 操作的平均时间复杂度为 O(1),即使在多次调用中,切片的扩容操作不会每次都需要重新分配和复制大量数据。

  • 切片是一个包含指向底层数组的指针、长度和容量的结构体。
  • 切片本身并不包含数据,而是对数组的一个视图,它提供了对底层数组的引用。
  • 切片的容量决定了它能够容纳的最大元素数量,超过容量时,Go 会扩容底层数组。
  • 切片是一个非常灵活且高效的数据结构,广泛用于Go的标准库和用户的应用程序中。
2.5.2.6 扩容的内存分析
  1. 每个切片引用了一个底层的数组
  2. 切片本身不存储任何数据,都是底层的数组来存储的,所以修改了切片也就是修改了这个数组中的数据
  3. 向切片中添加数据的时候,如果没有超过容量,直接添加,如果超过了这个容量,就会自动扩容,成倍的增加, copy
  4. 切片一旦扩容,就是重新指向一个新的底层数组。
package mainimport "fmt"// 切片扩容的内存分析
// 结论
// 1、每个切片引用了一个底层的数组
// 2、切片本身不存储任何数据,都是底层的数组来存储的,所以修改了切片也就是修改了这个数组中的数据
// 3、向切片中添加数据的时候,如果没有超过容量,直接添加,如果超过了这个容量,就会自动扩容,成倍的增加, copy
//   - 分析程序的原理
//   - 看源码
//
// 4、切片一旦扩容,就是重新指向一个新的底层数组。
func main() {// 1、cap 是每次成倍增加的// 2、只要容量扩容了,地址就会发生变化s1 := []int{1, 2, 3}fmt.Println(s1)fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:3,cap:3fmt.Printf("%p\n", s1)                          // 0xc000016108s1 = append(s1, 4, 5)fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:5,cap:6fmt.Printf("%p\n", s1)                          // 0xc000010390s1 = append(s1, 6, 7, 8)fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:8,cap:12fmt.Printf("%p\n", s1)                          // 0xc00005e060s1 = append(s1, 9, 10)fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:10,cap:12fmt.Printf("%p\n", s1)                          // 0xc00005e060s1 = append(s1, 11, 12, 13, 14)fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:14,cap:24fmt.Printf("%p\n", s1)                          // 0xc00010c000
}

2.6 深拷贝、浅拷贝

深拷贝:拷贝是数据的本身

  • 值类型的数据,默认都是深拷贝,array、int、float、string、bool、struct....

浅拷贝:拷贝是数据的地址,会导致多个变量指向同一块内存。

  • 引用类型的数据: slice、map
  • 因为切片是引用类的数据,直接拷贝的是这个地址

切片如何实现深拷贝??

package mainimport "fmt"// 切片实现深拷贝
func main() {// 将原来切片中的数据拷贝到新切片中s1 := []int{1, 2, 3, 4}s2 := make([]int, 0) // len:0 cap:0for i := 0; i < len(s1); i++ {s2 = append(s2, s1[i])}fmt.Println(s1)fmt.Println(s2)s1[0] = 100fmt.Println(s1)fmt.Println(s2)
}

2.7 函数中参数传递问题

按照数据的存储特点来分:

  • 值类型的数据:操作的是数据本身、int 、string、bool、float64、array...
  • 引用类型的数据:操作的是数据的地址 slice、map、channal....
package mainimport "fmt"func main() {arr1 := [4]int{1, 2, 3, 4}fmt.Println("arr1:", arr1)update(arr1)fmt.Println("end arr1:", arr1)s1 := []int{1, 2, 3, 4}fmt.Println("s1:", s1)update2(s1)fmt.Println("end s1:", s1)
}// 函数补充:在使用函数的时候,一定要特别注意参数问题,如果是值类型的,很多传递是无效的。
// 一些值传递的类型的参数,如果我们想通过函数来进行修改对应的值,这个时候就需要使用指针// 指针变量 -> 指向原来变量的地址// 数组是值类型的
func update(arr [4]int) {fmt.Println("--> arr:", arr)arr[0] = 100fmt.Println("--> end arr:", arr)
}// 切片是引用类型的
func update2(s []int) {fmt.Println("--> s:", s)s[0] = 100fmt.Println("--> end s:", s)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/68297.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

node.js 07.npm下包慢的问题与nrm的使用

一.npm下包慢 因为npm i 默认从npm官网服务器进行下包,但是npm官网服务器是海外服务器所以响应很慢. 于是我们通过npm下包的时候通常用淘宝镜像进行下包,下面是切换到淘宝镜像地址下包的操作. 二.nrm的使用 nrm是一个管理切换npm下包地址的工具,可以快速切换下包的地址. 安…

Windows10官方系统下载与安装保姆级教程【U盘-官方ISO直装】

Windows 10 官方系统安装/重装 制作启动盘的U盘微软官网下载Win10安装包创建启动盘U盘 安装Win10 本文采用U盘安装Windows10官方系统。 制作启动盘的U盘 微软官网下载Win10安装包 微软官网下载Win10安装包链接&#xff1a;https://www.microsoft.com/zh-cn/software-downloa…

[MySQL]MySQL数据库的介绍和库相关操作

目录 一、数据库介绍 1.什么是数据库 2.为什么使用数据库 3.数据库的操作运行逻辑 4.MySQL架构 5.SQL语句的分类 二、数据库的操作 1.数据库的连接 2.数据库的操作 创建数据库 查看数据库 显示数据库的创建语句 删除数据库 修改数据库 3.字符集和校验集 查看系…

亚博microros小车-原生ubuntu支持系列:13 激光雷达避障

一 背景知识 小车发了了数据包含激光雷达数据&#xff0c;类型是sensor_msgs/msg/LaserScan bohubohu-TM1701:~$ ros2 node info /YB_Car_Node /YB_Car_Node Subscribers: /beep: std_msgs/msg/UInt16 /cmd_vel: geometry_msgs/msg/Twist /servo_s1: std_msgs/…

机器学习 ---逻辑回归

逻辑回归是属于机器学习里面的监督学习&#xff0c;它是以回归的思想来解决分类问题的一种非常经典的二分类分类器。由于其训练后的参数有较强的可解释性&#xff0c;在诸多领域中&#xff0c;逻辑回归通常用作 baseline 模型&#xff0c;以方便后期更好的挖掘业务相关信息或提…

volatile之四类内存屏障指令 内存屏障 面试重点 底层源码

目录 volatile 两大特性 可见性 有序性 总结 什么是内存屏障 四个 CPU 指令 四大屏障 重排 重排的类型 为什么会有重排&#xff1f; 线程中的重排和可见性问题 如何防止重排引发的问题&#xff1f; 总结 happens-before 和 volatile 变量规则 内存屏障指令 写操作…

模拟电子技术-常用半导体器件

模拟电子技术-常用半导体器件 一、半导体基础知识二、PN结2.1 PN结简介2.2 PN结正向导电性2.2.1 正向电压2.2.2 反向电压2.2.3 PN结伏安特性 三、二极管3.1 二极管伏安特性曲线3.2 二极管参数和等效电路3.2.1 性能参数3.2.2 等效电路 3.3 二极管限幅和整流应用(正向特性)3.4 稳…

2024年博客之星主题创作|2024年蓝桥杯与数学建模年度总结与心得

引言 2024年&#xff0c;我在蓝桥杯编程竞赛和数学建模竞赛中投入了大量时间和精力&#xff0c;这两项活动不仅加深了我对算法、数据结构、数学建模方法的理解&#xff0c;还提升了我的解决实际问题的能力。从蓝桥杯的算法挑战到数学建模的复杂应用&#xff0c;我在这些竞赛中…

javascript-es6 (一)

作用域&#xff08;scope&#xff09; 规定了变量能够被访问的“范围”&#xff0c;离开了这个“范围”变量便不能被访问 局部作用域 函数作用域&#xff1a; 在函数内部声明的变量只能在函数内部被访问&#xff0c;外部无法直接访问 function getSum(){ //函数内部是函数作用…

使用eNSP配置GRE VPN实验

实验拓扑 实验需求 1.按照图示配置IP地址 2.在R1和R3上配置默认路由使公网区域互通 3.在R1和R3上配置GRE VPN&#xff0c;使两端私网能够互相访问&#xff0c;Tunne1口IP地址如图 4.在R1和R3上配置RIPv2来传递两端私网路由 GRE VPN配置方法&#xff1a; 发送端&#xff1a; …

Ansible自动化运维实战--script、unarchive和shell模块(6/8)

文章目录 一、script模块1.1、功能1.2、常用参数1.3、举例 二、unarchive模块2.1、功能2.2、常用参数2.3、举例 三、shell模块3.1、功能3.2、常用参数3.3、举例 一、script模块 1.1、功能 Ansible 的 script 模块允许你在远程主机上运行本地的脚本文件&#xff0c;其提供了一…

大数据Hadoop入门1

目录 相关资料 第一部分 1.课程内容大纲和学习目标 2.数据分析和企业数据分析方向 3.数据分析基本流程步骤 4.大数据时代 5.分布式和集群 6.Linux操作系统概述 7.VMware虚拟机概念与安装 8.centos操作系统的虚拟机导入 9.VMware虚拟机常规使用、快照 第二部分 1.课…

项目概述与规划 (I)

项目概述与规划 (I) JavaScript的学习已经接近尾声了&#xff0c;最后我们将通过一个项目来讲我们在JavaScript中学习到的所有都在这个项目中展现出来&#xff0c;这个项目的DEMO来自于Udemy中的课程&#xff0c;作者是Jonas Schmedtmann&#xff1b; 项目规划 项目步骤 用户…

项目集成RabbitMQ

文章目录 1.common-rabbitmq-starter1.创建common-rabbitmq-starter2.pom.xml3.自动配置1.RabbitMQAutoConfiguration.java2.spring.factories 2.测试使用1.创建common-rabbitmq-starter-demo2.目录结构3.pom.xml4.application.yml5.TestConfig.java 配置交换机和队列6.TestCon…

RK3568 adb使用

文章目录 一、adb介绍**ADB 主要功能****常用 ADB 命令****如何使用 ADB****总结** 二、Linux下载adb**方法 1&#xff1a;使用包管理器&#xff08;适用于 Ubuntu/Debian 系统&#xff09;****方法 2&#xff1a;通过 Snap 安装&#xff08;适用于支持 Snap 的系统&#xff09…

STM32项目分享:智能宠物喂食系统(升级版)

目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 PCB图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片&#xff1a; 哔哩哔哩视频链接&#xff1a; STM32智能宠物喂食系统(升级版) &#xff08;资…

软件测试 —— 性能测试(jmeter)

软件测试 —— 性能测试&#xff08;jmeter&#xff09; 什么是jmeter安装jmeterjmeter常用组件线程组取样器结果树 我们之前学习了接口测试工具Postman&#xff0c;我们今天要学习的是性能测试工具——jmeter 什么是jmeter Apache JMeter 是一个开源的性能测试工具&#xff…

电阻补偿OTA的噪声分析

上文&#xff08;补偿电阻对ota零极点的影响-CSDN博客&#xff09;分析了补偿电阻对五管OTA零极点的影响&#xff0c;该篇借分析电阻补偿OTA的噪声来串联复习下噪声章节的一些基础概念。 1.噪声分析 辅助定理 开始分析OTA噪声之前&#xff0c;先引入一个辅助定理&#xff08;R…

从CRUD到高级功能:EF Core在.NET Core中全面应用(四)

初识表达式树 表达式树&#xff1a;是一种可以描述代码结构的数据结构&#xff0c;它由一个节点组成&#xff0c;节点表示代码中的操作、方法调用或条件表达式等&#xff0c;它将代码中的表达式转换成一个树形结构&#xff0c;每个节点代表了代码中的操作例如&#xff0c;如果…

C语言初阶力扣刷题——349. 两个数组的交集【难度:简单】

1. 题目描述 力扣在线OJ题目 给定两个数组&#xff0c;编写一个函数来计算它们的交集。 示例&#xff1a; 输入&#xff1a;nums1 [1,2,2,1], nums2 [2,2] 输出&#xff1a;[2] 输入&#xff1a;nums1 [4,9,5], nums2 [9,4,9,8,4] 输出&#xff1a;[9,4] 2. 思路 直接暴力…