Skip to content

Golang面试题

1、有哪些数据类型?

1、与其他语言相比,使用 Go 有什么好处?

  1. 与其他作为学术实验开始的语言不同,Go 代码的设计是务实的。每个功能和语法决策都旨在让程序员的生活 更轻松。
  2. Golang 针对并发进行了优化,并且在规模上运行良好。
  3. 由于单一的标准代码格式,Golang 通常被认为比其他语言更具可读性。
  4. 自动垃圾收集明显比 Java 或 Python 更有效,因为它与程序同时执行。

2、Golang 使用什么数据类型?

Golang 使用以下类型:

  • Method
  • Boolean
  • Numeric
  • String
  • Array
  • Slice
  • Struct
  • Pointer
  • Function
  • Interface
  • Map
  • Channel

3、关于整型切片的初始化,下面正确的是?

  • A. s := make([]int)
  • B. s := make([]int, 0)
  • C. s := make([]int, 5, 10)
  • D. s := []int

答案:BCD

2、channel有哪些特性?

1、关于channel的特性,下面说法正确的是?

  • A. 给一个 nil channel 发送数据,造成永远阻塞
  • B. 从一个 nil channel 接收数据,造成永远阻塞
  • C. 给一个已经关闭的 channel 发送数据,引起 panic
  • D. 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值

答案 ABCD

2、下列代码有什么问题?

go
const i = 100

var j = 123

func main() {
    fmt.Println(&j, j)
    fmt.Println(&i, i)
}

答案: Go语言中,常量无法寻址, 是不能进行取指针操作的

3、下列代码输出什么?

go
func Test62(t *testing.T) {
    x := []string{"a", "b", "c"}

    for v := range x {
        fmt.Print(v)
    }
}

答案 012

range 一个返回值时,这个值是下标,两个值时,第一个是下标,第二个是值,当 x 为 map时,第一个是 key,第二个是value

3、请描述下select机制

1、关于无缓冲和有冲突的channel,下面说法正确的是?

  • A. 无缓冲的channel是默认的缓冲为1的channel;
  • B. 无缓冲的channel和有缓冲的channel都是同步的;
  • C. 无缓冲的channel和有缓冲的channel都是非同步的;
  • D. 无缓冲的channel是同步的,而有缓冲的channel是非同步的;

答案 D

2、下列代码输出什么?

go
func Foo(x interface{}) {
    if x == nil {
        fmt.Println("empty interface")
        return
    }

    fmt.Println("non-empty interface")
}


func Test64(t *testing.T) {
    var x *int = nil
    Foo(x) 
}

答案: non-empty interface

接口除了有静态类型,还有动态类型和动态值, 当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。 这里的 x 的动态类型是 *int,所以 x 不为 nil

3、关于select机制,下面说法正确的是?

  • A. select机制用来处理异步IO问题;
  • B. select机制最大的一条限制就是每个case语句里必须是一个IO操作;
  • C. golang在语言级别支持select关键字;
  • D. select关键字的用法与switch语句非常类似,后面要带判断条件;

答案 ABC

4、字符串拼接

1、Go 程序中的包是什么?

包(pkg)是 Go 工作区中包含 Go 源文件或其他包的目录。源文件中的每个函数、变量和类型都存储在链接包中。每 个 Go 源文件都属于一个包,该包在文件顶部使用以下命令声明:

go
package <packagename>

可以使用以下方法导入和导出包以重用导出的函数或类型:

go
import <packagename>

Golang 的标准包是 fmt,其中包含格式化和打印功能,如 Println().

2、关于字符串拼接,下列正确的是?

  • A. str := 'abc' + '123'
  • B. str := "abc" + "123"
  • C. str := '123' + "abc"
  • D. fmt.Sprintf("abc%d", 123)

答案: B D

双引号用来表示字符串 string,其实质是一个 byte 类型的数组,单引号表示 rune 类型。

