golang知识点

1、go 如何表示枚举

type Name int32

const (
    S1 Name = iota
    S2
    S3
)

type HH struct {
    Name string
}

2、字符串打印时%v 和 %+v的区别
    %v会打印结构体属性的值,%+v会打印属性的名称和值。
    
    
3、空struct的用途
    使用空结构体可以节省内存,一般作为占位符使用,表明这里不需要一个具体的值。
    比如使用map表示集合时,只关注key,那么value就可以使用空结构体 struct{}{}表示。
    使用通道(channel)控制并发时,我们只是需要一个信号,但并不需要传值,这时候也可以使用struct{}{}来表示,举例:
    
    ch := make(chan struct{}, 1)

    go func() {
        <-ch
    }()
    ch <- struct{}{}
    
4、判断两个切片是否相等
    ①使用reflect.deepequal
    ②判断所有的元素是否相等
func main() {
    // 如何判断两个切片相等?
    s1 := []string{"", "haha"}
    s2 := []string{"", "haha"}
    ret := CheckSliceEqual(s1, s2)
    klog.Infof("s1 equal s2---> %v", ret)
    ret2:= reflect.DeepEqual(s1, s2)
    klog.Infof("s1 equal s2---> %v", ret2)
}

5、defer执行顺序
    先进后出
    
    // 返回0
    func test() int {
    i := 0
    defer func() {
        println("test() first defer()")
    }()

    defer func() {
        println("test() second defer()")
        i += 1
    }()
    return i
}

    // 返回1
    func test()  (i int){
    i = 0
    defer func() {
        println("test() first defer()")
    }()

    defer func() {
        println("test() second defer()")
        i += 1
    }()
    return i
}


func CheckSliceEqual(s1, s2 []string) bool {
    if len(s1) != len(s2) {
        return false
    }
    if (s1 == nil) != (s2 == nil) {
        return false
    }
    for i, v := range s1 {
        if s2[i] != v {
            return false
        }
    }
    return true
}
6、如何高效地拼接字符串
    strings.Builder
7、Go 语言的局部变量分配在栈上还是堆上?
    由编译器决定,go语言编译器会自动决定把一个变量放在堆还是栈里面,编译器会做陶艺分析,当发现变量的作用域没有超出函数范围,就可以在栈上,否则必须分配在堆上。
    
    func main() {
    // defer的执行顺序
    ret := foo()
    println(*ret)
}

func foo() *int{
    a:=10
    return &a
}

    上面代码,当a分配在栈时,&a就不存在了,go的编译器发现了a的引用脱离了foo函数的作用域,就把a分配在堆上了。因此main函数中仍然能正常访问到该值。
    
8、interface是否可以比较是否相等

func main() {
    var s1, s2 StuInterface = &Stu{""}, &Stu{""}
    var s3, s4 StuInterface = Stu{""}, Stu{""}
    println(s1 == s2) // false
    println(s3 == s4) // false
}

func (Stu) hi() {
}

func (Stu) bye() {
}

false
true 


9、两个nil可能不相等吗?
    可能。接口interface是对非接口值(例如指针,struct的封装)。内部包含2个字段,类型T和值V。一个接口等于nil。当且仅当T和V都是unset状态(T=nil,V is unset)
    两个接口值比较时,会先比较T,再比较V。
    接口值与非接口值比较时,会先将非接口值尝试转换为接口值,再比较。
    
    func main() {
    var a *int = nil
    var b interface{} = nil

    println(a == b)
}

func main() {
    var p *int = nil
    var i interface{} = p
    fmt.Println(i == p) // true
    fmt.Println(p == nil) // true
    fmt.Println(i == nil) // false
}

    
Go 语言中,interface 的内部实现包含了 2 个字段,类型 T 和 值 V,interface 可以使用 == 或 != 比较。2 个 interface 相等有以下 2 种情况
两个 interface 均等于 nil(此时 V 和 T 都处于 unset 状态)
类型 T 相同,且对应的值 V 相等。

