迭代器与部分类回顾

该篇是在对于std标准库的迭代器初步接触时所写,只是一些基本的常识以及对于部分类知识的回顾

迭代器

初步了解

​ 迭代器本身其实并没有什么特殊的,我们需要先从它存在的意义开始了解,对于一个迭代器,其的存在意义就是为一系列的容器提供一个通用的接口。通过这个接口,我们能够实现一系列的操作。

基础作用

​ 既然是迭代器,那我我们势必要了解这个迭代到底是针对谁的迭代。在std库中,这个迭代就是针对于数据元素的迭代。简单来说,一个迭代器的基本作用就是实现对于一些数据的访问,至于这些数据到底是什么,相对来说迭代器本身不是多么的关注。也就是说,迭代器其实可以看做是一个相对独立的数据访问机制。其于数据组织结构之间是一种弱耦合的状态的。

​ 通过使用迭代器,我们应该能够实现对于一定数据的访问操作。

出现契机

​ 对于迭代器的出现原因进行一定的了解可以更加方便我们熟悉这个东西。

​ 个人感觉迭代器的出现与设计模式的出现契机有点相似。都是为了对于一系列经验的总结后自然而然出现的东西。由前文知道,我们创建一个迭代器的目的就是为了去进行一个数据的查询,修改等操作,可以预料到这些操作都是相当常见的,如果我们使用常规的设计,我们需要在一个类中去定制特定的数据处理操作。这样其实就是把底层数据组织模块与操作模块进行了一次的强耦合绑定,这种绑定可以预料到时非常难看的。而且复用性很差。

​ 自然而然,我们就需要一种工具来独立进行这种数据操作,通过将数据组织模块和数据处理模块之间的解耦,提高代码复用性的同时减少了在设计时的复杂度,其实也可以看做是一种结构性设计模式。

​ 至此,我们知道了对于迭代器到底应该以怎么样的。同时,我们应该能够理解一些迭代器所应该具有的一些性质。

迭代器实现

​ 到现在,我们还不知道一个迭代器到底是什么。我们直接进入正题。

裸指针

​ 由前面知道,迭代器的基础功能是完成数据的查询,修改等操作。那么根据这些属性,我们最先能够想到的基本数据类型是什么,其实就是裸指针。回想一下,通过对指针进行解引用以及各种运算符的操作,我们是不是能够操作这个指针指向的内存中存在的数据,而且通过对指针本身进行一些操作符的运算,我们能够实现通过一个指针访问到一块数据。

​ 这些个性质其实都是一个迭代器所应该具有的性质,接下来,我们需要进行的就是对于这个裸指针进行2扩展。毕竟,你知道的,裸指针这东西往往是无法满足我们自己的需求的。

​ 相对来说,使用一个类作为迭代器来在实际应用中更加常见,先来看一个demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
template <typename T>
class MyVector{
private:
T* data;
size_t size; //容器当前的大小
size_t capacity; //容器的最大容量

public:
MyVector():size(0),capacity(10)
{
data=new size_t[capacity];;
}
MyVector(size_t s):size(0),capacity(s)
{
data=new size_t[capacity];;
}
~MyVector()
{
delete[] data;
}
void push_back(const T& value) {
if (size >= capacity) {
capacity *= 2;
T* new_data = new T[capacity];
for (size_t i = 0; i < size; ++i) {
new_data[i] = data[i];
}
delete[] data;
data = new_data;
}
data[size++] = value;
}

size_t get_size() const {
return size;
}

// 迭代器内部实现
class Iterator {
private:
T* ptr;

public:
Iterator(T* ptr) : ptr(ptr) {}

// 解引用操作符,返回迭代器指向的元素
T& operator*() {
return *ptr;
}

// 前进操作符,返回迭代器指向的下一个元素
Iterator& operator++() {
++ptr;
return *this;
}

// 比较操作符,用于判断迭代器是否相等
bool operator!=(const Iterator& other) const {
return ptr != other.ptr;
}
};

// 返回一个指向容器第一个元素的迭代器
Iterator begin() {
return Iterator(data);
}

// 返回一个指向容器末尾之后位置的迭代器
Iterator end() {
return Iterator(data + size);
}
};

​ 从此间可以看到,一个迭代器其实并不神秘,就是一个类中类,在这个类中类中,又有着对应的类型指针。这个类型指针又有着一系列的方法。在一些书中,可以看到很多的迭代器类型,这些本质上其实就是一些基于类提供的不同方法所进行的分类。再简单点,就是通过重载方法的不同实现以及所带来的不同功能所进行的分类。

​ 在此时先不用去了解各个类型的迭代器的具体,先来简单过一遍概念即可。

迭代器类型

  1. 输入迭代器

​ 输入是相对于程序来说的。不难推的,一个输入迭代器必须能够使得程序能够读取由这个输入迭代器管理着的数据。

​ 这个体现在迭代器类中的设计中则是解引用符(*)的重载,通过对于这个迭代器进行解引用,程序能够实现对应数据的读取,这也是该迭代器被加上输入这一前缀的原因。

​ 同时,为了单一职责原则以及对于数据的保护,输入迭代器不应该有对于数据的修改权限,这个则是通过不实现对应的修改数据方法实现的。

  1. 输出迭代器

​ 有了上面那个的初了解,我们应该也粗略知道了这个输出所应该的含义。通过这个输出迭代器,我们能够使用容器中的数据作为输出提供给其他需要的地方。跟输入迭代器一样,这里也应该对于迭代器处理数据的权限做一些限制。

​ 对应的是,输出迭代器应该具有数据的修改权限而不具备数据的读取权限,这个的实现也很简单,略。

  1. 正向迭代器

​ 该迭代器与前面俩个迭代器一致,都使用++重载来进行容器的遍历,需要注意的是,这个迭代器类型应该能够是的先前的迭代器能够被保存,同时,这个迭代器还存在着一些对于数据的访问控制。但是这个正向迭代器不像之前的俩个迭代器有统一的规定。你可以根据自己的需求去进行修改。

  1. 双向迭代器

​ 这个就不进行赘诉了,跟正向迭代器都是大同小异的。主要就是相对于来说需要额外添加一个反向的重载运算符。没什么意思就不进行深入了。

​ 5. 随机访问迭代器

​ 这个迭代器具有双向迭代器的所有性质,除此之外,还支持能够进行随机访问。实际上就是对于[]的重载,毕竟指针的[]使用就是我们常用的一种随机访问,通过这种统一能够具有更好的扩展性。

小结

​ 简单来看,上面5种迭代器其实形成了一种比较直观的层次结构。以输入和输出迭代器为基础。在上面包裹了一层正向迭代器,在正向迭代器的基础上进行扩展产生了双向迭代器。最后再对双向迭代器进行包装实现了随机访问迭代器。这种架构下的每一层的实现难度逐渐递增,同时功能也逐渐强大,带来的代价也成正比增加。因此,我们可以根据自己的需求去选择自己需要的迭代器种类去平衡性能与损耗之间的关系。

-------------本文结束 感谢阅读-------------