1. go包管理的方式有哪些?
1.1 goPath<go1.5
提供存放包的路径,依赖默认存储在$GOPATH/src下.
go get : 拉取远程代码并执行go install
go install : 生成二进制可执行文件,放到$GOPATH/bin目录下.
go build : 编译生成二进制可执行文件,但不会复制到本地
go run : 编译并运行,一般用来测试
1.2 goVendor
本质是将源码拷贝到vendor目录下,维护vendor.json文件,不能解决依赖冲突,包的依赖不能重用.
govendor init: 初始化项目
1.3 go Moudles>=go1.11
目前主流的go包管理工具,在go1.13时默认开启,设置auto会在gopath目录中自动关闭,避免管理冲突.依赖包存在$GOPATH/pkg/mod.
set GO111MODULE=on (windows)
export GO111MODULE=on (unix)
go mod init: 初始化gomodules工程,并创建go.mod文件.
go mod tidy: 解决包的依赖缺少或冗余问题,缺少的会下载并将依赖包的信息维护到go.mod和go.sum中,没用到的会从go.mod和go.sum中移除.
go mod download: 下载依赖包到本地缓存
go mod vendor: 兼容go vendor模式
go.mod文件: 该文件描述模块的属性,以及对其他模块和go版本的依赖性
require: 项目的直接或间接依赖
replace: 替换依赖包的路径
include: 指定必须包含的依赖
exclude: 指定要排除的依赖或版本避免冲突
go.sum文件: 是触发项目依赖校验时生成,如执行go build,go run,go get等
它详细记录了当前项目直接或间接依赖的所有模块的版本
每行由模块导入路径,特定版本和预期哈希组成<module> <version>[/go.mod] <hash>go.sum如何做包校验的?
当拿到项目源代码并在本地构建时,go命令会从本地缓存中查找所有go.mod中记录的依赖包,并计算本地依赖包的哈希值,与go.sum中的记录做对比,即检测本地缓存中的依赖包版本是否满足go,sum文件的期望,校验失败时构建会被拒绝
缺少模块时,若缓存中没有,需要下载并计算hash添加记录到go.sum里,如果缓存中有就匹配go.sum的记录
三种情况不对依赖包做哈希校验:
配置goprivate时,goprivate变量主要是设置内部的包
vendor目录中的包,因为可以离线编译,也不做校验
GOSUMDB设置为off时
2. init()函数是什么时候执行的?
2.1 init函数的作用是什么?
用于程序执行前做包的初始化,比如初始化变量等
2.2 init()函数的执行顺序?
main函数前,全局变量后,如果有依赖的话先执行被依赖包中的init函数,
所有init函数在同一个goroutine内执行
2.3 go文件的初始化顺序?
引入的包->全局变量->init->main
3. go中如何获取项目的根目录?
获取项目根目录有4个函数:
os.Getwd()
os.Args[0]
os.Executable
这三个与可执行文件路径有关,有一定风险.
推荐runtime.Caller,不受执行文件路径影响
4. go输出 %v %+v %#v有什么区别?
以结构体为例:
%v只输出值,
%+v会输出值和字段名,
%#v会输出值,字段名,结构体名字,以及字段里指针类型的名称Scan和Scanln有什么区别?
Scan等所有值输入完成执行程序,Scanln按行读取.
5. new和make有什么区别?
new和make都是用来分配内存的内建函数,
make分配内存会对切片元素初始化零值,map和channel不会被初始化,可能开辟多个内存块,
new分配内存,被零值填充,只会开辟一个内存块.make只能分配并初始化引用类型数据,如slice,map,channel等,
new可以分配任意类型的数据.make返回的是类型本身T,而new返回的是该类型的指针 *T
func make(t Type, size ...IntegerType) Type
func new(Type) *Type
6. 数组和切片有什么区别?
相同点: 存储同类型元素,内存连续.
区别:
数组内存空间在声明时开辟出来,
切片声明后长度是0,是[],make指定长度后才会填充零值.
数组是值类型,默认值传递;切片是引用类型,默认地址传递.
切片可以和nil比较,但空切片不为nil
7. 双引号和反引号有什么区别?
双引号中字符串支持转义,反引号不转义
8. strings.TrimRight和strings.TrimSuffix有什么区别?
trimRight接收一个或多个参数,移除右侧所有匹配的字符或空白字符,
trimSuffix,接收一个后缀参数,移除后缀.
9. 数值类型运算后值溢出会发生什么?
无符号整型溢出,值为0
有符号整型溢出,值出现反转,在计算过程中需要先对数值做检验,使用math里的maxInt做边界检查
浮点型溢出后为最大值
常量值不允许溢出,编译时不通过
数值类型转换需要考虑溢出
整型转无符号整型时,需要考虑符号丢失问题
移位操作需要考虑位数是否会溢出浮点类型精度丢失问题:
计算机中精度是指特定范围内,将n位十进制数转为二进制数,再转回十进制,转换过程不发生损失,则有n位精度.
10. 值在内存中只分布在一个内存块上的类型有哪些?
内存逃逸: 如果一个局部变量的某些值部被开辟到了堆上,就认为这个局部变量发生了内存逃逸,go编译器的逃逸分析不完善,所以某些本可以开辟到栈上的值也可能被开辟到堆上.
在go程序运行时,每个被使用的逃逸到堆上的值部至少会被一个开辟在栈上的值部所引用.
比如局部变量T发生逃逸分配到堆上,那么一个*T类型的隐式指针会创建在栈上,该指针存储T在堆的地址,所以内存逃逸发生时,会有一个从栈到堆的引用关系.
堆: 全局变量,动态分配(new/make)的内存(需要显式垃圾回收),对象实例,切片和映射和通道(句柄存储在栈上)
栈:函数参数和局部变量直接值部是每个值只分布在一个内存块的类型,包括布尔类型,数值类型,指针类型,结构体和数组.
有些类型存在间接值部,间接值部是被一个或者多个直接值部引用的值,这种类型的值会分布在不同的内存块上,含有间接值部的类型有切片,映射,通道,函数,接口,字符串.
11. 哪些类型可以被内嵌?
以字段形式内嵌到结构体里,为匿名字段,只有类型没有字段名的,类型名就是字段名.
哪些类型可以被结构体内嵌?
为一个类型且非指针,如基本类型或自定义结构体类型
12. 哪些类型可以使用len?哪些类型可以使用cap?
在go中有3种容器类型,数组,切片和map映射,
其中数组容量和长度始终相等,
映射容量没有限制,会自动扩容,cap对映射无意义,
切片的容量大于等于切片长度.
cap: 数组和切片
len: 数组,切片,映射,通道,字符串
13. 哪些类型的值可以用组合字面量表示?
组合字面量: 用于创建和初始化复合类型(如数组、切片、结构体、映射等)的语法,组合字面量可以直接在代码中构造这些类型的值,并且可以指定具体的元素或字段值.
组合字面量: 结构体类型和容器(数组切片映射)类型的值,形式为T{...},其中T
是类型,{...}
是初始化值的集合.允许在代码中直接构造这些类型的值,并指定具体的元素或字段值。容器类型组合字面量: var arr [3]int = [3]int{1, 2, 3}
可以在 Go 中使用组合字面量表示的类型: 数组,切片,结构体,映射,通道,(匿名)函数
14. go语言的指针有哪些限制?
14.1 指针
指针: 一个指针变量本身存储的是一个内存地址,可以用来定位一段内存,一个内存地址在64位操作系统上占8个字节,一般用整数的十六进制表示.
当变量被声明时,将为它开辟一段内存,内存起始地址为此变量的地址.普通指针为一维指针,指向一个变量地址.
指针被取地址后的,为二维指针,多维影响可读性.获取指针的值:&
指针有什么用?
传递地址指向同一块内存 可以修改原始值指针有什么限制?
不支持进行算术运算,
不能被随意转换为其他指针类型,当底层类型一致且为无名类型时可以,
不能随意和其他指针类型的值进行比较.除非类型相同或可隐式转换
14.2 哪些值不可被寻址?
不可被寻址的值通常包括字面量,函数返回值,映射的值,接口的值和通道中的值.这些值在运行时可能没有固定的内存地址,或者它们的地址是不确定的.
变量,结构体字段,切片元素,数组等都是可以取地址的,
一个值不能被取址,一般是由于它的地址可能是发生变化的.
15. 哪些类型的零值可以用nil表示?
指针,切片,管道,函数,接口,映射
16. 如何实现任意数值转换?
类型转换和类型断言,可以通过switch value.(type)对空接口断言,数值转化过程中需要考虑溢出问题.
对于字符串和数值之间的转换,可以使用
strconv
包提供的函数.
17. float或切片可以作为map的key吗?
只有可以比较的类型才能作为map的key,除了slice,map,func外,其他都是可比较类型,
切片不可以做key,浮点做key会因为精度出现问题.
18. 怎么使用可变参数?
函数只能有一个可变参数,为参数列表里最后一个形参,可变参数是切片类型.
func myFunction(a int, b string, c ...int) { // 函数体 }
19. 调用函数传入结构体时,是传值还是传指针?
在go中,函数参数传递只有值传递,而且传递的实参是原始数据的一份拷贝.
在go中赋值操作和函数调用传参都是将原始值的直接部分复制给了目标值,如果原始值含有间接部分,则在赋值完成后,目标值和原始值的直接部分将引用着相同的间接部分,也就是说,两个值共享底层的间接值部.
具有间接值部的数据结构有切片,字符串,映射,管道.字符串内部实现也包含指针.(但字符串不可变,所以传参时也是副本)
直接值部是元素本身就有的一些值或属性,间接值部是被引用的部分.
在go里,赋值和参数传递都是拷贝直接值部,所以不存在所谓的值传递和引用传递,只不过在直接值部被复制的过程中,如果该类型具有间接值部(指针地址),则间接值部的地址也会被复制.ps: 如果希望函数修改原始值,需要传递指针去指向同一个内存,通过指针可以直接访问和修改原始值.
20. interface可以比较吗?
哪些类型可以比较?
除了切片,映射,函数外,其他都是可比较类型.若数组或结构体中存在不可比较类型,那么也不可比较.
当判断两个接口类型变量是否相同,只需要判断_type/tab是否相同,以及data指针指向内存空间中存储数据的值是否相同就可以了,
对于空接口类型,只有_type和data所指向数据内容一致的情况下,两个空接口类型变量才相等.类型检查: 使用
reflect.TypeOf
检查两个接口变量的类型是否相同值比较: 使用
reflect.DeepEqual
检查两个接口变量的值是否相同
21. 如何使一个结构体不能被比较?
对于结构体而言,希望不能被比较,给该结构体一个不可比较类型的字段即可.
什么条件下两个结构体相等?
能够比较的前提是两者的字段都只包含可比较类型.其次两个结构体之间需要能够隐式转换,结构体的比较等于逐个比较相应的字段.
两个结构体值只有在他们相应字段都相等的情况下才相等.
22. reflect.DeepEqual()和bytes.Equal()比较切片时有什么区别?
reflect.DeepEqual(): 比较的是值在逻辑上的相等性,会递归地比较两个值的内容,确保在逻辑上是相等的.对于指针类型,它比较的是指针指向的值,而不是指针本身的地址.适用于复杂数据结构,切片,映射,结构体等.
bytes.Equal():比较字面量,适用于字节切片.两者都比较切片时:
DeepEqual():两个切片有一个初始化另一个没有初始化时为false,可以认为比较两者是否都有地址或都没有地址
bytes.Equal():比较字面量
23. 空struct{}有什么用?
当用map类型,且只需要判断key是否存在不关心值时,通常会用空结构体来做map的值,因为空结构体不占空间,
这种用法相当于把map当set集合用,go没有set集合类型,所以通常用空结构体配合map实现set集合类型.
24. 怎么处理错误算最优雅?
go 的错误处理是在函数返回值中带上error,对error进行判断处理,高度自主化的错误处理模式是go的一个特色.
go的error类型就是一个只包含Error()方法的interface类型.也就是说,可以自定义一个错误类型,只要实现了Error方法就是一个Error类型.
可以参考PathError的处理.程序出错启用处理方案,就不再返回错误.
程序最外层通过日志记录错误,而在调用链中,直接传递error.
25. 如何判断两个对象是否完全相同?
在go中,除了slice,map,func外,其他都是可比较类型,reflect.DeepEqual()方法,可以用来比较两个对象是否深度相等.
.
==和DeepEqual()函数的区别?
==用于比较两个值是否相等,取决于比较的类型:
基本类型:== 直接比较两个值是否相等.
切片、映射、结构体:== 只比较两个变量是否指向同一个对象(即内存地址是否相同),而不是比较内容.reflect.DeepEqual(): 比较的是值在逻辑上的相等性,会递归地比较两个值的内容,确保在逻辑上是相等的.对于指针类型,它比较的是指针指向的值,而不是指针本身的地址.适用于复杂数据结构,切片,映射,结构体等.
举例:
若x和y类型不同,调用DeepEqual(x,y)的结果为false,但使用==可能为true,==判断基类型和值,比如基类型都是结构体且值相同.
若同类型的x,y是两个引用着不同值的指针值,则==结果为false,但DeepEqual(x,y)可能为true,因为DeepEqual比较引用的两个值是否相等,不关心两个值是否指向同一个地址.
26. 怎么判断一个对象是否拥有某个方法?
通过类型转化判断.
通过reflect包中反射实现.