“完全”转义指严格按rfc 3986仅放行a-z、a-z、0-9、-、.、_、~共65个字节,其余所有字节(如/、?、空格、utf-8中文)一律无条件编码为%xx大写十六进制,且必须用unsigned char遍历、查表判断、手动拼接hex_digits并预分配空间。

什么叫“完全”转义?
“完全”在这里不是指把所有字符都编码,而是指严格按 RFC 3986 的 unreserved 字符集做判断:只放行 A-Z、a-z、0-9、-、.、_、~ 这 62+3 个字节,其余一字节不漏全转成 %XX(大写十六进制)。比如 /、?、 (空格)、中(UTF-8 字节 0xE4)都必须编码,不能靠“上下文是否需要”来跳过。
为什么不能直接遍历 char?
因为 char 在多数平台是有符号的,遇到 UTF-8 多字节字符的后续字节(如 0x85、0xBD)会变成负数,传给 std::hex 就输出一长串补码(如 ffffff85),结果是错的。必须用 unsigned char 强转:
-
for (unsigned char c : s)是安全起点 - 别写
for (char c : s)或for (auto c : s)(auto推导出char) - 对
c做判断或格式化前,先确保它是unsigned char类型
怎么高效判断是否要编码?
查表法比每次调 std::string::find 或一堆 || 快得多,也更可靠:
static constexpr bool should_encode[256] = {
[0 ... 32] = true, // 控制字符(含空格 0x20)
['A' ... 'Z'] = false,
['a' ... 'z'] = false,
['0' ... '9'] = false,
['-'] = false, ['.'] = false, ['_'] = false, ['~'] = false,
[127 ... 255] = true // 所有非 ASCII 字节(含 UTF-8 各字节)都编码
};
注意:should_encode[0x2F](即 '/')是 true,should_encode[0x41]('A')是 false。这个表覆盖全部 256 种字节值,无分支、零运行时开销。
如何生成正确的 %XX 格式?
std::ostringstream 配合流操纵符看似方便,但容易踩坑:
-
std::hex默认小写,必须加std::uppercase -
std::setw(2)只对下一个输出项生效,且会污染流状态;多次使用需重置 - 更稳妥的是手动拼接:
result += '%'; result += HEX_DIGITS[c >> 4]; result += HEX_DIGITS[c & 0x0F]; - 预分配空间:
result.reserve(s.size() * 3)(最坏情况每个字节变 3 字节)
其中 HEX_DIGITS 定义为:static constexpr char HEX_DIGITS[] = "0123456789ABCDEF";
真正难的不是写几行循环,而是记住:RFC 3986 编码操作对象永远是字节,不是字符;% 本身必须编码为 %25;已存在的 %XX 序列不能二次编码——这些边界条件一旦漏掉,就不是“合规”,只是“看起来像”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











