C++ sort排序函数用法详解

前言


std::sort定义在<algorithm>中,是 C++ 最常用的排序算法。它的接口只有两个核心参数——首迭代器和尾后迭代器——再加一个可选的比较器。看似简单,但围绕它有大量误用:比较器写成<=、对std::list调用、以为排序稳定、在比较器里修改元素。这些错误有的直接编译失败,有的则是未定义行为(undefined behavior, UB),运行时才以奇怪的方式暴露。


先把几个事实说清楚。std::sort要求随机访问迭代器,因此std::vector、std::array、普通数组、std::deque可以用,std::list、std::forward_list不行(它们有成员函数sort)。它不保证稳定,需要稳定排序用std::stable_sort。它的比较次数是 O(N log N),但具体实现(内省排序、混合算法)由标准库决定,标准不规定。比较器必须满足严格弱序(strict weak ordering),违反它是 UB,不是“结果可能不理想”。


本文覆盖基本用法、自定义比较器、结构体多关键字排序、稳定性与部分排序,并给出常见错误的正确写法。示例以 C++17 为基准。


一、基本用法


最简单的形式是对一个区间升序排序:


// 适用于 C++17
#include <algorithm>
#include <array>
#include <iostream>
#include <vector>

int main()
{
std::vector<int> v{5, 2, 9, 1, 5, 6};
std::sort(v.begin(), v.end()); // 升序

std::array<int, 4> a{4, 3, 2, 1};
std::sort(a.begin(), a.end()); // 对 std::array 同样适用

int raw[5] = {5, 4, 3, 2, 1};
std::sort(raw, raw + 5); // 普通数组用指针作为迭代器

for (int x : v) {
std::cout << x << ' ';
}
std::cout << '\n';
return 0;
}

first与last构成左闭右开区间[first, last),last是尾后位置,不参与排序。对空区间或只有一个元素的区间调用sort是合法的,什么也不做。


降序有两种常见写法:


// 适用于 C++17
#include <algorithm>
#include <functional>
#include <vector>

void sort_desc(std::vector<int>& v)
{
std::sort(v.begin(), v.end(), std::greater<int>{});
// 或:std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
}

std::greater<int>在<functional>中。C++14 起可以写std::greater<>{},让模板实参由调用时推导,省去重复写类型。


二、比较器的语义


比较器comp(a, b)返回true表示“a 应该排在 b 前面”。它必须满足严格弱序,可以概括为四条:



  1. 对任意 a,comp(a, a)为false(非自反);

  2. 若comp(a, b)为真,则comp(b, a)为假(非对称);

  3. 若comp(a, b)且comp(b, c),则comp(a, c)(传递);

  4. 不可比较的元素之间满足传递性(等价关系传递)。


写成a <= b违反第 1 条:comp(a, a)会返回true。标准库的排序实现依赖“小于”语义做划分和二分,<=会让它把相等元素来回搬运,甚至越界访问。这是 UB,不是“排序结果不稳定”这么轻。


// 适用于 C++17
#include <algorithm>
#include <vector>

void wrong(std::vector<int>& v)
{
std::sort(v.begin(), v.end(),
[](int a, int b) { return a <= b; }); // UB
}

void right(std::vector<int>& v)
{
std::sort(v.begin(), v.end(),
[](int a, int b) { return a < b; }); // 严格弱序
}

比较器还应当是纯函数:不修改被比较的元素,不依赖会在排序过程中变化的外部状态。如果比较器读取一个全局计数器并在内部自增,排序过程中元素间的相对顺序会随调用次序改变,同样破坏严格弱序。


三、结构体与多关键字排序


对结构体排序,比较器要明确“主键、次键”:


// 适用于 C++17
#include <algorithm>
#include <iostream>
#include <string>
#include <tuple>
#include <vector>

struct Person {
std::string name;
int age;
double score;
};

int main()
{
std::vector<Person> people{
{"Ann", 30, 88.5},
{"Bob", 25, 92.0},
{"Cid", 30, 75.0},
{"Dan", 25, 92.0}
};

// 按年龄升序,年龄相同按分数降序,再按姓名升序
std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) {
if (a.age != b.age) {
return a.age < b.age;
}
if (a.score != b.score) {
return a.score > b.score;
}
return a.name < b.name;
});

// 等价的简洁写法:std::tie 构造可比较的元组
std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) {
return std::tie(a.age, b.name) < std::tie(b.age, b.name);
});

for (const auto& p : people) {
std::cout << p.name << ' ' << p.age << '\n';
}
return 0;
}

