C++模板实战6:迭代器
1 迭代器的类型:
输入迭代器 、前向迭代器、双向迭代器、跳转迭代器以及输出迭代器。这五种迭代器的限制条件从左至右越来越强。
2 输入迭代器需满足的条件:
| X u(a); | X可复制构造 |
| u=a; | 可赋值 |
| u==a; | 可比较相等 |
| u!=a; | 可比较不相等 |
| *u; | 可去引用,且若有u==a,*u==*a |
| u->m; | 等价于(*u).m |
| ++u; | 若之前有a==u,则在++u后不一定有++a==u |
| (void)u++; | 等价于(void)++u |
| *u++; | 等价于{X tmp=u; ++u; return tmp;} |
3 前向迭代器满足的条件:
| X u(a); X(); | X可复制构造 |
| X u=a; X(a); X u=a; | 可赋值构造 |
| u==a; | 可比较相等 |
| u=a; | 可赋值 |
| u!=a; | 可比较不相等 |
| *u; | 可去引用,且若有u==a,*u==*a |
| u->m; | 等价于(*u).m |
| ++u; | 若之前有a==u,则一定有++u==++a //这点和输入迭代器不同 |
| u++; | 等价于{X tmp=u; ++u; return tmp;} |
输入迭代器和前向迭代器的++u操作不同,输入迭代器自增后之前和该迭代器相等的都失效了,实例如下:构造一个栈的迭代器(事实上栈不需要迭代器因为其只在栈顶操作),每次栈迭代器自增操作就会弹出栈顶元素,指向新的栈顶元素
#include <iostream>
#include <stack> template<typename Stack>
class stack_iterator
{
public:
typedef typename Stack::value_type value_type;
typedef typename Stack::reference reference; private:
Stack &s;
value_type *pos;
public:
stack_iterator(Stack &_s) : s(_s), pos(_s.size() ? &_s.top() : 0) {}
reference operator * () const {return *pos;}
stack_iterator& operator ++ () {
s.pop();
pos = s.size() ? &s.top() : 0;
return *this;
}
bool operator == (stack_iterator const &rh) const {return pos == rh.pos;}
bool operator != (stack_iterator const &rh) const {return pos != rh.pos;}
}; int main()
{
using namespace std;
int numbers[] = {0, 1, 2, 3, 4};
typedef stack<int> int_stack;
int_stack s;
for (int i = 0; i < 5; ++i) s.push(numbers[i]); stack_iterator<int_stack> a(s);
stack_iterator<int_stack> b(s);
cout<<*a<<" "<<*b<<endl;//a和b指向同一个栈顶元素
cout << ((a == b) ? "a == b" : "a != b") << endl;
++a;//之前的b所指的栈顶元素被弹出去了,a指向新的栈顶
cout<<*a<<" "<<*b<<endl;
++b;//之前a指向的栈顶元素被弹出去了,b执行新的栈顶
cout<<*a<<" "<<*b<<endl;
cout << ((a == b) ? "++a == ++b" : "++a != ++b") << endl; cout << *a << endl; // 危险操作,a所指数据已经出栈,空间已经被释放
}
程序输出:
4 4
a == b
3 4
3 2
++a != ++b
3 //虽然有输出,但是这是危险操作
前向迭代器支持算法多次遍历数据,而输入迭代器在自增操作时可能会使其它迭代器失效。
4 双向迭代器,在前向迭代器基础上增加了后退操作如下:
| --u; | 将迭代器后退一位,只要存在迭代器s满足++s==u,则一定有—u==s |
| u--; | 等价于{X tmp=u; --u; return tmp;} |
5 跳转迭代器(随机迭代器):在双向迭代器基础上增加了跳转指定位操作:
| u+=n; | 当n>0时相当于进行n次++u操作,当n<0时等价于进行n次—u操作 |
| u-=n; | 等价于u+=-n; |
| u+n; n+u; | 等价于{X tmp=u; return tmp+=n;} |
| u-n; | 等价于{X tmp=u; return tmp-=n;} |
| v-u; | 得到两代器v和u间的距离,结果为一整数满足:v=u+n; |
| u[n]; | 等价于*(u+n) |
| u<n; | 等价于u-n<0 |
| u>v; | 等价于v<u |
| u<=v; | 等价于!(u>v) |
| u>=v | 等价于!(u<v) |
6 输出迭代器:与输入迭代器类似,输出迭代器移至下一位置后不能保证和之前相等的迭代器还有效,作用于输出迭代器的算法也只能遍历一次。例如:将一个缓冲区写入文件,当迭代器向前移动时,其它迭代器可能失效,因为数据已经改变了。
| X(a); X u(a); X u=a; | 可复制构造 |
| *u=o; | 去引用所得必须是一个对所指数据的左值引用 |
| ++u; | 迭代器移至下一个位置 |
| u++; | 等价于{X tmp=u; ++u; return tmp;} |
7 如果迭代器能满足输出迭代器的要求,则可称为是可写迭代器,即通过迭代器可以改变其所指数据的值*u=o;否则称为只读迭代器。vector、deque、array提供可写跳转迭代器,list提供可写双向迭代器,forward_list提供可写前向迭代器,set和multiset提供只读双向迭代器(key值不能改变),而map和multimap能修改value但是不能修改key所以迭代器类型不易分类,hash和二叉搜索树也如此。
8 迭代器属性类模板:迭代器出了提供必要的操作外,还需要包含足够的信息描述其本身的属性。标准模板类std::iterator_traits<T>为算法提取迭代器的属性。标准规定了迭代器的五种属性:
1) difference_type迭代器差值类型
2) valule_type迭代器所指数据类型
3) pointer数据指针类型
4) reference数据引用类型
5) iterator_category迭代器所属类型
利用iterator_category算法作者可以根据迭代器类型采取不同的策略,实例如下:模仿std::advance(i,n)
// 适用于前向迭代器的advance函数实现,n次加1操作
template<typename I>
void advance_impl(I &i,
typename std::iterator_traits<I>::difference_type n,
std::forward_iterator_tag)
{for (; n > 0; --n) ++i;} // 适用于跳转迭代器的advance函数实现,直接用i += n
template<typename I>
void advance_impl(I &i,
typename std::iterator_traits<I>::difference_type n,
std::random_access_iterator_tag)
{i += n;} template<typename I>
void advance(I &i, typename I::difference_type n)
{
// 以iterator_category()为哑参数指导编译器选择适当的重载实现,不参与任何实现工作,只用于重载识别,不需要参数名
advance_impl(i, n,
typename std::iterator_traits<I>::iterator_category());
}
同理可以实现std::distance.
9 标准定义的迭代器基类模板,实例如下:
namespace std{
template<typename Category,typename Value,typename Distance=std::ptrdiff_t,
typename Pointer=Value*,typename Reference=Value&>
struct iterator{
using iterator_category=Category;
using value_type=Value;
using difference_type=Distance;
using pointer=Pointer;
using reference=Reference;
};
}
template<typename T>
struct myIterator: public std::iterator<std::forward_iterator_tag,T>//继承标准的迭代器类可以实现简化自己的迭代器代码,并且无需再为指针之类实现自定义迭代器模板特例
{
//...
};
10
反向迭代器模板类reverse_iterator<T>可以构造一个前进方向与原迭代器完全相反的迭代器,其自增操作是实际为后退操作,这在一些反向访问序列算法中非常有用,例如通过向find(i,j,v)传入反向迭代器从而可以找到元素v在序列[i,j)中最后一次出现的位置。值得注意的是:反向迭代器的解引用是其所指位置后退一位的数据引用,这是为了保证左开右闭区间的使用习惯。实例如下:
#include <cstring>
#include <iostream>
#include <iterator>
#include <algorithm> void print(char c) {std::cout << c;}
int main()
{
char array[] = "Madam I'm Adam";
typedef std::reverse_iterator<char*> backward_iterator; using namespace std; for_each(backward_iterator(array + strlen(array)),backward_iterator(array),print);//第一个迭代器指向的是'\0'但是其解引用是'm'。第二个迭代器指向'a'但是其解引用是'M' cout << endl;
return 0;
}
11
插入迭代器:在指定位置前插入元素。三种插入模板类迭代器以容器为模板参数:
1) back_insert_iterator<C> 在容器末端插入数据,容器支持push_back操作
2) front_insert_iterator<C> 在容器前端插入数据,容器支持push_front操作
3) insert_iterator<C> 在容器指定位置插入数据,容器支持insert(i,v)操作
标准实现了三个模板函数用于插入:
1) back_inserter(c)
2) front_inserter(c)
3) inserter(c,i)
实例如下:
void foo() {
using namespace std;
int array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
list<int> il;
// 将array[5]到array[9]插入到il末尾
copy(array + 5, array + 10, back_inserter(il));
// 将array[4]到array[0]插入到il前端,为保证数据顺序不变,需要用到
// reverse_iterator
typedef reverse_iterator<int*> array_reverse_iterator;
copy(array_reverse_iterator(array + 5),array_reverse_iterator(array),front_inserter(il));
}
11
流迭代器:可以直接作用于内存外的数据,如文件/标准输入输出。迭代器器只符合输入迭代器或输出迭代器的要求,因此只能为算法提供单次遍历。
标准定义的流迭代器类模板:
1) istream_iterator 输入流迭代器
2) ostream_iterator输出流迭代器
3) istreambuf_iterator 输出缓冲区迭代器
4) ostreambuf_iterator 输出缓冲区迭代器
istream_iterator和ostream_iterator基于">>"操作符提取/输出数据,而istreambuf_iterator和ostreambuf_iterator基于底层面向字节的函数实现数据提取/输出。
实例如下:
#include<algorithm>
#include<iostream>
#include<iterator>
#include<sstream>
using namespace std;
int main(){
using namespace std;
istringstream s("The quick brown fox jumps over the lazy dog.");
using char_istream_iterator=istream_iterator<char>;
//typedef istreambuf_iterator<char> char_istream_iterator;
cout << count(char_istream_iterator(s),char_istream_iterator(),'o')<<endl; // 统计流中某字符的出现次数
//cout<<count(char_istream_iterator(s),char_istream_iterator(),' ')<<endl;
}
统计'o'在字符串中出现的次数,由于istream_buf基于">>"操作符实现,所以不能统计空格出现的次数。注释的istreambuf_iterator基于sgetc()面向字符处理函数能统计空格出现的次数。
输出流迭代器不支持比较相等,ostream_iterator利用"<<"操作符实现,其输出流在构造时指定,还需接收一个分隔符作为参数,而ostreambuf_iterator基于sputc()面向字符输出,不支持分隔符。当为标准库容器或其它自定义类重载">>"或"<<"用于流迭代器时,一定要把该重载操作符写在std命名空间内,因为流迭代器在std内,重载操作符也应该在std内。实例如下:
#include <iostream>
#include <iterator>
#include <map> namespace std {
// 必须在std命名空间内为pair声明“<<”重载函数
template<typename T0, typename T1>
ostream& operator << (ostream& os, pair<T0, T1> const &p)
{
os << '(' << p.first << ", " << p.second << ')';
return os;
}
} int main()
{
using namespace std;
typedef map<int, char> ic_map;
typedef ic_map::value_type value_type;
// 映射初始值
value_type data[] = {value_type(0, 'a'),
value_type(1, 'b'),
value_type(2, 'c')}; ic_map m(data, data + 3); copy(m.begin(), m.end(),
ostream_iterator<ic_map::value_type>(cout, "\n"));//采用换行符为分隔符
return 0;
}
程序输出:
(0, a)
(1, b)
(2, c)
C++模板实战6:迭代器的更多相关文章
- AngularJS学习--- AngularJS中的模板template和迭代器过滤filter step2 step3
1.AngularJS 模板---step2: mvc(Model-View-Controller)模式在后端用的比较多,在前端也是一样的常用; 在AngularJS中,一个视图是模型通过HTML模板 ...
- 8、泛型程序设计与c++标准模板库3.迭代器
理解迭代器对于理解STL框架并掌握STL的使用至关重要.简单地说,迭代器是面向对象版本的指针,STL算法利用迭代器对存储在容器中的元素序列进行遍历,迭代器提供了访问容器和序列中每个元素的方法. 虽然指 ...
- (42) Aeroo 模板实战
用writer设计一个采购单的模板 我用的是libreoffice 5.2.x 对于这个表格是通过工具栏上的插入指定的表格行和列完成,然后排版 对于单号po00001 这这样插入的 这样就完成一个订单 ...
- velocity模板实战
场景:json配置报文转换遇到的问题:1.json报文转换成map,多节点如何处理?数组如何处理? 2.velocity模板处理数组 3.应用之间rabbitmq通讯map反序列化,数组报错?知识点: ...
- [js高手之路]Node.js模板引擎教程-jade速学与实战1
环境准备: 全局安装jade: npm install jade -g 初始化项目package.json: npm init --yes 安装完成之后,可以使用 jade --help 查看jade ...
- [js高手之路]Node.js模板引擎教程-jade速学与实战1-基本用法
环境准备: 全局安装jade: npm install jade -g 初始化项目package.json: npm init --yes 安装完成之后,可以使用 jade --help 查看jade ...
- [转]C++ template —— 模板基础(一)
<C++ Template>对Template各个方面进行了较为深度详细的解析,故而本系列博客按书本的各章顺序编排,并只作为简单的读书笔记,详细讲解请购买原版书籍(绝对物超所值).---- ...
- C++ template —— 模板基础(一)
<C++ Template>对Template各个方面进行了较为深度详细的解析,故而本系列博客按书本的各章顺序编排,并只作为简单的读书笔记,详细讲解请购买原版书籍(绝对物超所值).---- ...
- C++ 基础知识回顾(string基础、智能指针、迭代器、容器类)
[1] string基础 [1.1] string 的构造 #include <iostream> #include <string> int main() { using n ...
随机推荐
- dom4j解析带命名空间的xml文件
文件内容如下 <ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd=& ...
- 内网环境NTP服务及时间同步(CentOS6.x)配置和部署
目标环境,5台linux centos 6.3, 一台作为NTPD服务与外部公共NTP服务同步时间,同时作为内网的NTPD服务器,其他机器与这台服务做时间同步. 服务器IP 角色 说明 同步方式 ...
- WPF路由事件一:逻辑树和可视树
一.什么是逻辑树 逻辑树就是描述WPF界面元素的实际构成,它是由程序在XAML中所有的UI元素组成.最显著的特点就是由布局控件.或者其他常用的控件组成. <Window x:Class=&quo ...
- WPF教程二:布局之StackPanel面板
应用程序界面设计中,合理的元素布局至关重要,它可以方便用户使用,并将信息清晰合理地展现给用户.WPF提供了一套功能强大的工具-面板(Panel),来控制用户界面的布局.你可以使用这些面板控件来排布元素 ...
- 转载------让IE6 IE7 IE8 IE9 IE10 IE11支持Bootstrap的解决方法
本文是转载及收藏 让IE6 IE7 IE8 IE9 IE10 IE11支持Bootstrap的解决方法 最近做一个Web网站,之前一直觉得bootstrap非常好,这次使用了bootstrap3,在c ...
- ggplot2学习
R语言里面一个比较重要的绘图包——ggplot2,是由Hadley Wickham于2005年创建,于2012年四月进行了重大更新,作者目前的工作是重写代码,简化语法,方便用户开发和使用.ggplot ...
- e655. 混合风格的文本
This example applies a new font and background color to a part of the text. You can apply styles to ...
- linux -- ubuntu修改IP地址、网关、dns
ubuntu系统 一.使用命令设置Ubuntu IP地址 1.修改配置文件blacklist.conf禁用IPV6 sudo vi /etc/modprobe.d/blacklist.conf 表示用 ...
- 【Java面试题】30 子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次,请写出程序。
题目如下: 子线程循环10次,接着主线程循环100,接着又回到子线程循环10次, 接着再回到主线程又循环100,如此循环50次 思路如下: 子线程语主线程为互斥,可用SYNCHRONIZED.很容易想 ...
- Oracle Net Manager 服务命名配置以及用PL/SQL 登陆数据库
我们知道,要连接一个数据库需要知道四个参数: 1. 登陆用户名:user: 2. 登录密码:password: 3. 存放数据库的服务器地址(server_ip)和端口(server_port): 4 ...