1.go传值还是传引用
- Go语言并不存在类似其他语言的引用类型(没有开辟新的内存地址,则是引用,切片没有扩容,指针没有new 或者&{})
- 无论参数是普通类型还是指针类型都会发生参数值拷贝(变量值,指针值都是新的)
- 函数内对切片进行了append,需要将切片作为返回值返回
2.结构地指针是传值还是传引用
- 指针的值会改变,但是指向的内存空间还是相同的,若指针没有开辟新的内存地址,则传引用,开辟的话,开辟后改变值,不会影响外面
func TestAdd(tt *testing.T) {
t := &T{
FieldA: "a",
FieldB: "b",
}
//Before changing: value-&{a b}, address-0xc000164088
fmt.Printf("Before changing: value-%v, address-%p\n", t, &t)
changeTypeField(t)
//After changeTypeField: value-&{A B}, address-0xc000164088
fmt.Printf("After changeTypeField: value-%v, address-%p\n", t, &t)
changeTypeStruct(t)
//After changeTypeStruct: value-&{A B}, address-0xc000164088
fmt.Printf("After changeTypeStruct: value-%v, address-%p\n", t, &t)
}
//这里p 是局部变量,值是新的,但指针指向同一片内存,所以外面发生变化
func changeTypeField(p *T) {
p.FieldA = "A"
p.FieldB = "B"
fmt.Printf("In changeTypeField: value-%v, address-%p\n", p, &p)
// value-&{A B}, address-0xc000164090
}
//p 是局部变量,p的值是新的,但是开辟新的内存地址,所以改变里面内容,不会影响外面
func changeTypeStruct(p *T) {
p = &T{
FieldA: "Alpha",
FieldB: "Beta",
}
fmt.Printf("In changeTypeStruct: value-%v, address-%p \n", p, &p)
}
Before changing: value-&{a b}, address-0xc000164088
In changeTypeField: value-&{A B}, address-0xc000164090
After changeTypeField: value-&{A B}, address-0xc000164088
In changeTypeStruct: value-&{Alpha Beta}, address-0xc000164098
After changeTypeStruct: value-&{A B}, address-0xc000164088
3.切片传指针还是传引用
- 切片底层由数组指针 + len(长度) +cap(容量) 组成
- 需要切片、数组分开看,局部切片是len、cap传值,若没有扩容,cap还有容量,若元素改变,局部切片的长度会改变,外面的切片长度不会改变,但是数组内容改变
- 若cap不够,发生扩容,局部切片数组指针指向新的内存地址、len、cap 发生改变,局部切片是新的,外部切片不受影响
func TestTest(t *testing.T) {
var s []int
for i := 1; i <= 3; i++ {
s = append(s, i)
}
//此时len=3 cap=4 [1 2 3] before
fmt.Println(s, "before")
//RRR [1 2 3] 3 4
fmt.Printf("RRR \x1B[1;32;32m %+v %d %d \n", s, len(s), cap(s))
reverse(s)
//[1 2 3] after
fmt.Println(s, "after")
//RRR [1 2 3] 3 4
fmt.Printf("RRR \x1B[1;32;32m %+v %d %d \n", s, len(s), cap(s))
}
func reverse(s []int) {
s = append(s, 999,1000)
for i, j := 0, len(s)-1; i < j; i++ {
j = len(s) - (i + 1)
s[i], s[j] = s[j], s[i]
}
//RRR [1000 999 3 2 1] 5 8
fmt.Printf("RRR \x1B[1;32;32m %+v %d %d \n", s, len(s), cap(s))
}
4.发生错误时使用defer关闭一个文件
如果你在一个for循环内部处理一系列文件,你需要使用defer确保文件在处理完毕后被关闭:
- defer仅在函数返回时才会执行,在循环的结尾或其他一些有限范围的代码内不会执行。
for _, file := range files {
if f, err = os.Open(file); err != nil {
return
}
// 这是错误的方式,当循环结束时文件没有关闭
defer f.Close()
// 对文件进行操作
f.Process(data)
}
但是在循环结尾处的defer没有执行,所以文件一直没有关闭!垃圾回收机制可能会自动关闭文件,但是这会产生一个错误,更好的做法是:
for _, file := range files {
if f, err = os.Open(file); err != nil {
return
}
// 对文件进行操作
f.Process(data)
// 关闭文件
f.Close()
}
5.误用短声明导致变量覆盖
- if 语句内若有跟外部变量有关系的变量,不要使用 :=声明
- err 也是如此,if内 避免使用 if _,err:=SomeFunc(){}
var remember bool = false
if something {
remember := true //错误
}
// 外面remember还是为false,
//因为 if语句内remember被重新声明&&赋值了
//所以正确的写法应该是:
if something {
remember = true
}
参考资料:https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/directory.md