当前位置: 首页 > news >正文

Golang相关知识总结

Golang 相关知识总结

1. Go 基础面试题

1.1 与其他语言相比,使用 Go 有什么好处?

  • 语法简洁务实:Go 代码设计务实,语法更简洁,每个功能决策都旨在提高开发效率
  • 并发优化:针对并发进行优化,支持协程,实现高效的 GMP 调度模型
  • 可读性强:单一标准代码格式,代码更具可读性和一致性
  • 高效垃圾回收:支持并行垃圾回收,回收效率比 Java 或 Python 更高
  • 编译速度快:快速的编译时间,提高开发迭代效率
  • 部署简单:编译为单一可执行文件,部署简单方便

1.2 什么是协程?

协程是用户态轻量级线程,是线程调度的基本单位。在函数前加上 go 关键字就能实现并发:

  • 启动栈很小(2KB 或 4KB)
  • 栈空间不足时自动伸缩
  • 可轻易实现成千上万个 goroutine 同时启动
  • 由 Go 运行时调度,非操作系统线程

1.3 协程和线程和进程的区别?

特性 进程 线程 协程
资源分配 系统资源分配最小单位 CPU 调度基本单位 用户态轻量级线程
内存空间 独立内存空间 共享进程内存 共享线程内存
上下文切换 开销大(栈、寄存器等) 开销较小 开销极小
通信方式 进程间通信 共享内存 Channel 通信
稳定性 稳定安全 相对不稳定 用户控制调度
创建数量 数十个 数百个 数万个

1.4 Golang 中 make 和 new 的区别?

make:

  • 用于初始化并分配内存
  • 只能用于创建 slice、map 和 channel 三种类型
  • 返回的是初始化后的数据结构本身
  • 会进行初始化操作

new:

  • 用于分配内存但不初始化
  • 可以用于任何类型的内存分配
  • 返回的是指向该内存的指针
  • 只分配内存,不进行初始化
// make 示例
s := make([]int, 5)    // 创建长度为5的slice,初始化为[0,0,0,0,0]
m := make(map[string]int) // 创建空的map
ch := make(chan int, 10) // 创建缓冲大小为10的channel// new 示例
p := new(int)          // 分配int类型内存,返回指针,*p为0

1.5 Golang 中数组和切片的区别?

数组:

  • 固定长度,长度是类型的一部分
  • [3]int[4]int 是不同类型
  • 值传递,赋值或传参时会复制整个数组
  • 需要指定大小或由初始化自动推算

切片:

  • 可变长度,动态数组
  • 包含指针、长度、容量三个属性
  • 引用传递,底层共享数组
  • 可通过数组初始化或 make() 创建
// 数组
var arr1 [3]int = [3]int{1, 2, 3}
arr2 := [...]int{1, 2, 3, 4} // 自动推断长度// 切片
var slice1 []int = make([]int, 3, 5) // 长度3,容量5
slice2 := arr1[0:2] // 从数组创建切片

底层结构:

type slice struct {array unsafe.Pointer // 指向底层数组len   int           // 长度cap   int           // 容量
}

1.6 使用 for range 的时候,它的地址会发生变化吗?

Go 1.22 之前:

  • 迭代变量的内存地址保持不变
  • 每次迭代将当前元素值复制到固定地址
  • 可能导致并发问题

Go 1.22 及以后:

  • 迭代变量地址是临时的,每次迭代重新生成
  • 避免并发安全问题
  • 更符合开发者预期
