本文详解 go 语言中结构体实例化与切片赋值时常见的引用陷阱,重点指出 := 与 = 的关键区别——前者创建新局部变量(遮蔽外层变量),后者复用原有指针变量,是解决多段 markdown 解析中数据混叠问题的核心。
本文详解 go 语言中结构体实例化与切片赋值时常见的引用陷阱,重点指出 := 与 = 的关键区别——前者创建新局部变量(遮蔽外层变量),后者复用原有指针变量,是解决多段 markdown 解析中数据混叠问题的核心。
在 Go 中,结构体指针和切片本身是引用类型,但它们的“引用性”常被误解。真正关键的是:变量声明方式决定了作用域与绑定关系。原代码中问题根源并非内存共享本身,而是典型的 变量遮蔽(variable shadowing) ——rule := &Section{} 在 if 块内新建了一个同名局部变量,导致外层 rule 指针从未被更新,始终指向初始空结构体。后续所有 rule.Lines = append(...) 都在向同一个 Section 实例追加数据,最终所有条目共享同一份 Lines 切片。
正确写法:复用指针变量,而非声明新变量
将 rule := &Section{} 改为 rule = &Section{},确保始终操作外层声明的 rule 变量:
func main() {
type Section struct {
Category string
Lines []string
}
file, err := os.Open("./src/basicmarkdown/basicmarkdown.md")
if err != nil {
log.Fatal(err)
}
defer file.Close()
rgxRoot, _ := regexp.Compile("^#[^#]")
rgxBehaviour, _ := regexp.Compile("^-[ ]?.*")
scanner := bufio.NewScanner(file)
ruleArr := []*Section{}
var rule *Section // 显式初始化为 nil,更清晰
for scanner.Scan() {
linetext := scanner.Text()
trimmed := strings.TrimSpace(linetext)
// 遇到标题行:创建新 Section,并重置 rule 指针
if rgxRoot.MatchString(linetetext) {
rule = &Section{Category: linetext} // ✅ 使用 = 赋值,复用 rule 变量
continue // 跳过后续处理(标题行不加入 Lines)
}
// 遇到列表行:追加到当前 Section 的 Lines
if rgxBehaviour.MatchString(linetext) {
if rule != nil { // 安全检查:避免 nil 指针 panic
rule.Lines = append(rule.Lines, linetext)
}
continue
}
// 遇到空行:保存当前 Section(若非 nil),并重置 rule
if len(trimmed) == 0 {
if rule != nil {
ruleArr = append(ruleArr, rule)
rule = nil // 显式置空,避免误用
}
}
}
// 文件末尾可能无空行,需手动追加最后一个 Section
if rule != nil {
ruleArr = append(ruleArr, rule)
}
jsonSection, err := json.MarshalIndent(ruleArr, "", "\t")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(jsonSection))
}
关键注意事项
- := vs =::= 是声明+赋值,在块内创建新变量;= 是纯赋值,操作已有变量。此处必须用 =。
- nil 检查:在 rule.Lines = append(...) 前增加 if rule != nil,防止程序在首行为空或格式异常时 panic。
- 空行逻辑优化:原代码中空行触发 append(ruleArr, rule),但此时 rule 可能仍为初始值(未被标题初始化)。改进后仅在 rule 有效时才追加。
- 结尾处理:Markdown 文件末尾未必有空行,循环结束后需显式检查并追加最后一个 Section。
- 错误处理:生产代码中应检查 os.Open、regexp.Compile 和 json.MarshalIndent 的返回错误,避免静默失败。
通过理解 Go 的变量作用域规则与指针语义,你不仅能修复此问题,更能规避大量因遮蔽导致的逻辑错误——这正是 Go “少即是多”哲学下对开发者清晰思维的隐性要求。











