C++ STL库复习(11) set

和 map、multimap 容器不同,使用 set 容器存储的各个键值对,要求键 key 和值 value 必须相等。
举个例子,如下有 2 组键值对数据:

{<'a', 1>, <'b', 2>, <'c', 3>}
{<'a', 'a'>, <'b', 'b'>, <'c', 'c'>}

显然,第一组数据中各键值对的键和值不相等,而第二组中各键值对的键和值对应相等。对于 set 容器来说,只能存储第 2 组键值对,而无法存储第一组键值对。基于 set 容器的这种特性,当使用 set 容器存储键值对时,只需要为其提供各键值对中的 value 值(也就是 key 的值)即可。
map、multimap 容器都会自行根据键的大小对存储的键值对进行排序,set 容器也会如此,只不过 set 容器中各键值对的键 key 和值 value 是相等的,根据 key 排序,也就等价为根据 value 排序。
另外,使用 set 容器存储的各个元素的值必须各不相同。更重要的是,从语法上讲 set 容器并没有强制对存储元素的类型做 const 修饰,即 set 容器中存储的元素的值是可以修改的。但是,C++ 标准为了防止用户修改容器中元素的值,对所有可能会实现此操作的行为做了限制,使得在正常情况下,用户是无法做到修改 set 容器中元素的值的。

值得一提的是,set 容器定义于< set >头文件,并位于 std 命名空间中。因此如果想在程序中使用 set 容器,该程序代码应先包含如下语句:

#include <set>
using namespace std;

创建C++ set容器

1

调用默认构造函数,创建空的 set 容器。比如:

std::set<std::string> myset;

2

除此之外,set 类模板还支持在创建 set 容器的同时,对其进行初始化。例如:

std::set<std::string> myset{"C++",
                            "STL",
                            "Python"};

由此即创建好了包含 3 个 string 元素的 myset 容器。由于其采用默认的 std::less 规则,因此其内部存储 string 元素的顺序如下所示:

"C++"
"Python"
"STL"

3

set 类模板中还提供了拷贝(复制)构造函数,可以实现在创建新 set 容器的同时,将已有 set 容器中存储的所有元素全部复制到新 set 容器中。
例如,在第 2 种方式创建的 myset 容器的基础上,执行如下代码:

std::set<std::string> copyset(myset);
//等同于
//std::set<std::string> copyset = myset

另外,C++ 11 标准还为 set 类模板新增了移动构造函数,其功能是实现创建新 set 容器的同时,利用临时的 set 容器为其初始化。比如:

set<string> retSet() 
{
    std::set<std::string> myset{ "Java",
                                 "STL",
                                 "Python" };
                                 
    return myset;
}

std::set<std::string> copyset(retSet());
//或者
//std::set<std::string> copyset = retSet();

4

在第 3 种方式的基础上,set 类模板还支持取已有 set 容器中的部分元素,来初始化新 set 容器。例如:

std::set<std::string> myset{ "Java",
                             "STL",
                             "Python" };
std::set<std::string> copyset(++myset.begin(), myset.end());

由此初始化的 copyset 容器,其内部仅存有如下 2 个 string 字符串:

"Python"
"STL"

5

以上几种方式创建的 set 容器,都采用了默认的std::less< T >规则。其实,借助 set 类模板定义中第 2 个参数,我们完全可以手动修改 set 容器中的排序规则。比如:

std::set< std::string,std::greater<string> > myset{"Java", "STL", "Python" };

通过选用 std::greater 降序规则,myset 容器中元素的存储顺序为:

"STL"
"Python"
"Java"

举个例子:

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

using namespace std;

int main()
{
    //创建空set容器
    std::set<std::string> myset;
    //空set容器不存储任何元素
    cout << "1、myset size = " << myset.size() << endl;
    
    //向myset容器中插入新元素
    myset.insert("Java");
    myset.insert("STL");
    myset.insert("Python");
    cout << "2、myset size = " << myset.size() << endl;
    
    //利用双向迭代器,遍历myset
    for (auto iter = myset.begin(); iter != myset.end(); ++iter) 
    {
        cout << *iter << endl;
    }
    return 0;
}

输出结果:

1、myset size = 0
2、myset size = 3
Java
Python
STL

set容器迭代器

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

下面程序以 begin()/end() 为例,演示了如何使用相关迭代器遍历 set 容器:

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

using namespace std;

int main()
{
    //创建并初始化set容器
    std::set<std::string> myset{"Java", "STL", "Python"};
    
    //利用双向迭代器,遍历myset
    for( auto iter = myset.begin(); iter != myset.end(); ++iter) 
    {
        cout << *iter << endl;
    }
    
    return 0;
}

