最安全做法是用 std::format(C++20)或 std::stringstream 按字节遍历转十六进制;因 std::stoi 仅解析合法 hex 数字字符串,无法处理普通文本或二进制数据的逐字节转换。

直接用 std::hex 配合 std::stringstream 或 std::format(C++20)最安全;但“快速”要看场景——若字符串含非 ASCII 字节(如 UTF-8 中文、二进制数据),必须按字节处理,不能用字符流默认的 locale 解码,否则会乱码或截断。
为什么不能直接用 std::stoi(s, nullptr, 16)?
这个函数只适用于「输入是合法十六进制数字字符串」(如 "ff0a"),而你的原始字符串很可能是普通文本(如 "hello")或二进制数据,目标是把它每个字节转成两位十六进制字符(即 "68656c6c6f")。用 std::stoi 会解析失败、抛异常或只读前缀,完全跑偏。
-
std::stoi("hello", ...)立即抛std::invalid_argument - 即使输入是
"123",它返回的是整数 291,不是你想要的 hex 字符串 - 不支持逐字节处理,无法应对
\x00或高位字节
推荐做法:按字节遍历 + std::format(C++20)
这是目前最简洁、类型安全、且不易出错的方式。关键点是把字符串视为 std::string_view 或 std::span<const std::byte></const>,强制按字节解释,绕过编码歧义。
std::string to_hex(std::string_view s) {
std::string out;
out.reserve(s.size() * 2);
for (unsigned char c : s) {
out += std::format("{:02x}", c); // 小写,补零
}
return out;
}- 用
unsigned char遍历,确保\xFF不被解释为负数再符号扩展 -
{:02x}是 C++20 格式化规范,02表示至少两位、左补零,x是小写十六进制 -
reserve()避免多次内存重分配,对长字符串明显提速 - 若需大写,改用
{:02X}
兼容 C++11/14/17:用 std::stringstream + std::hex + std::setw
老标准下没 std::format,但别用 ss 这种写法——它不补零,<code>0 变成 "0" 而非 "00",导致结果长度不可控。
立即学习“C++免费学习笔记(深入)”;
std::string to_hex_legacy(const std::string& s) {
std::stringstream ss;
ss << std::hex << std::setfill('0');
for (unsigned char c : s) {
ss << std::setw(2) << (int)c;
}
return ss.str();
}- 必须同时用
std::setfill('0')和std::setw(2),缺一不可 -
(int)c强制提升为 int,防止char被当符号类型参与运算 - 注意
std::stringstream构造和清空开销略高,短字符串影响不大,超 1KB 建议预分配缓冲或换fmt库
性能陷阱:避免临时 std::string 拼接
有人写 out = out + std::format(...) 或 out += std::to_string(...),这在循环里会触发多次小内存分配,比预分配慢 3–5 倍(实测 10KB 字符串)。
- 永远先调
reserve(n * 2),n 是原始字节数 - 不要用
std::to_string处理十六进制——它只支持十进制 - 如果输出目标是写文件或网络流,直接逐段写入,别拼成一个大字符串
- 极端性能场景(如加密哈希输出),考虑用查表法(256 项静态数组映射
"00"~"ff"),但可读性下降,一般没必要
真正容易被忽略的是:原始字符串是否含嵌入的 \0?用 c_str() 或构造 std::string 时若没传长度,会提前截断。务必用 std::string_view 或显式长度参数接管字节边界。


















