关于 Go 的一些问题

  1. 什么是 interface?

    • Go 语言中 interface 是一个抽象类型,不涉及具体实现,用于定义一组方法的集合
  2. 两个 interface 可以比较吗?

    • 接口值可以进行比较,基于接口的动态类型和动态值。接口类型不能直接比较。
  3. string 可变嘛?

    • 在 Go 语言中,string 类型是不可修改的,其本质是字符数组。这意味着一旦你创建了一个字符串,无法直接修改它的内容。如果你想要改变一个字符串的值,你通常需要创建一个新的字符串。
  4. 单引号、双引号、反引号区别

    • 单引号,表示字符常量,用于表示 Unicode 字符,实际上是 int32 别名。处理多语言字符串优势大,例如 utf-8 使用 1-4 个字节表示一个字符,因此不能简单使用 byte[] 处理。
    • 双引号,表示字符串常量
    • 反引号,表示原始字符串字面量。内容不需要转义
  5. Go 的数据类型有哪些

    • 基本数据类型:int,float,string,bool,complex,rune(int32),byte(uint8)
    • struct,slice,array,map,channel,interface,function,指针
  6. 什么是 defer 语句?多个 defer 语句如何运行?

    • defer 是 Go 语言的一种延迟调用机制,defer 后面的函数只有在当前函数执行完成之后才会被执行。当 Go 看见 defer 之后,会将其压入栈中,因此多个 defer 是遵循先进后出原则执行。
    • defer 可以嵌套,依旧遵循先进后出原则,并且 defer 在发生 panic 之后也依然有效。
  7. 介绍一下 init 函数

    • init 函数在包被导入时自动调用,无需显式调用
    • 一个包可以定义多个 init 函数,按同个包内按照定义顺序执行,不同包按导入顺序
    • 没有返回值和参数,且优于其他函数
    • 只能在包级别定义,运行期间只会执行一次
  8. string[]byte 相互转换会不会发生内存拷贝

    • 标准的转换方法是会的,当一个字符转转成一个字节切片时,Go 会创建一个新的字节数组,并将数据复制到这个新数组中
    • 强制转换不会,但是不安全
  9. switch-case 语句

    1
    2
    3
    4
    5
    6
    7
    8
    
    switch simpleStatement; condition {
        case expression1, expression2:
            statements
        case expression3:
            statements
        default:
            statements
        }
    
    • switch 包含两个部分,分号前的是初始化器,分号之后是需要检查的值,可以两个部分都使用,或是使用一个,或者一个都不用

    • Go 语言中匹配到一个 case 条件执行完对应的逻辑之后就会跳出这个 switch 语句,如果不想要隐式退出的话可以使用 fallthrough 语句来继续下一个 case 的处理逻辑。

    • switch语句不仅可以处理值,还可以处理类型。

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      
      func main() {
          var i interface{} = "hello"
      
          switch v := i.(type) {
              case int:
              fmt.Println("i is an int and its value is", v)
              case string:
              fmt.Println("i is a string and its value is", v)
              default:
              fmt.Println("Unknown type")
          }
      }
      
  10. Go 拼接字符串方法

    • 使用 +

      1
      2
      3
      4
      5
      
      func main() {
      	s := "hello "
      	s += "world"
      	fmt.Println(s)  // hello world
      }
      
    • 字符串格式化函数 fmt.Sprintf

      1
      2
      3
      4
      5
      
      func main() {
      	s := "hello"
      	s = fmt.Sprintf("%s%s", s, s)
      	fmt.Println(s)  // hellohello
      }
      
    • string.Builder:

      1
      2
      3
      4
      5
      6
      
      func main() {
      	var builder strings.Builder
      	builder.WriteString("hello ")
      	builder.WriteString("world")
      	fmt.Println(builder.String())  // hello world
      }
      
    • bytes.Buffer:

      1
      2
      3
      4
      5
      6
      
      func main() {
      	buf := new(bytes.Buffer)
      	buf.WriteString("hello")
      	buf.WriteString("world")
      	fmt.Println(buf.String())
      }
      
    • strings.join:

      1
      2
      3
      4
      5
      
      func main() {
      	baseSlice := []string{"hello", "world"}
      	fmt.Println(baseSlice)  // [hello world]
      	fmt.Println(strings.Join(baseSlice, ""))  // helloworld
      }
      
    • 切片 + append:

      1
      2
      3
      4
      5
      6
      
      func main() {
      	buf := make([]byte, 0)
      	base := "hello"
      	buf = append(buf, base...)
      	fmt.Println(string(buf))
      }
      
    • 性能对比

      使用 go test -bench="." -run=none -benchmem 对上述六种方法进行新跟那个测试

      image-20250120195008806

      利用 pprof 进行内存和 CPU 分析

      image-20250120195656856

      image-20250120200008863

      不难发现,strings.joinstring.Builder 的性能比较优越。

  11. 如何实现可变参数

    1
    2
    3
    4
    5
    6
    
    // name ...Type 声明一个 Type 类型的可变参数
    // 这是官方库中 Println 的设计,接收任意个接口
    type any = interface{}
    func Println(a ...any) (n int, err error) {
    	return Fprintln(os.Stdout, a...)
    }
    
    • 可变参数就是一个占位符,你可以将 1 个或者多个参数赋值给这个占位符,这样不管实际参数的数量是多少,都能交给可变参数来处理
  12. uintptr 和 unsafe.Pointer 有那些区别

    Go 是一门强类型的语言,它对指针的操作非常严格。但是,有时我们需要绕过类型系统,直接操作内存地址,比如调用 C 语言的函数或者实现一些底层的功能。这时,我们就需要用到 golang 的内置包 unsafe 和它提供的两种特殊的类型:uintptr 和 unsafe.Pointer。

    • uintptr 是 golang 的内置类型,它是一个能够存储指针的整数类型。它的底层类型是 int,它的大小和平台相关,通常是 32 位或者 64 位。我们可以把任何类型的指针转换成 uintptr,也可以把 uintptr 转换成任何类型的指针。例如:

      1
      2
      3
      4
      5
      
      var s string = "hello"
      var p *string = &s
      var u uintptr = uintptr(unsafe.Pointer(p)) // 把 *string 类型的指针转换成 uintptr 类型的整数
      var q *string = (*string)(unsafe.Pointer(u)) // 把 uintptr 类型的整数转换成 *string 类型的指针
      fmt.Println(*q) // 输出 hello
      
    • unsafe.Pointer 是 golang 的内置类型,它是一种通用的指针类型,可以指向任何类型的值。它的作用是,可以把不同类型的指针相互转换,而不用经过 uintptr 的中间转换。例如:

      1
      2
      3
      4
      5
      
      var s string = "world"
      var p *string = &s
      var q unsafe.Pointer = unsafe.Pointer(p) // 把 *string 类型的指针转换成 unsafe.Pointer 类型的指针
      var r *int = (*int)(q) // 把 unsafe.Pointer 类型的指针转换成 *int 类型的指针
      fmt.Println(*r) // 输出一个随机的整数,因为字符串的内存布局和整数的不同
      
    • uintptr 和 unsafe.Pointer 的区别和联系可以总结如下:

      • uintptr 是一种整数类型,unsafe.Pointer 是一种指针类型。
      • uintptr 可以进行算术运算,unsafe.Pointer 不可以进行算术运算。
      • uintptr 不会被 GC 识别和追踪,unsafe.Pointer 会被 GC 识别和追踪。
      • uintptr 和 unsafe.Pointer 可以相互转换,也可以和任何类型的指针相互转换。
  13. Go 的类型断言

    类型断言是Go语言中将一个接口类型变量转换为指定具体类型的一种技术手段。它可以检测存储在接口内部的具体类型,从而实现对不同类型数据的灵活处理。

    • 基本语法

      1
      2
      
      value, ok := x.(T) // 安全的断言方式
      value := x.(T)  // 这样也可以,但是如果断言失败会引发 panic
      

      x 是一个接口类型的变量,T 是希望断言的类型。value 将会是 x 转换为类型 T 后的值,ok 是一个布尔值,当类型断言成功时为 true,失败时为 false 。

      类型断言的必要条件是 x 必须是接口类型,非接口类型的 x 不能做类型断言。

    • type-switch 结构

      1
      2
      3
      4
      5
      6
      7
      8
      
      switch v := x.(type) {  
      case int:  
          // 处理int类型  
      case string:  
          // 处理string类型  
      default:  
          // 处理其他类型  
      }
      
Licensed under CC BY-NC-SA 4.0
载入天数...载入时分秒...
使用 Hugo 构建
主题 StackJimmy 设计