最直接安全的做法是用std::stable_partition配合std::islower(unsigned char),确保小写字母稳定前置且避免非ASCII字符引发的未定义行为。

用 std::stable_partition 一次到位
最直接、安全且符合语义的做法是用 std::stable_partition,它能在保持相对顺序的前提下把满足条件的元素“拎到前面”。对字符串来说,就是把所有小写字母稳定地移到开头,其余字符(大写、数字、符号等)按原序跟在后面。
关键点在于:必须用 std::islower 并传入 unsigned char,否则遇到非 ASCII 字符(比如中文、带重音字母)可能触发未定义行为——这是 C++ 标准明文警告的坑。
std::string s = "Hello123World!";
std::stable_partition(s.begin(), s.end(), [](unsigned char c) {
return std::islower(c);
});
// 结果: "elloorldHWr123!"- 不要用
std::islower(int)直接传char,负值 char 会崩 - 不用手写双指针,
stable_partition内部已优化,且保证稳定性 - 如果不需要保持非小写字母的顺序,可用
std::partition略快一点,但通常没必要牺牲语义
手动双指针实现(适合理解或嵌入式环境)
当标准库不可用,或你想完全控制逻辑时,双指针是清晰的选择:一个从头找非小写,一个从尾找小写,交换。注意边界和终止条件,否则容易越界或漏判。
std::string s = "Hello123World!";
int left = 0, right = s.size() - 1;
while (left < right) {
while (left < right && !std::islower(static_cast<unsigned char>(s[left]))) ++left;
while (left < right && std::islower(static_cast<unsigned char>(s[right]))) --right;
if (left < right) std::swap(s[left], s[right]);
}- 每次比较前都做
static_cast<unsigned char></unsigned>,和上面一致 -
while循环内必须重复检查left ,防止越界 - 这个版本不保序:非小写字母的相对位置会被打乱,比如
"aBcD"可能变成"acBD"而非"acBD"(此处巧合相同,但换作"XaYb"就会看出区别)
为什么不用 std::sort 加自定义比较函数?
有人会想:给每个字符打个“是否小写”的标签,然后排序。这可行但危险——std::sort 要求比较函数满足严格弱序,而只按大小写分类的比较器不满足传递性,会导致未定义行为(常见表现是崩溃或随机结果)。
立即学习“C++免费学习笔记(深入)”;
例如:compare('A', 'a') == true(大写排后),compare('a', '1') == true(小写排前),但 compare('A', '1') 如果也返回 true,就破坏了逻辑一致性。
- 即使临时“凑出”一个看似工作的比较器,也是靠运气,不同 STL 实现可能表现不同
-
stable_partition是为此类二分场景设计的,语义明确、无副作用、复杂度 O(n) - 别为省一行代码引入不可控风险
处理 UTF-8 字符串要格外小心
以上所有方法都按字节操作,对 UTF-8 编码的中文、emoji 等无效——它们的小写字母判断本身就不成立,而且移动字节会破坏多字节序列,导致乱码。
- 如果输入可能含 UTF-8,先确认需求:是否真要“按 Unicode 小写”移动?那得用 ICU 或
std::wstring_convert(已弃用)等方案转成宽字符再处理 - 更现实的做法是加断言或文档注明:“本函数仅适用于 ASCII 字符串”
- 线上服务中,没做编码校验就直接传 UTF-8 字符串进来,是最常被忽略的隐性故障点


















