Effective STL 章节(四)
——50条有效使用STL的经验(25/50)
关联容器专项
文章目录
第十九条、理解相等(equality)和等价(equivalence)的区别
直接举例:
test1 == test2 // 相等
!(test1 < test2) && !(test2 < test1) // 等价
这一条的意义是提示你,在使用非关联容器时,请使用他们自带的比较函数,而不是直接==
判断
因为他们是用“等价”来作为两个元素是否相同的判据的
第二十条、为包含指针的关联容器指定比较类型
考虑如下代码
std::set<std::string*> datas;
datas.insert(new std::string("Anteater"));
datas.insert(new std::string("Wombat"));
datas.insert(new std::string("Lemur"));
datas.insert(new std::string("Penguin"));
再输出这些元素,大概率是没法输出正确字符的
问题就出在set储存这个指针时,需要进行比较,调用了一个比较函数,这个函数是不合适的
因为存的是指针,所以set在排序时使用的是std::string*
的大小顺序,而不是std::string
代表的字符
解决方法就是自己构建一个比较函数,用std::sting
比较,而不是指针,并传给set
auto func = [](const string * p1, const string * p2)
{
return *p1 < *p2;
}
set<string *, func> datas;
... // do something
也就是说,当容器保存的是指针时,请注意要为它指定比较类型
第二十一条、总是让比较函数在等值情况下返回false
细节不用太了解,只需要知道在定义比较函数时遵守:
bool func(T a, T b)
{
return a < b;
// return a > b;
}
能看到,如果a=b,不管怎么样都是false,这是为了保护标准关联容器
比如set中已经有了10,再次插入10,使用a<=b
判断会怎么样
!(10 <= 10) && !(10 <= 10)
!(true) && !(true)
false && false
false
也就是说,这个10和set中的10是不一样的,那么就会导致set有两个10,那么set也就不是set了
第二十二条、切勿直接修改set或multiset中的键
set和multiset会按照元素的键值对元素排序,如果修改了键值,那么排序是否依然正确就没法保证了
所以不要直接改它的键值
如果必须修改这个元素的键值,怎么办?
对其拷贝,再拷贝上修改,再插入拷贝,同时删除原本的元素
auto i = set.find("a");
if(i != set.end())
{
AClass temp(*i); // 拷贝原本的元素
temp.setKey("b"); // 在拷贝中修改
set.erase(i ++); // 先删除原本的元素,并且递增迭代器,保证有效性
set.insert(temp); // 插入拷贝
}
总之不能在set里面直接改元素键值
第二十三条、考虑用排序的vector替代关联容器
在需要搜索工作的环境中,有时候排序的vector容器搜索比关联容器更快
假设有巨大的数据量,vector能够保证内存连续,而关联容器则会把内存散落各地
这就可能导致vector的搜索反而比关联容器快(具体可以搜C++内存对齐问题)
但是!仅在你知道使用环境中,查找工作和插入删除工作几乎不在一起时,才适合
不然vector的插入删除和重排序在这里是毫无好处的
第二十四条、当效率至关重要时,请在map::operator[]与map::insert之间做出谨慎选择
在map的使用中有:
map<int, int> m;
m[1] = 5;
这个意思是,将元素5插入map中键值为1的位置,如果已经有了该元素,就更新为5,如果没有,就插入
在插入时,它实际上是调用了三个函数:构造{int, int}
、插入、赋值
这样明显不如直接调用insert函数来的快,省去析构函数和赋值函数的调用
但是在更新时,则是反过来的,所以
当对map进行插入时,请使用insert函数
当对map进行更新时,请使用[]赋值
第二十五条、熟悉非标准的散列容器
散列容器指的是以哈希表为基础的容器,这本书比较老了,现在C++11往后已经有了散列容器
如unordered_set unordered_map等,在从零开始手写STL库中也有实现