Go 面试题

问题集合

此处先展示题目,后面会有题目解析。

for rangemapPrintf(),Sprintf(),FprintF()

答案解析

1. go 语言中的关键字有哪些?提示一共有25个。

// 1. 程序声明:
        import
        package
// 2. 实体声明和定义:
        chan    // 通道
        const   // 常量声明
        func    // 函数声明
        interface   // 接口声明
        map     // map 声明
        struct  // 结构体声明
        type    // 类型声明
        var     // 变量声明
// 3. 流程控制
        go      // 开启协程
        select  // 
        break   // 跳出循环
        case    // switch选择选项
        continue    // 跳出当前循环
        default     // switch默认区域
        else        // 条件判断
        fallthrough // 
        for         // 循环控制程序
        goto        // 并发控制程序运行函数
        if          // 条件结构程序
        range       // 循环遍历
        return      // 函数返回
        switch      // 选项选择

2. go 语言中类型是如何定义的?

拿字符串来举例,可以有下面三种方式:

func main() {
    var a string = "a"  // 定义类型并赋值
    b := "b"    // 自动判断类型并赋初值
    var c string    // 只定义类型不赋初值
    c = "c"         // 变量后面随时可以修改
    var d = "d"     // 不指明类型的并赋初值

    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(d)

}

3. go 语言全局变量的定义方式是怎么样的?

var a string = "a"

var b = "b"

c := "c"

var d string
d = "d"

func main() {

    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(d)

}

4. go 语言中的结构体是如何定义的?

struct
type Person struct {
    name string
    age int
}

func main() {
    p1 := Person{"Alice", 20}

    var p2 Person
    p2.name  "Panda"
    p2.age = 24

    fmt.Println(p1)
    fmt.Println(p2)
}

5. go 语言通过指针访问成员变量的方式有几种?

1. 直接使用 ``:=`` 可以获取变量的地址
2. 用&xxxx来获取地址
func main() {

    person := Person{"Alice", 20}

    p1 := &person

    fmt.Println(person)
    fmt.Println(&person)
    fmt.Println(&p1)
    fmt.Println(*p1)

    fmt.Println(person.name)
    fmt.Println(&person.name)
    fmt.Println(p1.name)
    fmt.Println(&p1.name)
}

type Person struct {
    name string
    age  int
}

6. go 语言格式化输出的方式有哪些?

- %T 类型
- %t 布尔
- %d 10进制整数
- %x 16进制整数
- %f     浮点数
- %s     字符串
person := Person{"Alice", 20}
    fmt.Printf("%T\n", person)

    flag := true
    fmt.Printf("%t\n", flag)

    number10 := 99
    fmt.Printf("%d\n", number10)

    //十进制100为16进制的64
    number16:= 0x64
    fmt.Printf("%X\n", number16)
    fmt.Printf("%d\n", number16)

    number0:= 0.123
    fmt.Printf("%f\n", number0)

    str:= "hello world"
    fmt.Printf("%s\n", str)

7. go 语言中的接口作用是什么?一个接口如果实现了一个接口的所有函数,那么?

一个类如果实现了一个接口的所有函数,那么这个类就实现了这个接口。

go 语言接口的作用是,可以实现OO面向对象的特性,从语法上看,Interface定义了一个或一组method(s),这些method(s)只有函数签名,没有具体的实现代码(有没有联想起C++中的虚函数?)。下面是一个运用的实例:

type MyInterface interface{
       Print()
   }

   func TestFunc(x MyInterface) {}
   type MyStruct struct {}
   func (me MyStruct) Print() {}

   func main() {
       var me MyStruct
       TestFunc(me)
   }

8. go 语言中 init 函数有什么特性?能够在一个包里面写多个init吗?

init 是初始化函数,在包引入的时候就会调用,一个包可以写多个init函数。

init 函数的主要作用是:

  • 初始化不能采用初始化表达式初始化的变量;
  • 程序运行前的注册;
  • 实现 sync.Once 的功能;
  • 其他;

