1. =
和 :=
的区别? 1 2 3 4 var foo int foo = 10 foo := 10
2. 指针的作用 1 2 3 4 5 var x = 5 var p *int = &x fmt.Printf("x = %d" , *p)
3. Go 有异常类型么? 1 2 3 4 5 f, err := os.Open("test.txt" ) if err != nil { log.Fatal(err) }
4. 协程和线程和进程的区别? Goroutines是可以同时运行的函数与方法。Goroutines 可以被认为是轻量级的线程。 与线程相比,创建 Goroutine 的开销很小。 Go应用程序同时运行数千个 Goroutine 是非常常见的做法。 并发掌握,goroutine和channel声明与使用!
5. 拼接字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 a := "a" b := "b" var str1 string str1 += a str1 += b var str strings.Builder for i := 0 ; i < 1000 ; i++ { str.WriteString("a" ) } fmt.Println(str.String())
6. 什么是 rune 类型 1 2 3 4 5 fmt.Println(len ("Go语言" )) fmt.Println(len ([]rune ("Go语言" )))
7. 判断 map 结构中是否包含某个 key 的方法 1 2 3 4 if val, ok := map_["key" ]; ok { }
8. defer 的执行顺序 多个 defer 语句,遵从后进先出(Last In First Out,LIFO)的原则,最后声明的 defer 语句,最先得到执行。 panic 需要等defer 结束后才会向上传递。出现panic恐慌时候,会先按照defer的后入先出的顺序执行,最后才会执行panic。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func test () int { i := 0 defer func () { fmt.Println("defer1" ) }() defer func () { i += 1 fmt.Println("defer2" ) }() return i } func main () { fmt.Println("return" , test()) }
defer 在 return 语句之后执行,但在函数真正退出之前,defer 可以修改返回值。我们可以注意到前一个例子的返回值并没有被修改(注意:只有被预先定义的返回值才能被 defer 语句修改)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func test () (i int ) { i = 0 defer func () { i += 1 fmt.Println("defer2" ) }() return i } func main () { fmt.Println("return" , test()) }
9. tag的用法? tag 是 struct 结构的注解,不同的框架或者工具可以通过反射获取到某个字段的属性,增加语义 例如下方例子定义了 struct 结构字段和 json 结构的映射关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package main import "fmt" import "encoding/json" type Stu struct { Name string `json:"user_name"` Id string `json:"user_id"` Age int `json:"-"` } func main () { buf, _ := json.Marshal(Stu{"Bishop" , "1001" , 18 }) fmt.Printf("%s\n" , buf) }
10. Golang 如何判断两个列表(slice)结构等价 可以使用反射 reflect.DeepEqual(a, b) 来判断,但是此种方法比较影响性能,我们这里还是通过基础遍历的方法来完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func SliceAEqurlSliceB (a, b []int ) bool { if len (a) != len (b) { return false } if (a == nil ) != (b == nil ) { return false } b = b[:len (a)] for i, v := range a { if v != b[i] { return false } } return true }
11. 如何标识枚举值(enums) 1 2 3 4 5 6 7 8 9 10 11 12 type Level int32 const ( Level1 Level = iota Level2 Level3 Level4 ) func main () { fmt.Println(Level1, Level2, Level3, Level4) }
12. 空 struct{} 结构的一些用法 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 fmt.Println(unsafe.Sizeof(struct {}{})) type Set map [string ]struct {} func main () { set := make (Set) for _, item := range []string {"A" , "A" , "B" , "C" } { set[item] = struct {}{} } fmt.Println(len (set)) if _, ok := set["A" ]; ok { fmt.Println("A exists" ) } } func main () { ch := make (chan struct {}, 1 ) go func () { <-ch }() ch <- struct {}{} }
13. 声明结构体方法 1 2 3 4 5 6 7 8 9 type Lamp struct {} func (l Lamp) On() { println ("On" ) } func (l Lamp) Off() { println ("Off" ) }
14. make 和 new
make 仅用来分配及初始化类型为 slice、map、chan 的数据。new 可分配任意类型的数据.
new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type.
new 分配的空间被清零, make 分配空间后,会进行初始化. 一个例子1 2 3 4 5 6 func main () { var i *int i = new (int ) *i = 10 fmt.Println(*i) }
15. 代码输出啥相关题 (1) 1 2 3 4 5 6 func main () { var a uint = 1 var b uint = 2 fmt.Println(a-b) }
强类型语言,计算结果也是 uint 类型,1-2 可以转换为 0-1 ,计算机中按照 0 + -1 来计算,-1 通常表示为补码,即所有位数都是1的一个数,即当前系统可表示的最大数
(2) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func main () { runtime.GOMAXPROCS(1 ) wg := sync.WaitGroup{} wg.Add(20 ) for i := 0 ; i < 10 ; i++ { go func () { fmt.Println("A: " , i) wg.Done() }() } for i := 0 ; i < 10 ; i++ { go func (i int ) { fmt.Println("B: " , i) wg.Done() }(i) } wg.Wait() }
考点:go执行的随机性和闭包 解答:谁也不知道执行后打印的顺序是什么样的,所以只能说是随机数字。但是A:
均为输出10,B:
从0~9输出(顺序不定)。第一个go func中i是外部for的一个变量,地址不变化。遍历完成后,最终i=10。故go func执行时,i的值始终是10。 第二个go func中i是函数参数,与外部for中的i完全是两个变量。尾部(i)将发生值拷贝,go func内部指向值拷贝地址。
(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 type People struct {}func (p *People) ShowA() { fmt.Println("showA" ) p.ShowB() } func (p *People) ShowB() { fmt.Println("showB" ) } type Teacher struct { People } func (t *Teacher) ShowB() { fmt.Println("teacher showB" ) } func main () { t := Teacher{} t.ShowA() }
考点:go的组合继承 解答:这是Golang的组合模式,可以实现OOP的继承。被组合的类型People所包含的方法虽然升级成了外部类型Teacher这个组合类型的方法(一定要是匿名字段),但它们的方法(ShowA())调用时接受者并没有发生变化。此时People类型并不知道自己会被什么类型组合,当然也就无法调用方法时去使用未知的组合者Teacher类型的功能。
(4) 1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { runtime.GOMAXPROCS(1 ) int_chan := make (chan int , 1 ) string_chan := make (chan string , 1 ) int_chan <- 1 string_chan <- "hello" select { case value := <-int_chan: fmt.Println(value) case value := <-string_chan: panic (value) } }
考点:select随机性 解答: select会随机选择一个可用通用做收发操作。所以代码是有肯触发异常,也有可能不会。单个chan如果无缓冲时,将会阻塞。但结合 select可以在多个chan间等待执行。有三点原则:
select 中只要有一个case能return,则立刻执行。
当如果同一时间有多个case均能return则伪随机方式抽取任意一个执行。
如果没有一个case能return则可以执行”default”块。(5) 1 2 3 4 5 6 7 8 9 func main () { s := make ([]int , 5 ) s = append (s, 1 , 2 , 3 ) fmt.Println(s) s2 := make ([]int , 0 ) s2 = append (s2, 1 , 2 , 3 ) fmt.Println(s2) }
注意 make 会给定义的长度分配初值(6) map线程安全
16. 如何理解go语言中的interface?
interface是方法申明的集合
任何类型的对象实现了在interface接口中声明的全部方法,则表明该类型实现了该接口
interface可以作为一种数据类型,实现了该接口的任何对象都可以给对应的接口类型变量赋值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 package mainimport ( "fmt" ) type DataWriter interface { WriteData(data interface {}) error } type file struct {} func (d *file) WriteData(data interface {}) error { fmt.Println("WriteData:" , data) return nil } func Cprint (writer DataWriter) { fmt.Println("Cprint" ) } func main () { f := new (file) var writer DataWriter writer = f err := writer.WriteData("data" ) if err != nil { fmt.Println("writeData err!" ) return } Cprint(f) }
17. Go 语言是如何实现切片扩容的? 1 2 3 4 5 6 7 func main () { arr := make ([]int , 0 ) for i := 0 ; i < 2000 ; i++ { fmt.Println("len为" , len (arr), "cap为" , cap (arr)) arr = append (arr, i) } }
我们可以看下结果 依次是 0,1,2,4,8,16,32,64,128,256,512,1024 但到了1024之后,就变成了 1024,1280,1696,2304 每次都是扩容了四分之一左右
18. 数组和切片 golang 中存在两种函数传入方式:值类型和引用类型 值类型只改变当前作用域的值,在该作用域外部不会生效;引用类型改变当前地址对应的值,在作用域外部生效。可以参考下边的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func changeArray (x [3]int ) {x[0 ] = 123 } func changeSlice (x []int ) {x[0 ] = 123 }func main () { x1 := []int {1 ,2 ,3 } fmt.Println("x1 original->" , x1) changeArray(x) fmt.Println("x1 current->" . x1) x := []int {1 ,2 ,3 } fmt.Println("x2 ->" , x) changeSlice(x) fmt.Println("x2 ->" . x) }
19. go 通过切片模拟栈和队列 栈
1 2 3 4 5 6 7 8 9 stack:=make ([]int ,0 ) stack=append (stack,10 ) v:=stack[len (stack)-1 ] stack=stack[:len (stack)-1 ] len (stack)==0
队列
1 2 3 4 5 6 7 8 9 queue:=make ([]int ,0 ) queue=append (queue,10 ) v:=queue[0 ] queue=queue[1 :] len (queue)==0
注意点
参数传递,只能修改,不能新增或者删除原始数据
默认 s=s[0:len(s)],取下限不取上限,数学表示为:[
20.字典 基本用法
1 2 3 4 5 6 7 8 9 10 m:=make (map [string ]int ) m["hello" ]=1 delete (m,"hello" )for k,v:=range m{ println (k,v) }
注意点
map 键需要可比较,不能为 slice、map、function
map 值都有默认值,可以直接操作默认值,如:m[age]++ 值由 0 变为 1
比较两个 map 需要遍历,其中的 kv 是否相同,因为有默认值关系,所以需要检查 val 和 ok 两个值
21.标准库 sort
1 2 3 4 5 6 sort.Ints([]int {}) sort.Strings([]string {}) sort.Slice(s,func (i,j int ) bool {return s[i]<s[j]})
math
1 2 3 4 5 6 math.MaxInt32 math.MinInt32 math.MaxInt64 math.MinInt64
copy
1 2 3 4 5 6 7 8 9 10 copy (a[i:],a[i+1 :])a=a[:len (a)-1 ] a:=make ([]int ,n) a[n]=x a:=make ([]int ,0 ) a=append (a,x)
22.常用技巧 类型转换
1 2 3 4 5 6 7 8 9 10 s="12345" num:=int (s[0 ]-'0' ) str:=string (s[0 ]) b:=byte (num+'0' ) fmt.Printf("%d%s%c\n" , num, str, b) num,_:=strconv.Atoi() str:=strconv.Itoa()
读取标准输入
断言
1 2 3 if f, ok := w.(*os.File); ok { }
互斥锁常用语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var ( mu sync.Mutex balance int ) func Deposit (amount int ) { mu.Lock() balance = balance + amount mu.Unlock() } func Balance () int { mu.Lock() b := balance mu.Unlock() return b }
读写锁 “多读单写”锁(multiple readers, single writer lock)
1 2 3 4 5 6 7 var mu sync.RWMutexvar balance int func Balance () int { mu.RLock() defer mu.RUnlock() return balance }
sync.Once 惰性初始化
1 2 3 4 5 6 7 var loadIconsOnce sync.Oncevar icons map [string ]image.Imagefunc Icon (name string ) image.Image { loadIconsOnce.Do(loadIcons) return icons[name] }