AI Library · 知识库

# 转成字符串:格式化与显示形式

学习目标:通过将日志与度量数据拼接为结构化文本,理解各语言的字符串插值、格式化函数与对象文本化协议。

前置知识与范围:基本数据类型、字符串与标准输出。本篇处理内存数据到固定格式文本的转换。

# 问题与边界

将用户 ID、IP 地址以及浮点延迟毫秒数格式化为标准的指标日志行:

  1. 格式契约:USER#<id> [<ip>] took <latency_ms>ms。
  2. 精度约定:延迟数保留两位小数,如 12.5 格式化为 12.50,0.0 格式化为 0.00。
  3. 纯函数:函数接收参数并返回字符串,由外层执行打印或断言。

测试用例:

# 关键差异

语言 字符串插值与格式化 浮点数保留两位小数 默认字符串化协议
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 控制输出格式。

# 迁移误区与适用限制

# 运行与验证

语言版本基线见示例基线。 所有程序运行后控制台输出均为:

纯文本USER#42 [127.0.0.1] took 12.50ms

测试断言已验证四舍五入对零值与多位小数的格式化一致性。

# 相关阅读