init 函数的主要特点:

  • init 函数咸鱼main函数自动执行,不能被其他函数调用;
  • init 函数没有输入参数、返回值;
  • 每个包可以有多个init函数;
  • 同一个包的init执行顺序,golang没有明确定义,编程时注意不要依赖这个执行顺序。;
  • 不同包的init函数的执行顺序依照包导入的依赖关系决定执行顺序。

具体可以参考下面的例子:

package main                                                                                                                     

import (
   "fmt"              
)

var T int64 = a()

func init() {
   fmt.Println("init in main.go ")
}

func a() int64 {
   fmt.Println("calling a()")
   return 2
}
func main() {                  
   fmt.Println("calling main")     
}

输出:

calling a()
init in main.go
calling main

初始化顺序:变量初始化->init()->main()

实例2:

func init() {
    fmt.Println("init 1")
}

func init() {
    fmt.Println("init 2")
}

func main() {
    fmt.Println("main")
}

输出:

init 1
init 2
main

9. go 语言如何定义多参数函数, 调用其的方式有哪些?

可以直接在函数参数列表里输入...,来定义多参数函数

func add(args ...int) int {}

// 这个函数的调用方式有:
add(1, 2, 3)
add([]int{1, 2, 3}...)

这样就可以传入多个参数来给函数了。

10. go 语言中是如何进行类型转换的?

类型名称(变量)

举例如下:

type MyInt int
var a int = 1
var b MyInt = MyInt(a)

11. go 语言中引用类型有哪些?

  • slice
  • map
  • channel

Golang的引用类型包括 slice、map 和 channel。它们有复杂的内部结构,除了申请内存外,还需要初始化相关属性。

12. go 语言中引用的作用是什么?

能够让外部变量直接操作某块内存地址。 内置函数 new 计算类型大小,为其分配零值内存,返回指针。而 make 会被编译器翻译 成具体的创建函数,由其分配内存和初始化成员结构,返回对象而非指针。

引用类型:

变量存储的是一个地址,这个地址所存储的值,
在内存上通常是将其分配在堆上面,
在程序中通过GC来进行回收。
获取指针类型的所指向的值,
可以使用: “*”取值符号。
比如 var *p int, 使用*p获取p指向的值
指针、slice、map、chan都是引用类型。

13. go 语言main函数的特点有什么?

  • main函数不能带参数
  • main函数不能定义返回值
  • main函数所在的包必须为main
  • main函数中可以使用flag包来获取和解析命令行参数

14. slice切片是如何初始化的?

实现切片初始化的方式,大致我学习到的可以分为两种:

  • 使用make进行切片的初始化
  • 使用数组直接定义初始化

具体可以参考下面的代码:

func main() {
    s1 := make([]int, 0)
    s2 := make([]int, 6,10)
    s3 := []int{1, 2, 3, 4, 5}

    fmt.Println(s1)
    fmt.Println(s2)
    fmt.Println(s3)

    fmt.Println(len(s2))
    fmt.Println(cap(s2))
}

15. go 语言中函数的定义方式有哪些?请举例说明。

go语言当中函数的定义,大致我概括一下可以分为如下几类:

  • 不带参数的函数定义
  • 带参数的函数定义
  • 返回值有/无标识的函数定义
  • 多参数函数定义
  • 类函数定义

具体可以参考如下的代码:

// - 不带参数的函数定义

func main() {
    r1, r2 := getResult(1, 2)
    fmt.Println(r1)
    fmt.Println(r2)
}

// - 带参数的函数定义

func getResult(a int, b int) (c int, d int) {
    return a + b, a - b
}

// - 返回值有/无标识的函数定义

func getResult(a int, b int) (int, int) {
    return a + b, a - b
}

func getResult(a int, b int) (c int, d int) {
    return a + b, a - b
}

// - 多参数函数定义
func getResult(a int , b int , c ...int) (c int, d int) {
    return a + b, a - b
}

// - 类成员函数定义

