每日八股-Golang-1
1. init 和 main 函数的特点
1.1 init函数(没有输入参数、返回值)的主要作用
- 初始化不能采用初始化表达式初始化的变量。
- 程序运行前的注册。
- 实现sync.Once功能。
- 其他
1.2 init 顺序:
-
在同一个 package 中,可以多个文件中定义 init 方法
-
在同一个 go 文件中,可以重复定义 init 方法
-
在同一个 package 中,不同文件中的 init 方法的执行按照文件名先后执行各个文件中的 init 方法
-
在同一个文件中的多个 init 方法,按照在代码中编写的顺序依次执行不同的 init 方法
-
对于不同的 package,如果不相互依赖的话,按照 main 包中 import 的顺序调用其包中的 init() 函数
-
如果 package 存在依赖,调用顺序为最后被依赖的最先被初始化,例如:导入顺序 main –> A –> B –> C,则初始化顺序为 C –> B –> A –> main,一次执行对应的 init 方法。
所有 init 函数都在同⼀个 goroutine 内执行。
所有 init 函数结束后才会执行 main.main 函数

2. Go的数据结构的零值是什么
-
所有整型类型:0
-
浮点类型:0.0
-
布尔类型:false
-
字符串类型:””
-
指针、interface、切片(slice)、channel、map、function :nil
-
Go的零值初始是递归的,即数组、结构体等类型的零值初始化就是对其组成元素逐一进行零值初始化。
3. byte和rune有什么区别
-
rune和byte在go语言中都是字符类型,且都是别名类型
-
byte型本质上是uint8类型的别名,代表了ASCII 码的一个字符
-
rune型本质上是int32型的别名,代表一个 UTF-8 字符
4. Go struct 能不能比较
需要具体情况具体分析,如果struct中含有不能被比较的字段类型,就不能被比较,如果struct中所有的字段类型都支持比较,那么就可以被比较。
4.1 不可被比较的类型:
- slice,因为slice是引用类型,除非是和nil比较
- map,和slice同理,如果要比较两个map只能通过循环遍历实现
- 函数类型
其他的类型都可以比较。
还有两点值得注意:
- 结构体之间只能比较它们是否相等,而不能比较它们的大小
- 只有所有属性都相等而且属性顺序都一致的结构体才能进行比较
5. Go import的三种方式
一、加下划线:
import 下划线(如:_ “github.com/go-sql-driver/mysql”)的作用:当导入一个包时,该包下的文件里所有init()函数 都会被执行。然而,有些时候我们并不需要把整个包都导入进来,仅仅是是希望它执行init()函数而已。这个时候就可以使用 import _ 引用该包。即:使用[import _ 包路径]只是引用该包,仅仅是为了调用init()函数,所以无法通过包名来调用包中的其他函数。
二、加点(.):
import和引用的包名之间加点(.)操作的含义就是这个包导入之后在调用这个包的函数时,可以省略前缀的包名。
三、别名:
别名操作顾名思义可以把包命名成另一个用起来容易记忆的名字。
6. 与其他语言相比,使用** Go **有什么好处?
- 与其他作为学术实验开始的语⾔不同,Go 代码的设计是务实的。每个功能和语法决策都旨在让程序员的⽣活更轻松。
- Golang 针对并发进行了优化,并且在规模上运行良好。
- 由于单⼀的标准代码格式,Golang 通常被认为比其他语⾔更具可读性。
- ⾃动垃圾收集明显比Java 或 Python 更有效,因为它与程序同时执行。
7. 听说go有什么什么的缺陷,你怎么看
1、缺少框架;
2、go语言通过函数和预期的调用代码简单地返回错误,容易丢失错误发生的范围;
3、go语言的软件包管理没有办法制定特定版本的依赖库。
8. Golang的常量取地址
const i = 100
var j = 123
func main() { fmt.Println(&j, j) fmt.Println(&i, i) //panic}Go语⾔中,常量⽆法寻址, 是不能进⾏取指针操作的
9. string和[]byte如何取舍
9.1 string 擅长的场景:
- 需要字符串比较的场景;
- 不需要nil字符串的场景;
9.2 []byte擅长的场景:
- 修改字符串的场景,尤其是修改粒度为1个字节;
- 函数返回值,需要用nil表示含义的场景;
- 需要切片操作的场景;
10. 字符串转成byte数组,会发生内存拷贝吗
字符串转成切片,会产生拷贝。严格来说,只要是发生类型强转都会发生内存拷贝。
⭐10.1 零拷贝转换
package main
import ( "fmt" "reflect" "unsafe")
func main() { a :="aaa" ssh := *(*reflect.StringHeader)(unsafe.Pointer(&a)) b := *(*[]byte)(unsafe.Pointer(&ssh)) fmt.Printf("%v",b)}解释:
StringHeader是字符串在go的底层结构。
type StringHeader struct { Data uintptr Len int}SliceHeader是切片在go的底层结构。
type SliceHeader struct { Data uintptr Len int Cap int}- 那么如果想要在底层转换二者,只需要把
StringHeader的地址强转成SliceHeader就行。那么go有个很强的包叫unsafe。- 1.
unsafe.Pointer(&a)方法可以得到变量a的地址。 - 2.
(*reflect.StringHeader)(unsafe.Pointer(&a))可以把字符串a转成底层结构的形式。 - 3.
(*[]byte)(unsafe.Pointer(&ssh))可以把ssh底层结构体转成byte的切片的指针。 - 4.再通过
*转为指针指向的实际内容。
- 1.
11. Printf()、Sprintf()、Fprintf()函数的区别用法是什么
-
都是把格式好的字符串输出,只是输出的目标不一样。
-
Printf(),是把格式字符串输出到标准输出(一般是屏幕,可以重定向)。Printf() 是和标准输出文件 (stdout) 关联的,Fprintf 则没有这个限制。
-
Sprintf(),是把格式字符串输出到指定字符串中,所以参数比printf多一个char*。那就是目标字符串地址。
-
Fprintf(),是把格式字符串输出到指定文件设备中,所以参数比 printf 多一个文件指针 FILE*。主要用于文件操作。Fprintf() 是格式化输出到一个stream,通常是到文件。
12. Go如何避免panic
首先明确panic定义:go把真正的异常叫做 panic,是指出现重大错误,比如数组越界之类的编程BUG或者是那些需要人工介入才能修复的问题,比如程序启动时加载资源出错等等。
12.1 几个容易出现panic的点:
- 函数返回值或参数为指针类型,nil, 未初始化结构体,此时调用容易出现panic,可加 != nil 进行判断
- 数组切片越界
- 如果我们关闭未初始化的通道,重复关闭通道,向已经关闭的通道中发送数据,这三种情况也会引发 panic,导致程序崩溃
- 如果我们直接操作未初始化的映射(map),也会引发 panic,导致程序崩溃
- 另外,操作映射可能会遇到的更为严重的一个问题是,同时对同一个映射并发读写,它会触发 runtime.throw,不像 panic 可以使用 recover 捕获。所以,我们在对同一个映射并发读写时,一定要使用锁。
- 如果类型断言使用不当,比如我们不接收布尔值的话,类型断言失败也会引发 panic,导致程序崩溃。
- 如果很多时候不可避免地出现了panic, 记得使用 defer/recover
13. Go的切片
13.1 切片的底层结构(三要素)
切片在 Go 底层实际是一个结构体,包含三个字段(位于 reflect.SliceHeader 中):
- Data:指向底层数组的指针。
- Len:切片当前包含的元素个数(长度)。
- Cap:底层数组从指针位置开始到数组末尾的总容量(容量)。
// 伪代码示意type SliceHeader struct { Data uintptr // 指向底层数组的指针 Len int // 长度 Cap int // 容量}关键认知:切片本身不拥有数据,它只是底层数组某一段的引用。多个切片可以共享同一个底层数组。
13.2 创建切片的四种方式
| 方式 | 代码 | 特点 |
|---|---|---|
| 字面量 | s := []int{1, 2, 3} | 自动创建底层数组,长度=容量=3 |
| make 函数 | s := make([]int, 5, 10) | 指定长度 5,容量 10(容量可省略,默认等于长度) |
| 从数组切割 | s := arr[1:4] | 引用 arr 的索引 1~3,长度 3,容量取决于数组剩余大小 |
| 声明零值 | var s []int | 声明为 nil 切片,没有底层数组,长度=容量=0 |
13.3 扩容策略
当 append 时发现 Len 即将超过 Cap,Go 会重新分配一个更大的底层数组,并将旧数据复制过去。
- Go 1.18 之前的策略:容量 < 1024 时翻倍;>= 1024 时增长约 25%。
- Go 1.18 及之后:内存分配策略更精细(根据元素类型大小调整),但大致规律仍是小容量翻倍,大容量增长放缓。
s := make([]int, 0, 2)s = append(s, 1, 2) // len=2, cap=2s = append(s, 3) // len=3, cap=4 (容量翻倍了)13.4 深拷贝 copy 函数
由于切片是引用类型,直接赋值 s2 := s1 会让两个变量共享底层数组。若要完全独立,使用 copy:
src := []int{1, 2, 3}dst := make([]int, len(src)) // 必须预先分配足够长度n := copy(dst, src) // 复制成功,n = 313.5 nil 切片 与 空切片
| 类型 | 声明方式 | 是否等于 nil | 能否 append |
|---|---|---|---|
nil 切片 | var s []int | ✅ 是 | ✅ 可以(会分配新数组) |
| 空切片 | s := []int{} 或 make([]int, 0) | ❌ 否 | ✅ 可以 |
建议:函数返回切片时,若没有数据,建议返回
nil而非空切片,以避免不必要的内存分配。
14. 结尾
没了喵,谢谢欣赏

文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!