不要对Go并发函数的执行时机做任何假设

    1. import (
    2. "fmt"
    3. "runtime"
    4. "time"
    5. )
    6. func main(){
    7. names := []string{"lily", "yoyo", "cersei", "rose", "annei"}
    8. for _, name := range names{
    9. go func(){
    10. fmt.Println(name)
    11. }()
    12. }
    13. runtime.GOMAXPROCS(1)
    14. runtime.Gosched()
    15. }

    输出结果:

    1. annei
    2. annei
    3. annei
    4. annei
    5. annei

    为什么呢?是不是有点诧异?
    输出的都是“annei”,而“annei”又是“names”的最后一个元素,那么也就是说程序打印出了最后一个元素的值,而name对于匿名函数来讲又是一个外部的值。因此,我们可以做一个推断:虽然每次循环都启用了一个协程,但是这些协程都是引用了外部的变量,当协程创建完毕,再执行打印动作的时候,name的值已经不知道变为啥了,因为主函数协程也在跑,大家并行,但是在此由于names数组长度太小,当协程创建完毕后,主函数循环早已结束,所以,打印出来的都是遍历的names最后的那一个元素“annei”。
    如何证实以上的推断呢?
    其实很简单,每次循环结束后,停顿一段时间,等待协程打印当前的name便可。

    1. import (
    2. "fmt"
    3. "runtime"
    4. "time"
    5. )
    6. func main(){
    7. names := []string{"lily", "yoyo", "cersei", "rose", "annei"}
    8. for _, name := range names{
    9. go func(){
    10. fmt.Println(name)
    11. }()
    12. time.Sleep(time.Second)
    13. }
    14. runtime.GOMAXPROCS(1)
    15. runtime.Gosched()
    16. }

    打印结果:

    1. lily
    2. yoyo
    3. cersei
    4. rose
    5. annei

    以上我们得出一个结论,不要对“go函数”的执行时机做任何的假设,除非你确实能做出让这种假设成为绝对事实的保证。

    假设T类型的方法上接收器既有T类型的,又有T指针类型的,那么就不可以在不能寻址的T值上调用T接收器的方法

    请看代码,试问能正常编译通过吗?

    1. import (
    2. "fmt"
    3. )
    4. type Lili struct{
    5. Name string
    6. }
    7. func (Lili *Lili) fmtPointer(){
    8. fmt.Println("poniter")
    9. }
    10. func (Lili Lili) fmtReference(){
    11. fmt.Println("reference")
    12. }
    13. func main(){
    14. li := Lili{}
    15. li.fmtPointer()
    16. }

    答案

    1. 能正常编译通过,并输出"poniter"

    感觉有点诧异,请接着看以下的代码,试问能编译通过?

    1. import (
    2. "fmt"
    3. )
    4. type Lili struct{
    5. Name string
    6. }
    7. func (Lili *Lili) fmtPointer(){
    8. fmt.Println("poniter")
    9. }
    10. func (Lili Lili) fmtReference(){
    11. fmt.Println("reference")
    12. }
    13. func main(){
    14. Lili{}.fmtPointer()
    15. }

    答案:

    1. 不能编译通过。
    2. cannot call pointer method on Lili literal
    3. cannot take the address of Lili literal

    是不是有点奇怪?这是为什么呢?其实在第一个代码示例中,main主函数中的“li”是一个变量,li的虽然是类型Lili,但是li是可以寻址的,&li的类型是Lili,因此可以调用Lili的方法。

    一个包含nil指针的接口不是nil接口
    请看下列代码,试问返回什么

    1. import (
    2. "bytes"
    3. "fmt"
    4. "io"
    5. )
    6. const debug = true
    7. func main(){
    8. var buf *bytes.Buffer
    9. if debug{
    10. buf = new(bytes.Buffer)
    11. }
    12. f(buf)
    13. }
    14. func f(out io.Writer){
    15. if out != nil{
    16. fmt.Println("surprise!")
    17. }
    18. }

    答案是输出:surprise。
    ok,让我们吧debug开关关掉,及debug的值变为false。那么输出什么呢?是不是什么都不输出?

    1. import (
    2. "bytes"
    3. "fmt"
    4. "io"
    5. )
    6. const debug = false
    7. func main(){
    8. var buf *bytes.Buffer
    9. if debug{
    10. buf = new(bytes.Buffer)
    11. }
    12. f(buf)
    13. }
    14. func f(out io.Writer){
    15. if out != nil{
    16. fmt.Println("surprise!")
    17. }
    18. }

    答案是:依然输出surprise。
    这是为什么呢?
    这就牵扯到一个概念了,是关于接口值的。概念上讲一个接口的值分为两部分:一部分是类型,一部分是类型对应的值,他们分别叫:动态类型和动态值。类型系统是针对编译型语言的,类型是编译期的概念,因此类型不是一个值。
    在上述代码中,给f函数的out参数赋了一个*bytes.Buffer的空指针,所以out的动态值是nil。然而它的动态类型是bytes.Buffer,意思是:“A non-nil interface containing a nil pointer”,所以“out!=nil”的结果依然是true。
    但是,对于直接的bytes.Buffer类型的判空不会出现此问题。

    1. import (
    2. "bytes"
    3. "fmt"
    4. )
    5. func main(){
    6. var buf *bytes.Buffer
    7. if buf == nil{
    8. fmt.Println("right")
    9. }
    10. }

    还是输出: right
    只有 接口指针 传入函数的接口参数时,才会出现以上的坑。
    修改起来也很方便,把*bytes.Buffer改为io.Writer就好了。

    1. import (
    2. "bytes"
    3. "fmt"
    4. "io"
    5. )
    6. const debug = false
    7. func main(){
    8. var buf io.Writer //原来是var buf *bytes.Buffer
    9. if debug{
    10. buf = new(bytes.Buffer)
    11. }
    12. f(buf)
    13. }
    14. func f(out io.Writer){
    15. if out != nil{
    16. fmt.Println("surprise!")
    17. }
    18. }

    将map转化为json字符串的时候,json字符串中的顺序和map赋值顺序无关
    请看下列代码,请问输出什么?若为json字符串,则json字符串中key的顺序是什么?

    1. func main() {
    2. params := make(map[string]string)
    3. params["id"] = "1"
    4. params["id1"] = "3"
    5. params["controller"] = "sections"
    6. data, _ := json.Marshal(params)
    7. fmt.Println(string(data))
    8. }

    答案:输出{“controller”:”sections”,”id”:”1”,”id1”:”3”}
    利用Golang自带的json转换包转换,会将map中key的顺序改为字母顺序,而不是map的赋值顺序。map这个结构哪怕利用for range遍历的时候,其中的key也是无序的,可以理解为map就是个无序的结构,和php中的array要区分开来

    Json反序列化数字到interface{}类型的值中,默认解析为float64类型
    请看以下程序,程序想要输出json数据中整型id加上3的值,请问程序会报错吗?

    1. func main(){
    2. jsonStr := `{"id":1058,"name":"RyuGou"}`
    3. var jsonData map[string]interface{}
    4. json.Unmarshal([]byte(jsonStr), &jsonData)
    5. sum := jsonData["id"].(int) + 3
    6. fmt.Println(sum)
    7. }

    答案是会报错,输出结果为:
    panic: interface conversion: interface {} is float64, not int

    使用 Golang 解析 JSON 格式数据时,若以 interface{} 接收数据,则会按照下列规则进行解析:

    bool, for JSON booleans
    float64, for JSON numbers
    string, for JSON strings
    []interface{}, for JSON arrays
    map[string]interface{}, for JSON objects
    nil for JSON null

    1. func main(){
    2. jsonStr := `{"id":1058,"name":"RyuGou"}`
    3. var jsonData map[string]interface{}
    4. json.Unmarshal([]byte(jsonStr), &jsonData)
    5. sum := int(jsonData["id"].(float64)) + 3
    6. fmt.Println(sum)
    7. }

    即使在有多个变量、且有的变量存在有的变量不存在、且这些变量共同赋值的情况下,也不可以使用:=来给全局变量赋值
    :=往往是用来声明局部变量的,在多个变量赋值且有的值存在的情况下,:=也可以用来赋值使用,例如:

    1. msgStr := "hello wolrd"
    2. msgStr, err := "hello", errors.New("xxx")//err并不存在

    但是,假如全局变量也使用类似的方式赋值,就会出现问题,请看下列代码,试问能编译通过吗?

    1. var varTest string
    2. func test(){
    3. varTest, err := function()
    4. fmt.Println(err.Error())
    5. }
    6. func function()(string, error){
    7. return "hello world", errors.New("error")
    8. }
    9. func main(){
    10. test()
    11. }

    但是如果改成如下代码,就可以通过:

    1. var varTest string
    2. func test(){
    3. err := errors.New("error")
    4. varTest, err = function()
    5. fmt.Println(err.Error())
    6. }
    7. func function()(string, error){
    8. return "hello world", errors.New("error")
    9. }
    10. func main(){
    11. test()
    12. }

    输出:

    error

    这是什么原因呢?
    答案其实很简单,在test方法中,如果使用varTest, err := function()这种方式的话,相当于在函数中又定义了一个和全局变量varTest名字相同的局部变量,而这个局部变量又没有使用,所以会编译不通过。


    image.jpeg