一、理解相等和等价的区别

在STL中有很多函数,它们需要确定两个值是否相同,但是这些函数判断的方式有多不同。例如find函数和set::insert。find对“相同”定义为相等,是以operator==为基础的;set::insert对“相同”的定义是等价,是以operator<为基础的。如果要有效地使用STL,必须理解“相等”和“等价”的区别。

相等:如果表达式X==Y为真,则X和Y相等,否则就不等。

等价:如果表达式!(X<Y)&&!(Y<X)为真,则X和Y等价,否则就不等价。这里的含义是:如果两个值中的任何一个(按照一定的排序准则)都不在另一个的前面,那么这两个值(按照这一准则)就是等价的。

一个例子就是考虑一个不区分大小写的set,这个比较函数把STL和Stl看成是等价的。如果set的成员函数find可以查找成功,但是使用非成员函数find,会查找失败。原因是成员函数基于等价(STL等价于Stl)而非成员函数基于相等(STL不等于Stl)。

【Note】:
(1)标准关联容器是基于等价而不是相等的,原因很复杂,总之是为了避免一大堆“若使用两个比较函数(equal_to和operator==)将带来的问题”,从而避免混合使用相等和等价带来的混乱。

二、为包含指针的关联容器指定比较类型

当关联容器中保存的是对象指针时,需要自己定义比较器(不是一个函数,而是一个仿函数模板),不然关联容器会按照指针大小进行排序,而不是指针指向的内容。

#include <bits/stdc++.h>
using namespace std;
//定义函数子类
struct StringPtrLess : public binary_function<const string*, const string*, bool>
{
    bool operator()(const string *ps1, const string *ps2) const
    {
        return *ps1 < *ps2;
    }
};

int main(int argc, char const *argv[])
{
    set<string*, StringPtrLess> ssp;//因为set类型的每一个参数都是类型,所以必须定义函数子而不是函数
    ssp.insert(new string("ABC"));
    ssp.insert(new string("EDF"));
    ssp.insert(new string("GH"));
    for (auto s : ssp)
        cout << *s << endl;
    return 0;
}

三、 永远让比较函数对“相等的值”返回false

在关联容器中,用户自定义比较类型时,当两个元素相等时,应该返回false。举例:建立一个set,比较类型用less_equal,然后插入一个10:

set<int, less_equal<int> > s; // s以“<=”排序

s.insert(10); // 插入10

s.insert(10);//然后再插入一次10

关联容器对“相同”的定义是等价,因此set测试10B是否等价于10A。当执行这个测试时,它自然是使用set的比较函数。在这一例子 里,是operator<=,因为我们指定set的比较函数为less_equal,而less_equal意思就是operator<=。 于是,set将计算这个表达式是否为真:

!(10A <= 10B) && !(10B <= 10A) // 测试10A和10B是否等价

显然,该表达式返回false,于是两个10都会插入这个set,结果是set以拥有了两个为10的值的拷贝而告终,也就是说它不再是一个set了。通过使用less_equal作为我们的比较类型,我们破坏了容器!

四、避免原地修改set和multiset的键

原地修改map和multimap的键值是不允许的,同时,应避免原地修改set和multiset的键(尽管这是允许的),因为这可能影响容器有序性的元素部分,破坏掉容器。

五、考虑用有序vector代替关联容器

标准关联容器通常被实现为平衡的二叉查找树,这意味着关联容器存储一个对象所伴随的空间开销至少是三个指针(父节点,左右儿子节点)。和vector相比,使用关联容器占用了大约两倍的内存。如果操作系统使用了虚拟内存,会导致更多的页面错误。

【Note】:
(1)在排序的vector中存储数据可能比在关联容器中存储同样的数据要耗费更少的内存,考虑到页面错误时,通过二分搜索法来查找一个排序的vector可能比查找一个关联容器更快一些。
(2)但是也要看具体的应用场景,如果程序中存在大量的插入和删除,使用vector显然效率更低,所以查找操作不和插入删除操作混在一起时,才使用排序的vector。

六、在map的insert()和operator[]中仔细选择

插入时,insert效率高;当进行“增加”操作时,operator[]会有三个函数调用:构造临时对象,撤销临时对象和对象复制,而insert不会有。

更新时,[]效率更高,而对于“更新”操作,insert需要构造和析构对象,而operator[] 采用的对象引用,不会有这样的效率损耗。