输出结果:

Java
Python
STL

除此之外,如果只想遍历 set 容器中指定区域内的部分数据,则可以借助 find()、lower_bound() 以及 upper_bound() 实现。通过调用它们,可以获取一个指向指定元素的迭代器。
需要特别指出的是,equal_range(val) 函数的返回值是一个 pair 类型数据,其包含 2 个迭代器,表示 set 容器中和指定参数 val 相等的元素所在的区域,但由于 set 容器中存储的元素各不相等,因此该函数返回的这 2 个迭代器所表示的范围中,最多只会包含 1 个元素。

举个例子:

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

using namespace std;

int main()
{
    //创建并初始化set容器
    std::set<std::string> myset{ "Java", "STL", "Python" };
    
    set<string>::iterator iter = myset.find("Python");
    
    for (;iter != myset.end();++iter)
    {
        cout << *iter << endl;
    }
    
    return 0;
}

输出结果:

Python
STL

set insert()方法详解

为满足不同场景的需要,C++ 11 标准的 set 类模板中提供了多种不同语法格式的 insert() 成员方法,它们各自的功能和用法如下所示。

1

只要给定目标元素的值,insert() 方法即可将该元素添加到 set 容器中,其语法格式如下:

//普通引用方式传参
pair<iterator,bool> insert(const value_type& val);
//右值引用方式传参
pair<iterator,bool> insert(value_type&& val);

其中,val 表示要添加的新元素,该方法的返回值为 pair 类型。

可以看到,以上 2 种语法格式的 insert() 方法,返回的都是 pair 类型的值,其包含 2 个数据,一个迭代器和一个 bool 值:

  • 当向 set 容器添加元素成功时,该迭代器指向 set 容器新添加的元素,bool 类型的值为 true;
  • 如果添加失败,即证明原 set 容器中已存有相同的元素,此时返回的迭代器就指向容器中相同的此元素,同时 bool 类型的值为 false。

举个例子:

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

using namespace std;

int main()
{
    //创建并初始化set容器
    std::set<std::string> myset;
    //准备接受 insert() 的返回值
    pair<set<string>::iterator, bool> retpair;
    //采用普通引用传值方式
    string str = "STL";
    retpair = myset.insert(str);
    cout << "iter->" << *(retpair.first) << " " << "bool = " << retpair.second << endl;
    //采用右值引用传值方式
    retpair = myset.insert("Python");
    cout << "iter->" << *(retpair.first) << " " << "bool = " << retpair.second << endl;
    
    return 0;
}

输出结果:

iter->STL bool = 1
iter->Python bool = 1

2

insert() 还可以指定将新元素插入到 set 容器中的具体位置,其语法格式如下:

//以普通引用的方式传递 val 值
iterator insert (const_iterator position, const value_type& val);
//以右值引用的方式传递 val 值
iterator insert (const_iterator position, value_type&& val);

以上 2 种语法格式中,insert() 函数的返回值为迭代器:

  • 当向 set 容器添加元素成功时,该迭代器指向容器中新添加的元素;
  • 当添加失败时,证明原 set 容器中已有相同的元素,该迭代器就指向 set 容器中相同的这个元素。

举个例子:

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

using namespace std;

int main()
{
    //创建并初始化set容器
    std::set<std::string> myset;
    //准备接受 insert() 的返回值
    set<string>::iterator iter;
    //采用普通引用传值方式
    string str = "STL";
    iter = myset.insert(myset.begin(), str);
    cout << "myset size = " << myset.size() << endl;
    //采用右值引用传值方式
    iter = myset.insert(myset.end(), "Python");
    cout << "myset size = " << myset.size() << endl;
    
    return 0;
}

输出结果:

myset size = 1
myset size = 2

3

insert() 方法支持向当前 set 容器中插入其它 set 容器指定区域内的所有元素,只要这 2 个 set 容器存储的元素类型相同即可。
insert() 方法的语法格式如下:

template <class InputIterator>
  void insert (InputIterator first, InputIterator last);

其中 first 和 last 都是迭代器,它们的组合 [first,last) 可以表示另一 set 容器中的一块区域,该区域包括 first 迭代器指向的元素,但不包含 last 迭代器指向的元素。

举个例子:

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

using namespace std;

int main()
{
    //创建并初始化set容器
    std::set<std::string> myset{ "STL", "Python", "Java" };

    //创建一个同类型的空 set 容器
    std::set<std::string> otherset;
    //利用 myset 初始化 otherset
    otherset.insert(++myset.begin(), myset.end());
    //输出 otherset 容器中的元素
    for (auto iter = otherset.begin(); iter != otherset.end(); ++iter) 
    {
        cout << *iter << endl;
    }
    return 0;
}