type Person struct {
    name string
    age int
}

func (p Person) setName(n string) {
    if n == "" {
        p.name = "NaN"
    }
    p.name = n
}

关于可变参数的讲解,可以参考: Go语言“可变参数函数”终极指南 关于go中面向对象定义类成员函数,可以参考: go中的面向对象

这里展示一块面向对象使用的代码:

package main
import "fmt"

type Human struct {
    height float32
    weight int
}

func (h Human) BMIindex() (index int){
    index = h.weight / int(h.height * h.height)
    return
}

func (h Human) setHeight(height float32) {
    h.height = height
}

func main() {
    person := Human{1.83, 75}
    fmt.Printf("this person's height is %.2f m\n", person.height)
    fmt.Printf("this person's weight is %d kg\n", person.weight)
    fmt.Printf("this person's BMI index is %d\n", person.BMIindex())
    person.setHeight(1.90)
    fmt.Printf("this person's height is %.2f m\n", person.height)
}

输出结果:
    this person's height is 1.83 m
    this person's weight is 75 kg
    this person's BMI index is 25
    this person's height is 1.83 m

可以看出,我们调用person.setHeight(1.90)之后,person的height属性并没有改变为1.90。而为了解决这个问题,我们需要改变receiver。我们将setHeight()函数定义为下述形式即可。

func (h *Human) BMIindex() (index int){
    index = h.weight / int(h.height * h.height)
    return
}

16. go 两个接口之间可以存在什么关系?

  • 如果两个接口有相同的方法列表,那么他们就是等价的,可以相互赋值。
  • 如果接口A的方法列表是接口B的方法列表的自己,那么接口B可以赋值给接口A。
  • 接口查询是否成功,要在运行期才能够确定。

17. go 当中同步锁有什么特点?作用是什么

  • 当一个goroutine(协程)获得了Mutex后,其他gorouline(协程)就只能乖乖的等待,除非该gorouline释放了该Mutex
  • RWMutex在 读锁 占用的情况下,会阻止写,但不阻止读
  • RWMutex在 写锁 占用情况下,会阻止任何其他goroutine(无论读和写)进来,整个锁相当于由该goroutine独占

同步锁的作用是保证资源在使用时的独有性,不会因为并发而导致数据错乱,保证系统的稳定性。

18. go 语言当中channel(通道)有什么特点,需要注意什么?

  • 如果给一个 nil 的 channel 发送数据,会造成永远阻塞
  • 如果从一个 nil 的 channel 中接收数据,也会造成永久爱阻塞
  • 给一个已经关闭的 channel 发送数据, 会引起 pannic
  • 从一个已经关闭的 channel 接收数据, 如果缓冲区中为空,则返回一个零值

19. go 语言当中channel缓冲有什么特点?

无缓冲的 channel是同步的,而有缓冲的channel是非同步的。

20. go 语言中cap函数可以作用于那些内容?

cap函数在讲引用的问题中已经提到,可以作用于的类型有:

  • array(数组)
  • slice(切片)
  • channel(通道)

21. go convey是什么?一般用来做什么?

  • go convey是一个支持golang的单元测试框架
  • go convey能够自动监控文件修改并启动测试,并可以将测试结果实时输出到Web界面
  • go convey提供了丰富的断言简化测试用例的编写

22. go 语言中类型断言是什么?其作用是什么?举例说明。

详细的类型断言的学习,可以参考: Go语言圣经-类型断言

在go语言中断言的代码举例如下:

func main() {

    m := make(map[int]interface{})
    m[0] = Person{}
    m[1] = "abc"

    // 两个参数分别获得值和对应是否是相同类型
    r1, r2 := m[0].(Person)
    r3, r4 := m[1].(string)

    fmt.Println(r1)
    fmt.Println(r2)
    fmt.Println(r3)
    fmt.Println(r4)

}
输出结果为
{}
true
abc
true

一个类型断言的作用是:一个类型断言检查它操作对象的动态类型是否和断言的类型匹配。

