C++ Primer :第十章 :泛型算法之再探迭代器以及其他算法

除了为每个容器定义的迭代器之外,标准库在头文件<iterator>还定义了额外集中迭代器, 包括:

  • 插入迭代器,这些迭代器被绑定到一个容器上,可以向容器插入元素。                                                        
  • 流迭代器,    这些迭代器被绑定到输入或输出流上,可以遍历所关联的IO流 
  • 反向迭代器,这些迭代器向后而不是向前移动,除了forward_list之外的标准库容器都有反向迭代器。
  • 移动迭代器,这些专用得对得起不是拷贝而是移动其中的元素。

插入迭代器

插入迭代器操作:
it = t   在it指定的位置插入值t
*it, ++it, --it

有三种插入迭代器:
  • back_inserter 创建一个使用push_back的迭代器。
  • front_inserter创建一个使用push_front的迭代器。
  • inserter创建一个使用insert的迭代器。此函数接受第二个参数,这个参数必须是一个指向给定容器的迭代器。元素被插入到指定的迭代器所表示的元素之前。
只有容器支持push_back才可以使用back_inserter, 同理,只有容器支持push_front时才可以使用front_inserter

当调用inserter时,得到一个迭代器,接下来使用它时,会将元素插入到这个迭代器所指向的元素之前的位置。即,如果it是由inserter生成的迭代器,则下面这样的赋值语句
*it = val;
效果和下面的代码一样:
it = c.insert(it, val);
++it; // 递增it使它指向原来的元素

而front_inserter生成迭代器的行为和inserter不一样,front_inserter会将传入的元素逆序插入到容器内:

list<int> lst1 = {1, 2, 3, 4};
list<int> lst2, lst3;
copy(lst.begin(), lst.end(), front_inserter(lst2));
copy(lst.begin(), lst.end(), inserter(lst3, llst3.begin()));

我们如果输出lst2和lst3的内容,则可以看到lst2 = 4, 3, 2, 1  而lst3 = 1, 2, 3, 4


iostream迭代器

标准库为iostream定义了可用于这些IO类型对象的迭代器。 istream_iterator读取输入流, ostream_iterator向一个输出流写数据。

1. istream_iterator操作

当创建一个流迭代器时,必须指定迭代器将要读写的对象类型。一个istream_iterator使用 >> 来读取流。因此, istream_iterator要读取的类型必须定义了 >> 运算符。

创建一个istream_iterator时,我们可以将它绑定到一个流。
// 从cin读取string
istream_iterator<string> isiter(cin);

// 从 file读取string
ifstream ifs("file");
istream_iterator<string> isiter1(ifs);

我们还可以默认初始化一个流迭代器,这样就创建了一个尅当做尾后值使用的迭代器。
下面是用从标准输入输入数据,存入一个vector:
vector<int> vec;
istream_iterator<int> in_iter(cin);
istream_iterator<int> end; // istream_iterator尾后迭代器
while(in_iter != end)
    vec.push_back(*in_iter++);

istream_iterator允许懒惰求值, 当我们将一个istream_iterator绑定到一个流时,标准库并不保证我们立即从流中读取数据,我们可以在使用的时候再读。标准库的实现保证的是,在我们第一次解引用迭代器之前,从流中读取数据的操作已经完成, 立即读取还是推迟读取没什么区别。


2.ostream_iterator操作

同样的, 我们可以对任何定义了 << 运算符的类型定义ostream_iterator。当我们创建了一个ostream_iterator时,我们有可选的第二个参数,它必须是一个C风格字符串, 在输出每个元素后都会打印此字符。必须将ostream_iterator绑定到一个指定的流,不允许空的或ostream_iterator来表示尾后迭代器。


我们可以用ostream_iterator来输出值的序列:
ostream_iterator<int> out(cout, " ");
for (auto const e : vec)
    *out ++ = e;
cout << endl;

这个程序会将vec中元素输出,而且元素之间都用" " 隔开。


反向迭代器

反向迭代器就是从容器的尾元素开始向首元素反向移动的迭代器。对于反向迭代器, 递增以及递减的操作会和正常迭代器产生相反的效果。递增一个反向迭代器(++it)会移动到前一个元素, 递减一个反向迭代器(--it),会移动到下一个元素。
除了forward_list之外,其他容器都支持反向迭代器。我们可以调用rbegin、rend、crbegin、crend来获得反向迭代器,这些成员函数返回指向容器尾元素和首元素之前的一个位置的迭代器。例如,逆序打印出vector中的元素:

