Golang面试题整理(一)
golang的特点和优势
在单机上,一个大型go应用编译只需要数秒;
简单的依赖管理,不像C那样复杂的文件和库依赖;
没有继承,不用浪费精力去解释类型之间的关系,go语言里的类比其他OOP语言里的类更轻;
垃圾回收;
并发,程序可以很方便的运行在多核上,对多核的利用效率很高(GMP模型)。
如何使用并发
1
2
3
| go func(){
// todo
}()
|
go关键字加函数调用
并发和并行
并发指一个处理器同时处理多个任务(其实是交替执行,操作系统时间片)
并行指多个处理器同时处理多个任务。
并发是逻辑上的同时执行,并行是物理上的同时发生。
数据竞争
多个协程并发处理同一个变量。可通过锁或原值操作处理问题
编译时可使用--race 进行数据竞争检测,但这样会增加cpu和内存消耗,生产中不要使用
go build -race .

使用defer处理程序panic
1
2
3
4
5
6
7
8
9
| func foo(){
defer func(){ //recover()必须放在defer func(){}()内(匿名函数)
if err:=recover();err!=nil{ //如果发生panic,则err不为nil
//do something
}
//do something
}()
panic(any)
}
|
go怎么实现接口
不需要显示的生成 只要实现了接口定义的所有方法就视为实现了该接口
结构体实现接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| // 定义一个手机接口
type Phone interface{
Call(num int64)
}
// 小米手机
type XiaoMi struct{
Size float32
}
// 华为手机
type HuaWei struct{
Size float32
}
//小米手机实现call方法
func (xiaoMi *XiaoMi) Call(num int64){
}
//华为手机实现call方法
func (huaWei *HuaWei) Call(num int64){
}
func main() {
var phone Phone
phone = &XiaoMi{Size: 5.5}
phone.Call(10086)
phone = &HuaWei{Size: 6.0}
phone.Call(10010)
}
|
自定义类型实现接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| package main
import "fmt"
type Printer interface {
PrintSelf()
PrintType()
}
type MyInt int
func (mi MyInt) PrintSelf() {
fmt.Println(mi)
}
func (mi MyInt) PrintType() {
fmt.Printf("%T", mi)
}
func main() {
var p Printer
var mi MyInt = 100
p = &mi
p.PrintSelf()
p.PrintType()
}
|
程序运行时可以修改struct定义吗
不可以
怎么进行强制类型转换
目标类型(变量)
1
2
3
4
5
| func main() {
var a int = 10
var b float32 = float32(a)
fmt.Println(b)
}
|
结构体嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| type A struct {
b B
Size float32
}
type B struct {
Size float32
}
func main() {
a := A{
b: B{
Size: 1.0,
},
Size: 2.0,
}
fmt.Println(a.b.Size)
fmt.Println(a.Size)
}
|
怎么实现继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| type A struct {
B
Size string
}
type B struct {
Size float32
}
func main() {
a1 := A{
B: B{
Size: 3.0,
},
Size: "2",
}
fmt.Println(a1.Size) // 2
fmt.Println(a1.B.Size) // 3
}
|
匿名结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| func main() {
jsonStr := `
{"name":"test","age":18}
`
// 使用map接收
var m map[string]any
err := json.Unmarshal([]byte(jsonStr), &m)
if err != nil {
panic(err)
}
fmt.Println(m["name"])
fmt.Println(m["age"])
// 使用匿名结构体接收
var s struct {
Name string `json:"name"`
Age int `json:"age"`
}
err = json.Unmarshal([]byte(jsonStr), &s)
if err != nil {
panic(err)
}
fmt.Println(s.Name)
fmt.Println(s.Age)
}
|
什么是方法
method其实就是归属于一个type的函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| type MyInt int
func (m *MyInt) Add(n MyInt) MyInt {
return *m + n
}
func (m MyInt) Sub(n MyInt) MyInt {
return m - n
}
func main() {
var tempMyInt MyInt = 10
var m1 = &tempMyInt
fmt.Println(m1.Add(2))
fmt.Println(m1.Sub(2))
var m2 MyInt = 10
fmt.Println(m2.Add(2))
fmt.Println(m2.Sub(2))
}
|
值拥有的方法,其指针也默认拥有;反之不然
但是这里的Add方法是指针拥有的方法 但是m2作为值类型也能调用 是因为go进行了自动类型转换
什么是包
工作空间内的一个目录 包含若干个go文件,目录和包名可以不一样,同一个包内不可以有重复的变量 函数 结构体
什么是module
module是特定版本的包的集合。
如何创建常量
讲一下channel
channel 通道 类似队列
有缓冲和无缓冲的channel
向一个已关闭的channel发送内容会发生panic
可以从一个已关闭的channel里读取数据
1
2
3
4
| v,ok:=<-ch //ch已关闭且为空时,ok为true。
for value:=range ch{
}
|
数据与切片
数组是切片的底层数据结构
切片可以扩容,数组不可以
1
2
3
| var arr [8]int = [8]int{1,2,3}
var arr = [8]int{1,2,3}
arr:=[...]int{1,2,3}
|
讲一下map
1
2
3
| mp:=map[string]int{}
mp:=make(map[string]int)
mp:=make(map[string]int,10)
|
go为什么不保证map顺序?因为扩容的时候保证顺序会带来无谓的开销。
go为什么故意对map乱序?go是一个非常“工程化”的语言,故意乱序是为了让大家都知道它不保证map顺序,减少工程师犯错的几率。
插入和查找时间复杂度为O(1),可以自动扩容
1
2
3
4
5
6
| //遍历map
for key,value:=range mp{
//key和value是副本
value = "newValue1" // 不生效
mp[key] = "newValue2" // 生效
}
|
用map实现一个Set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| package main
import (
"fmt"
)
type Empty struct {
}
type Set struct {
m map[any]Empty
}
func (s *Set) Add(items ...interface{}) {
for _, item := range items {
s.m[item] = Empty{}
}
}
func (s *Set) Remove(item any) {
delete(s.m, item)
}
func (s *Set) Contains(item any) bool {
_, ok := s.m[item]
return ok
}
func (s *Set) Clear() {
s.m = make(map[any]Empty)
}
func (s *Set) Size() int {
return len(s.m)
}
func NewSet(items ...any) *Set {
s := &Set{}
s.m = make(map[any]Empty)
s.Add(items...)
return s
}
func main() {
set := NewSet("AA", "C", 546, false, false, true)
for a := range set.m {
fmt.Print(" ", a)
}
fmt.Println()
set.Add("dd", "ff", 23.2)
set.Remove("C")
for a := range set.m {
fmt.Print(" ", a)
}
set.Clear()
fmt.Println()
fmt.Println("Size: ", set.Size())
}
|
空白标识符:_
_相当于一个占位符,通常一个变量只定义不使用编译器会报错,但_可以避免报错
1
2
3
4
5
6
7
8
9
| func Add(a, b int) int {
fmt.Println(a + b)
return a + b
}
func main() {
a := Add(3, 4)
_ = a
}
|
当函数返回多个值,如果有些值不需要使用,也可以使用_进行占位
如何创建、使用函数闭包
函数闭包其实就是把匿名函数赋给一个变量
1
2
3
4
5
|
a:=func(a int) int{
return a+3
}
_=a(7)
|
类型断言
1
2
3
4
5
| var val any=8
i,ok:=val.(int)
if ok{
fmt.Printf("%d",i)
}
|
type switch的用法
1
2
3
4
5
6
7
8
9
| var val any=8
switch v:=val.(type){
case int:
fmt.Printf("%d",v)
case string:
fmt.Printf("%s",v)
default:
fmt.Println("other type")
}
|
reflect的用法
reflect.TypeOf(x)
reflect.ValueOf(x)
go语言反射里最重要的两个概念是Type和Value,Type用于获取类型相关的信息(比如Slice的长度,struct的成员,函数的参数个数),Value用于获取和修改原始数据的值(比如修改slice和map中的元素,修改struct的成员变量)
crypto包
对称加密AES DES
非对称加密 RSA ECC
哈希 sha256 md5
os包
操作文件,目录
1
2
3
| // 接收系统信号
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
|
byte和rune
byte是一个字节,rune是4个字节。一个rune可以表示unicode里的任意一个字符,比如一个英文、一个中文、一个标点、一个符号等等。
rune和int32
在Go语言中,rune 和 int32 是两种不同的类型,但它们之间存在密切的关系。
rune 类型:rune 是 int32 的别名(alias)。- 它用于表示 Unicode 码点(code point),即 Unicode 字符的整数表示。
- 由于 Unicode 码点的范围是 0 到 0x10FFFF,因此
rune 类型可以表示所有 Unicode 字符。
int32 类型:int32 是一个 32 位的有符号整数类型。- 它的取值范围是 -2^31 到 2^31 - 1。
由于 rune 是 int32 的别名,因此它们在内存中占用相同的大小(32 位),并且可以互换使用。例如:
1
2
3
4
5
6
7
8
9
10
11
12
| package main
import (
"fmt"
)
func main() {
var r rune = 'A'
var i int32 = r
fmt.Printf("rune: %c, int32: %d\n", r, i)
}
|
在这个例子中,rune 类型的变量 r 被赋值为字符 ‘A’,然后将其转换为 int32 类型的变量 i。输出结果将是:
这表明 rune 和 int32 之间可以无缝转换。
交叉编译
GOOS=windows GOARCH=amd64 go build -o myprogram.exe
则编译出在windows上的可执行文件(windows上的可执行文件必须以.exe结尾)
GOOS=darwin GOARCH=amd64 go build -o myprogram
则编译出在Mac上的可执行文件
如何拼接2个string
StringBuilder
stings.Join()