Loading

C++ STL 系列——关联容器(Map、Multimap、Set、MultiSet)

一、什么是关联容器

关联容器存储的元素,是由一个个“键值对”(<key, value>)组成。通过键,往往能很快的检索到对应的值。

关联容器可以快速查找、读取或者删除所存储的元素,同时该类型的容器插入元素的效率比序列容器高。

STL 标准库在实现关联式容器时,底层选用红黑树来组织和存储各键值对。

1.1 关联式容器种类

关联式容器名称 特点
map 定义在 <map> 头文件中,使用该容器存储的数据,其各个元素的键必须是唯一的(即不能重复),该容器会根据各元素键的大小,默认进行升序排序(调用 std::less<T>)。
set 定义在 <set> 头文件中,使用该容器存储的数据,各个元素键和值完全相同,且各个元素的值不能重复(保证了各元素键的唯一性)。该容器会自动根据各个元素的键(其实也就是元素值)的大小进行升序排序(调用 std::less<T>)。
multimap 定义在 <map> 头文件中,和 map 容器唯一的不同在于,multimap 容器中存储元素的键可以重复。
multiset 定义在 <set> 头文件中,和 set 容器唯一的不同在于,multiset 容器中存储元素的值可以重复(一旦值重复,则意味着键也是重复的)。

C++ 11 还新增了 4 种哈希容器,即 unordered_map、unordered_multimap 以及 unordered_set、unordered_multiset。严格来说,它们也属于关联式容器。哈希容器底层采用的是哈希表。

1.2 自定义关联容器的排序规则

  • 使用函数对象自定义排序规则

    //定义函数对象类,也可以用 struct
    class cmp {
    public:
        // 重载 () 运算符
        bool operator ()(const string &a,const string &b) {
            // 按照字符串的长度,做升序排序(即存储的字符串从短到长)
            return (a.length() < b.length());
        }
    };
    int main() {
        //创建 set 容器,并使用自定义的 cmp 排序规则
        std::set<string, cmp>myset{"http://c.biancheng.net/stl/",
                                   "http://c.biancheng.net/python/",
                                   "http://c.biancheng.net/java/"};
    }
    

    注意:按照字符串长度做升序排序,因此无法存储具有相同长度的多个字符串。

  • 重载关系运算符实现自定义排序
    C++ STL 标准库适用于关联式容器的排序规则

    排序规则 功能
    std::less 底层采用 < 运算符实现升序排序,各关联式容器默认采用的排序规则。
    std::greater 底层采用 > 运算符实现降序排序,同样适用于各个关联式容器。
    std::less_equal 底层采用 <= 运算符实现升序排序,多用于 multimap 和 multiset 容器。
    std::greater_equal 底层采用 >= 运算符实现降序排序,多用于 multimap 和 multiset 容器。
    这些排序规则,其底层也是采用函数对象的方式实现的。以 std::less 为例,其底层实现为:
    template <typename T>
    struct less {
        //定义新的排序规则
        bool operator()(const T &_lhs, const T &_rhs) const {
            return _lhs < _rhs;
        }
    }
    

    当关联式容器中存储的数据类型为自定义的结构体变量或者类对象时,通过对现有排序规则中所用的关系运算符进行重载,也能实现自定义排序规则的目的。注意,当关联式容器中存储的元素类型为结构体指针变量或者类的指针对象时,只能使用函数对象的方式自定义排序规则,此方法不再适用。

    //自定义类
    class myString {
    public:
        // 定义构造函数,向 myset 容器中添加元素时会用到
        myString(string tempStr) :str(tempStr) {};
        //获取 str 私有对象,由于会被私有对象调用,因此该成员方法也必须为 const 类型
        string getStr() const;
    private:
        string str;
    };
    
    string myString::getStr() const{
        return this->str;
    }
    
    // 重载 < 运算符,参数必须都为 const 类型
    bool operator <(const myString &stra, const myString & strb) {
        //  以字符串的长度为标准比较大小
        return stra.getStr().length() < strb.getStr().length();
    }
    
     int main() {
        // 创建空 set 容器,仍使用默认的 less<T> 排序规则
        std::set<myString>myset;
        // 向 set 容器添加元素,这里会调用 myString 类的构造函数
        myset.emplace("http://c.biancheng.net/stl/");
        myset.emplace("http://c.biancheng.net/c/");
        myset.emplace("http://c.biancheng.net/python/");
        // 会按照字符串长度排序
    }
    

    上面程序以全局函数的形式实现对 < 运算符的重载,还可以使用成员函数或者友元函数的形式实现。其中,当以成员函数的方式重载 < 运算符时,该成员函数必须声明为 const 类型,且参数也必须为 const 类型。至于参数的传值方式是采用按引用传递还是按值传递,都可以(建议采用按引用传递,效率更高)。同样,如果以友元函数的方式重载 < 运算符时,要求参数必须使用 const 修饰。

1.3 修改关联式容器中键值对的键

对于如何修改容器中某个键值对的键,所有关联式容器可以采用同一种解决思路,即先删除该键值对,然后再向容器中添加修改之后的新键值对。

map 和 multimap 容器只能采用“先删除,再添加”的方式修改某个键值对的键。原因很简单,C++ STL 标准中明确规定,map 和 multimap 容器用于存储类型为 pair<const K, V> 的键值对。

C++ STL 标准中并没有用 const 限定 set 和 multiset 容器中存储元素的类型。换句话说,对于 set 或者 multiset 类型的容器,其存储元素的类型是 T 而不是 const T。所以从语法的角度分析,可以直接修改容器中元素的值,但不要修改元素的键,C++ STL 标准也不允许用户借助迭代器来直接修改 set 或者 multiset 容器中的元素。

如何才能正确修改 set 或 multiset 容器中的元素?

