专业的编程技术博客社区

网站首页 > 博客文章 正文

c++ 构建一个 zip 迭代器适配器(c++ 自定义迭代器)

baijin 2024-10-12 02:10:01 博客文章 12 ℃ 0 评论


许多脚本语言都包含了一个用于将两个序列打包在一起的函数。典型的 zip 操作将两个输入序列并返回每个输入中每个位置的一对值:

考虑两个序列的情况——它们可以是容器、迭代器或初始化列表:




我们想要将它们打包在一起,制作一个新的序列,其中包含来自前两个序列的元素对:


在这个食谱中,我们将使用迭代器适配器来完成这项任务。

如何做到这一点…

在这个食谱中,我们将构建一个 zip 迭代器适配器,它接受两个相同类型的容器,并将值打包到 std::pair 对象中:

在我们的 main() 函数中,我们想用两个向量调用我们的适配器:

int main() {
    vector<std::string> vec_a {"Bob", "John", "Joni"};
    vector<std::string> vec_b {"Dylan", "Williams", "Mitchell"};
    cout << "zipped: ";
    for(auto [a, b] : zip_iterator(vec_a, vec_b)) {
        cout << format("[{}, {}] ", a, b);
    }
    cout << '\n';
}


这允许我们使用 zip_iterator 代替单独的向量迭代器。

我们期望的输出如下:

zipped: [Bob, Dylan] [John, Williams] [Joni, Mitchell]


我们的迭代器适配器在一个名为 zip_iterator 的类中。我们将从一些类型别名开始,以方便使用:

template<typename T>
class zip_iterator {
    using val_t = typename T::value_type;
    using ret_t = std::pair<val_t, val_t>;
    using it_t = typename T::iterator;
}


这些允许我们方便地定义对象和函数。

我们的迭代器不存储任何数据。我们只存储目标容器的 begin() 和 end() 迭代器的副本:

it_t ita_{};
it_t itb_{};
// for begin() and end() objects
it_t ita_begin_;
it_t itb_begin_;
it_t ita_end_;
it_t itb_end_;


ita_ 和 itb_ 是来自目标容器的迭代器。其他四个迭代器用于为 zip_iterator 适配器生成 begin() 和 end() 迭代器。

我们还有一个私有构造函数:

// private constructor for begin() and end() objects
zip_iterator(it_t ita, it_t itb) : ita_{ita}, itb_{itb} {}


这稍后用于构造专门用于 begin() 和 end() 迭代器的适配器对象。

在 public 部分,我们从迭代器特性类型定义开始:

public:
    using iterator_concept = std::forward_iterator_tag;
    using iterator_category = std::forward_iterator_tag;
    using value_type = std::pair<val_t, val_t>;
    using difference_type = long int;
    using pointer = const val_t*;
    using reference = const val_t&;


构造函数设置所有私有迭代器变量:

zip_iterator(T& a, T& b) :
    ita_{a.begin()},
    itb_{b.begin()},
    ita_begin_{ita_},
    itb_begin_{itb_},
    ita_end_{a.end()},
    itb_end_{b.end()}
{}


我们定义了最少的运算符重载,以与前向迭代器一起工作:

zip_iterator& operator++() {
    ++ita_;
    ++itb_;
    return *this;
}
bool operator==(const zip_iterator& o) const {
    return ita_ == o.ita_ || itb_ == o.itb_;
}
bool operator!=(const zip_iterator& o) const {
    return !operator==(o);
}
ret_t operator*() const {
    return { *ita_, *itb_ };
}


最后,begin() 和 end() 函数返回相应的迭代器:

zip_iterator begin() const { return zip_iterator(ita_begin_, itb_begin_); }
zip_iterator end() const { return zip_iterator(ita_end_, itb_end_); }


这些通过存储的迭代器和私有构造函数变得简单。

现在让我们扩展我们的 main() 函数进行测试:

int main() {
    vector<std::string> vec_a {"Bob", "John", "Joni"};
    vector<std::string> vec_b {"Dylan", "Williams", "Mitchell"};
    cout << "vec_a: ";
    for(auto e : vec_a) cout << format("{} ", e);
    cout << '\n';
    cout << "vec_b: ";
    for(auto e : vec_b) cout << format("{} ", e);
    cout << '\n';
    cout << "zipped: ";
    for(auto [a, b] : zip_iterator(vec_a, vec_b)) {
        cout << format("[{}, {}] ", a, b);
    }
    cout << '\n';
}


这给我们带来了我们想要的输出:

vec_a: Bob John Joni
vec_b: Dylan Williams Mitchell
zipped: [Bob, Dylan] [John, Williams] [Joni, Mitchell]


它是如何工作的…

打包迭代器适配器是迭代器抽象灵活性的一个例子。我们可以取两个容器的迭代器,并在一个大的聚合迭代器中使用它们。让我们来看看这是如何工作的。

zip_iterator 类的主要构造函数接受两个容器对象。为了本次讨论的目的,我们将这些对象称为目标对象。

zip_iterator(T& a, T& b) :
    ita_{a.begin()},
    itb_{b.begin()},
    ita_begin_{ita_},
    itb_begin_{itb_},
    ita_end_{a.end()},
    itb_end_{b.end()}
{}


构造函数从目标 begin() 迭代器初始化 ita_ 和 itb_ 变量。这些将用于导航目标对象。目标 begin() 和 end() 迭代器也被保存供以后使用。

这些变量在私有部分定义:

it_t ita_{};
it_t itb_{};
// for begin() and end() objects
it_t ita_begin_;
it_t itb_begin_;
it_t ita_end_;
it_t itb_end_{};


it_t 类型被定义为目标迭代器类的类型:

using val_t = typename T::value_type;
using ret_t = std::pair<val_t, val_t>;
using it_t = typename T::iterator;


其他别名类型是 val_t,表示目标值的类型,以及 ret_t,表示返回对的类型。这些类型定义在整个类中用于方便。

begin() 和 end() 函数使用只初始化 ita_ 和 itb_ 值的私有构造函数:

zip_iterator begin() const { return zip_iterator(ita_begin_, itb_begin_); }
zip_iterator end() const { return zip_iterator(ita_end_, itb_end_); }


私有构造函数如下所示:

// private constructor for begin() and end() objects
zip_iterator(it_t ita, it_t itb) : ita_{ita}, itb_{itb} {}


这是一个接受 it_t 迭代器作为参数的构造函数。它只初始化 ita_ 和 itb_,以便它们可以在比较运算符重载中使用。

类的其余部分表现得像一个普通的迭代器,但它操作的是来自目标类的迭代器:

zip_iterator& operator++() {
    ++ita_;
    ++itb_;
    return *this;
}
bool operator==(const zip_iterator& o) const {
    return ita_ == o.ita_ || itb_ == o.itb_;
}
bool operator!=(const zip_iterator& o) const {
    return !operator==(o);
}


解引用运算符返回一个 std::pair 对象(ret_t 是 std::pair<val_t, val_t> 的别名)。这是从迭代器检索值的接口。

ret_t operator*() const {
    return { *ita_, *itb_ };
}


还有更多…

zip_iterator 适配器可以用来轻松地将对象打包到 map 中:

map<string, string> name_map{};
for(auto [a, b] : zip_iterator(vec_a, vec_b)) {
    name_map.try_emplace(a, b);
}
cout << "name_map: ";
for(auto [a, b] : name_map) {
    cout << format("[{}, {}] ", a, b);
}
cout << '\n';


如果我们将这段代码添加到 main() 中,我们得到以下输出:

name_map: [Bob, Dylan] [John, Williams] [Joni, Mitchell]

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表