类型断言是一个使用在接口值上的操作。语法上它看起来像$x.(T)$被称为断言类型,这里x表示一个接口的类型和T表示一个类型。

23. go 语言当中,切片是如何删除元素的?

在go中想要对切片进行元素的删除,具体过程如下:

func main() {

    s := make([]string, 0)
    s = append(s, "abc0")
    s = append(s, "abc1")
    fmt.Println(s)

    s = append(s[:0], s[0+1:]...)
    fmt.Println(s)

    s = append(s[:0], s[0+1:]...)
    fmt.Println(s)
}

// 输出结果
[abc0 abc1]
[abc1]
[]

24. go 语言当中,如果对json进行重命名?

在go当中,我们可以利用json库来对某些结构体进行相关的json格式化重命名的操作,来完成对应的数据类型的转换。

func main() {

    p1 := Person{"Alice", 20}
    fmt.Println(p1)

    bytes, _ := json.Marshal(p1)
    fmt.Println(string(bytes))

}

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

// 输出结果
{Alice 20}
{"name":"Alice","age":20}

25. go 语言当中,是如何实现类似继承的操作的?

我们通过在一个新的结构体里面,定义某个类型的数据的对象,就能够实现继承这个类的数据的操作。比如类似下面这样:

func main() {
    stu := Student{Person{"Panda", 20}}
    fmt.Println(stu)
}

type Person struct {
    Name string
    Age int
}

type Student struct {
    Person
}


输出结果
{{Panda 20}}
for rangemap
for rangemapmap
func main() {
    m := make(map[string]int)
    m["string"] = 1

    m["int"] = 1
    m["float"] = 2
    m["bool"] = 3
    m["byte"] = 4


    for k, v := range m {
        fmt.Println(k, ",", v)
    }

    // 打印的顺序会出现不一样的情况
}

27. go 语言中基本的数据类型有哪些?

一共有18个,主要有:

1. bool
2. string
3. byte
4. int
5. uint
6. float

28. go 语言中switch是如何运用的?有什么特殊的地方?

go 当中switch语句和其他语言类似,只是有一个特殊的地方,switch后面可以不跟表达式

func main() {
    i := rand.Intn(2)

    switch i {
    case 0:
        fmt.Println("get 0")
    case 1:
        fmt.Println("get 1")
    }
}

// switch后面可以不跟表达式
func main() {
    i := rand.Intn(2)

    switch {
    case i == 0:
        fmt.Println("get 0")
    case i == 1:
        fmt.Println("get 1")
    }
}

29. go 语言结构体在序列化时,非导出变量(以小写字母开头的变量名)在解码的时候会出现什么问题?为什么?

结构体在序列化的时候非导出变量(以小写字母开头的变量名)不会被encode,所以在decode时这些非到处变量的值为其类型的零值。

30. go 语言当中 new 和 make 有什么区别吗?

newnew
func new(Type) *Type
newnew

那么可以看一下代码

func main() {
    p := new(Person)
    person := Person{"Panda"}
    fmt.Printf("%T\n",p)
    fmt.Printf("%T\n",person)

}


type Person struct {
    name string
}


// 输出结果为
// *main.Person
// main.Person

// 所以new函数返回的是指针
// person是实体

31. go 语言中 make 的作用是什么?

makeslice, map or chanmake
func make(Type, size IntegerType) Type
make(T, args)new(T)slice, map, channel
Printf(),Sprintf(),FprintF()

虽然这三个函数,都是格式化输出,但是输出的目标不一样

Printf
Sprintf()
func main() {
    person := Person{"Alice"}
    s := fmt.Sprintf("类型是%T\n", person)
    fmt.Println(s)
}
Fprintf()
file, e := os.OpenFile("f:1.txt", os.O_RDWR, 0777)
defer file.Close()
if e != nil {
    fmt.Println(e)
}

fmt.Fprintln(file, "Hello, world")

33. go 语言当中数组和切片的区别是什么?

