golang不常见的笔试题
1. 常量与变量
1.赋值的奇巧淫技
func main() {
const (
a, b = "golang", 100
c, d
f bool = true
g
)
fmt.Println(c, d, g)
}
问:输出什么?
答:golang 100 true
分析: 在同一个 const group 中,如果常量定义与前一行的定义一致,则可以省略类型和值。编译时,会按照前一行的定义自动补全
2.强制类型转换
func main() {
const N = 100
var x int = N
const M int32 = 100
var y int = M
fmt.Println(x, y)
}
问:输出什么?
答:编译失败:cannot use M (type int32) as type int in assignment
分析: const N = 100,属于无类型常量,赋值给其他变量时,如果字面量能够转换为对应类型的变量,则赋值成功,例如,var x int = N。但是对于有类型的常量 const M int32 = 100,赋值给其他变量时,需要类型匹配才能成功,应该这样:var y int = int(M)
3.数值的范围
func main() {
var a int8 = -1
var b int8 = -128 / a
fmt.Println(b)
}
问:输出什么?
答: -1
分析: int8 能表示的数字的范围是 [-2^7, 2^7-1],即 [-128, 127]。数据计算后为128是一个变量,变量转换时允许溢出,符号位变为1,转为补码后恰好等于 -128
4.常量转换
func main() {
const a int8 = -1
var b int8 = -128 / a
fmt.Println(b)
}
问:输出什么?
答:编译失败:-128 / a (constant 128 of type int8) overflows int8
分析: 两个常量相除,结果也是一个常量,常量类型转换时不允许溢出,因而编译失败
5.如何高效地拼接字符串
func concatWithBuilder() string {
var builder strings.Builder
builder.Grow(10000) // 预分配空间
for i := 0; i < 10000; i++ {
builder.WriteString("a")
}
return builder.String()
}
6.什么是 rune 类型
Unicode包含世界上书写系统中存在的所有字符,在 Go 语言中称之为 rune,是 int32 类型的别名。在go中,一般是utf-8编码,语 和 言 使用 UTF-8 编码后各占 3 个 byte,因此 len("Go语言") 等于 8
fmt.Println(len("Go语言")) // 8
fmt.Println(len([]rune("Go语言"))) // 4
2. 作用域
1.声明的作用域
func main() {
var err error
if err == nil {
err := fmt.Errorf("err")
fmt.Println(1, err)
}
if err != nil {
fmt.Println(2, err)
}
}
问:输出什么?
答:1 err
分析: := 表示声明并赋值,= 表示仅赋值;局部变量 err。对该局部变量的赋值不会影响到外部的 err
3. defer 延迟调用
1.调用链
type T struct{}
func (t T) f(n int) T {
fmt.Print(n)
return t
}
func main() {
var t T
defer t.f(1).f(2)
fmt.Print(3)
}
问:输出什么?
答:132
分析: defer 延迟调用时,需要保存函数指针和参数,因此链式调用的情况下,除了最后一个函数/方法外的函数/方法都会在调用时直接执行。也就是说 t.f(1) 直接执行,然后执行 fmt.Print(3),最后函数返回时再执行 .f(2),因此输出是 132
2.压栈保存
func f(n int) {
defer fmt.Println(n)
n += 100
}
func main() {
f(1)
}
问:输出什么?
答:1
分析: defer 语句执行时,会将需要延迟调用的函数和参数保存起来,也就是说,执行到 defer 时,参数 n(此时等于1) 已经被保存了。因此后面对 n 的改动并不会影响延迟函数调用的结果。
3.传参
func main() {
n := 1
defer func() {
fmt.Println(n)
}()
n += 100
}
问:输出什么?
答:101
分析: 匿名函数没有通过传参的方式将 n 传入,因此匿名函数内的 n 和函数外部的 n 是同一个,延迟执行时,已经被改变为 101。
4.后进先出
func main() {
n := 1
if n == 1 {
defer fmt.Println(n)
n += 100
}
fmt.Println(n)
}
问:输出什么?
答:101 1
分析: 先打印 101,再打印 1。defer 的作用域是函数,而不是代码块,因此 if 语句退出时,defer 不会执行,而是等 101 打印后,整个函数返回时,才会执行。
4. 函数
1.默认参数
问:Go 支持默认参数或可选参数吗?
答:原生不支持默认参数或可选参数
分析: 可通过结构体选项和函数选项模拟
结构体传递可选参数
type Options struct {
Timeout int // 默认值在使用时设置
Retries int // 默认值在使用时设置
Protocol string // 默认值在使用时设置
}
func Connect(host string, opts *Options) error {
// 设置默认值
if opts == nil {
opts = &Options{
Timeout: 10,
Retries: 3,
Protocol: "tcp",
}
}
// 使用opts参数
return nil
}
// 使用
Connect("example.com", &Options{
Timeout: 5,
})
函数选项模式
type Option func(*Options)
func WithTimeout(timeout int) Option {
return func(o *Options) {
o.Timeout = timeout
}
}
func WithRetries(retries int) Option {
return func(o *Options) {
o.Retries = retries
}
}
func Connect(host string, options ...Option) error {
opts := Options{
Timeout: 10, // 默认值
Retries: 3, // 默认值
Protocol: "tcp", // 默认值
}
for _, opt := range options {
opt(&opts)
}
// 使用opts参数
return nil
}
// 使用
Connect("example.com", WithTimeout(5), WithRetries(2))
使用...接收可变参数,在函数内部处理默认值
func Sum(nums ...int) int {
sum := 0
for _, num := range nums {
sum += num
}
return sum
}
// 使用
Sum() // 返回0
Sum(1, 2, 3) // 返回6
5.其他
1.空 struct{} 的用途
使用空结构体 struct{} 可以节省内存,一般作为占位符使用
2.Go 语言中如何表示枚举值(enums)通常使用常量(const) 来表示枚举值。
type Weekday int
const (
Sunday Weekday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
// 使用
func IsWeekend(day Weekday) bool {
return day == Sunday || day == Saturday
}
3.字符串打印时,%v 和 %+v 的区别
%v 和 %+v 都可以用来打印 struct 的值,区别在于 %v 仅打印各个字段的值,%+v 还会打印各个字段的名称。
4.Go 语言 tag 的用处
tag 可以理解为 struct 字段的注解,可以用来定义字段的一个或多个属性。框架/工具可以通过反射获取到某个字段定义的属性,采取相应的处理方式。tag 丰富了代码的语义,增强了灵活性。