最直接的方式就是借助 const_cast 运算符,该运算符可以去掉指针或者引用的 const 限定符。

set<student>::iterator iter = mymap.begin();
const_cast<student&>(*iter).setname("xiaoming");

map 和 multimap 容器中元素的键是无法直接修改的,但借助 const_cast,我们可以直接修改 set 和 multiset 容器中元素的非键部分。

#include<iostream>
#include<set>
#include<string>

using namespace std;

class student {
public:
  student(string name, int id, int age) :name(name), id(id), age(age) {}
  const int& getid() const {
    return id;
  }
  
  void setname(const string name){
    this->name = name;
  }
  
  string getname() const{
    return name;
  }

  void setage(int age){
    this->age = age;
  }

  int getage() const{
    return age;
  }

  void display()const {
    cout << id << " " << name << " " << age << endl;
  }

private:
  string name;
  int id;
  int age;
};

//自定义 myset 容器的排序规则
class cmp {
public:
  bool operator ()(const student &stua, const student &stub) {
    // 按照字符串的长度,做升序排序(即存储的字符串从短到长)
    return stua.getid() < stub.getid();
  }
};

int main() {
  set<student, cmp> mymap{ {"zhangsan",10,20},{"lisi",20,21},{"wangwu",15,19} };
  set<student>::iterator iter = mymap.begin();
  // 直接将 {"zhangsan",10,20} 中的 "zhangsan" 修改为 "xiaoming"
  const_cast<student&>(*iter).setname("xiaoming");
  while (iter != mymap.end()) {
    (*iter).display();
    ++iter;
  }
  return 0;
}

二、pair

考虑到“键值对”并不是普通类型数据,C++ STL 标准库提供了 pair 类模板,其专门用来将 2 个普通元素 first 和 second(可以是 C++ 基本数据类型、结构体、类自定的类型)创建成一个键值对形式的新元素 <first, second>。

pair 类模板定义在 <utility> 头文件中。

pair 类模板构造函数:

// 1. 默认构造函数
pair();

// 2. 直接使用 2 个元素初始化成 pair 对象
pair(const first_type& a, const second_type& b);

// 3. 拷贝(复制)构造函数,即借助另一个 pair 对象,创建新的 pair 对象
template<class U, class V> pair(const pair<U,V>& pr);

// 在 C++ 11 标准中,在引入右值引用的基础上,pair 类模板中又增添了如下 2 个构造函数:
// 4. 移动构造函数
template<class U, class V> pair(pair<U,V>&& pr);

// 5. 使用右值引用参数,创建 pair 对象
template<class U, class V> pair(U&& a, V&& b);

// 6. C++11 标准新加,很少用到
pair(piecewise_construct_t pwc, tuple<Args1...> first_args, tuple<Args2...> second_args);

具体用法:

#include <iostream>
#include <utility>
#include <string>

using namespace std;