3、Go 支持什么形式的类型转换?

将整数转换为浮点数。 Go 支持显式类型转换以满足其严格的类型要求。

go
 
i := 55 //int
j := 67.8 //float64

sum := i + int(j) //j is converted to int

1、Log包线程安全吗?

Golang的标准库提供了log的机制,但是该模块的功能较为简单(看似简单,其实他有他的设计思路)。在输出的 位置做了线程安全的保护。

2、下列哪一行会panic?

go
func Test76(t *testing.T) {
    var x interface{}
    var y interface{} = []int{3, 5}

    _ = x == x
    _ = x == y
    _ = y == y
}

答案 _ = y == y 会发生panic, 因为两个比较值的动态类型为同一个不可比较类型

3、下列哪行代码会panic?

go
func Test77(t *testing.T) {
    x := make([]int, 2, 10)
    _ = x[6:10]
    _ = x[6:]
    _ = x[2:] 
}

答案: _ = x[6:] 这一行会发生panic

截取符号 [i:j],如果 j 省略,默认是原切片或者数组的⻓度,x 的⻓度是 2,小于起始下标 6 ,所以 panic

5、协程,线程,进程的区别

1、关于switch语句,下面说法正确的有?

  • A. 条件表达式必须为常量或者整数;
  • B. 单个case中,可以出现多个结果选项;
  • C. 需要用break来明确退出一个case;
  • D. 只有在case中明确添加fallthrough关键字,才会继续执行紧跟的下一个case;

答案: BD

2、关于 bool 变量 b 的赋值,下面错误的用法是?

  • A. b = true
  • B. b = 1
  • C. b = bool(1)
  • D. b = (1 == 2)

答案: BC

3、协程,线程,进程的区别。

(1)进程

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单 位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存, 所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。

(2)线程

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不 拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其 他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进 程不够稳定容易丢失数据。

(3)协程

协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换 时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基 本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

6、什么是channel?

1、关于协程,下列说法正确的有?

  • A. 协程和线程都可以实现程序的并发执行;
  • B. 线程比协程更轻量级;
  • C. 协程不存在死锁问题;
  • D. 通过 channel 来进行协程间的通信;

答案: AD

2、什么是channel,为什么它可以做到线程安全?

Channel是Go中的一个核心类型,可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯 (communication),Channel也可以理解是一个先进先出的队列,通过管道进行通信。

Golang的Channel,发送一个数据到Channel 和 从Channel接收一个数据 都是 原子性的。而且Go的设计思想就是: 不要通过共享内存来通信,而是通过通信来共享内存,前者就是传统的加锁,后者就是Channel。也就是说,设计 Channel的主要目的就是在多任务间传递数据的,这当然是安全的。

3、读写锁或者互斥锁读的时候能写吗?

Go中读写锁包括读锁和写锁,多个读线程可以同时访问共享数据;写线程必须等待所有读线程都释放锁以后,才 能取得锁;同样的,读线程必须等待写线程释放锁后,才能取得锁,也就是说读写锁要确保的是如下互斥关系,可 以同时读,但是读-写,写-写都是互斥的。

7、哪个类型可以使用 cap()函数?

1、Channel是同步的还是异步的?

Channel是异步进行的。

channel存在3种状态:

  • nil,未初始化的状态,只进行了声明,或者手动赋值为nil
  • active,正常的channel,可读或者可写
  • closed,已关闭,千万不要误认为关闭channel后,channel的值是nil

2、下列哪个类型可以使用 cap()函数?

  • A. array
  • B. slice
  • C. map
  • D. channel

答案: ABD

解析:

  • array 返回数组的元素个数;
  • slice 返回 slice 的最大容量;
  • channel 返回 channel 的容量;

3、如何在运行时检查变量类型?

类型开关是在运行时检查变量类型的最佳方式。类型开关按类型而不是值来评估变量。每个 Switch 至少包含一个 case,用作条件语句,和一个 defaultcase,如果没有一个 case 为真,则执行。

