map&set

STL中的map和set底层都是用红黑树实现的关联式容器。两者都是对红黑树做的封装去实现的。

set(集合)
set是一种key结构,key就是value,二者等价,进入set的数据会自动排序。不允许键值重复,无法通过迭代器去改变set的值。

template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set {
public:
  // typedefs:

  typedef Key key_type;
  typedef Key value_type;
  typedef Compare key_compare;
  typedef Compare value_compare;
private:
   typedef rb_tree<key_type, value_type, 
                  identity<value_type>, key_compare, Alloc> rep_type;
  rep_type t;  // red-black tree representing set
  };

通过set的成员可以明显看出,其底层是红黑树,并且kye和value是同一个东西。

看下迭代器部分的声明

public:
  typedef typename rep_type::const_pointer pointer;
  typedef typename rep_type::const_pointer const_pointer;
  typedef typename rep_type::const_reference reference;
  typedef typename rep_type::const_reference const_reference;
  typedef typename rep_type::const_iterator iterator;
  typedef typename rep_type::const_iterator const_iterator;
  typedef typename rep_type::const_reverse_iterator reverse_iterator;
  typedef typename rep_type::const_reverse_iterator const_reverse_iterator;
  typedef typename rep_type::size_type size_type;
  typedef typename rep_type::difference_type difference_type;

set的迭代器是用红黑树的迭代器去封装的,并且是const迭代器,显然通过迭代器去修改set的值是无法实现的。
基本的结构就是这样,下面去认识一些常用的接口。

insert()
提供了三个接口:


//指定元素插入  返回值为pair 第二个参数bool值可以判断元素是否插入成功
pair<iterator,bool> insert (const value_type& val);


//指定位置插入,但位置可能是不合适的,会导致插入失败,插入成功返回新位置的迭代器
iterator insert (iterator position, const value_type& val);


//指定迭代器区间插入
template <class InputIterator>
  void insert (InputIterator first, InputIterator last);

erase()


// 指定位置删除
void erase (iterator position);

//指定元素删除  删除成功返回1
size_type erase (const value_type& val);

//删除一段迭代器区间
void erase (iterator first, iterator last);

set是对红黑树进行封装来实现的,为了能更加深刻认识这点,让我们来看下set的内部实现

// 擦除指定位置的元素, 会导致内部的红黑树重新排列
void erase(iterator position)
{
  typedef typename rep_type::iterator rep_iterator;
  t.erase((rep_iterator&)position);
}

// 会返回擦除元素的个数, 其实就是标识set内原来是否有指定的元素
size_type erase(const key_type& x)
{
  return t.erase(x);
}

// 擦除指定区间的元素, 会导致红黑树有较大变化
void erase(iterator first, iterator last)
{
  typedef typename rep_type::iterator rep_iterator;
  t.erase((rep_iterator&)first, (rep_iterator&)last);
}

可以明显看出,其自身并没有做什么过多的事情。
clean()
清除函数 同样是调用红黑树的clean();
find()
红黑树也进行了实现,直接调用即可

count()
查找指定元素的个数,我们明显可以看出这个接口对于set来说有点鸡肋了,不允许重复它的个数不是0就是1,显然就是存在或不存在。
它存在的意义在于与multiset(允许键值重复)相对应。

其他操作函数

// 返回小于当前元素的第一个可插入的位置
iterator lower_bound(const key_type& x) const
{
  return t.lower_bound(x);
}

// 返回大于当前元素的第一个可插入的位置
iterator upper_bound(const key_type& x) const
{
  return t.upper_bound(x);
}
// 返回与指定键值相等的元素区间
pair<iterator,iterator> equal_range(const key_type& x) const
{
  return t.equal_range(x);
}

map (kv结构)
map的增删查改与set是类似的,这里就不多做赘述了,map着重认识一个运算符重载函数 operator[];

map是kv结构,也就是一个key值对应一个value.显然可以用这个结构去统计次数。
给定下列的场景,统计水果出现的次数。
方案1:

#include <iostream>
#include <map>
#include <string>

using namespace std;

int main()
{
    string fruit[] = { "apple", "pair", "banana",
        "apple", "pair",
        "apple" };

    map<string, int> countmap;
    for (size_t i = 0; i < 6; ++i)
    {
        map<string, int>::iterator ret = countmap.find(fruit[i]);
        if (ret != countmap.end())
        {
            ret->second++;
            //(*ret).second++;  
        }
        else
        {
            countmap.insert(pair<string, int>(fruit[i], 1));
        }
    }

    return 0;
}

这里写图片描述
第一种方法先查找,然后判断,在直接++,否则插入。

方案2:

#include <iostream>
#include <map>
#include <string>

using namespace std;

int main()
{
    string fruit[] = { "apple", "pair", "banana",
        "apple", "pair",
        "apple" };

    map<string, int> countmap;
    for (size_t i = 0; i < 6; ++i)
    {
        pair<map<string, int>::iterator, bool> ret = countmap.insert(make_pair(fruit[i], 1));
        if (ret.second == false)
        {
            ++(ret.first->second);
        }
    }
    return 0;
}

方案利用了map不允许键值重复的特性,如果重复,那么返回指向出现该键值的迭代器,和为false的bool值两者组成的pair。

方案三:

#include <iostream>
#include <map>
#include <string>

using namespace std;

int main()
{
    string fruit[] = { "apple", "pair", "banana",
        "apple", "pair",
        "apple" };

    map<string, int> countmap;
    for (size_t i = 0; i < 6; ++i)
    {
        countmap[fruit[i]]++;
    }

    return 0;
}

方案三用一句代码搞定了之前的所有事情,它呢用了我们今天的主角,operator[];
operator[]究竟做了什么,让我们来看下operator[]的实现.

T& operator[](const key_type& k)
 {

   return (*((insert(value_type(k, T()))).first)).second;

  }

stl的实现显然很有水准,让我们分开来看
insert( value_type( k, T() ) ) 这部分先记为V
先调用插入函数,插入的是一个用key值以及T()的默认构造初始化的value;
return (* ( (V).first ) ).second;
然后取出V.first 即是指向元素的迭代器(指向value_field 的指针)
接着呢对调用迭代器的 operator* 会取出 value_field pair<key,value>
最后拿到pair.second 即value

分析玩代码,可以看出,这就相当于一个方案二的变种。
operator[] 会拿到当前key值对应的value,并且还是value的引用,显然就意味着你可以通过调用 operator[] 对当前key值的value进行修改。

备注:
multimap 显然是不会实现operator[] 的,因为无法确定拿出key值对应的那个value。

猜你喜欢

转载自blog.csdn.net/nuyexiaoxiang/article/details/78505567