golang基础面试题(1)

in #cnlast month

Go包管理的方式有哪些

  • GOPATH: go version < Go1.5
    通过统一包存放的路径实现包管理;不支持依赖包的版本控制。
    GOPATH 模式:是指我们通过GOPATH来管理我们的包。
    GOPATH路径:指的是GOPATH这个环境变量的路径。
    独立用户GOPATH配置,不同开发者使用不同版本的GO, 可以设置GOPATH在 ~/.bash_profile文件中;如果要全局生效,配置到/etc/profile文件中。
    GOPATH模式下,工程代码必须放在GOPATH/src目录下。
  1. go get: 先将远程代码clone到本地$GOPATH/src目录下;再执行go install命令,安装生成可执行命令保存到$GOPATH/bin目录;使用制定参数-d可以只是下载,不进行安装。
  2. go install: 生成可执行而进行文件,存在$GOPATH/bin下;普通包编译生成.a后缀文件保存到$GOPATH/pkg下,提升编译速率。(main包中存在main函数情况下,才可以生成可执行文件)
  3. go build: 当前目录下编译生成可执行文件;不会将可执行文件拷贝到$GOPATH/bin目录下
  4. go run: 编译运行go文件;不依赖go path; 只能编译可执行go文件。
➜  block ll $GOPATH
drwxr-xr-x  28 896 10 16  2023 bin/     //  可执行文件
drwxr-xr-x   7 224  6 23  2023 pkg/      // go install编译后的文件
drwxr-xr-x  20 640 10  7  2023 src/      // 源代码文件

Godep
诞生于2013年,社区开发第一个包管理工具;通过扫描并记录版本控制的信息,再配合go命令加壳实现;
源码保存到Godeps/workspace 并作为GOPATH使用。

Glide
诞生于2014年,通过glide.yaml记录包版本依赖信息,通过glide.lock文件追踪每个包的具体修改。


  • Go Vender: go version >= Go1.5 (2015)
    诞生于2015年,GO15VENDOREXPERIMENT=1开启;go1.6默认开启;go1.7作为功能支持,取消环境变量,go默认包支持管理工具;源码拷贝到vendor目录,维护vendor.json文件,可制定版本。
go get -u github.com/kardianos/govendor
govendor init
goverdor  add +external
goverdor remove +unused

包依赖不能重用,使得包冗余度提升。


  • Go Modules: go version >= Go1.11 (2018)
    于2018年,go1.11版本发布,GO111MODULE=on开启。从go1.13开始,go modules默认开启。

开启Go Module

export  GO111MODULE=on (unix环境)
set GO111MODULE=on (windows环境)
go env -w GO111MODULE=on

常规使用命令

go mod init
go mod tidy
内部包使用

内部包包含两种:

  1. internal文件夹内的包
  2. 内部开发的包
  • 通过本地包方式导入
    通过在go.mod文件中,使用关键字replace, 可以将在工程中依赖的具体包路径,替换成本地的包路径。
replace github.com/user_name/remote/pkg =>  ../local/path/pkg

缺点,通用性不强,各个开发者本地环境不一致。

  • 通过私有仓库方式导入
    注意设置好GOPROXY和GOPRIVATE变量。
    GOPRIVATE可以设置指定域名的包,不走GOPROXY代理拉取代码。从GO.13版本开始生效使用。
    GONOSUMDB变量命中规则,拉取包时候,不进行代码依赖版本的sum校验。

init()函数是什么时候执行的

  1. 程序执行前包的初始化:类似一些中间件默认配置的初始化。
  2. init()函数的执行顺序:
    a. 在同一个go文件中的多个init方法,按照代码顺序依次执行。
    b. 同一个package中,按照文件名的顺序执行。
    c. 不同package且不相互依赖,按照import顺序执行;相互依赖最后依赖的先执行。

一个包被引用多次,这个包的init函数只会执行一次。
所有的init函数都在同一个goroutine内执行

new和make的区别

make不仅仅分配内存,还会对类型进行初始化(可能会初始化申请多个内存块);new只会分配零值填充的值。(通常只会申请一个内存块)

make只能用于slice, map, channel类型的数据,new则没有限制;

make会返回原始类型T;new返回类型的指针(*T)。

数组与切片

相同点:
全部元素的类型必须都是相同的;都是紧挨存放在一块连续的内存中。

