zoukankan      html  css  js  c++  java
  • 通过golang小案例,了解golang程序常见机制

    目录

    代码理解及纠错

    1、defer和panic执行先后顺序

    package main
    
     import (
         "fmt"
     )
    
     func main() {
         defer_call()
     }
    
    func defer_call() {
        defer func() { fmt.Println("打印前") }()
        defer func() { fmt.Println("打印中") }()
        defer func() { fmt.Println("打印后") }()
        panic("触发异常")
    }
    
        打印后
        打印中
        打印前
        panic: 触发异常
    

    defer 的执行顺序是后进先出。当出现 panic 语句的时候,会先按照 defer 的后进先出的顺序执行,最后才会执行panic

    2、for循环元素副本问题

    func main() {
    
         slice := []int{0,1,2,3}
         m := make(map[int]*int)
    
         for key,val := range slice {
             m[key] = &val
             
            // 正确写法
            // value := val
            // m[key] = &value
         }
         
    
    
        for k,v := range m {
            fmt.Println(k,"->",*v)
        }
    }
    
        0 -> 3
        1 -> 3
        2 -> 3
        3 -> 3
    

    这是新手常会犯的错误写法,for range 循环的时候会创建每个元素的副本,而不是元素的引用,所以 m[key] = &val 取的都是变量 val 的地址,所以最后 map 中的所有元素的值都是变量 val 的地址,因为最后 val 被赋值为3,所有输出都是3

    3、slice追加元素问题

    // 1.
     func main() {
         s := make([]int, 5)
         s = append(s, 1, 2, 3)
         fmt.Println(s)
     }
    
    // 2.
     func main() {
        s := make([]int,0)
        s = append(s,1,2,3,4)
        fmt.Println(s)
    }
    

    两段代码分别输出:

        [0 0 0 0 0 1 2 3]
        [1 2 3 4]
    

    append 向 slice 添加元素,第一段代码常见的错误是 [1 2 3],需要注意。

    4、返回值命名问题

        // 第二个返回值未命名错误
        func funcMui(x,y int)(sum int,error){
            return x+y,nil
        }
    

    在函数有多个返回值时,只要有一个返回值有命名,其他的也必须命名。如果有多个返回值必须加上括号();如果只有一个返回值且命名也必须加上括号()。这里的第一个返回值有命名 sum,第二个没有命名,所以错误。

    5、用new初始化内置类型问题

    func main() {
        list := new([]int)
        list = append(list, 1)
        fmt.Println(list)
    }
    

    不能通过编译,new([]int) 之后的 list 是一个 *[]int 类型的指针,不能对指针执行 append 操作。可以使用 make() 初始化之后再用。同样的,map 和 channel 建议使用 make() 或字面量的方式初始化,不要用 new() 。

    6、切片append另外一个切片问题

    func main() {
        s1 := []int{1, 2, 3}
        s2 := []int{4, 5}
        s1 = append(s1, s2)
        fmt.Println(s1)
    }
    

    不能通过编译。append() 的第二个参数不能直接使用 slice,需使用 … 操作符,将一个切片追加到另一个切片上:append(s1,s2…)。或者直接跟上元素,形如:append(s1,1,2,3)。

    7、全局变量用:=声明问题

    var(
        size := 1024
        max_size = size*2
    )
    
    func main() {
        fmt.Println(size,max_size)
    }
    

    :=只能在函数内部使用

    8、结构体比较问题

    func main() {
        sn1 := struct {
            age  int
            name string
        }{age: 11, name: "qq"}
        sn2 := struct {
            age  int
            name string
        }{age: 11, name: "qq"}
    
        if sn1 == sn2 {
            fmt.Println("sn1 == sn2")
        }
    
        sm1 := struct {
            age int
            m   map[string]string
        }{age: 11, m: map[string]string{"a": "1"}}
        sm2 := struct {
            age int
            m   map[string]string
        }{age: 11, m: map[string]string{"a": "1"}}
    
        if sm1 == sm2 {
            fmt.Println("sm1 == sm2")
        }
    }
    

    编译不通过 invalid operation: sm1 == sm2;

    • 结构体只能比较是否相等,但是不能比较大小。
    • 相同类型的结构体才能够进行比较,结构体是否相同不但与属性类型有关,还与属性顺序相关
    • 如果 struct 的所有成员都可以比较,则该 struct 就可以通过 == 或 != 进行比较是否相等,比较时逐个项进行比较,如果每一项都相等,则两个结构体才相等,否则不相等;

    那什么是可比较的呢,常见的有 bool、数值型、字符、指针、数组等。像切片、map、函数等是不能比较的。 具体可以参考 Go 说明文档。

    9、iota的使用

    const (
         x = iota
         _
         y
         z = "zz"
         k 
         p = iota
     )
    
    func main()  {
        fmt.Println(x,y,z,k,p)
    }
    

    编译通过,输出:0 2 zz zz 5。知识点:iota 的使用.

    • 每次 const 出现时,都会让 iota 初始化为0
    • iota出现后,下面的变量无初始值,会按照iota自增长
    • 下划线_可以跳过该行iota的增长,iota还是按行增长的,知道遇到不为const和_的其他变量

    10、接口类型断言使用

    func GetValue() int {
         return 1
     }
    
     func main() {
         i := GetValue()
         switch i.(type) {
         case int:
             println("int")
         case string:
            println("string")
         case interface{}:
            println("interface")
         default:
            println("unknown")
        }
    }
    

    编译失败。考点:类型选择,类型选择的语法形如:i.(type),其中 i 是接口,type 是固定关键字,需要注意的是,只有接口类型才可以使用类型选择。

    11、不同类型相加问题

    func main() {  
        a := 5
        b := 8.1
        fmt.Println(a + b)
    }
    

    a 的类型是 int,b 的类型是 float,两个不同类型的数值不能相加,编译报错。可以使用类型强转来相加

    12、数组类型比较问题

    func main() {
        a := [2]int{5, 6}
        b := [3]int{5, 6}
        if a == b {
            fmt.Println("equal")
        } else {
            fmt.Println("not equal")
        }
    }
    

    Go 中的数组是值类型,可比较,另外一方面,数组的长度也是数组类型的组成部分,所以 a 和 b 是不同的类型,是不能比较的,所以编译错误。

    13、map删除不存在的值和获取不存在的值

    func main() {  
        s := make(map[string]int)
        delete(s, "h")
        fmt.Println(s["h"])
    }
    

    删除 map 不存在的键值对时,不会报错,相当于没有任何作用;获取不存在的减值对时,返回值类型对应的零值,所以返回 0。

    14、格式化输出问题

    func main() {  
        i := -5
        j := +5
        fmt.Printf("%+d %+d", i, j)
    }
    

    %d表示输出十进制数字,+表示输出数值的符号。

    15、结构体优先调用外层方法

    type People struct{}
    
    func (p *People) ShowA() {
        fmt.Println("showA")
        p.ShowB()
    }
    func (p *People) ShowB() {
        fmt.Println("showB")
    }
    
    type Teacher struct {
        People
    }
    
    func (t *Teacher) ShowB() {
        fmt.Println("teacher showB")
    }
    
    func main() {
        t := Teacher{}
        t.ShowB()
    }
    

    外部类型通过嵌套可以继承内部结构体的方法,外部类型还可以定义自己的属性和方法,甚至可以定义与内部相同的方法,这样内部类型的方法就会被“屏蔽”。这个例子中的 ShowB() 就是同名方法。

    16、defer参数传递副本

    func hello(i int) {  
        fmt.Println(i)
    }
    func main() {  
        i := 5
        defer hello(i)
        i = i + 10
    }
    

    这个例子中,hello() 函数的参数在执行defer语句的时候会保存一份副本,在实际调用 hello() 函数时用,所以是输出5

    17、字符串只读

    func main() {
        str := "hello"
        str[0] = 'x'
        fmt.Println(str)
    }
    

    Go 语言中的字符串是只读的。所以编译错误compilation error

    18、整数强转字符串

    func main() {  
        i := 65
        fmt.Println(string(i))
    }
    

    UTF-8 编码中,十进制数字 65 对应的符号是 A。输出A

    19、切片长度问题

    func main() {
    
        s := [3]int{1, 2, 3}
        a := s[:0]
        b := s[:2]
        c := s[1:2:cap(s)]
    }
    

    a长度是0,容量是3;

    b长度是2,容量是3;

    c长度是1,容量是2;cap(s)虽然是3,但是子切片的容量不能大于底层数组的长度

    截取操作有带 2 个或者 3 个参数,形如:[i:j] 和 [i:j:k],假设截取对象的底层数组长度为 l。在操作符 [i:j] 中,如果 i 省略,默认 0,如果 j 省略,默认底层数组的长度,截取得到的切片长度和容量计算方法是 j-i、l-i。操作符 [i:j:k],k 主要是用来限制切片的容量,但是不能大于数组的长度 l,截取得到的切片长度和容量计算方法是 j-i、k-i。

    20、闭包引用和匿名函数问题

    type Person struct {
        age int
    }
    
    func main() {
        person := &Person{28}
    
        // 1. 
        defer fmt.Println(person.age)
    
        // 2.
        defer func(p *Person) {
            fmt.Println(p.age)
        }(person)  
    
        // 3.
        defer func() {
            fmt.Println(person.age)
        }()
    
        person.age = 29
    }
    

    1.person.age 此时是将 28 当做 defer 函数的参数,会把 28 缓存在栈中,等到最后执行该 defer 语句的时候取出,即输出 28;

    2.defer 缓存的是结构体 Person{28} 的地址,最终 Person{28} 的 age 被重新赋值为 29,所以 defer 语句最后执行的时候,依靠缓存的地址取出的 age 便是 29,即输出 29;

    3.闭包引用,输出 29;

    又由于 defer 的执行顺序为先进后出,即 3 2 1,所以输出 29 29 28。

    21、错吧字符串和nil比较的问题

    package main
    import (
        "fmt"
    )
    func main() {
        var x string = nil // 错误1
        if x == nil { // 错误2
            x = "default"
        }
        fmt.Println(x)
    }
    

    golang 的字符串类型是不能赋值 nil 的,也不能跟 nil 比较。

    22、return后的defer无效问题

    var a bool = true
    func main() {
        defer func(){
            fmt.Println("1")
        }()
        if a == true {
            fmt.Println("2")
            return
        }
        defer func(){
            fmt.Println("3")
        }()
    }
    

    输出2 1; defer 关键字后面的函数或者方法想要执行必须先注册,return 之后的 defer 是不能注册的, 也就不能执行后面的函数或方法

    23、切片共享底层数组,append扩容后生成新的数组

    func main() {
    
        s1 := []int{1, 2, 3}
        s2 := s1[1:]
        s2[1] = 4
        fmt.Println(s1)
        s2 = append(s2, 5, 6, 7)
        fmt.Println(s1)
    }
    
    [1 2 4]
    
    [1 2 4]
    

    1、golang 中切片底层的数据结构是数组。当使用 s1[1:] 获得切片 s2,和 s1 共享同一个底层数组,这会导致 s2[1] = 4 语句影响 s1。

    2、append 操作会导致底层数组扩容,生成新的数组,因此追加数据后的 s2 不会影响 s1。

    24、map无序问题

    func main() {
        m := map[int]string{0:"zero",1:"one"}
        for k,v := range m {
            fmt.Println(k,v)
        }
    }
    
    # 由于map无序,所以输出结果为
    0 zero
    1 one
    # 或者
    1 one
    0 zero
    

    25、defer嵌套其他函数问题

    func main() {
        a := 1
        b := 2
        defer calc("1", a, calc("10", a, b))
        a = 0
        defer calc("2", a, calc("20", a, b))
        b = 1
    }
    
    func calc(index string, a, b int) int {
        ret := a + b
        fmt.Println(index, a, b, ret)
        return ret
    }
    
    10 1 2 3
    20 0 2 2
    2 0 2 2
    1 1 3 4
    

    defer会预先把需要的值存起来,如果该值是一个函数的返回,会先计算。之后再按照defer倒序输出

    26、指针接收者实现接口,值无法调用问题

    type People interface {
        Speak(string) string
    }
    
    type Student struct{}
    
    func (stu *Student) Speak(think string) (talk string) {
        if think == "speak" {
            talk = "speak"
        } else {
            talk = "hi"
        }
        return
    }
    
    func main() {
        var peo People = Student{}
        think := "speak"
        fmt.Println(peo.Speak(think))
    }
    

    编译失败;原因是基础面试题26点,注意事项第二点。如果是值接收者,实体类型的值和指针都可以实现对应的接口;如果是指针接收者,那么只有类型的指针能够实现对应的接口

    27、接口赋值为nil问题

    type People interface {
        Show()
    }
    
    type Student struct{}
    
    func (stu *Student) Show() {
    
    }
    
    func main() {
    
        var s *Student
        if s == nil {
            fmt.Println("s is nil")
        } else {
            fmt.Println("s is not nil")
        }
        var p People = s
        if p == nil {
            fmt.Println("p is nil")
        } else {
            fmt.Println("p is not nil")
        }
    }
    
    s is nil
    
    p is not nil
    

    这道题看似有点诧异,我们分配给变量 p 的值明明是 nil,然而 p 却不是 nil。记住一点,当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。上面的代码,给变量 p 赋值之后,p 的动态值是 nil,但是动态类型却是 *Student,是一个 nil 指针,所以相等条件不成立

    28、go中不同类型不能比较

    func main() {
        fmt.Println([...]int{1} == [2]int{1})
        fmt.Println([]int{1} == []int{1})
    }
    

    有两处错误:

    1、go 中不同类型是不能比较的,而数组长度是数组类型的一部分,所以 […]int{1} 和 [2]int{1} 是两种不同的类型,不能比较;

    2、切片是不能比较的;

    29、循环且追加切片

    func main() {
        v := []int{1, 2, 3}
        for i := range v {
            v = append(v, i)
        }
    }
    
    fmt.Println(v)
    
    [1 2 3 0 1 2]
    

    不会出现死循环,能正常结束。循环次数在循环开始前就已经确定,循环内改变切片的长度,不影响循环次数。注意,这点跟java不一样

    public class Test {
    
        public static void main(String[] args) {
            List<Integer> lss = new ArrayList<>();
            lss.add(1);
            lss.add(2);
            lss.add(3);
            for (Integer integer : lss) {
                lss.add(integer);
            }
    
            System.out.println(JSON.toJSON(lss));
        }
    }
    
    # java的这段循环,会报错
    Exception in thread "main" java.util.ConcurrentModificationException
    	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
    	at java.util.ArrayList$Itr.next(ArrayList.java:859)
    	at com.dtstack.common.test.main(test.java:22)
    

    30、协程闭包引用问题

    func main() {
    
        var m = [...]int{1, 2, 3}
    
        for i, v := range m {
            go func() {
                fmt.Println(i, v)
            }()
            
            // 正确写法
            // go func(i,v int) {
            //     fmt.Println(i, v)
            // }(i,v)
        }
    
        time.Sleep(time.Second * 3)
    }
    
    2 3
    2 3
    2 3
    

    各个 goroutine 中输出的 i、v 值都是 for range 循环结束后的 i、v 最终值,而不是各个goroutine启动时的i, v值。可以理解为闭包引用,使用的是上下文环境的值。

    31、数组传递给for循环也是值拷贝

    func main() {
        var a = [5]int{1, 2, 3, 4, 5}
        var r [5]int
    
        for i, v := range a {
        // 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)
    }
    
    r =  [1 2 3 4 5]
    a =  [1 12 13 4 5]
    

    可以对数组的地址进行for循环,或者使用切片都可

    func main() {
    	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)
    }
    
    r =  [1 12 13 4 5]
    a =  [1 12 13 4 5]
    

    32、切片扩容生成新的底层数组

    func change(s ...int) {
        s = append(s,3)
    }
    
    func main() {
        slice := make([]int,5,5)
        slice[0] = 1
        slice[1] = 2
        // 触发扩容
        change(slice...)
        fmt.Println(slice)
        // 新切片长度为2,容量为5。不会触发扩容,所以会改变底层数组
        change(slice[0:2]...)
        fmt.Println(slice)
    }
    
    [1 2 0 0 0]
    [1 2 3 0 0]
    

    Go 提供的语法糖…,可以将 slice 传进可变函数,不会创建新的切片。第一次调用 change() 时,append() 操作使切片底层数组发生了扩容,原 slice 的底层数组不会改变;第二次调用change() 函数时,使用了操作符[i,j]获得一个新的切片,假定为 slice1,它的底层数组和原切片底层数组是重合的,不过 slice1 的长度、容量分别是 2、5,所以在 change() 函数中对 slice1 底层数组的修改会影响到原切片。

    33、通道channal和select机制

    func main() {
        runtime.GOMAXPROCS(1)
        int_chan := make(chan int, 1)
        string_chan := make(chan string, 1)
        int_chan <- 1
        string_chan <- "hello"
        select {
        case value := <-int_chan:
            fmt.Println(value)
        case value := <-string_chan:
            panic(value)
        }
    }
    

    select 会随机选择一个可用通道做收发操作,所以可能触发异常,也可能不会。

    34、常量寻址问题

    const i = 100
    var j = 123
    
    func main() {
        fmt.Println(&j, j)
        fmt.Println(&i, i)
    }
    

    常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用,所以常量无法寻址。

    35、协程间调度问题

    func main() {
         ch := make(chan int, 100)
         // A
         go func() {              
             for i := 0; i < 10; i++ {
                 ch <- i
             }
         }()
         // B
        go func() {
            for {
                a, ok := <-ch
                if !ok {
                    fmt.Println("close")
                    return
                }
                fmt.Println("a: ", a)
            }
        }()
        close(ch)
        fmt.Println("ok")
        time.Sleep(time.Second * 10)
    }
    

    程序会抛异常。先定义下,第一个协程为 A 协程,第二个协程为 B 协程;当 A 协程还没起时,主协程已经将 channel 关闭了,当 A 协程往关闭的 channel 发送数据时会 panic,panic: send on closed channel。

    36、nil的map不能直接赋值,nil的切片可以用append增加元素

    func main() {
    
        var s []int
        s = append(s,1)
    
        var m map[string]int
        m["one"] = 1  // 报错
    }
    

    37、常量默认赋值问题

    const (
         x uint16 = 120
         y
         s = "abc"
         z
     )
     func main() {
        fmt.Printf("%T %v
    ", y, y)
        fmt.Printf("%T %v
    ", z, z)
    }
    
        uint16 120
        string abc
    

    38、结构体变量可导出,json反序列化

    type People struct {
         name string `json:"name"`
    }
    func main() {
         js := `{
             "name":"seekload"
         }`
         var p People
         err := json.Unmarshal([]byte(js), &p)
         if err != nil {
            fmt.Println("err: ", err)
            return
        }
        fmt.Println(p)
    }
    
    {}
    

    结构体访问控制,因为 name 首字母是小写,导致其他包不能访问,所以输出为空结构体。

    39、结构体虽然值传递,引用字段仍然可操作底层结构

    type T struct {
        ls []int
    }
    func foo(t T) {
        t.ls[0] = 100
    }
    func main() {
        var t = T{
            ls: []int{1, 2, 3},
        }
        foo(t)
        fmt.Println(t.ls[0])
    }
    
    100
    

    调用 foo() 函数时虽然是传值,但 foo() 函数中,字段 ls 依旧可以看成是指向底层数组的指针。

    40、函数只能和nil比较,不能同其他函数比较

    func main() {
        var fn1 = func() {}
        var fn2 = func() {}
        if fn1 != fn2 {
           println("fn1 not equal fn2")
        }
    }
    
    invalid operation: fn1 != fn2 (func can only be compared to nil)
    

    41、recover捕获panic,函数遇panic就不会往下执行

    func f() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("recover:%#v", r)
            }
        }()
        panic(1)
        panic(2)
    }
    
    func main() {
        f()
    }
    
    recover:1
    

    当程序 panic 时就不会往下执行,可以使用 recover() 捕获 panic 的内容。这里panic(2)得不到执行

    42、WaitGroup的wait和add和done问题

    func main() {
        var wg sync.WaitGroup
        wg.Add(1)
        go func() {
            fmt.Println("1")
            wg.Done()
            wg.Add(1)
        }()
        wg.Wait()
    }
    

    协程里面,使用 再次wg.Add(1) 但是没有其他协程 wg.Done(),导致 panic()。

    43、切片共享底层数组及扩容问题

    func main() {
        a := [3]int{0, 1, 2}
        s := a[1:2] // {1}
    
        s[0] = 11 // 由于共享底层数组,所以a变为{0, 11, 2} s变为{11}
        s = append(s, 12) // a变为{0, 11, 12} s变为{11, 12}
        s = append(s, 13) // 超过cap,s发生扩容,不再共享a的底层数组 s变为{11, 12, 13} a仍为{0, 11, 12}
        s[0] = 21 // s变为{21, 12, 13} 底层数组不共享a,a仍然为{0, 11, 12}
    
        fmt.Println(a)
        fmt.Println(s)
    }
    
        [0 11 12]
        [21 12 13]
    

    44、切片拷贝问题

    func main() {
        var src, dst []int
        src = []int{1, 2, 3}
        copy(dst, src) 
        fmt.Println(dst)
    }
    
    []
    

    copy(dst, src) 函数返回 len(dst)、len(src) 之间的最小值。如果想要将 src 完全拷贝至 dst,必须给 dst 分配足够的内存空间。

    可以预分配空间拷贝,或者使用append函数拷贝

    func main() {
        var src, dst []int
        src = []int{1, 2, 3}
        dst = make([]int, len(src))
        n := copy(dst, src)
        fmt.Println(n,dst)
    }
    
    func main() {
        var src, dst []int
        src = []int{1, 2, 3}
        dst = append(dst, src...)
        fmt.Println("dst:", dst)
    }
    

    45、go的锁不可重入

    var mu sync.Mutex
    var chain string
    
    func main() {
        chain = "main"
        A()
        fmt.Println(chain)
    }
    func A() {
        mu.Lock()
        defer mu.Unlock()
        chain = chain + " --> A"
        B()
    }
    
    func B() {
        chain = chain + " --> B"
        C()
    }
    
    func C() {
        mu.Lock()
        defer mu.Unlock()
        chain = chain + " --> C"
    }
    
    fatal error: all goroutines are asleep - deadlock!
    

    会fatal error错误。使用 Lock() 加锁后,不能再继续对其加锁,直到利用 Unlock() 解锁后才能再加锁。

    46、结构体带有锁,在赋值的之后会一并赋值当前锁状态

    type MyMutex struct {
        count int
        sync.Mutex
    }
    
    func main() {
        var mu MyMutex
        mu.Lock()
        var mu1 = mu
        mu.count++
        mu.Unlock()
        mu1.Lock()
        mu1.count++
        mu1.Unlock()
        fmt.Println(mu.count, mu1.count)
    }
    
    fatal error: all goroutines are asleep - deadlock!
    

    加锁后复制变量,会将锁的状态也复制,所以 mu1 其实是已经加锁状态,再加锁会死锁。

  • 相关阅读:
    程序数据集算地数据库
    使用属性升级mybank
    第一个C#程序
    CSS3动画
    定位网页元素的解析
    CSS3中的浮动
    CSS中的盒子模型
    (十三)mybatis 整合 ehcache
    (十二)mybatis 查询缓存
    (十一)延迟加载
  • 原文地址:https://www.cnblogs.com/darope/p/14478473.html
Copyright © 2011-2022 走看看