- Go语言的赋值和函数传参规则:除了闭包函数以引用的方式对外部变量访问之外,其他赋值和函数传参都是以传值的方式处理。
- 数组的长度是数组类型的组成部分,不同长度或不同类型的数据组成的数据都是不同的类型,不同长度的数组因为类型不同而无法直接赋值,
数组
- Go语言的数组是值语义。一个数组变量即表示整个数组,它并不是隐式地指向第一个元素的指针,而是一个完整的值。每一次传递或者赋值实际上都是赋值整一个数组,为了避免复制数组带来的开销,可以传递一个指向数组的指针,但是数组指针并不是数组。但是指向不同长度的数组指针的指针类型是完全不同的。
var unknown1 []interface{}
var unknown2 =[...]interface{1243, "hello"}
var d [0]int
var e =[0]int{}
c1 := make(chan [0]int)
go func() {
fmt.Println("c1")
c1 <- [0]int{}
}()
<-c1
c2 := make(chan struct{})
go func() {
fmt.Println("c2")
c2 <- struct{}{}
}()
<-c2
字符串
- Go语言的源代码要求UTF8编码,导致字符串面值常量一般也是UTF8编码。源代码中的文本字符串通常被解释为采用UTF8的Unicode码点序列。
- for range等语法不支持非UTF8编码的字符串的遍历
type StringHeader struct {
Data uintptr
Len int
}
- 字符串结构由两个信息组成
- 字符串其实也是一个结构体,因此字符串的赋值操作也就是reflect.StringHeader结构体的复制过程,并不会涉及到底层字节数组的复制。因为字符串是只读的,所以相同的字符串面值常量通常对应同一个字符串常量。
- 字符串和[]rune的转换比较特殊,因为他们的底层类型不一样([]byte,[]int32)。因此这种转换可能隐含重新分配内存的操作。
切片
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
- 即使容量足够,依然需要用append()函数的返回值来更新切片本身,因为新切片的长度已经发生了变化。
a = append([]int{0},a...)
- 在判断一个切面是否为空时,一般通过len获取切片长度来判断,而很少用切片和nil直接做比较
避免切片内存泄漏问题
- 底层的数组会被保存在内存中,直到它不再被引用为止,但是有的时候可能会因为一个小的内存的引用而导致底层整个数组处于被使用的状态,这会延迟垃圾回收器对底层数组的回收。
func FindPhoneNumber(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
return regexp.MustCompile("[0-9]+").Find(b)
}
- 要解决这个问题,可以将感兴趣的数据复制到一个新的切片中(虽然传值有一定的代价,但是换取的好处是切断了对原始数据的依赖)
func FindPhoneNumber(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
b = regexp.MustCompile("[0-9]+").Find(b)
return append([]byte{}, b...)
}
- 假设切片里存放的是指针对象,那么删除元素后,被删除的元素依然被切片底层数据引用,从而导致不能及时被垃圾回收器回收
var a []*int{ ... }
a = a[:len(a)-1]
- 保险的方式是先将指向需要提前回收内存的指针设置为nil,保证垃圾回收器可以发现需要回收的对象,然后再进行切片删除操作。
var a[]*int{...}
a[len(a)-1] = nil
a = a[:len(a)- 1]