# 转成字符串:格式化与显示形式
学习目标:通过将日志与度量数据拼接为结构化文本,理解各语言的字符串插值、格式化函数与对象文本化协议。
前置知识与范围:基本数据类型、字符串与标准输出。本篇处理内存数据到固定格式文本的转换。
# 问题与边界
将用户 ID、IP 地址以及浮点延迟毫秒数格式化为标准的指标日志行:
- 格式契约:
USER#<id> [<ip>] took <latency_ms>ms。 - 精度约定:延迟数保留两位小数,如
12.5格式化为12.50,0.0格式化为0.00。 - 纯函数:函数接收参数并返回字符串,由外层执行打印或断言。
测试用例:
id=42, ip="127.0.0.1", latency=12.5->"USER#42 [127.0.0.1] took 12.50ms"。id=7, ip="0.0.0.0", latency=0.0->"USER#7 [0.0.0.0] took 0.00ms"。
# 关键差异
| 语言 | 字符串插值与格式化 | 浮点数保留两位小数 | 默认字符串化协议 |
|---|---|---|---|
| Rust | format!("USER#{id} [{ip}] took {latency:.2}ms") |
宏内格式修饰符 :.2 |
std::fmt::Display |
| Go | fmt.Sprintf("USER#%d [%s] took %.2fms", id, ip, latency) |
格式占位符 %.2f |
fmt.Stringer 接口 |
| Java | String.format(Locale.ROOT, "USER#%d [%s] took %.2fms", ...) |
占位符 %.2f |
toString() 方法 |
| JavaScript | 模板字符串配合 latency.toFixed(2) |
Number.prototype.toFixed(2) |
toString() / Symbol.toPrimitive |
| TypeScript | 沿用 JS 模板字面量与 toFixed(2) |
toFixed(2) |
toString() |
| Kotlin | 字符串模板配合 String.format(Locale.ROOT, "%.2f", latency) |
String.format 或扩展 |
toString() |
| Python | f-string f"USER#{user_id} [{ip}] took {latency:.2f}ms" |
格式说明符 :.2f |
__str__ / __repr__ |
| C++ | C++20 std::format;C++17 使用 std::ostringstream 配合 std::fixed |
std::setprecision(2) |
重载 operator<< |
# 各语言实现
# Rust
Rustfn format_log(id: u64, ip: &str, latency: f64) -> String {
format!("USER#{id} [{ip}] took {latency:.2}ms")
}
fn main() {
assert_eq!(format_log(42, "127.0.0.1", 12.5), "USER#42 [127.0.0.1] took 12.50ms");
assert_eq!(format_log(7, "0.0.0.0", 0.0), "USER#7 [0.0.0.0] took 0.00ms");
println!("{}", format_log(42, "127.0.0.1", 12.5));
}
Rust 的 format! 宏在编译期静态校验格式字符串占位符与参数类型是否匹配。参见 std::fmt 格式化语法。
# Go
Gopackage main
import (
"fmt"
)
func FormatLog(id uint64, ip string, latency float64) string {
return fmt.Sprintf("USER#%d [%s] took %.2fms", id, ip, latency)
}
func main() {
res := FormatLog(42, "127.0.0.1", 12.5)
if res != "USER#42 [127.0.0.1] took 12.50ms" {
panic("mismatch")
}
if FormatLog(7, "0.0.0.0", 0.0) != "USER#7 [0.0.0.0] took 0.00ms" {
panic("mismatch for zero")
}
fmt.Println(res)
}
Go 使用标准库 fmt.Sprintf。参数类型在运行时检查,配合静态工具 go vet 可在编译前检测参数与动词的不匹配。
# Java
Javaimport java.util.Locale;
public class Main {
public static String formatLog(long id, String ip, double latency) {
return String.format(Locale.ROOT, "USER#%d [%s] took %.2fms", id, ip, latency);
}
public static void main(String[] args) {
String res = formatLog(42, "127.0.0.1", 12.5);
assert "USER#42 [127.0.0.1] took 12.50ms".equals(res);
assert "USER#7 [0.0.0.0] took 0.00ms".equals(formatLog(7, "0.0.0.0", 0.0));
System.out.println(res);
}
}
注意显式传递 Locale.ROOT,防止在部分欧洲语言环境下浮点数小数点被格式化为逗号 ,。
# JavaScript
JavaScriptconst assert = {
equal: (a, b) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
deepEqual: (a, b) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
throws: (fn, errType) => {
try { fn(); throw new Error("expected throw"); } catch (e) {
if (e.message === "expected throw") throw e;
if (errType && !(e instanceof errType)) throw e;
}
}
};
function formatLog(id, ip, latency) {
return `USER#${id} [${ip}] took ${latency.toFixed(2)}ms`;
}
assert.equal(formatLog(42, "127.0.0.1", 12.5), "USER#42 [127.0.0.1] took 12.50ms");
assert.equal(formatLog(7, "0.0.0.0", 0.0), "USER#7 [0.0.0.0] took 0.00ms");
console.log(formatLog(42, "127.0.0.1", 12.5));
模板字面量反引号 ` 内嵌入表达式 ${},浮点数通过 .toFixed(2) 截取两位小数。
# TypeScript
TypeScriptconst assert = {
equal: (a: unknown, b: unknown) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
deepEqual: (a: unknown, b: unknown) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
throws: (fn: () => void, errType?: unknown) => {
try { fn(); throw new Error("expected throw"); } catch (e: unknown) {
if (e instanceof Error && e.message === "expected throw") throw e;
if (typeof errType === "function" && !(e instanceof errType)) throw e;
}
}
};
function formatLog(id: number, ip: string, latency: number): string {
return `USER#${id} [${ip}] took ${latency.toFixed(2)}ms`;
}
assert.equal(formatLog(42, "127.0.0.1", 12.5), "USER#42 [127.0.0.1] took 12.50ms");
assert.equal(formatLog(7, "0.0.0.0", 0.0), "USER#7 [0.0.0.0] took 0.00ms");
console.log(formatLog(42, "127.0.0.1", 12.5));
TypeScript 提供类型约束,确保传给 toFixed 的目标确为数值类型。
# Kotlin
Kotlinimport java.util.Locale
fun formatLog(id: Long, ip: String, latency: Double): String {
val latencyStr = String.format(Locale.ROOT, "%.2f", latency)
return "USER#$id [$ip] took ${latencyStr}ms"
}
fun main() {
val res = formatLog(42, "127.0.0.1", 12.5)
check(res == "USER#42 [127.0.0.1] took 12.50ms")
check(formatLog(7, "0.0.0.0", 0.0) == "USER#7 [0.0.0.0] took 0.00ms")
println(res)
}
Kotlin 字符串模板使用 $id 与 ${expr} 语法,结合 Locale.ROOT 格式化浮点。
# Python
Pythondef format_log(user_id: int, ip: str, latency: float) -> str:
return f"USER#{user_id} [{ip}] took {latency:.2f}ms"
if __name__ == "__main__":
assert format_log(42, "127.0.0.1", 12.5) == "USER#42 [127.0.0.1] took 12.50ms"
assert format_log(7, "0.0.0.0", 0.0) == "USER#7 [0.0.0.0] took 0.00ms"
print(format_log(42, "127.0.0.1", 12.5))
Python 3.6 起推荐使用 f-string,格式说明符 :.2f 紧跟在表达式之后。
# C++
C++#include <iostream>
#include <sstream>
#include <iomanip>
#include <string>
#include <cassert>
std::string format_log(uint64_t id, const std::string& ip, double latency) {
std::ostringstream oss;
oss << "USER#" << id << " [" << ip << "] took "
<< std::fixed << std::setprecision(2) << latency << "ms";
return oss.str();
}
int main() {
assert(format_log(42, "127.0.0.1", 12.5) == "USER#42 [127.0.0.1] took 12.50ms");
assert(format_log(7, "0.0.0.0", 0.0) == "USER#7 [0.0.0.0] took 0.00ms");
std::cout << format_log(42, "127.0.0.1", 12.5) << "\n";
}
在 C++17 基线中,标准做法是使用 std::ostringstream 搭配 <iomanip> 的 std::fixed 与 std::setprecision 控制输出格式。
# 迁移误区与适用限制
- 区域敏感性(Locale):直接使用未指定区域的
String.format或printf在德语、法语系统下会将12.5输出为12,50,导致下游机器解析崩溃。技术协议日志必须显式绑定Locale.ROOT。 - 循环内高频拼接:在循环中反复使用
+操作符拼接不可变字符串会造成大量瞬时中间对象;批量构造应选用各语言的字符串缓冲区(如StringBuilder、strings.Builder)。
# 运行与验证
语言版本基线见示例基线。 所有程序运行后控制台输出均为:
纯文本USER#42 [127.0.0.1] took 12.50ms
测试断言已验证四舍五入对零值与多位小数的格式化一致性。