C++迭代器

什么是迭代器

迭代器是一个变量,相当于容器和操纵容器的算法之间的中介。迭代器可以指向容器中的某个元素,通过迭代器就可以读写它指向的元素。从这一点上看,迭代器和指针类似
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装

迭代器的定义

迭代器按照定义方式分成以下四种。

  1. 正向迭代器,定义方法如下:
    以vector举例
//容器类名::iterator  迭代器名;
vector<int>::iterator it;
  1. 常量正向迭代器,定义方法如下:
//容器类名::const_iterator  迭代器名;
vector<int>::const_iterator it;
  1. 反向迭代器,定义方法如下:
//容器类名::reverse_iterator  迭代器名;
vector<int>::reverse_iterator rit;
  1. 常量反向迭代器,定义方法如下:
//容器类名::const_reverse_iterator  迭代器名;
vector<int>::const_reverse_iterator rit;

迭代器的使用

通过迭代器可以读取它指向的元素,*迭代器名就表示迭代器指向的元素。通过非常量迭代器还能修改其指向的元素。

迭代器都可以进行++操作。反向迭代器和正向迭代器的区别在于:
正向迭代器进行++操作时,迭代器会指向容器中的后一个元素
而对反向迭代器进行++操作时,迭代器会指向容器中的前一个元素

接下来我们以 vector 为例来演示如何使用迭代器。

#include <vector>
using namespace std;
void PrintVector(const vector<int>& v)
{
	// const对象使用const迭代器进行遍历打印
	vector<int>::const_iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}
int main()
{
	// 使用push_back插入4个数据
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	// 使用迭代器进行遍历打印
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	// 使用迭代器进行修改
	it = v.begin();
	while (it != v.end())
	{
		*it *= 2;
		++it;
	}

	// 使用反向迭代器进行遍历再打印
	vector<int>::reverse_iterator rit = v.rbegin();
	//auto rit = v.rbegin(); C++11支持
	while (rit != v.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;

	PrintVector(v);
	return 0;
}

程序运行结果:
在这里插入图片描述

迭代器的功能分类

不同容器的迭代器,其功能强弱有所不同。容器的迭代器的功能强弱,决定了该容器是否支持 STL 中的某种算法。例如,排序算法需要通过随机访问迭代器来访问容器中的元素,因此有的容器就不支持排序算法。

常用的迭代器按功能强弱分为输入、输出、正向、双向、随机访问五种,这里只介绍常用的三种。

  1. 正向迭代器。假设 p 是一个正向迭代器,则 p 支持以下操作:++p,p++,*p。此外,两个正向迭代器可以互相赋值,还可以用==和!=运算符进行比较。

  2. 双向迭代器。双向迭代器具有正向迭代器的全部功能。除此之外,若 p 是一个双向迭代器,则–p和p–都是有定义的。–p使得 p 朝和++p相反的方向移动。

  3. 随机访问迭代器。随机访问迭代器具有双向迭代器的全部功能。若 p 是一个随机访问迭代器,i 是一个整型变量或常量,则 p 还支持以下操作:
    p+=i:使得 p 往后移动 i 个元素。
    p-=i:使得 p 往前移动 i 个元素。
    p+i:返回 p 后面第 i 个元素的迭代器。
    p-i:返回 p 前面第 i 个元素的迭代器。
    p[i]:返回 p 后面第 i 个元素的引用。
    在这里插入图片描述

迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

对于vector可能会导致其迭代器失效的操作

  1. 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。
#include <iostream>
using namespace std;
#include <vector>
int main()
{
	vector<int> v{ 1,2,3,4,5,6 };

	auto it = v.begin();

	// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
	// v.resize(100, 8);

	// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
	// v.reserve(100);

	// 插入元素期间,可能会引起扩容,而导致原空间被释放
	// v.insert(v.begin(), 0);
	// v.push_back(8);

	// 给vector重新赋值,可能会引起底层容量改变
	v.assign(100, 8);

	/*
	出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,
   而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的
   空间,而引起代码运行时崩溃。
	解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新
   赋值即可。
	*/
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}
  1. 指定位置元素的删除操作–erase
#include <iostream>
using namespace std;
#include <vector>
int main()
{
	int a[] = { 1, 2, 3, 4 };
	vector<int> v(a, a + sizeof(a) / sizeof(int));
	// 使用find查找3所在位置的iterator
	vector<int>::iterator pos = find(v.begin(), v.end(), 3);
	// 删除pos位置的数据,导致pos迭代器失效。
	v.erase(pos);
	cout << *pos << endl; // 此处会导致非法访问
	return 0;
}
  1. 注意:Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。
// 1. 扩容之后,迭代器已经失效了,程序虽然可以运行,但是运行结果已经不对了
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	for (size_t i = 0; i < v.size(); ++i)
		cout << v[i] << " ";
	cout << endl;
	auto it = v.begin();
	cout << "扩容之前,vector的容量为: " << v.capacity() << endl;
	// 通过reserve将底层空间设置为100,目的是为了让vector的迭代器失效
	v.reserve(100);
	cout << "扩容之后,vector的容量为: " << v.capacity() << endl;

	// 经过上述reserve之后,it迭代器肯定会失效,在vs下程序就直接崩溃了,但是linux下不会
	// 虽然可能运行,但是输出的结果是不对的
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}
//程序输出:
//1 2 3 4 5
//扩容之前,vector的容量为: 5
//扩容之后,vector的容量为 : 100
//0 2 3 4 5 409 1 2 3 4 5
// 2. erase删除任意位置代码后,linux下迭代器并没有失效
// 因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的
#include <vector>
#include <algorithm>
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	vector<int>::iterator it = find(v.begin(), v.end(), 3);
	v.erase(it);

	cout << *it << endl;
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}
//程序可以正常运行,并打印:
//4
//4 5

