flag.Parse()必须在所有flag定义之后调用,因其仅解析此前已注册的flag;String用于快速定义返回指针,StringVar绑定结构体字段更易测试;-h与--help需分别注册;支持多值需实现flag.Value接口。

flag.Parse() 必须在所有 flag.String()、flag.Int() 等定义之后调用,否则变量永远是零值——这不是 bug,是 flag 包的注册式设计决定的。
为什么 flag.Parse() 一定要放在最后?
Go 的 flag 包不自动扫描 os.Args,它靠显式调用 flag.Parse() 才真正开始解析。而解析只作用于**此前已注册**的 flag。注册动作就是你调用 flag.String("port", 8080, "") 这类函数时发生的。
- 错误写法:
flag.Parse()写在第一行,后面再定义flag.Int()→ 用户传了-port 3000,但*port还是0 - 报错现象:
flag provided but not defined,其实是定义晚了,不是拼写错 - 别在
if分支里动态注册 flag:比如根据os.Getenv("DEBUG")决定是否调用flag.Bool("debug",...),这会让参数逻辑不可预测,也难测试
String 和 StringVar 到底该用哪个?
区别不在“好不好用”,而在变量生命周期和使用场景:
-
flag.String("host", "localhost", "")返回*string,适合脚本式快速定义,但必须在flag.Parse()后解引用:*host -
flag.StringVar(&cfg.Host, "host", "localhost", "")直接绑定已有变量地址,适合配置结构体字段,后续直接用cfg.Host,不用解引用 - 传给
StringVar的必须是指针:flag.StringVar(cfg.Host, ...)会编译失败;flag.StringVar(&cfg.Host, ...)才对 - 测试更友好:
StringVar可传局部变量地址,mock 成本低;String返回的指针若在Parse前被解引用,会 panic
如何支持 -h 和 --help 共存?
flag 包默认不映射短选项到长选项。-h 不等于 --help,它们是两个独立 flag。
- 必须显式注册两个:
flag.Bool("h", false, "show help")和flag.Bool("help", false, "show help") - 它们各自绑定不同变量,不会自动同步 → 要么手动合并判断:
if *h || *help { flag.Usage(); os.Exit(0) } - 要么统一用长选项(推荐),或换
spf13/pflag(cobra底层所用,原生支持 -h/--help 映射) - 注意:
flag.Usage默认只响应-h或--help中的一个,除非你重写了它
想接收多个 -tag a -tag b 怎么办?
flag.StringSlice() 和 flag.StringSliceVar() 不会自动累积重复 flag;它们只取最后一个值。
立即学习“go语言免费学习笔记(深入)”;
- 要支持多次出现的 flag(如
-tag foo -tag bar),必须实现flag.Value接口 - 核心是
Set(string) error方法:每次遇到该 flag 就调一次,内部用append累积 - 别漏空字符串处理:如果用户传
-tag "",Set仍会被调,需按需过滤或报错 - 示例类型:
type tags []string,func (t *tags) Set(s string) { *t = append(*t, s) }
flag.Value 类型时,String() string 方法返回的字符串会出现在 --help 输出里,如果返回空或格式混乱,会影响用户理解;而 Set 中未返回 error 的解析失败,会被 flag 静默吞掉。


