输出结果:

Python
STL

注意,程序第 15 行在初始化 otherset 容器时,选取的是 myset 容器中从第 2 个元素开始(包括此元素)直到容器末尾范围内的所有元素,所以程序输出结果中只有 2 个字符串。

4

采用如下格式的 insert() 方法,可实现一次向 set 容器中添加多个元素:

void insert ( {E1, E2,...,En} );

其中,Ei 表示新添加的元素。

举个例子:

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

using namespace std;

int main()
{
    //创建并初始化set容器
    std::set<std::string> myset;
    //向 myset 中添加多个元素
    myset.insert({ "STL", "Python", "Java" });
        
    for (auto iter = myset.begin(); iter != myset.end(); ++iter) 
    {
        cout << *iter << endl;
    }
    
    return 0;
}

输出结果:

Java
Python
STL

set emplace()和emplace_hint()

emplace() 和 emplace_hint() 是 C++ 11 标准加入到 set 类模板中的,相比具有同样功能的 insert() 方法,完成同样的任务,emplace() 和 emplace_hint() 的效率会更高。

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

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

其中,参数 (Args&&… args) 指的是,只需要传入构建新元素所需的数据即可,该方法可以自行利用这些数据构建出要添加的元素。比如,若 set 容器中存储的元素类型为自定义的结构体或者类,则在使用 emplace() 方法向容器中添加新元素时,构造新结构体变量(或者类对象)需要多少个数据,就需要为该方法传入相应个数的数据。

另外,该方法的返回值类型为 pair 类型,其包含 2 个元素,一个迭代器和一个 bool 值:

  • 当该方法将目标元素成功添加到 set 容器中时,其返回的迭代器指向新插入的元素,同时 bool 值为 true;
  • 当添加失败时,则表明原 set 容器中已存在相同值的元素,此时返回的迭代器指向容器中具有相同键的这个元素,同时 bool 值为false。

举个例子:

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

using namespace std;

int main()
{
    //创建并初始化 set 容器
    std::set<string> myset;
    //向 myset 容器中添加元素
    pair<set<string, string>::iterator, bool> ret = myset.emplace("STL");
    cout << "myset size = " << myset.size() << endl;
    cout << "ret.iter = <" << *(ret.first) << ", " << ret.second << ">" << endl;
    
    return 0;
}

输出结果:

myset size = 1
ret.iter = <STL, 1>

显然,从执行结果可以看出,通过调用 emplace() 方法,成功向空 myset 容器中添加了一个元素,并且该方法的返回值中就包含指向新添加元素的迭代器。

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

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

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

  • 该方法需要额外传入一个迭代器,用来指明新元素添加到 set 容器的具体位置(新元素会添加到该迭代器指向元素的前面);
  • 返回值是一个迭代器,而不再是 pair 对象。当成功添加元素时,返回的迭代器指向新添加的元素;反之,如果添加失败,则迭代器就指向 set 容器和要添加元素的值相同的元素。

下面程序演示 emplace_hint() 方法的用法:

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

using namespace std;

int main()
{
    //创建并初始化 set 容器
    std::set<string>myset;
    //在 set 容器的指定位置添加键值对
    set<string>::iterator iter = myset.emplace_hint(myset.begin(), "STL");
    cout << "myset size = " << myset.size() << endl;
    cout << *iter << endl;
    
    return 0;
}

输出结果:

myset size = 1
STL

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

set删除数据:erase()和clear()

1

set 类模板中,erase() 方法有 3 种语法格式,分别如下:

//删除 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 容器中删除元素之后的第一个元素。

下面程序演示了以上 3 种 erase() 方法的用法:

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

using namespace std;

int main()
{
    //创建并初始化 set 容器
    std::set<int> myset{1,2,3,4,5};
    cout << "myset size = " << myset.size() << endl;
    cout << endl;
    
    //1) 调用第一种格式的 erase() 方法
    int num = myset.erase(2); //删除元素 2,myset={1,3,4,5}
    cout << "1、myset size = " << myset.size() << endl;
    cout << "num = " << num << endl;
    cout << endl;
    
    //2) 调用第二种格式的 erase() 方法
    set<int>::iterator iter = myset.erase(myset.begin()); //删除元素 1,myset={3,4,5}
    cout << "2、myset size = " << myset.size() << endl;
    cout << "iter->" << *iter << endl;
    cout << endl;
    
    //3) 调用第三种格式的 erase() 方法
    set<int>::iterator iter2 = myset.erase(myset.begin(), --myset.end());//删除元素 3,4,myset={5}
    cout << "3、myset size = " << myset.size() << endl;
    cout << "iter2->" << *iter2 << endl;
    cout << endl;
    
    return 0;
}