vector<int> vec{0, 1, 2, 3, 4, 5};
for (auto riter = vec.rbegin(); riter != vec.rend(), ++riter)
    cout << *riter << endl;

我们还可以将一个容器的元素从递增排序变为递减顺序:
// 递增顺序
sort(vec.begin(), vec.end());
// 递减顺序
sort(vec.rbegin(), vec.rend());

从一个string中打印第一个单词,假定string的单词之间都用逗号隔开:
string line{"FIRST", "MIDDLE", "LAST"};
auto comma = find(line.begin(), line.end(), ",");
cout << string(line.cbegin(), comma); 

如果希望打印最后一个单词,我们可以使用反向迭代器:

auto rcomma = find(line.rbegin(), line.rend(), ",");

而我们试图打印最后一个单词时,有意思的事发生了:
cout << string(line.crbegin(), rcomma); 

这时候会输出 : TSAL

因为我们使用的是反向迭代器,所以会反向处理string,如果我们希望按正常顺序打印string,我们通过调用reverse_iterator的base成员来完成这一转换,此成员函数会返回其对应的普通迭代器:

cout << string(rcomma.base(), line.cend()); 

rcomma和rcomma.base()必须生成相邻的位置而不是同一个位置。crbegin()和cend()也是如此。它们表示的是元素的范围。

反向迭代器的目的是表示元素范围,而这些范围是不对称的。这导致一个重要的结果:当我们从一个普通迭代器初始化一个反向迭代器, 或是给一个反向迭代器赋值时,结果迭代器与原迭代器指向的并不是相同的元素。


※ 算法命名规范
接受谓词参数来代替 <或 <=运算符的算法,以及那些不接受额外参数的算法,通常都是重载的函数。

1. _if版本的算法 

	find(beg, end, val);	  // 查找val第一次出现的位置
	find_if(beg, end, pred); // 查找第一个使得谓词pred为真的元素

2. _copy版本的算法
	reverse(beg, end);		// 反转范围中的元素的顺序
	reverse(beg, end, dest);	// 将元素按逆序拷贝到dest

一些算法溶蚀提供_if和_copy版本:
//从v中删除奇数元素
remove_if(v.begin(), v.end(), [] (int i) { return i % 2; });  
// 将偶数元素从v拷贝到v2,v不变。
remove_copy_if(v.begin(), v.end(), back_inserter(v2),  [] (int i) { return i % 2;});




特定容器算法
list和forward_list定义了几个成员函数形式的算法,它们定义了独有的sort、merge、remove、reverse和unique。通用版本的sort要求随机访问迭代器,因此不能用于list和forward_list, 因为这两个类型分别提供双向迭代器和前向迭代器。

lst.merge(lst2);            将来自lst2的元素合并入lst,lst和lst2都必须是有序的
lst.merge(lst2, comp);      元素将从lst2中删除,在合并之后,lst2变为空,第一个版本使用 < 运算符, 第二个版本使用给定的比较操作

lst.remove(val);            调用erase删除掉与给定值相等或令一元谓词为真的每个元素
lst.remove_if(pred);
lst.reverse();              反转lst中元素的顺序
lst.sort();                 使用< 或给定比较操作排序元素
lst.sort(comp); 
lst.unique();               调用erase删除用一个值的连续拷贝,第一个版本使用==, 第二个版本使用给定的二元谓词。
lst.unique(pred);

链表类型还定义了splice成员算法

// lst.splice(args)或flst.splice_after(args)
(p, lst2)            p是一个指向lst中元素的迭代器,或一个指向flst首前位置的迭代器。
                     函数将lst2所有元素移动到lst中p之前的位置或是flst中p之后的位置。将元素从lst2中删除,lst2的类型必须与lst或flst相同,且不能是同一个链表

(p, lst2, p2)        p2是一个指向lst2中位置的有效迭代器,将p2指向的元素移动到lst中,或将p2之后的元素移动到flst中,lst2可以使与lst或flst相同的链表

(p, lst2, b, e)      b和e必须表示lst2中的合法范围,将给定范围中的元素从lst2移动到lst或flst。lst2与lst(或flst)可以是相同的链表,但p不能指向给定范围中的元素


merge和splice会销毁其参数, 通用版本的merge将合并的序列写到一个给定的目的迭代器,两个输入序列式不变的。而链表版本的merge会销毁给定的链表——元素从参数指定的链表中删除,被合并的调用merge的链表对象中,在merge之后,来自两个链表的元素仍然存在,但它们都已在同一个链表中。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值