// 3: erase删除的迭代器如果是最后一个元素,删除之后it已经超过end
// 此时迭代器是无效的,++it导致程序崩溃
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	// vector<int> v{1,2,3,4,5,6};
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			v.erase(it);
		++it;
	}
	for (auto e : v)
		cout << e << " ";
	cout << endl;
	return 0;
}
//========================================================
 使用第一组数据时,程序可以运行
//[sly@VM - 0 - 3 - centos 20220114]$ g++ testVector.cpp - std = c++11
//[sly@VM - 0 - 3 - centos 20220114]$ . / a.out
//1 3 5
//======================================================== =
 使用第二组数据时,程序最终会崩溃
//[sly@VM - 0 - 3 - centos 20220114]$ vim testVector.cpp
//[sly@VM - 0 - 3 - centos 20220114]$ g++ testVector.cpp - std = c++11
//[sly@VM - 0 - 3 - centos 20220114]$ . / a.out
//Segmentation fault
  1. 与vector类似,string在插入+扩容操作+erase之后,迭代器也会失效
#include <string>
void TestString()
{
	string s("hello");
	auto it = s.begin();
	// 放开之后代码会崩溃,因为resize到20会string会进行扩容
	// 扩容之后,it指向之前旧空间已经被释放了,该迭代器就失效了
	// 后序打印时,再访问it指向的空间程序就会崩溃
	//s.resize(20, '!');
	while (it != s.end())
	{
		cout << *it;
		++it;
	}
	cout << endl;
	it = s.begin();
	while (it != s.end())
	{
		it = s.erase(it);
		// 按照下面方式写,运行时程序会崩溃,因为erase(it)之后
		// it位置的迭代器就失效了
		// s.erase(it);
		++it;
	}
}

迭代器失效解决办法:在使用前,对迭代器重新赋值即可

反向迭代器的实现

反向迭代器实现的核心思想是借助迭代器适配器,通过正向迭代器适配出反向迭代器
反向迭代器在设计时,为了最求极致的对称,rbegin() 指向最后一个有效元素的下一个位置,rend() 指向第一个有效元素(位置是与正向迭代器相反的)
在这里插入图片描述

具体实现(这里引入了多参数,通过对形参传递不同的对象,变换为不同属性的迭代器):

#pragma once

namespace zha123
{
	template<class Iterator, class Ref, class Ptr>
	struct ReverseIterator
	{
		typedef ReverseIterator<Iterator, Ref, Ptr> Self;
		Iterator _cur;

		ReverseIterator(Iterator it)
			:_cur(it)
		{}

		Ref operator*()
		{
			Iterator tmp = _cur;
			--tmp;
			return *tmp;
		}

		Ptr operator->()
		{
			return &(operator*());
		}

		Self& operator++()
		{
			--_cur;
			return *this;
		}

		Self& operator++(int)
		{
			Iterator tmp = cur;
			--_cur;
			return *tmp;
		}

		Self& operator--()
		{
			++_cur;
			return *this;
		}

		Self& operator--(int)
		{
			Iterator tmp = cur;
			++_cur;
			return *tmp;
		}

		bool operator!=(const Self& s)
		{
			return _cur != s._cur;
		}
	};
}

之后的list我们也会讲到多个模板参数的问题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++ 中的迭代器(Iterator)是一种泛型指针,它允许对容器中的元素进行遍历。迭代器提供了一种统一的方法来访问容器中的元素,而不必考虑容器的实现细节。迭代器可以用于任何标准 C++ 容器,如数组、向量、链表、集合、映射等。 迭代器分为五种类型,分别是: 1. 输入迭代器(Input Iterator):只读,只能单向移动,可以比较判等和自增。 2. 输出迭代器(Output Iterator):只写,只能单向移动,可以比较判等和自增。 3. 前向迭代器(Forward Iterator):可读写,只能单向移动,可以比较判等和自增。 4. 双向迭代器(Bidirectional Iterator):可读写,可双向移动,可以比较判等和自增、自减。 5. 随机访问迭代器(Random Access Iterator):可读写,可随机访问,可以比较大小、判等和自增、自减、加减整数。 以下是一个使用迭代器遍历 vector 容器的示例代码: ```c++ #include <iostream> #include <vector> int main() { std::vector<int> v = {1, 2, 3, 4, 5}; for (auto it = v.begin(); it != v.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl; return 0; } ``` 在上述代码中,使用 `v.begin()` 获取 vector 容器的起始迭代器,使用 `v.end()` 获取 vector 容器的结束迭代器,然后使用 for 循环遍历容器中的每个元素。使用 `*it` 获取当前迭代器指向的元素的值,即可输出容器中的元素。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值