Part3:关联容器(一)

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);                // 第五步:插入新值;提示它的位置
                        // 和原先元素的一样
}

如果你进行任何容器元素的原地修改,你有责任确保容器保持有序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值