1. 永远不要使用一个指针指向一个接口类型,因为它已经是一个指针
函数参数为 interface{} 时可以接收任何类型的参数,包括用户自定义类型等
type S struct {
}
func fa(x interface{}) {
}
func g(x *interface{}) {
}
func TestFour01(t *testing.T) {
s := S{}
p := &s
fa(s)
g(s)
fa(p)
g(p)
}
2. 基于类型创建的方法必须定义在同一个包内
3. golang 的字符串类型是不能赋值 nil 的,也不能跟 nil 比较
4. 对于类似 X = Y的赋值操作,必须知道 X 的地址,才能够将 Y 的值赋给 X
var m = map[string]Math{
//"foo": Math{2, 3},
// fix2.修改数据结构
//"foo": &Math{2, 3},
}
// go 中的 map 的 value 本身是不可寻址的
// map[key]struct 中 struct 是不可寻址的
func TestFour07(t *testing.T) {
//m["foo"].x = 4
// fix1.使用临时变量
tmp := m["foo"]
tmp.x = 4
m["foo"] = tmp
fmt.Println(m["foo"].x)
}
5. 对于使用:=定义的变量,如果新变量与同名已定义的变量不在同一个作用域中,那么 Go 会新定义这个变量
var p3 *int
func foo() (*int, error) {
var i int = 5
return &i, nil
}
func bar() {
//use p
fmt.Println(*p3)
}
func TestFour08(t *testing.T) {
// 函数里的 p3 是新定义的变量,会遮住全局变量 p3,
// 导致执行到bar()时程序,全局变量 p3 依然还是 nil,程序随即 Crash
p3, err := foo()
if err != nil {
fmt.Println(err)
return
}
bar()
fmt.Println(*p3)
}
6. 循环次数在循环开始前就已经确定,循环内改变切片的长度,不影响循环次数
func TestFour09(t *testing.T) {
v := []int{1, 2, 3}
for i := range v {
v = append(v, i)
}
println(v)
}
7. goroutine 中输出的 i、v 值都是 for range 循环结束后的 i、v 最终值, 而不是goroutine启动时的i, v值。
// 可以理解为闭包引用,使用的是上下文环境的值。
func TestFour10(t *testing.T) {
var m = [...]int{1, 2, 3}
for i, v := range m {
go func() {
fmt.Println(i, v)
}()
}
// fix1:使用函数传递
for i, v := range m {
go func(i, v int) {
fmt.Println(i, v)
}(i, v)
}
// fix2:使用临时变量保留当前值
for i, v := range m {
i := i // 这里的 := 会重新声明变量,而不是重用
v := v
go func() {
fmt.Println(i, v)
}()
}
time.Sleep(time.Second * 1)
}
8. 切片在 go 的内部结构有一个指向底层数组的指针
当 range 表达式发生复制时,副本的指针依旧指向原底层数组,所以对切片的修改都会反应到底层数组上,所以通过 v 可以获得修改后的数组元素
func change1(s ...int) {
s = append(s, 3)
}
// 第一次调用 change() 时,append() 操作使切片底层数组发生了扩容,原 slice 的底层数组不会改变;
// 第二次调用change() 函数时,使用了操作符[i,j]获得一个新的切片,
// 假定为 slice1,它的底层数组和原切片底层数组是重合的,
// 不过 slice1 的长度、容量分别是 2、5,所以在 change() 函数中对 slice1 底层数组的修改会影响到原切片。
func TestFour13(t *testing.T) {
slice := make([]int, 5, 5)
slice[0] = 1
slice[1] = 2
change1(slice...)
fmt.Println(slice)
change1(slice[0:2]...)
fmt.Println(slice)
var a = []int{1, 2, 3, 4, 5}
var r [5]int
for i, v := range a {
if i == 0 {
a[1] = 12
a[2] = 13
}
r[i] = v
}
fmt.Println("r = ", r)
fmt.Println("a = ", a)
}
9. 常量组中如不指定类型和初始化值,则与上一行非空常量右值相同
func TestFour15(t *testing.T) {
const (
x uint16 = 120
y
s = "abc"
z
)
fmt.Printf("%T %v\n", y, y)
fmt.Printf("%T %v\n", z, z)
const x1 = 123
const y1 = 1.23
fmt.Println(x1 + y1)
}
10. 在方法中,指针类型的接收者必须是合法指针(包括 nil),或能获取实例地址;不可寻址的结构体不能调用带结构体指针接收者的方法
type X struct{}
func (x *X) test() {
println(x)
}
type T struct {
n int
}
func (t *T) Set(n int) {
t.n = n
}
func getT() T {
return T{}
}
// X{} 是不可寻址的,不能直接调用方法。
func TestFour16(t *testing.T) {
var a *X
a.test()
//X{}.test()
// fix1
var x = X{}
x.test()
//getT().Set(1)
// fix2
i := getT()
i.Set(2)
}
11. 当指针值赋值给变量或者作为函数参数传递时, 会立即计算并复制该方法执行所需的接收者对象,与其绑定,以便在稍后执行时,能隐式第传入接收者参数
当目标方法的接收者是指针类型时,那么被复制的就是指针
type N int
func (n N) test() {
fmt.Println(n)
}
func TestFour18(t *testing.T) {
var n N = 10
p := &n
n++
f1 := n.test
n++
f2 := p.test
n++
fmt.Println(n)
f1()
f2()
}
12. 截取符号 [i:j],如果 j 省略,默认是原切片或者数组的长度
// x 的长度是 2,小于起始下标 6
func TestFour20(t *testing.T) {
x := make([]int, 2, 10)
_ = x[6:10]
_ = x[6:] //panic
_ = x[2:]
}
13. 将 Mutex 作为匿名字段时,相关的方法必须使用指针接收者,否则会导致锁机制失效
type data struct {
sync.Mutex
// fix2
//*sync.Mutex
}
func (d data) test(s string) {
// fix1
//func (d *data) test(s string) {
d.Lock()
defer d.Unlock()
for i := 0; i < 5; i++ {
fmt.Println(s, i)
time.Sleep(time.Second)
}
}
func TestFour21(t *testing.T) {
var wg sync.WaitGroup
wg.Add(2)
var d data
// fix2
//d:=data{new(sync.Mutex)}
go func() {
defer wg.Done()
d.test("read")
}()
go func() {
defer wg.Done()
d.test("write")
}()
wg.Wait()
}
14. defer() 后面的函数如果带参数,会优先计算参数, 并将结果存储在栈中,到真正执行 defer() 的时候取出
func TestFour24(t *testing.T) {
f := F(5)
defer func() {
fmt.Println("1+==", f())
}()
defer fmt.Println("2+==", f())
i := f()
fmt.Println("4+==", i)
}
// 在调用 defer() 时,便会计算函数的参数并压入栈中,
// 所以执行a1处代码时,此时便会捕获 panic(2);此后的 panic(1),会被上一层的 recover() 捕获
func TestFour25(t *testing.T) {
// a1
defer func() {
fmt.Print(recover())
}()
defer func() {
defer fmt.Print(recover())
panic(1)
}()
// recover() 必须在 defer() 函数中调用才有效
defer recover() //无效的
panic(2)
}
文章部分知识点出自:5 年 Gopher 都不知道的 defer 细节,你别再掉进坑里!;Go代码断行规则