Golang 面试题 - 基础题

961 阅读11分钟

golang.jpg

题目索引


题:如何一次声明多个类型的变量?

解题思路

考点:变量声明

  • 单个变量的声明:
    • var 变量名 变量类型 声明,如:var name string;
    • var 变量名 = 变量值 声明初始化,如:var name = "xx",编译的时候会对值类型进行类型检查;
    • := 声明,如:name := "xx",,编译的时候会对值类型进行类型检查;
  • 多个变量的声明:
    • var 变量名1,变量名2, ... 变量类型
    • 变量值1, 变量值2, ...`**,如:var x, y = 5, 6;
    • := 声明,如:x, y:= 5, "name";
    • var (变量名 变量类型),如: var (x int y string) 这种因式分解关键字的写法一般用于声明全局变量;
  • 多个变量多种类型:
    • 变量值1, 变量值2, ...`**,如:var x, y = 5, 6;
    • := 声明,如:x, y:= 5, "name";
    • var (变量名 变量类型),如: var (x int y string) 这种因式分解关键字的写法一般用于声明全局变量;

回答

var (
    x int
    y string
)

x, y := 1, "name"

题:Go语言中的引用类型有哪些?

解题思路

考点:引用类型(reference type)

引用类型(reference type): 由类型的实际值引用(类似于指针)表示的数据类型。

在Go语言中简单识别是不是 引用类型(reference type) 就看能否被 make() 去初始化。

回答

slicemap、channel

题:变量的 "静态类型" 与 "动态类型" 声明分别是什么?

解题思路

考点: 静态类型动态类型

静态类型:程序在编译阶段就可以明确变量类型。

动态类型:程序运行阶段才能推导出变量类型。

在Go中常规的数据类型都是可以在编译阶段确认下来,除了像interface{}可变的类型,只有当程序运行起来,通过赋予的值,来推导数据类型。

回答

//动态类型声明
var i interface{}

//静态类型声明
var num int

题:下面变量赋值正确的是()

A. var x = nil

B. var x interface{} = nil

C. var x string = nil

D. var x error = nil

解题思路

考点:nil 基本理解

  • 尽管 nil 值作为多个类型的零值,但是使用的时候必须明确变量类型,所以排除 选项A

  • 支持nil赋值类型:pointerslicemapchannelinterfaceerror ,所以排除 选项C

回答: B、D


题:"=" 和 ":=" 的区别?

解题思路

基本变量赋值的语法问题,我们知道 :== 的最大区别就是,不仅可以声明变量,还可以赋值

回答:

:= 声明+赋值,= 仅赋值


题:Go语言中零值是 nil 的类型有哪些?

解题思路

零值类型:pointerslicemapchannelinterfaceerror

回答

pointerslicemapchannelinterfaceerror


题: 2个 nil 可能不相等吗?

解题思路

这个官方是有FAQ:为什么我的nil错误值不等于nil?

首先我们要知道 nil 是一个 【预声明的标识符】,不是 【关键字】nil 本身是没有固定的类型,使用的时候会提供相关的信息,以便让编译器能够推断出一个类型不确定的 nil 所期望类型。

package main

import "fmt"

