迭代器学习笔记

内容均为来自于互联网,整理一遍主要是为了加深印象,便于复习。

迭代器引入

迭代器(iterator)有时又称光标(cursor)是程序设计的软件设计模式,可在容器对象(container,例如链表或数组)上遍访的接口,设计人员无需关心容器对象的内存分配的实现细节。

无论是序列容器还是关联容器,最常做的操作无疑是遍历容器中存储的元素,而实现此操作,多数情况会选用“迭代器(iterator)”来实现。那么,迭代器到底是什么呢?

我们知道,尽管不同容器的内部结构各异,但它们本质上都是用来存储大量数据的,换句话说,都是一串能存储多个数据的存储单元。因此,诸如数据的排序、查找、求和等需要对数据进行遍历的操作方法应该是类似的。

既然类似,完全可以利用泛型技术,将它们设计成适用所有容器的通用算法,从而将容器和算法分离开。但实现此目的需要有一个类似中介的装置,它除了要具有对容器进行遍历读写数据的能力之外,还要能对外隐藏容器的内部差异,从而以统一的界面向算法传送数据。

这是泛型思维发展的必然结果,于是迭代器就产生了。简单来讲,迭代器和 C++ 的指针非常类似,它可以是需要的任意类型,通过迭代器可以指向容器中的某个元素,如果需要,还可以对该元素进行读/写操作

迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。迭代器修改了常规指针的接口,所谓迭代器是一种概念上的抽象:那些行为上像迭代器的东西都可以叫做迭代器。然而迭代器有很多不同的能力,它可以把抽象容器和通用算法有机的统一起来。

迭代器提供一些基本操作符:*、++、==、!=、=。这些操作和C/C++"操作array元素"时的指针接口一致。不同之处在于,迭代器是个所谓的复杂的指针,具有遍历复杂数据结构的能力。其下层运行机制取决于其所遍历的数据结构。因此,每一种容器型都必须提供自己的迭代器。事实上每一种容器都将其迭代器以嵌套的方式定义于内部。因此各种迭代器的接口相同,型号却不同。这直接导出了泛型程序设计的概念:所有操作行为都使用相同接口,虽然它们的型别不同。

迭代器类别

参考文档
STL 标准库为每一种标准容器定义了一种迭代器类型,这意味着,不同容器的迭代器也不同,其功能强弱也有所不同。

常用的迭代器按功能强弱分为输入迭代器输出迭代器前向迭代器双向迭代器随机访问迭代器 5 种。
输入迭代器和输出迭代器属于属于流迭代器,后面细说。
(1) 前向迭代器(forward iterator)
假设 p 是一个前向迭代器,则 p 支持 ++p,p++,*p 操作,还可以被复制或赋值,可以用 == 和 != 运算符进行比较。此外,两个正向迭代器可以互相赋值。

(2) 双向迭代器(bidirectional iterator)
双向迭代器具有正向迭代器的全部功能,除此之外,假设 p 是一个双向迭代器,则还可以进行 --p 或者 p-- 操作(即一次向后移动一个位置)。

(3) 随机访问迭代器(random access iterator)
随机访问迭代器具有双向迭代器的全部功能。除此之外,假设 p 是一个随机访问迭代器,i 是一个整型变量或常量,则 p 还支持以下操作:

  • p+=i:使得 p 往后移动 i 个元素。
  • p-=i:使得 p 往前移动 i 个元素。
  • p+i:返回 p 后面第 i 个元素的迭代器。
  • p-i:返回 p 前面第 i 个元素的迭代器。
  • p[i]:返回 p 后面第 i 个元素的引用。

此外,两个随机访问迭代器 p1、p2 还可以用 <、>、<=、>= 运算符进行比较。另外,表达式 p2-p1 也是有定义的,其返回值表示 p2 所指向元素和 p1 所指向元素的序号之差(也可以说是 p2 和 p1 之间的元素个数减一)。
在这里插入图片描述
注意,容器适配器 stack 和 queue 没有迭代器,它们包含有一些成员函数,可以用来对元素进行访问。

尽管不同容器对应着不同类别的迭代器,但这些迭代器有着较为统一的定义方式,具体分为 4 种。
在这里插入图片描述
通过定义以上几种迭代器,就可以读取它指向的元素,*迭代器名就表示迭代器指向的元素。常量迭代器和非常量迭代器的分别在于,通过非常量迭代器还能修改其指向的元素。
反向迭代器和正向迭代器的区别在于:

  • 对正向迭代器进行 ++ 操作时,迭代器会指向容器中的后一个元素;
  • 而对反向迭代器进行 ++ 操作时,迭代器会指向容器中的前一个元素。

注意,以上 4 种定义迭代器的方式,并不是每个容器都适用。有一部分容器同时支持以上 4 种方式,比如 array、deque、vector;而有些容器只支持其中部分的定义方式,例如 forward_list 容器只支持定义正向迭代器,不支持定义反向迭代器。

迭代器测试

