Skip to content

第02章 面试题精选 —— 变量与基本数据类型

这一章的题目几乎是 Go 笔试题的"开胃菜"——不会写就直接挂。


★ 基础题

Q1:var:=const 三种声明方式有什么区别?

var:=const
适用范围包级别 + 函数内仅函数内全局
是否可推导类型可(省略类型时)永远推导
是否可重新赋值
是否编译期确定不要求不要求要求

经典坑:

go
package main
x := 10   // ❌ 编译错误:包级别不能用 :=
var x = 10  // ✅

Q2:Go 的零值是什么?为什么这样设计?

:变量声明后不显式赋值时的默认值

go
var i int      // 0
var s string   // ""
var p *int     // nil

设计理由

  1. 避免野指针/野内存——这是 C 的核心痛点
  2. 简化判断——结构体不用挨个初始化也能用
  3. runtime 友好——分配内存时直接 zero-fill

Q3:intint32rune 有什么区别?

go
int     // 与平台相关,32 位机是 4 字节,64 位机是 8 字节
int32   // 始终 4 字节
rune    // int32 的别名,专门表示 Unicode 码点
byte    // uint8 的别名,专门表示一个字节

💡 rune 这个词来自北欧文字"如尼文",象征 Unicode 字符。


Q4:下面这段代码输出什么?

go
n := 65
fmt.Println(string(n))

:输出 "A"不是 "65"

原因string(int) 把整数当作 Unicode 码点 解释,65 是字母 A 的 ASCII 码。

正确做法

go
strconv.Itoa(n)        // "65"
fmt.Sprintf("%d", n)   // "65"

🚨 Go 1.15+ 编译器会对 string(int) 给出警告:

conversion from int to string yields a string of one rune

Q5:常量为什么不能取地址?

go
const x = 100
p := &x   // ❌ 编译错误:cannot take the address of x

:常量在编译期就被替换到使用处,根本不存在内存地址。它就像 C 的 #define,是文本替换。


★★ 进阶题

Q6:iota 的工作原理?说出下面这段代码的输出。

go
const (
    a = iota       // 0
    b              // 1
    c = iota + 100 // 102
    d              // 103
    e = iota * 2   // 8
    f              // 10
)

关键规则

  1. iota 在每个 const(...) 块开头被设为 0
  2. 每经过一个 const 就 +1
  3. 如果某行没写表达式,沿用上一行的表达式(不是值)

Q7:Go 中浮点数比较为什么不能直接用 ==

go
fmt.Println(0.1 + 0.2 == 0.3)   // false
fmt.Println(0.1 + 0.2)          // 0.30000000000000004

原因:浮点数遵循 IEEE 754 标准,许多十进制小数无法精确表示成二进制。

正确比较

go
const epsilon = 1e-9
if math.Abs(a-b) < epsilon { ... }

Q8:字符串可以修改吗?为什么?

go
s := "hello"
s[0] = 'H'   // ❌ 编译错误

:字符串是只读的字节序列。Go 在底层用一个结构体 { data *byte; len int } 表示字符串,data 指向只读内存段。

要修改:

go
b := []byte(s)
b[0] = 'H'
s2 := string(b)

💡 注意 []byte(s)拷贝一份新内存,所以是安全的。


Q9:len("中国") 是几?为什么?

len("中国") = 6

原因

  1. Go 字符串在内存里以 UTF-8 编码存储
  2. 一个汉字在 UTF-8 中占 3 个字节
  3. len() 返回的是字节数

要得到字符数:

go
len([]rune("中国"))   // 2
utf8.RuneCountInString("中国")   // 2

Q10:下面这段代码有什么问题?

go
package main

import "fmt"

var x = "hello"
y := "world"

func main() {
    fmt.Println(x, y)
}

:编译错误。y := "world" 写在了包级别,但 := 只能用于函数内。

修复

go
var y = "world"   // ✅

★★★ 深度题

Q11:newmake 有什么区别?

new(T)make(T, ...)
返回类型*T(指针)T(值)
用途任何类型仅 slice / map / channel
初始化零值根据参数初始化容量等
go
p := new(int)          // *int,*p = 0
s := make([]int, 5)    // []int,len=5
m := make(map[string]int)  // 可用的空 map
c := make(chan int, 10)    // 缓冲为 10 的 channel

🤔 为什么 slice/map/channel 需要 make? 因为它们底层是带运行时元信息的引用类型,光分配内存不够,还要初始化哈希表、循环队列等结构。


Q12:常量为什么可以参与不同类型的运算?

go
const Pi = 3.14
var f float32 = Pi      // ✅
var d float64 = Pi      // ✅

:Go 区分有类型常量无类型常量(untyped const)。

go
const Pi = 3.14         // 无类型,可以匹配任何兼容数值类型
const Pi64 float64 = 3.14   // 有类型,只能用于 float64

无类型常量的精度可以远超 int64,编译器会按需"塌缩"成具体类型。


Q13:能否在运行时修改 const 的值?

:不能。常量在编译期就被替换到使用处,运行时根本不存在常量这个东西

证据:

bash
go build -gcflags=-S hello.go

查看汇编,常量 100 会直接以立即数形式出现,没有 load 指令。


📌 速记口诀

"零值不慌张,:= 函数内,const 编译时,类型必显式,整数转字符靠 strconv"


🎯 自测练习

  1. 写出 Go 中的所有零值
  2. iota 写一个 HTTP 状态码枚举
  3. 把字符串 "Hello, 世界" 反转(提示:必须先转 []rune
  4. 写一个函数判断一个浮点数是不是"接近 0"
  5. 解释为什么 string(65) 是 "A" 不是 "65"

完成后再进入第03章:流程控制 🚀