std::tie生成左值引用组成的tuple,tuple的比较是按元素字典序进行的,因此std::tie(a.age, a.name) < std::tie(b.age, b.name)等价于“先比年龄,再比姓名”。注意不能把“降序字段”和“升序字段”混在一个tie里,因为tuple的每个元素都按同一种方向比较;混合方向时还是手写分支更清楚。


如果结构体本身定义了operator<,可以直接调用无比较器版本:


// 适用于 C++17
struct Point {
int x;
int y;
};

bool operator<(const Point& a, const Point& b)
{
return a.x != b.x ? a.x < b.x : a.y < b.y;
}

四、稳定性、部分排序与容器选择


稳定性


std::sort不保证相等元素的相对顺序。如果业务要求“先按分数排,再按姓名排,且同分者保持原有顺序”,应使用std::stable_sort:


// 适用于 C++17
#include <algorithm>
#include <string>
#include <vector>

struct Record {
std::string name;
int score;
};

void sort_stable(std::vector<Record>& v)
{
std::stable_sort(v.begin(), v.end(),
[](const Record& a, const Record& b) {
return a.score > b.score;
});
}

stable_sort的比较次数同样是 O(N log N),但可能需要额外的临时存储;具体实现和内存用量由标准库决定,标准不规定。


部分排序


只需要前 K 个元素有序时,不必对整个区间排序:


// 适用于 C++17
#include <algorithm>
#include <cstddef>
#include <vector>

void top_k(std::vector<int>& v, std::size_t k)
{
if (k < v.size()) {
std::partial_sort(v.begin(), v.begin() + static_cast<std::ptrdiff_t>(k), v.end());
}
}

partial_sort(first, middle, last)之后,[first, middle)是整体最小的 K 个元素且已排序,[middle, last)的顺序未指定。如果只关心“第 K 小是谁”而不关心前面是否有序,用std::nth_element,平均复杂度是线性的。


容器选择




容器/区间能否用std::sort替代方案



std::vector可以无

std::array可以无

普通数组可以(指针作迭代器)无

std::deque可以无

std::list不可以(非随机访问)成员sort()

std::forward_list不可以成员sort()

std::map/std::set不需要插入时自动有序



对std::list调用std::sort会在编译期报错,因为它的迭代器不满足随机访问要求。std::list::sort是成员函数,稳定,复杂度 O(N log N),并且只重新链接节点,不移动元素。


常见坑点



  1. ❌ 比较器写return a <= b;。


✅ 必须是严格弱序,用a < b;<=会让comp(a, a)为真,属于 UB。



  1. ❌ 比较器依赖并在内部修改外部状态。


✅ 比较器应是纯函数;需要附加信息就作为参数传入,不要在比较过程中改变它。



  1. ❌ 对std::list或std::forward_list调用std::sort。


✅ 用它们的成员sort();std::sort要求随机访问迭代器。



  1. ❌ 认为std::sort稳定,依赖相等元素的原有顺序。


✅ 需要稳定用std::stable_sort;std::sort只保证有序,不保证相等元素的相对次序。



  1. ❌ 在比较器里用a.score - b.score的符号做判断,整数相减可能溢出。


✅ 写成a.score < b.score或a.score > b.score,不要用减法制造溢出风险。



  1. ❌ 对含 NaN 的浮点数组直接用<排序。


✅ NaN 与任何值比较都为假,破坏严格弱序。先把 NaN 过滤或定义明确的排序规则。



  1. ❌ 排序后继续使用指向原元素的迭代器,以为它还指向原来的值。


✅std::sort会重排元素;迭代器本身仍有效(对vector不重新分配),但指向的位置内容已变。



  1. ❌ 只对前 K 个元素排序却调用了完整的std::sort。


✅ 用std::partial_sort或std::nth_element,避免做多余的工作。


总结




用法写法说明



升序std::sort(v.begin(), v.end())默认operator<

降序std::sort(v.begin(), v.end(), std::greater<>{})需要<functional>

自定义std::sort(begin, end, comp)comp必须严格弱序

稳定std::stable_sort(...)保留相等元素相对顺序

前 K 个std::partial_sort(first, middle, last)只保证前段有序

第 K 小std::nth_element(...)平均线性

链表lst.sort()成员函数,稳定



std::sort的难点不在调用,而在比较器:只要保证它满足严格弱序、不修改元素、不依赖变化的状态,剩下的交给标准库即可。需要稳定就用stable_sort,需要部分有序就用partial_sort,不要把不同需求都压给同一个函数。




相关新闻

Java Web网上作业批改系统(Servlet+JSP+MySQL)

Java Web网上作业批改系统(Servlet+JSP+MySQL)

简介&#xff1a;这是一套基于Java Web技术栈开发的网上作业批改系统完整源码&#xff0c;面向高校计算机专业课程设计、毕业设计及Java初学者实践项目。系统采用B/S架构&#xff0c;后端以JavaServlet实现业务逻辑&#xff0c;前端使用JSPCSSJavaScript构建交互界面&#xff0…