输出结果:

myset size = 5

1、myset size = 4
num = 1

2、myset size = 3
iter->3

3、myset size = 1
iter2->5

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

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

using namespace std;

int main()
{
    //创建并初始化 set 容器
    std::set<int> myset{1,2,3,4,5};
    cout << "1、myset size = " << myset.size() << endl;
    //清空 myset 容器
    myset.clear();
    cout << "2、myset size = " << myset.size() << endl;
    
    return 0;
}

输出结果:

1、myset size = 5
2、myset size = 0

multiset容器

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

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

C++ STL 标准库中还提供有一个和 set 容器相似的关联式容器,即 multiset 容器。和 set 容器不同的是,multiset 容器可以存储多个值相同的元素。

和 set 类模板一样,multiset 类模板也定义在< set >头文件,并位于 std 命名空间中。这意味着,如果想在程序中使用 multiset 容器,该程序代码应包含如下语句:

#include <set>
using namespace std;

multiset 容器类模板的定义如下所示:

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

1 创建C++ multiset容器的方法

multiset 类模板中提供了 5 种构造函数,也就代表有 5 种创建 multiset 容器的方式,分别如下。

1

调用默认构造函数,创建空的 multiset 容器。比如:

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

由此就创建好了一个 mymultiset 容器,该容器采用默认的std::less< T >规则,会对存储的 string 类型元素做升序排序。

2

除此之外,multiset 类模板还支持在创建 multiset 容器的同时,对其进行初始化。例如:

std::multiset<std::string> mymultiset{ "Java", "STL", "Python" }; 

由于其采用默认的std::less< T >规则,其内部存储 string 元素的顺序如下所示:

"Java"
"Python"
"STL"

3

multiset 类模板中还提供了拷贝(复制)构造函数,可以实现在创建新 multiset 容器的同时,将已有 multiset 容器中存储的所有元素全部复制到新 multiset 容器中。

例如,在第 2 种方式创建的 mymultiset 容器的基础上,执行如下代码:

std::multiset<std::string> copymultiset(mymultiset);
//等同于
//std::multiset<std::string> copymultiset = mymultiset;

该行代码在创建 copymultiset 容器的基础上,还会将 mymultiset 容器中存储的所有元素,全部复制给 copymultiset 容器一份。

另外,C++ 11 标准还为 multiset 类模板新增了移动构造函数,其功能是实现创建新 multiset 容器的同时,利用临时的 multiset 容器为其初始化。比如:

multiset<string> retMultiset() 
{
    std::multiset<std::string> tempmultiset{ "Java", "STL", "Python" }; 
    return tempmultiset;
}
std::multiset<std::string> copymultiset(retMultiset());
//等同于
//std::multiset<std::string> copymultiset = retMultiset();

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

4

在第 3 种方式的基础上,multiset 类模板还支持取已有 multiset 容器中的部分元素,来初始化新 multiset 容器。例如:

std::multiset<std::string> mymultiset{ "Java", "STL", "Python" }; 
std::set<std::string> copymultiset(++mymultiset.begin(), mymultiset.end());

以上初始化的 copyset 容器,其内部仅存有如下 2 个 string 字符串:

"Python"
"STL"

5

以上几种方式创建的 multiset 容器,都采用了默认的std::less< T >规则。其实,借助 multiset 类模板定义中的第 2 个参数,我们完全可以手动修改 multiset 容器中的排序规则。
下面样例中,使用了 STL 标准库提供的 std::greater< T > 排序方法,作为 multiset 容器内部的排序规则:

std::multiset<std::string, std::greater<string> > mymultiset{ "Java", "STL", "Python" }; 

通过选用std::greater< string >降序规则,mymultiset 容器中元素的存储顺序为:

"STL"
"Python"
"Java"

multiset 容器提供的成员方法,和 set 容器提供的完全一样。
举个例子:

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

using namespace std;

int main() 
{
    std::multiset<int> mymultiset{1,2,2,2,3,4,5};
    cout << "multiset size = " << mymultiset.size() << endl;
    cout << "multiset count(2) = " << mymultiset.count(2) << endl;
    //向容器中添加元素 8
    mymultiset.insert(8);
    //删除容器中所有值为 2 的元素
    int num = mymultiset.erase(2);
    cout << "删除了 " << num << " 个元素 2" << endl;
    //输出容器中存储的所有元素
    for (auto iter = mymultiset.begin(); iter != mymultiset.end(); ++iter) 
    {
        cout << *iter << " ";
    }
    return 0;
}

输出结果:

multiset size = 7
multiset count(2) = 3
删除了 3 个元素 2
1 3 4 5 8    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值