网站首页 > 技术文章 正文
本文是一个完整的连续/随机访问迭代器的例子。这是容器迭代器中最完整的类型。随机访问迭代器包含了所有其他类型容器迭代器的所有特性,以及它的随机访问能力。
虽然我认为在本章中包含一个完整的迭代器很重要,但这个例子有超过 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 类,以及其他更复杂的数据结构。尽管如此,你可能会发现阐明一个完整的迭代器类的工作原理是有价值的。
猜你喜欢
- 2024-11-13 如何让跨手册的链接发布后不被破坏
- 2024-11-13 直播预告|敲黑板,划重点!智能模型平台知识点讲解
- 2024-11-13 Excel中如何每隔3行求和(隔3行求和公式excel)
- 2024-11-13 铭说 | Sunburst供应链攻击解析(供应链buysell交易模式)
- 2024-11-13 Nginx+keepalived 实现高可用,防盗链及动静分离配置详解
- 2024-11-13 Nginx + FastCGI 程序(C/C++) 搭建高性能web service
- 2024-11-13 数据库 CTREATE INDEX 用法(数据库replicate用法)
- 2024-11-13 点点滴滴学5G—NR CCE起始位置计算方法
- 2024-11-13 聊聊 C++ 中的四种类型转换符(c++4种类型转换)
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)