// Go 1.22 之前的问题示例
func main() {var wg sync.WaitGroupfor _, v := range []int{1, 2, 3} {wg.Add(1)go func() {defer wg.Done()fmt.Println(v) // 可能都输出3}()}wg.Wait()
}// 解决方案(在所有版本都有效)
for _, v := range []int{1, 2, 3} {v := v // 创建局部变量副本wg.Add(1)go func() {defer wg.Done()fmt.Println(v) // 正确输出1,2,3}()
}

1.7 如何高效地拼接字符串?

性能比较:
strings.Join ≈ strings.Builder > bytes.Buffer > "+" > fmt.Sprintf

各种拼接方式:

func main() {a := []string{"a", "b", "c"}// 方式1:+ (性能最差)ret1 := a[0] + a[1] + a[2]// 方式2:fmt.Sprintf (性能较差)ret2 := fmt.Sprintf("%s%s%s", a[0], a[1], a[2])// 方式3:strings.Builder (推荐)var sb strings.Buildersb.WriteString(a[0])sb.WriteString(a[1])sb.WriteString(a[2])ret3 := sb.String()// 方式4:bytes.Bufferbuf := new(bytes.Buffer)buf.WriteString(a[0])buf.WriteString(a[1])buf.WriteString(a[2])ret4 := buf.String()// 方式5:strings.Join (推荐,尤其对于切片)ret5 := strings.Join(a, "")
}

strings.Builder 优势:

  • 内部使用 []byte 缓冲区
  • String() 方法直接将 []byte 转换为 string
  • 避免不必要的内存分配和拷贝

1.8 defer 的执行顺序是怎样的?defer 的作用和使用场景?

执行顺序:

  • 后进先出(LIFO)
  • 多个 defer 语句按声明顺序相反的顺序执行

作用:

  • 延迟执行函数,直到包含 defer 的函数执行完毕
  • 无论函数正常返回还是 panic,defer 都会执行

使用场景:

  • 资源释放(文件关闭、连接关闭、锁释放)
  • 成对操作(打开/关闭、连接/断开)
  • 错误处理和恢复
func test() int {i := 0defer func() {fmt.Println("defer1")}()defer func() {i += 1fmt.Println("defer2")}()return i
}func main() {fmt.Println("return", test()) // 输出:// defer2// defer1  // return 0
}

有名返回值的影响:

func test() (i int) {i = 0defer func() {i += 1fmt.Println("defer2")}()return i
}func main() {fmt.Println("return", test())// 输出:// defer2// return 1
}

1.9 什么是 rune 类型?

字符类型:

  • byte:uint8 类型,代表 ASCII 码字符
  • rune:int32 类型,代表 UTF-8 字符
func main() {var str = "hello 你好"// golang中string底层通过byte数组实现// 按字节长度计算,汉字占3字节fmt.Println("len(str):", len(str))  // 输出: 12// 通过rune类型处理unicode字符fmt.Println("rune:", len([]rune(str))) // 输出: 8// 遍历字符串的两种方式for i := 0; i < len(str); i++ {fmt.Printf("%c ", str[i]) // 按字节遍历}fmt.Println()for _, r := range str {fmt.Printf("%c ", r) // 按rune遍历}
}

1.10 Go 语言 tag 有什么用?

常见用途:

  • json:JSON 序列化/反序列化时的字段名
  • db:数据库字段名(sqlx 等 ORM 使用)
  • form:表单字段名(Gin 框架等)
  • binding:字段验证规则
  • xml:XML 序列化
  • yaml:YAML 序列化
type User struct {ID       int    `json:"id" db:"user_id" form:"id" binding:"required"`Username string `json:"username" db:"username" form:"username"`Password string `json:"-" db:"password"` // - 表示不序列化Email    string `json:"email,omitempty"` // omitempty 表示空值时不序列化
}

1.11 go 打印时 %v %+v %#v 的区别?

type student struct {id   int32name 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:"微客鸟窝"}
}

区别:

  • %v:只输出所有的值
  • %+v:先输出字段名,再输出字段值
  • %#v:先输出结构体名,再输出字段名和字段值

1.12 Go语言中空 struct{} 占用空间么?

package mainimport ("fmt""unsafe"
)func main() {fmt.Println(unsafe.Sizeof(struct{}{}))  // 输出: 0
}

空结构体 struct{} 不占用任何内存空间。

1.13 Go语言中,空 struct{} 有什么用?

1. 实现 Set 集合

type Set map[string]struct{}func main() {set := make(Set)for _, item := range []string{"A", "A", "B", "C"} {set[item] = struct{}{}}fmt.Println(len(set)) // 输出: 3if _, ok := set["A"]; ok {fmt.Println("A exists") // 输出: A exists}
}

2. 通道信号

func main() {ch := make(chan struct{}, 1)go func() {<-ch// do something}()ch <- struct{}{}// ...
}

3. 仅有方法的结构体

type Lamp struct{}func (l Lamp) On() {fmt.Println("On")
}func (l Lamp) Off() {fmt.Println("Off")
}

1.14 init() 函数是什么时候执行的?

执行时机:

  • main 函数之前执行
  • 由 runtime 初始化每个导入的包

初始化顺序:

  1. 导入的包(按依赖关系,无依赖的包最先初始化)
  2. 包作用域的常量(常量优先于变量)
  3. 包作用域的变量
  4. 包的 init() 函数
  5. main() 函数

特点:

  • 同一个包可以有多个 init() 函数
  • 同一个源文件可以有多个 init() 函数
  • init() 函数没有入参和返回值
  • 不能被其他函数调用
  • 同一个包内多个 init() 函数的执行顺序不作保证
// 执行顺序:import -> const -> var -> init() -> main()

1.15 2 个 interface 可以比较吗?

可以比较,但需满足以下条件:

  1. 两个 interface 均等于 nil
  2. 动态类型相同,且对应的动态值相等
type Stu struct {Name string
}type StuInt interface{}func main() {var stu1, stu2 StuInt = &Stu{"Tom"}, &Stu{"Tom"}var stu3, stu4 StuInt = Stu{"Tom"}, Stu{"Tom"}fmt.Println(stu1 == stu2) // false,指针地址不同fmt.Println(stu3 == stu4) // true,结构体值相同
}

1.16 2 个 nil 可能不相等吗?

可能不相等:

var p *int = nil
var i interface{} = nilif p == i {fmt.Println("Equal")
} else {fmt.Println("Not Equal") // 输出这个
}

总结: 两个 nil 只有在类型相同时才相等。

1.17 Go 语言函数传参是值类型还是引用类型?

Go 语言中只存在值传递:

  • 要么是值的副本
  • 要么是指针的副本

注意区分:

  • 值传递 vs 引用传递:传递机制
  • 值类型 vs 引用类型:数据类型特性
func modifySlice(s []int) {s[0] = 100 // 会影响外部,因为底层数组共享s = append(s, 200) // 不会影响外部,因为发生了扩容
}func modifyPtr(s *[]int) {(*s)[0] = 100 // 会影响外部*s = append(*s, 200) // 会影响外部
}

1.18 如何知道一个对象是分配在栈上还是堆上?

逃逸分析:

go build -gcflags '-m -m -l' xxx.go

逃逸的可能情况:

  • 变量大小不确定
  • 变量类型不确定
  • 变量分配的内存超过用户栈最大值
  • 暴露给了外部指针
  • 闭包引用外部变量

示例:

// 栈分配
func stackAlloc() int {x := 10 // 可能在栈上分配return x
}// 堆分配  
func heapAlloc() *int {x := 10 // 逃逸到堆上return &x
}

1.19 Go语言的多返回值是如何实现的?

实现机制:

  1. 函数调用时,编译器计算所有返回值的总大小
  2. 在调用方栈帧上预留连续内存空间
  3. 函数执行 return 时,将返回值复制到预留空间
  4. 调用方直接从自己的栈帧获取返回值
func multiReturn() (int, string, error) {return 1, "hello", nil
}// 底层类似:
// 调用方预留 [int, string, error] 的空间
// 被调用方将值复制到该空间
// 调用方直接读取

1.20 Go语言中"_"的作用

1. 忽略多返回值

func getValues() (int, string) {return 1, "hello"
}func main() {num, _ := getValues() // 忽略字符串返回值fmt.Println(num)
}

2. 匿名导入包

import ("fmt"_ "net/http/pprof" // 只执行init函数,注册profiling接口
)func main() {fmt.Println("Application started. Profiling tools are likely registered.")
}

3. 在循环中忽略索引或值

for _, value := range slice {fmt.Println(value)
}for index, _ := range slice {fmt.Println(index)
}

1.21 Go语言普通指针和unsafe.Pointer有什么区别?

普通指针:

  • 有明确的类型信息(*int*string
  • 编译器进行类型检查
  • 受垃圾回收跟踪
  • 不同类型指针不能直接转换

unsafe.Pointer:

  • 通用指针类型,类似 C 的 void*
  • 绕过 Go 的类型系统
  • 可与任意类型指针相互转换
  • 可与 uintptr 进行转换来做指针运算
  • 仍受 GC 跟踪
var x int = 10
var p *int = &x// 普通指针转换(编译错误)
// var f *float64 = (*float64)(p)// 使用 unsafe.Pointer 转换
var f *float64 = (*float64)(unsafe.Pointer(p))

1.22 unsafe.Pointer与uintptr有什么区别和联系

联系:

  • 可以相互转换
  • 是 Go 中唯一合法的指针运算方式

区别:

  • unsafe.Pointer:会被 GC 跟踪,有 GC 保护
  • uintptr:普通整数,GC 不知道其指向,无 GC 保护
http://www.dtcms.com/a/491666.html

相关文章:

  • LeetCode算法日记 - Day 73: 最小路径和、地下城游戏
  • 设计案例网站网站自身seo优化怎么做
  • 手绘风格制图新选择:Excalidraw+cpolar让视觉化工作流无缝协作
  • apt 安装任意软件产生 `libc6:amd64 package post-installation` 异常问题
  • Product Hunt 每日热榜 | 2025-10-16
  • 2025最新如何申请Google Translate API免费版图文教程
  • 提供常州微信网站建设单页企业网站模板
  • 证件阅读机在酒店与旅游业场景的应用
  • 深圳分销网站设计费用常平镇网站建设公司
  • 华为 FreeBuds SE4 ANC 如何手势调节音量?
  • Git怎么管理软件版本(代码,模型,配置等)
  • 翁虹庆爱女刘莳18岁生日 中式成人礼传承华夏底蕴
  • 苏州seo建站微信网站多少钱
  • Process Monitor 学习笔记(5.9):Procmon 的自动化操作——命令行选项
  • 荣耀手机Magic8系列都有哪些,分别通过硬件参数、性能参数、价格等方面进行详细对比
  • 合肥网站开发培训学校网站怎么做图片按按钮跳转
  • 西安专业做网站建设电子商务网站建设期中
  • 生态文明建设网站中城投建设集团网站
  • C++--- override 关键字 强制编译器验证当前函数是否重写基类的虚函数
  • LLM对话框项目技术栈重难点总结
  • 常州企业网站建设价格湛江宇锋网站建设
  • 网站开发实用吗搞钱路子一天两万
  • Ubuntu Server 系统安装图形界面远程工具(RDP)
  • 新版电脑微信4.1.x.x小程序逆向之——寻找小程序存放位置目录和__APP__.wxapkg
  • 我在高职教STM32(新05)——呼吸灯实验(基础版)
  • 丽泽桥网站建设wordpress分类列表去掉分类目录
  • 网站开发创业计划书模板宝安中心医院是什么级别
  • 华清远见25072班QT学习day2
  • 数据质量:Great Expectations检查点,校验失败怎样处理?
  • Ethernaut Level 12: Privacy - 存储布局分析