STL—Iterator的分类和copy的重载及其使用

本文介绍了C++ STL中的迭代器,包括基本迭代器如iterator、reverse_iterator、const_iterator和const_reverse_iterator,以及插入型迭代子back_insert_iterator、front_insert_iterator和insert_iterator。此外,还讲解了适配器函数back_inserter、front_inserter和inserter的作用,以及流迭代器和Copy重载的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Iterator是什么

简单的来说,iterator是C++中容器的访问接口,在STL不需要关心容器内如何实现的,C++标准库中有五种预定义的迭代子,其中功能最为强大的是随机访问迭代子,在上次博文中提到了输入流迭代器和输出流迭代器,在标准库定义中五种迭代子越往下功能越强大,但是不会继承。
上次博文中对于泛式算法的分类并没有提到,这次简明提一下:
泛式算法通常是返回一个迭代子,使用迭代子间接操作容器元素,泛式算法分为以下几种:

  1. 变化序列算法
  2. 非变化序列算法
  3. 数字型算法
    在这里插入图片描述
    现在我们看一看迭代器分类及其作用

基本迭代器 //正序 逆序 普通 常量

系统提供的四种迭代器:

  1. iterator
  2. reverse_iterator//反向遍历变量数据
  3. const_iterator
  4. const_reverse//反向遍历常量数据
我们以遍历函数作为例子来介绍四种迭代器:
template<typename Container>//遍历数据
void Show(Container& con)
{
	Container::iterator it = con.begin();
	while (it != con.end())
	{
		std::cout << *it << " ";
		it++;
	}
	std::cout << std::endl;
}

我们知道,迭代器是容器的外部接口,在上面这个例子中,使用容器自带的begin()接口返回首元素的迭代器类型,从而遍历整个容器,可以看到我们使用了函数模板操作,这可以让本函数适用于所有顺序容器(关联容器因为内存不连续,所以无法++操作)

template <typename Container>//遍历常量数据,表示数据的值不能改变
void Show(const Container& con)
{
	Container::const_iteraior it = con.begin();
	while(it != con.end())
	{
		std::cout<<*it<<" "'
	}
	std::cout<<std::endl;
}
template <typename Continer>
void Show(const Continer& con)
{
	Continer::iterator it = con.begin();//此时运行到此行会报错,因为普通指针指向一个const修饰的容器对象
    这会有修改的风险,是不可以的。
}
template <typename Continer>
void Show(const Continer& con)
{
	const Continer::iterator it = con.begin();//此时运行到此行会报错,这里的const表示it的指向不能变,但是值可以变
    所以不能使用
    这会有修改的风险,是不可以的。
}

const_iterator,常量迭代器,就和这个名字一样,是对常量容器进行遍历的操作,这涉及到如果容器存储的数据是由const修饰的,那么普通迭代器获取这个常量元素时就会报错,需要使用const_iterator才可以进行相关操作。

template <typename Container>
void Show(Container& con)//这是对常变量进行反向遍历的方法
{
	Container::reverse_iterator rit = con.rbegin();
	while(rit != con.rend())
	{
		std::cout << *rit << " ";
		rit++;
	}
	std::cout<<std::endl;
}

reverse_iterator 反向迭代器,实现了对容器元素反向遍历,使用了容器自带的rbegin()&rend(),这两个接口,rbegin的作用是本元素的最后一个有效元素,我们知道end()指向的是最后一个有效元素的后一个无效的地址,反过来如果需要反向遍历,那么我们就不能使用end(),这会导致遍历出错,rend()指向的是容器首元素的前一个无效地址,这样,我们在体感上就和正常方法遍历无异,可以说制作这个接口的人也是很用心了。

template <typename Container>
void Show(const Container& con)//这是对常变量进行反向遍历的方法
{
	Container::const_reverse_iterator rit = con.rbegin();
	while(rit != con.rend())
	{
		std::cout << *rit << " ";
		rit++;
	}
	std::cout<<std::endl;
}

const_reverse_iterator 常量反向迭代器,实现了反向迭代常量元素的操作。

