Go面试题汇总

1. golang协程为什么比线程轻量?

  1. go协程调用跟切换比线程效率高。

   线程是内核对外提供的服务,应用程序可以通过系统调用让内核启动线程,由内核来负责线程调度和切换。线程在等待IO操作时线程变为unrunnable状态会触发上下文切换。现代操作系统一般都采用抢占式调度,上下文切换一般发生在时钟中断和系统调用返回前,调度器计算当前线程的时间片,如果需要切换就从运行队列中选出一个目标线程,保存当前线程的环境,并且恢复目标线程的运行环境,最典型的就是切换ESP指向目标线程内核堆栈,将EIP指向目标线程上次被调度出时的指令地址。

        go协程是不依赖操作系统和其提供的线程,golang自己实现的CSP并发模型实现(MPG),同时go协程也叫用户态线程,协程之间的切换发生在用户态,很轻量。在用户态没有时钟中断,系统调用等机制, 因此效率比较高。

  2. go协程占用内存少。

        执行go协程只需要极少的栈内存(大概是4~5KB),默认情况下,而线程栈的大小为1MB。goroutine就是一段代码,一个函数入口,以及在堆上为其分配的一个堆栈。所以它非常廉价,我们可以很轻松的创建上万个goroutine,但它们并不是被操作系统所调度执行。

 

 

 

 2. 数组与切片比较

     数组:

       1. 数组是具有固定长度的,并且拥有0个或者多个相同元素的数据类型,数组在使用前必须确定数组长度,

       2.  数组的长度是数组类型的一部分,比如[3]int 和 [4]int是两种不同类型的数组,可以通过内置函数len(array)来获取数组的长度。

           3. 数组需要指定大小,如果不指定会根据初始化来自动推算出大小,在初始化之后不可改变,无法修改其长度。

     4. 数组是值传递。

           5. 数组是内置(build-in)类型,是一组同一类型数据的集合,它是值类型。

     6. 和php等语言一样,go数组从0开始的下标索引来访问数组元素。

 

     切片:

     1. 切片表示一个拥有相同类型的可变长度的序列。它对底层的数组(内部是通过数组保存数据的)进行了抽象,并提供相关的操作方法

     2. 切片是一种轻量级的数据结构,它有三个属性:指针,长度,容量。这些属性是 Golang 需要操作底层数组的元数据:

     切片数据结构的定义:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

    上面结构体里的3 个字段分别是指向底层数组的指针、切片访问的元素的个数(即长度)和切片允许增长到的元素个数(即容量)。

    3. 切片不需要指定大小。

    4. 切片是地址传递。

    5. 切片可以痛殴数组来初始化,也可以通过内置函数make()来初始化。初始化时候len = cap,在append等追加元素时候容量不够(cap不足)时候将len扩容2倍。

  

 3. cap()和len()函数的区别

  len()返回切片中的元素个数。

  cap()返回切片的容量即切片最长可以容纳的元素数量。

package main

import "fmt"

func main() {
   var makeSlice = make([]int,3,5)
   dumpSlice(makeSlice)
}

func dumpSlice(makeSlice []int){
   fmt.Printf("len长度=%d cap长度=%d slice=%v\n",len(makeSlice),cap(makeSlice),makeSlice)
}

输出结果:

len长度=3 cap长度=5 slice=[0 0 0]

 

4. 创建切片slice几种方式

   // 直接声明
   var slice []int
   // make的方式
   var makeSlice1 = make([]int,3,5) // 创建一个类型为 int,长度为 3,容量为 5 的切片
   var makeSlice2 = make([]int,3)  // 不指定容量,默认容量等于初始时的长度 
   // 字面量方式
   var strSlice = []int{1,2,3,4,5}
   // 从数组中截取
   var array  = [...]int{1, 2, 3, 4, 5, 6}
   myslice := array[1:4]

 

5. map 是无序的吗?

  是的。看下面代码

m:= map[string]string{"1":"aaa","2":"bbb","3":"ccc","4":"ddd","5":"eee"}
for k, v := range m {
     log.Printf("k: %v, v: %v", k, v)
}