2026/10/21 4:43:52 阅读更多 →
插件化知识工作流:从收藏到行动的一体化实践

插件化知识工作流:从收藏到行动的一体化实践

知识工作者最头疼的从来不是“找不到工具”&#xff0c;而是工具太多、信息太碎、流程断档。你收藏了一堆好文章&#xff0c;最后只是在收藏夹里吃灰&#xff1b;你记了一堆笔记&#xff0c;回顾时发现根本想不起来当时为什么存。我自己折腾了很久之后&#xff0c;得出一个结论…

2026/10/21 4:42:51 阅读更多 →
AI辅助学术专著:分离研究层与表达层的高效写作方法

AI辅助学术专著:分离研究层与表达层的高效写作方法

前阵子我带着某高校的一个科研团队完成了《某领域智能评估方法与应用》这本约15万字的学术专著。整本书从定框架到交付排版稿&#xff0c;用了不到两个月。很多人听到这个周期&#xff0c;第一反应是“是不是用了AI代写&#xff1f;”我的回答很直接&#xff1a;AI确实深度参与…

2026/10/21 4:42:51 阅读更多 →

最新新闻

代码托管平台访问慢?从DNS到资源加载的全链路优化指南

代码托管平台访问慢?从DNS到资源加载的全链路优化指南

1. 为什么打开代码托管平台会慢&#xff1a;先搞懂瓶颈在哪很多人一遇到页面转圈&#xff0c;第一反应就是“网不行”&#xff0c;然后开始重启路由器、切换网络&#xff0c;折腾半天发现该慢还是慢。我刚开始也这样&#xff0c;后来才意识到&#xff0c;打开这类海外代码托管平…

2026/10/21 5:28:10 阅读更多 →
PHP性能调优实战:Xdebug与QCacheGrind定位慢接口分析

PHP性能调优实战:Xdebug与QCacheGrind定位慢接口分析

做PHP开发这几年&#xff0c;我最大的体会是&#xff1a;性能问题不是靠猜&#xff0c;而是要靠数据说话。一个接口从20ms变成200ms&#xff0c;一行SQL的事&#xff1b;一个页面响应慢到让人抓狂&#xff0c;可能只是循环里不小心嵌套了一次查询。但难的不是修&#xff0c;而是…

2026/10/21 5:28:10 阅读更多 →
Agent零基础实操指南:用低代码搭一个能干的数字分身

Agent零基础实操指南:用低代码搭一个能干的数字分身

1. 为什么现在都在聊Agent&#xff1f;先搞清楚它到底解决了什么问题我接触过不少刚工作一两年的职场新人&#xff0c;也包括还在上学但已经开始实习的同学&#xff0c;大家挂在嘴边的一句话是&#xff1a;“事情太多太杂&#xff0c;精力不够用。”写周报、整理会议纪要、找文…

2026/10/21 5:28:10 阅读更多 →
ARM Linux嵌入式libiconv构建与字符集转换实战指南

ARM Linux嵌入式libiconv构建与字符集转换实战指南

简介&#xff1a;本资源是专为ARM架构Linux嵌入式系统定制的libiconv字符编码转换库&#xff0c;面向嵌入式开发工程师、交叉编译实践者及需在树莓派、ARM网关等设备上处理多语言文本&#xff08;如UTF-8/GB2312/ASCII互转&#xff09;的技术人员。资源通过arm-none-linux-gnue…

2026/10/21 5:28:10 阅读更多 →
降AI率有套路!3步把论文AIGC检测率从80%降到5%

降AI率有套路!3步把论文AIGC检测率从80%降到5%

又到交论文的季节了&#xff0c;后台收到好多私信&#xff0c;问的最多的就是“AI率太高怎么办”。说实话&#xff0c;我看到不少同学用AI辅助写完后&#xff0c;一检测&#xff0c;AI率直奔80%。这个数字确实吓人&#xff0c;但别慌&#xff0c;这题我能解。这篇就把我自己实操…

2026/10/21 5:28:10 阅读更多 →
在线字典学习MATLAB实现:稀疏编码与字典更新实战指南

在线字典学习MATLAB实现:稀疏编码与字典更新实战指南

简介&#xff1a;在线字典学习代码是一套基于MATLAB的机器学习实现资源&#xff0c;面向处理文本、音频等序列数据的开发者与研究者。代码演示了在线学习与字典学习的结合&#xff1a;通过逐样本或小批量方式更新模型参数&#xff0c;在节省内存的同时适应数据流场景&#xff0…

2026/10/21 5:27:10 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/21 4:27:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →