一、有序容器、排序函数的对于数据类型的排序要求
??????? 在使用有序容器 map/multimap 和 set/multiset等时,如果是采用常用数据类型(如int、float)作为其key,是能够很好实现的(要么可直接比对,要么标准库已实现),但采用自定义类型结构时,就需要自行提供该类型的比较能力,容器才能基于该类型的key进行有效排序。
??????? 同样的对于排序函数qsort, qsort_s,搜索函数bsearch, bsearch_s等API,用户自定义类型时,也需要自行提供该类型的比较能力。
二、有序容器、排序函数对类型的比较能力设计
??????? 有序容器、排序函数对类型的比较,无非就是满足比较运算数(==、!=、>、<、>=、<=),因此需要类型提供相关的运算符实现就能达到要求。例如,为了实现快速查找,map内部本身就是按序存储的(比如红黑树)。在我们插入<key, value>键值对时,就会按照key的大小顺序进行存储。这是作为key的类型必须能够进行<运算比较的原因。如果string类型作为key,就是按字典顺序规则排序储存的。
????????map是stl里面的一个模板类,map的定义:
template < class Key, class T, class Compare = less<Key>,
class Allocator = allocator<pair<const Key,T> > > class map;
????????第三个参数: class Compare = less<Key> 这是一个class类型的,而且提供了默认值 less<Key>。 less是stl里面的一个函数对象,即调用操作符的类,其对象常称为函数对象(function object),它们是行为类似函数的对象。表现出一个函数的特征,就是通过“对象名+(参数列表)”的方式使用一个 类,其实质是对operator()操作符的重载。less的实现如下:
template <class T> struct less : binary_function <T,T,bool>
{
bool operator() (const T& x, const T& y) const {return x<y;}
};
??????? map指定了less作为其默认比较函数(对象),所以通常如果不自己指定Compare,map中键值对就会按照Key的less顺序进行组织存储,例如string的key,及int的value一个map,就是按照字典顺序输出的,即string的less序列,其真实定义是map<string, int, less<string> > name_less_map。类似地,库还提供与less相对的还有greater,由于其不是默认方式,就缺失需要需要使用时,要显式定义map<string, int, greater<string> > name_more_map。
??????? 那么在实际项目应用,我们常遇到复合结构数据类型作为有序容器的key时,其实现基于以上原理,类似的提供该数据结构的比较函数(对象)即可。假设,现有一个类型,包含两个int型变量,组ID及元ID:
class KeyObj
{
public:
KeyObj(int _gid, int _id)
: m_gid(_gid), m_id(_id)
{
};
private:
int m_gid;
int m_id;
};
??????? 以该KeyObje类型的作为有序容器的key,就需要实现其比较运算数的操作函数:
inline bool operator==(const KeyObj& obj1, const KeyObj& obj2);
inline bool operator!=(const KeyObj& obj1, const KeyObj& obj2);
inline bool operator>=(const KeyObj& obj1, const KeyObj& obj2);
inline bool operator<=(const KeyObj& obj1, const KeyObj& obj2);
inline bool operator>(const KeyObj& obj1, const KeyObj& obj2);
inline bool operator<(const KeyObj& obj1, const KeyObj& obj2);
??????? 而运算操作符的实现就是KeyObj内部比较逻辑的设计了,假如我们明确着这样的规则,如组ID优先于元ID:
class KeyObj
{
public:
KeyObj(int _gid, int _id)
: m_gid(_gid), m_id(_id)
{
};
//
static int cmp_Key(const KeyObj &obj1, const KeyObj &obj2)
{
int diff = obj1.m_gid - obj2.m_gid; //组ID优先于元ID
if (diff != 0) return diff;
diff = obj1.m_id - obj2.m_id; //在组ID一致时,元ID才有比较意义
if (diff != 0) return diff;
return 0;
};
int get_gid() const {return m_gid; };
int get_id() const { return m_id; };
private:
int m_gid;
int m_id;
};
inline bool operator==(const KeyObj& obj1, const KeyObj& obj2) { return KeyObj::cmp_Key(obj1, obj2) == 0; }
inline bool operator!=(const KeyObj& obj1, const KeyObj& obj2) { return KeyObj::cmp_Key(obj1, obj2) != 0; }
inline bool operator>=(const KeyObj& obj1, const KeyObj& obj2) { return KeyObj::cmp_Key(obj1, obj2) >= 0; }
inline bool operator<=(const KeyObj& obj1, const KeyObj& obj2) { return KeyObj::cmp_Key(obj1, obj2) <= 0; }
inline bool operator>(const KeyObj& obj1, const KeyObj& obj2) { return KeyObj::cmp_Key(obj1, obj2) > 0; }
inline bool operator<(const KeyObj& obj1, const KeyObj& obj2) { return KeyObj::cmp_Key(obj1, obj2) < 0; }
??????? 对于qsort, qsort_s来说,
定义于头文件 <stdlib.h>
void qsort( void *ptr, size_t count, size_t size,int (*comp)(const void *, const void *) );
其中参数comp - 比较函数。若首个参数小于第二个,则返回负整数值,若首个参数大于第二个,则返回正整数值,若两参数等价,则返回零。
比较函数的签名应等价于如下形式:
int cmp(const void *a, const void *b);
该函数必须不修改传递给它的对象,而且在调用比较相同对象时必须返回一致的结果,无关乎它们在数组中的位置。
??????? 因此需要再做适应该比较函数签名模式进行调整:
inline int cmp_Key_p(const void* obj1, const void* obj2)
{
const KeyObj obj1_= *(const KeyObj*)obj1;
const KeyObj obj2_= *(const KeyObj*)obj2;
return KeyObj::cmp_Key(obj1_, obj2_);
}
三、测试
???????? 下来,创建一个KeyObj数组,并将这些元素作为key插入到map容器,并调用qsort函数对KeyObj数组进行排序,打印排序结果和map容器顺序输出结果。本文集中在一个test.cpp源文件中实现:
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <map>
class KeyObj
{
public:
KeyObj(int _gid, int _id)
: m_gid(_gid), m_id(_id)
{
};
//
static int cmp_Key(const KeyObj &obj1, const KeyObj &obj2)
{
int diff = obj1.m_gid - obj2.m_gid;
if (diff != 0) return diff;
diff = obj1.m_id - obj2.m_id;
if (diff != 0) return diff;
return 0;
};
int get_gid() const {return m_gid; };
int get_id() const { return m_id; };
private:
int m_gid;
int m_id;
};
inline int cmp_Key_p(const void* obj1, const void* obj2)
{
const KeyObj obj1_= *(const KeyObj*)obj1;
const KeyObj obj2_= *(const KeyObj*)obj2;
return KeyObj::cmp_Key(obj1_, obj2_);
}
//
inline bool operator==(const KeyObj& obj1, const KeyObj& obj2) { return KeyObj::cmp_Key(obj1, obj2) == 0; }
inline bool operator!=(const KeyObj& obj1, const KeyObj& obj2) { return KeyObj::cmp_Key(obj1, obj2) != 0; }
inline bool operator>=(const KeyObj& obj1, const KeyObj& obj2) { return KeyObj::cmp_Key(obj1, obj2) >= 0; }
inline bool operator<=(const KeyObj& obj1, const KeyObj& obj2) { return KeyObj::cmp_Key(obj1, obj2) <= 0; }
inline bool operator>(const KeyObj& obj1, const KeyObj& obj2) { return KeyObj::cmp_Key(obj1, obj2) > 0; }
inline bool operator<(const KeyObj& obj1, const KeyObj& obj2) { return KeyObj::cmp_Key(obj1, obj2) < 0; }
//程序入口
int main(void){
KeyObj ks[5] = {KeyObj(3,1),KeyObj(2,4),KeyObj(1,5),KeyObj(2,2),KeyObj(1,3)};
std::map<KeyObj,int> ms;
for(int i=0; i<5; i++)
{
ms[ks[i]] = i;
}
qsort(ks, 5, sizeof(KeyObj), cmp_Key_p);//数组排序
for(int i=0; i<5; i++)
{
std::cout << "[" <<i<< "]" <<ks[i].get_gid()<< "," << ks[i].get_id() << std::endl;
}
std::map<KeyObj,int>::iterator iter;
int i=0;
for(iter=ms.begin(); iter!=ms.end(); iter++)
{
std::cout << "[" <<i++<< "]" << iter->first.get_gid() << "," <<iter->first.get_id()
<<":"<< iter->second << std::endl;
}
//KeyObj key(1,5);
//KeyObj *res = bsearch(&key, ks, 5, sizeof KeyObj, cmp_Key_p);//搜索函数调用测试
system("read");
return 0;
}
本文编译环境是64bit的centos7下gcc 4.8.2编译,Makefile如下
#/bin/sh
#CX= D:/workForSoftware/MinGW/bin/g++.exe
CX= g++
BIN := ./bin
TARGET := test
FLAGS := -std=c++11
#-DNDEBUG
#-static
SRCDIR := ./src
#INCLUDES
INCLUDEDIR := -I"$(SRCDIR)"
source := $(wildcard $(SRCDIR)/*.cpp $(SRCDIR)/*.c)
$(TARGET) :
$(CX) $(FLAGS) $(INCLUDEDIR) $(source) -o $(BIN)/$(TARGET)
clean:
rm $(BIN)/$(TARGET)
????????测试输出结果:
|