【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]
}
在上面的例子中,arr1
和 arr2
是两个独立的数组,它们分别存储在不同的内存位置。当你修改 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) // 输出: 编译错误: 数组长度不同,不能比较
}
arr1 == arr2
返回true
,因为它们的长度相同且每个元素的值都相同。arr1 == arr3
返回false
,因为数组的元素顺序不同。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]
- 如果只有 start 没有 end,就表示从 start 开始到结尾的所有数据
- 如果没有 start 有 end,表示从0到 end 之前的所有数据
- 如果有 start 没有 end,表示从 start 开始到结尾的所有数据
- 如果有 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 在函数参数传递的时候是值传递还是引用传递:值传递,效果上呈现出了引用的效果(不完全是)。
-
切片本身是引用类型: 切片包含三个部分:指向底层数组的指针、切片的长度和切片的容量。切片本身并不存储数据,而是通过指针指向底层的数组。因此,切片的元素存储在底层数组中,而切片本身是一个结构体,包含了这个数组的一个视图。
-
切片作为函数参数传递时:
- 当切片作为参数传递给函数时,传递的是 切片的引用,即切片指向底层数组的指针。这样,在函数内部对切片的修改会影响原始切片。
- 然而,切片本身的结构(例如,切片的长度、容量)是可以在函数中修改的,但这不会改变传递到函数中的切片的原始引用。也就是说,如果函数修改了切片的长度或容量,这些变化不会影响原始切片的长度和容量。
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 切片的结构
切片本质上是一个结构体,包含以下三个部分:
-
指向底层数组的指针(
ptr
): 切片内部有一个指针指向底层的数组。这使得切片可以动态地调整大小,而不需要重新分配数组。 -
切片的长度(
len
): 这是切片中元素的数量,表示当前切片中包含的元素个数。 -
切片的容量(
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 扩容的内存分析
- 每个切片引用了一个底层的数组
- 切片本身不存储任何数据,都是底层的数组来存储的,所以修改了切片也就是修改了这个数组中的数据
- 向切片中添加数据的时候,如果没有超过容量,直接添加,如果超过了这个容量,就会自动扩容,成倍的增加, copy
- 切片一旦扩容,就是重新指向一个新的底层数组。
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)
}
相关文章:

【go语言】数组和切片
一、数组 1.1 什么是数组 数组是一组数:数组需要是相同类型的数据的集合;数组是需要定义大小的;数组一旦定义了大小是不可以改变的。 1.2 数组的声明 数组和其他变量定义没有什么区别,唯一的就是这个是一组数,需要给…...

2025美赛MCM数学建模A题:《石头台阶的“记忆”:如何用数学揭开历史的足迹》(全网最全思路+模型)
✨个人主页欢迎您的访问 ✨期待您的三连 ✨ 《石头台阶的“记忆”:如何用数学揭开历史的足迹》 目录 《石头台阶的“记忆”:如何用数学揭开历史的足迹》 ✨摘要✨ ✨引言✨ 1. 引言的结构 2. 撰写步骤 (1)研究背景 &#…...
使用 Docker Compose 一键启动 Redis、MySQL 和 RabbitMQ
目录 一、Docker Compose 简介 二、服务配置详解 1. Redis 配置 2. MySQL 配置 3. RabbitMQ 配置 三、数据持久化与时间同步 四、部署与管理 五、总结 目录挂载与卷映射的区别 现代软件开发中,微服务架构因其灵活性和可扩展性而备受青睐。为了支持微服务的…...

新增自定义数据功能|UWA Gears V1.0.7
UWA Gears 是UWA最新发布的无SDK性能分析工具。针对移动平台,提供了实时监测和截帧分析功能,帮助您精准定位性能热点,提升应用的整体表现。 本次版本更新新增了自定义数据功能,支持灵活定义和捕获关键性能指标,满足特…...

docker 简要笔记
文章目录 一、前提内容1、docker 环境准备2、docker-compose 环境准备3、流程说明 二、打包 docker 镜像1、基础镜像2、国内镜像源3、基础的dockerfile4、打包镜像 四、构建运行1、docker 部分2、docker-compose 部分2.1、构建docker-compose.yml2.1.1、同目录构建2.1.2、利用镜…...
在Ubuntu上使用Apache+MariaDB安装部署Nextcloud并修改默认存储路径
一、前言 Nextcloud 是一款开源的私有云存储解决方案,允许用户轻松搭建自己的云服务。它不仅支持文件存储和共享,还提供了日历、联系人、任务管理、笔记等丰富的功能。本文将详细介绍如何在 Ubuntu 22.04 LTS 上使用 Apache 和 MariaDB 安装部署 Nextcl…...