//遍历 vector 容器。
#include <iostream>
//需要引入 vector 头文件
#include <vector>
using namespace std;
int main()
{
   vector<int> v{ 1,2,3,4,5,6,7,8,9,10 }; //v被初始化成有10个元素
	cout << "第一种遍历方法:" << endl;
	//size返回元素个数
	for (int i = 0; i < v.size(); i++)
	{
		cout << v[i] << " ";
	}

	cout << endl << "第二种遍历方法:" << endl;
	vector<int>::iterator i;
	//用 != 比较两个迭代器
	for (i = v.begin(); i != v.end(); i++)
	{
		cout << *i << " ";
	}

	cout << endl << "第三种遍历方法:" << endl;
	//用 < 比较两个迭代器
	for (i = v.begin(); i < v.end(); i++)
	{
		cout << *i << " ";
	}

	cout << endl << "第四种遍历方法:" << endl;
	i = v.begin();
	while (i < v.end())
	{
		cout<<*i<<" ";
		i++;
	}
}

测试结果:
在这里插入图片描述

注意使用iterator遍历时,下标取值v[i]的方法就不能用了
在这里插入图片描述

流迭代器–输入迭代器

输出迭代器不整理了,遇到了再说。
参考文档
输入流迭代器是一个可以在文本模式下从流中提取数据的输入迭代器,这意味着不能用它处理二进制流。

一般用两个流迭代器来从流中读取全部的值:指向要读入的第一个值的开始迭代器,指向流的末尾的结束迭代器。在输入流的文件结束状态(End-Of-File,EOF)被识别时,就可以确定结束迭代器。

定义在 iterator 头文件中的 istream_iterator 模板会用提取运算符 >> 从流中读入 T 类型的值。对于这些工作,必须有一个从 istream 对象读取 T 类型值的 operator>>() 函数的重载版本。因为 istream_iterator 是一个输入迭代器,所以它的实例是一个单向迭代器;它只能被使用一次。默认情况下,我们认为这种流包含的是 char 类型的字符。

可以通过传入一个输入流对象到构造函数来生成一个 istream_iterator 对象。istream_iterator 对象有拷贝构造函数。下面是一个生成输入流迭代器的示例:

std::istream_iterator<string> in {std::cin}; // Reads strings from cin
std::istream_iterator<string> end_in;// End-of-stream iterator

默认构造函数会生成一个代表流结束的对象,也就是当 EOF 被识别时的那种对象。

虽然默认情况下,这种流被认为包含的是 char 类型的字符,但能够定义输入流迭代器来读取包含其他类型字符的流。例如,下面展示了如何定义输入流迭代器来读取一个包含 wchar_t 字符的流:

#include <iterator>
std::basic_ifstream<wchar_t> file_in{"no_such_file.txt"};// File stream of wchar_t
std::istream_iterator<std::wstring, wchar_t> in {file_in}; // Reads strings of wchar_t
std::istream_iterator<std::wstring, wchar_t> end_in; // End-of-stream iterator

第一条语句定义了一个 wchar_t 字符的输入文件流。为了读这个文件,第二条语句定义了一个流迭代器。流中的字符类型是由 istream_iteratot 的第二个模板类型参数指定的,在这个实例中是 wchar_t。当然,指定从流中读入的对象的类型的第一个模板类型参数必须是 wstring,它是 wchar_t 字符的字符串类型。

使用std::ostream_iterator或者std::istream_iterator要包含头文件iterator

istream_iterator 对象有下面这些成员函数:

  • opemtor*() 会返回一个流中当前对象的引用,可以运用这个运算符多次以重读相同的值。
  • operator->() 会返回流中当前对象的地址。
  • apemtor++() 会从底层输入流中读取一个值,并将它保存到一个迭代器对象中,返回一个迭代器对象的引用。因此,表达式 *++in的值为最新的被保存的值。这不是一般的用法,因为它可能会跳过流中的第一个值。
  • operator++(int) 会从底层输入流读取一个值,并将它保存到一个迭代器对象中,为使用 operator*()或operator->()访问做准备。在流中的新值被保存之前,这个函数会返回迭代器对象的一个代理。这意味着在读和保存底层输入流中的最新值之前,表达式 *in++的值是保存在迭代器中的对象。

也有非成员函数,operator==() 和 operator!=() 可以比较相同类型的对象。两个输入迭代器是相等的,前提是它们都是同一个流的迭代器或者都是流的结束迭代器;否则,它们就不相等。

测试

#include <iostream>
#include <iterator>
#include <vector>
#include <string>
using namespace std;
int main()
{
	std::cout << "Enter one or more words. Enter . to end:";
	std::istream_iterator<string> in{ std::cin }; // Reads strings from cin
	std::vector<string> words;
	while (true)
	{
		string word = *in;
		if (word == ".") 
		{
			break;
		}
		words.push_back(word);
		++in;
	}
	std::cout << "You entered " << words.size() << "words." << std::endl;
	return 0;
}

循环从标准输入流中读取单词,并把它们添加到 vector 容器中,直到按下回车键。表达式 *in 的值是从底层流读到的当前 string 对象。++in 会导致从流中读取下一个 string 对象,并保存到这个迭代器中。下面是执行这段代码的示例输出:
在这里插入图片描述
将istream_iterator绑定至ifstreamobject,将ostream_iterator绑定至ofstream object读取文件

c++中求某个数据的数据类型
使用typeid()函数

#include <iostream>
#include <typeinfo>
int main()
{
	int product{ 1 };
	std::cout<<typeid(product).name();
}

注意上面{}的使用,我是第一次见。
c++中大括号的使用:
参考文献
在这里插入图片描述
C++大括号初始化和大括号赋值的区别

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙叙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值