优秀的编程知识分享平台

网站首页 > 技术文章 正文

c++ 创建一个随机访问迭代器(c++怎么随机产生一个数)

nanyue 2024-11-13 11:34:30 技术文章 2 ℃


本文是一个完整的连续/随机访问迭代器的例子。这是容器迭代器中最完整的类型。随机访问迭代器包含了所有其他类型容器迭代器的所有特性,以及它的随机访问能力。

虽然我认为在本章中包含一个完整的迭代器很重要,但这个例子有超过 700 行代码,比本书中的其他例子要大一些。我将在这里介绍代码的主要组件。

如何做到这一点…

我们需要一个迭代器的容器。我们将为此使用一个简单的数组,并称之为 Container。迭代器类嵌套在 Container 类中。

所有这些都是为了与 STL 容器接口保持一致。

Container 被定义为一个模板类。它的私有部分只有两个元素:

template<typename T>
class Container {
    std::unique_ptr<T[]> c_;
    size_t n_elements_;
}


我们使用 unique_pointer 来管理数据。我们让智能指针管理它自己的内存。这减少了对 ~Container() 析构函数的需求。n_elements_ 变量保持我们容器的大小。

在 public 部分,我们有我们的构造函数:

Container(initializer_list<T> l) : n_elements_{l.size()} {
    c_ = std::make_unique<T[]>(n_elements_);
    size_t index{0};
    for(T e : l) {
        c_[index++] = e;
    }
}


第一个构造函数使用 initializer_list 传递容器的元素。我们调用 make_unique 来分配空间,并使用基于范围的 for 循环填充容器。

我们还有一个不填充元素的空间分配构造函数:

Container(size_t sz) : n_elements_{sz} {
    c_ = std::make_unique<T[]>(n_elements_);
}


make_unique() 函数为元素构建空对象。

size() 函数返回元素数量:

size_t size() const {
    return n_elements_;
}


operator[]() 函数返回一个索引元素:

const T& operator[](const size_t index) const {
    return c_[index];
}


at() 函数返回一个带有边界检查的索引元素:

T& at(const size_t index) const {
    if(index > n_elements_ - 1) {
        throw std::out_of_range("Container::at(): index out of range");
    }
    return c_[index];
}


这与 STL 用法一致。at() 函数是首选方法。

begin() 和 end() 函数调用迭代器构造函数,传入容器数据的地址。

iterator begin() const { return iterator(c_.get()); }
iterator end() const { return iterator(c_.get() + n_elements_); }


unique_ptr::get() 函数从智能指针返回地址。

迭代器类作为公共成员嵌套在 Container 类中。

class iterator {
    T* ptr_;
}


迭代器类有一个私有成员,一个在 Container 类的 begin() 和 end() 方法中初始化的指针。

迭代器构造函数接受指向容器数据的指针。

iterator(T* ptr = nullptr) : ptr_{ptr} {}


我们提供了一个默认值,因为标准要求一个默认构造函数。

运算符重载

这个迭代器提供了以下运算符的运算符重载:++, 后置 ++, --, 后置 --, [], 默认比较 <=> (C++20), ==, *, ->, +, 非成员 +, 数字 -, 对象 -, +=, 和 -=。我们将在这里介绍一些值得注意的重载。请参阅源代码中的所有内容。

C++20 默认比较运算符 <=> 提供了全套比较运算符的功能,除了等同 == 运算符:

const auto operator<=>(const iterator& o) const {
    return ptr_ <=> o.ptr_;
}


这是 C++20 的特性,因此需要一个兼容的编译器和库。

有两个 + 运算符重载。这些支持 it + n 和 n + it 操作。

iterator operator+(const size_t n) const {
    return iterator(ptr_ + n);
}


// 非成员运算符 (n + it)

friend const iterator operator+(
    const size_t n, const iterator& o) {
    return iterator(o.ptr_ + n);
}


友元声明是一个特殊情况。当在模板类成员函数中使用时,它等同于非成员函数。这允许在类上下文中定义非成员函数。

- 运算符也有两个重载。我们需要支持数字运算符和迭代器运算符。

const iterator operator-(const size_t n) {
    return iterator(ptr_ - n);
}
const size_t operator-(const iterator& o) {
    return ptr_ - o.ptr_;
}


这允许 both it – n 和 it – it 操作。不需要非成员函数,因为 n – it 不是有效操作。

验证代码

C++20 规范 §23.3.4.13 要求一个有效的随机访问迭代器具有一组特定的操作和结果。我在源代码中包含了一个 unit_tests() 函数来验证这些要求。

main() 函数创建一个 Container 对象并执行一些简单的验证函数。

首先,我们创建一个包含十个值的 Container<string> 对象 x。

Container<string> x{"one", "two", "three", "four", "five",
    "six", "seven", "eight", "nine", "ten" };
cout << format("Container x size: {}\n", x.size());


输出给出了元素的数量:

Container x size: 10


我们使用基于范围的 for 循环显示容器的元素:

puts("Container x:");
for(auto e : x) {
    cout << format("{} ", e);
}
cout << '\n';


输出:

Container x:
one two three four five six seven eight nine ten


接下来,我们测试几种直接访问方法:

puts("direct access elements:");
cout << format("element at(5): {}\n", x.at(5));
cout << format("element [5]: {}\n", x[5]);
cout << format("element begin + 5: {}\n",
    *(x.begin() + 5));
cout << format("element 5 + begin: {}\n",
    *(5 + x.begin()));
cout << format("element begin += 5: {}\n",
    *(x.begin() += 5));


输出:

direct access elements:
element at(5): six
element [5]: six
element begin + 5: six
element 5 + begin: six
element begin += 5: six


我们使用 ranges::views 管道和 views::reverse 测试容器:

puts("views pipe reverse:");
auto result = x | views::reverse;
for(auto v : result) cout << format("{} ", v);
cout << '\n';


输出:

views pipe reverse:
ten nine eight seven six five four three two one


最后,我们创建一个包含 10 个未初始化元素的 Container 对象 y:

Container<string> y(x.size());
cout << format("Container y size: {}\n", y.size());
for(auto e : y) {
    cout << format("[{}] ", e);
}
cout << '\n';


输出:

Container y size: 10
[] [] [] [] [] [] [] [] [] []


它是如何工作的…

尽管代码很多,但这个迭代器并不比一个更小的迭代器复杂。大部分代码都在运算符重载中,每个重载大多是一行或两行代码。

容器本身由一个智能指针管理。由于它是一个扁平数组,不需要扩展或压缩,这简化了管理。

当然,STL 提供了一个扁平的 std::array 类,以及其他更复杂的数据结构。尽管如此,你可能会发现阐明一个完整的迭代器类的工作原理是有价值的。

Tags:

最近发表
标签列表