8、同步锁Mutex

1、Go 两个接口之间可以存在什么关系?

如果两个接口有相同的方法列表,那么他们就是等价的,可以相互赋值。

如果接口 A的方法列表是接口 B的方法列表的自己,那么接口 B可以赋值给接口 A。

接口查询是否成功,要在运行期才能够确定。

2、关于map,下面说法正确的是?

  • A. map 反序列化时 json.unmarshal() 的入参必须为 map 的地址;
  • B. 在函数调用中传递 map,则子函数中对 map 元素的增加不会导致父函数中 map 的修改;
  • C. 在函数调用中传递 map,则子函数中对 map 元素的修改不会导致父函数中 map 的修改;
  • D. 不能使用内置函数 delete() 删除 map 的元素

答案: A

3、关于同步锁,下面说法正确的是?

  • A. 当一个 goroutine 获得了 Mutex 后,其他 goroutine 就只能乖乖的等待,除非该 goroutine 释放这个 Mutex;
  • B. RWMutex 在读锁占用的情况下,会阻止写,但不阻止读;
  • C. RWMutex 在写锁占用情况下,会阻止任何其他 goroutine(无论读和写)进来,整个锁相当于由该 goroutine 独占;
  • D. Lock() 操作需要保证有 Unlock() 或 RUnlock() 调用与之对应;

答案: A B C

9、Channel(通道)有什么特点

1、Go 语言当中 Channel(通道)有什么特点,需要注意什么?

如果给一个 nil 的 channel 发送数据,会造成永远阻塞如果从一个 nil 的 channel 中接收数据,也会造成永久阻 塞给一个已经关闭的 channel 发送数据,会引起 pannic 从一个已经关闭的 channel 接收数据,如果缓冲区中为 空,则返回一个零值。

2、Go 语言当中 Channel 缓冲有什么特点?

无缓冲的 channel 是同步的,而有缓冲的 channel 是非同步的。

3、关于channel,下面语法正确的是?

  • A. var ch chan int
  • B. ch := make(chan int)
  • C. <- ch
  • D. ch <-

答案:ABC

写 chan 时,<- 右端必须要有值

9、new 和 make 有什么区别

1、Go 语言当中 new 和 make 有什么区别吗?

new的作用是初始化一个纸箱类型的指针 new函数是内建函数,函数定义:

go
func new(Type)*Type

使用 new函数来分配空间

传递给 new函数的是一个类型,而不是一个值

返回值是指向这个新非配的地址的指针

2、Go 语言中 make 的作用是什么?

make的作用是为 slice, map or chan 的初始化然后返回引用 make函数是内建函数,函数定义:

go
func make(Type, size IntegerType) Type

make(T, args)函数的目的和 new(T)不同仅仅用于创建 slice, map, channel 而且返回类型是实例。

3、Printf(),Sprintf(),FprintF()都是格式化输出,有什么不同?

虽然这三个函数,都是格式化输出,但是输出的目标不一样

  • Printf 是标准输出,一般是屏幕,也可以重定向。
  • Sprintf()是把格式化字符串输出到指定的字符串中。
  • Fprintf()是吧格式化字符串输出到文件中。

10、值传递和地址传递(引用传递)

1、Go 语言当中数组和切片的区别是什么?

数组: 数组固定⻓度数组⻓度是数组类型的一部分,所以[3]int[4]int 是两种不同的数组类型数组需要指定大 小,不指定也会根据处初始化对的自动推算出大小,不可改变数组是通过值传递的

切片: 切片可以改变⻓度切片是轻量级的数据结构,三个属性,指针,⻓度,容量不需要指定大小切片是地址传递 (引用传递)可以通过数组来初始化,也可以通过内置函数 make()来初始化,初始化的时候 len=cap,然后进行 扩容。

