主题
第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设计理由:
- 避免野指针/野内存——这是 C 的核心痛点
- 简化判断——结构体不用挨个初始化也能用
- runtime 友好——分配内存时直接 zero-fill
Q3:int、int32、rune 有什么区别?
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
)关键规则:
iota在每个const(...)块开头被设为 0- 每经过一个 const 行就 +1
- 如果某行没写表达式,沿用上一行的表达式(不是值)
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。
原因:
- Go 字符串在内存里以 UTF-8 编码存储
- 一个汉字在 UTF-8 中占 3 个字节
len()返回的是字节数
要得到字符数:
go
len([]rune("中国")) // 2
utf8.RuneCountInString("中国") // 2Q10:下面这段代码有什么问题?
go
package main
import "fmt"
var x = "hello"
y := "world"
func main() {
fmt.Println(x, y)
}答:编译错误。y := "world" 写在了包级别,但 := 只能用于函数内。
修复:
go
var y = "world" // ✅★★★ 深度题
Q11:new 和 make 有什么区别?
| 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"
🎯 自测练习
- 写出 Go 中的所有零值
- 用
iota写一个 HTTP 状态码枚举 - 把字符串 "Hello, 世界" 反转(提示:必须先转
[]rune) - 写一个函数判断一个浮点数是不是"接近 0"
- 解释为什么
string(65)是 "A" 不是 "65"
完成后再进入第03章:流程控制 🚀