golang基础面试题(2)

in #cn17 days ago

函数调用时struct参数传递问题

在调用一个函数,入参是struct类型对象时候,是进行传值还是引用的传递?

在Go中,函数的参数传递都是值传递,并且传递的实参数都是原始数据的一份拷贝; 就是直接值部的拷贝,如果直接值部包含间接值部的引用,则两者共享相同的间接值部。

slice, map,function,chan都是包含底层的间接值部,go中的赋值操作函数调用传参,都是将原始值的直接值部复制给了目标值。

也就是说,目标值和原始值有相同的间接值部的引用,两个值共享了底层的间接值部。在函数内对入参数的间接值部的修改,会影响到外部的对象。

type person struct {
    name string
    age  int
}

type persons struct {
    names []string
}

func changeStruct1(p person) {
    p.name = "hehe"
    p.age = 20
}
func changeStruct2(p2 persons) {
    fmt.Printf("p2.names.pointer:%p \n", p2.names)
    p2.names[2] = "d"
}

func changeStruct3(ps persons) {
    fmt.Printf("ps.names.pointer:%p \n", ps.names)

       // 赋值后指向了不同的间接值部
       ps.names = []string{"e","f","g"}
       fmt.Printf("ps.names.pointer:%p \n", ps.names)
}


func main() {
    p1 := person{
        name: "haha",
        age:  18,
    }

    changeStruct1(p1)
    fmt.Println("p1:=", p1)

    s := []string{"a", "b", "c"}
    p2 := persons{names: s}
    changeStruct2(p2)
    fmt.Printf("p2.names.pointer:%p \n", p2.names)
    fmt.Println("p2:=", p2)
}



// output
p1:= {haha 18}   //  入参数struct属性没有引用类型的字段,函数内修改不影响原对象
p2.names.pointer:0x1400008c060 
p2.names.pointer:0x1400008c060 
p2:= {[a b d]}    // 入参数struct属性包含引用类型的字段,函数内修改会影响原对象


  • 当结构体的字段中包含间接值部的类型时,实参和原始字段的间接值部共享同一个内存地址
  • 在函数内对整个结构体赋值时,实参的间接值部地址指向会改变
  • 结构体赋值过程也会先复制结构体以及结构体字段值的直接值部

interface 类型的可比较性

在golang中那些类型可以比较

  1. 除了slice,map, function 这几种类型之外的都可以比较
  2. 元素类型为可比较类型的数组,字段为可比较类型的切片(struct字段包含不可比较类型,则也是不可比较)
  3. map, slice,function 可以与nil 比较。
type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

判断两个接口类型变量是否相同,只需要判断 :

  1. _type/tab 是否相同
  2. data指针指向的内存空间所存储的数据值知否相同。
       var a int
    var b interface{} = a // eface._type = int
    var c int32
    var d interface{} = c // eface._type = int32
    println("b=", b, "d=", d)
    println("b==d ?", b == d)


// output
b= (0x104b02300,0x14000096f60) d= (0x104b02380,0x14000096f58)
b==d ? false

对于空接口类型变量,只有_type和data所指向数据内容一致的情况下,两个空接口类型变量才想等。

空结构体{} 的使用

空结构体{} 是不占用内存空间的。

 st := struct{}{}
 fmt.Println("unsafe.Sizeof st:", unsafe.Sizeof(st)) // 0

在 Go 语言中,空结构体struct{}具有以下作用:

  • 节省内存:空结构体不占用任何内存空间,因此可以用于节省内存。例如,在创建一个包含大量元素的结构体数组时,如果某些元素不需要存储任何数据,可以将其类型定义为空结构体。

  • 表示占位符:空结构体可以用作占位符,表示某个位置或某个参数不需要实际的数据。

  • 实现通道标识:空结构体可以用作通道的元素类型,用于实现信号通知机制。通过在通道中发送空结构体的值,可以向接收方发送信号,表示某个事件已发生或某个操作已完成。

net/http包相关

启动http server执行过程

  1. 创建http server 对象
  2. 注册路由和中间件
  3. 启动http server服务器,传入监听地址和多路复用器。
Server.Serve(listener) ->  

go c(net.Conn).serve() ->   (每个connection连接开独立携程处理请求)

for {  serverHander.ServeHTTP } 

Go的编译过程

go help build (常见的使用命令参数)

    -a
        force rebuilding of packages that are already up-to-date.
    -n
        print the commands but do not run them.
    -x
        print the commands.
    -v
        print the names of packages as they are compiled.

    -gcflags '[pattern=]arg list'
        arguments to pass on each go tool compile invocation.
         -race
        enable data race detection.

go build整体的编译过程

image.png

go的编译器和连接器都是使用golang语言编写的,集成在go sdk中runtime支持。
分别在路径$GOROOT/src/cmd/compile和$GOROOT/src/cmd/link中。

编译大体流程就是: 词法分析-> 语法分析 -> 类型检查 -> 中间代码生成 -> 代码优化 -> SSA生成 -> 机器码生成。

