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