条款19:理解相等和等价的区别
在关联容器set/multiset, map/multimap中,有一个模板参数用来确定容器元素的排序规则,使用的是标准款less,而在set, map等只能保留唯一的元素中,是以刚才的排序规则来定义等价的。
模板类map形式如下:
std::map
template < class Key, // map::key_type
class T, // map::mapped_type
class Compare = less<Key>, // map::key_compare
class Alloc = allocator<pair<const Key,T> > // map::allocator_type
> class map;
标准模板类set形式如下:
std::set
template < class T, // set::key_type/value_type
class Compare = less<T>, // set::key_compare/value_compare
class Alloc = allocator<T> // set::allocator_type
> class set;
等价按照如下定义,通俗的说就是a不在b前面,b也不再a前面,但是跟相等不是一回事,相等时直接用operator==比较,两个容器的元素可能等价,但是不一定相等
!comp(a,b) && !comp(b,a))
一个例子,字符串之间以不区分大小写进行比较的一个实现(可能有点Low,进行了字符串拷贝,这不是重点忽略)
// string不区分大小写的比较
struct CIStringCompare : public std::binary_function<string, string, bool> {
bool operator()(const string &lhs, const string &rhs) const {
string lstr(lhs);
std::transform(lhs.cbegin(), lhs.cend(), lstr.begin(), tolower);
string rstr(rhs);
std::transform(rhs.cbegin(), rhs.cend(), rstr.begin(), tolower);
return lstr < rstr;
}
};
void test_19() {
set<string, CIStringCompare> ciss; // 不区分大小写的字符串集合
ciss.insert("Persephone"); // 显然,这两个字符串不相等,但是根据我们对排序规则的定义,它们是等价的
ciss.insert("persephone");
std::for_each(ciss.cbegin(), ciss.cend(), [](const string &str) { std::cout << str << std::endl; });
return;
}
条款20:为包含指针的关联容器指定比较类型
假设定义一个包含string*的set容器,我们依次插入一些动物的名字到该集合中,然后逐一打印,如下所示,没错,你打印了个寂寞,*Iter是取元素,也就是string*,打印字符串还需要加上*,取指针的内容噻
void test_20() {
set<string *> ssp;
ssp.insert(new string("anteater"));
ssp.insert(new string("wombat"));
ssp.insert(new string("lemur"));
ssp.insert(new string("penguin"));
for(auto iter = ssp.cbegin(); iter != ssp.cend(); ++iter) {
std::cout << *iter << std::endl;
}
return;
}
输出:
0x3a5170
0x3a5220
0x3a52d0
0x3a5380
修改后正常了
std::cout << **iter << std::endl;
输出:
anteater
wombat
lemur
penguin
以上都不是重点,你注意到没,怎么字符串没有按照规则排序呢,因为容器元素是指针,less默认对指针排序了,有字符串什么事,因此引出本条款的核心,你应该为指针写出自己的比较类型,如下所示,正确了。
// string*比较
struct StrPtrCompare : public std::binary_function<string *, string *, bool> {
bool operator()(const string *lhs, const string *rhs) const {
return *lhs < *rhs;
}
};
void test_20() {
set<string *, StrPtrCompare> ssp;
ssp.insert(new string("anteater"));
ssp.insert(new string("wombat"));
ssp.insert(new string("lemur"));
ssp.insert(new string("penguin"));
for (auto iter = ssp.cbegin(); iter != ssp.cend(); ++iter) {
std::cout << **iter << std::endl;
}
return;
}
输出:
anteater
lemur
penguin
wombat
等会,还没完呢,我们竟然直接new string插入数组,呵呵,你是想自己管理内存然后一个个delete释放吗,不,C++11给我们提供了智能指针
// std::shared_ptr<string>比较
struct SharedPtrCompare : public std::binary_function<std::shared_ptr<string>, std::shared_ptr<string>, bool> {
bool operator()(const std::shared_ptr<string> &lhs, const std::shared_ptr<string> &rhs) const {
return *lhs < *rhs;
}
};
void test_20() {
// 智能指针
set<std::shared_ptr<string>, SharedPtrCompare> sSharedp;
sSharedp.insert(std::make_shared<string>("anteater"));
sSharedp.insert(std::make_shared<string>("wombat"));
sSharedp.insert(std::make_shared<string>("lemur"));
sSharedp.insert(std::make_shared<string>("penguin"));
for (auto iter = sSharedp.cbegin(); iter != sSharedp.cend(); ++iter) {
std::cout << **iter << std::endl;
}
return;
}
输出:
anteater
lemur
penguin
wombat
最后,定义一个模板他不香吗,为啥要为每个类型都定义一个比较类型
// 为指针、智能指针定义的通用比较模板类
struct DereferenceLess {
template<typename PtrType>
bool operator()(PtrType p1, PtrType p2) const {
return *p1 < *p2;
}
};
void test_20() {
// 普通指针
// set<string *, StrPtrCompare> ssp;
set<string *, DereferenceLess> ssp;
ssp.insert(new string("anteater"));
ssp.insert(new string("wombat"));
ssp.insert(new string("lemur"));
ssp.insert(new string("penguin"));
for (auto iter = ssp.cbegin(); iter != ssp.cend(); ++iter) {
std::cout << **iter << std::endl;
}
// 智能指针
// set<std::shared_ptr<string>, SharedPtrCompare> sSharedp;
set<std::shared_ptr<string>, DereferenceLess> sSharedp;
sSharedp.insert(std::make_shared<string>("anteater"));
sSharedp.insert(std::make_shared<string>("wombat"));
sSharedp.insert(std::make_shared<string>("lemur"));
sSharedp.insert(std::make_shared<string>("penguin"));
for (auto iter = sSharedp.cbegin(); iter != sSharedp.cend(); ++iter) {
std::cout << **iter << std::endl;
}
return;
}
输出:
anteater
lemur
penguin
wombat
anteater
lemur
penguin
wombat
参考:《Effective STL中文版》第3章