Go基础面试题

4.说说Go语言中,数组和切片的区别?切片的底层

数组 数组的长度是类型的一部分,不同的长度的数组,类型不是一致的

  1. 切片的长度可变,切片是一种类型

    a := [3]int{1, 2, 3}
    b := [4]int{1, 2, 3}
    fmt.Printf("a 的类型为%s, b的类型为%s\n", reflect.TypeOf(a), reflect.TypeOf(b))
    fmt.Println(reflect.TypeOf(a) == reflect.TypeOf(b))
       
    // a 的类型为[3]int, b的类型为[4]int
    // false
    
    as := a[0:2]
    bs := b[0:2]
    fmt.Printf("as的类型为%v,bs的类型为%v\n", reflect.TypeOf(as), reflect.TypeOf(bs))
    fmt.Println(reflect.TypeOf(as) == reflect.TypeOf(bs))
       
    // as的类型为[]int,bs的类型为[]int
    // true
    
  2. 数组是值类型,值传递,拷贝时会复制整个数组

    而切片是引用类型,是对数组的一个连续片段的引用,引用传递

    func main(){ 
     arrayA := [2]int{100, 200}
     var arrayB [2]int
     arrayB = arrayA
    
     fmt.Printf("arrayA : %v , %v\n", &arrayA[0], arrayA)
     fmt.Printf("arrayB : %v , %v\n", &arrayB[0], arrayB)
     testArray(arrayA)
    
     fmt.Println()
     
     sliceA := arrayA[0:1]
     sliceB := sliceA
     fmt.Printf("sliceA : %v , %v\n", &sliceA[0], sliceA)
     fmt.Printf("sliceB : %v , %v\n", &sliceB[0], sliceB)
     testSlice(sliceA)
    
    }
    func testArray(x [2]int) {
     fmt.Printf("func Array : %v , %v\n", &x[0], x)
    }
    func testSlice(x []int) {
     fmt.Printf("func Slice : %v , %v\n", &x[0], x)
    }
    /*
    arrayA : 0xc0000180b0 , [100 200]
    arrayB : 0xc0000180c0 , [100 200]
    func Array : 0xc000018110 , [100 200]
    
    sliceA : 0xc0000180b0 , [100]
    sliceB : 0xc0000180b0 , [100]
    func Slice : 0xc0000180b0 , [100]
    */
    
  3. 数组需要指定大小,不指定也会根据初始化自动推算出大小,不可改变

    切片不需要指定大小,也可直接从数组中切片

    c := [...]int{1,2,3,4}
    fmt.Printf("c 的类型为%s, 长度为%v\n", reflect.TypeOf(c), len(c))
    // c 的类型为[4]int, 长度为4
    
    cs := []int{1, 2, 3, 4}
    cs = append(cs, 5)
    fmt.Printf("cs 的类型为%s, 长度为%v\n", reflect.TypeOf(cs), len(cs))
    // cs 的类型为[]int, 长度为5
    
  4. 切片数据结构

    type slice struct {
        array unsafe.Pointer // 指向数组的指针
        len   int             // 当前切片的长度
        cap   int             // 当前切片容量
    }
    

    当长度一旦大于容量时,容量会按增长因子扩容,但是会导致空间的重新分配,切片此时指向了一个全新的数组

    func main() {
     arr := [4]int{1, 2, 3, 4}
     sliceA := arr[:]
     fmt.Printf("len(silceA) = %v, cap(sliceA) = %v\n", len(sliceA), cap(sliceA))
     // len(silceA) = 4, cap(sliceA) = 4
    
     // 修改arr的第一个元素值,sliceA的第一个元素值跟着变化
     arr[0] = 5
     fmt.Printf("sliceA = %v\n", sliceA)
     // sliceA = [5 2 3 4]
    
     // 给slice添加元素,导致slice扩容,空间重新分配
     sliceA = append(sliceA, 6)
     fmt.Printf("sliceA = %v\n", sliceA)
     fmt.Printf("len(silceA) = %v, cap(sliceA) = %v\n", len(sliceA), cap(sliceA))
     // sliceA = [5 2 3 4 6]
     // len(silceA) = 5, cap(sliceA) = 8
    
     // 修改arr的第一个元素值,发现sliceA的第一个元素值不在跟着变化
     arr[0] = 7
     fmt.Printf("arr = %v\n", arr)
     fmt.Printf("sliceA = %v\n", sliceA)
     // arr = [7 2 3 4]
     // sliceA = [5 2 3 4 6]
    }
    
    

    两道题

    func main() {
     arr := [3]int{1, 2, 3}
     slice := arr[0:1]
     slice2 := append(slice, 5, 6, 7)
    
     fmt.Println(arr, slice, slice2)
    }
    // [1 2 3] [1] [1 5 6 7]
    
    func main() {
     arr := [3]int{1, 2, 3}
     slice := arr[0:1]
     slice2 := append(slice, 5, 6)
    
     fmt.Println(arr, slice, slice2)
    }
    // [1,5,6] [1] [1,5,6]
    