不同点:
数组的零值是每个元素类型的零值;切片的零值是nil.
指针类型的数组和切片直接用类型声明后是nil, 不能直接使用。

image.png


存储形式不同:

数组结构,连续内存空间,长度已知。
image.png

切片有指向存储数据的数组内存空间。
image.png

切片的赋值和函数传递,都是引用同一份内存数据。


func main() {
    str1 := []string{"a", "b", "c"}
    str2 := str1
    fmt.Printf("str1 %p \n", str1)
    fmt.Printf("str2 %p \n", str2)
    str(str1)
}

func str(s []string) {
    fmt.Printf("func param %p \n", s)
}

// output
str1 0x14000100060 
str2 0x14000100060 
func param 0x14000100060 

每个值在内存中只分布在一个内存块上的类型

内存块就是一段在程序运行时刻保存着若干值部的连续内存片段

内存块申请与开辟

  1. 变量声明时(make声明,字面量,普通变量声明)
  2. 拼接非常量字符串
  3. 字符串与字节切片互相转换
  4. 将一个整数转换为字符串
  5. 调用内置的append函数触发切片扩容
  6. map结构添加数据,底层需要扩容。

内存块在哪里开辟和申请

栈: 本质是一个预申请的内存段,由协程维护,初始或最小2KB(>= GO1.19版本自适应)
协程栈尺寸有最大限制,64位系统512M,32位系统128M。
开辟在栈上的内存块只能在此协程内部使用,与其他协程隔离。(并发安全)

堆:内存块可在堆上申请,每个程序程序只有一个堆。
可以被多个协程共享使用,注意并发安全问题。


Q: 什么情况下内存块被开辟在堆上,或者是栈上?

A: 内存块会开辟的位置是由编译器来决定。
从栈上开辟的内存块速度会更快(CPU缓存友好);并且栈上开辟内存块不需要被GC,减轻垃圾回收压力。

内存逃逸
如果一个局部变量的某些值,被开辟到了堆上,就认为这个局部变量发生了内存逃逸。

逃逸到堆上的值部至少被一个开辟到栈上的值部所引用。包级别的变量的都被开辟到了堆上,并被一个全局内存区的隐式指针所引用。

image.png

直接值部
直接值部是每个值只分布在一个内存块的类型;包括布尔,数值,指针,结构体和数组类型。

间接值部
间接值部是被一个或者多个直接值部引用的值,会分布在不同的内存块上;包括切片类型,映射类型,函数类型,接口类型,字符串类型。


内存块的地址是可能发生改变的
若是发生内存扩容调整,栈上引用的堆上的(间接值部)的地址,就有可能会发生数据同步迁移,对应的内存地址可能发生改变。

GO 的指针

  1. 一个指针变量本身存储的是一个内存地址
  2. 一个内存地址在32位系统上占4个字节;在64位上占据8个字节
  3. 内存地址一般用整数的16进制表示,比如0xc000012340

在计算机内存中存储的都是01数据,若用16进制表现出来就是0x...的数据。对数据的定义,是由于具体数据使用场景来表达。例如可以是一个程序局部变量存储的地址,或者是一个指向其他内存区域的地址(指针地址)。

当一个变量被声明的时候,go运行时将为此变量开辟一段内存。此内存的起始地址极为变量的地址。

x := new(int) // 声明指针x
y :=x // 将x的地址赋值给y
tmp := 5
x = &tmp // 将tmp地址赋值给x

image.png

那些值不可被寻址

  1. 映射map的元素
  2. 字符串的字节元素
  3. 常量包级别的函数以及用作函数值的方法等。

GO中的nil

  1. 不同数据类型的nil值,对应的内存大小不同 。(常规类型nil值是8字节,slice切片24字节,interface16字节)
  2. nil 值不一定是可以相互比较的,主要取该数据类型是否可以比较。
  3. 可以比较的两个nil值也不一定相等。

Go中的map

只有可比较的类型才能作为map中的key。(不可比较类型:slice,map,function, 含有不可比较类型的组合结构)

float类型如果作为map的key, 会有什么问题?
map在操作key的时候,会通过math.Float64bits函数转换,若是两个浮点数相同,则会出现覆盖的情况。

Coin Marketplace

STEEM 0.20
TRX 0.12
JST 0.028
BTC 64097.37
ETH 3476.43
USDT 1.00
SBD 2.53