数组:

[3]int[4]int

切片:

make()len=cap

34. go 语言当中值传递和地址传递(引用传递)如何运用?有什么区别?举例说明

  1. 值传递只会把参数的值复制一份放进对应的函数,两个变量的地址不同,不可相互修改。
  2. 地址传递(引用传递)会将变量本身传入对应的函数,在函数中可以对该变量进行值内容的修改。

具体我们可以参考如下的代码:

func main() {
    p1 := Person{"Alice"}
    p2 := Person{"Bob"}
    change(p1)
    changeAddress(&p2)

    fmt.Println(p1)
    fmt.Println(p2)
}
 // 值传递
func change(p Person) {
    p.name = "Hello"
}

// 地址传递 
func changeAddress(p *Person) {
    p.name = "Hello"
}

type Person struct {
    name string
}

35. go 语言当中数组和切片在传递的时候的区别是什么?

1. 数组是值传递 2. 切片是引用传递

具体可以参考下面的代码:

func main() {

    arr1 := [3]int{1, 2, 3}
    arr2 := []int{1, 2, 3}

    changeArr(arr1)
    changeSlice(arr2)

    fmt.Printf("%T\n", arr1)
    fmt.Printf("%T\n", arr2)

    fmt.Println(arr1)
    fmt.Println(arr2)
}

func changeArr(arr [3]int) {
    arr[0] = 9
}

func changeSlice(arr []int)  {
    arr[0] = 9
}


结果为
[1 2 3]
[9 2 3]

36. go 语言如何完成写入文件的操作的 ?

file, err := os.OpenFile("f:/1.txt", os.O_RDWR, 0777)
    defer file.Close()
    if err != nil {
        fmt.Println(err)
        return
    }

    file.WriteString("hello world")

37. go 语言是如何实现切片扩容的?

func main() {
    arr := make([]int, 0)
    for i := 0; i < 2000; i++ {
        fmt.Println("len为", len(arr), "cap为", cap(arr))
        arr = append(arr, i)
    }
}


我们可以看下结果
依次是
0,1,2,4,8,16,32,64,128,256,512,1024
但到了1024之后,就变成了
1024,1280,1696,2304
每次都是扩容了四分之一左右

38. go 语言如何实现类似foreach的操作的?

我们下来看一下使用代码:

func pase_student() {
    m := make(map[string]*student)
    stud := []student{
        {Name:"zhou", Age:24},
        {Name: "li", Age: 23},
        {Name: "wang", Age: 22},
    }

    for _, stu := range stus {
        m[stu.Name] = &stu
    }

    fmt.Println(m)

}


看下结果
map[zhou:0xc000004440 li:0xc000004440 wang:0xc000004440]


我们再看一段直观一点的代码
    stus := []student{
        {Name: "zhou", Age: 24},
        {Name: "li", Age: 23},
        {Name: "wang", Age: 22},
    }

    for _, stu := range stus {
        fmt.Printf("%p\n",&stu)
    }
结果是
0xc000052400
0xc000052400
0xc000052400


原因是foreach使用副本的方式,所以&stu实际上
指向的是同一个指针
最终该指针的值是最后一个struct的值的拷贝


正确的方法应该是这样
    for i := 0; i < 3; i++ {
        stu:=stus[i]
        fmt.Printf("%p\n",&stu)
    }

39. go 语言中 runtime.GOMAXPROCS 的作用是什么?

在 Go语言程序运行时(runtime)实现了一个小型的任务调度器。这套调度器的工作原理类似于操作系统调度线程,Go 程序调度器可以高效地将 CPU 资源分配给每一个任务。传统逻辑中,开发者需要维护线程池中线程与 CPU 核心数量的对应关系。同样的,Go 地中也可以通过 runtime.GOMAXPROCS() 函数做到,格式为:

runtime.GOMAXPROCS(逻辑CPU数量)

这里的逻辑CPU数量可以有如下几种数值: - $<1$: 不修改任何数值。 - $=1$: 单核心运行。 - $>1$: 多核心运行。

