Go101细节

老貘写的Go101,只能说老貘精力真足,这里记录一些细节部分感觉有意思的点。

 

switch表达式中的值会默认类型确定,可以自己指定

package main

func main() {
    switch 123 {
    case int64(123):  // error: 类型不匹配
    case uint32(789): // error: 类型不匹配
    }
}


func main() {
    switch int64(123) {
    case 123:
    case int(123): // error: 类型不匹配
    case int64(123):    
    }
}

 

嵌套的defer可以修改函数的返回值,当然不要这么用。

func F() (r int) {
    defer func() {
        r = 123
    }()
    return
}

func main() {
    fmt.Println(F()) // 123
}

 

这里主要是退出协程的细节,这里不会在执行输出Java了,因为在这个它协程的退出后,但是不影响输出c,因为main自己是一个独立的协程。

package main

import "fmt"
import "runtime"

func main() {
    c := make(chan int)
    go func() {
        defer func() {
            c <- 1
        }()
        defer fmt.Println("Go")
        func() {
            defer fmt.Println("C")
            runtime.Goexit()
        }()
        fmt.Println("Java")
    }()
    //<-c
    println(<-c)
}

C
Go
1

 

一些运算符的优先级

package main

import "fmt"

type T struct {
    x int
    y *int
}

func main() {
    var t T
    p := &t.x // <=> p := &(t.x)
    fmt.Printf("%T\n", p) // *int

    *p++ // <=> (*p)++
    *p-- // <=> (*p)--

    t.y = p
    a := *t.y // <=> *(t.y)
    fmt.Printf("%T\n", a) // int
}

 

移位运算时,类型判断取决于右边是否是常量

package main

func main() {
}

const M  = 2
var _ = 1.0 << M // 编译没问题。1.0将被推断为一个int值。

var N = 2
var _ = 1.0 << N // 编译失败。1.0将被推断为一个float64值。

var _ = 1.0 << 2 // 编译没问题。1.0将被推断为一个int值。

 

两个指针类型的基类共享相同的底层类型,则可以相互转换

package main

type MyInt int64
type Ta    *int64
type Tb    *MyInt

func main() {
    var a Ta
    var b Tb

    //a = Ta(b) // error: 直接转换是不允许的。

    // 但是间接转换是允许的。
    y := (*MyInt)(b)
    x := (*int64)(y)
    a = x           // 等价于下一行
    a = (*int64)(y) // 等价于下一行
    a = (*int64)((*MyInt)(b))
    _ = a
}

 

两个零值的地址可能相等也可能不相等,其实在栈上是肯定不相等的,在堆上是可能相等的。

package main

import "fmt"

func main() {
    a := struct{}{}
    b := struct{}{}
    x := struct{}{}
    y := struct{}{}
    m := [10]struct{}{}
    n := [10]struct{}{}
    o := [10]struct{}{}
    p := [10]struct{}{}

    fmt.Println(&x, &y, &o, &p)

    // 对于标准编译器1.14版本,x、y、o和p将
    // 逃逸到堆上,但是a、b、m和n则开辟在栈上。

    fmt.Println(&a == &b) // false
    fmt.Println(&x == &y) // true
    fmt.Println(&a == &x) // false

    fmt.Println(&m == &n) // false
    fmt.Println(&o == &p) // true
    fmt.Println(&n == &p) // false
}

 

一个指针类型的基类可以是该指针类型本身,不推荐如此使用。

package main

func main() {
    type P *P
    var p P
    p = &p
    p = **************p
}
package main

func main() {
    type S []S
    type M map[string]M
    type C chan C
    type F func(F) F

    s := S{0:nil}
    s[0] = s
    m := M{"Go": nil}
    m["Go"] = m
    c := make(C, 3)
    c <- c; c <- c; c <- c
    var f F
    f = func(F)F {return f}

    _ = s[0][0][0][0][0][0][0][0]
    _ = m["Go"]["Go"]["Go"]["Go"]
    <-<-<-c
    f(f(f(f(f))))
}

 

无论一个指针值的类型是定义的还是非定义的,如果它的(指针)类型的基类型为一个结构体类型,则我们可以使用此指针值来选择它所引用着的结构体中的字段。 但是,如果此指针的类型为一个定义的类型,则我们不能使用此指针值来选择它所引用着的结构体中的方法。

我们总是不能使用二级以上指针来选择结构体字段和方法。这里尤其注意引用方法。这里用指针理解蛮好理解的,可能对于确定类型的一级指针无法引用基类的方法难理解一点,还是方法绑定的问题。

package main

type T struct {
    x int
}
func (T) m(){} // T有一个方法m。

type P *T  // P为一个定义的一级指针。
type PP *P // PP为一个定义的二级指针。

func main() {
    var t T
    var tp = &t
    var tpp = &tp
    var p P = tp
    var pp PP = &p
    tp.x = 12  // 没问题
    p.x = 34   // 没问题
    pp.x = 56  // error: 类型PP没有名为x的字段或者方法。
    tpp.x = 78 // error: 类型**T没有名为x的字段或者方法。

    tp.m()  // 没问题,因为类型*T也有一个m方法。
    p.m()   // error: 类型P没有名为m的字段或者方法。
    pp.m()  // error: 类型PP没有名为m的字段或者方法。
    tpp.m() // error: 类型**T没有名为m的字段或者方法。
}

 

对于映射我们一般不需要在取item处进行大量的判断,因为不论你的映射是否为空都无所谓,所以个人建议从入口和出口控制而不是中间过程。

func Foo1(m map[string]int) int {
    if m != nil {
        return m["foo"]
    }
    return 0
}

func Foo2(m map[string]int) int {
    return m["foo"]
}

 

不整理了,进度过慢。

 

posted @ 2020-04-29 11:12  zhangyu63  阅读(318)  评论(0编辑  收藏  举报