5. 说说go语⾔的同步锁

var x = 0
var waitGroup sync.WaitGroup

func main() {
    waitGroup.Add(2)
    go add()
    go add()
    waitGroup.Wait()
    fmt.Println(x)
}

func add() {
    for i := 0; i < 10000; i++ {
        y := x
        y = y + 1
        x = y
    }
    waitGroup.Done()
}
var x = 0
var waitGroup sync.WaitGroup
var mutex sync.Mutex

func main() {
    waitGroup.Add(2)
    go add()
    go add()
    waitGroup.Wait()
    fmt.Println(x)
}

func add() {
    for i := 0; i < 10000; i++ {
        mutex.Lock()
        y := x
        y = y + 1
        x = y
        mutex.UnLock()
    }
    waitGroup.Done()
}

于此之外还有读写锁,即

var rwmutex sync.RWMutex
rwmutex.Lock()// 加写锁,阻止其他读写获取锁
rwmutex.RLock()// 加读锁,如果其他gorountine要获取读锁的话,会获得读锁,如果要获取写锁就会等待

channel特性

给⼀个 nil channel 发送数据,造成永远阻塞

从⼀个 nil channel 接收数据,造成永远阻塞

给⼀个已经关闭的 channel 发送数据,引起 panic

从⼀个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回⼀个零值

⽆缓冲的channel是同步的,⽽有缓冲的channel是⾮同步的

func main() {
   c := make(chan int)
   go add(c)
   go send(c)
   // 给5秒时间让前两个goroutine有足够时间运行
   time.Sleep(5 * time.Second)
}

// 不断向channel c中发送[0,10)的随机数
func send(c chan int) {
   for {
      c <- rand.Intn(10)
   }
}
func add(c chan int) {
   sum := 0
   // 1秒后,将向t.C通道发送时间点,使其可读
   t := time.NewTimer(2 * time.Second)
   for {
      // 两秒内,将一直选择第一个case
      // 两秒后,t.C可读,将选择第二个case
      // c变成nil channel后,两个case分支都将一直阻塞
      select {
      case input := <-c:
         // 不断读取c中的随机数据进行加总
         sum = sum + input
      case <-t.C:
         c = nil
         fmt.Println(sum)
      }
   }
}
func main() {
   c := make(chan int)
   go send(c)
   go receive(c)
   // 给5秒时间让前两个goroutine有足够时间运行
   time.Sleep(5 * time.Second)
}

// 不断向channel c中发送[0,10)的随机数
func send(c chan int) {
   for {
      c <- rand.Intn(10)
   }
}

func receive(c chan int) {
   i := 0
   t := time.NewTimer(1 * time.Second)
   for {
      select {
      case <-t.C:
         c = nil
      case <-c:
         fmt.Println(i)
         i++
      }
   }
}

你可能感兴趣的:(Go基础面试题)