词法分析: 具体词法分析源代码路径$GOROOT/src/cmd/compile/internal/syntax/scanner.go.

package main

import "fmt"

func main() {
    fmt.Println("hello world")
}

image.png
扫描十六进制的数据,转换成token序列,为后续语法分析准备。

image.png

语法分析: 将token需求转换成抽象语法树(AST)。

AST 是树状的方式表示编程语言的语法结构,是源代码语法结构一种抽象表示。每个节点都表示一个语法元素。

image.png

类型检查: $GOROOT/src/cmd/compile/internal/types2/check.go , 配合AST对类型进行检查是否有错误。

中间代码生成IR: $GOROOT/src/cmd/compile/internal/coder/irgen.go
便于对代码进行优化,并且接耦和复用。支持在不同的环境下进行不同的处理。(生成一种介于源代码和机器码之间的中间类型代码)

代码优化: 死代码消除,函数内联,逃逸分析,闭包重写等。
通过命令生成SSA格式的html文件,展开可看细节。

➜  main GOSSAFUNC=main go build main.go
# runtime
dumped SSA to /Users/cotox/go/src/go-learn/src/main/ssa.html
# command-line-arguments
dumped SSA to ./ssa.html

image.png

  • 函数内联
    用函数体替换函数调用来减少因函数调用和造成的额外上下文切换开销。(简而言之,就是函数方法提代码拷贝到调用方法上下文)
func main() {
    n := 1
    for i := 0; i < 10; i++ {
        incr(i)
    }
    println(n)
}

func incr(n int) int {
    return n + 1
}

执行命令➜  main go build -gcflags="-m -m" main.go
# command-line-arguments
./main.go:11:6: can inline incr with cost 4 as: func(int) int { return n + 1 }
./main.go:3:6: can inline main with cost 26 as: func() { n := 1; for loop; println(n) }
./main.go:6:7: inlining call to incr func(int) int { return n + 1 }

如果使用//go:noinline ,则编译器不会进行内联操作。

# command-line-arguments
./main.go:12:6: cannot inline incr: marked go:noinline
./main.go:3:6: can inline main with cost 79 as: func() { n := 1; for loop; println(n) }

  • 逃逸分析
    由编译器自动决定将变量分配到goroutine的栈内存或者全局堆内存上。
func main() {
    x := 1
    y := 2
    go func() {
        fmt.Println(x, y)
    }()
    x = 2
    z := x + y
    fmt.Println(z)
    time.Sleep(time.Millisecond)
}

通过命令go build -gcflags="-m -m" main.go可以看到逃逸分析结果

./main.go:11:5: func literal escapes to heap:
./main.go:11:5:   flow: {heap} = &{storage for func literal}:
./main.go:11:5:     from func literal (spill) at ./main.go:11:5
./main.go:11:5:     from (func literal)() (call parameter) at ./main.go:13:3
./main.go:9:2: x escapes to heap:
./main.go:9:2:   flow: {storage for func literal} = &x:
./main.go:9:2:     from func literal (captured by a closure) at ./main.go:11:5
./main.go:9:2:     from x (reference) at ./main.go:12:15
./main.go:12:14: y escapes to heap:
./main.go:12:14:   flow: ~arg1 = &{storage for y}:
./main.go:12:14:     from y (spill) at ./main.go:12:14
./main.go:12:14:     from ~arg0, ~arg1 := x, y (assign-pair) at ./main.go:12:14
./main.go:12:14:   flow: {storage for []interface {}{...}} = ~arg1:
./main.go:12:14:     from []interface {}{...} (slice-literal-element) at ./main.go:12:14
./main.go:12:14:   flow: fmt.a = &{storage for []interface {}{...}}:
./main.go:12:14:     from []interface {}{...} (spill) at ./main.go:12:14
./main.go:12:14:     from fmt.a = []interface {}{...} (assign) at ./main.go:12:14
./main.go:12:14:   flow: {heap} = *fmt.a:
./main.go:12:14:     from fmt.Fprintln(io.Writer(os.Stdout), fmt.a...) (call parameter) at ./main.go:12:14

源代码: $GOROOT/src/cmd/compile/internal/inline/inl.go

  • 闭包变量捕获
    还是使用上述的demo代码,通过编译参数进行查看:
./main.go:11:5: func literal escapes to heap:
./main.go:11:5:   flow: {heap} = &{storage for func literal}:
./main.go:11:5:     from func literal (spill) at ./main.go:11:5
./main.go:11:5:     from (func literal)() (call parameter) at ./main.go:13:3
./main.go:9:2: x escapes to heap:
./main.go:9:2:   flow: {storage for func literal} = &x:
./main.go:9:2:     from func literal (captured by a closure) at ./main.go:11:5   // 看到闭包捕获x,传入x地址引用
./main.go:9:2:     from x (reference) at ./main.go:12:15

Coin Marketplace

STEEM 0.19
TRX 0.12
JST 0.028
BTC 64060.29
ETH 3471.63
USDT 1.00
SBD 2.52