golang常见面试题
go中触发异常的场景有哪些?
- 空指针解析
- 下标越界
- 除数为0
- 调用panic函数
Printf、Sprintf、Fprintf函数的区别用法是什么?
都是把格式好的字符串输出,只是输出的目标不一样
- Printf: 是把格式字符串输出到标准输出(一般是屏幕,可以重定向),Printf是和标准输出文件(stdout)关联的
- Sprintf:是把格式字符串输出到指定字符串中,所以参数比Printf多一个char*, 那就是目标字符串地址
- Fprintf: 是把格式字符串输出到指定文件设备中,所以参数比Sprintf多一个文件指针FILE*, 主要用于文件操作
Fprintf是格式化输出到一个stream, 通常是到文件
new和make的区别?
- new的作用是初始化一个指向类型的指针(*T)
new函数是内建函数,函数定义func new(Type) *Type
,使用new函数来分配空间,
传递给new函数的是一个类型,而不是值,返回值是指向这个新分配的零值的指针 - make的作用是为slice、map、chan初始化,make函数是内建函数,函数定义:
func make(t Type, size ...IntegerType) Type
第一个参数是一个类型,第二个参数是长度,返回值是一个类型
make(T, args)与函数new(T)目的不同,它仅仅用于创建Slice、Map、Channel, 并且返回值是T(不是T*)
一个初始化的(不是零值)的实例
详细说说切片和数组的区别?
数组是具有固定长度,且拥有零个或者多个,相同数据类型元素的序列,数组的长度是数组类型的一部分,
[3]int和[4]int是两种不同类型的数组
数组需要指定大小,不指定的也会根据初始化的自动推算出大小,不可改变,数组是值传递
数组是内置类型,是一组同类型数据的集合,在其初始化后长度是固定的,无法改变其长度
当作为方法的参数传入时将复制一份数组,而不是引用同一指针,数组的长度也是其类型的一部分,
可通过内置函数len(array)可获取其长度
package main
import "fmt"
func main() {
var array [5]int
array = [5]int{1, 2, 3, 4, 5}
fmt.Println(array)
}
切片表示一个拥有相同类型元素的可变长度的序列,切片是一种轻量级的数据结构,它有三种属性:指针、长度和容量
切片不需要指定其大小,切片可以通过数组进行初始化,也可以通过make函数进行初始化,初始化时len=cap,
在追加元素时如果容量不够,就进行扩容
golang的内存模型,为什么小对象多了会造成gc压力
通常小对象过多会导致GC三色法消耗过多的GPU,优化思路是,减少对象分配
Data Race问题怎么解决,能不能不加锁解决这个问题?
解决数据竞争的问题可以使用互斥锁sync.Mutex,
解决数据竞争(Data Race)也可以使用管道解决,使用管道的效率要比互斥锁高
使用channel的示例:
func main() {
fmt.Println(getNumber())
}
func getNumber() int {
var i int
var ch = make(chan byte)
go func() {
defer func() {
ch <- '0'
}()
i = 5
}()
<-ch
return i
}
在range迭代slice时,你怎么修改值的?
在range迭代中,得到的值其实是一份元素的值拷贝,更新拷贝并不会更改原来的元素,即拷贝的地址并不是原有元素的地址
func main() {
s := []int{11, 22, 33}
for _, v := range s {
v *= 10
fmt.Printf("%p\n", &v)
}
fmt.Println(s)
fmt.Printf("%p, %p\n", &s[0], &s)
}
如果要修改原来元素的值,应该使用索引直接访问
func main() {
s := []int{11, 22, 33}
for i := range s {
s[i] *= 10
}
fmt.Println(s)
}
如果你的集合保存的是指向值的指针,需稍作修改,依旧需要使用索引访问元素,不过可以使用range出来的元素
直接更新原有的值
func main() {
data := []*struct{num int}{{num: 1}, {2}, {3}}
for _, v := range data {
v.num *= 10
}
fmt.Println(data[0], data[1], data[2])
}
select可以用于什么?
常用goroutine的完美退出,golang的select就是监听IO操作,当IO操作发生时,触发响应的动作,
每个case语句里必须是一个IO操作,确切地说,应该是一个面向channel的IO操作
go语言编程的好处是什么?
- 概念
golang是一种强类型语言,这意味着它本质上不如解释语言灵活,但go提供了任何类型(接口)和反射机制,
使语言在灵活性上与解释语言非常接近,越来越多的人开始学习golang。 - 性能(机器代码)
golang是一种编译型语言,可以编译为机器代码,编译后的二进制文件可以直接部署到目标机器而不需要额外的依赖,
性能优于那些解释语言,
动态的语言感受到golang是一种静态语言,但是它给开发人员带来了动态语言的感觉,作为静态的语言,
在进行编译时可以检测到许多隐藏的问题,尤其是语法错误
你是否主动关闭过http链接,为啥要这样做?
有关闭,不关闭有可能会消耗完socket描述符,有如下2中关闭方式
- 直接设置请求变量的Close字段值为true,每次请求结束后就会主动关闭链接
- 设置Header请求头部选项Connection:close, 然后服务器返回的响应头部也会有这个选项,
此时http标准库会主动断开链接
主动关闭链接
func main() {
req, _ := http.NewRequest("GET", "http://mayanan.cn/", nil)
// 方法1:主动关闭http请求链接
//req.Close = true
// 方法2:酌定关闭http请求链接
req.Header.Add("Connection", "close")
res, _ := http.DefaultClient.Do(req)
if res != nil {
defer func() {
_ = res.Body.Close()
}()
}
body, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(body))
}
你可以创建一个自定义配置的http transport客户端,用来取消http全局的复用链接
func main() {
transport := http.Transport{DisableKeepAlives: true}
client := http.Client{Transport: &transport}
req, _ := http.NewRequest("GET", "http://mayanan.cn", nil)
res, err := client.Do(req)
fmt.Println(res.StatusCode)
if err != nil {
log.Println(err)
}
defer func() {
_ = res.Body.Close()
}()
body, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(body))
}
recover的执行时机?
recover必须在defer函数中运行,recover捕获的是祖父级调用时的异常,直接调用时无效
func main() {
recover()
panic(1)
}
直接defer调用也是无效的
func main() {
defer recover()
panic(1)
}
defer调用时,多层嵌套,依然无效
func main() {
defer func() {
func() {
recover()
}()
}()
panic("这是一个错误,啊哈哈哈")
}
必须在defer函数中,直接调用才有效
func main() {
defer func() {
recover()
}()
panic("这是一个错误,啊哈哈哈")
}
说出一个避免goroutine泄漏的措施
可以通过context包来避免内存泄漏
func main() {
ctx, cancel := context.WithCancel(context.Background())
ch := func() <-chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
select {
case <-ctx.Done():
return
case ch <- i:
}
}
}()
return ch
}()
for v := range ch {
if v == 5 {
cancel() // 退出子goroutine,防止goroutine泄漏
fmt.Println(v)
break
}
fmt.Println(v)
}
}
下面的for循环停止读取数据时就用cancel函数,让另一个协程停止写数据,
如果下面for循环停止读取数据,上面goroutine没有退出,就会造成goroutine泄漏
如何跳出for select循环
通常在for循环中,使用break可以跳出循环,但是注意在go语言中,for select配合时,
使用break并不能跳出循环
func main() {
ctx, cancel := context.WithCancel(context.Background())
ch := make(chan int)
go func(ctx context.Context) {
for i := 0; ; i++{
select {
case <-ctx.Done():
return
case ch <- i:
}
}
}(ctx)
EXIT1:
for {
select {
case value := <-ch:
if value == 5 {
fmt.Println(value)
cancel() // 结束子goroutine
break EXIT1
//goto EXIT2
}
fmt.Println(value)
}
}
//EXIT2:
fmt.Println("over")
}
如何初始化带嵌套结构的结构体
go的哲学是组合优于继承,使用struct嵌套即可完成组合,内层的结构体属性就像外层的结构体属性一样,可以直接调用
注意初始化外层结构体时必须指定内嵌结构体名称和结构体初始化,如下,s1方式报错,s2方式正确
type stPeople struct {
Gender bool
Name string
}
type stStudent struct {
stPeople // 匿名内嵌结构体
Class int
}
func main() {
//s1 := stStudent{false, "Json", 3}
s2 := stStudent{stPeople{false, "mayanan"}, 3}
fmt.Println(s2.Name, s2.Gender, s2.Class)
}
go语言中的引用类型包含哪些?
slice map chan interface
说说go语言的select机制
select机制用来处理异步IO问题
select机制的最大一条限制就是每个case语句必须是一个IO操作
golang在语言级别支持select机制
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)