数组是具有固定长度具有零个或者多个相同数据类型元素的序列。
由于数组长度固定,在Go里很少直接使用。
// 3种方式,声明,初始,省略号
// 变量arr1类型为[5]int
var arr1 [5]int
// 变量arr2类型为[3]int,同时初始化赋值
var arr2 [5]int = [5]int{1,2,3}
// 让编译器自己数,结果为[3]int
arr3 := [...]int{1,2,3}
// 错误例子,因为[3]int和[4]int是两种类型
arr3 := [4]int{1,2,3,4}
[3]int
和[5]int
就是两种类型。arr1 := [...]{1,2,3}
lenth := len(arr1)
arr1 := [3]{1,2,3}
for i,val := range arr1 {
}
数组是值类型,值类型意味着拷贝。
注意!!!在其他语言中,数组是隐式的使用引用传递;Golang传参时,传入的参数会创建一个副本,使用这种方式传递大的数组会变的很低效。
可以显式的给函数传递一个数组的指针。
// 1. 遍历数组,将数组中元素清零
fun zero (ptr *[32]byte) {
for i := range ptr {
ptr[i] = 0
}
}
// 2. 利用数组指针,将数组元素清零
func zero (ptr *[32]byte) {
*ptr = [32]byte{}
}
a := [2]int{1,2} // [2]int类型
b := [...]int{1,2} // [2]int类型
c := [2]int{1,3} // [2]int类型
fmt.Println(a==b,a==c,b==c) // true,false,false
d := [3]int{1,2} // [3]int类型
fmt.Println(a == d) // 编译错误:不能比较[2]int和[3]int
切片用于表示一个拥有相同类型元素的可变长的序列,看上去像是没有长度的数组类型。
通过数组定义
// 定义一个数组
arr := [...]int{0,1,2,3,4,5,6,7}
// 根据数组,定义该数组的view,即slice
s := arr[2:6] // s={2,3,4,5},左开右闭
// s就是一个切片,它是数组arr的一个视图
// 总结来说,一个数组取它的slice,使用slice操作符arr[:]即可,看的是底层数组的全部景象
直接定义切片
var s1 []int
s2 := []int{0,1,2,3,4}
s3 := make([]int,6) // 默认len == cap == 6
s4 := make([]int,10,32) // len == 10 ,cap == 32
// 定义一个数组
arr := [...]int{0,1,2,3,4,5,6,7}
// 根据数组,定义该数组的view,即slice
s := arr[2:6] // s={2,3,4,5},左开右闭
fmt.Println(len(s)) // len(s) == 4 (2-5)
fmt.Println(cap(s)) // cap(s) == 6 (2-7)
// slice 不能使用 == 操作符来比较两个切片元素是否相等
// 唯一允许的比较操作是和nil作比较
// 但是,slice为nil的情况有很多种
var s []int // len(s) == 0, s == nil
s = nil // len(s) == 0, s == nil
s = []int{nil} // len(s) == 0, s == nil
s = []int{} // len(s) == 0, s!=nil
// 所以一般情况不用 s == nil来判断slice是否为空,而是使用len(s) == 0来判断
for _,s := range s1 {
}
// 使用append追加元素可以超过capacity,它会触发slice底层数组的扩容机制
// 扩容就是重新分配长度更大的底层数组,原来的数组就会进行垃圾回收
// 由于值传递的关系,必须要使用一个新的slice接收append的返回值
// append的时候,ptr,len,cap有可能都会发生变化,必须接收新的ptr,len和cap
s := []int{0,1,2,3,4,5}
s = append(s,val)
// 下面的一种操作,可以达到删除slice中元素的目的
// ...表示变长参数列表,追加s[4:]切片后所有元素
s = append(s[:3],s[4:]...)
// append是从切片的len,向后进行扩展的操作
var ints []int = make([]int,2,5) // len=2,cap=5
// 底层数组:0 0 0 0 0
ints = append(ints,1) // len=3,cap=5
// 底层数组:0 0 1 0 0
切片拷贝:
- 对于切片直接使用
=
拷贝,实际上是浅拷贝,只拷贝了切片在堆上的内存地址;sliceA = sliceB
。- 对于切片深拷贝的需求,可以使用
copy
内置函数完成;copy(sliceA,sliceB)
。
func nonempty(strings []string) []string {
i := 0
for _, s := range strings {
if s != "" {
strings[i] = s
i++
}
}
return strings[:i]
}
//定义一个函数,给切片添加一个元素
func addOne(s []int) {
s[0] = 4 // 可以改变原切片值
s = append(s, 1) // 扩容后分配了新的地址,原切片将不再受影响
s[0] = 8
}
var s1 = []int{2} // 初始化一个切片
addOne(s1) // 调用函数添加一个切片
fmt.Println(s1) // 输出一个值 [4]
// 预估规则:
if oldCap * 2 < newCap
直接分配内存
else
if oldLen < 1024 newCap = oldCap * 2
if oldLen > 1024 newCap = oldCap *1.25
// 预估到的newCap只是扩容后元素的个数,具体分配多大的内存呢?
// newCap * sizeof(T)吗?
// 事实上,许多编程语言中,申请分配内存,并不是直接和操作系统交涉,而是和语言自身实现的内存管理模块。内存管理模块会提前申请一批常用的内存,管理起来,需要申请 内存时内存管理模块会帮我们匹配到最接近的规格
TODO: 以java 中StringBuffer和ArrayList为例作比较