int main() {
    // 调用构造函数 1,也就是默认构造函数
    pair <string, double> pair1;
    // 调用第 2 种构造函数
    pair <string, string> pair2("1234","1234");
    // 调用拷贝构造函数
    pair <string, string> pair3(pair2);
    //调用移动构造函数
    pair <string, string> pair4(make_pair("2345", "2345"));
    // 调用第 5 种构造函数
    pair <string, string> pair5(string("3456"), string("3456"));

在创建 pair4 对象时,调用了 make_pair() 函数,它是 头文件提供的,其功能是生成一个 pair 对象。当将 make_pair() 函数的返回值(是一个临时对象)作为参数传递给 pair() 构造函数时,其调用的是移动构造函数,而不是拷贝构造函数。

C++ 11 还允许我们手动为 pair1 对象赋值,比如:

pair1.first = "4567";
pair1.second = "4567";
cout << "new pair1: " << pair1.first << " " << pair1.second << endl;

同时,上面程序中 pair4 对象的创建过程,还可以写入如下形式,它们是完全等价的:

pair <string, string> pair4 = make_pair("2345", "2345");

头文件中除了提供创建 pair 对象的方法之外,还为 pair 对象重载了 <、<=、>、>=、==、!= 这 6 的运算符,其运算规则是:对于进行比较的 2 个 pair 对象,先比较 pair.first 元素的大小,如果相等则继续比较 pair.second 元素的大小。

pair 类模板还提供有一个 swap() 成员函数,能够互换 2 个 pair 对象的键值对,其操作成功的前提是这 2 个 pair 对象的键和值的类型要相同。

pair <string, int> pair1("pair", 10);
pair <string, int> pair2("pair2", 20);
pair1.swap(pair2);

三、map 容器

  • map 容器存储的都是 pair 对象,也就是用 pair 类模板创建的键值对。其中,各个键值对的键和值可以是任意数据类型,包括 C++ 基本数据类型(int、double 等)、使用结构体或类自定义的类型。
  • 通常 map 容器中存储的键值对选用 string 字符串作为键的类型,同时该容器会自动根据各键值对的键的大小,按照既定规则排序,默认选用 std::less<T>
  • map 存储的各个键值对,键的值既不能重复也不能被修改。存储的都是 pair<const K, T> 类型。

map 容器的模板定义如下:

template <class key,                                    // 指定键(key)的类型
          class T,                                      // 指定值(value)的类型
          class Compare = less<Key>,                    // 指定排序规则
          class Alloc = allocator<pair<const Key,T>>    // 指定分配器对象的类型,几乎不会用到
          > class map;

3.1 创建 map 容器的几种方法

  • 调用 map 容器类的默认构造函数,创建出一个空的 map 容器
    std::map<std::string, int> myMap;     // 常用
    
  • 创建 map 容器的同时初始化
    std::map<std::string, int> myMap{{{"C 语言教程",10}, {"STL 教程",20}};
    std::map<std::string, int>myMap{std::make_pair("C 语言教程",10),std::make_pair("STL 教程",20)};
    
  • 利用已有的 map 容器,创建新的 map 容器
    std::map<std::string, int>newMap(myMap);              // 调用复制构造函数
    
    // C++ 11 为 map 容器添加了移动构造函数,当有临时的 map 对象作为参数,传递给要初始化的 map 容器时,会调用移动构造函数
    std::map<std::string, int> disMap(){
        std::map<std::string, int> tempMapp{ {"C 语言教程",10},{"STL 教程",20} };
        return tempMap;
    }
    
    std::map<std::string, int> newMap(disMap());          // 调用移动构造函数
    
  • 根据已有 map 容器指定区域创建并初始化新的 map 容器
    std::map<std::string, int>myMap{ {"C 语言教程",10},{"STL 教程",20} };
    std::map<std::string, int>newMap(++myMap.begin(), myMap.end());
    
  • 修改排序规则
    std::map<std::string, int>myMap{ {"C 语言教程",10},{"STL 教程",20} };
    std::map<std::string, int, std::less<std::string> >myMap{ {"C 语言教程",10},{"STL 教程",20} };
    std::map<std::string, int, std::greater<std::string> >myMap{ {"C 语言教程",10},{"STL 教程",20} };     // 改为降序排序
    

3.2 map 容器成员方法

成员方法 功能
begin() 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 map 容器用const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
find(key) 在 map 容器中查找键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和end() 方法一样的迭代器。另外,如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(key) 返回一个指向当前 map 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(key) 返回一个指向当前 map 容器中第一个大于 key 的键值对的迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(key) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为key 的键值对(map 容器键值对唯一,因此该范围最多包含一个键值对)。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前 map 容器中存有键值对的个数。
max_size() 返回 map 容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。
operator[] map 容器重载了 [] 运算符,只要知道 map 容器中某个键值对的键的值,就可以向获取数组中元素那样,通过键直接获取对应的值。
at(key) 找到 map 容器中 key 键对应的值,如果找不到,该函数会引发 out_of_range 异常。
insert() 向 map 容器中插入键值对。
erase() 删除 map 容器指定位置、指定键(key)值或者指定区域内的键值对。
swap() 交换 2 个 map 容器中存储的键值对,这意味着,操作的 2 个键值对的类型必须相同。
clear() 清空 map 容器中所有的键值对,即使 map 容器的 size() 为 0。
emplace() 在当前 map 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。
emplace_hint() 在本质上和 emplace() 在 map 容器中构造新键值对的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示键值对生成位置的迭代器,并作为该方法的第一个参数。
count(key) 在当前 map 容器中,查找键为 key 的键值对的个数并返回。注意,由于 map 容器中各键值对的键的值是唯一的,因此该函数的返回值最大为 1。

3.3 map 容器迭代器

C++ STL 标准库为 map 容器配备的是双向迭代器(bidirectional iterator)。这意味着,map 容器迭代器只能进行 ++p、p++、--p、p--、*p 操作,并且迭代器之间只能使用 == 或者 != 运算符进行比较。

成员方法 功能
begin() 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和begin() 结合使用。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 map 容器用const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
find(key) 在 map 容器中查找键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和end() 方法一样的迭代器。另外,如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(key) 返回一个指向当前 map 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(key) 返回一个指向当前 map 容器中第一个大于 key 的键值对的迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(key) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为key 的键值对(map 容器键值对唯一,因此该范围最多包含一个键值对)。
// 1. 遍历 map 容器
//创建并初始化 map 容器
std::map<std::string, std::string>myMap{ {"STL 教程","http://c.biancheng.net/stl/"},{"C 语言教程","http://c.biancheng.net/c/"} };

//调用 begin()/end() 组合,遍历 map 容器
for (auto iter = myMap.begin(); iter != myMap.end(); ++iter)
    cout << iter->first << " " << iter->second << endl;

// 2. 查找指定 key 值的键值对,
//查找键为 "Java 教程" 的键值对
auto iter = myMap.find("Java 教程");
// 从 iter 开始,遍历 map 容器
for(; iter != myMap.end(); ++iter)
    cout << iter->first << " " <<  iter->second << endl;

同时,map 类模板中还提供有 lower_bound(key) 和 upper_bound(key) 成员方法,它们的功能是类似的,唯一的区别在于:

  • lower_bound(key) 返回的是指向第一个键不小于 key 的键值对的迭代器;
  • upper_bound(key) 返回的是指向第一个键大于 key 的键值对的迭代器;

3.4 map 获取键对应值的几种方法

map 容器存储的各个键值对,其键的值都是唯一的,因此指定键对应的值最多有 1 个。

  • [] 运算符
    //创建并初始化 map 容器
    std::map<std::string, std::string>myMap{ 
        {"STL 教程","http://c.biancheng.net/stl/"},
        {"C 语言教程","http://c.biancheng.net/c/"},
        {"Java 教程","http://c.biancheng.net/java/"} };
    string cValue = myMap["C 语言教程"];
    cout << cValue << endl;
    
    当容器中没有包含该指定键的键值对,使用 [] 运算符将不再访问容器中的元素,而是向该容器添加一个键值对。
  • at() 函数,查找失败时,不会添加新的键值对,直接抛 out_of_range 异常
    //创建并初始化 map 容器
      std::map<std::string, std::string>myMap{ {"STL 教程","http://c.biancheng.net/stl/"},
                                               {"C 语言教程","http://c.biancheng.net/c/"},
                                               {"Java 教程","http://c.biancheng.net/java/"} };
      cout << myMap.at("C 语言教程") << endl;
      cout << myMap.at("Python 教程") << endl;    // out_of_range 异常
    

3.5 map insert() 插入数据

当操作对象为 map 容器中已存储的键值对时,则借助 [ ] 运算符,既可以获取指定键对应的值,还能对指定键对应的值进行修改;反之,若 map 容器内部没有存储以 [ ] 运算符内指定数据为键的键值对,则使用 [ ] 运算符会向当前 map 容器中添加一个新的键值对。

实际上,除了使用 [ ] 运算符实现向 map 容器中添加新键值对外,map 类模板中还提供有 insert() 成员方法,该方法专门用来向 map
容器中插入新的键值对。

  • 不指定插入位置,直接添加

    //1、引用传递一个键值对
    pair<iterator,bool> insert (const value_type& val);
    //2、以右值引用的方式传递键值对
    template <class P>
    pair<iterator,bool> insert (P&& val);     // ,val 参数表示键值对变量
    

    如果成功插入 val,则该迭代器指向新插入的 val,bool 值为 true;
    如果插入 val 失败,则表明当前 map 容器中存有和 val 的键相同的键值对(用 p 表示),此时返回的迭代器指向 p,bool 值为 false。

    以上 2 种语法格式的区别在于传递参数的方式不同,即无论是局部定义的键值对变量还是全局定义的键值对变量,都采用普通引用传递的方式;而对于临时的键值对变量,则以右值引用的方式传参。

    // 创建空的 map 容器
    std::map<string, string> mymap;
    
    // 创建一个真实存在的键值对变量
    std::pair<string, string> STL = { "STL 教程","http://c.biancheng.net/stl/" };
    
    // 创建一个接收 insert() 方法返回值的 pair 对象
    std::pair<std::map<string, string>::iterator, bool> ret;
    
    // 插入 STL,由于 STL 并不是临时变量,因此会以第一种方式传参
    ret = mymap.insert(STL);
    
    //以右值引用的方式传递临时的键值对变量
    ret = mymap.insert({ "C 语言教程","http://c.biancheng.net/c/" });
    //调用 pair 类模板的构造函数
    ret = mymap.insert(pair<string,string>{ "C 语言教程","http://c.biancheng.net/c/" });  // 等价
    //调用 make_pair() 函数
    ret = mymap.insert(make_pair("C 语言教程", "http://c.biancheng.net/c/"));             // 等价
    
  • 向指定位置插入新键值对

    // 以普通引用的方式传递 val 参数
    iterator insert (const_iterator position, const value_type& val);
    // 以右值引用的方式传递 val 键值对参数
    template <class P>
    iterator insert (const_iterator position, P&& val);
    

    返回迭代器,而不是 pair 对象

    • 插入成功:返回指向 map 容器中已插入键值对的迭代器
    • 插入失败:指向 map 容器中和 val 具有相同键的键值对
    //创建一个空 map 容器
    std::map<string, string> mymap;
    
    // 创建一个真实存在的键值对变量
    std::pair<string, string> STL = { "STL 教程","http://c.biancheng.net/stl/" };
    //指定要插入的位置
    std::map<string, string>::iterator it = mymap.begin();
    // 向 it 位置以普通引用的方式插入 STL
    auto iter1 = mymap.insert(it, STL);
    cout << iter1->first << " " << iter1->second << endl;
    
    // 向 it 位置以右值引用的方式插入临时键值对
    auto iter2 = mymap.insert(it, std::pair<string, string>("C 语言教程", "http://c.biancheng.net/c/"));
    
    //插入失败样例
    auto iter3 = mymap.insert(it, std::pair<string, string>("STL 教程", "http://c.biancheng.net/java/"));
    

    即便指定了新键值对的插入位置,map 容器仍会对存储的键值对进行排序。

  • 向当前 map 容器中插入其它 map 容器指定区域内的所有键值对

    template <class InputIterator>
    void insert (InputIterator first, InputIterator last);
    
    //创建并初始化 map 容器
    std::map<std::string, std::string>mymap{  {"STL 教程","http://c.biancheng.net/stl/"},
                                              {"C 语言教程","http://c.biancheng.net/c/"},
                                              {"Java 教程","http://c.biancheng.net/java/"} };
    
    //创建一个空 map 容器
    std::map<std::string, std::string>copymap;
    
    //指定插入区域
    std::map<string, string>::iterator first = ++mymap.begin();
    std::map<string, string>::iterator last = mymap.end();
    //将<first,last>区域内的键值对插入到 copymap 中
    copymap.insert(first, last);
    
  • 一次向 map 容器中插入多个键值对

    //创建空的 map 容器
    std::map<std::string, std::string>mymap;
    
    //向 mymap 容器中添加 3 个键值对
    mymap.insert({ {"STL 教程", "http://c.biancheng.net/stl/"},
                   { "C 语言教程","http://c.biancheng.net/c/" },
                   { "Java 教程","http://c.biancheng.net/java/" } });
    

除了 insert() 方法,map 类模板还提供 emplace() 和 emplace_hint() 方法,它们也可以完成向 map 容器中插入键值对的操作,且效率还会 insert() 方法高。

3.6 operator[]和 insert()效率对比

当实现“向 map 容器中添加新键值对元素”的操作时,insert() 成员方法的执行效率更高;而在实现“更新 map 容器指定键值对的值”的操作时,operator[ ] 的效率更高。

向 map 容器中增添元素,insert()效率更高

// 向 mymap 容器添加新的键值对元素
mymap["STL 教程"] = "http://c.biancheng.net/java/";

mymap["STL 教程"] 实际上是 mymap.operator[ ](“STL 教程”) 的缩写。此时并没有键“STL教程”对应的 value 值,这种情况会默认构造一个 string 对象,将其作为键对应的值,然后再将 "http://c.biancheng.net.java/" 赋值给这个 string 对象。

也就是说这行代码的执行效率,等价于

typedef map<string, string> mstr;

//创建要添加的默认键值对元素
pair<mstr::iterator, bool>res = mymap.insert(mstr::value_type("STL 教程", string()));

//将新键值对的值赋值为指定的值
res.first->second = "http://c.biancheng.net/java/";

可以看到,使用 operator[ ] 添加新键值对元素的流程是,先构造一个有默认值的键值对,然后再为其 value 赋值。

而 insert 方法省略了创建临时 string 对象的过程以及析构该对象的过程,同时还省略了调用 string 类重载的赋值运算符。

mymap.insert(mstr::value_type("STL 教程", "http://c.biancheng.net/java/"));

更新 map 容器中的键值对,operator[]效率更高

// operator[]
mymap["STL 教程"] = "http://c.biancheng.net/stl/";
// insert()
std::pair<string, string> STL = { "Java 教程","http://c.biancheng.net/python/" };
mymap.insert(STL).first->second = "http://c.biancheng.net/java/";

insert() 方法在进行更新操作之前,需要有一个 pair 类型(也就是 map::value_type 类型)元素做参数。这意味着,该方法要多构造一个 pair 对象(附带要构造 2 个 string 对象),并且事后还要析构此 pair 对象(附带 2 个 string 对象的析构)。

operator[ ] 就不需要使用 pair 对象,自然不需要构造(并析构)任何 pair 对象或者 string 对象。因此,对于更新已经存储在 map 容器中键值对的值,应优先使用 operator[ ] 方法。

3.7 emplace()和 emplace_hint()方法

实现相同的插入操作,无论是用 emplace() 还是 emplace_hont(),都比 insert() 方法的效率高

语法格式(只有一种):

template <class... Args>
pair<iterator,bool> emplace (Args&&... args);

参数 (Args&&... args) 指的是,这里只需要将创建新键值对所需的数据作为参数直接传入即可,此方法可以自行利用这些数据构建出指定的键值对。另外,该方法的返回值也是一个 pair 对象,其中 pair.first 为一个迭代器,pair.second 为一个 bool 类型变量:

  • 当该方法将键值对成功插入到 map 容器中时,其返回的迭代器指向该新插入的键值对,同时 bool 变量的值为 true;
  • 当插入失败时,则表明 map 容器中存在具有相同键的键值对,此时返回的迭代器指向此具有相同键的键值对,同时 bool 变量的值为 false。
// 创建并初始化 map 容器
std::map<string, string>mymap;

// 插入键值对
pair<map<string, string>::iterator, bool> ret = mymap.emplace("STL 教程", "http://c.biancheng.net/stl/");

// 插入新键值对
ret = mymap.emplace("C 语言教程", "http://c.biancheng.net/c/");

// 失败插入的样例
ret = mymap.emplace("STL 教程", "http://c.biancheng.net/java/");

emplace_hint() 方法的功能和 emplace() 类似,其语法格式如下:

template <class... Args>
iterator emplace_hint (const_iterator position, Args&&... args);

与 emplace 方法的不同点

  • 该方法不仅要传入创建键值对所需要的数据,还需要传入一个迭代器作为第一个参数,指明要插入的位置(新键值对键会插入到该迭代器指向的键值对的前面);
  • 该方法的返回值是一个迭代器,而不再是 pair 对象。当成功插入新键值对时,返回的迭代器指向新插入的键值对;反之,如果插入失败,则表明 map 容器中存有相同键的键值对,返回的迭代器就指向这个键值对。
// 创建并初始化 map 容器
std::map<string, string>mymap;

// 指定在 map 容器插入键值对
map<string, string>::iterator iter = mymap.emplace_hint(mymap.begin(),"STL 教程", "http://c.biancheng.net/stl/");

iter = mymap.emplace_hint(mymap.begin(), "C 语言教程", "http://c.biancheng.net/c/");

// 插入失败样例
iter = mymap.emplace_hint(mymap.begin(), "STL 教程", "http://c.biancheng.net/java/");

注意,和 insert() 方法一样,虽然 emplace_hint() 方法指定了插入键值对的位置,但 map 容器为了保持存储键值对的有序状态,可能会移动其位置。

emplace() 和 emplace_hint() 的执行效率会比 insert() 高的原因:

  • 使用 insert() 向 map 容器中插入键值对的过程是,先创建该键值对,然后再将该键值对复制或者移动到 map 容器中的指定位置;
  • 使用 emplace() 或 emplace_hint() 插入键值对的过程是,直接在 map 容器中的指定位置构造该键值对。

也就是说,向 map 容器中插入键值对时,emplace() 和 emplace_hint() 方法都省略了移动键值对的过程,因此执行效率更高。

四、multimap 容器

multimap 容器用于存储 pair<const K, T> 类型的键值对(其中 K 表示键的类型,T 表示值的类型),其中各个键值对的键的值不能做修改;并且,该容器也会自行根据键的大小对存储的所有键值对做排序操作。和 map 容器的区别在于,multimap 容器中可以同时存储多(≥2)个键相同的键值对。

multimap 容器的类模板也定义在 <map> 头文件。

multimap 容器类模板的定义:

template < class Key,                                     // 指定键(key)的类型
           class T,                                       // 指定值(value)的类型
           class Compare = less<Key>,                     // 指定排序规则
           class Alloc = allocator<pair<const Key,T> >    // 指定分配器对象的类型
> class multimap;

4.1 创建 multimap 容器

  • 创建空的 multimap 容器
    std::multimap<std::string, std::string> mymultimap;
    
  • 创建的同时初始化
    // 底层会先将每一个{key, value} 创建成 pair 类型键值对,然后再初始化 multimap 容器
    multimap<string, string>mymultimap{ {"C 语言教程", "http://c.biancheng.net/c/"},
                                        {"Python 教程", "http://c.biancheng.net/python/"},
                                        {"STL 教程", "http://c.biancheng.net/stl/"} };
    
    // 借助 pair 类模板的构造函数来生成各个 pair 类型的键值对
    multimap<string, string>mymultimap{
        pair<string,string>{"C 语言教程", "http://c.biancheng.net/c/"},
        pair<string,string>{ "Python 教程", "http://c.biancheng.net/python/"},
        pair<string,string>{ "STL 教程", "http://c.biancheng.net/stl/"}
    };
    
    // 调用 make_pair() 函数,生成键值对元素
    // 创建并初始化 multimap 容器
    multimap<string, string>mymultimap{
        make_pair("C 语言教程", "http://c.biancheng.net/c/"),
        make_pair("Python 教程", "http://c.biancheng.net/python/"),
        make_pair("STL 教程", "http://c.biancheng.net/stl/")
    };
    
  • 调用复制构造函数
    // 复制构造函数
    multimap<string, string> newmultimap(mymultimap);
    
    // 调用移动构造函数
    // 创建一个会返回临时 multimap 对象的函数
    // 由于 dismultimap() 函数返回的 tempmultimap 容器是一个临时对象,因此在实现初始化 newmultimap 容器时,底层调用的是 multimap 容器的移动构造函数,而不再是拷贝构造函数。
    
    multimap<string, string> dismultimap() {
        multimap<string, string>tempmultimap{ {"C 语言教程", "http://c.biancheng.net/c/"},
                                              {"Python 教程", "http://c.biancheng.net/python/"} };
        return tempmultimap;
    }
    
  • 从已有 multimap 容器选定区域初始化新 multimap 容器
    // 创建并初始化 multimap 容器
    multimap<string, string>mymultimap{ {"C 语言教程", "http://c.biancheng.net/c/"},
                                        {"Python 教程", "http://c.biancheng.net/python/"},
                                        {"STL 教程", "http://c.biancheng.net/stl/"} };
    multimap<string, string>newmultimap(++mymultimap.begin(), mymultimap.end());
    
  • 指定排序规则
    multimap<char, int>mymultimap{ {'a',1},{'b',2} };
    multimap<char, int, std::less<char>>mymultimap{ {'a',1},{'b',2} };      // 升序
    multimap<char, int, std::greater<char>>mymultimap{ {'a',1},{'b',2} };   // 降序
    

4.2 成员方法

成员方法 功能
begin() 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
find(key) 在 multimap 容器中查找首个键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(key) 返回一个指向当前 multimap 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 multimap 容器用const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(key) 返回一个指向当前 multimap 容器中第一个大于 key 的键值对的迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(key) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为key 的键值对。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前 multimap 容器中存有键值对的个数。
max_size() 返回 multimap 容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。
insert() 向 multimap 容器中插入键值对。
erase() 删除 multimap 容器指定位置、指定键(key)值或者指定区域内的键值对。
swap() 交换 2 个 multimap 容器中存储的键值对,这意味着,操作的 2 个键值对的类型必须相同。
clear() 清空 multimap 容器中所有的键值对,使 multimap 容器的 size() 为 0。
emplace() 在当前 multimap 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。
emplace_hint() 在本质上和 emplace() 在 multimap 容器中构造新键值对的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示键值对生成位置的迭代器,并作为该方法的第一个参数。
count(key) 在当前 multimap 容器中,查找键为 key 的键值对的个数并返回。
  • 和 map 容器相比,multimap 未提供 at() 成员方法,也没有重载 [] 运算符。这意味着,map 容器中通过指定键获取指定指定键值对的方式,将不再适用于 multimap 容器。因为 multimap 容器中指定的键可能对应多个键值对。
  • 由于 multimap 容器可存储多个具有相同的键值对,因此 lower_bound()、upper_bound()、equal_range() 以及 count() 成员方法会经常用到。

五、set 容器

和 map、multimap 容器不同,使用 set 容器存储的各个键值对,要求键 key 和值 value 必须相等。

举个例子,如下有 2 组键值对数据:

{<'a', 1>, <'b', 2>, <'c', 3>}            // set 不可以存储
{<'a', 'a'>, <'b', 'b'>, <'c', 'c'>}      // set 可以存储
  • set 容器只需要为其提供各键值对中的 value 值(也就是 key 的值)即可。仍以存储上面第 2 组键值对为例,只需要为 set 容器提供 {'a','b','c'} ,该容器即可成功将它们存储起来。
  • 使用 set 容器存储的各个元素的值必须各不相同。
  • set 容器中的元素可以修改,但 C++ 标准对此进行了限制,,最正确的修改 set 容器中元素值的做法是:先删除该元素,然后再添加一个修改后的元素。
#include <set>

using namespace std;

// set 容器的类模板定义
template < class T,               // 键 key 和值 value 的类型
    class Compare = less<T>,      // 指定 set 容器内部的排序规则
    class Alloc = allocator<T>    // 指定分配器对象的类型
    > class set;

5.1 创建 set 容器

  • 默认构造函数,创建空的 set 容器
    std::set<std::string> myset;
    
  • 创建时初始化
    std::set<std::string> myset{"http://c.biancheng.net/java/",
                                "http://c.biancheng.net/stl/",
                                "http://c.biancheng.net/python/"};
    
  • 拷贝构造函数,通过已有 set 容器创建
    std::set<std::string> copyset(myset);
    // 等同于
    std::set<std::string> copyset = myset
    
    // 移动构造函数,C++11
    set<string> retSet() {
      std::set<std::string> myset{ "http://c.biancheng.net/java/",
                                   "http://c.biancheng.net/stl/",
                                   "http://c.biancheng.net/python/" };
      return myset;
    }
    
    std::set<std::string> copyset(retSet());
    // 或者
    std::set<std::string> copyset = retSet();
    

    由于 retSet() 函数的返回值是一个临时 set 容器,因此在初始化 copyset 容器时,其内部调用的是 set 类模板中的移动构造函数,而非拷贝构造函数。

  • 根据已有 set 容器部分元素,来初始化新的 set 容器
    std::set<std::string> myset{ "http://c.biancheng.net/java/",
                                 "http://c.biancheng.net/stl/",
                                 "http://c.biancheng.net/python/" };
    std::set<std::string> copyset(++myset.begin(), myset.end());
    
  • 修改排序规则
    std::set<std::string,std::greater<string> > myset{  "http://c.biancheng.net/java/",
                                                        "http://c.biancheng.net/stl/",
                                                        "http://c.biancheng.net/python/"};
    

5.2 成员方法

成员方法 功能
begin() 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
find(val) 在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(val) 返回一个指向当前 set 容器中第一个大于或等于 val 的元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(val) 返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是const 类型的双向迭代器。
equal_range(val) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val 的元素(set 容器中各个元素是唯一的,因此该范围最多包含一个元素。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前 set 容器中存有元素的个数。
max_size() 返回 set 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。
insert() 向 set 容器中插入元素。
erase() 删除 set 容器中存储的元素。
swap() 交换 2 个 set 容器中存储的所有元素。这意味着,操作的 2 个 set 容器的类型必须相同。
clear() 清空 set 容器中所有的元素,即令 set 容器的 size() 为 0。
emplace() 在当前 set 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。
emplace_hint() 在本质上和 emplace() 在 set 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。
count(val) 在当前 set 容器中,查找值为 val 的元素的个数,并返回。注意,由于 set 容器中各元素的值是唯一的,因此该函数的返回值最大为 1。

5.3 set 容器迭代器用法

  • 和 map 容器不同,C++ STL 中的 set 容器类模板中未提供 at() 成员函数,也未对 [] 运算符进行重载。因此,要想访问 set 容器中存储的元素,只能借助 set 容器的迭代器。
  • C++ STL 标准库为 set 容器配置的迭代器类型为双向迭代器。这意味着,假设 p 为此类型的迭代器,则其只能进行++p、p++、--p、p--、*p 操作,并且 2 个双向迭代器之间做比较,也只能使用 == 或者 != 运算符。

set 容器迭代器方法

成员方法 功能
begin() 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin()结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。通常和 rbegin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
find(val) 在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(val) 返回一个指向当前 set 容器中第一个大于或等于 val 的元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(val) 返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是const 类型的双向迭代器。
equal_range(val) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价, pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val的元素(set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。

以上成员函数返回的迭代器,指向的只是 set 容器中存储的元素,而不再是键值对。另外,以上成员方法返回的迭代器,无论是const 类型还是非 const 类型,都不能用于修改 set 容器中的值。

5.4 set insert() 方法

  1. 给定目标元素的值,insert() 方法即可将该元素添加到 set 容器中,其语法格式如下:
// 普通引用方式传参
pair<iterator,bool> insert (const value_type& val);
// 右值引用方式传参
pair<iterator,bool> insert (value_type&& val);
std::set<std::string> myset;                // 创建并初始化 set 容器
pair<set<string>::iterator, bool> retpair;  // 准备接受 insert() 的返回值

// 采用普通引用传值方式
string str = "http://c.biancheng.net/stl/";
retpair = myset.insert(str);

// 采用右值引用传值方式
retpair = myset.insert("http://c.biancheng.net/python/");
  1. 将新元素插入到 set 容器中的具体位置
// 以普通引用的方式传递 val 值
iterator insert (const_iterator position, const value_type& val);
// 以右值引用的方式传递 val 值
iterator insert (const_iterator position, value_type&& val);
std::set<std::string> myset;      // 创建并初始化 set 容器
set<string>::iterator iter;       // 准备接受 insert() 的返回值

// 采用普通引用传值方式
string str = "http://c.biancheng.net/stl/";
iter = myset.insert(myset.begin(),str);

// 采用右值引用传值方式
iter = myset.insert(myset.end(),"http://c.biancheng.net/python/");
  1. 向当前 set 容器中插入其它 set 容器指定区域内的所有元素
// insert() 方法的语法格式如下:
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
// 创建并初始化 set 容器
std::set<std::string> myset{ "http://c.biancheng.net/stl/",
                             "http://c.biancheng.net/python/",
                             "http://c.biancheng.net/java/" };

std::set<std::string> otherset;                     // 创建一个同类型的空 set 容器
otherset.insert(++myset.begin(), myset.end());      // 利用 myset 初始化 otherset
  1. 一次向 set 容器中添加多个元素
void insert ( {E1, E2 ,...,En} );
// 创建并初始化 set 容器
std::set<std::string> myset;
// 向 myset 中添加多个元素
myset.insert({ "http://c.biancheng.net/stl/",
               "http://c.biancheng.net/python/",
               "http://c.biancheng.net/java/" });

5.5 emplace()和 emplace_hint() 方法

set 类模板能实现向指定 set 容器中添加新元素的,只有 3 个成员方法,分别为 insert()、emplace() 和 emplace_hint()。

emplace() 方法的语法格式如下:

template <class... Args>
pair<iterator,bool> emplace (Args&&... args);
  • 参数 (Args&&... args) 指的是,只需要传入构建新元素所需的数据即可,该方法可以自行利用这些数据构建出要添加的元素。
  • 当该方法将目标元素成功添加到 set 容器中时,其返回的迭代器指向新插入的元素,同时 bool 值为 true;
  • 当添加失败时,则表明原 set 容器中已存在相同值的元素,此时返回的迭代器指向容器中具有相同键的这个元素,同时 bool 值为 false。
// 创建并初始化 set 容器
std::set<string>myset;

// 向 myset 容器中添加元素
pair<set<string, string>::iterator, bool> ret = myset.emplace("http://c.biancheng.net/stl/");

emplace_hint() 方法的功能和 emplace() 类似,其语法格式如下:

template <class... Args>
iterator emplace_hint (const_iterator position, Args&&... args);

和 emplace() 方法相比,有以下 2 点不同:

  • 该方法需要额外传入一个迭代器,用来指明新元素添加到 set 容器的具体位置(新元素会添加到该迭代器指向元素的前面);
  • 返回值是一个迭代器,而不再是 pair 对象。当成功添加元素时,返回的迭代器指向新添加的元素;反之,如果添加失败,则迭代器就指向 set 容器和要添加元素的值相同的元素。
// 创建并初始化 set 容器
std::set<string>myset;
// 在 set 容器的指定位置添加键值对
set<string>::iterator iter = myset.emplace_hint(myset.begin(), "http://c.biancheng.net/stl/");

和 insert() 方法一样,虽然 emplace_hint() 方法中指定了添加新元素的位置,但 set 容器为了保持数据的有序状态,可能会移动其位置。

5.6 删除数据:erase() 和 clear() 方法

// 删除 set 容器中值为 val 的元素
size_type erase (const value_type& val);
// 删除 position 迭代器指向的元素
iterator erase (const_iterator position);
// 删除 [first,last) 区间内的所有元素
iterator erase (const_iterator first, const_iterator last);

其中,第 1 种格式的 erase() 方法,其返回值为一个整数,表示成功删除的元素个数;后 2 种格式的 erase() 方法,返回值都是迭代器,其指向的是 set 容器中删除元素之后的第一个元素。

//创建并初始化 set 容器
std::set<int>myset{1,2,3,4,5};

// 1) 调用第一种格式的 erase() 方法
int num = myset.erase(2);             // 删除元素 2,myset={1,3,4,5}

//2) 调用第二种格式的 erase() 方法
set<int>::iterator iter = myset.erase(myset.begin());   // 删除元素 1,myset={3,4,5}

//3) 调用第三种格式的 erase() 方法
set<int>::iterator iter2 = myset.erase(myset.begin(), --myset.end());   // 删除元素 3,4,myset={5}

如果需要删除 set 容器中存储的所有元素,可以使用 clear() 成员方法。

六、multiset 容器

set 容器具有以下几个特性:

  • 不再以键值对的方式存储数据,因为 set 容器专门用于存储键和值相等的键值对,因此该容器中真正存储的是各个键值对的值(value);
  • set 容器在存储数据时,会根据各元素值的大小对存储的元素进行排序(默认做升序排序);
  • 存储到 set 容器中的元素,虽然其类型没有明确用 const 修饰,但正常情况下它们的值是无法被修改的;
  • set 容器存储的元素必须互不相等。

multiset 容器和 set 相似,仅在第 4 点特性上有差异,multiset 容器可以存储多个值相同的元素。

#include <set>
using namespace std;

template < class T,               // 存储元素的类型
    class Compare = less<T>,      // 指定容器内部的排序规则
    class Alloc = allocator<T> >  // 指定分配器对象的类型
    > class multiset;

6.1 创建 multiset 容器

  • 默认构造函数,创建空容器

    std::multiset<std::string> mymultiset;
    

    默认 std::less 排序规则。

  • 创建容器同时初始化

    std::multiset<std::string> mymultiset{ "http://c.biancheng.net/java/",
                                           "http://c.biancheng.net/stl/",
                                           "http://c.biancheng.net/python/" };
    
  • 拷贝/移动构造函数初始化

    std::multiset<std::string> copymultiset(mymultiset);
    // 等同于
    std::multiset<std::string> copymultiset = mymultiset;
    
    // 移动构造函数
    multiset<string> retMultiset() {
        std::multiset<std::string> tempmultiset{ "http://c.biancheng.net/java/",
                                                 "http://c.biancheng.net/stl/",
                                                  "http://c.biancheng.net/python/" };
        return tempmultiset;
    }
    
    std::multiset<std::string> copymultiset(retMultiset());
    // 等同于
    // std::multiset<std::string> copymultiset = retMultiset();
    
  • 已有容器部分元素来初始化新容器

    std::multiset<std::string> mymultiset{ "http://c.biancheng.net/java/",
                                           "http://c.biancheng.net/stl/",
                                           "http://c.biancheng.net/python/" };
    std::set<std::string> copymultiset(++mymultiset.begin(), mymultiset.end());
    
  • 修改排序规则

    std::multiset<std::string, std::greater<string> > mymultiset{ "http://c.biancheng.net/java/",
                                                                  "http://c.biancheng.net/stl/",
                                                                  "http://c.biancheng.net/python/" };
    

6.2 multiset 容器成员方法

成员方法 功能
begin() 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin()结合使用。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
find(val) 在 multiset 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(val) 返回一个指向当前 multiset 容器中第一个大于或等于 val 的元素的双向迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(val) 返回一个指向当前 multiset 容器中第一个大于 val 的元素的迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(val) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价, pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含所有值为 val 的元素。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前 multiset 容器中存有元素的个数。
max_size() 返回 multiset 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。
insert() 向 multiset 容器中插入元素。
erase() 删除 multiset 容器中存储的指定元素。
swap() 交换 2 个 multiset 容器中存储的所有元素。这意味着,操作的 2 个 multiset 容器的类型必须相同。
clear() 清空 multiset 容器中所有的元素,即令 multiset 容器的 size() 为 0。
emplace() 在当前 multiset 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。
emplace_hint() 本质上和 emplace() 在 multiset 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。
count(val) 在当前 multiset 容器中,查找值为 val 的元素的个数,并返回。

注意,虽然 multiset 容器和 set 容器拥有的成员方法完全相同,但由于 multiset 容器允许存储多个值相同的元素,因此诸如 count()、 find()、lower_bound()、upper_bound()、equal_range()等方法,更常用于 multiset 容器。

posted @ 2021-11-22 11:06  锦瑟,无端  阅读(831)  评论(0编辑  收藏  举报