C++ STL容器初识:迭代器
迭代器,STL标准容器,算法,我会在这一系列的文章中讲解,我们第一节先从迭代器开始,明白了迭代器的基本工作原理,我们以后面对STL容器及算法也就毫无压力了。。。
迭代器初步
引入
到目前你所知道的遍历一个数组的方式有哪几种?
- 方式一:普通for循环遍历(这个应该没有人不会吧)
//1.数组遍历
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
cout << arr[i] << " ";
}
cout << endl;
- 方式二:C++11 基于范围的for语句(不太常见,但是很好用)
//2. 基于范围的for语句
for (auto& x : arr)
{
cout << x << " ";
}
cout << endl;
- 方式三:指针方式(这个我们一般很少用到)
//3. 指针访问
for (int* it = arr; it != arr + sizeof(arr) / sizeof(arr[0]); it++)
{
cout << *it << " ";
}
cout << endl;
这三种方式都能打印出我们的数组:
难道就这几种方式吗?
现在,我们又多了一种方式,马上你就知道了!
自己创建迭代器
什么是迭代器?
迭代器(iterator)是一种抽象的设计概念,现实程序语言中并没有直接对应于这个概念的实物。
iterator模式定义如下:提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。(实际上是封装了指针的类中类)
迭代器的设计思维-STL的关键所在,STL的中心思想在于将容器(container)和算法(algorithms)分开,彼此独立设计,最后再一贴胶着剂将他们撮合在一起。从技术角度来看,容器和算法的泛型化并不困难,c++的class template和function template可分别达到目标,如果设计出两这个之间的良好的胶着剂,才是大难题。
迭代器的种类:
种类 | 功能 | 操作 |
---|---|---|
输入迭代器 | 提供对数据的只读访问 | 只读,支持++、==、!= |
输出迭代器 | 提供对数据的只写访问 | 只写,支持++ |
前向迭代器 | 提供读写操作,并能向前推进迭代器 | 读写,支持++、==、!= |
双向迭代器 | 提供读写操作,并能向前和向后操作 | 读写,支持++、–, |
随机访问迭代器 | 提供读写操作,并能以跳跃的方式访问容器的任意数据,是功能最强的迭代器 | 读写,支持++、–、[n]、-n、<、<=、>、>= |
模仿一个迭代器
我们来模仿一下最简单的迭代器
工作原理:
#include <iostream>
using namespace std;
#define N 10
using Iterator = int*;
Iterator begin(int* arr)
{
return arr;
}
Iterator end(int* arr)
{
return arr + N-1;
}
int main()
{
int arr[N] = { 1,2,3,4,5,6,7,8,9 };
for (Iterator iter = begin(arr); iter != end(arr); iter++)
{
cout << *iter << " ";
}
}
注意:
- 使用Iterator来当作int*的别名,Iterator的意思是迭代器,标准库中有同名函数,注意区分。
- beign:是一个返回数组首元素的地址的函数。(指向首元地址)
- end:是一个返回数组尾元素的下一个地址的函数。(指向尾后地址)
这样写有什么好处呢,迭代器的算法,很多就是使用容器的begin和end函数进行遍历数组的操作的,容器类型是我们之后要学习的一种类型,它包括vector容器,array容器,deque容器,list容器等等,数组也是一个容器,容器类型可以使用很多算法,对我们遍历与操作数据有着很大的帮助,其中vector容器很厉害,它是可以几乎完全替代普通数组的一个数据类型,它不仅包含数组的所有操作,还具有数组不具有的非常多的操作,关于vector容器,我们马上就自己创建一个来试验一下
在这之前,我们不妨自己写一个vector容器来预习一下以后要学的知识。
简单写一个vector容器
vector容器和数组有什么不同之处吗?
它就是一个数组,只不过它还包括一些其他的属性,比如:记录当前元素的个数。
让我们来试着模仿一个vector容器:
#include <iostream>
using namespace std;
template <class T>
class Vector
{
public:
Vector(const int& size = 10) :Maxsize(size), cursize(0)
{
arr = new T[Maxsize]{0}; //创建并且初始化为0
}
~Vector()
{
delete[] arr;
}
void push_back(const T& data)
{
arr[cursize++] = data;
}
public:
using Iterator = T*; //声明迭代器类型
/*
容器应该具有 beign和end方法
*/
Iterator begin()
{
return arr;
}
Iterator end()
{
return arr + cursize;
}
private:
T* arr;
int cursize; //记录当前元素个数
int Maxsize; //最大元素个数
};
int main()
{
Vector<int> a;
for (int i = 0; i < 10; i++)
{
a.push_back(i);
}
for (Vector<int>::Iterator iter = a.begin(); iter != a.end(); iter++)
{
cout << *iter << " ";
}
return 0;
}
我们利用模板类写了一个自定义的Vector类型,并且在Vector里面创建了一个Iterator的迭代器类型,并给这个vector类声明了begin和end方法
。
注意:beign和end方法是所有容器所必须的一个方法,只有它,我们才能使用对器进行一系列的操作。
但我们遍历的时候:我们通过普通的迭代器类型进行for循环的遍历,进行解引用的输出。
思考?我们能否用基于范围的for循环?
for (auto& iter : a)
{
cout << iter<<" ";
}
答案是可以的:
但是!!!! 当我们把begin和end方法去除后,会发现我们无法使用此基于范围的for语句:
由此可见,begin和end提供了访问容器及其他类型的一个重要的方法,必须要有being和end方法!!!!!
我们在尝试反向遍历Vector:
Iterator rbegin()const
{
return arr + cursize - 1;
}
Iterator rend()const
{
return arr - 1;
}
类中类创建迭代器
我们尝试换一种方式,创建一个在类中的迭代器,因为标准库中有很多迭代器都是封装的,我们要给每一种迭代器都创建一些方法与其他属性:
如何创建迭代器?
只需在Vector类里面再创建一个类即可:
class Iterator
{
public:
Iterator(T* arr)
:arr(arr)
{}
~Iterator() {}
Iterator operator++()
{
//前置++
return arr++;
}
Iterator operator++(int)
{
//后置++
Iterator iter = *this;
arr++;
return iter;
}
bool operator!=(const Iterator& other)
{
return this->arr != other.arr;
}
T operator*()
{
return *arr;
}
private:
T* arr;
};
注意我们创建的这个类是类中类,要在Vector类的里面创建,可以给Vector再来一个public段,来存放这个类中类。
像这样,把刚才的单独的using声明改为class类的形式,并且它也有自己的方法。
注意!!!
- 我们创建的这个Iterator类已经不是刚才using声明的Iterator指针类型了,它是一个单独的类,因此需要对他实现遍历Vector的一些基本操作:
重载运算符
。
我们来使用刚才的示例运行它:
for (Vector<int>::Iterator iter = a.begin(); iter != a.end(); iter++)
{
cout << *iter << " ";
}
成功了,我们的类中类创建并且使用成功了。
不仅如此,begin和end函数还具有反向的操作:rbegin和rend分别相反,指向尾元素和超前元素。
class Reverse_Iterator
{
public:
Reverse_Iterator(T* arr)
:arr(arr)
{}
~Reverse_Iterator() {}
Reverse_Iterator operator++()
{
//前置++
return arr--;
}
Reverse_Iterator operator++(int)
{
//后置++
Reverse_Iterator iter = *this;
arr--;
return iter;
}
bool operator!=(const Reverse_Iterator& other)
{
return this->arr != other.arr;
}
T operator*()
{
return *arr;
}
private:
T* arr;
};
注意:和Iterator正向迭代器一样,Reverse_Iterator反向迭代器也具有这些方法:唯一不同的是,反向,顾名思义,它的重载++运算符是从后往前递减的。
另外,rbeign和rend分别要用Reverse_Iterator来定义:
Reverse_Iterator rbegin()const
{
return arr + cursize - 1;
}
Reverse_Iterator rend()const
{
return arr - 1;
}
我们来试着打印它:
for (Vector<int>::Reverse_Iterator iter = a.rbegin(); iter != a.rend(); iter++)
{
cout << *iter << " ";
}
反向迭代器就创建好了!!!
自定义容器Vector存放自定义类型
我们用模板来定义Vector容器,它可推导出任意的标准库中声明的数据类型,如double string char类型,但是能否使用存放自定义类型呢?可以
创建一个自定义的Sheep类:
class Sheep
{
public:
Sheep() = default;
Sheep(const string& name, const int& age)
:name(name), age(age)
{}
~Sheep() {}
friend ostream& operator<<(ostream& out, const Sheep& other)
{
out << other.name << ":" << other.age << endl;
return out;
}
private:
string name;
int age;
};
我们来试着打印:(别忘了重载输出<<运算符,因为自定义类型不像int等类型一样,编译器不认识他,需要你自己写一个输出的<<重载):
int main()
{
Vector<Sheep> a;
a.push_back(Sheep("喜羊羊", 18));
a.push_back(Sheep("美羊羊", 19));
a.push_back(Sheep("懒洋洋", 20));
a.push_back(Sheep("灰太狼", 21));
for (Vector<Sheep>::Iterator it = a.begin(); it != a.end(); it++)
{
cout << *it << " ";
}
return 0;
}
这样,我们的自定义类型sheep就可以放在我们自定义的Vector里面了,并且可以进行访问:
initializer_list聚合初始化列表
你是否想,你自己创建的Vector类能否像标准库中自带的vector那样,进行这种初始化?
我们可以直接在后面用括号进行列表初始化,但是我们自己写的办不到,我们只能使用push_back(尾插)一个一个插入,那么怎么才能像这样直接快速的列表初始化呢?
initializer_list
提供了我们这个方法
头文件:#include <initializer_list>
我们在我们的Vector类中加上一个构造函数:
Vector(const initializer_list<T>& list)
{
for (auto& x : list)
{
arr[cursize++] = x;
}
}
这样我们就能愉快的直接使用列表初始化构造了:
效果显示:
但是,注意,我们的列表初始化构造函数必须是const initializer_list&,否则会出错!!!!
自己创建的容器算法
我们创建一个容器的算法:用来遍历我们的容器:
template <class Iterator,class FUN>
void travel(Iterator begin, Iterator end, FUN func)
{
for (Iterator it = begin; it != end; it++)
{
func(*it);
}
}
void show(int a)
{
cout << a << " ";
}
- 传递参数给func函数,这个函数是我们自定义的。
- 关于算法, 我们以后会深入学习,现在我们先简单的创建一个算法,用来显示容器的所有元素,类似的,这样的算法起始都有自带的,比如copy和for_each算法,这个我们以后再说,
不过标准库自带的算法实现方式和我们定义的算法基本上是一样的
,我们以后会深入讨论:
int main()
{
Vector<int> a{ 1,2,3,4,5,6 };
travel(a.begin(), a.end(), show);
}
运行得:
我们难道为每个算法都要再手动定义一个函数吗,像是show 函数,不,我们可以使lamdba表达式:
travel(a.begin(), a.end(), [](const int& data) {cout << data << " "; });
lamdba表达式:[](const int& data) {cout << data << " "; }
关于lamdba表达式,可以看我之前发布的博文:
lamdba及泛型算法基础
本文来自博客园,作者:hugeYlh,转载请注明原文链接:https://www.cnblogs.com/helloylh/p/17209711.html