1.recover的执行时机
无,recover 必须在 defer 函数中运行。recover 捕获的是祖父级调用时的异常,直接调用时无效。
func main() {
    recover()
    panic(1)
}
 
直接 defer 调用也是无效。
func main() {
    defer recover()
    panic(1)
}
 
defer 调用时多层嵌套依然无效。
func main() {
    defer func() {
        func() { recover() }()
    }()
    panic(1)
}
 
必须在 defer 函数中直接调用才有效。
func main() {
    defer func() {
        recover()
    }()
    panic(1)
}
 
2.闭包错误引用同一个变量问题怎么处理
在每轮迭代中生成一个局在这里插入代码片部变量 i 。如果没有 i := i 这行,将会打印同一个变量。
func main() {
    for i := 0; i < 5; i++ {
        i := i
        defer func() {
            println(i)
        }()
    }
}
 
或者是通过函数参数传入 i 。
func main() {
    for i := 0; i < 5; i++ {
        defer func(i int) {
            println(i)
        }(i)
    }
}
 
3.在循环内部执行defer语句会发生啥
defer 在函数退出时才能执行,在 for 执行 defer 会导致资源延迟释放。
func main() {
    for i := 0; i < 5; i++ {
        func() {
            f, err := os.Open("/path/to/file")
            if err != nil {
                log.Fatal(err)
            }
            defer f.Close()
        }()
    }
}
 
func 是一个局部函数,在局部函数里面执行 defer 将不会有问题。
4.说出一个避免Goroutine泄露的措施
可以通过 context 包来避免内存泄漏。
func main() {
    ctx, cancel := context.WithCancel(context.Background())
    ch := func(ctx context.Context) <-chan int {
        ch := make(chan int)
        go func() {
            for i := 0; ; i++ {
                select {
                case <- ctx.Done():
                    return
                case ch <- i:
                }
            }
        } ()
        return ch
    }(ctx)
    for v := range ch {
        fmt.Println(v)
        if v == 5 {
            cancel()
            break
        }
    }
}
 
下面的 for 循环停止取数据时,就用 cancel 函数,让另一个协程停止写数据。如果下面 for 已停止读取数据,上面 for 循环还在写入,就会造成内存泄漏。
5.如何跳出for select 循环
通常在for循环中,使用break可以跳出循环,但是注意在go语言中,for select配合时,break 并不能跳出循环。
func testSelectFor2(chExit chan bool){
 EXIT:
    for  {
        select {
        case v, ok := <-chExit:
            if !ok {
                fmt.Println("close channel 2", v)
                break EXIT//goto EXIT2
            }
            fmt.Println("ch2 val =", v)
        }
    }
    //EXIT2:
    fmt.Println("exit testSelectFor2")
}
 
6.如何在切片中查找
go中使用 sort.searchXXX 方法,在排序好的切片中查找指定的方法,但是其返回是对应的查找元素不存在时,待插入的位置下标(元素插入在返回下标前)。
可以通过封装如下函数,达到目的。
func IsExist(s []string, t string) (int, bool) {
    iIndex := sort.SearchStrings(s, t)
    bExist := iIndex!=len(s) && s[iIndex]==t
    return iIndex, bExist
}
 
7.如何初始化带嵌套结构的结构体
go 的哲学是组合优于继承,使用 struct 嵌套即可完成组合,内嵌的结构体属性就像外层结构的属性即可,可以直接调用。
 注意初始化外层结构体时,必须指定内嵌结构体名称的结构体初始化,如下看到 s1方式报错,s2 方式正确。
type stPeople struct {
    Gender bool
    Name string
}
type stStudent struct {
    stPeople
    Class int
}
//尝试4 嵌套结构的初始化表达式
//var s1 = stStudent{false, "JimWen", 3}
var s2 = stStudent{stPeople{false, "JimWen"}, 3}
fmt.Println(s2.Gender, s2.Name, s2.Class)
 
8.切片和数组的区别
数组是具有固定长度,且拥有零个或者多个,相同数据类型元素的序列。数组的长度是数组类型的一部分,所以[3]int 和 [4]int 是两种不同的数组类型。数组需要指定大小,不指定也会根据初始化的自动推算出大小,不可改变;数组是值传递。数组是内置类型,是一组同类型数据的集合,它是值类型,通过从0开始的下标索引访问元素值。在初始化后长度是固定的,无法修改其长度。
 当作为方法的参数传入时将复制一份数组而不是引用同一指针。数组的长度也是其类型的一部分,通过内置函数len(array)获取其长度。数组定义:
var array [10]int
var array =[5]int{1,2,3,4,5}
 
切片表示一个拥有相同类型元素的可变长度的序列。切片是一种轻量级的数据结构,它有三个属性:指针、长度和容量。切片不需要指定大小;切片是地址传递;切片可以通过数组来初始化,也可以通过内置函数make()初始化 。初始化时len=cap,在追加元素时如果容量cap不足时将按len的2倍扩容。切片定义:
var slice []type = make([]type, len)
 
9.new和make的区别
new 的作用是初始化一个指向类型的指针 (*T) 。new 函数是内建函数,函数定义:func new(Type) Type。使用 new 函数来分配空间。传递给 new 函数的是一个类型,不是一个值。返回值是指向这个新分配的零值的指针。
 make 的作用是为 slice,map 或 chan 初始化并返回引用 (T)。make 函数是内建函数,函数定义:func make(Type, size IntegerType) Type;第一个参数是一个类型,第二个参数是长度;返回值是一个类型。
 make(T, args) 函数的目的与 new(T) 不同。它仅仅用于创建 Slice, Map 和 Channel,并且返回类型是 T(不是T)的一个初始化的(不是零值)的实例。
10.Printf()、Sprintf()、Fprintf()函数的区别用法是什么
都是把格式好的字符串输出,只是输出的目标不一样。
 Printf(),是把格式字符串输出到标准输出(一般是屏幕,可以重定向)。Printf() 是和标准输出文件 (stdout) 关联的,Fprintf 则没有这个限制。
 Sprintf(),是把格式字符串输出到指定字符串中,所以参数比printf多一个char*。那就是目标字符串地址。
 Fprintf(),是把格式字符串输出到指定文件设备中,所以参数比 printf 多一个文件指针 FILE*。主要用于文件操作。Fprintf() 是格式化输出到一个stream,通常是到文件。