runtime.GOMAXPROCS 的作用是:调整并发的运行性能。

看下代码

func main() {
    runtime.GOMAXPROCS(1)
    wg := sync.WaitGroup{}
    wg.Add(20)
    for i := 0; i < 10; i++ {
        go func() {
            fmt.Println("A: ", i)
            wg.Done()
        }()
    }
    for i := 0; i < 10; i++ {
        go func(i int) {
            fmt.Println("B: ", i)
            wg.Done()
        }(i)
    }
    wg.Wait()
}

先看第一个循环
首先,for循环很快就结束了
然后开启10个go协程
所以输出了10个A:10

再看第二个循环
每次循环把i当做参数传入函数
但是go协程启动的顺序是不一定的
所以输出10个数字,顺序是不一定的

40. go 语言中是如何实现组合继承的?

可以来看一段代码:

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.ShowA()
}

首先,调用ShowA方法
那么会输出ShowA
然后People指针调用ShowB方法
这时候这个指针不知道自己是Teacher
所以还是走People的ShowB
所以结果是
ShowA
ShowB

41. 解释一下 go 语言当中的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中的两个case都满足
那么会随机选择一个来执行
所以程序有可能崩溃,也有可能不会崩溃

42. 看看下面代码的defer的执行顺序是什么? defer的作用和特点是什么?

defer的作用是:

  1. 你只需要在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。
  2. 当defer语句被执行时,跟在defer后面的函数会被延迟执行。
  3. 直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。
  4. 你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。

defer的常用场景: - defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。 - 通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。 - 释放资源的defer应该直接跟在请求资源的语句后。

观察下面的程序中defer的执行顺序是什么

func calc(index string, a, b int) int {
    ret := a + b
    fmt.Println(index, a, b, ret)
    return ret
}

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
}
结果为
"10" 1 2 3
"20" 0 2 2
"2"  0 2 2
"1"  1 3 4

原因:
1. 函数calc调用的时候,b参数是使用的新的calc的返回值,所以先运行作为参数的calc
2. defer会造成延迟运行,所以main中定义的两个defer会按照与定义相反的顺序返回结果

43. 看看下面切片的代码输出是什么,为什么?

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

    // 结果为0,0,0,0,0,1,2,3
    // make初始化是有默认值的,这里默认值是0

}

44. go 语言是如何实现线程安全的?下面这段代码会出现什么情况?请分析

type UserAges struct {
    ages map[string]int
    sync.Mutex
}

func (ua *UserAges) Add(name string, age int) {
    ua.Lock()
    defer ua.Unlock()
    ua.ages[name] = age
}

func (ua *UserAges) Get(name string) int {
    if age, ok := ua.ages[name]; ok {
        return age
    }
    return -1
}


这段代码有可能会出现
fatal error: concurrent map read and map write
原因是在读取的时候没有给数据源加锁

我们可以修改一下代码,保证线程安全

type UserAges struct {
    ages map[string]int
    sync.Mutex
}

func (ua *UserAges) Add(name string, age int) {
    ua.Lock()
    defer ua.Unlock()
    ua.ages[name] = age
}

func (ua *UserAges) Get(name string) int {
    ua.Lock()
    defer ua.Unlock()
    if age, ok := ua.ages[name]; ok {
        return age
    }
    return -1
}

45. go 语言中 cache 缓冲池(chan)是如何实现的?有什么机制?请举例实现代码。

个人小结一下定义go中的缓冲池需要的步骤如下: 1. 定义chan接口 2. 编写线程运行函数 3. 开启线程锁 4. 添加缓冲 5. 释放线程锁 6. 返回缓冲池

具体可以看如下的代码:

func (set *threadSafeSet) Iter() <-chan interface{} {
    ch := make(chan interface{})
    go func() {
        set.RLock()

        for elem := range set.s {
            ch <- elem
        }

        close(ch)
        set.RUnlock()

    }()
    return ch
}