插入迭代子,插入适配器函数,流迭代子和Copy重载

插入型迭代子:用输出迭代子生成一个容器元素序列,用插入型迭代子进行元素添加而不需要重写元素这个输出迭代器,有以下三种迭代子
back_insert_iterator//插入迭代器
front_insert_iterator
insert_iterator

back_insert_iterator

是输出迭代子,用来将产生的元素插入到type类型的容器元素序列的尾部,这个和字符串调用strcat()函数很像,在末端补上同type的容器元素。
我们上一个自己实现的back_insert_iterator

template <typename Container>
class My_back_insert_iterator//后插型迭代器
{
private:
	Container * pcon;
public:
	My_back_insert_iterator(Container & con)
	{
		pcon = &con;
	}
	My_back_insert_iterator<Container>& operator*()
	{
		return *this;
	}
	My_back_insert_iterator<Container>& operator=(const typename Container::value_type& val)
	{
		//赋值运算符重载返回值必定是一个对象的引用//对象有时也可以,但是会降低效率
		//每个容器都有value_type这个调用可以分析当前容器的元素类型
		//传入模板时需要声明typename告诉编译器
		pcon->push_back(val);
		return *this;
	}
};
插入型迭代器
//std::back_insert_iterator<std::vector<int>> bii(vec);
//My_back_insert_iterator<std::vector<int>> bii(vec);
//*bii = 20;//后插型迭代器对于解引用和不解引用相同,放上解引用重载操作是因为指针都有解引用操作
//Show(vec);

在back_insert_iterator中有一个迭代器类型的指针pcon,给目标容器“尾插”元素是由赋值运算符的重载完成的,内部调用了容器自带的接口push_back(),同时实现了解引用运算符的重载,在back_insert_iterator中,解引用返回的类型依旧是back_insert_iterator类型,这说明无论是否使用解引用操作,效果都是一样的,加上解引用运算符重载的原因是保证一致性,体感上和普通指针无异。

front_insert_iterator

是输出迭代子,将产生的元素以逆序的形式插入到被控容器之前,每次都在头部插入,所以插入一串元素的时候,看起来就像是逆序一样。

template <typename Container>
class My_front_insert_iterator//前插型迭代器
{
private:
	Container *pcon;
public:
	typedef My_front_insert_iterator<Container> _Myt;
	My_front_insert_iterator(Container& con)
	{
		pcon = &con;
	}
	_Myt& operator*()
	{
		return *this;
	}
	_Myt& operator=(const typename Container::value_type& val)
	{
		pcon->push_front(val);
		return *this;
	}
};
//My_front_insert_iterator<std::list<int>>bii(list1);
//*bii = 0;

可见,我们调用了容器内部的push_front操作实现前插,在顺序容器中我们并没有push_front接口,vector无法使用front_insert_iterator,解引用运算符重载和前面的道理相同。

insert_iterator

输出迭代子,他用来将产生的元素插入到一个由迭代子决定的元素的前面,这里需要两个参数,一个被控容器对象,一个迭代子。

template <typename Container>//插入迭代器
class My_insert_iterator
{
private:
	Container *pcon;
	typename Container::iterator pit;//使用模板内的数据需要声明typename
public:
	typedef My_insert_iterator<Container> _Myi;
	My_insert_iterator(Container& con,typename Container::iterator& it)
	{
		pcon = &con;
		pit = it;
	}
	_Myi& operator*()
	{
		return *this;
	}
	_Myi& operator=(const typename Container::value_type& val)
	{
		pcon->insert(pit,val);
		return *this;
	}
};
	//std::insert_iterator<std::list<int>>bii(list1,list1.begin());
	//My_insert_iterator<std::list<int>>bii(list1,list1.begin());
	//*bii = 33;
	//Show(list1);

我们在insert_iterator中的赋值运算符重载中调用了被控容器的insert接口,从而插入元素到迭代子指定的元素前面,解引用也和前面的原理相同,这里有个注意的一点,如果在类模板中声明类模板时,需要添加typename关键字告知这是一个模板类型而不是什么自定义的变量/类名类型。
总结:

  1. 三种插入迭代器的适用范围仅限于顺序容器,对于内存不连续的容器会报错
  2. 三种插入迭代器都是调用了被控容器内部的接口进行实际操作,适用场景和被控容器的适用场景相同
  3. 插入型迭代器实现了对原容器的保护,不对原容器进行修改,重写,保证了封装性
