golang常见面试题

go中触发异常的场景有哪些?

  1. 空指针解析
  2. 下标越界
  3. 除数为0
  4. 调用panic函数

Printf、Sprintf、Fprintf函数的区别用法是什么?

都是把格式好的字符串输出,只是输出的目标不一样

  1. Printf: 是把格式字符串输出到标准输出(一般是屏幕,可以重定向),Printf是和标准输出文件(stdout)关联的
  2. Sprintf:是把格式字符串输出到指定字符串中,所以参数比Printf多一个char*, 那就是目标字符串地址
  3. Fprintf: 是把格式字符串输出到指定文件设备中,所以参数比Sprintf多一个文件指针FILE*, 主要用于文件操作
    Fprintf是格式化输出到一个stream, 通常是到文件

new和make的区别?

  1. new的作用是初始化一个指向类型的指针(*T)
    new函数是内建函数,函数定义func new(Type) *Type,使用new函数来分配空间,
    传递给new函数的是一个类型,而不是值,返回值是指向这个新分配的零值的指针
  2. 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语言编程的好处是什么?

  1. 概念
    golang是一种强类型语言,这意味着它本质上不如解释语言灵活,但go提供了任何类型(接口)和反射机制,
    使语言在灵活性上与解释语言非常接近,越来越多的人开始学习golang。
  2. 性能(机器代码)
    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机制

posted @ 2022-08-10 16:33  专职  阅读(223)  评论(0编辑  收藏  举报