百分比编码是将URL中非保留字符(仅A-Z、a-z、0-9、-、_、.、~)转换为%加两位十六进制码的机制,需按UTF-8字节序列处理,避免符号扩展与多字节误切。

什么是百分比编码,哪些字符必须编码
百分比编码不是简单地把空格换成 %20 就完事。它要求对所有“非保留字符”(non-reserved characters)进行转义,包括空格、中文、标点、控制字符等。RFC 3986 明确规定:只有 A-Z、a-z、0-9、-、_、.、~ 这些是安全的,其余都得编码。比如 → %20,/ → %2F,中 → %E4%B8%AD(UTF-8 编码后 hex)。
用 std::ostringstream + std::hex 手动实现最可控
标准库没有内置 URL encode 函数,但用 std::ostringstream 拼接 + std::hex 转十六进制,逻辑清晰、无外部依赖、可精确控制编码范围。关键点在于:先将输入字符串按 UTF-8 字节序列处理(不按 char 或 wchar_t 切分),再逐字节判断是否需编码。
常见错误是直接遍历 std::string 的每个 char,误把多字节 UTF-8 当作单字节——结果中文变成乱码或部分编码失败。
- 用
for (unsigned char c : input)避免符号扩展问题(char可能为负) - 检查时用
!is_unreserved(c),其中is_unreserved是自定义函数,只放行那 7 类字符 - 对需编码的字节,用
std::setw(2) (c) - 注意:不能对
+特殊处理成空格(那是 application/x-www-form-urlencoded 的规则,纯 percent-encoding 不做此映射)
使用 curl_easy_escape 要小心内存和编码前提
libcurl 提供了 curl_easy_escape,但它默认假设输入是 ISO-8859-1 编码,若传入 UTF-8 字符串(如含中文),结果会错。必须配合 curl_easy_unescape 的逆向逻辑理解其行为——它不会自动 UTF-8 检测,也不会报错。
立即学习“C++免费学习笔记(深入)”;
更现实的做法是:先确保输入是合法 UTF-8(可用轻量校验),再用 curl_free 释放返回指针,否则内存泄漏。它返回的是 char*,不是 std::string,需手动转换。
- 不要传入
std::string.c_str()后直接丢给curl_easy_escape就认为万事大吉 - 若项目已链接 libcurl 且允许依赖,可封装一层:
std::string url_encode(const std::string& s) { auto p = curl_easy_escape(nullptr, s.c_str(), s.size()); std::string r(p); curl_free(p); return r; } - 但该函数在遇到非法 UTF-8 字节序列时可能截断或行为未定义——这点常被忽略
为什么不用 Boost.URL 或 cpp-httplib 的 encode?
Boost.URL(v1.82+)的 boost::urls::encode 是目前最规范的实现,它明确以 UTF-8 字节流为输入、严格遵循 RFC 3986,并区分「路径编码」和「查询编码」(后者对 / 和 ? 也编码)。但它的构建依赖较重,头文件编译慢,且默认不启用 C++20 模块支持时,include 开销明显。
cpp-httplib 的 detail::encode_url 是内部函数,未暴露为 public API,且它对空格编码为 +(兼容 x-www-form-urlencoded),不符合纯 percent-encoding 要求。
- 若你已在用 Boost,且能接受其构建成本,
boost::urls::encode是最省心的选择 - 若只是临时需要、不想引入新依赖,手写版本 20 行内就能跑通,且逻辑完全透明
- 最容易被忽略的点:URL 解码(decode)必须与编码(encode)使用同一字符集解释——编码用 UTF-8,解码端也必须按 UTF-8 解,否则中文永远对不上


















