下面代码展示了向std::set(std::unordered_set)插入数据的方法(代码示例1):
// 示例代码1
#include <array>
#include <iostream>
#include <iterator>
#include <memory>
#include <set>
#include <unordered_set>
#include <utility>
// 下面用了选择使用 std::unordered_set 或 std::set
// 这里使用了 std::set
// using set_type = std::unordered_set<int>;
using set_type = std::set<int>;
// set_type使用 std::set<int> 或 std::unordered_set<int>
// 唯一的差别就是这个函数
// 这是因为它们的存储方式不同导致的 ---- 这个下次会解释
std::ostream& operator<<(std::ostream& os, const set_type& sets) {
if (sets.empty()) {
os << "{}";
} else {
os << "{" << *sets.begin();
for (auto iter = std::next(sets.begin()); iter != sets.end(); ++iter) {
os << ", " << *iter;
}
os << "}";
}
os << std::endl;
return os;
}
int main()
{
set_type sets;
std::cout << "元素数据为" << std::endl;
std::cout << sets;
{
// 下面这句同 auto result = sets.insert(10);
std::pair<set_type::iterator, bool> result = sets.insert(10);
// result.first 是 值为 10 的迭代器
// result.second 表示是否真正发生了insert操作
// -- 这是因为 std::set 不允许同一个值反复插入,
// 当已经存在 10 时,这时就不会发生真实的插入
// 但result.first还是有效的 指向的是已经存在的
std::cout << "插入10后为" << std::endl;
std::cout << sets;
}
{
// 下面这句同 auto result = sets.insert(std::move(15));
std::pair<set_type::iterator, bool> result = sets.insert(std::move(15));
std::cout << "插入15后为" << std::endl;
std::cout << sets;
}
{
// 这个insert方法的返回值和上面的result.first一致
// 另外,第一个参数的推荐的插入位置 -- 重点是推荐,这个看附录二有更多解释
set_type::iterator iter = sets.insert(sets.begin(), 5);
std::cout << "推荐在开头插入5后为" << std::endl;
std::cout << sets;
}
{
set_type::iterator iter = sets.insert(sets.end(), std::move(8));
std::cout << "推荐在结尾插入8后为" << std::endl;
std::cout << sets;
}
{
std::array<int, 4> a = {51, 52, 53, 54};
// 使用迭代器批量插入 --- 没有返回值(即void类型)
sets.insert(a.begin(), a.end());
std::cout << "插入迭代器数据后为" << std::endl;
std::cout << sets;
}
{
// 使用std::initializer_list批量插入
sets.insert({31, 35, 41});
std::cout << "插入td::initializer_list数据后为" << std::endl;
std::cout << sets;
}
{
// 开始已经插入10, 这里插入重复数据
auto result = sets.insert(10);
std::cout << "再次插入10后为" << std::endl;
std::cout << sets;
std::cout << "返回迭代器为 " << *result.first << std::endl;
std::cout << "插入是否成功 " << std::boolalpha << result.second << std::endl;
}
}
运行结果(使用std::set)如下:
元素数据为
{}
插入10后为
{10}
插入15后为
{10, 15}
推荐在开头插入5后为
{5, 10, 15}
推荐在结尾插入8后为
{5, 8, 10, 15}
插入迭代器数据后为
{5, 8, 10, 15, 51, 52, 53, 54}
插入td::initializer_list数据后为
{5, 8, 10, 15, 31, 35, 41, 51, 52, 53, 54}
再次插入10后为
{5, 8, 10, 15, 31, 35, 41, 51, 52, 53, 54}
返回迭代器为 10
插入是否成功 false
除了insert方法,还有emplace方法。但emplace方法的返回值为std::pair类型,这个比insert简单就不举例了。
需要注意的一点,std::set和std::unordered_set是不能(通过迭代器等)修改元素的。这是因为,修改元素就可能会导致内部的顺序变化,这就不是简单的修改元素属性了。当然,可以通过删除后添加的方式进行修改,看下面代码(代码示例2):
// 代码示例2
#include <iostream>
#include <set>
std::ostream& operator<<(std::ostream& os, conststd::set<int>& sets) {
if (sets.empty()) {
os << "{}";
} else {
os << "{" << *sets.begin();
for (auto iter = std::next(sets.begin()); iter != sets.end(); ++iter) {
os << ", " << *iter;
}
os << "}";
}
os << std::endl;
return os;
}
int main()
{
std::set<int> a = {10, 20, 30, 40};
std::cout << a;
// 查找 10
auto iter = a.find(10);
// 10 是否找到
if (iter != a.end()) {
// 删除 10
a.erase(iter);
// 添加 35
a.insert(35);
}
std::cout << a;
}
运行结果如下:
{10, 20, 30, 40}
{20, 30, 35, 40}
std::set和std::unordered_set等可以使用find方法判断指定元素是否存在。std::set这类容器主要使用的就是erase、find和insert(包括emplace)方法。
附录:
附录一
关于std::set的必要性。在实际使用中,std::map比std::set的存在感高很多,但是,我还是计划使用很大的篇幅说明std::set的用法,这能在很大程度上帮助理解std::map。
本质上可以认为std::set内的元素不可以修改,但std::map可以修改元素,这就是std::map更能够广泛使用的原因。这也在一定程度上说明了使用std::set的场景(不需要修改元素的属性)。
另,std::set也可以修改元素的属性,要删除旧的后,添加新的。
附录二
std::set(和std::unordered_set)的insert方法在插入一个元素时,可以提供推荐的插入位置(附近),这个实际上是不能影响实际的插入位置的,但影响插入的效率,看下面代码(代码示例3):
// 代码示例3
#include <cstdlib>
#include <chrono>
#include <iostream>
#include <set>
// 该代码使用 std::set效果才明显
class timing
{
public:
timing() : start_(std::chrono::high_resolution_clock::now()) {}
~timing() {
auto end = std::chrono::high_resolution_clock::now();
auto d = std::chrono::duration_cast<std::chrono::milliseconds>(end - start_);
std::cout << "耗时: " << d.count() << "秒" << std::endl;
}
private:
std::chrono::time_point<std::chrono::high_resolution_clock> start_;
};
int main()
{
// 根据机器性能,自行调整loop_count的值
constexprstd::size_t loop_count = 100000;
{
std::set<int> sets;
std::cout << "前插入" << loop_count << "项";
timing t;
for (std::size_t index = 0; index < loop_count; ++index) {
sets.insert(sets.begin(), index);
}
}
{
std::set<int> sets;
std::cout << "后插入" << loop_count << "项";
timing t;
for (std::size_t index = 0; index < loop_count; ++index) {
sets.insert(sets.end(), index);
}
}
}
该代码因为计算耗时,有一定的随机性,大致结果如下:
前插入100000项耗时: 21秒
后插入100000项耗时: 18秒
非常高的概率是后插入耗时更小,这和std::set的内部实现方式有关,下次,就看看这个吧。
1512

被折叠的 条评论
为什么被折叠?



