STL中的迭代器
来源:互联网 发布:mac如何更改文件夹 编辑:程序博客网 时间:2024/05/19 19:39
最近在学习侯捷老师的STL源码剖析,看到迭代器这里突然有些疑惑,不知道为什么会存在
迭代器? 只知道迭代器是STL库中的算法和容器的胶合剂。
下面我们就来一步一步探究下为什么会存在迭代器。
“指针”对所有的C/C++的程序员来说,一点都不陌生,在接触到C语言的malloc函数
和C++的new关键字后,我们也知道这两个函数返回的都是一个指针,该指针指向我们
所申请的一个“堆”,提到“堆”,就不得不想到“栈”,从C/C++程序设计角度来思考。
“堆”和“栈”的最大区别是“栈”由系统自动分配并且自动回收,而“堆”是程序员
手动申请,并且显示释放。如果程序员不显示释放“堆”,便会造成内存泄露,内存泄漏的
危害大家都知道,严重时会导致系统崩溃。
既然“指针”的使用者一不小心就可能导致内存泄漏,那么我们如何做到使得指针的使用变
得更加安全呢?从C++面向对象的角度来分析,我们有没有可能将“指针”封装起来,使得
用户不直接接触指针,从而使用一个封装后的对象来替代指针的操作呢?
答案是肯定的,“智能指针”(smart pointer)正解决此类问题,尤其是在防止内存泄漏
方面做得非常突出,C++标准库std中提供了一种“智能指针”名为“auto_ptr”先看下面
的代码:
void f() { int *p=new int(42); ////////此处发生异常//// delete p; }
正如上面的代码显示,如果在这两条语句之中发生异常,会导致指针p所指的那块内存
泄露,因为在执行delete之前发生异常,就不会自动释放堆,然而使用auto_ptr对象代替
常规指针,就会自动释放内存,因为编译器能保证出了这个对象的作用域,会自动调用
析构函数。
#include<memory>//auto_ptr的头文件 void f() { auto_ptr<int>p(new int (42)); }
通过以上的分析,我们便对智能指针有了一定的了解,迭代器iterator就是一种智能指针,
它对原始指针进行了封装,并且提供了一些等价于原始指针的操作,做到既方便又安全。
一提到STL,必须要想到其主要的6个组成部件,分别是:容器,算法,迭代器,仿函数,
适配器,和空间配置器。本文主要介绍迭代器,迭代器是连接容器和算法的桥梁,为什么呢?
我们来看个例子。
void Testfind(){ vector<int> v; for (size_t i = 0; i < 10; i++) { v.push_back(i); } vector<int>::iterator it=find(v.begin(), v.end(), 5); if (it != v.end()) cout << "找到了" << endl; else cout << "没找到" << endl;}
上述代码中值得注意的是用到了find函数,find函数原型如下:
template <class InputIterator, class T> InputIterator find (InputIterator first, InputIterator last, const T& val);
参数InputIterator是迭代器,我们实际传的实参为find(v.begin(),v.end(),5);
这样我们的形参迭代器就将算法和容器vector联系起来了,从这个角度来看,
我们可以很容易的理解为什么说迭代器是算法和容器联系的桥梁了。
为什么上面的形参是一个InputItertor,而能够接受实参v.begin()呢?
要回答这个问题,我们就得从迭代器的分类说起。
在STL迭代器主要分为5类,分别是输入迭代器,输出迭代器,前向迭代器,
双向迭代器和随机访问迭代器。
输入迭代器:只读,支持++ == !=;
输出迭代器:只写,支持++;
前向迭代器:读写,支持++ == !=;
双向迭代器:读写,支持++,–,C++的标准库都至少在双向迭代器的层次上。
随机访问迭代器:读写,支持++ ,–,[n] , -n ,< <= > >=;
输入迭代器和输出迭代器,是最低级的迭代器,后面三种迭代器都是对前边的一种派生。
//迭代器的类型struct InputIteratorTag{}; //只读struct OutputIteratorTag{}; //只写struct ForwardIteratorTag :public InputIteratorTag {}; //读写struct BirdirectionalTag :public ForwardIteratorTag{}; //双向struct RandomAccessIteratorTag : public BirdirectionalTag{}; //随机
输入和输出迭代器是最低级的迭代器,后面三种迭代器都是前一种的派生;
想想为什么要对迭代器进行分类呢?主要是泛型算法可以根据不同类别的迭代器,
所具有的不同能力,来实现不同性能的版本,使得能力大的迭代器用于这些算法
时具有更高的效率,较典型的算法就是distance和advance。
回到刚刚的那个问题,为什么实参v.begin()能够与形参InputIterator虚实结合呢?
我们看看下面的代码
class A{}; //相当ForwardIteratorTagclass B :public A{}; //相当BirdirectionalTagclass C : public B{}; //相当RandomAccessIteratorTagvoid Print(A){ cout << "This is base A" << endl;}void Print(C){ cout << "This is base C" << endl;}void TestABC(){ Print(A()); Print(B()); Print(C());}
从上边的代码我们可以看出来,在TestABC函数中,我们调用Print(B()),
即可用派生类对象作为实参传递给以基类类型为形参的函数,所以find
函数之中的v.begin()作为实参,以输入迭代器作为形参,两者可以达到虚实结合的
目的而不会出错。所以说迭代器为各类算法和各类容器提供了一个交互的平台。
在STL中,容器的迭代器被作为容器元素对象或者I/O流的对象的位置指示,因此可以
把它理解为面向对象的指针——– 一种泛型指针 或者通用指针,它不依赖于元素的真实类型。
我们怎么理解“通用”二字的含义呢?
迭代器的“通用”是一种概念上的通用,所有的泛型容器和泛型算法都使用“迭代器”来指示
元素对象,所有的迭代器都具有相同的或类似的接口,但是每一种都有自己的迭代器类型,
毕竟每一种容器的底层存储方式不尽相同,所以迭代器的实现方式就会不同,千万不要
以为存在一种“通用的迭代器”–它可以应用任何类型的容器
注意:切记不能把迭代器与void* 和“基类指针”这样的通用指针混淆
指针代表真正的内存地址,即对象在内存中的存储位置;迭代器则代表元素在容器之中的相对位置。
STL中迭代器原码
struct input_iterator_tag{};//输入迭代器 struct output_iterator_tag{};//输出迭代器 struct forward_iterator_tag:public input_iterator_tag{};//前向迭代器 struct bidirectional_iterator_tag:public forward_iterator_tag{};//双向迭代器 struct random_access_iterator_tag:public bidirectional_iterator_tag{};//随机访问迭代器 //std::iterator,标准迭代器的类模板 template<class Category,class T,class Distance=ptrdiff_t, class Pointer=T*,class Reference=T&> struct iterator//迭代器包含五个常用属性 { typedef Category iterator_category;//迭代器的类型,五种之一 typedef T value_type;//迭代器所指向的元素的类型 typedef Distance difference_type;//两个迭代器的差值 typedef Pointer pointer;//迭代器的原始指针 typedef Reference reference;//迭代器所指向元素的引用 }; //定义iterator_traits,用于提取迭代器的属性,该类的对象不应该让用户看到 template<class Iterator> struct iterator_traits { //下面的操作相当于一个递归的操作,用于递归提取原始指针的相关值 typedef typename Iterator::iterator_category iterator_category; typedef typename Iterator::value_type value_type; typedef typename Iterator::difference_type difference_type; typedef typename Iterator::pointer pointer; typedef typename Iterator::reference reference; }; //针对原始指针的偏特化版本 template<class T> struct iterator_traits<T*> { //相当于递归终止条件 typedef random_access_iterator_tag iterator_category; typedef T value_type; typedef ptrdiff_t diffrence_type; typedef T* pointer; typedef T& reference; }; //针对指向常用的原始指针设计的偏特化版本 template<class T> struct iterator_traits<const T*> { typedef random_access_iterator_tag iterator_category; typedef T value_type; typedef ptrdiff_t diffrence_type; typedef const T * pointer;//重点在这里 typedef const T & reference;//还有这里 }; //定义两个迭代器的差值类型的函数distance_type template<class Iterator> inline typename iterator_traits<Iterator>::difference_type * distance_type(const Iterator&) { return static_cast<typename iterator_traits<Iterator>::difference_type *>(0); } //获取迭代器的value_type函数 template<class Iterator> inline typename iterator_traits<Iterator>::value_type * value_type(const Iterator&) { return static_cast<typename iterator_traits<Iterator>::value_type*>(0); } //求两个一般迭代器之间的距离的函数_distance,供distance函数分类调用 template<class InputIterator> inline typename iterator_traits<InputIterator>::difference_type _distance(InputIterator first,InputIterator last,input_iterator_tag) { typename iterator_traits<InputIterator>::difference_type n=0; while(first!=last) { ++first; ++n; } return n; } //求两个随机访问迭代器之间的距离的函数_distance,供distance函数分类调用 template<class RandomAccessIterator> inline typename iterator_traits<RandomAccessIterator>::difference_type _distance(RandomAccessIterator first,RandomAccessIterator last, random_access_iterator_tag) { return last-first; } //自适应地调用distance函数 template<class InputIterator> inline typename iterator_traits<InputIterator>::difference_type distance(InputIterator first,InputIterator last) { typedef typename iterator_traits<InputIterator>::iterator_category category; //从typename可以看出,category是一个类型 return _distance(first,last,category());//显示调用category类的构造函数,返回该类的一个对象 } /*****下面的函数用于将指针移动n位的方法*/ //一般迭代器求向前移动的方法,与双向迭代器和随机反问迭代器不同 template<class InputIterator,class Distance> inline void _advance(InputIterator& i,Distance n,input_iterator_tag) { while(n--) { ++i; } } //针对双向迭代器移动的方法 template<class BidirectionalIterator,class Distance> inline void _advance(BidirectionalIterator &iter,Distance n, bidirectional_iterator_tag) { if(n>=0)//当n大于0时,向后移动 { while(n--) { ++iter; } } else//当n小于0时,向前移 { while(n++) { --iter; } } } //定义随机访问迭代器移动的方法 template<class RandomAccessIterator,class Distance> inline void _advance(RandomAccessIterator &iter,Distance n, random_access_iterator_tag) { iter+=n; } //自适应的调用advance函数 template<class InputIterator,class Distance> inline void advance(InputIterator &iter,Distance n) { _advance(i,n,iterator_catetory(iter)); }
从上面的代码中不难发现,实现一个迭代器,需要做一下工作,
- 定义5类迭代器的标志位,该标志位用于实现函数的重载,例如求两个迭代器
之间距离的函数distance (it1,it2,tag),移动函数advance(iter,n,tag).这五个标志位
分别为:input_iterator_tag ,output_iterator_tag,forward_iterator_tag,
bidirectional_iterator_tag,random_access_iterator_tag.
2.对于每一个iterator类,都必须包含五个属性,分别为 iterator_category,
value_type,difference_type ,pointer,reference
3.定义一个迭代器的“属性榨汁机”iterator_traits,用于获取iterator中的5个属性值。
4.定义迭代器的操作,分别为:
a.获取iterator的标志——>iterator_category
b.获取两个迭代器差值的类型——>distance_type
c.获取迭代器的原始类型——->value_type
d.求两个迭代器之间的距离——->distance
e.将迭代器移动n位—–>advance
建议一:尽量使用迭代器类型,而不是显示的使用指针,例如,使用
vector< int>::iterator,而不是int* ,虽然它们等价。
建议二:只使用迭代器提供的标准操作,不要使用任何非标准操作,以避免
STL版本更新时,出现不兼容的问题,
建议三:当不会改动容器中元素的值的时候,请使用const迭代器。
- STL中的迭代器
- STL中的迭代器分类
- STL中的迭代器学习
- STL中的迭代器
- STL中的迭代器的类型
- STL中的容器、迭代器与算法
- 一步一步认识C++STL中的迭代器
- STL中的简单容器和迭代器
- 迭代器模拟实现STL中的list
- 试用EVC中的STL
- STL中的remove问题
- g++ 中的stl allocator
- STL中的适配器
- STL中的算法总结
- STL中的型别
- 浅谈STL中的容器
- (转)STL中的适配器
- STL 中的全排列
- spfa的SLF 和 LLL优化算法
- 简单汽车管理系统
- 将CentOS系统默认的python一键自动升级到最新的2.7.13版本
- Java并发编程深入学习
- 使用Genymotion时,想用ADB遇到的问题
- STL中的迭代器
- 学生信息管理系统---SQL语句的中的符号有什么用(二)
- C# 语言历史版本特性(C# 1.0到C# 7.1汇总更新)
- Java 8 中 Date与LocalDateTime、LocalDate、LocalTime互转
- hud 6038 Function
- stm32的外设初始化步骤,以定时器为例。
- 高性能MySQL之Count统计查询
- MyBatis中的动态SQL
- https://unsplash.it API获取图片