每日八股-Golang-1

2517 字
13 分钟
每日八股-Golang-1

1. initmain 函数的特点#

1.1 init函数(没有输入参数、返回值)的主要作用#

  • 初始化不能采用初始化表达式初始化的变量。
  • 程序运行前的注册。
  • 实现sync.Once功能。
  • 其他

1.2 init 顺序:#

  1. 在同一个 package 中,可以多个文件中定义 init 方法

  2. 在同一个 go 文件中,可以重复定义 init 方法

  3. 在同一个 package 中,不同文件中的 init 方法的执行按照文件名先后执行各个文件中的 init 方法

  4. 在同一个文件中的多个 init 方法,按照在代码中编写的顺序依次执行不同的 init 方法

  5. 对于不同的 package,如果不相互依赖的话,按照 main 包中 import 的顺序调用其包中的 init() 函数

  6. 如果 package 存在依赖,调用顺序为最后被依赖的最先被初始化,例如:导入顺序 main –> A –> B –> C,则初始化顺序为 C –> B –> A –> main,一次执行对应的 init 方法。

所有 init 函数都在同⼀个 goroutine 内执行。

所有 init 函数结束后才会执行 main.main 函数

202309211606395
202309211606395

2. Go的数据结构的零值是什么#

  1. 所有整型类型:0

  2. 浮点类型:0.0

  3. 布尔类型:false

  4. 字符串类型:””

  5. 指针、interface、切片(slice)、channel、map、function :nil

  6. 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数组,会发生内存拷贝吗#

golang面试题:字符串转成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.再通过 *转为指针指向的实际内容。

11. Printf()、Sprintf()、Fprintf()函数的区别用法是什么#

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

  2. Printf(),是把格式字符串输出到标准输出(一般是屏幕,可以重定向)。Printf() 是和标准输出文件 (stdout) 关联的,Fprintf 则没有这个限制。

  3. Sprintf(),是把格式字符串输出到指定字符串中,所以参数比printf多一个char*。那就是目标字符串地址。

  4. 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=2
s = 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 = 3

13.5 nil 切片 与 空切片#

类型声明方式是否等于 nil能否 append
nil 切片var s []int✅ 是✅ 可以(会分配新数组)
空切片s := []int{}make([]int, 0)❌ 否✅ 可以

建议:函数返回切片时,若没有数据,建议返回 nil 而非空切片,以避免不必要的内存分配。

14. 结尾#

没了喵,谢谢欣赏

gopher
gopher

文章分享

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

每日八股-Golang-1
https://blog.tuf3i.cc/posts/daily-bagu-go/
作者
TuF3i
发布于
2026-04-13
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
TuF3i
一只区,什么也不想写
分类
标签
站点统计
文章
9
分类
7
标签
13
总字数
26,233
运行时长
0
最后活动
0 天前

目录