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) // x 可以用 *p 访问
// `*` 运算符,也称为解引用运算符,用于访问地址中的值。
// `&`运算符,也称为地址运算符,用于返回变量的地址。

3. Go 有异常类型么?

1
2
3
4
5
// go 只有错误类型
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

// 推荐使用 `strings.Builder`,最小化内存拷贝次数。
var str strings.Builder
for i := 0; i < 1000; i++ {
str.WriteString("a")
}
fmt.Println(str.String())

6. 什么是 rune 类型

1
2
3
4
5
/*
Go 语言中,字符串的底层表示是 byte (8 bit) 序列,而非 rune (32 bit) 序列。例如下面的例子中 `语` 和 `言` 使用 UTF-8 编码后各占 3 个 byte,因此 `len("Go语言")` 等于 8,当然我们也可以将字符串转换为 rune 序列。
*/
fmt.Println(len("Go语言")) // 8
fmt.Println(len([]rune("Go语言"))) // 4

7. 判断 map 结构中是否包含某个 key 的方法

1
2
3
4
// ok 为 true 代表有这个 key,且 val 是这个 key 的值
if val, ok := map_["key"]; ok {
//do something
}

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())
}
// defer2
// defer1
// return 0

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())
}
// defer2
// return 1

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:"-"` // 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) // 0, 1, 2, 3
}

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{}{})) // 0

// 可以用作占位符使用
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)) // 3
if _, ok := set["A"]; ok {
fmt.Println("A exists") // A exists
}
}

// 用作传递信号
func main() {
ch := make(chan struct{}, 1)
go func() {
<-ch
// do something
}()
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

  1. make 仅用来分配及初始化类型为 slice、map、chan 的数据。new 可分配任意类型的数据.
  2. new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type.
  3. new 分配的空间被清零, make 分配空间后,会进行初始化.
    一个例子
    1
    2
    3
    4
    5
    6
    func main() {
    var i *int
    i = new(int) // 分配内存地址并返回指向改地址的指针,同时置为类型的0值
    *i = 10
    fmt.Println(*i) // 10
    }

15. 代码输出啥相关题

(1)
1
2
3
4
5
6
func main() {
var a uint = 1
var b uint = 2
fmt.Println(a-b)
}
// 2^32-1 or 2^64-1 根据操作系统位数有不同结果

强类型语言,计算结果也是 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()
}

// shwoA
// showB

考点: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 int1)
    string_chan := make(chan string1)
    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) // [0,0,0,0,0,1,2,3]

    s2 := make([]int, 0)
    s2 = append(s2, 1, 2, 3)
    fmt.Println(s2) // [1,2,3]
    }
    注意 make 会给定义的长度分配初值
    (6)
    map线程安全

16. 如何理解go语言中的interface?

  1. interface是方法申明的集合
  2. 任何类型的对象实现了在interface接口中声明的全部方法,则表明该类型实现了该接口
  3. 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 main
    import (
    "fmt"
    )
    // 定义一个数据写入器
    type DataWriter interface {
    WriteData(data interface{}) error
    }
    // 定义文件结构,用于实现DataWriter
    type file struct {
    }
    // 实现DataWriter接口的WriteData方法
    func (d *file) WriteData(data interface{}) error {
    // 模拟写入数据
    fmt.Println("WriteData:", data)
    return nil
    }
    // 接口可以作为数据类型传递,体现2,3
    func Cprint(writer DataWriter){
    fmt.Println("Cprint")
    }

    func main() {
    // 实例化file
    f := new(file)
    // 声明一个DataWriter的接口
    var writer DataWriter
    // 将接口赋值f,也就是*file类型
    writer = f
    // 使用DataWriter接口进行数据写入
    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)


}
// x1 -> [1,2,3]
// x2 -> [1,2,3]

19. go 通过切片模拟栈和队列

1
2
3
4
5
6
7
8
9
// 创建栈
stack:=make([]int,0)
// push压入
stack=append(stack,10)
// pop弹出
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)
// enqueue入队
queue=append(queue,10)
// dequeue出队
v:=queue[0]
queue=queue[1:]
// 长度0为空
len(queue)==0

注意点

  • 参数传递,只能修改,不能新增或者删除原始数据
  • 默认 s=s[0:len(s)],取下限不取上限,数学表示为:[

20.字典

基本用法

1
2
3
4
5
6
7
8
9
10
// 创建
m:=make(map[string]int)
// 设置kv
m["hello"]=1
// 删除k
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
// int排序
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
// int32 最大最小值
math.MaxInt32 // 实际值:1<<31-1
math.MinInt32 // 实际值:-1<<31
// int64 最大最小值(int默认是int64)
math.MaxInt64
math.MinInt64

copy

1
2
3
4
5
6
7
8
9
10
// 删除a[i],可以用 copy 将i+1到末尾的值覆盖到i,然后末尾-1
copy(a[i:],a[i+1:])
a=a[:len(a)-1]

// make创建长度,则通过索引赋值
a:=make([]int,n)
a[n]=x
// make长度为0,则通过append()赋值
a:=make([]int,0)
a=append(a,x)

22.常用技巧

类型转换

1
2
3
4
5
6
7
8
9
10
// byte转数字
s="12345" // s[0] 类型是byte
num:=int(s[0]-'0') // 1
str:=string(s[0]) // "1"
b:=byte(num+'0') // '1'
fmt.Printf("%d%s%c\n", num, str, b) // 111

// 字符串转数字
num,_:=strconv.Atoi()
str:=strconv.Itoa()

读取标准输入

1

断言

1
2
3
if f, ok := w.(*os.File); ok {
// ...use f...
}

互斥锁常用语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var (
mu sync.Mutex // guards balance
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.RWMutex
var balance int
func Balance() int {
mu.RLock() // readers lock
defer mu.RUnlock()
return balance
}

sync.Once 惰性初始化

1
2
3
4
5
6
7
var loadIconsOnce sync.Once
var icons map[string]image.Image
// Concurrency-safe.
func Icon(name string) image.Image {
loadIconsOnce.Do(loadIcons)
return icons[name]
}