2、Go 语言当中值传递和地址传递(引用传递)如何运用?有什么区别?举例说明

  1. 值传递只会把参数的值复制一份放进对应的函数,两个变量的地址不同,不可相互修改。

  2. 地址传递(引用传递)会将变量本身传入对应的函数,在函数中可以对该变量进行值内容的修改。

3、Go 语言当中数组和切片在传递的时候的区别是什么?

  1. 数组是值传递

  2. 切片是引用传递

12、defer 的作用和特点

1、Go 语言是如何实现切片扩容的?

go
func main(){
    arr := make([]int,0)

    for i := 0; i < 2000; i++{
        fmt.Println("len 为", len(arr), "cap 为", cap(arr)) arr = append(arr, i)
    } 
}

我们可以看下结果

依次是0,1,2,4,8,16,32,64,128,256,512,1024 但到了1024 之后,就变成了1024,1280,1696,2304 每次都是扩容了四 分之一左右

2、关于 channel 下面描述正确的是?

  • A. 向已关闭的通道发送数据会引发 panic;
  • B. 从已关闭的缓冲通道接收数据,返回已缓冲数据或者零值;
  • C. 无论接收还是接收,nil 通道都会阻塞;
  • D. close() 可以用于只接收通道;
  • E. 单向通道可以转换为双向通道;
  • F. 不能在单向通道上做逆向操作(例如:只发送通道用于接收);

答案: ABCF

3、defer 的作用和特点是什么?

3.1、defer 的作用是:

你只需要在调用普通函数或方法前加上关键字 defer,就完成了 defer 所需要的语法。

当 defer 语句被执行时,跟 在 defer 后面的函数会被延迟执行。

直到包含该 defer 语句的函数执行完毕时,defer 后的函数才会被执行,不论 包含 defer 语句的函数是通过 return 正常结束,还是由于 panic 导致的异常结束。

你可以在一个函数中执行多条 defer 语句,它们的执行顺序与声明顺序相反。

3.2、defer 的常用场景:

defer 语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。

通过 defer 机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。

释放资源的 defer 应该直接跟在请求资源的语句后。

13、Slice 的底层实现

1、Golang Slice 的底层实现

切片是基于数组实现的,它的底层是数组,它自己本身非常小,可以理解为对底层数组的抽象。

因为基于数组实现,所以它的底层的内存是连续分配的,效率非常高,还可以通过索引获得数据,可以迭代以及垃圾回收优化。

切片本身并不是动态数组或者数组指针。

它内部实现的数据结构通过指针引用底层数组,设定相关属性将数据读写操作限定在指定的区域内。

切片本身是一个只读对象,其工作机制类似数组指 针的一种封装。

切片对象非常小,是因为它是只有3 个字段的数据结构:

  • 指向底层数组的指针
  • 切片的⻓度
  • 切片的容量

2、Golang Slice 的扩容机制,有什么注意点?

Go 中切片扩容的策略是这样的:

  • 首先判断,如果新申请容量大于2 倍的旧容量,最终容量就是新申请的容量
  • 否则判断,如果旧切片的⻓度小于1024,则最终容量就是旧容量的两倍
  • 否则判断,如果旧切片⻓度大于等于1024,则最终容量从旧容量开始循环增加原来的1/4,直到最终容量大于 等于新申请的容量
  • 如果最终容量计算值溢出,则最终容量就是新申请容量

3、扩容前后的 Slice 是否相同?

情况一:原数组还有容量可以扩容(实际容量没有填充完),这种情况下,扩容以后的数组还是指向原来的数组, 对一个切片的操作可能影响多个指针指向相同地址的 Slice。

情况二:原来数组的容量已经达到了最大值,再想扩容, Go 默认会先开一片内存区域,把原来的值拷⻉过来,然 后再执行 append()操作。这种情况丝毫不影响原数组。

要复制一个 Slice,最好使用 Copy函数。

文章来源:2022Go后端开发大厂面试题.pdf