Rule20:为指针的关联容器指定比较类型
有如下情况,有一个string*指针的set,你把一些动物的名字插入set:
set<string*> ssp;
ssp.insert(new string("Anteater"));
ssp.insert(new string("Wombat"));
ssp.insert(new string("Lemur"));
ssp.insert(new string("Penguin"));
然后你遍历set打印出元素的内容,你希望字符串按字母顺序出现,因为set保证他们的内容有序。
for (set<string*>::iterator it = ssp.begin();it!=ssp.end();it++)
{
cout<<*it<<endl;
}
输出的内容并不是字符串的内容,而是字符串的地址值。因为set容纳指针,it的类型不是string,而是string。是一个指针。
为了将字符串打印出来,我们需要使用 **it。这样就转为string类型了,但这并没有改变set的存放顺序,按照指针的值进行存放,而不是按照字符串的比较来存放。
这是输出的结果:
Anteater
Wombat
Lemur
Penguin
所以我们必须手动指定排序的算法。事实上,set< string* >ssp是set< string*,less< string*>>ssp的简写。
如果你想要string*指针以字符串值确定顺序被存储在set中,你不能使用默认比较仿函数类less< string*>。你需要改写为你自己的比较仿函数类。他的对象带有string*指针并按照指向的字符串值来进行排序。
struct StringPtrLess:public binary_function<string*,string*,bool>
{
bool operator()(const string* lhs,const string* rhs) const
{
// 调用 自己的比较函数 返回 bool类型
return *lhs<*rhs;
}
};
set<string*,StringPtrLess> ssp;
ssp.insert(new string("Anteater"));
ssp.insert(new string("Wombat"));
ssp.insert(new string("Lemur"));
ssp.insert(new string("Penguin"));
for (set<string*,StringPtrLess>::iterator it = ssp.begin();it!=ssp.end();it++)
{
cout<<**it<<endl;
}
输出结果如下:
set的第二个参数不是一个函数,它是能在内部用实例化建立函数的一种类型。
无论何时你简历指针的关联容器,注意你得指定容器的比较类型。大多时候,你的比较类型只是解引用指针并比较所指向的对象,对于这种情况,你可以写一个用于那种比较的仿函数模板。
struct DereferenceLess {
template <typename PtrType>
bool operator()(PtrType pT1,PtrType pT2) const
{
return *pT1 < *pT2;
}
};
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 (set<string*,DereferenceLess>::iterator it = ssp.begin();it!=ssp.end();it++)
{
cout<<**it<<endl;
}
Rule22:避免原地修改set和multiset的键
set和multiset保持它们的元素有序,这些容器的正确行为依赖于它们保持有序。如果你改了关联容器中的一个元素的值,新值就会不在正确的位置上,否则会破坏容器的有序性。
对于map和multimap,如果试图改变这些容器里的一个键值将会直接报错。
那是因为map
struct IDNumberLess{
public binary_function<Employee, Employee, bool> {
bool operator()(const Employees lhs,
const Employee& rhs) const
{
return lhs.idNumber() < rhs.idNumber();
}
};
typedef set<Employee, IDNumberLess> EmpIDSet;
EmpIDSet se; // se是雇员的set,
// 按照ID号排序
因为set或multiset里的值不是const,所以试图改变他们可以编译。但是我们需要主要到:你必须确保不改变一个键部分-影响容器有序性的元素部分。如果你做了,就会破坏容器。
还有的时候,在某些编译器上会拒绝你直接修改set的值,因为Iterator*返回的是一个const T值,这种情况下,我们如果想继续进行修改应该怎样做呢?
映射。我们将其映射到一个引用,使用const_cast脱去const限制。
首先看一个简单的例子:
const int a = 5;
int b = const_cast<int&>(a);
b = 3;
和
const int a = 5;
int& b = const_cast<int&>(a);
b = 3;
或者直接:
const_cast<int&>(a)=3;
这两个代码,都使用了const_cast进行去const限制,但是谁真正的能修改const原值呢?答案是后者,使用了引用映射的变量。前者的b相当于一个临时变量,他的改变对a没有任何影响。
所以我们可以这样修改const Iterator的内容。
if(it!=se.end())
{
const_cast<Employee&>(*it).setTitle("Corporate Deity");
}
这可以得到it指向的对象,告诉编译器把映射的结果当成一个非常数Employee的引用。
但是如下的几种代码就不行:
if (i != se.end()){
static_cast<Employee>(*i).setTitle("Corporate Deity");
}
if (i != se.end()) {
((Employee)(*i)).setTitle("Corporate Deity");
}
这两种情况里,映射的结果是一个*i副本的临时匿名对象,而setTitle是在匿名的物体上调用,不在这个*i上,*i没有被修改。
还有一种安全的方法是,将需要进行修改的内容先保存副本,对副本进行修改,删除原始数据,然后在该位置插入修改后的副本值。
EmpIDSet se; // 同前,se是一个以ID号
// 排序的雇员set
Employee selectedID; // 同前,selectedID是一个带有
// 需要ID号的雇员
...
EmpIDSet::iterator i =
se.find(selectedID); // 第一步:找到要改变的元素
if (i!=se.end()){
Employee e(*i); // 第二步:拷贝这个元素
se.erase(i++); // 第三步:删除这个元素;
// 自增这个迭代器以
// 保持它有效(参见条款9)
e.setTitle("Corporate Deity"); // 第四步:修改这个副本
se.insert(i, e); // 第五步:插入新值;提示它的位置
// 和原先元素的一样
}
如果你进行任何容器元素的原地修改,你有责任确保容器保持有序。