golang不常见的笔试题

68 阅读5分钟

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 丰富了代码的语义,增强了灵活性。