最简可靠写法是用std::hex、std::setw(2)、std::setfill('0')配合std::ostringstream,并将每个字节转为unsigned char,避免符号扩展导致的高位字节错误。

std::string 转 16 进制字符串:最简可靠写法
直接用 std::hex + std::setw(2) + std::setfill('0') 配合 std::ostringstream,比手写循环或 sprintf 更安全、更易读,也避免了无符号截断问题。
关键点是:每个字节必须按 unsigned char 解释,否则遇到高位为 1 的字节(如 \xFF)会因符号扩展变成负数,再转十六进制就错成 ffffffxx。
std::string to_hex(const std::string& s) {
std::ostringstream oss;
oss (c);
}
return oss.str();
}
- 必须用
unsigned char遍历,不是char或auto -
static_cast<int>(c)</int>是必须的:流操作符对unsigned char会默认当字符输出,不是数值 -
std::setw(2)每次生效一次,所以得放在循环内
处理 UTF-8 字符串时:别误以为“转码”了
把 std::string 当作字节序列转 16 进制,和字符编码无关——它只是把内存里的每个字节原样转成两位十六进制。UTF-8 下一个汉字占 3 字节,就会变成 6 位 hex;ASCII 字符仍占 1 字节,对应 2 位 hex。
如果你本意是“把 Unicode 码点转成 U+XXXX 格式”,那这不是 std::string 能直接解决的,得先用 ICU、utf8cpp 或 C++20 std::text_encoding(尚未普及)做解码。
- 输入
"你"(UTF-8 编码为\xE4\xBD\xA0),输出是"e4bda0",不是"u+4f60" - 没有额外 decode 步骤,就不存在“UTF-8 转 hex”的编码转换逻辑,只有“字节序列转 hex”
- 若需显示 Unicode 码点,先用
utf8::next()(来自 utf8cpp)提取码点,再格式化
性能敏感场景:避免 std::ostringstream 的动态分配
高频调用(如日志、网络封包)中,std::ostringstream 内部 string buffer 可能反复扩容。此时可预分配空间(2×原长),或改用查表法手动填充。
const char HEX_TABLE[512] = {
"000102030405060708090a0b0c0d0e0f"
"101112131415161718191a1b1c1d1e1f"
// ... 全部 256 行,省略
};
<p>std::string to_hex_fast(const std::string& s) {
std::string out;
out.reserve(s.size() <em> 2);
for (unsigned char c : s) {
out.append(HEX_TABLE + c </em> 2, 2);
}
return out;
}</p>
- 查表法快 3–5 倍,但代码体积略增(约 1KB 静态数据)
-
reserve()很重要,否则每次append都可能 realloc - 注意:GCC/Clang 下
HEX_TABLE会被优化进只读段,不影响运行时内存
Windows 下 std::string 含 \0 时的陷阱
如果原始 std::string 是从二进制数据(如加密结果、图片头)构造而来,里面含 \0 字节,那它仍是合法的 std::string,但若误用 c_str() 或传给 C 接口再转 hex,就可能被提前截断。
- 确认来源:用
s.data()和s.size()操作,而非s.c_str() - 错误示范:
to_hex(std::string(buf, len))是对的;to_hex(std::string(cstr))(其中cstr含 \0)就错了 - 调试时可用
printf("len=%zu\n", s.length());验证是否和预期一致
实际用的时候,多数情况选第一种写法就够了;真卡在性能上,再切查表;只要记得字节必须是 unsigned char,就不会掉进符号扩展那个坑里。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











