飞道的博客

Golang面试

312人阅读  评论(0)

简单

golang里的数组和切片有了解过吗

  • 数组和切片都是拥有相同类型一组元素集合

  • 数组为固定长度,切片为可变长度

  • 数组不可扩容,切片可以,切片扩容,如果不足1024每次扩容为两倍扩容,如果高于1024,为1.25倍扩容

  • 切片实际底层指向的也是一个数组的指针,切片的底层的结构体可以看到,有 指针 容量长度

  • 数组是值类型,将一个数组赋值给另一个数组时,传递的是一份深拷贝,赋值和函数传参操作都会复制整个数组数据,会占用额外的内存;切片是引用类型,将一个切片赋值给另一个切片时,传递的是一份浅拷贝,赋值和函数传参操作只会复制len和cap,但底层共用同一个数组,不会占用额外的内存。

for range (slice和map)时遇到的“坑”


   
  1. func main() {
  2. s := [] int{ 1, 2, 3}
  3. m := make( map[ int]* int)
  4. for i, v := range s {
  5. // m[i] = &v 这样是不行的,
  6. // 因为for range 迭代过程是根据slice中的变量遍历出来的新变量,遍历出来的值都是同一地址。所以应该用一个变量进行接受,这样每个不同值的地址就不同了
  7. n := v
  8. m[i] = &n //这样才是正确的姿势
  9. }
  10. fmt.Println(s) // [1 2 3]
  11. for _, v := range m {
  12. fmt.Println(*v) // 1 2 3
  13. }
  14. }

