Go面试题及详细答案120题(0-20)

发布于:2025-08-13 ⋅ 阅读:(37) ⋅ 点赞:(0)

前后端面试题》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,SQL,Linux… 。

前后端面试题-专栏总目录

在这里插入图片描述

文章目录

一、本文面试题目录

1. Go语言的主要特点有哪些?与其他语言(如Java、Python)相比有何优势?

Go语言(又称Golang)是由Google开发的静态强类型编程语言,其主要特点及与其他语言的优势如下:

主要特点

  • 简洁易学:语法简洁,去除了传统语言中的冗余特性(如继承、泛型早期缺失,后期已支持)。
  • 并发原生支持:通过goroutine(轻量级线程)和channel实现高效并发,比Java线程更轻量。
  • 内存安全:内置垃圾回收(GC),无需手动管理内存,比C/C++更安全。
  • 静态类型:编译时类型检查,比Python等动态类型语言更早发现错误。
  • 编译快速:编译速度远快于Java和C++,接近脚本语言的开发体验。
  • 跨平台:支持交叉编译,可在一个平台为其他平台编译二进制文件。
  • 丰富标准库:内置网络、加密、并发等功能,开箱即用。

与其他语言的优势

  • 对比Java:goroutine比线程更轻量(内存占用约几KB vs MB级),并发模型更简洁;编译速度更快,部署更简单(单二进制文件)。
  • 对比Python:静态类型带来更好的性能和代码健壮性;原生支持高并发,适合后端服务。

示例:用goroutine实现简单并发(比Java线程更简洁)

package main

import (
    "fmt"
    "time"
)

func main() {
    // 启动10个goroutine
    for i := 0; i < 10; i++ {
        go func(n int) {
            fmt.Printf("goroutine %d running\n", n)
            time.Sleep(100 * time.Millisecond)
        }(i)
    }
    time.Sleep(1 * time.Second) // 等待所有goroutine执行
}

2. Go语言的变量声明方式有几种?分别是什么?

