Go常见错误(一)

spigcoder 发布于 2025-08-21 90 次阅读


panic和defer

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都有的时候,会首先按照defer先进后出的顺序执行,然后才会调用panic,这里也很好理解,defer的目的是可以保证一些代码在函数或者方法返回之前被调用,即使方法没有正常执行完,发生了panic,defer 后面的代码也会执行。

panic 是 Go 的内置函数,可以打断当前的代码的正常执行流程,如果一个函数中出现 panic,该函数后续的代码都会停止执行。但是会执行 F 中的 defer 代码。然后其他调用 F 函数的地方也会出现 panic,层层向上传递,直到栈顶,最后程序会崩溃。

recover 也是 Go 的内置函数,这个函数可以从 panic 中恢复程序的正常执行。recover 需要和 defer 定义在一起。

在正常的流程中,recover 的执行不会产生任何影响。只有在 panic 发生的时候,recevoer 才会恢复应用,阻止程序崩溃。而 panic 发生的时候只会执行 defer 代码。所以 recover 只在和 defer 搭配的时候才会有意义。recover 和 panic 需要在同一个 goroutine 使用,跨 goroutine 无法恢复应用

这里还有一点要注意的是recover和panic一定要在同一个goroutine中,panic会向上传递是向调用者传递,但是不会向其他goroutine传递,看下面这个代码

func main() {
    wg := sync.WaitGroup{}
    go func() {
        wg.Add(1)
        defer wg.Done()
        defer func() {
            recover()
        }()
        go func() {
            wg.Add(1)
            defer wg.Done()
            defer func() {
                recover()
            }()
            go func() {
                wg.Add(1)
                defer wg.Done()
                panic("panic")
            }()

        }()
    }()
    wg.Wait()
    defer func() {
        recover()
    }()
    log.Println("Go 语言编程之旅:一起用 Go 做项目")
}

这个代码潜逃了好几层goroutine,每一层都进行recover(),但是最后依然没有捕捉到panic,但是在调用方还是会一层层的向上传递,比如下面的代码

func main() {
    defer func() {
        recover() // 可以捕获到 panic
    }()

    funcA() // panic 会从这里向上传递到 main
}

func funcA() {
    funcB()
}

func funcB() {
    panic("error") // panic 发生在这里
}

slice

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

这个代码各位会认为最后结果是多少:[0 0 0 0 0 1 2 3],不知道你们做对了吗,make,如果只有一个参数,那么指定的是长度,也就是说s,本身就是一个长度为5,所有值都是0的切片,然后会在后面加上1、2、3。

如果想要正确的写法,应该是

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

命名返回值

func funcMui(x,y int)(sum int, error){
 return x+y,nil
}

这个函数的问题就在于返回值中,一个变量命名了,但是另一个变量没有命名,所以有问题,正确的做法应该是只要有一个变量命名了,那么所有的变量都要命名。

make vs new

new(T) 会为 T 类型的新值分配已置零的内存空间,并返回地址(指针),即类型为 *T 的值。换句话说就是,返回⼀个指针,该指针指向新分配的、类型为 T 的零值。 适⽤于值类型,如数组、结构体等。

make(T,args) 返回初始化之后的 T 类型的值,这个值并不是 T 类型的零值,也不是指针 *T,是经过初始化之后的 T 的引⽤。make() 只适⽤于 slice、map 和 channel.

结构体是否可以比较

首先在go中结构体要想比较首先要是相同的结构体,不同的结构体即便拥有相同的字段也是不可以比较的比如

type Student struct {
    age  int
    name string
}

type Student2 struct{
    age int
    name string
}

func main() {
    sn1 := Student{age: 11, name: "qq"}
    sn2 := Student2{age: 11, name: "qq"}
    if sn1 == sn2 {
        fmt.Println("sn1 == sn2")
    }
}

在上面的例子中sn1和sn2都是不可以比较的,会编译错误,除此之外,匿名结构体只有在所有的字段都相同(字段的顺序也相同),并且所有的字段类型都是可以比较时才能比较,比如

func main() {
    sn1 := struct {
        age  int
        name string
    }{age: 11, name: "qq"}
    sn2 := struct {
        name int
        age  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")
    }
}

在这个例子中,sn1和sn2是可以比较的,但是sm1和sm2是不可以比较的,因为map是不可以比较类型,map,slice,func都是不可以比较的,当结构体中包含不可比较类型时,结构体就不能比较。

类型别名和类型定义的区别

看一个例子:

type Student struct {
    age  int
    name string
}

func (s *Student) String() string {
    return fmt.Sprintf("age: %d, name: %s", s.age, s.name)
}

type S = Student

func (s *S) Name() string {
    return s.name
}

type su Student

func main() {
    s := &Student{age: 11, name: "qq"}
    fmt.Println(s.Name(), s.String())
    s = &S{age: 11, name: "qq"}
    fmt.Println(s.String(), s.Name())
    // s = &su{age: 11, name: "qq"}
    x := &su{age:11, name:"qq"}
}

在我注释的位置会报编译错误,因为无法将Student,S类型的变量赋值给su类型,这就是因为type S = Student只是给Student另一个名字,我们看到我分别在S和Student两个类型上实现了方法,s两个方法都可以调用,但是如果我们去调用x的方法,会发现一个方法都没有,这是因为su是一个全新的类型,没有剩余的方法可以调用

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表示的偏移量,他会从0开始计数,然后遇到_会跳过数字,如果出现字符串之类的非数字,他会跳过,然后p=iota会继续计数。

类型断言

这里要知道的是只有接口(interface)才可以进行类型断言,具体类型是无法类型断言的

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是int类型的,所以他是无法进行类型断言的。

一名追求技术的Gopher
最后更新于 2025-08-21