完整的演示示例如下:

type threadSafeSet struct {
    sync.RWMutex
    s []interface{}
}

func (set *threadSafeSet) Iter() <-chan interface{} {
    // ch := make(chan interface{}) // 解除注释看看!
    ch := make(chan interface{}, len(set.s))
    go func() {
        set.RLock()

        for elem, value := range set.s {
            ch <- elem
            fmt.Println("Iter:", elem, value)
        }

        close(ch)
        set.RUnlock()

    }()

    return ch
}

func main() {

    th := threadSafeSet{
        s: []interface{}{"1", "2"},
    }
    v := <-th.Iter()
    fmt.Sprintf("%s%v", "ch", v)
}

运行结果
Iter: 0 1
Iter: 1 2

46. go 语言当中 interface 的内部结构是什么样的?

type People interface {
    Show()
}

type Student struct {}

func (stu *Student) Show() {

}

func live() People {
    var stu *Student
    return stu
}

func main() {
    if live() == nil {
        fmt.Println("AAAAAA")
    } else {
        fmt.Println("BBBBBB")
    }
}
interfaceinterface
var people interface{}
type People interface{
    sayHello()
}

底层结构如下:
type eface struct {      //空接口
    _type *_type         //类型信息
    data  unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
type iface struct {      //带有方法的接口
    tab  *itab           //存储type信息还有结构实现方法的集合
    data unsafe.Pointer  //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
type _type struct {
    size       uintptr  //类型大小
    ptrdata    uintptr  //前缀持有所有指针的内存大小
    hash       uint32   //数据hash值
    tflag      tflag
    align      uint8    //对齐
    fieldalign uint8    //嵌入结构体时的对齐
    kind       uint8    //kind 有些枚举值kind等于0是无效的
    alg        *typeAlg //函数指针数组,类型实现的所有方法
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}
type itab struct {
    inter  *interfacetype  //接口类型
    _type  *_type          //结构类型
    link   *itab
    bad    int32
    inhash int32
    fun    [1]uintptr      //可变大小 方法集合
}

可以看出iface比eface中间多了一层itab结构
itab存储_type信息和[]fun方法集
从上面的结构我们可以看出
data指向了nil
但是并不代表interface是nil
所以返回值不为空
这里的fun方法集定义了接口的接收规则
在编译的过程中需要验证是否实现接口

47. 下面的结构体的结果是什么?

func main() {
    var p1 Person
    var p2 Person
    p2.name= "Alice"
    p3:=Person{}

    fmt.Println(p1)
    fmt.Println(p2)
    fmt.Println(p3)
}

结果为
{}
{Alice}
{}

48. 下面的 channel 会出现什么结果?为什么?

会出现随机的结果,因为go 启动的时机是随机的,所以 num打印和channel的打印顺序是随机交错的。

channel := make(chan int)

    for i := 0; i < 10; i++ {
        go func() {
            fmt.Println(i)
            channel <- i
        }()
    }

    for i := 0; i < 10; i++ {
        num := <-channel
        fmt.Println("num:", num)
    }

49. 解释一下 go 语言的同步锁的机制

  1. 当一个go程获得了Mutex,其他的只能等待,除非这个go程释放这个Mutex
  2. RWMutex在读锁占用的情况下,会阻止写,但不会阻止读
  3. RWMutex在写锁占用的情况下,会阻止写,也阻止读

50. 解释一下 go 语言是一门什么类型的语言?有什么特点,主要能用来做什么?

go语言是Google开发的一种: 1. 静态强类型 2. 编译型 3. 并发型 4. 有垃圾回收功能 的编程语言

51. 解释一下 go 语言当中的强类型是什么?有何作用?

强类型指的是程序中表达的任何对象所从属的类型 都必须能在编译时刻确定 常见的强类型语言有 C++, Java, Python, Golang等 适合大规模信息系统开发

javascript是弱类型的 比如

var a = 1;
var b = 'a';
console.log(a+b)