该用 fmt.Printf 还是 fmt.Sprintf 取决于是否需要立即输出:需直接打印到终端、日志或 io.Writer 时选 fmt.Printf;需构造字符串用于拼接、传参或嵌入模板时选 fmt.Sprintf。
fmt.Printf 直接输出到标准输出,fmt.Sprintf 返回格式化后的字符串——选哪个取决于你是否需要“打印”还是“构造字符串”。
当你想立刻把内容输出到终端、日志或 io.Writer(比如文件、网络连接)时,用 fmt.Printf 更直接高效。
fmt.Sprintf 会先生成字符串再输出,多一次拷贝io.Writer:比如 fmt.Fprintf(os.Stderr, "error: %v", err)
fmt.Printf("user %s not found\n", name) 比先 Sprintf 再 Println 更简洁fmt.Sprintf 不输出,只返回 string,适合拼接、组装、传参、嵌入模板等不能/不该立即输出的场合。
query := fmt.Sprintf("SELECT * FROM users WHERE id = %d", userID)
body := fmt.Sprintf(`{"status":"ok","data":%s}`, jsonData)
log.Print(fmt.Sprintf("retry #%d for %s", attempt, url))
strings.ReplaceAll 或正则配合前预处理:key := fmt.Sprintf("cache:%s:%d", category, version)
Golang 的格式动词比 C 更严格,类型不匹配不会自动转换,容易 panic 或输出意外结果。
%v 是最安全的通用打印,但结构体默认不带字段名;加 +%v 可显示字段名(如 {Name:"Alice" Age:30})%d 只接受整数类型:fmt.Sprintf("%d", int64(42)) 合法,但 fmt.Sprintf("%d", float64(42)) 编译报错%s 只接受 string 或 []byte(后者会转成字符串),传 int 会输出对应 Unicode 字符(比如 %s 打印 65 得到 "A")%.2f 表示保留两位小数,%e 用科学计数法,%g 自动选更紧凑的形式name := "Bob"
age := 28
s1 := fmt.Sprintf("Hello %s, you are %d years old.", name, age) // 正确
s2 := fmt.Sprintf("Age: %s", age) // 错!%s 不能接 int,编译失败
s3 := fmt.Sprintf("Code: %c", 65) // 输出 "Code: A"
每次调用 fmt.Sprintf 都会分配新字符串,频繁调用(尤其在 hot path 或大循环中)可能引发 GC 压力和内存逃逸。
strings.Builder 手动拼接(适合已知大致长度的场景)var b strings.Builder; b.Grow(128); b.WriteString(...)
log.Printf,它内部做了优化,比 fmt.Sprintf + log.Print 略快go tool compile -gcflags="-m" yourfile.go 可检查是否发生堆逃逸真正难的不是记住 %d 和 %v
的区别,而是意识到:当格式化结果要反复构造、又不立刻输出时,Sprintf 的隐式分配成本很容易被忽略。