如何对包含自定义(即用户定义)对象的向量进行排序呢?
可能需要使用标准STL算法sort和一个谓词(一个函数或函数对象),该谓词将操作自定义对象中的一个字段(作为排序的键)。
我走在正确的道路上吗?
使用std::sort
的一个简单例子
struct MyStruct
{
int key;
std::string stringValue;
MyStruct(int k, const std::string& s) : key(k), stringValue(s) {}
};
struct less_than_key
{
inline bool operator() (const MyStruct& struct1, const MyStruct& struct2)
{
return (struct1.key < struct2.key);
}
};
std::vector < MyStruct > vec;
vec.push_back(MyStruct(4, "test"));
vec.push_back(MyStruct(3, "a"));
vec.push_back(MyStruct(2, "is"));
vec.push_back(MyStruct(1, "this"));
std::sort(vec.begin(), vec.end(), less_than_key());
编辑:正如Kirill V. Lyadvinsky所指出的那样,您可以实现MyStruct
的operator<
,而不是提供一个排序谓词:
struct MyStruct
{
int key;
std::string stringValue;
MyStruct(int k, const std::string& s) : key(k), stringValue(s) {}
bool operator < (const MyStruct& str) const
{
return (key < str.key);
}
};
使用这种方法,您可以按照以下方式简单地对向量进行排序:
std::sort(vec.begin(), vec.end());
编辑2: 正如Kappa所建议的,您还可以通过重载>
运算符并稍微更改sort函数的调用来将向量按降序排序:
struct MyStruct
{
int key;
std::string stringValue;
MyStruct(int k, const std::string& s) : key(k), stringValue(s) {}
bool operator > (const MyStruct& str) const
{
return (key > str.key);
}
};
同时,您应该这样调用排序函数:
std::sort(vec.begin(), vec.end(),greater<MyStruct>());
std::sort(vec.begin(), vec.end(), greater<MyStruct>())
这样既干净又优雅。 - kappa<functional>
才能使用"std::greater"。 - Nick Hartungoperator<
,然后使用 std::sort(vec.begin(), vec.end());
或者 std::sort(vec.rbegin(), vec.rend());
来实现升序或降序排序。 - Pixelchemist为了更全面的覆盖,我提出了一个使用lambda表达式的实现。
C++11
#include <vector>
#include <algorithm>
using namespace std;
vector< MyStruct > values;
sort( values.begin( ), values.end( ), [ ]( const MyStruct& lhs, const MyStruct& rhs )
{
return lhs.key < rhs.key;
});
C++14
#include <vector>
#include <algorithm>
using namespace std;
vector< MyStruct > values;
sort( values.begin( ), values.end( ), [ ]( const auto& lhs, const auto& rhs )
{
return lhs.key < rhs.key;
});
>
代替<
。 - bhaller你可以在调用 std::sort
函数时使用一个函数对象作为第三个参数,或者你可以在你的类中定义 operator<
运算符重载。
struct X {
int x;
bool operator<( const X& val ) const {
return x < val.x;
}
};
struct Xgreater
{
bool operator()( const X& lx, const X& rx ) const {
return lx.x < rx.x;
}
};
int main () {
std::vector<X> my_vec;
// use X::operator< by default
std::sort( my_vec.begin(), my_vec.end() );
// use functor
std::sort( my_vec.begin(), my_vec.end(), Xgreater() );
}
const
? - prongsconst
关键字表示 operator()
函数不会修改 Xgreater
结构体的实例(一般情况下此结构体可能具有成员变量),而仅在输入值前加上 const
表示这些输入值是不可变的。 - schestervector
或其他适用的(可变输入迭代器)范围内的自定义对象类型X
,可以使用各种方法,特别是使用标准库算法如:
由于大多数技术已经发布,以获取X
元素的相对顺序,因此我将从“为什么”和“何时”使用不同方法的一些注释开始。X
对象范围是否是常见或罕见任务(这些范围是否在程序中的多个不同位置或库用户中排序)?X
对象范围进行排序?X
范围是常见任务,并且所获得的排序是预期的(即X
仅包装单个基本值),那么可能会重载operator<
,因为它使排序无需任何模糊(如正确传递适当的比较器)并反复产生预期结果。X
对象,则我会选择Functors(自定义类的重载operator()
函数)或函数指针(例如一个Functor/Function用于词法排序,另一个用于自然排序)。X
的排序范围不常见或不太可能出现在其他上下文中,我倾向于使用lambda而不是使任何命名空间混乱更多函数或类型。operator<
在第一眼看起来就是不透明的,您必须查找定义才能知道将应用哪个排序逻辑。operator<
定义是单点故障,而多个lambda是多个故障点,并且需要更加谨慎。operator<
的定义,编译器可能会强制调用函数进行比较,而不是内联排序逻辑,这可能会带来严重的缺陷(至少当链接时间优化/代码生成未应用时)。class X
的可比性以使用标准库排序算法的方法:std::vector<X> vec_X;
和std::vector<Y> vec_Y;
1. 重载T::operator<(T)
或operator<(T, T)
并使用标准库模板,但不需要一个比较函数。可以重载成员operator<
:struct X {
int i{};
bool operator<(X const &r) const { return i < r.i; }
};
// ...
std::sort(vec_X.begin(), vec_X.end());
或者使用免费的 operator<
:
struct Y {
int j{};
};
bool operator<(Y const &l, Y const &r) { return l.j < r.j; }
// ...
std::sort(vec_Y.begin(), vec_Y.end());
struct X {
int i{};
};
bool X_less(X const &l, X const &r) { return l.i < r.i; }
// ...
std::sort(vec_X.begin(), vec_X.end(), &X_less);
bool operator()(T, T)
重载函数,该函数可作为比较函数传递。struct X {
int i{};
int j{};
};
struct less_X_i
{
bool operator()(X const &l, X const &r) const { return l.i < r.i; }
};
struct less_X_j
{
bool operator()(X const &l, X const &r) const { return l.j < r.j; }
};
// sort by i
std::sort(vec_X.begin(), vec_X.end(), less_X_i{});
// or sort by j
std::sort(vec_X.begin(), vec_X.end(), less_X_j{});
struct less_i
{
template<class T, class U>
bool operator()(T&& l, U&& r) const { return std::forward<T>(l).i < std::forward<U>(r).i; }
};
这可以用于排序任何具有支持<
运算符的成员i
的类型。
struct X {
int i{}, j{};
};
std::sort(vec_X.begin(), vec_X.end(), [](X const &l, X const &r) { return l.i < r.i; });
C++14使得Lambda表达式更加通用:
std::sort(a.begin(), a.end(), [](auto && l, auto && r) { return l.i < r.i; });
这可以被包装在一个宏中。
#define COMPARATOR(code) [](auto && l, auto && r) -> bool { return code ; }
// sort by i
std::sort(v.begin(), v.end(), COMPARATOR(l.i < r.i));
// sort by j
std::sort(v.begin(), v.end(), COMPARATOR(l.j < r.j));
bool X_less(X const &l, X const &r) const { return l.i < r.i; }
作为比较器,但应该删除 const
关键字(因为它不是成员函数)。 - PolGraphicstd::sort
或类似的方法,但需要一个Compare
实例,例如在实例化std::set
时,我该如何使用(4.) lambda方法? - azrdevtemplate<class T, class C> std::set<T, C> make_set(C const& compare) { return std::set<T, C>{ compare }; }
,可以像这样使用:auto xset = make_set<X>([](auto && l, auto && r) { return l.i < r.i; });
。 - Pixelchemist你已经走在了正确的道路上。std::sort
默认会使用operator<
作为比较函数。因此,如果要对你的对象进行排序,你需要重载bool operator<( const T&, const T& )
或提供一个函数对象来执行比较,就像这样:
struct C {
int i;
static bool before( const C& c1, const C& c2 ) { return c1.i < c2.i; }
};
bool operator<( const C& c1, const C& c2 ) { return c1.i > c2.i; }
std::vector<C> values;
std::sort( values.begin(), values.end() ); // uses operator<
std::sort( values.begin(), values.end(), C::before );
使用函数对象的优点是可以使用访问类的私有成员的函数。
operator<
作为类(或结构体)的成员,因为全局的操作符可能会使用受保护或私有的成员。或者你可以将其设置为结构体C的友元函数。 - Kirill V. Lyadvinsky#include <vector>
#include <algorithm>
using namespace std;
struct MyStruct
{
int key;
std::string stringValue;
MyStruct(int k, const std::string& s) : key(k), stringValue(s) {}
};
int main()
{
std::vector < MyStruct > vec;
vec.push_back(MyStruct(4, "test"));
vec.push_back(MyStruct(3, "a"));
vec.push_back(MyStruct(2, "is"));
vec.push_back(MyStruct(1, "this"));
std::sort(vec.begin(), vec.end(),
[] (const MyStruct& struct1, const MyStruct& struct2)
{
return (struct1.key < struct2.key);
}
);
return 0;
}
我很好奇在不同的std::sort函数调用方式之间是否存在可衡量的性能影响,因此我创建了这个简单的测试:
$ cat sort.cpp
#include<algorithm>
#include<iostream>
#include<vector>
#include<chrono>
#define COMPILER_BARRIER() asm volatile("" ::: "memory");
typedef unsigned long int ulint;
using namespace std;
struct S {
int x;
int y;
};
#define BODY { return s1.x*s2.y < s2.x*s1.y; }
bool operator<( const S& s1, const S& s2 ) BODY
bool Sgreater_func( const S& s1, const S& s2 ) BODY
struct Sgreater {
bool operator()( const S& s1, const S& s2 ) const BODY
};
void sort_by_operator(vector<S> & v){
sort(v.begin(), v.end());
}
void sort_by_lambda(vector<S> & v){
sort(v.begin(), v.end(), []( const S& s1, const S& s2 ) BODY );
}
void sort_by_functor(vector<S> &v){
sort(v.begin(), v.end(), Sgreater());
}
void sort_by_function(vector<S> &v){
sort(v.begin(), v.end(), &Sgreater_func);
}
const int N = 10000000;
vector<S> random_vector;
ulint run(void foo(vector<S> &v)){
vector<S> tmp(random_vector);
foo(tmp);
ulint checksum = 0;
for(int i=0;i<tmp.size();++i){
checksum += i *tmp[i].x ^ tmp[i].y;
}
return checksum;
}
void measure(void foo(vector<S> & v)){
ulint check_sum = 0;
// warm up
const int WARMUP_ROUNDS = 3;
const int TEST_ROUNDS = 10;
for(int t=WARMUP_ROUNDS;t--;){
COMPILER_BARRIER();
check_sum += run(foo);
COMPILER_BARRIER();
}
for(int t=TEST_ROUNDS;t--;){
COMPILER_BARRIER();
auto start = std::chrono::high_resolution_clock::now();
COMPILER_BARRIER();
check_sum += run(foo);
COMPILER_BARRIER();
auto end = std::chrono::high_resolution_clock::now();
COMPILER_BARRIER();
auto duration_ns = std::chrono::duration_cast<std::chrono::duration<double>>(end - start).count();
cout << "Took " << duration_ns << "s to complete round" << endl;
}
cout << "Checksum: " << check_sum << endl;
}
#define M(x) \
cout << "Measure " #x " on " << N << " items:" << endl;\
measure(x);
int main(){
random_vector.reserve(N);
for(int i=0;i<N;++i){
random_vector.push_back(S{rand(), rand()});
}
M(sort_by_operator);
M(sort_by_lambda);
M(sort_by_functor);
M(sort_by_function);
return 0;
}
$ g++ -O2 -o sort sort.cpp && ./sort
Measure sort_by_operator on 10000000 items:
Took 0.994285s to complete round
Took 0.990162s to complete round
Took 0.992103s to complete round
Took 0.989638s to complete round
Took 0.98105s to complete round
Took 0.991913s to complete round
Took 0.992176s to complete round
Took 0.981706s to complete round
Took 0.99021s to complete round
Took 0.988841s to complete round
Checksum: 18446656212269526361
Measure sort_by_lambda on 10000000 items:
Took 0.974274s to complete round
Took 0.97298s to complete round
Took 0.964506s to complete round
Took 0.96899s to complete round
Took 0.965773s to complete round
Took 0.96457s to complete round
Took 0.974286s to complete round
Took 0.975524s to complete round
Took 0.966238s to complete round
Took 0.964676s to complete round
Checksum: 18446656212269526361
Measure sort_by_functor on 10000000 items:
Took 0.964359s to complete round
Took 0.979619s to complete round
Took 0.974027s to complete round
Took 0.964671s to complete round
Took 0.964764s to complete round
Took 0.966491s to complete round
Took 0.964706s to complete round
Took 0.965115s to complete round
Took 0.964352s to complete round
Took 0.968954s to complete round
Checksum: 18446656212269526361
Measure sort_by_function on 10000000 items:
Took 1.29942s to complete round
Took 1.3029s to complete round
Took 1.29931s to complete round
Took 1.29946s to complete round
Took 1.29837s to complete round
Took 1.30132s to complete round
Took 1.3023s to complete round
Took 1.30997s to complete round
Took 1.30819s to complete round
Took 1.3003s to complete round
Checksum: 18446656212269526361
看起来除了传递函数指针以外,所有选项都非常相似,而传递函数指针会导致+30%的惩罚。
此外,运算符<版本似乎慢了约1%(我多次重复测试,效果仍然存在),这有点奇怪,因为它表明生成的代码不同(我缺乏分析--save-temps输出的技能)。
class MyClass
{
bool operator <(const MyClass& rhs)
{
return this->key < rhs.key;
}
}
#include <iostream>
#include <compare>
#include <vector>
#include <algorithm>
struct MyInt
{
int value;
MyInt(int val) : value(val) {}
auto operator<=>(const MyInt& other) const = default;
};
int main()
{
MyInt Five(5);
MyInt Two(2);
MyInt Six(6);
std::vector V{Five, Two, Six};
std::sort(V.begin(), V.end());
for (const auto& element : V)
std::cout << element.value << std::endl;
}
输出:
2
5
6
是的,使用第三个参数(函数或对象)的std::sort()
会更容易。例如:http://www.cplusplus.com/reference/algorithm/sort/