URL编码的“完全双向”指encode后字符串能精确还原任意字节序列(含\0、控制字符、非UTF-8数据),要求输入按const unsigned char*+size_t处理,编码将0–255字节全转%XX,解码严格解析%AB为0xAB,非法序列不静默忽略。

什么是URL编码的“完全双向”?
所谓完全双向,是指 encode 后的字符串能被 decode 精确还原为原始字节序列,且对任意 std::string(含 null 字节、控制字符、非 UTF-8 二进制数据)都成立。这和常见“只处理 ASCII 子集”的简易实现有本质区别——那些实现会丢弃或误解释 \0、\x01、\xFF 等字节。
C++ 中必须用 raw 字节流,不能依赖 std::string 的文本语义
std::string 在 C++ 中本质是字节容器,不是文本容器。只要不把它当 UTF-8 字符串用(比如不调用 .c_str() 后传给 libc 函数),它完全可以安全存 \0 和任意字节。URL 编码操作对象就是字节,不是字符。
- 输入必须按
const unsigned char*+size_t处理,避免隐式转换丢失\0 - 编码时:每个字节(0–255)都转成
%XX格式,包括空格(' '→%20)、斜杠('/'→%2F)、甚至\0(→%00) - 解码时:遇到
%AB必须严格解析为字节0xAB;非法格式(如%GZ、%结尾)应设为错误或跳过,不能静默忽略 - 不要用
std::stringstream或std::hex做单字节转换——它们默认带符号扩展,(char)0xFF可能变成-1,再转十六进制就错
encode_url 和 decode_url 的最小可靠实现
以下函数不依赖 Boost 或第三方库,仅用标准 C++11:
std::string encode_url(const std::string& s) {
std::string out;
out.reserve(s.size() * 3);
for (unsigned char c : s) {
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~') {
out += c;
} else {
out += '%';
out += "0123456789ABCDEF"[c >> 4];
out += "0123456789ABCDEF"[c & 0xF];
}
}
return out;
}
<p>std::string decode_url(const std::string& s) {
std::string out;
out.reserve(s.size());
for (size_t i = 0; i < s.size(); ++i) {
if (s[i] == '%' && i + 2 < s.size()) {
int hi = hex_digit(s[i + 1]);
int lo = hex_digit(s[i + 2]);
if (hi != -1 && lo != -1) {
out += static_cast<char>((hi << 4) | lo);
i += 2;
continue;
}
}
out += s[i];
}
return out;
}</p><p>int hex_digit(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
return -1;
}注意:encode_url 中保留的字符集严格按 RFC 3986 的 “unreserved” 字符定义;decode_url 对非法 % 序列不做修复,直接透传原字符——这是防止歧义的合理策略。
立即学习“C++免费学习笔记(深入)”;
容易踩的坑:空字节、locale、以及 std::string::data() 的陷阱
常见错误是把 std::string 当作 C 风格字符串用:
-
s.c_str()返回的指针在遇到\0时就截断,导致后续字节全丢 -
s.data()在 C++11 中不保证以\0结尾,但长度正确;C++17 起data()和c_str()行为一致,仍需配合size()使用 - 某些 locale-aware 的
std::toupper或流操作会在非 ASCII 字节上出错,URL 编码必须无 locale - 如果原始数据来自文件读取或网络接收,确认你拿到的是原始字节——别用
std::ifstream默认的 text mode,要用std::ios::binary
真正需要双向保真时,std::string 是最轻量又足够可靠的载体;换用 std::vector<uint8_t> 也行,但接口适配成本更高。关键不在容器类型,而在全程绕过任何文本解释逻辑。


















