URL编码仅对RFC 3986定义的unreserved字符(A-Z、a-z、0-9、-、_、.、~)放行,其余字符须按UTF-8字节逐个转义为%XX格式,空格为%20,中文“你好”为%E4%BD%A0%E5%A5%BD,%必须硬编码且十六进制小写补零。

URL编码的本质是哪些字符必须转义
URL编码不是对所有非字母数字字符一概而论地替换,而是严格按 RFC 3986 定义:只对unreserved字符(A-Z、a-z、0-9、-、_、.、~)放行,其余一律百分号编码。空格要变成%20,中文如你好会变成%E4%BD%A0%E5%A5%BD,注意这是 UTF-8 编码后的字节再 hex 转义,不是 GBK 或其他编码。
常见误区是直接遍历字符串、遇到非 ASCII 就 std::hex 输出——这会漏掉对/、?、=、&等保留字符的处理;更隐蔽的问题是没指定编码前提,导致本地 locale 下宽字符转换出错。
用 std::ostringstream + std::hex 实现安全转义
不依赖第三方库时,最可控的方式是手动逐字节处理 UTF-8 字符串。关键点在于:先确保输入是合法 UTF-8,再对每个字节判断是否属于unreserved集合,否则格式化为%XX。
-
%必须硬编码,不能用std::format(C++20 不普及且不保证无异常) - 小写十六进制更通用,
std::hex默认大写,需配合std::nouppercase - 每个字节必须补零:
std::setw(2) ,否则<code>0x5会输出成%5而非%05 - 别用
std::string::c_str()直接 reinterpret_cast,UTF-8 多字节字符必须按字节拆解,不能按char单个判
std::string urlencode(const std::string& s) {
static const std::string unreserved = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~";
std::ostringstream escaped;
escaped.fill('0');
escaped.flags(std::ios::hex | std::ios::lowercase);
<pre class="brush:php;toolbar:false;">for (unsigned char c : s) {
if (unreserved.find(c) != std::string::npos) {
escaped << c;
} else {
escaped << '%' << std::setw(2) << static_cast<int>(c);
}
}
return escaped.str();}
立即学习“C++免费学习笔记(深入)”;
中文、emoji 等多字节字符的正确处理
上面函数能跑通,但前提是输入std::string已经是 UTF-8 编码。如果原始数据是std::u8string(C++20)、std::u16string或 Windows 的std::wstring,必须先转 UTF-8。例如 Windows 下用WideCharToMultiByte(CP_UTF8, ...);Linux/macOS 下通常std::wstring_convert<:codecvt_utf8>></:codecvt_utf8>已弃用,推荐用 std::iconv 或跨平台库如 utf8cpp。
典型错误:
- 直接把
std::wstring L"你好"reinterpret_cast 成char*传入 urlencode → 输出乱码或截断 - 用
std::to_string拼接%和数字 → 效率低且易出错 - 忽略 URL 中已有
%是否需要双重编码(一般不需要,除非明确要求编码已编码内容)
性能与边界场景提醒
短字符串(std::ostringstream内部缓冲区反复分配。可预分配空间:escaped.str().reserve(s.size() * 3)(最坏情况全转义,1 字节变 3 字符)。
真正容易被忽略的是:URL 解码(decode)不是 encode 的逆操作。encode 后的%20在 decode 时必须还原为空格,但+号在表单提交中代表空格,标准 URL 编码本身不用+。所以如果你对接的是老式 PHP 或表单解析逻辑,得额外处理+→空格的映射,而标准urlencode不生成+。











