这两天闲来无事,分析了下标准库sort算法的实现,一整理才发现大有学问.然后这里总结一下,分享所得。
包含文件: algorithm
命名空间: std
原型:
// order[first, last), using operator <
template<classRan> void sort(Ran first, Ran last)
// order[first, last), using Cmp
template<classRan, class Cmp> void sort(Ran first, Ran last, Cmp cmp)
一:
先看第一个,我们很容易想到它的一些用法,比如:
int nArray[] = {2, 4, 5, 1, 3};
sort(nArray, nArray+5);
for (auto it=begin(nArray);it!=end(nArray); it++)
{
cout<<*it<<"\t";
}
//Result: 1 2 3 4 5
同理,float型,double型数组也是一样的。
但是如果我们自定义了一种数据类型,比如个人信息,那又如何排序呢?
typedef struct PersonInfomation
{
int _age;
string _name;
string _phone_no;
string _home_address;
}PInfo, *PPInfo;
这就需要我们对该结构体重载operator<了。因为我们排序时会涉及到同类型对象的比较,所以要定义一个小于操作符来说明两个PInfo对象时如何比较的。
typedef struct PersonInfomation
{
int _age;
string _name;
string _phone_no;
string _home_address;
// 假定我们以年龄作为排序依据
bool operator<(constPersonInfomation& other)
{
return(this->_age<other._age);
}
}PInfo, *PPInfo;
进一步的我们可以从sort实现的源码分析,为什么要重载小于操作符。
我这里分析的是vs2012里面sort的实现部分,话说这个sort的排序时根据排序规模依次用了快速排序,堆排序和插入排序。考虑到代码可读性且为了便于说明,这里我就用自己写的冒泡排序当做分析说明了,虽然实现不一样,但要讲的核心是一样的。
template<class Ran>
voidsort(Ranfirst, Ran last)
{
for (Ran fi=first; fi!=last; fi++)
{
for (Ran fj=fi+1; fj!=last; fj++)
{
if (*fj < *fi)
{
std::swap(*fi, *fj);
}
}
}
}
其中Ran代表随机访问迭代器,可以看做是一个对象的指针,这里可以看到里面有个if判断句,对两个对象进行比较,所以在此如果我们传入的first和last是PersonInfomation的对象指针,则需要重载PersonInfomation的小于操作符。至此我们可以对PersonInfomation进行排序了。
下面是示例代码:
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
usingnamespace std;
typedefstruct PersonInfomation
{
public:
PersonInfomation(int age, string name, string phone_no, string home_address)
: _age(age),
_name(name),
_phone_no(phone_no),
_home_address(home_address)
{
}
int _age;
string _name;
string _phone_no;
string _home_address;
bool operator<(PersonInfomation& a)
{
return this->_age<a._age;
}
}PInfo,*PPInfo;
int_tmain(int argc, _TCHAR* argv[])
{
PersonInfomation Lucy(19, "Lucy", "123456", "American");
PersonInfomation Lily(20, "Lily", "135876", "Candian");
PersonInfomation Turner(18, "Turner", "8607969", "China");
vector<PersonInfomation> vtPerson;
vtPerson.push_back(Lucy);
vtPerson.push_back(Lily);
vtPerson.push_back(Turner);
sort(vtPerson.begin(),vtPerson.end()); // 排序后会发现vtPerson里面的PInfo按年龄由小到大排好了。
return 0;
}
二:
这时可能有人会说了,PersonInfomation里面那么多项,我不想用_age作为比较项,而选用其它项或其它多个项组合比较,那该怎么办呢?
这里就可以用到sort的带Cmp的形式了。
那如何写呢?我们先想一下,Cmp的原型会是什么样呢?我先把自己实现的sort源码写出来大家看一下。
template<class Ran, class Cmp>
voidsort(Ranfirst, Ran last, Cmp cmp)
{
for (Ran fi=first; fi!=last; fi++)
{
for (Ran fj=fi+1; fj!=last; fj++)
{
if ( cmp(*fj < *fi) )
{
std::swap(*fi, *fj);
}
}
}
}
大家很快发现多了个cmp,然后比较部分变成了cmp(*fj, *fi)。所以我们猜测Cmp是一个返回bool类型的,带两个参数的函数对象或函数指针。而这个Cmp被称作谓词(Pred),即sort函数排序时,告诉sort如何排。下面看一下Cmp的示例代码:
// 按年龄排序
boolcmp_age(const PInfo& left, const PInfo& right)
{
return (left._age<right._age);
}
// 名字排序
boolcmp_name(const PInfo& left, const PInfo& right)
{
return (left._name.compare(right._name)<0);
}
那现在我们想按名字进行排序,那我们就可以进行如下调用了:
// 按名字排序
sort(vtPerson.begin(),vtPerson.end(), cmp_name);
// 按年龄排序
sort(vtPerson.begin(),vtPerson.end(), cmp_age);
完整的示例代码如下:
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
usingnamespace std;
typedefstruct PersonInfomation
{
public:
PersonInfomation(int age, string name, string phone_no, string home_address)
: _age(age),
_name(name),
_phone_no(phone_no),
_home_address(home_address)
{
}
int _age;
string _name;
string _phone_no;
string _home_address;
bool operator<(PersonInfomation& a)
{
return this->_age<a._age;
}
}PInfo,*PPInfo;
// 按年龄排序
boolcmp_age(const PInfo& left, const PInfo& right)
{
return (left._age<right._age);
}
// 名字排序
boolcmp_name(const PInfo& left, const PInfo& right)
{
return (left._name.compare(right._name)<0);
}
int_tmain(int argc, _TCHAR* argv[])
{
PersonInfomation Lucy(19, "Lucy", "123456", "American");
PersonInfomation Lily(20, "Lily", "135876", "Candian");
PersonInfomation Turner(18, "Turner", "8607969", "China");
vector<PersonInfomation> vtPerson;
vtPerson.push_back(Lucy);
vtPerson.push_back(Lily);
vtPerson.push_back(Turner);
// 按名字排序
sort(vtPerson.begin(),vtPerson.end(), cmp_name);
// 按年龄排序
sort(vtPerson.begin(),vtPerson.end(), cmp_age);
return 0;
}
/* Result:
Sort before:
Lucy 19 123456 American
Lily 20 135876 Candian
Turner 18 8607969 China
Sort by age:
Turner 18 8607969 China
Lucy 19 123456 American
Lily 20 135876 Candian
Sort by name:
Lily 20 135876 Candian
Lucy 19 123456 American
Turner 18 8607969 China
*/
这时你可能会说,每次难道Cmp都要手写一次函数,然后实现么?那好麻烦啊……当然,现在vs2012已经支持了C++ 11标准的lambda表达式,所以在vs2012下面可以像如下一样写代码:
sort(begin(vtPI), end(vtPI),
[&](const PersonInfomation& left, const PersonInfomation& right)->bool
{
return (left._age<right._age);
});
sort(begin(vtPI), end(vtPI),
[&](const PersonInfomation& left, const PersonInfomation& right)->bool
{
return (left._name<right._name);
});
对于初学C++的人可能比较难以理解什么是函数对象,如何运用这些Cmp来方便的编程呢?下面简单介绍下什么是谓词,什么是函数对象:
函数对象就是重载了应用运算符的对象,即 operator()().举个例子
template<typename T>
classAdd
{
public:
Add(){} // 默认构造函数
T operator()(T a, T b) // 重载应用运算符
{
return a+b;
}
};
Add<int> plus;
int a = plus(10, 20); // 函数对象调用
可以看到,声明了一个Add<int>的plus对象,这个对象可以像函数一样去调用。它和函数指针不同的地方在于,函数对象是可以带状态的,即可以构造的时候传入参数。
谓词就是返回bool的函数对象(或者函数).
具体想进一步了解,则可以翻看《The C++ Programming Language》,C++之父Bjarne Stroustrup的经典之作.