适配器函数-返回插入型迭代子

back_inserter返回了一个back_insert_iterator迭代子,可以说是适配了back_insert_iterator这个迭代子的一个函数,实际作用就是用push_back代替了赋值操作符

template <typename Container>
std::back_insert_iterator<Container> My_back_inserter(Container &con)
{
	return std::back_insert_iterator<Container>(con);//显式生成一个临时对象,返回的是局部对象,不能返回局部对象的引用
	//编译器优化并不会生成两个临时对象,而是以生成临时对象的方式生成返回值对象
}
My_back_inserter(vec) = 33;

front_inserter返回了一个front_insert_iterator迭代子,可以说是适配了front_insert_iterator这个迭代子的一个函数,实际作用就是用push_front代替了赋值操作符

template <typename Container>
std::front_insert_iterator<Container> My_front_inserter(Container &con)
{
	return std::front_insert_iterator<Container>(con);//显式生成一个临时对象,返回的是局部对象,不能返回局部对象的引用
	//编译器优化并不会生成两个临时对象,而是以生成临时对象的方式生成返回值对象
}
My_front_inserter(list1) = 99;

inserter返回了一个insert_iterator迭代子,要求两个参数,一个是容器本身一个是指定的迭代子,并且指定的迭代子并不是保持不变的,而是随着每次插入而递增,这样可以保证每个元素都能被顺序插入。
可以说是适配了insert_iterator这个迭代子的一个函数,实际作用就是用insert代替了赋值操作符

template <typename Container>
std::insert_iterator<Container> My_inserter(Container &con,typename Container::iterator pit)
{
	return std::insert_iterator<Container>(con,pit);
}
My_inserter(list1,list1.begin());

总结:

  1. 返回了插入型迭代器,插入型迭代器又返回了容器内部提供的调用接口push_back push_front insert,代替了赋值操作符。
  2. 以后插适配器为例,先从左边将容器解引用,在用赋值运算符重载调用push_back函数最后返回的是一个back_insert_iterator,实现了对容器的插入操作
流迭代器&Copy重载
template < typename InputIterator, 
	       typename OutputIterator >
OutputIterator My_copy(InputIterator first1,InputIterator last,OutputIterator first2)
{
	for(first1;first1 != last;++first1)
	{
		*first2 = *first1
	}
	return first2;
}
	流式迭代器
	//std::vector<int>vce1;
	//copy(std::istream_iterator<int>(std::cin),std::istream_iterator<int>(),std::back_insert_iterator<std::vector<int>>(vce1));
	输入流迭代器的使用,想要退出输入必须用非同类型数据抛出异常后可以输出数据
	第三个参数必须是插入型迭代器,指明怎么插入数据
	//std::ofstream test("vce.txt");//生成文件,在头文件#include<fstream>中
	//copy(vce1.begin(),vce1.end(),std::ostream_iterator<int>(std::cout,","));//将数据输出到cout流中
	//copy(vce1.begin(),vce1.end(),std::ostream_iterator<int>(test," "));//第二个参数是用来控制每个数据之间的间隔,是字符串类型
	输出流迭代器的应用,将输出流迭代器和文件绑定,输出数据到文件里
	Show(vce1);

这里的输入输出是针对缓冲区来说的,往缓冲区内写数据称为输入,从缓冲区读数据称为输出

这里实现了对copy的重载,前两个参数类型必须相同,第三个参数为输出流迭代器(插入型迭代器本身即是输出迭代器),这时,可以说我们的copy是一个函数对象,第三个参数控制了如何插入数据,比如上面提到的输出到cout和输出到文件中去
第一个copy中,第一个输入流迭代器传入cin,表示从标准输入流中接收数据。
例如第三个copy,前两个是对已有容器的首尾迭代器,控制输出的范围。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值