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及泛型算法基础

posted @ 2022-09-20 21:29  hugeYlh  阅读(20)  评论(0编辑  收藏  举报  来源