func main() {
    // 两个nil值,不同类型比输出false
    fmt.Printf("[interface == int]=>%v", (interface{})(nil) == (*int)(nil))

    // ------------- -------------
    //把nil当做变量使用,所以不是关键字
    nil := "hello world"
    fmt.Println(nil)

    // ------------- -------------
    var p *int = nil
    var i interface{} = p
    fmt.Println(i == p)     // true
    fmt.Println(p == nil)   // true
    fmt.Println(i == nil)   // false

回答: 可能


题:Go语言中不支持比较的类型有哪些?

解题思路

考点:比较运算符

可参考官方文档 比较运算符 中的解释。

可比较类型不可比较类型
BoolSlice
IntMap
Floatfunction
struct 包含==可比较类型==的字段struct 包含==不可比较类型==的字段
Array具有==可比较元素==的数组类型Array具有==不可比较元素==的数组类型
Complex-
String-
Pointer-
Channel-
Interface-

回答

SliceMapFunctionStruct(包含不可比较类型的字段)Array(具有不可比较元素的数组类型)


题:内置函数 make() 适用哪些类型?

解题思路

考点:内置函数 make()

make()函数主要用于初始化变量并分配指定的内存,make()只适用slicemapchannel 这三个类型,而这三个类型在使用前必须初始化的数据结构才能使用。

回答

slicemapchan


题:内置函数 cap() 适用哪些类型?

解题思路

考点:内置函数cap()

可参考官方 cap() 文档

cap() 是内置函数之一,根据传递变量返回其对应的容量。

  • Array:返回的数组的元素个数与 len() 返回的值相同,即 len(v) == cap(v);
  • Pointer to array:指向数组的指针,本质上还是在计算数组;
  • Slice:重新切片时切片可以达到的最大长度;
  • channel:通道缓冲容量,以元素为单位;

回答

arrayslicechan


题:new() 和 make() 之间的区别?

解题思路

考点:new()make()

两个内置函数,主要都是用来创建类型并分配内存(堆上)。

函数定义:

作用:

  • new(): 是根据传入的类型分配一片内存空间并返回指向这片内存空间的指针;
  • make() 是初始化内置的数据结构,主要适用slicemapchannel

回答

make() 只用于slicemapchannel的初始化并返回非零值,而 new() 的作用是为类型申请一片内存空间,并返回指向这片内存的指针。


题:下面程序是否可以编译通过?如果通过,运行结果是什么?

package main

import "fmt"

func main() {
    l := new([]int)
    l = append(l, 1)
    fmt.Println(l)
}

解题思路

考点:new()append()

和上一题类似,首先我们知道 new() 作用是申请一片内存空间,并返回指向内存空间的地址,也就说 l 是指向切片的指针。

再看 append() 函数定义:func append(slice []Type, elems ...Type) []Type

根据函数定义可知,第一个参数是 []Type 类型,不是指针类型,所以在编译的时候,类型检查会不通过,会报一下的错误:

first argument to append must be slice; have *[]int

回答

编译不通过


题:请根据下面代码,写出运行结果是什么?

package main

import "fmt"

func main() {
    s := make([]int, 5)
    s = append(s, 1, 2, 3)
    fmt.Println(s)
}

解题思路

考点:make()

make在声明切片的时候,会用缺省值

回答

[0 0 0 0 0 1 2 3]

题:如何在Go中打印变量的类型?

解题思路

考点:格式化输出

可参考 格式说明符

回答

a := 1
fmt.Printf("%T", a)

题:Printf()、Sprintf()、Fprintf() 函数的区别是什么?

解题思路

回答

都是把格式好的字符串输出,只是输出的目标不一样:

  • Printf(): 是把格式字符串输出到标准输出(一般是屏幕,可以重定向)
  • Printf(): 是和标准输出文件(stdout)关联的,Fprintf 则没有这个限制
  • Sprintf():是把格式字符串输出到指定字符串中,所以参数比printf多一个char*。那就是目标字符串地址
  • Fprintf(): 是把格式字符串输出到指定文件设备中,所以参数笔printf多一个文件指针FILE*。主要用于文件操作。Fprintf()是格式化输出到一个stream,通常是到文件。

题:关于 init() 函数,下面说法正确的是()

A. 一个包中,可以包含多个init函数

B. 程序编译时,先执行导入包的init函数,再执行本包内的init函数

C. main包中,不能有init函数

D. init函数可以被其他函数调用

解题思路

考点:init()函数执行顺序

包导入流程,init()函数的执行顺序如图显示:

image

主要作用:

主要特点:

  • init() 是先于 main() 函数自动执行,不能被其他函数调用;
  • init() 是没有输入参数和返回值;
  • 每个包可以有多个 init() 函数;
  • 包的每个源文件也可以有多个 init() 函数;
  • 不同包的 init() 函数按照包导入的依赖关系决定执行顺序;

回答

AB

题:init() 函数是什么时候执行的?

解题思路

init() 函数是 Go 程序初始化的一部分。Go 程序初始化先于 main 函数,由 runtime 初始化每个导入的包,初始化顺序不是按照从上到下的导入顺序,而是按照解析的依赖关系,没有依赖的包最先初始化。

每个包首先初始化包作用域的常量和变量(常量优先于变量),然后执行包的 init() 函数。同一个包,甚至是同一个源文件可以有多个 init() 函数。init() 函数没有入参和返回值,不能被其他函数调用,同一个包内多个 init() 函数的执行顺序不作保证。

Go 程序执行顺序:

import() –> const() –> var() –> init() –> main()

具体的可以看下示例图: image

回答

Go 程序初始化先于 main() 函数之前执行。

题:下面的程序的运行结果是什么?

package main

import "fmt"

const (
    a = iota
    b
    c = "name"
    d
    e = iota
)

func main() {
    fmt.Println(a, b, c, d, e)
}

解题思路

考点: iota

  • iota 在const关键字出现时被重置为0
  • const声明块中每新增一行 iota 值自增1

iota代表了const声明块的行索引(下标从0开始)

回答

0 1 name name 4

题:下面的程序的运行结果是 ?

defer fmt.Println(9)

fmt.Println(0)

defer fmt.Println(8)

fmt.Println(1)

defer func() {
    defer fmt.Println(7)
    fmt.Println(3)
    defer func() {
        fmt.Println(5)
        fmt.Println(6)
    }()
    fmt.Println(4)
}()

fmt.Println(2)

解题思路

考点:defer

defer 作为一个延时函数调用的关键词,其主要的作用就是将延迟一个函数调用,它不会立即被执行。它将被推入由当前协程维护的一个延迟调用堆栈。

defer 执行的顺序,是先进后出的原则(FILO)

回答

0、1、2、3、4、5、6、7、8、9

题:下面对 Add() 函数调用正确的是()

Add() 函数实现代码如下:

func Add(args ...int) int {
    sum := 0
    for _, arg := range args {
        sum += arg
    }
    return sum
}

A. Add(1, 2, 3)

B. Add([]int{1, 2, 3})

C. Add([]int{1, 2, 3}...)

D. Add(1, 2)

解题思路

回答

A、C、D

题:下面示例是 Sum() 函数的调用代码,请根据调用方法,请实现 Sum() 函数

package main

import "fmt"

func main() {
    var a, b int = 1, 2
    var i interface{} = &a
    sum := i.(*int).Sum(b)
    fmt.Println(sum)
}

解题思路

回答

type Integer int

func (i *Integer) Add(v Integer) Integer {
    return *i + v
}

题:下面的程序有什么问题,请说明原因?

package main

type student struct {
	Name string
	Age  int
}

func main() {

    m := make(map[string]*student)

    stus := []student{
        {Name: "zhou", Age: 24},
        {Name: "li", Age: 23},
        {Name: "wang", Age: 22},
    }

    for _, stu := range stus {
        m[stu.Name] = &stu
    }
}

解题思路

考点:for ... range

foreach 使用的是变量副本拷贝的方式,所以在循环获取的时候,实际上都是指向同一个指针,导致获取的值都是同一个值问题。

针对本题,在看到程序的时候可能不太在意这个细节地方,实际上这里 stu 循环遍历之后仍然指向的是同一个地址。

回答

这里的for循环赋值使用问题的,由于 for 循环的时候使用的是副本拷贝的方式,所以这里 m 存放 *sutdent 都是指向同一个地址。