flag.String()返回新指针,flag.StringVar()直接写入变量地址;需按需求选:快速声明用前者,复用变量用后者;自定义类型须实现flag.Value接口;flag.Parse()必须最后调用且仅一次;错误处理需设ContinueOnError。

flag.String() 和 flag.StringVar() 有什么区别,该选哪个
区别在于变量绑定时机:前者返回新分配的字符串指针,后者直接把值写入你传入的变量地址。用错会导致参数解析后变量仍是零值。
实操建议:
- 想快速声明+使用,用
flag.String(),比如port := flag.String("port", "8080", "server port"),后续用*port取值 - 想复用已有变量或结构体字段,必须用
flag.StringVar(),比如flag.StringVar(&cfg.Port, "port", "8080", "server port") - 别混用:如果写了
var port string; flag.String("port", "8080", ""),port永远不会被赋值——因为flag.String()返回的是另一个指针
自定义类型参数(如 time.Duration、自定义 struct)怎么注册
flag 包不支持直接传入任意类型,必须实现 flag.Value 接口(含 Set(string) error 和 String() string 方法),否则调用 flag.Var() 会 panic。
常见错误现象:直接传 struct 或未实现接口的类型,报错 flag provided but not defined 或运行时 panic。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 对
time.Duration,直接用flag.DurationVar()——标准库已内置支持 - 自定义类型(比如
type LogLevel string),必须实现Set()方法,例如:func (l *LogLevel) Set(s string) error { switch s { case "debug", "info", "warn", "error": *l = LogLevel(s) return nil } return fmt.Errorf("invalid log level: %s", s) } - 注册时用
flag.Var(&cfg.Level, "level", "log level"),不能用flag.StringVar()
为什么 flag.Parse() 必须在所有 flag.* 调用之后、业务逻辑之前执行
因为 flag.Parse() 才真正触发参数扫描和赋值;之前所有 flag.String() 等只是注册描述,不读取命令行。
容易踩的坑:
- 在
flag.Parse()前就访问参数变量(比如打印*port),得到的是默认值,不是用户输入 - 调用
flag.Parse()两次,第二次会 panic:flag redefined - 在
flag.Parse()后又调用flag.String(),新注册的 flag 不会被解析,但也不会报错——静默失效 - 如果用了
flag.CommandLine = flag.NewFlagSet(...)自定义 FlagSet,记得用对应实例的Parse(),别误用全局flag.Parse()
flag 包解析失败时不退出进程,怎么统一处理错误
默认情况下,flag 在遇到未知参数或类型转换失败时会调用 os.Exit(2),无法拦截。这不是 bug,是设计如此。
要控制错误流,必须禁用默认行为:
- 在任何
flag.*调用前,设置flag.Usage = func() {}并关闭自动退出:flag.CommandLine.Init("myapp", flag.ContinueOnError) - 然后手动调用
err := flag.CommandLine.Parse(os.Args[1:]),检查err != nil - 注意:此时
flag.PrintDefaults()不再自动触发,需自己在错误处理中调用 - 如果用了自定义 FlagSet,也要设
fs.Init("", flag.ContinueOnError),否则仍会 exit
flag 的边界很清晰:它只负责“把命令行字符串转成 Go 值”,不处理子命令、补全、类型推导或配置合并。想支持 myapp serve --port 8080 这种多级命令,得换 cobra 或 kingpin——flag 本身没这个能力。










