🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ### **go中"_"的作用** >import中的下滑线 **当导入一个包的时候,不需要把所有的包都导进来,只需要导入使用该包下的文件里所有的init()的函数。** >下划线在代码中 下划线在代码中是忽略这个变量 >占位符 是特殊标识符,用来忽略结果 ### **make 和 new 的区别** * make 仅用来分配及初始化类型为 slice、map、chan 的数据。 * new 可分配任意类型的数据,根据传入的类型申请一块内存,返回指向这块内存的指针,即类型 \*Type。 * make 返回引用,即 Type,new 分配的空间被清零, make 分配空间后,会进行初始。 ### **数组和切片的区别** **相同点:** 1)只能存储一组相同类型的数据结构 2)都是通过下标来访问,并且有容量长度,长度通过 len 获取,容量通过 cap 获取 **区别:** 1)数组是定长,访问和复制不能超过数组定义的长度,否则就会下标越界,切片长度和容量可以自动扩容 2)数组是值类型,切片是引用类型,每个切片都引用了一个底层数组,切片本身不能存储任何数据,都是这底层数组存储数据,所以修改切片的时候修改的是底层数组中的数据。切片一旦扩容,指向一个新的底层数组,内存地址也就随之改变 ### **for range** 在 for k,v := range c 遍历中, k 和 v 在内存中只会存在一份,即之后每次循环时遍历到的数据都是以值覆盖的方式赋给 k,v ,它们的内存地址始终不变。由于有这个特性,for 循环里面如果开协程,不要直接把 k 或者 v 的地址传给协程。解决办法:在每次循环时,创建一个临时变量。 ### **go defer,多个 defer 的顺序,defer 在什么时机会修改返回值** 多个defer ,遵循先进后出, defer,return,return value(函数返回值) 执行顺序:首先return,其次return value,最后defer defer可以修改函数最终返回值,修改时机:**有名返回值或者函数返回指针** ### **Go 的 defer 底层数据结构和一些特性** 每个 defer 语句都对应一个\_defer 实例,多个实例使用指针连接起来形成一个单连表,保存在 gotoutine 数据结构中,每次插入\_defer 实例,均插入到链表的头部,函数结束再一次从头部取出,从而形成后进先出的效果。 **defer 的规则总结**: 延迟函数的参数是 defer 语句出现的时候就已经确定了的。 延迟函数执行按照后进先出的顺序执行,即先出现的 defer 最后执行。 延迟函数可能操作主函数的返回值。 申请资源后立即使用 defer 关闭资源(文件、锁、链接)。 ### **介绍下 rune 类型吗** * uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。 * rune 类型,代表一个 UTF-8 字符,当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型等价于 int32 类型。 ### **golang 中解析 tag 是怎么实现的?反射原理是什么?** tag 是通过反射实现的 反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力或动态知道给定数据对象的类型和结构,并有机会修改它。 >三大原理: 反射将接口变量转换成反射对象 Type 和 Value; 反射可以通过反射对象 Value 还原成原先的接口变量; 反射可以用来修改一个变量的值,前提是这个值可以被修改; ### 讲一下**Go 的 slice 底层数据结构和一些特性** 结构:指向底层数组的指针、切片的长度(len)和切片的容量(cap)。 扩容机制: * 首先判断,如果新申请容量大于 2 倍的旧容量,最终容量就是新申请的容量。 * 否则判断,如果旧切片的长度小于 1024,则最终容量就是旧容量的两倍。 * 否则判断,如果旧切片长度大于等于 1024,则最终容量从旧容量开始循环增加原来的 1/4 , 直到最终容量大于等于新申请的容量。 * 如果最终容量计算值溢出,则最终容量就是新申请容量。 ### **讲讲 Go 的 select 底层数据结构和一些特性** select 结构组成主要是由 case 语句和执行的函数组成 select 实现的多路复用是:每个线程或者进程都先到注册和接受的 channel(装置)注册,然后阻塞,然后只有一个线程在运输,当注册的线程和进程准备好数据后,装置会根据注册的信息得到相应的数据。 **select 的特性** 1)select 操作至少要有一个 case 语句,出现读写 nil 的 channel 该分支会忽略,在 nil 的 channel 上操作则会报错。 2)select 仅支持管道,而且是单协程操作。 3)每个 case 语句仅能处理一个管道,要么读要么写。 4)多个 case 语句的执行顺序是随机的。 5)存在 default 语句,select 将不会阻塞,但是存在 default 会影响性能。 ### **单引号,双引号,反引号的区别?** 单引号,表示byte类型或rune类型,对应 uint8和int32类型,默认是 rune 类型。byte用来强调数据是raw data,而不是数字;而rune用来表示Unicode的code point。 双引号,才是字符串,实际上是字符数组。可以用索引号访问某字节,也可以用len()函数来获取字符串所占的字节长度。 反引号,表示字符串字面量,但不支持任何转义序列。字面量 raw literal string 的意思是,你定义时写的啥样,它就啥样,你有换行,它就换行。你写转义字符,它也就展示转义字符。 ### **Go 当中同步锁有什么特点?作用是什么** > 特点 1、当一个 Goroutine(协程)获得了 Mutex 后,其他 Goroutine(协程)就只能等待,除非该 Goroutine 释放了该Mutex。 2、RWMutex 在读锁占用的情况下, 会阻止写,但不阻止读 RWMutex。 在写锁占用情况下,会阻止任何其他 Goroutine(无论读和写)进来,整个锁相当于由该 Goroutine 独占 >作用: 同步锁的作用是保证资源在使用时的独有性,不会因为并发而导致数据错乱, 保证系统的稳定性。 ### **Go 语言中 cap 函数可以作用于哪些内容** Array、Slice、Channel ### **Printf(),Sprintf(),FprintF() 都是格式化输出,有什么不 同?** * Printf 是标准输出,一般是屏幕,也可以重定向。 * Sprintf()是把格式化字符串输出到指定的字符串中。 * Fprintf()是把格式化字符串输出到文件中。 ### **go 打印时 %v %+v %#v 的区别?** * %v 只输出所有的值; * %+v 先输出字段名字,再输出该字段的值; * %#v 先输出结构体名字值,再输出结构体(字段名字+字段的值); ~~~ package main import "fmt" type student struct { id int32 name string } func main() { a := &student{id: 1, name: "微客鸟窝"} fmt.Printf("a=%v \n", a) // a=&{1 微客鸟窝} fmt.Printf("a=%+v \n", a) // a=&{id:1 name:微客鸟窝} fmt.Printf("a=%#v \n", a) // a=&main.student{id:1, name:"微客鸟窝"} } ~~~ ### **什么是进程、线程、协程?** 进程:是应用程序的启动实例,每个进程都有独立的内存空间,不同的进程通过进程间的通信方式来通信。 线程:从属于进程,每个进程至少包含一个线程,线程是 CPU 调度的基本单位,多个线程之间可以共享进程的资源并通过共享内存等线程间的通信方式来通信。 协程:为轻量级线程,与线程相比,协程不受操作系统的调度,协程的调度器由用户应用程序提供,协程调度器按照调度策略把协程调度到线程中运行 ### **协程和线程的区别?** * 一个线程可以有多个协程 * 线程、进程都是同步机制,而协程是异步 * 协程可以保留上一次调用时的状态,当过程重入时,相当于进入了上一次的调用状态 * 协程是需要线程来承载运行的,所以协程并不能取代线程,「线程是被分割的CPU资源,协程是组织好的代码流程」 ### **Go的Struct能不能比较?** * 相同struct类型的可以比较 * 不同struct类型的不可以比较,编译都不过,类型不匹配 ### **两个 interface 可以比较吗?** * 判断类型是否一样 reflect.TypeOf(a).Kind() == reflect.TypeOf(b).Kind() * 判断两个interface{}是否相等 reflect.DeepEqual(a, b interface{}) * 将一个interface{}赋值给另一个interface{} reflect.ValueOf(a).Elem().Set(reflect.ValueOf(b)) ### **能说说uintptr和unsafe.Pointer的区别吗?** > uintptr:是一种整数类型,其大小足以容纳任何指针的位模式 * unsafe.Pointer只是单纯的通用指针类型,用于转换不同类型指针,它不可以参与指针运算; * 而uintptr是用于指针运算的,GC 不把 uintptr 当指针,也就是说 uintptr 无法持有对象, uintptr 类型的目标会被回收; * unsafe.Pointer 可以和 普通指针 进行相互转换; * unsafe.Pointer 可以和 uintptr 进行相互转换。