Go 面试题集锦

  1. Go 中赋值的几种形式?

         s := "" 
         var s string
         var s = "" 
         var s string = ""
    
  2. Go 如何运用 ++ \ --操作运算符?

    Go 中的++ \ -- 操作运算符与其它语言有本质区别(例如C语言),Go 中定义的 ++ 或者--操作并不是表达式,所以无法 实现类似C 语言这样的 j = i++ 操作, 并且 Go语言中没有--i这样的前置操作

  3. Go 中指针操作是如何实现的?

    Go 语言虽然使用的是值传递方式,但是并不代表它不支持指针操作,针对自定义的或者大数据量的数据结构,如果采用值传递,将造成大量的内存交换浪费以及时间浪费,值传递需要对对象进行拷贝的。所以针对此种情况Go 支持指针操作,通过 & 可以返回一个变量的内存地址, * 此操作可以获取指针指向的变量内容。并且 Go 中没有指针运算,不能针对指针进行加减运算

  4. Go 中如何定义变量,以及变量如何被外部使用?

    Go 针对变量操作和其他语言相同,存在作用域限制,如果变量定义在函数内部, 那么只能在函数内部使用,函数外不可见, 如果变量在函数外定义, 那么整个包都可变(Go 么有类的概念), 如果变量采用的是大写字母开头的, 那么次变量可以在包外被访问(Go 默认所有的大写开头内容都是可导出的,可被包外部访问的)

  5. Go 中常见的几种类型声明?

    Go 中定义了几种类型声明方式,分别对应不同的类型
    var , const, type, func

  6. Go 如何定义变量?
    参考上边

  7. Go 语言是强类型语言吗, 是否在定义变量时必须指定变量类型?

    Go 语言是强类型语言,变量在定义时虽然不需要指定类型,但是Go 会根据变量的值进行类型推断,如果第一次推断后,变量类型确定,之后如果给变量赋值不同类型值,那么将无法通过编译

  8. Go 中 slice, map, chan ,接口 分别对应的默认值是什么?

    go 中每种类型都有其对应的默认值,在声明变量时,默认进行初始化,以上几种类型的默认值都为 nil 。 并且Go语言中不存在没有初始化的变量

  9. Go 中定义的变量是如何初始化的? 包级别变量与局部变量初始化时机是否相同?

    Go中如果定义在包级别的变量,所有变量会在 main 入口函数执行前完成初始化,此处涉及到 go 中包初始化的过程,详细的会在包初始化过程中介绍。 局部变量会在声明语句执行前完成初始化操作 。两种变量的初始化时机是完全不相同的

  10. 针对 := 操作, Go 是如何定义的?

    := 是快速进行变量声明和初始化的方式,如果这个变量在当前作用域中已经声明过了,那么次操作就是针对此变量的赋值操作,并且使用 := 操作时,必须有一个变量是新的没有被声明过的

      f, err = os.Open(infile)
      out, err = os.Create(outfile)
      // 第二步操作时对 err 的重新赋值并且,声明了一个新的变量 out
    

    使用此操作时,必须有一个变量是新声明的,不然是无法使用的,并且其只针对当前作用域下的存在的变量起赋值作用,如果没有存在,将重新声明新的变量

  11. New 函数操作了解吗?其在使用时是否有需要注意的地方?

    new 函数返回的是一个指针, new可以创建任意类型变量,例如:p := new(int). 每次调用new 函数都会返回新的变量地址(不论什么数据类型), new 只是一个预定义函数,并不是一个关键字,可以对其进行重新定义操作
    __如果两个类型都为空的,类型大小为0(struct{}或者[]int)等,有可能返回相同的地址。尽量不要使用类型大小为0的,,容易出现runtime.SetFinalizer 错误

  12. 变量的生命周期是怎样的?垃圾回收机制是如何实现的? 在何种情况下会存在变量逃逸问题?

    对于包一级的变量,其生命周期和整个程序生命周期是相同的。并且在代码实现上,如果存在换行操作,但是并不是语句结束需要在其尾部添加 , 以避免编译器自动添加换行符
    垃圾回收机制 : 是通过从包级变量和每个当前运行函数的局部变量开始,通过指针或引用的访问路径遍历是否可以找到该变量,如果不存在路径就说明当前运行程序没有对其进行引用,此变量的销毁与否与当前运行程序无影响,垃圾回收机制可以对其进行回收
    变量逃逸: go语言中将变量分配在堆或者栈上并不是通过new关键字来区分的,当函数运行完毕,其中局部变量有可能并不会释放,此种情况称为变量逃逸,这种变量一般在堆上创建。

  13. type Integer int 与 int 是否是相同类型?

    通过type Integer int 声明一个新的类型Integer 其底层类型是 int 类型,不过,需要注意,虽然与 int 类型的底层类型是相同的,不过在go 中,两者代表的是不同的类型,其并不能进行兼容,所以两者是不同类型

  14. 包是如何进行初始化的?

    包的初始化首先是解决包级变量的依赖顺序,然后按照包级声明出现进行初始化。其每个包都可以有 init 方法,在包初始化时,默认进行执行,所以可以通过定义此方法来针对复杂的初始化过程进行简化。并且go 中针对 init 的实现没有任何限制,每个包都可以有多个 init 函数

  15. 以下代码中,其中是否存在不当的地方?

        var cwd string 
        func main(){
            cwd , err := os.Getwd() 
            if err != nil {
    
            }
        }
    

    以上代码 存在 两个 cwd 变量,一个是全局的,一个是局部变量,以上是 := 操作实现,不过忘记了作用域的问题存在,,在 main 函数 内部 是会重新创建 cwd 变量的

  16. Go 中存在几种数据类型?

    1. 基础类型 (数字, 字符, 布尔)
    2. 复合类型 (数组, 结构体, 字符串)
      • 数组类型 是一种值类型,虽然可以修改,但是其作为参数传递时,采用的是复制形式处理
      • 字符串底层是字节数组,但字符串复制只是复制了数据地址和对应长度,不会导致底层数据的复制
    3. 引用类型 (指针,切片,字典,函数,通道)
      • 切片 切片底层也是数据类型的数组,但是每个切片有独立的长度和容量信息,切片复制和函数传参也是讲切片头信息部分按传值方式处理(切片头包含底层数据的指针,所以复制也不会导致底层数据的复制)
    4. 接口类型

    除了闭包函数以引用的方式对外部变量访问之外, 其他赋值和函数传参都是以传值的方式处理。

  17. 数组有几种常用的定义方式?

        var a [3]int                        // 3个元素, 都为 0  
        var b = [...]int{1,2,3}                //3个元素,1, 2, 3  
        var c = [...]int{2:3,1:2}            //3个元素,第一个元素默认的 0 ,第二个元素 2, 第三个元素为3
        var d = [...]int{1,2,4:5,6}        //6个元素,1,2,0,0,5,6
    

    字符串数组, 结构体数组,函数数组,接口数组,管道数组

  18. 切片的定义方式?

        a []int                    //nil 切片,和 nil 相等,表示不存在的切片
        b = []int{}                //空切片
        c = []int{1,2,3}            //是哪个元素,len和cap 都为3  
        d = c[:2]                    //两个元素,len为2. cap 为3 
        e = c[0:2:cap(c)]        //两个元素,len 为2, cap 为3
        f = c[:0]                    //0个元素,len为 0, cap 为3
        g = make([]int, 3)        //三个元素,len和cap 都为 3 
        h = make([]int,2,3)        //2个元素,len为2, cap 为3
        i = make([]int, 0,3)    //0 个元素,len为0, cap 为3   // cap 表示容量
    
  19. 在针对切片操作时,是进行的指针操作还是值操作?

    在对切片操作时,和数组相同进行的是值操作,并不是使用的指针操作,虽然只是操作的切片的头信息,底层数据并没有进行复制操作

  20. 在切片头追加内容会不会有性能为题?

    针对切片头追加会导致整个切片的内存重新分配,导致所有元素全部被复制1次,所以会影响性能

  21. 针对切片的删除是如何实现的呢?是否使用delete实现?

    针对切片的删除操作,系统并没有给定特定的方法实现,不过可以通过 append或者copy放法来实现
    不过在针对切片删除时,需要注意内存管理的问题,删除切片的内容时需要特别注意内存的引用问题,可以先将对应的内存至为 nil ,然后将需要的不分转换为新的切片

        var a []*int{...}  
        a[len(a)- 1] = nil 
        a = a[:len(a) - 1]
    
  22. Go 中切片的类型转换是如何实现的?

        var a = []float64{3,4,6,7,8,88}
        func SortFloat64FastV1(a []float64){
            //强制类型转换 
            var b[]int = ((*[1<<20]int)(unsafe.Pointer(&a[0])))[:len(a):cap(a)]
        }
    
        func SortFloat64FastV2(a []float64){
            //通过 更新切片头部信息实现转换  
            var c []int 
            aHdr := (*reflect.SliceHeader)(unsafe.Pointer(&a))
            cHdr := (*reflect.SliceHeader)(unsafe.Pointer(&c))
            *cHdr = *aHdr 
        }
    
  23. Go 是否针对数据等进行了大小限制,如果限制,那么最大是多少?

    Go中非0大小数组的长度不得超过 2 GB, 需要针对数组元素的类型大小进行计算数组的最大范围 ([]uint8最大2GB,[]uint16最大1GB,以此类推,[]struct{} 数组长度可以超过2GB)

  24. 函数的几种声明方式

        //匿名函数
        var Add  = func(a,b int) int { 
            return a  + b
        }
    
        func f(i, j, m int, s, k string)
    
  25. Go中在使用递归时,是否存在栈溢出问题?

    go 采用的是可变栈, 其每个线程分配的栈是可变的,在其他语言中大部分都是分配 64kb-2m 不等的栈大小。go 独特的采用动态栈采用,其会在调用过程中如果栈不够用,系统会动态调整,最大可达到 1~2GB

  26. 为什么go中采用的是值复制形式传递,但是针对数组和切片等只传递头信息,不会复制底层的数据,是否存在矛盾?

    两者不会产生矛盾,数组切片等数据类型,并不是简单的数据类型,其内部包含的数据并不只有基本的存储数据,其还有自身需要的数据,比如数组的大小,数组的头信息等内容。值传递在这里体现的并不是真个数组的复制,而只是复制了其基本的数据头信息。而指针传递是传递的整个数组的指针。所以本质上还是有很大区别的

  27. go中针对包初始化时有一个init函数是在main 函数执行前执行的,如果在 init 函数中启用 goroutine,那么这个goroutine 是如何执行的?

    在main 函数没有执行前,go 程序会对包进行初始化, 初始化包中的变量内容,执行init 函数,这些操作都是在通一个 goroutine中,不过 如果 init 中使用 go 开启一个新的goroutine,新的 goroutine 并不会立即执行,而是会等待 main 函数执行后才会启动执行。

  28. go针对错误的处理方式?

    一个良好的程序,永远不应该发生 panic 异常
    针对错误的5种处理方式

    • 传播错误
      子函数将错误信息传播给父函数,错误向上传递,将部分错误进行加工为有意义的后再向上传递
    • 重新尝试失败操作
      针对部分偶然性或者不可预知的错误,可以通过重试操作,不过要设置重试事件间隔和次数
    • 输出信息结束程序
      如果错误导致程序无法进行,可以输出错误信息,结束程序。一般只针对main 函数中的错误,如果是库函数,那么一般是将错误向上传递
    • 只输出错误信息
      通过log 包只输出错误信息或者标准输出流中输出错误信息
            log.Printf("xxxxxxxx", err)
            fmt.Fprintf(os.Stderr, "xxdnkagnkgna", err)
      
    • 忽略错误信息
      对错误不进行处理,直接忽略
  29. go 中可能引起 panic 的操作

    • 针对 nil 函数值进行调用
  30. 是否了解匿名函数以及函数值?这两者在使用时有哪些要求?

    go 中函数可以作为返回值或者参数; 函数拥有类型,和其他类型一样,函数的零值为 nil; 函数值可以和 nil 进行比较,但是两个函数之间是不能比较的; 并且不能作为map 的key 存在; 函数值是引用类型 ; go 采用闭包的形式实现函数值

    匿名函数只能在包级语法声明; 在函数内部定义的匿名函数可以引用该函数的变量; 在函数内部定义匿名函数,不仅仅是一串代码,其还可以记录函数内部状态,可以操作函数内部的局部变量;

  31. 是否对可变参数了解?

    go 支持可变参数

    func sum(vals ...int){
    
        }
    

    可变参数本质是创建了一个数组,然后将参数复制到数组中,再讲数组的切片传递给函数(所以拥有可变参数的函数,实际传递的是一个切片)

  32. 那么可变参数本质是一个切片,那么针对原始数据就是一个切片时是如何处理?
    针对原始数据已经是一个切片时,go 采用如下方式进行处理

        values := []int{1,2,4,5} 
        sum(values...)
        //通过在操作时,穿如变量,并且在其后跟 ... 实现
    

    针对可变参数和切片参数的函数的定义:

    func f(...int){}  
    func g([]int){}
    

    虽然可变参数内部实现的是一个切片传递,但是和直接传递切片为参数的是不同的

  33. go 中的 defer 了解吗? 如何使用? 常用的场景?

    go 中的defer 语句代码会被延迟执行,其在退出函数体之前执行,并且其实先出现后执行。可以将 defer 的执行顺序理解为一个栈结构,先进后出;并且可以在一个函数中执行多个 defer语句
    一般用defer 来关闭文件打开,以及网络连接等操作;获取函数执行时间;以及观察函数返回值

  34. 如何获取堆栈信息?

    func main(){
            defer printStack() 
            xxxxxx
        }
        func printStack(){
            var buf [4096]byte  
            n := runtime.Stack(buf[:], false) 
            os.Stdout.Write(buf[:])
        }
    
  35. go 中的异常处理了解吗? 是如何实现的?

    go 语言中并不是使用 try - catch 来实现异常处理的,使用panicrecover结合来实现异常处理
    一般程序遇到panic 异常,程序会立即中断执行,并且执行延迟函数以及输出日志信息
    panic 是一个内置函数,直接调用此函数也会引发panic异常,此函数接收任意类型值参数
    _尽量少使用 panic 机制,应该用错误机制代替 panic 机制

    revcoer 会使程序从 panic 中恢复,并返回 panic value. 导致 panic的函数不会继续执行,但能正常返回; 如果在未发生 panic时调用 recover, revocer 会返回nil

  36. go中方法定义时如何实现的,有什么需要注意的地方?

    方法和函数相同,只不过函数名前添加接收者就变为了方法
    在结构体中如果字段名和方法名相同,将在调用中出错
    定义类型方法必须和类型在同一个包中,并且每个方法名是唯一的,方法和函数不支持重载
    go 可以给任意类型定义方法(除了指针和interface) go 函数会对每个参数进行拷贝,可以通过指针操作避免默认拷贝操作(默认值传递)

    //更新接收者的属性值  
        func (p * Point)ScaleBy(factor float64){
            p.X *= factor
        }
    

    一般一个结构体有Hige指针作为接收器的方法, 那么所有的方法都应该是指针接收器的(定义应该统一)

  37. go 中方法与函数的区别?

    方法与函数没有本质区别,都是对代码块的封装。在函数名前添加变量就是方法; 方法必须有一个载体,其添加的那个变量就是载体(接收器)

        //函数   
        func Distance(p, q Point) float64 {
    
        }
        //方法
        func (p Paint)Distance(p, q Point) float64{
    
        }
    
  38. go 如何实现方法到函数的转换?

    通过方法表达式可以将方法转换为普通函数

    var CloseFile = (*File).Close 
    
    f, _ := OpenFile("foo.dat")  
    CloseFile(f)
    
Copyright © 抓🐱的🐟.com 2017 all right reserved,powered by Gitbook该文件修订时间: 2020-03-13 07:05:40

results matching ""

    No results matching ""