必须编码的字符包括空格(%20)、保留字符(/、?、&、=、#等)、:、@及非ascii字符;可放行的仅有a-z、a-z、0-9、-、_、.、~共7类字符;url编码对象是utf-8字节而非字符,需逐字节判断并严格按rfc 3986处理。

哪些字符必须编码,哪些可以放行
URL编码不是“看到非字母数字就加%”,而是严格按 RFC 3986 划分:只有 A-Z、a-z、0-9、-、_、.、~ 这7类字符可原样保留;其余全部要转义。空格必须变成 %20,不能用 +;/、?、&、=、# 等保留字符也必须编码,否则拼出的 URL 可能被解析器提前截断。
常见错误是漏掉 : 或 @ ——它们在 URI authority 部分有语义,不编码会导致鉴权失败或路径错乱;还有人把中文直接 cast 成 unsigned char 处理,结果只取了第一个字节,造成乱码。
UTF-8 字节层面逐字处理才是正确起点
URL 编码的对象是字节,不是字符。输入字符串必须是合法 UTF-8(比如来自 std::string 且已确保编码一致),然后对每个 unsigned char 判断是否属于 unreserved 集合。不能用 wchar_t 或 std::u32string 直接遍历——那会跳过 UTF-8 多字节结构,把一个汉字拆成多个非法字节再分别编码。
示例逻辑:
for (unsigned char c : s) {
if (c >= 'A' && c = 'a' && c = '0' && c <p>注意:<code>std::string</code> 的 <code>operator[]</code> 返回 <code>char</code>,直接用于条件判断可能因符号扩展出错,务必先转 <code>unsigned char</code>。</p><h3>十六进制输出必须补零且用大写字母</h3><p>RFC 3986 要求 <code>%</code> 后跟两位十六进制数,不足补零,字母用大写(<code>%20</code> 正确,<code>%2</code> 或 <code>%20</code> 小写都违规)。别依赖 <code>std::hex</code> 默认行为——它不补零,也不控制大小写。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill5502" title="C++ Code Review Master"><img
src="https://img.php.cn/upload/skill/000/000/081/179051228971575.jpg" alt="C++ Code Review Master" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill5502" title="C++ Code Review Master" class="overflowclass">C++ Code Review Master</a>
<p class="overflowclass">组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。</p>
</div>
<a rel="nofollow" href="/xiazai/skill5502" title="C++ Code Review Master" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>安全做法是查表或手动拆字节:</p>
-
"0123456789ABCDEF"查表最稳,索引用c >> 4和c & 0x0F - 用
std::ostringstream时必须配std::setw(2)+std::setfill('0')+std::uppercase - 别用
std::to_string拼接——它不支持补零,且对高位字节会丢数据
另外,% 字符自身必须编码为 %25,否则解码端无法区分真实转义和字面 %。
预分配空间和性能边界要注意
每个需编码字节会膨胀成 3 字符(如 ' ' → "%20"),所以输出容量建议用 result.reserve(s.size() * 3)。但实际长度通常远小于此,因为多数 ASCII 字符不编码;过度 reserve 会浪费内存,完全不 reserve 则频繁 realloc 影响性能。
更隐蔽的问题是:某些实现把整个字符串先转成大写再查表,这会误改非转义部分(如 "API/KEY" 里不该动的 K 和 E);还有人用 std::replace 处理空格,结果把路径中本该保留的 / 前后空格也一并替换了。
真正健壮的实现,从输入到输出全程只做一次遍历、一次查表、一次 push_back,不拷贝中间字符串,也不依赖 locale 或 codecvt。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