Go语言有4种变量声明方式:

  1. 使用var关键字声明(完整形式)
    语法:var 变量名 类型 = 值
    示例:

    var age int = 25
    var name string = "Alice"
    
  2. var声明并自动推导类型
    省略类型,编译器根据赋值自动推断类型:

    var height = 1.75 // 自动推导为float64
    var isStudent = true // 自动推导为bool
    
  3. var批量声明
    在一个var块中声明多个变量:

    var (
        id    int     = 1001
        score float64 = 95.5
        pass  bool    = true
    )
    
  4. 短变量声明(:=
    只能在函数内部使用,省略var和类型,通过赋值自动推导:

    func main() {
        count := 10      // 推导为int
        message := "Hi"  // 推导为string
    }
    

注意:=要求至少有一个变量是新声明的,否则会报错:

a := 10
a := 20 // 错误:no new variables on left side of :=

3. 简述Go中var:=的区别。

var:=是Go中两种变量声明方式,核心区别如下:

特性 var :=(短变量声明)
使用范围 可在函数内、外使用 仅能在函数内部使用
类型指定 可显式指定类型或自动推导 必须通过赋值自动推导类型
初始化要求 可声明不初始化(有默认零值) 必须初始化(右侧必须有值)
多变量声明 支持批量声明(var块) 支持同时声明多个变量
重复声明 不可重复声明同一变量 允许部分变量重复(需至少一个新变量)

示例对比:

// var的使用
var a int       // 声明不初始化,默认0
var b = "test"  // 自动推导类型

// :=的使用(仅函数内)
func main() {
    c := 3.14    // 必须初始化,推导为float64
    d, e := 10, "hello" // 多变量声明
    d = 20       // 允许重新赋值
    d, f := 30, true // 合法:f是新变量
}

4. Go的基本数据类型有哪些?各自的长度是多少?

Go的基本数据类型分为四大类,长度如下(单位:字节):

  1. 整数类型

    • int:与平台相关(32位系统4字节,64位系统8字节)
    • int8:1字节(范围:-128 ~ 127)
    • int16:2字节(-32768 ~ 32767)
    • int32:4字节(-2¹⁰ ~ 2¹⁰-1)
    • int64:8字节(-2³¹ ~ 2³¹-1)
    • 无符号整数:uintuint8(byte)、uint16uint32uint64uintptr(指针类型,长度与平台相关)
  2. 浮点类型

    • float32:4字节(精度约6位小数)
    • float64:8字节(精度约15位小数,默认浮点类型)
  3. 复数类型

    • complex64:8字节(由两个float32组成)
    • complex128:16字节(由两个float64组成,默认复数类型)
  4. 其他基本类型

    • bool:1字节(值为truefalse
    • string:长度不固定(存储UTF-8编码的字符串)
    • byte:uint8的别名(1字节,用于表示ASCII字符)
    • rune:int32的别名(4字节,用于表示Unicode码点)

示例:

package main

import "fmt"

func main() {
    var a int = 42
    var b float64 = 3.14159
    var c bool = true
    var d string = "Hello"
    var e rune = '中' // Unicode码点,占4字节
    
    fmt.Printf("int: %d字节\n", sizeof(a))
    fmt.Printf("float64: %d字节\n", sizeof(b))
    fmt.Printf("rune: %d字节\n", sizeof(e))
}

// 辅助函数:打印变量占用字节数
func sizeof(v interface{}) int {
    return int(unsafe.Sizeof(v))
}

5. 什么是值类型和引用类型?举例说明它们在Go中的区别。

值类型:变量直接存储值,赋值或传参时会复制整个值。
引用类型:变量存储的是值的内存地址(指针),赋值或传参时复制的是地址,多个变量指向同一块内存。

Go中的值类型

  • 基本类型:intfloatboolstringrunebyte
  • 复合类型:数组、结构体(struct

Go中的引用类型

  • 切片(slice)、映射(map)、通道(channel)、指针(pointer)、接口(interface

核心区别示例

package main

import "fmt"

func main() {
    // 1. 值类型示例(int)
    a := 10
    b := a // 复制值
    b = 20
    fmt.Println(a, b) // 输出:10 20(a不受b影响)

    // 2. 引用类型示例(切片)
    s1 := []int{1, 2, 3}
    s2 := s1 // 复制引用(地址)
    s2[0] = 100
    fmt.Println(s1, s2) // 输出:[100 2 3] [100 2 3](共享底层数据)

    // 3. 数组(值类型)vs 切片(引用类型)
    arr1 := [3]int{1, 2, 3}
    arr2 := arr1 // 复制整个数组
    arr2[0] = 100
    fmt.Println(arr1, arr2) // 输出:[1 2 3] [100 2 3](arr1不受影响)
}

总结:值类型操作的是副本,修改不影响原变量;引用类型操作的是同一份数据,修改会影响所有引用该地址的变量。

6. 数组和切片的区别是什么?切片的底层实现原理是什么?

数组和切片的区别

特性 数组(Array) 切片(Slice)
长度 固定,声明时必须指定 可变,可动态扩容
类型 包含长度(如[3]int[4]int是不同类型) 不包含长度(如[]int
初始化 需指定长度或通过元素数量推断 可通过make()或数组/切片派生
传递方式 值传递(复制整个数组) 引用传递(复制底层数组指针)
零值 各元素为对应类型零值 nil(长度和容量为0,无底层数组)

切片的底层实现原理
切片是对数组的抽象,其内部结构包含三个字段(源码定义):

type slice struct {
    array unsafe.Pointer // 指向底层数组的指针
    len   int            // 切片长度(当前元素个数)
    cap   int            // 切片容量(底层数组的长度)
}

关键特性

  1. 切片本身不存储数据,而是引用底层数组。
  2. 当切片长度超过容量时,会触发扩容(创建新数组,复制原数据)。
  3. 多个切片可引用同一底层数组,修改切片元素会影响共享该数组的其他切片。

示例:

package main

import "fmt"

func main() {
    // 数组(长度固定)
    arr := [5]int{1, 2, 3, 4, 5}
    
    // 切片(引用数组的一部分)
    s := arr[1:3] // 长度2,容量4(从索引1到数组末尾)
    fmt.Printf("s: %v, len: %d, cap: %d\n", s, len(s), cap(s)) // [2 3], 2, 4
    
    // 修改切片元素会影响原数组
    s[0] = 200
    fmt.Println(arr) // [1 200 3 4 5]
    
    // 切片扩容(超过容量时)
    s = append(s, 6, 7, 8) // 原容量4,添加3个元素后总长度5,触发扩容
    fmt.Printf("扩容后: %v, len: %d, cap: %d\n", s, len(s), cap(s)) // [200 3 6 7 8], 5, 8
}

7. 如何初始化一个长度为5、容量为10的切片?

在Go中,可通过以下两种方式初始化长度为5、容量为10的切片:

  1. 使用make()函数
    语法:make(切片类型, 长度, 容量)
    示例:

    s := make([]int, 5, 10)
    fmt.Printf("len: %d, cap: %d\n", len(s), cap(s)) // 输出:len: 5, cap: 10
    fmt.Println(s) // 输出:[0 0 0 0 0](初始值为int的零值)
    
  2. 通过数组或切片派生
    先创建容量足够的数组/切片,再截取指定长度:

    // 方式1:从数组派生
    arr := [10]int{}
    s1 := arr[:5] // 截取前5个元素,长度5,容量10
    
    // 方式2:从更大的切片派生
    s2 := make([]int, 10)
    s3 := s2[:5] // 长度5,容量10
    

注意:初始化后,切片的前5个元素可直接访问(默认零值),而访问索引5~9会导致越界错误,需通过append()扩展长度后使用:

s := make([]int, 5, 10)
s[0] = 1 // 合法
// s[5] = 6 // 错误:index out of range [5] with length 5

s = append(s, 6, 7, 8) // 长度变为8,仍在容量范围内
s[5] = 6 // 此时合法

8. makenew的区别是什么?分别适用于哪些场景?

makenew都是Go中用于内存分配的内置函数,但用途和行为不同:

特性 make new
作用 初始化引用类型(创建并初始化) 分配值类型内存(仅分配,不初始化)
返回值 返回类型本身(已初始化) 返回指向类型的指针(*T
适用类型 切片(slice)、映射(map)、通道(channel 所有类型(主要用于值类型,如intstruct等)
零值处理 初始化为类型的“可用零值”(如切片的空结构) 初始化内存为类型的零值(如int的0)

make的使用场景
用于创建需要预初始化的引用类型,确保其可以直接使用:

// 切片(指定长度和容量)
s := make([]int, 5, 10)

// 映射(必须用make初始化才能使用)
m := make(map[string]int)
m["age"] = 25 // 直接使用,无需额外初始化

// 通道
ch := make(chan int, 5) // 带缓冲的通道

new的使用场景
用于分配值类型的内存,返回指针,适用于需要指针的场景:

// 基本类型
num := new(int)
*num = 10 // 需要解引用赋值

// 结构体
type Person struct {
    Name string
    Age  int
}
p := new(Person)
p.Name = "Alice" // 结构体指针可直接访问字段(语法糖)

注意new对引用类型的作用有限,例如new([]int)会返回*[]int(指向nil切片的指针),仍需手动初始化:

sPtr := new([]int)
// *sPtr = make([]int, 5) // 必须手动初始化才能使用

9. 简述Go中的map结构,如何判断一个键是否存在于map中?

Go中的map结构
map是一种无序的键值对(key-value)集合,类似其他语言中的字典或哈希表。其特点如下:

  • 键必须是支持相等运算符(==!=)的类型(如intstring、指针等),切片、map、函数等不可作为键。
  • 值可以是任意类型。
  • 底层通过哈希表实现,查找、插入、删除的平均时间复杂度为O(1)。
  • map是引用类型,赋值或传参时传递的是引用(地址)。

声明与初始化
map必须初始化后才能使用(通常用make()):

// 声明并初始化
m1 := make(map[string]int)

// 字面量初始化
m2 := map[string]int{
    "apple":  5,
    "banana": 3,
}

判断键是否存在
通过map访问键时,可返回两个值:value, ok := m[key],其中ok是布尔值,表示键是否存在:

package main

import "fmt"

func main() {
    m := map[string]int{"apple": 5, "banana": 3}
    
    // 判断"apple"是否存在
    if val, ok := m["apple"]; ok {
        fmt.Printf("apple exists: %d\n", val) // 输出:apple exists: 5
    }
    
    // 判断"orange"是否存在
    if _, ok := m["orange"]; !ok {
        fmt.Println("orange does not exist") // 输出:orange does not exist
    }
}

注意:如果直接访问不存在的键,会返回值类型的零值(如int返回0),因此不能通过返回值是否为零值来判断键是否存在。

10. Go中的字符串是值类型还是引用类型?字符串能否被修改?

Go中的字符串是值类型,但具有特殊的存储机制:

  • 字符串的底层是一个只读的字节数组([]byte),字符串变量存储的是该数组的指针和长度。
  • 赋值或传参时,复制的是指针和长度(而非整个字节数组),因此效率较高。
  • 虽然复制成本低,但字符串本身仍是值类型(变量存储的是“值”——指针和长度)。

字符串不可修改
Go中的字符串是只读的,不能直接修改其内容。任何看似修改的操作,实际上都会创建一个新的字符串:

package main

import "fmt"

func main() {
    s := "hello"
    // s[0] = 'H' // 错误:cannot assign to s[0](字符串不可修改)
    
    // 若需修改,需先转换为字节切片,修改后再转回字符串
    b := []byte(s)
    b[0] = 'H'
    s = string(b) // 创建新字符串
    fmt.Println(s) // 输出:Hello
}

原理:字符串的底层字节数组被设计为只读,多个字符串可共享同一份底层数据(如字符串切片):

s1 := "hello world"
s2 := s1[0:5] // s2与s1共享底层字节数组

这种设计既保证了字符串的安全性(不可修改),又兼顾了性能(避免不必要的复制)。

11. 什么是指针?Go中指针的使用场景有哪些?

指针是存储另一个变量内存地址的变量。通过指针可以间接访问或修改所指向变量的值。

Go中指针的声明方式为*类型,取地址用&,解引用用*

var a int = 10
var p *int = &a // p是指向int的指针,存储a的地址
fmt.Println(*p) // 解引用,输出:10
*p = 20         // 通过指针修改a的值
fmt.Println(a)  // 输出:20

Go中指针的使用场景

  1. 修改函数外部变量
    函数参数默认是值传递,通过指针可让函数修改外部变量:

    func increment(p *int) {
        *p++
    }
    
    func main() {
        x := 5
        increment(&x)
        fmt.Println(x) // 输出:6
    }
    
  2. 传递大型数据结构
    对于结构体等大型数据,传递指针可避免值复制的性能开销:

    type LargeStruct struct {
        Data [10000]int
    }
    
    // 传递指针,避免复制整个结构体
    func process(s *LargeStruct) {
        s.Data[0] = 100
    }
    
  3. 实现数据共享
    多个指针可指向同一变量,实现数据共享(如链表、树等数据结构):

    type Node struct {
        Val  int
        Next *Node // 指向另一个Node的指针
    }
    
  4. 区分“存在零值”和“未设置”
    对于可能有零值的类型(如int的0),可用指针的nil表示“未设置”:

    func getValue() *int {
        // 某些条件下返回nil,表示未找到值
        return nil
    }
    

注意:Go不支持指针运算(如p++),比C/C++的指针更安全。

12. defer语句的作用是什么?它的执行顺序是怎样的?

defer语句的作用
用于延迟执行函数调用,通常用于释放资源、关闭连接等清理操作,确保代码在函数退出前(无论正常返回还是异常 panic)执行。

常见使用场景

  • 关闭文件
  • 释放锁
  • 关闭网络连接

示例:

package main

import "fmt"

func main() {
    fmt.Println("start")
    defer fmt.Println("deferred 1") // 延迟执行
    defer fmt.Println("deferred 2") // 延迟执行
    fmt.Println("end")
}

输出:

start
end
deferred 2
deferred 1

执行顺序

  1. defer语句在声明时会立即计算函数参数,但不会执行函数体。
  2. 多个defer按“后进先出”(LIFO)顺序执行,即最后声明的defer最先执行。
  3. defer在函数返回前执行,无论函数是正常返回、panic还是被return语句终止。

进阶示例(参数即时计算):

func main() {
    i := 0
    defer fmt.Println(i) // 参数i在声明时计算(值为0)
    i = 10
    fmt.Println(i) // 输出:10
}
// 输出:
// 10
// 0

defer与匿名函数结合
可用于捕获函数返回值或修改返回值:

func f() int {
    i := 5
    defer func() { i++ }() // 延迟执行匿名函数
    return i // 返回5(i的值在return时确定)
}

func main() {
    fmt.Println(f()) // 输出:5(匿名函数在return后执行,不影响返回值)
}

13. panicrecover的作用是什么?如何使用它们处理错误?

panic:用于触发程序异常(类似其他语言的“抛出异常”),会立即终止当前函数执行,并沿调用栈向上传播,直到被recover捕获或导致程序退出。

recover:用于捕获panic引发的异常,只能在defer语句中使用,返回panic传递的值,若没有panic则返回nil

使用场景
处理不可恢复的错误(如程序逻辑错误),或在顶层捕获异常以避免程序崩溃。

基本用法示例

package main

import "fmt"

func riskyOperation() {
    panic("something went wrong") // 触发异常
}

func main() {
    defer func() {
        if err := recover(); err != nil {
            // 捕获panic,打印错误信息
            fmt.Printf("recovered from: %v\n", err)
        }
    }()
    
    riskyOperation()
    fmt.Println("this line will not execute") // panic后不会执行
}
// 输出:recovered from: something went wrong

注意事项

  1. recover必须在defer语句中使用,否则无效。
  2. panic会终止当前函数,但会先执行该函数中的所有defer语句。
  3. 不推荐用panic/recover处理预期错误(如文件不存在),这类错误应通过函数返回值处理。

多层调用中的panic传播

func a() {
    defer fmt.Println("a's defer")
    b()
}

func b() {
    defer fmt.Println("b's defer")
    panic("error in b")
}

func main() {
    defer func() { recover() }()
    a()
}
// 输出:
// b's defer
// a's defer

14. Go中的函数可以返回多个值吗?如何处理函数返回的错误?

Go中的函数支持返回多个值,这是Go的特色功能之一,常用于同时返回结果和错误信息。

基本语法

// 声明返回多个值的函数
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

处理返回的错误
Go推荐通过返回值显式处理错误(而非异常),通常将错误作为最后一个返回值,约定nil表示无错误:

package main

import "fmt"

func main() {
    result, err := divide(10, 2)
    if err != nil {
        // 错误处理
        fmt.Println("Error:", err)
        return
    }
    // 无错误,使用结果
    fmt.Println("Result:", result) // 输出:Result: 5
    
    // 测试错误情况
    result, err = divide(5, 0)
    if err != nil {
        fmt.Println("Error:", err) // 输出:Error: division by zero
        return
    }
}

命名返回值
函数可声明返回值的名称,在函数体内直接使用,return时可省略返回值列表:

func calculate(a, b int) (sum, product int) {
    sum = a + b
    product = a * b
    return // 隐式返回sum和product
}

总结:多返回值使Go的错误处理清晰直观,通过判断错误返回值是否为nil,可明确处理成功和失败的情况,避免了其他语言中try/catch块的嵌套问题。

15. 什么是类型别名?它与自定义类型有何区别?

类型别名是给已存在的类型起一个新名字,语法为:type 别名 = 原类型
自定义类型是创建一个全新的类型,语法为:type 新类型 原类型

区别对比

特性 类型别名(Type Alias) 自定义类型(Custom Type)
本质 原类型的另一个名字,与原类型等价 全新的类型,与原类型不同
兼容性 可与原类型直接转换(无需显式转换) 与原类型不兼容,需显式转换
方法绑定 不能为类型别名定义方法(方法属于原类型) 可直接为自定义类型定义方法

示例

package main

import "fmt"

// 自定义类型:创建全新类型MyInt
type MyInt int

// 类型别名:IntAlias是int的别名
type IntAlias = int

// 可为自定义类型定义方法
func (m MyInt) Double() MyInt {
    return m * 2
}

func main() {
    var a int = 10
    var b MyInt = 20
    var c IntAlias = 30
    
    // 类型别名与原类型兼容
    a = c // 合法:IntAlias与int等价
    c = a // 合法
    
    // 自定义类型与原类型不兼容(需显式转换)
    // a = b // 错误:cannot use b (variable of type MyInt) as int value in assignment
    a = int(b) // 合法:显式转换
    
    // 调用自定义类型的方法
    fmt.Println(b.Double()) // 输出:40
}

类型别名的典型用途

  • 简化复杂类型(如type IntSlice = []int
  • 在不同包之间兼容类型
  • 重构时平滑过渡类型

自定义类型的典型用途

  • 封装数据和行为(面向对象风格)
  • 区分语义不同但底层类型相同的值(如type Meter inttype Foot int

16. 简述Go中的接口,接口的“隐式实现”是什么意思?

Go中的接口是一种抽象类型,定义了一组方法签名(只有方法声明,没有实现),用于描述某个对象的行为。接口不关心实现者的具体类型,只关心其是否具备特定方法。

接口定义语法

// 定义接口
type Shape interface {
    Area() float64   // 计算面积的方法
    Perimeter() float64 // 计算周长的方法
}

“隐式实现”的含义
Go中实现接口无需显式声明(如Java的implements关键字),只需类型实现了接口的所有方法,就自动视为实现了该接口。这种特性称为“隐式实现”或“鸭子类型”(“如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子”)。

示例

package main

import "fmt"

// 接口定义
type Shape interface {
    Area() float64
}

// 圆类型(隐式实现Shape接口)
type Circle struct {
    Radius float64
}

// 实现Shape接口的Area方法
func (c Circle) Area() float64 {
    return 3.14 * c.Radius * c.Radius
}

// 矩形类型(隐式实现Shape接口)
type Rectangle struct {
    Width, Height float64
}

// 实现Shape接口的Area方法
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// 接收Shape接口的函数
func PrintArea(s Shape) {
    fmt.Printf("Area: %.2f\n", s.Area())
}

func main() {
    c := Circle{Radius: 5}
    r := Rectangle{Width: 4, Height: 6}
    
    // Circle和Rectangle都隐式实现了Shape,可直接传递
    PrintArea(c) // 输出:Area: 78.50
    PrintArea(r) // 输出:Area: 24.00
}

优势
隐式实现降低了接口与实现者之间的耦合,使代码更灵活,便于扩展和重构。

17. 空接口(interface{})可以表示什么类型?使用时需要注意什么?

空接口(interface{} 是没有定义任何方法的接口,因此所有类型都隐式实现了空接口,可以表示任意类型的值。

使用场景

  • 存储任意类型的数据(如map[string]interface{}
  • 实现通用函数(可接收任意类型参数)
  • 作为函数返回值(返回任意类型)

示例

package main

import "fmt"

// 接收空接口参数(可接收任意类型)
func printAny(v interface{}) {
    fmt.Println(v)
}

func main() {
    // 空接口变量可存储任意类型
    var i interface{}
    i = 42          // 存储int
    i = "hello"     // 存储string
    i = []int{1, 2} // 存储切片
    
    // 通用函数
    printAny(100)      // 输出:100
    printAny("world")  // 输出:world
    printAny(map[int]string{1: "one"}) // 输出:map[1:one]
    
    // 空接口切片(可包含多种类型)
    data := []interface{}{1, "two", 3.14, true}
    fmt.Println(data) // 输出:[1 two 3.14 true]
}

使用空接口的注意事项

  1. 类型安全:空接口会丢失类型信息,直接操作可能导致运行时错误,需通过类型断言恢复类型。
  2. 性能开销:空接口的底层实现包含类型信息和值指针,操作时可能有额外的性能开销。
  3. 避免过度使用:滥用空接口会降低代码的可读性和安全性,应优先使用具体类型或泛型。

错误示例(未进行类型断言):

var i interface{} = "hello"
// fmt.Println(i + " world") // 错误:invalid operation: i + " world" (mismatched types interface{} and string)

18. 什么是类型断言?如何判断类型断言是否成功?

类型断言是用于将空接口(interface{})转换为具体类型的操作,语法为:value, ok := 接口变量.(目标类型)

作用
空接口可存储任意类型,但使用时需知道其具体类型才能正确操作,类型断言就是用于“恢复”具体类型的机制。

判断类型断言是否成功
类型断言返回两个值:

  • 第一个值是转换后的具体类型的值(若成功)或目标类型的零值(若失败)。
  • 第二个值(ok)是布尔值,true表示断言成功,false表示失败。

示例

package main

import "fmt"

func main() {
    var i interface{} = "hello"
    
    // 成功的类型断言
    if s, ok := i.(string); ok {
        fmt.Printf("'%s' is a string\n", s) // 输出:'hello' is a string
    }
    
    // 失败的类型断言
    if num, ok := i.(int); ok {
        fmt.Printf("%d is an int\n", num)
    } else {
        fmt.Println("i is not an int") // 输出:i is not an int
    }
    
    // 对非空接口使用类型断言
    var s interface{} = 100
    // 直接断言(不判断ok,失败会panic)
    num := s.(int)
    fmt.Println(num + 50) // 输出:150
}

类型断言与switch结合
使用type switch可高效判断空接口的具体类型:

func checkType(v interface{}) {
    switch t := v.(type) {
    case int:
        fmt.Printf("It's an int: %d\n", t)
    case string:
        fmt.Printf("It's a string: %s\n", t)
    case bool:
        fmt.Printf("It's a bool: %v\n", t)
    default:
        fmt.Printf("Unknown type: %T\n", t)
    }
}

func main() {
    checkType(42)    // 输出:It's an int: 42
    checkType("hi")  // 输出:It's a string: hi
    checkType(true)  // 输出:It's a bool: true
    checkType(3.14)  // 输出:Unknown type: float64
}

注意:若不判断ok而直接进行类型断言,失败时会触发panic,因此建议始终使用ok判断。

19. Go中的for循环有几种形式?如何用for实现while的功能?

Go中只有for一种循环语句,但支持三种形式,可替代其他语言的forwhiledo-while

  1. 基本形式(类似C的for)
    语法:for 初始化; 条件; 后处理 { ... }
    示例:

    for i := 0; i < 5; i++ {
        fmt.Println(i)
    }
    
  2. 条件循环(类似while)
    语法:for 条件 { ... }(省略初始化和后处理)
    示例(实现while功能):

    i := 0
    for i < 5 {
        fmt.Println(i)
        i++
    }
    
  3. 无限循环(类似for(;😉)
    语法:for { ... }(无任何条件),需配合break退出
    示例:

    i := 0
    for {
        if i >= 5 {
            break
        }
        fmt.Println(i)
        i++
    }
    
  4. 遍历循环(for range)
    用于遍历数组、切片、map、字符串、通道等:

    // 遍历切片
    nums := []int{1, 2, 3}
    for index, value := range nums {
        fmt.Printf("index: %d, value: %d\n", index, value)
    }
    
    // 遍历map
    m := map[string]int{"a": 1, "b": 2}
    for key, val := range m {
        fmt.Printf("key: %s, val: %d\n", key, val)
    }
    

for实现while功能
Go中没有while关键字,直接使用for 条件形式即可实现等效功能:

// 实现while (condition) { ... }
count := 0
for count < 3 {
    fmt.Println("count:", count)
    count++
}
// 输出:
// count: 0
// count: 1
// count: 2

实现do-while功能
通过无限循环+条件判断实现(先执行一次,再判断条件):

// 实现do { ... } while (condition)
i := 0
for {
    fmt.Println(i)
    i++
    if i >= 3 {
        break
    }
}
// 输出:
// 0
// 1
// 2

20. breakcontinue在循环中的作用有何不同?如何跳出多层循环?

breakcontinue的区别

  • break:立即终止当前循环,跳出循环体,执行循环后的代码。
  • continue:跳过当前循环的剩余语句,直接进入下一次循环的判断条件。

示例对比

package main

import "fmt"

func main() {
    // break示例:遇到3终止循环
    fmt.Println("break example:")
    for i := 0; i < 5; i++ {
        if i == 3 {
            break
        }
        fmt.Println(i)
    }
    // 输出:0 1 2
    
    // continue示例:跳过3
    fmt.Println("\ncontinue example:")
    for i := 0; i < 5; i++ {
        if i == 3 {
            continue
        }
        fmt.Println(i)
    }
    // 输出:0 1 2 4
}

跳出多层循环的方法
Go中可通过标签(label) 配合break跳出多层循环:

  1. 在外层循环前定义标签(label:)。
  2. 在需要跳出的地方使用break label

示例:

package main

import "fmt"

func main() {
    // 定义外层循环标签
    outerLoop:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            fmt.Printf("i=%d, j=%d\n", i, j)
            if i == 1 && j == 1 {
                break outerLoop // 跳出外层循环
            }
        }
    }
    fmt.Println("Loop exited")
}
// 输出:
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0
// i=1, j=1
// Loop exited

注意continue也可配合标签使用,用于跳过外层循环的当前迭代,进入下一次外层循环:

outerLoop:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if j == 1 {
            continue outerLoop // 跳过外层循环的当前迭代
        }
        fmt.Printf("i=%d, j=%d\n", i, j)
    }
}
// 输出:
// i=0, j=0
// i=1, j=0
// i=2, j=0

二、120道Go面试题目录列表

文章序号 Go面试题120道
1 Go面试题及详细答案120道(01-20)
2 Go面试题及详细答案120道(21-40)
3 Go面试题及详细答案120道(41-60)
4 Go面试题及详细答案120道(61-80)
5 Go面试题及详细答案120道(81-100)
6 Go面试题及详细答案120道(101-120)

网站公告

今日签到

点亮在社区的每一天
去签到