多个切片如果共享同一个底层数组,这种情况下,对其中一个切片或者底层数组的更改,会影响到其他切片


   
  1. func main() {
  2. slice1 := [] string{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"}
  3. Q2 := slice1[ 3: 6]
  4. fmt.Println(Q2, len(Q2), cap(Q2)) // [4 5 6] 3 9
  5. Q3 := slice1[ 5: 8]
  6. fmt.Println(Q3, len(Q3), cap(Q3)) // [6 7 8] 3 7
  7. Q3[ 0] = "Unknown"
  8. fmt.Println(Q2, Q3) // [4 5 Unknown] [Unknown 7 8]
  9. a := [] int{ 1, 2, 3, 4, 5}
  10. shadow := a[ 1: 3]
  11. fmt.Println(shadow, a) // [2 3] [1 2 3 4 5]
  12. shadow = append(shadow, 100)
  13. // 会修改指向数组的所有切片
  14. fmt.Println(shadow, a) // [2 3 100] [1 2 3 100 5]
  15. }

使用 func copy(dst, src []Type) int 解决


   
  1. func main() {
  2. slice1 := [] string{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"}
  3. Q2 := make([] string, 3)
  4. copy(Q2, slice1[ 3: 6])
  5. fmt.Println(Q2, len(Q2), cap(Q2)) // [4 5 6] 3 3
  6. Q3 := make([] string, 3)
  7. copy(Q3, slice1[ 5: 8])
  8. fmt.Println(Q3, len(Q3), cap(Q3)) // [6 7 8] 3 3
  9. Q3[ 0] = "Unknown"
  10. fmt.Println(Q2, Q3) // [4 5 6] [Unknown 7 8]
  11. a := [] int{ 1, 2, 3, 4, 5}
  12. shadow := make([] int, 2)
  13. copy(shadow, a[ 1: 3])
  14. fmt.Println(shadow, a) // [2 3] [1 2 3 4 5]
  15. shadow = append(shadow, 100)
  16. fmt.Println(shadow, a) // [2 3 100] [1 2 3 4 5]
  17. }

实现slice线程安全有两种方式

  1. 通过加锁实现slice线程安全,适合对性能要求不高的场景


   
  1. func main() {
  2. var lock sync.Mutex //互斥锁
  3. a := make([] int, 0)
  4. var wg sync.WaitGroup
  5. for i := 0; i < 10000; i++ {
  6. wg.Add( 1)
  7. go func(i int) {
  8. defer wg.Done()
  9. lock.Lock()
  10. defer lock.Unlock()
  11. a = append(a, i)
  12. }(i)
  13. }
  14. wg.Wait()
  15. fmt.Println( len(a)) // 10000
  16. }
  1. 通过channel实现slice线程安全,适合对性能要求高的场景


   
  1. func main() {
  2. buffer := make( chan int)
  3. a := make([] int, 0)
  4. // 消费者
  5. go func() {
  6. for v := range buffer {
  7. a = append(a, v)
  8. }
  9. }()
  10. // 生产者
  11. var wg sync.WaitGroup
  12. for i := 0; i < 10000; i++ {
  13. wg.Add( 1)
  14. go func(i int) {
  15. defer wg.Done()
  16. buffer <- i
  17. }(i)
  18. }
  19. wg.Wait()
  20. fmt.Println( len(a)) // 10000
  21. }

数组怎么转集合


   
  1. func main() {
  2. m := make( map[ int] int)
  3. arr := [] int{ 1, 2, 3, 4, 5}
  4. for i, v := range arr {
  5. m[i] = v
  6. }
  7. fmt.Println(m)
  8. }

介绍一下通道

  • 通道用于协程之间数据的传递,通道有三种类型,读和写的单向通道和双向通道

  • 通道可以控制协程的并发数

map取一个key,然后修改这个值,原map数据的值会不会变化


   
  1. func main() {
  2. ma := make( map[ int] int)
  3. ma[ 1] = 123
  4. updateMapValue(ma)
  5. fmt.Println(ma[ 1]) //321
  6. }
  7. func updateMapValue(ma map[int]int) {
  8. ma[ 1] = 321
  9. }

channel有缓冲和无缓冲在使用上有什么区别?

无缓冲的与有缓冲channel有着重大差别:一个是同步的 一个是非同步的

比如

ch1:=make(chan int) 无缓冲

ch2:=make(chan int,1) 有缓冲

ch1<-1 无缓冲的

不仅仅是 向 c1 通道放 1 而是 一直要有别的协程 <-ch1 接手了 这个参数,那么ch1<-1之后的代码才会继续执行下去,要不然就一直阻塞着

而 ch2<-1 则不会阻塞,因为缓冲大小是1 (其实是缓冲大小为0)只有当 放第二个值的时候 第一个还没被人拿走,这时候才会阻塞

打个比喻

无缓冲的 就是一个送信人去你家门口送信 ,你不在家 他不走,你一定要接下信,他才会走。

无缓冲保证信能到你手上

有缓冲的 就是一个送信人去你家仍到你家的信箱 转身就走 ,除非你的信箱满了 他必须等信箱空下来。

有缓冲的 保证 信能进你家的邮箱

如何判断channel是否关闭

用 select 和 <-ch 来结合可以解决这个问题

ok的结果和含义:

true:读到数据,并且通道没有关闭。

false:通道关闭,无数据读到。


   
  1. func main() {
  2. ch := make( chan int, 10)
  3. for i := 1; i <= 9; i++ {
  4. ch <- i
  5. }
  6. close(ch)
  7. for i := 0; i < 10; i++ {
  8. select {
  9. case v, ok := <-ch:
  10. if ok {
  11. fmt.Println(v)
  12. } else {
  13. fmt.Println( "关掉了")
  14. }
  15. default:
  16. fmt.Println( "没啥事")
  17. }
  18. }
  19. }

需要注意:

  • case 的代码必须是 _, ok:= <- ch 的形式,如果仅仅是 <- ch 来判断,是错的逻辑,因为主要通过 ok的值来判断;

  • select 必须要有 default 分支,否则会阻塞函数,我们要保证一定能正常返回;

  • 写入channel的时候判断其是否已经关闭,此时如果 channel 关闭,写入时触发panic: send on closed channel

make 与 new 的区别

简单的说,new只分配内存,make用于slice,map,和channel的初始化。


   
  1. func main() {
  2. var v * int
  3. *v = 8
  4. fmt.Println(*v)
  5. // 会报错
  6. // panic: runtime error: invalid memory address or nil pointer dereference
  7. }

解决:


   
  1. func main() {
  2. var v * int
  3. v = new( int)
  4. *v = 8
  5. fmt.Printf( "%d\n", *v)
  6. }

go语言的引用类型有什么?

  • map:golang中map是一种无序的、键值对的集合,其是通过key检索数据,且key类似于索引,指向数据的值,golang中常使用hash表来实现map。

  • pointers:golang中golang是指计算机内存中变量所在的内存地址,使用pointers可以节省内存,但golang中pointers不能进行偏移和运算,只能读取指针的位置。

  • slice:golang中slice是对数组的抽象,相对于数组,slice的长度是不固定的,可以追加元素,且在追加元素时可以增大slice的容量。

  • channel:golang中channel是指管道,是一种用于实现并行计算方程间通信的类型,允许线程间通过发送和接收来传输指定类型的数据,初始值为nil。

  • interface:golang中interface是指接口,是一组方法签名的集合,可以使用接口来识别一个对象够进行的操作。

  • function:golang中function是指函数,function不支持嵌套、重载和默认参数,但无需声明原型,常使用func关键字定义函数。


转载:https://blog.csdn.net/qq_42490050/article/details/128571402
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场