什么是 interface?
- Go 语言中 interface 是一个抽象类型,不涉及具体实现,用于定义一组方法的集合
两个 interface 可以比较吗?
- 接口值可以进行比较,基于接口的动态类型和动态值。接口类型不能直接比较。
string 可变嘛?
- 在 Go 语言中,
string
类型是不可修改的,其本质是字符数组。这意味着一旦你创建了一个字符串,无法直接修改它的内容。如果你想要改变一个字符串的值,你通常需要创建一个新的字符串。
- 在 Go 语言中,
单引号、双引号、反引号区别
- 单引号,表示字符常量,用于表示 Unicode 字符,实际上是 int32 别名。处理多语言字符串优势大,例如 utf-8 使用 1-4 个字节表示一个字符,因此不能简单使用 byte[] 处理。
- 双引号,表示字符串常量
- 反引号,表示原始字符串字面量。内容不需要转义
Go 的数据类型有哪些
- 基本数据类型:int,float,string,bool,complex,rune(int32),byte(uint8)
- struct,slice,array,map,channel,interface,function,指针
什么是 defer 语句?多个 defer 语句如何运行?
- defer 是 Go 语言的一种延迟调用机制,defer 后面的函数只有在当前函数执行完成之后才会被执行。当 Go 看见 defer 之后,会将其压入栈中,因此多个 defer 是遵循先进后出原则执行。
- defer 可以嵌套,依旧遵循先进后出原则,并且 defer 在发生 panic 之后也依然有效。
介绍一下 init 函数
- init 函数在包被导入时自动调用,无需显式调用
- 一个包可以定义多个 init 函数,按同个包内按照定义顺序执行,不同包按导入顺序
- 没有返回值和参数,且优于其他函数
- 只能在包级别定义,运行期间只会执行一次
string
和[]byte
相互转换会不会发生内存拷贝- 标准的转换方法是会的,当一个字符转转成一个字节切片时,Go 会创建一个新的字节数组,并将数据复制到这个新数组中
- 强制转换不会,但是不安全
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") } }
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
对上述六种方法进行新跟那个测试利用 pprof 进行内存和 CPU 分析
不难发现,
strings.join
、string.Builder
的性能比较优越。
如何实现可变参数
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 个或者多个参数赋值给这个占位符,这样不管实际参数的数量是多少,都能交给可变参数来处理
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 可以相互转换,也可以和任何类型的指针相互转换。
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: // 处理其他类型 }