打印出来结果都是随机无序的。

如何变成有序的输出?

 可以先将key进行索引后,在使用sort排序key,然后在根据排序好的索引进行打印输出。

m:= map[string]string{"1":"aaa","2":"bbb","3":"ccc","4":"ddd","5":"eee"}
   
var keys [] string
for k := range m{
   keys = append(keys,k)
}
fmt.Println(keys,len(keys))
sort.Strings(keys)//排序切片key
for _,val := range keys{   //循环key取值
      fmt.Println(m[val])
}

 上面使用了sort.Strings排序,对于golang中的排序

 go语言对于 int 、 float64 和 string 数组或是分片的排序, go 分别提供了 sort.Ints() sort.Float64s() sort.Strings() 函数, 默认都是从小到大排序。

 

6. 如何倒叙一个数组

    使用sort提供的一些方法

import (
    "fmt"
    "sort"
)

var s []int
   
func main() {
    s := []int{5, 2, 6, 3, 1, 4}
    sort.Sort(sort.Reverse(sort.IntSlice(s)))
    fmt.Println(s)
}

打印结果:

[1 2 3 4 5 6]

 

7. make和new的区别

  分别看下make和new的定义

  make:

// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
// Slice: The size specifies the length. The capacity of the slice is
// equal to its length. A second integer argument may be provided to
// specify a different capacity; it must be no smaller than the
// length, so make([]int, 0, 10) allocates a slice of length 0 and
// capacity 10.
// Map: An empty map is allocated with enough space to hold the
// specified number of elements. The size may be omitted, in which case
// a small starting size is allocated.
// Channel: The channel's buffer is initialized with the specified
// buffer capacity. If zero, or the size is omitted, the channel is
// unbuffered.
func make(t Type, size ...IntegerType) Type

  new:

// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type

  1. 从入参看,new 函数只接受一个参数,这个参数是一个类型,并且返回一个指向该类型内存地址的指针,make接受参数是两个。

  2.  make只能创建内建类型(slice、map、chan),因为对于引用类型的变量,不光要声明它,还要为它分配内容空间,同时返回这三个引用类型本身,并且初始化。

     new则是可以对所有类型进行内存分配,返回的是指向类型的指针。

    看下面代码

 var i *int
 *i=10
 fmt.Println(*i)

  输出结果报错。

  

     如果使用new方式修改,如下

var i *int
i=new(int)
*i=10
fmt.Println(*i)

   打印就是对的。

  3. new返回指针, make 返回引用。

  4. new初始化零值, make初始化非零值。

 

 

8. uint,int,int8, int16,int32, int64的区别

  查看下面代码所打印的结果

var i1 int = 1
var i2 int8 = 2
var i3 int16 = 3
var i4 int32 = 4
var i5 int64 = 5
var i6 uint = 6
fmt.Println("int类型的大小=",unsafe.Sizeof(i1))
fmt.Println("int8类型大小=",unsafe.Sizeof(i2))
fmt.Println("int16类型大小=",unsafe.Sizeof(i3))
fmt.Println("int32类型大小=",unsafe.Sizeof(i4))
fmt.Println("int64类型大小=",unsafe.Sizeof(i5))
fmt.Println("uint类型大小=",unsafe.Sizeof(i6))

 

  int包括有符号整型或无符号整型。

  uint和uint8等都属于无符号int类型。
  go语言中的int的大小是和操作系统位数相关的,如果是32位操作系统就是4个字节,如果是64位就是8个字节。

 范围大小列表

  •  有符号整型范围  
Int8 [-128 : 127]
Int16 [-32768 : 32767]
Int32 [-2147483648 : 2147483647]
Int64 [-9223372036854775808 : 9223372036854775807]

 

  • 无符号整型范围  
UInt8 [0 : 255]
UInt16 [0 : 65535]
UInt32 [0 : 4294967295]
UInt64 [0 : 18446744073709551615]

 

9. sprintf、fprintf和printf有什么区别

 

 

10. nil 切片和空切片区别

 

posted @ 2020-03-17 11:43  songguojun  阅读(1040)  评论(0编辑  收藏  举报