【JavaEE】-- 计算机是如何工作的
文章目录 1. 冯诺依曼体系(VonNeumann Architecture)2. CPU 基本工作流程2.1 寄存器(Register)和 内存(RAM)2.2 控制单元 CU(ControlUnit)2.3 指令(Instruction) 3. 操作系统(OperatingSystem)3.1 操作系统的定位3.2 什么是进程/任务(Process…...

政安晨的AI大模型训练实践三:熟悉一下LF训练模型的WebUI
政安晨的个人主页:政安晨 欢迎 👍点赞✍评论⭐收藏 希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正! 目录 启动WebUI 微调模型 LLaMA-Factory 支持通过 WebUI 零代码微调大语言模型。 启动Web…...

基于微信小程序的网上订餐管理系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…...
科技快讯 | 理想官宣:正式收费!WeChat 港币钱包拓宽商户网络;百川智能发布深度思考模型Baichuan-M1-preview
理想官宣:正式收费! 1月23日,理想汽车宣布,理想超充站超时占用费正式运营。触发超时占用费的条件为充电结束后15分钟内未将充电枪插回充电桩,收费标准为2元/分钟,单次封顶200元。理想汽车将在充电结束的四个…...

【java数据结构】map和set
【java数据结构】map和set 一、Map和Set的概念以及背景1.1 概念1.2 背景1.3 模型 二、Map2.1 Map说明2.2 Map的常用方法 三、Set3.1 Set说明3.2 Set的常用方法 四、Set和Map的关系 博客最后附有整篇博客的全部代码!!! 一、Map和Set的概念以及…...
飞牛NAS安装过程中的docker源问题
采用CloudFlare进行飞牛NAS的远程访问 【安全免费】无需公网IP、端口号,NAS外网访问新方法_网络存储_什么值得买 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<EOF {"registry-mirrors": ["https://docker.1panel.dev&quo…...
Linux(Centos 7.6)命令详解:dos2unix
1.命令安装 dos2unix 命令默认情况下是没有安装的,如配置yum源,可通过yum安装命令如下: yum install dos2unix dos2unix 有一个对立的命令unix2dos,也需要yum安装,一般使用不到这里不做过多解释,具体参数…...
Linux MySQL离线安装
一、准备工作 1. 下载MySQL安装包 访问MySQL官方网站,选择适合您Linux系统的MySQL版本进行下载。通常推荐下载Generic Linux (glibc 2.12)版本的.tar.gz压缩包,例如mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz。将下载好的安装包拷贝到Linux服务器的某…...

声明,这些内容和我无关
声明,下面这些内容和我无关,不是我写的,买了我不负责答疑,也不负责其他相关。 一下内容都不是我写的,系统自己加上去的,和我无关,我不负责答疑也不负责其他。...

ISO:摄影中的光线敏感度密码
目录 一、ISO 究竟是什么 二、ISO 与光线的关系 (一)低 ISO 在充足光线下的表现 (二)高 ISO 在光线不足时的作用 三、ISO 对画质的影响 (一)低 ISO 带来的优质画质 (二)高 IS…...

长短期记忆网络LSTM
视频链接 1.LSTM与RNN的区别 RNN想把所有信息都记住,不管是有用的信息还是没用的信息,并且有梯度爆炸或者梯度消失的问题 而LSTM设计了一个记忆细胞,具备选择记忆功能,可以选择记忆重要信息,过滤掉噪声信息࿰…...

2. 握手问题python解法——2024年省赛蓝桥杯真题
原题传送门:1.握手问题 - 蓝桥云课 问题描述 小蓝组织了一场算法交流会议,总共有 50人参加了本次会议。在会议上,大家进行了握手交流。按照惯例他们每个人都要与除自己以外的其他所有人进行一次握手 (且仅有一次)。但有 7 个人,…...

poi在word中打开本地文件
poi版本 5.2.0 方法1:使用XWPFFieldRun(推荐) 比如打开当前相对路径的aaaaa.docx XWPFFieldRun run paragraph.createFieldRun();CTRPr ctrPr run.getCTR().addNewRPr();CTFonts font ctrPr.addNewRFonts();// 设置字体font.setAscii(&quo…...

国产编辑器EverEdit - 输出窗口
1 输出窗口 1.1 应用场景 输出窗口可以显示用户执行某些操作的结果,主要包括: 查找类:查找全部,筛选等待操作,可以把查找结果打印到输出窗口中; 程序类:在执行外部程序时(如:命令窗…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...

(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...

宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...

springboot 日志类切面,接口成功记录日志,失败不记录
springboot 日志类切面,接口成功记录日志,失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...
pycharm 设置环境出错
pycharm 设置环境出错 pycharm 新建项目,设置虚拟环境,出错 pycharm 出错 Cannot open Local Failed to start [powershell.exe, -NoExit, -ExecutionPolicy, Bypass, -File, C:\Program Files\JetBrains\PyCharm 2024.1.3\plugins\terminal\shell-int…...

软件工程 期末复习
瀑布模型:计划 螺旋模型:风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合:模块内部功能紧密 模块之间依赖程度小 高内聚:指的是一个模块内部的功能应该紧密相关。换句话说,一个模块应当只实现单一的功能…...