GO学习笔记 | GO面试要点笔记(持续更新)
GO面试要点笔记(持续更新)
第一周

1.问题回答
1. 什么是闭包?闭包有什么缺陷?
- 什么是闭包:闭包是指一个函数(在 Go 中通常指匿名函数)引用了其外部作用域中的变量。即使该外部函数已经返回,这个匿名函数依然可以访问并修改这些外部变量。
- 缺陷(注意事项):
- 内存泄漏:如果闭包长期存活(例如作为全局变量或长期运行的协程),其捕获的外部变量将一直无法被 GC 回收,导致内存占用过高。
- 共享变量副作用:在循环中使用闭包非常容易出错。例如
for i:=0; i<10; i++ { go func() { println(i) }() }会打印多个10,因为所有闭包共享了同一个变量i。 - 性能开销:闭包本质上是通过指针访问外部变量,底层涉及逃逸分析,有一定的性能开销。
2. 什么情况下会出现栈溢出?
- 无限递归:函数直接或间接地无限调用自身,导致调用栈无限堆积。
- 分配过大的局部变量:在函数内部声明了非常大且不能逃逸到堆上的数组或结构体,导致单个栈帧过大。
- Go 特定情况:Go 的协程(Goroutine)初始栈很小(约 2KB),但会自动扩容。虽然 Go 的栈是动态调整的,但如果递归极深或单帧极大,导致扩容申请失败时,依然会出现
stack overflow导致程序崩溃。
3. 什么是不定参数?调用时能传 0 个值吗?方法内部怎么使用?
- 什么是不定参数:函数最后一个参数前加上
...符号(如func Sum(nums ...int)),表示该参数可接受 0 个或多个同类型的参数。 - 能否传 0 个值:可以。如果不传值,在函数内部得到的
nums会是一个空切片(长度为 0,容量为 0)。 - 内部使用方法:在方法内部,不定参数实际上是被作为一个切片(
[]T)来处理的。你可以使用range遍历它,也可以直接进行切片操作。如果你需要将其传给另一个不定参数函数,需要采用nums...的形式来解包。
4. 什么是 defer?你能解释一下 defer 的运作机制吗?
- 什么是 defer:
defer语句用于延迟执行一个函数调用,被推迟的函数会在所在函数返回之前(无论是否发生 panic)执行。 - 运作机制:
- 后进先出(LIFO):如果函数内有多个
defer,它们按照声明的相反顺序执行(类似栈)。 - 参数立即求值:
defer后面的函数参数(除了函数体内部的变量)会在声明 defer 的那一刻立即求值并拷贝保存,而不是在 defer 真正执行时才去求值。 - 数据结构:在 Go 的运行时中,
defer会被串联成一个链表挂在 Goroutine 结构体上。编译时会根据情况选择不同的实现(如堆分配、栈分配或开放编码)。
- 后进先出(LIFO):如果函数内有多个
5. 一个方法内部 defer 能不能超过 8 个?
- 能。语法和运行机制上没有硬性代码限制,你可以写 8 个以上甚至 100 个。
- 深入理解(性能优化):这个问题通常考察 Go 1.14 之后的编译优化。在 Go 1.14+ 中,如果
defer数量不超过 8 个,且不是在循环中调用,编译器会采用**“开放编码” (Open-Coded Defer)**,这是一种非常高效的内联实现。一旦超过 8 个,defer就会在运行时退化为栈或堆上的链表执行,性能会有所下降,但功能完全正常。
6. defer 内部能不能修改返回值?怎么改?
- 能修改,但取决于返回值的声明方式。
- 情况 1(匿名返回值):如果函数返回值是匿名的(如
func f() int),defer无法直接修改返回值(因为匿名返回值在函数栈中,defer无法拿到它的地址)。除非你在defer里操作的是全局变量或指针类型的返回值。 - 情况 2(具名返回值):如果函数返回值是具名的(如
func f() (result int)),defer可以直接修改result的值,因为defer可以访问到该命名返回变量的内存地址。最终返回的是defer修改后的值。
7. 数组和切片有什么区别?
- 长度:
- 数组:长度是固定的,且是数组类型定义的一部分(如
[5]int和[10]int是不同类型)。 - 切片:长度是动态的,可以扩容。
- 数组:长度是固定的,且是数组类型定义的一部分(如
- 传参:
- 数组:作为参数传递时是值拷贝(整个数组都会复制一份)。
- 切片:作为参数传递时是引用传递(传递的是指向底层数组的指针、长度和容量的结构体副本)。
- 适用场景:底层存储为数组,日常开发中极少使用数组作为参数传递,绝大多数情况使用切片。
8. 切片怎么扩容的?
使用 append 函数向切片追加元素时,如果切片的容量(cap)不足以容纳新元素,就会触发扩容机制。
- 扩容策略:
- 需求容量:如果追加后的新长度 > 当前容量的 2 倍,则新容量直接设为新长度。
- 2倍扩容:如果当前切片长度小于 1024,则新容量翻倍(
cap * 2)。 - 1.25倍扩容:如果当前切片长度大于等于 1024,则新容量增长为原来的 1.25 倍。
- 补充细节:扩容并不是仅仅数学上乘以一个倍数,Go 在确定上述估算值后,还会进行内存对齐操作,对最终分配的内存块大小进行向上取整,以节省内存并适应计算机的缓存和内存分配策略。
- 注意:扩容会重新分配底层数组。如果发生扩容,新切片和旧切片将不再共享底层数组。
2.defer相关




3.切片扩容


回来看的时候记得看一下最新版本的go是怎么扩容的,就看看还是不是256,1.25之类的,这个经常改动
第二周


这张幻灯片里提到的三个问题,确实是 Gin 面试中最基础、最容易被考察的核心知识点。我帮你把这三个问题,连同底部那条“黄金面试建议”一并详细解答出来:
1. 什么是 Gin 的 middleware?能用来解决什么问题?
(核心:拦截器)
- 定义:Middleware(中间件)本质上是一个函数,它在 HTTP 请求到达具体的业务处理函数(Handler)之前,以及处理完业务返回响应之后,插入执行的一段逻辑。它组成了一个“洋葱圈”的调用链。
- 能解决的问题(应用场景):
- 全局限流与防护:限制某个 IP 的请求频率,防止 DDOS 攻击。
- 鉴权与认证:统一校验请求头里的 Token 或 Cookie,如果没登录,直接在中间件里拦截并返回 401,不让请求触达真实的业务接口。
- 请求日志与追踪:记录每一个接口的耗时、请求参数、返回状态码,便于排查线上问题。
- 统一异常处理:捕获业务代码中可能发生的
panic,防止服务直接崩溃,并返回统一的错误格式给前端。 - 跨域处理(CORS):下面要讲到的跨域问题,通常就是通过中间件解决的。
- 在 Gin 中怎么用:非常简单,直接调用
r.Use(MyMiddleware())就可以把中间件挂载到全局或特定的路由组上。
2. 什么是跨域问题,怎么解决?
(核心:浏览器同源策略)
- 定义:为了安全,浏览器有一个同源策略。如果你的前端运行在
http://localhost:8080,但你请求的后端接口在http://localhost:3000(只要协议、域名、端口有一个不同,就算跨域),浏览器就会拦下这个请求,直接报错。 - 怎么解决(三种主要方式):
- 后端加 CORS 响应头(最常用):让后端告诉浏览器:“我是值得信任的,我允许前端跨域来访问我”。在 Gin 中,最推荐的做法是直接使用官方的
gin-contrib/cors中间件包,配置极简。 - 前端代理:在开发阶段,使用 Webpack、Vite 等前端构建工具配置
proxy代理,把对前端的请求转发给后端,因为“服务器与服务器之间”是没有跨域限制的。 - Nginx 反向代理:在生产环境,把前端和后端托管在同一个域名的不同路径下,比如
www.example.com/api和后端,www.example.com和前端,通过 Nginx 代理消除跨域。
- 后端加 CORS 响应头(最常用):让后端告诉浏览器:“我是值得信任的,我允许前端跨域来访问我”。在 Gin 中,最推荐的做法是直接使用官方的
3. 跨域问题需要设置哪些头部?
在 Gin 后端(或其他语言后端)解决跨域时,实质上就是在 HTTP 响应头里添加以下关键字段:
Access-Control-Allow-Origin(必须):明确告诉浏览器允许哪些域名来跨域访问。如果前端地址不固定,通常可以读取请求头里的Origin动态返回。注意:如果包含Authorization头部,这里不能写*,必须写死具体的域名。Access-Control-Allow-Methods:允许前端的请求方法,比如GET, POST, PUT, DELETE, OPTIONS。Access-Control-Allow-Headers:允许前端在请求头里携带自定义的 Header,例如Content-Type, Authorization, X-Requested-With等。Access-Control-Allow-Credentials(可选):如果前端请求携带了 Cookie,需要把这个值设为true。Access-Control-Max-Age(预检缓存):浏览器在发出复杂请求前,会先发一个OPTIONS预检请求。设置这个头部(例如86400秒),可以让浏览器在一天内无需再次发送OPTIONS探测,直接发送真实请求,能显著提升性能。
额外补充:幻灯片底部的“黄金面试建议”
“在 Gin 面试的时候,一定要提起自己研发了一个强大 Gin 插件库”,这句话非常有深意。它想考察的是你有没有读懂 Gin 的底层设计思想。
你可以这么说:
“除了会使用中间件外,我对 Gin 的洋葱模型(
Next()机制)有深入的理解。基于这种机制,我在工作中提取并封装了一套公司内部通用的 Gin 插件库。比如:
- 基于 Redis + 令牌桶算法实现的全局限流中间件;
- 统一处理
panic和业务异常,将error转换为标准 JSON 结构体返回的中间件;- 基于
pprof做了个自定义的性能监控中间件,并且给公司内部的其他 Go 微服务框架复用。熟练封装中间件的能力,能体现出我对 Gin 底层链式调用源码的理解。”
这样回答,展现了具备模块化封装、业务落地、和源码深度的能力,会是非常大的加分项!
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Darlingの妙妙屋!
评论












