Bootstrap

C++ STL set容器


C++ STL中定义的 set 模板有 4 种,其中两种默认使用 less<T> 来对元素排序,另外两种使用哈希值来保存元素。有序 set 的模板定义在 set 头文件中。无序 set 的模板定义在 unordered_set 头文件中。 有序 set 包含的元素必须支持比较运算(需要重载<运算符)无序 set 中的元素必须支持哈希运算

C++ STL中定义的 set 模板如下:
set<T> 容器保存 T 类型的对象,而且保存的对象是唯一的。其中保存的元素是有序的,默认用 less<T> 对象比较。可以用相等、不相等来判断对象是否相同。
multiSet<T> 容器和 set<T> 容器保存 T 类型对象的方式相同,但它可以保存重复的对象。
unorderd_set<T> 容器保存 T 类型的对象,而且对象是唯一的。元素在容器中的位置由元素的哈希值决定。默认用 equal_to<T> 对象来判断元素是否相等。
unordered_multiset<T> 容器保存 T 类型对象的方式和 unorderd_set<T> 相同,但它可以保存重复的对象。

从有序和无序关联容器获取的各种迭代器之间有一些区别。我们可以从有序容器得到正向和反向迭代器,但是只能从无序容器得到正向迭代器。

set

set初始化

std::set<string> words2 {
   std::begin(words), std::end(words)};
std::set<string, std::greater<string>> words3 {
   ++std::begin(words2), std::end(words2)};

第一条语句定义了 words2,它包含了 words 中元素的副本,words 用默认的 less<string>比较函数排序。

第二条语句定义了 words3,它包含 words2 中除第一个元素外的所有元素的副本。这个容器使用 greater<string> 实例排序。

set添加、删除和访问元素

添加元素

可以使用 insert()、emplace()、emplace_hint() 成员函数来向 set 中添加元素。

下面是一个使用 insert() 的示例:

std::set<string, std::greater<string>> words {
   "one", "two", "three"};
auto pr1 = words.insert("four");
auto pr2 = words.insert ("two") ;
auto iter3 = words.insert(pr.first, "seven");
words.insert ({
    "five","six"}) ;
string wrds[] {
   "eight", "nine", "ten"};
words.insert(std::begin(wrds) , std::end(wrds));

插入单个元素会返回一个 pair<iterator,bool> 对象。插入单个元素和一个标识,会返回一个迭代器。插入一段元素或一个初始化列表就不会有返回值。当 insert() 的参数是初始化列表时,会用列表中的字符串创建 string 对象。

下面是使用emplace() 和emplace_hint()的示例:

std::set<std::pair<string,string>> names;
auto pr = names.emplace("Lisa", "Carr");
auto iter = names.emplace_hint(pr.first, "Joe", "King");

成员函数 emplace() 会返回一个 pair<iterator,bool> 对象,而 emplace_hint() 只返回一个迭代器。前者的参数被直接传入元素的构造函数,用来创建元素。emplace_hint() 的第一个参数是一个迭代器,它指出了元素可能的插入位置,随后的参数会被传入元素的构造函数。

删除元素

成员函数 clear() 会删除 set 的所有元素。成员函数 erase() 会删除迭代器指定位置的元素或与对象匹配的元素。例如:

std::set<int> numbers {
   2, 4, 6, 8, 10, 12, 14};
auto iter = numbers.erase(++std::begin(numbers));
auto n = numbers.erase(12);
n = numbers.erase(13);
numbers.clear();

成员函数 erase() 可以删除一段元素:

std::set<int> numbers {
   2, 4, 6, 8, 10, 12, 14};
auto iter1 = std::begin(numbers); // iter1 points to 1st element
advance(iterl, 5); // Points to 6th element-12
auto iter = numbers.erase(++std:rbegin(numbers), iter1);// Remove 2nd to 5th inclusive. iter points to 12

如果 set 没有元素,成员函数 empty() 返回 true,成员函数 size() 返回它所包含的元素个数。如果担心无法在 set 中存储尽可能多的元素,可以调用成员函数 max_size() 来得到可存储的最大元素个数,这显然会是一个很大的值。

访问元素

set 的成员函数 find() 会返回一个和参数匹配的元素的迭代器。如果对象不在 set 中,会返回一个结束迭代器。例如:

std::set<string> words {
   "one", "two","three", "four","five"};
auto iter = words.find ("one") ; // iter points to "one"
iter = words.find(string{
   "two"});   // iter points to "two"
iter = words.find ("six");   // iter is std:: end (words)

调用成员函数 count() 可以返回指定键所对应的元素个数,返回值通常是 0 或 1,因为 set 容器中的元素是唯一的。set 容器模板定义了成员函数 equal_range()、lower_bound()、 upper_bound(),这和 multiset 容器在很大程度上是一致的。

set迭代器

set 容器的成员返回的迭代器都是双向迭代器。这些迭代器的类型的别名定义在 set 模板中,可以从 set 中得到类型别名有 iterator、reverse_iterator、const_iterator、 const_reverse_iterator,从它们的名称就可以看出它们的类型。例如:

  • 成员函数 begin() 和 end() 会返回 iterator 类型的迭代器;
  • 成员函数 rbegin() 和 rend() 会返回 reverse_iterator 类型的迭代器;
  • 成员函数 cbegin() 和 cend() 会返回 const_iterator 类型的迭代器。
  • 成员函数 crbegin() 和 crend() 可以返回 const_reverse_iterator 类型的迭代器。

所有 set 容器的成员函数返回的迭代器都指向 const T 类型的元素。这意味着我们不能修改元素。如果想要修改 set 容器中的元素,必须先删除它,然后再插入修改后的版本。

仔细思考一下,其实这是合理的。set 中的对象以它们自己作为键,对象在容器中的位置是通过比较对象决定的。如果可以修改元素,元素的顺序就失效了,也会扰乱后面的访问操作。、

当必须修改元素而且仍然需要将它们组合到一个或多个 set 容器中时,可以在 set 容器中保存指针,并且最好选择智能指针。通常会在它们中保存 shared_ptr 或 weak_ptr 对象。在 set 容器中保存 unique_ptr 对象没有多少意义。因为容器中不存在和 unique_ptr 对象匹配的独立键,所以我们从来不会直接检索元素。

set保存智能指针

如果改变对象,可能会改变 set 中对象指针的顺序,所以指针的比较函数不能和对象有关。大多数时候,我们并不在意元素在 set 中的顺序,而是在意容器中是否有这个元素。在这种情况下,就可以使用一个适用于指针但和它们所指向的元素无关的比较函数对象,推荐使用定义在 memory 头文件中的 owner_less<T> 函数对象类型的实例来比较容器中的智能指针。

owner_less<T> 模板为 shared_ptr 和 weak_ptr 对象定义了用于小于比较的函数对象类型。换句话说,允许 weak_ptr 对象和 shared_ptr 对象比较,反过来也可以,也允许和 weak_ptr 或 shared_ptr 对象比较。通过调用智能指针的成员函数 owner_before() 实现了一个 owner_less<T>

;