上面这个例子中,将一个 nil 非接口值 p 赋值给接口 i,此时,i 的内部字段为(T=*int, V=nil),i 与 p 作比较时,将 p 转换为接口后再比较,因此 i == p,p 与 nil 比较,直接比较值,所以 p == nil。
但是当 i 与 nil 比较时,会将 nil 转换为接口 (T=nil, V=nil),与i (T=*int, V=nil) 不相等,因此 i != nil。因此 V 为 nil ,但 T 不为 nil 的接口不等于 nil
    
    
10、go垃圾回收原理简述
最常见的垃圾回收算法有标记清除(Mark-Sweep) 和引用计数(Reference Count),Go 语言采用的是标记清除算法。并在此基础上使用了三色标记法和写屏障技术,提高了效率。
标记清除收集器是跟踪式垃圾收集器,其执行过程可以分成标记(Mark)和清除(Sweep)两个阶段:
标记阶段 — 从根对象出发查找并标记堆中所有存活的对象;
清除阶段 — 遍历堆中的全部对象,回收未被标记的垃圾对象并将回收的内存加入空闲链表。
标记清除算法的一大问题是在标记期间,需要暂停程序(Stop the world,STW),标记结束之后,用户程序才可以继续执行。为了能够异步执行,减少 STW 的时间,Go 语言采用了三色标记法。
三色标记算法将程序中的对象分成白色、黑色和灰色三类。
白色:不确定对象。
灰色:存活对象,子对象待处理。
黑色:存活对象。
标记开始时,所有对象加入白色集合(这一步需 STW )。首先将根对象标记为灰色,加入灰色集合,垃圾搜集器取出一个灰色对象,将其标记为黑色,并将其指向的对象标记为灰色,加入灰色集合。重复这个过程,直到灰色集合为空为止,标记阶段结束。那么白色对象即可需要清理的对象,而黑色对象均为根可达的对象,不能被清理。
三色标记法因为多了一个白色的状态来存放不确定对象,所以后续的标记阶段可以并发地执行。当然并发执行的代价是可能会造成一些遗漏,因为那些早先被标记为黑色的对象可能目前已经是不可达的了。所以三色标记法是一个 false negative(假阴性)的算法。
三色标记法并发执行仍存在一个问题,即在 GC 过程中,对象指针发生了改变。比如下面的例子:
1
A (黑) -> B (灰) -> C (白) -> D (白)
正常情况下,D 对象最终会被标记为黑色,不应被回收。但在标记和用户程序并发执行过程中,用户程序删除了 C 对 D 的引用,而 A 获得了 D 的引用。标记继续进行,D 就没有机会被标记为黑色了(A 已经处理过,这一轮不会再被处理)。
1
2
3
A (黑) -> B (灰) -> C (白) 
  ↓
 D (白)
为了解决这个问题,Go 使用了内存屏障技术,它是在用户程序读取对象、创建新对象以及更新对象指针时执行的一段代码,类似于一个钩子。垃圾收集器使用了写屏障(Write Barrier)技术,当对象新增或更新时,会将其着色为灰色。这样即使与用户程序并发执行,对象的引用发生改变时,垃圾收集器也能正确处理了。
一次完整的 GC 分为四个阶段:
1)标记准备(Mark Setup,需 STW),打开写屏障(Write Barrier)
2)使用三色标记法标记(Marking, 并发)
3)标记结束(Mark Termination,需 STW),关闭写屏障。
4)清理(Sweeping, 并发)

11、函数返回局部变量的指针是否安全?
    这在 Go 中是安全的,Go 编译器将会对每个局部变量进行逃逸分析。如果发现局部变量的作用域超出该函数,则不会将内存分配在栈上,而是分配在堆上。

12、非接口非接口的任意类型 T() 都能够调用 *T 的方法吗?反过来呢?
一个T类型的值可以调用为*T类型声明的方法,但是仅当此T的值是可寻址(addressable) 的情况下。编译器在调用指针属主方法前,会自动取此T值的地址。因为不是任何T值都是可寻址的,所以并非任何T值都能够调用为类型*T声明的方法。
反过来,一个*T类型的值可以调用为类型T声明的方法,这是因为解引用指针总是合法的。事实上,你可以认为对于每一个为类型 T 声明的方法,编译器都会为类型*T自动隐式声明一个同名和同签名的方法。
哪些值是不可寻址的呢?
字符串中的字节;
map 对象中的元素(slice 对象中的元素是可寻址的,slice的底层是数组);
常量;
包级别的函数等。
举一个例子,定义类型 T,并为类型 *T 声明一个方法 hello(),变量 t1 可以调用该方法,但是常量 t2 调用该方法时,会产生编译错误

type T string

func (t *T) hello() {
    fmt.Println("hello")
}

func main() {
    var t1 T = "ABC"
    t1.hello() // hello
    const t2 T = "ABC"
    t2.hello() // error: cannot call pointer method on t
}

    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值