
本文详解 Go 中结构体和切片初始化时因变量作用域与赋值方式不当导致的“共享引用”问题,并通过 Markdown 转 JSON 的实际案例,说明为何 := 会创建新局部变量、而 = 才能正确重置指针引用。
本文详解 go 中结构体和切片初始化时因变量作用域与赋值方式不当导致的“共享引用”问题,并通过 markdown 转 json 的实际案例,说明为何 `:=` 会创建新局部变量、而 `=` 才能正确重置指针引用。
在 Go 中,:= 是短变量声明(short variable declaration),它不仅赋值,更关键的是——在当前作用域内声明一个新变量。而本例中,rule 已在 for 循环外被声明为 *Section 类型的指针变量(rule := &Section{} → rule = &Section{})。当在 if 块内错误地使用 rule := &Section{} 时,Go 会创建一个同名但作用域仅限于该 if 块的新局部变量,外部的 rule 指针并未更新,仍指向原始结构体实例。这导致后续所有 rule.Lines = append(...) 都持续向同一个底层切片追加数据,最终所有 section 共享同一份 Lines 内容。
此外,原逻辑还存在两个关键缺陷:
- 空白行判断时机错误:当前代码在遇到空行时才追加 rule 到 ruleArr,但首个 section 在遇到第一个 #TITLE 前无对应空行,且末尾 section 缺少终止空行,导致漏存或重复;
- rule 初始化位置不合理:首次解析 #TITLE 1 前,rule 未初始化,rule.Lines 为 nil,虽 append 可安全处理 nil 切片,但语义不清晰,易引发混淆。
✅ 正确做法是:
- 使用 rule = &Section{}(赋值)而非 rule := &Section{}(声明+赋值),确保复用并更新外部指针;
- 在识别到新标题行时,先将前一个 section(若存在)追加进结果数组,再新建 rule;
- 循环结束后,手动追加最后一个 section,避免遗漏。
以下是重构后的完整可运行示例:
package main
import (
"encoding/json"
"fmt"
"io"
"os"
"regexp"
"strings"
"bufio"
)
func main() {
type Section struct {
Category string `json:"Category"`
Lines []string `json:"Lines"`
}
file, err := os.Open("./src/basicmarkdown/basicmarkdown.md")
if err != nil {
panic(err)
}
defer file.Close()
rgxRoot := regexp.MustCompile(`^#[^#]`)
rgxBehaviour := regexp.MustCompile(`^- .*$`)
scanner := bufio.NewScanner(file)
var ruleArr []*Section
var rule *Section // 初始化为 nil
for scanner.Scan() {
linetext := strings.TrimSpace(scanner.Text())
// 遇到标题行:保存上一个 section(若存在),并新建当前 section
if rgxRoot.MatchString(linetetext) {
if rule != nil {
ruleArr = append(ruleArr, rule)
}
rule = &Section{Category: linetext}
continue
}
// 遇到行为行(以 "- " 开头)
if rgxBehaviour.MatchString(linetext) {
if rule == nil {
// 防御性处理:跳过无归属的行
continue
}
rule.Lines = append(rule.Lines, linetext)
}
// 忽略空行、其他内容(如注释 <!-- -->)
}
// 循环结束,追加最后一个 section
if rule != nil {
ruleArr = append(ruleArr, rule)
}
jsonBytes, err := json.MarshalIndent(ruleArr, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(jsonBytes))
}
? 关键注意事项:
- Go 中切片本身是引用类型(包含底层数组指针、长度、容量),但 []string 字段存储的是值副本;真正引发共享问题的是多个 *Section 指针指向同一个结构体实例(或其 Lines 字段共用同一底层数组)。本例中,错误的 := 导致 rule 指针未更新,所有 append 操作都作用于初始 rule 的 Lines,造成数据污染。
- 始终区分 :=(声明新变量)与 =(赋值给已有变量);在循环内复用指针变量时,务必使用 =。
- 对于流式解析场景,推荐采用“遇新则存旧、建新”的模式,而非依赖分隔符(如空行)触发保存,提高鲁棒性。
通过以上修正,程序将严格按标题分组输出独立的 Section 对象,每个 Lines 切片持有专属数据,彻底解决引用复用问题。











