【C++ primer】第10章 泛型算法 (2)


Part II: The C++ Library
Chapter 10. Generic Algorithms


10.3 再探迭代器

除了为每个容器定义的迭代器外,库在 iterator 头文件中还定义了其他几种迭代器:

  • 插入迭代器 (insert iterator):与一个容器绑定,可用来向容器插入元素。
  • 流迭代器 (stream iterator):与输入或输出流绑定,可用来遍历所相关的 IO 流。
  • 反向迭代器 (reverse iterator):这些迭代器反向移动,从尾元素向首元素遍历容器。除了 forward_list 之外的库容器都有反向迭代器。
  • 移动迭代器 (move iterator):这些专用的迭代器移动其中的元素,而不是复制它。(第13章)

插入迭代器

插入器是一种迭代适配器,接受一个容器,生成一个迭代器,实现增加元素到特定容器这个功能。
当通过插入迭代器赋值时,该迭代器调用容器操作,在给定容器的指定位置增加元素。

表10.2 插入迭代器操作

操作说明
it = t在 it 指定的当前位置插入值 t。
假设 c 是 it 绑定的容器,根据插入迭代器的类型,调用 c.push_back(t)、c.push_front(t) 或 c.insert(t, p),
其中 p 是传递给 inserter 的迭代器位置。
*it, ++it, it++这些操作存在但不会对 it 做什么。每个操作返回 it。

三种插入器:

  • back_inserter:创建一个使用 push_back 的迭代器。
  • front_inserter:创建一个使用 push_front 的迭代器。
  • inserter:创建一个使用 insert 的迭代器。该函数接受第二个参数,必须是一个指向给定容器的迭代器。将元素插入到给定迭代器指定元素的前面。

仅当容器有 push_front 时,才能使用 front_inserter。
仅当容器有 push_back 时,才能使用 back_inserter。

如果 it 是由 inserter 生成的迭代器,

*it = va1;

上面的赋值效果与下面一样,

it = c.insert(it, val); // it points to the newly added element
++it; // increment it so that it denotes the same element as before 

当使用 front_inserter 时,元素总是插入到容器第一个元素的前面。

list<int> lst = {1,2,3,4};
list<int> lst2, lst3;     // empty lists
// after copy  completes, lst2 contains 4 3 2 1
copy(lst.cbegin(), lst.cend(), front_inserter(lst2));
// after copy completes, 1st3 contains 1 2 3 4
copy(lst.cbegin(), lst.cend(), inserter(lst3, lst3.begin()));

iostream 迭代器

istream_iterator 读取输入流。ostream_iterator 写入输出流。这些迭代器将它们对应的流当作特定类型元素的序列。

表10.3 istream_iterator 操作

操作说明
istream_iterator<T> in(is);in 从输入流 is 读取类型 T 的值。
istream_iterator<T> end;读取类型 T 的值的 istream_iterator 的尾后迭代器。
in1 == in2in1 和 in2 必须读取相同类型。如果它们都是尾后迭代器或绑定相同的输入流,则相等。
in1 != in2同上
*in返回从流中读取的值。
in->mem(*in).mem
++in, in++使用元素类型的 >> 操作符从输入流中读取下一个值。前置版本返回递增后迭代器的引用,后置版本返回旧值。

表10.4 ostream_iterator 操作

操作说明
ostream_iterator<T> out(os);out 将类型 T 的值写入输出流 os。
ostream_iterator<T> out(os,d);out 将类型 T 的值后加上 d,写入输出流 os。d 指向一个空字符结尾的字符串数组。
out = val使用 << 操作符将 val 写入 out 绑定的 ostream。val 的类型必须与 out 可写的类型兼容。
*out, ++out, out++这些操作存在,但不对 out 作任何事。每个操作都返回 out。

istream_iterator 上的操作

istream_iterator 读取的类型必须定义了输入运算符 >>。

istream_iterator<int> int_it(cin);   //   reads ints from cin
istream_iterator<int> int_eof;       //   end iterator value
ifstream in("afile");
istream_iterator<string> str_it(in); //   reads strings from "afile"

下面的程序是使用 istream_iterator 从标准输入读取数据,存入 vector。

istream_iterator<int> in_iter(cin);  // read ints from cin
istream_iterator<int> eof;           // istream ''end'' iterator
while (in_iter != eof)  // while there's valid input to read
	// postfix increment reads the stream and returns the old value of the iterator
	// we dereference that iterator to get the previous value read from the stream
	vec.push_back(*in_iter++);

可以将上面的程序重写成如下形式:

istream_iterator<int> in_iter(cin), eof;  // read ints from cin
vector<int> vec(in_iter, eof);  // construct vec from an iterator range

在算法中使用流迭代器

istream_iterator<int> in(cin), eof;
cout << accumulate(in, eof, 0) << endl;	// generate the sum of values read from the standard input

允许 istream_iterator 使用惰性求值 (Lazy Evaluation)

当将 istream_iterator 绑定到流上时,不能保证会立即读取流。
允许延迟读取流,直到使用迭代器为止。保证在第一次解引用迭代器之前,流已经读取。

ostream_iterator 上的操作

可以对任何具有输出操作符 >> 的类型定义 ostream_iterator。

ostream_iterator<int> out_iter(cout, " ");
for (auto e : vec)
	*out_iter++ = e;  // the assignment writes this element to cout
cout << endl;

向 out_iter 赋值时,解引用符和递增符都可以忽略,因为 * 和 ++ 运算符不对 out_iter 作任何事。上面的循环可重写成:

for (auto e : vec)
	out_iter = e;  // the assignment writes this element to cout
cout << endl;

但是,推荐第一种形式。因为这种写法与其他迭代器类型的使用方式一致;且更易读。

通过调用 copy 打印 vec 中的元素:

copy(vec.begin(), vec.end(), out_iter);
cout << endl;

在类类型中使用流迭代器

istream_iterator<Sales_item> item_iter(cin), eof;
ostream_iterator<Sales_item> out_iter(cout, "\n");
// store the first transaction in sum and read the next record
Sales_item sum = *item_iter++;
while (item_iter != eof) {
	// if the current transaction (which is stored in item_iter) has the same ISBN
	if (item_iter->isbn() == sum.isbn())
		sum += *item_iter++; // add it to sum and read the next transaction
	else {
		out_iter = sum;      // write the current sum
		sum = *item_iter++;  // read the next transaction
	}
}
out_iter = sum;  // remember to print the last set of records

反向迭代器

反向迭代器中递增(和递减)的含义与原本的相反。
递增 ++it 反向迭代器可将迭代器移至上一个元素;递减 --it 将迭代器移至下一个元素。

可以通过调用 rbeginrendcrbegincrend 成员获取反向迭代器。这些成员返回反向迭代器,指向容器内的尾元素和首元素前一个位置。

vector<int> vec = {0,1,2,3,4,5,6,7,8,9};
// reverse iterator of vector from back to front
for (auto r_iter = vec.crbegin(); // binds r_iter to the last element
	r_iter != vec.crend();  // crend refers 1 before 1st element
	++r_iter)               // decrements the iterator one element
	cout << *r_iter << endl;      // prints 9, 8, 7,... 0 

通过向 sort 传递一对反向迭代器,实现 vec 的递减排序:

sort(vec.begin(), vec.end()); // sorts vec in ''normal'' order
// sorts in reverse: puts the smallest element at the end of vec
sort(vec.rbegin(), vec.rend());

反向迭代器需要递减运算符

只能从支持 ++-- 的迭代器定义反向迭代器。

不能从 forward_list 或流迭代器创建反向迭代器。

反向迭代器与其他迭代器的关系

string line = "FIRST,MIDDLE,LAST";
// find the first element in a comma-separated list
auto comma = find(line.cbegin(), line.cend(), ',');
cout << string(line.cbegin(), comma) << endl;	// print "FIRST"

如果想要打印 line 中的最后一个单词:

// find the last element in a comma-separated list
auto rcomma = find(line.crbegin(), line.crend(), ',');

// WRONG: will generate the word in reverse order
cout << string(line.crbegin(), rcomma) << endl;	// print "TSAL"

将 rcomma 转换成普通迭代器,能在 line 中正向移动。
可以通过调用 reverse_iterator 的 base 成员,返回其对应的普通迭代器。

// ok: get a forward iterator and read to the end of line
cout << string(rcomma.base(), line.cend()) << endl;	// print "LAST"

从技术上来说,普通迭代器和反向迭代器之间的关系反映了左闭合区间的特性。
rcomma 和 rcomma.base() 必须生成相邻位置,而不是同一个位置。


10.4 泛型算法的结构

表10.5 迭代器类别 (Iterator Category)

迭代器说明
输入迭代器只读,不写;单遍扫描,只能递增
输出迭代器只写,不读;单遍扫描,只能递增
前向迭代器可读写;多遍扫描,只能递增
双向迭代器可读写;多遍扫描,可递增递减
随机访问迭代器可读写;多遍扫描,支持全部迭代器运算

五类迭代器

输入迭代器 (input iterator):可以读取序列中的元素。输入迭代器必须提供:

  • 相等和不相等运算符 ==!=,比较两个迭代器
  • 前置和后置递增 ++,推进迭代器
  • 解引用操作符 *,读取元素;解引用只出现赋值的右边
  • 箭头运算符 ->,等价于 (*it).member

输入迭代器只能顺序使用。算法 find 和 accumulate 算法需要输入迭代器。
istream_iterator 是输入迭代器。

输出迭代器 (output iterator):可以看作输入迭代器功能的补充。输出迭代器必须提供:

  • 前置和后置递增 ++,推进迭代器
  • 解引用操作符 *,只出现赋值的左边

用作目的位置的迭代器一般是输出迭代器。copy 函数的第三个参数是输出迭代器。
ostream_iterator 类型是输出迭代器。

前向迭代器 (forward iterator):可读写给定序列,只能在序列中沿一个方向移动。提供输入迭代器和输出迭代器的所有操作。
算法 replace 要求前向迭代器。forward_list 是前向迭代器。

双向迭代器 (bidirectional iterator):可正向或反向读写序列。支持前向迭代器所有操作,还支持前置和后置递减运算符 --
算法 reverse 要求双向迭代器。除了 forward_list,库容器提供的迭代器满足双向迭代器的要求。

随机访问迭代器 (random-access iterator):在常量时间内可访问序列中任意位置。支持双向迭代器所有操作,还支持以下操作:

  • 关系运算符 <, <=, >, >=,比较两个迭代器的相对位置。
  • 在迭代器和整数值上的加减运算符 +, +=, -, -=
  • 应用于两个迭代器的减法运算符 -,生成迭代器之间的距离。
  • 下标运算符 iter[n],等价于 *(iter+n)

算法 sort 要求随机访问迭代器。array、deque、string 和 vector 的迭代器是随机访问迭代器,用于访问内置数组元素的指针也是。

算法形参模式

大多数算法具有如下 4 种形式之一:

alg(beg, end, other args);
alg(beg, end, dest, other args);
alg(beg, end, beg2, other args);
alg(beg, end, beg2, end2, other args);

接受单个目标迭代器的算法

写入输出迭代器的算法假定目标空间足够容纳写入的数据。

dest 形参表示目标迭代器。如果 dest 直接指向容器,那么算法将输出数据直接写入到容器已存在的元素中。更常见的是,dest 绑定到插入迭代器或 ostream_iterator。

接受第二个输入序列的算法

只接受 beg2(不接受 end2)的算法将 beg2 视为第二个输入范围的首元素。这些算法假定从 beg2 开始的范围至少与 beg 和 end 表示的范围一样大。

算法命名规范

一些算法使用重载传递谓词

接受谓词替代 <== 运算符的算法,和不接受其他参数的算法,一般是重载。

unique(beg, end);       // uses the == operator to compare the elements
unique(beg, end, comp); // uses comp to compare the elements

_if 版本的算法

接受一个元素值的算法,一般有另一个不同名的版本,接受谓词替换值。接受谓词的算法有 _if 后缀。

find(beg, end, val);     // find the first instance of val in the input range
find_if(beg, end, pred); // find the first instance for which pred is true

区分拷贝元素的版本和不拷贝的版本

默认情况下,重排元素的算法会将重排后的元素写回到给定输入范围。这些算法提供另一个版本,写入指定输出目标位置。这样的算法有 _copy 后缀。

reverse(beg, end);           // reverse the elements in the input range
reverse_copy(beg, end, dest);// copy elements in reverse order into dest

一些算法同时提供 _copy 和 _if 版本。

// removes the odd elements from v1
remove_if(v1.begin(), v1.end(), [](int i) { return i % 2; });
// copies only the even elements from v1 into v2; v1 is unchanged
remove_copy_if(v1.begin(), v1.end(), back_inserter(v2), [](int i) { return i % 2; });

10.5 容器特定算法

于其他容器不同,list 和 forward_list 定义了几种算法作为成员。特别地,列表类型定义它们自己版本的 sort、merge、remove、reverse 和 unique。

表10.6 list 和 forward_list 成员算法

算法说明(这些操作都返回 void)
lst.merge(lst2)将 lst2 中的元素合并到 lst。lst 和 lst2 必须是已排序的。merge 后,lst2 为空。使用 < 操作符。
lst.merge(lst2, comp)使用给定的比较运算符。
lst.remove(val)调用 erase 删除每个 == 给定值的元素。
lst.remove_if(pred)调用 erase 删除每个使一元谓词为真的元素。
lst.reverse()反转 lst 中元素的顺序。
lst.sort()使用 < 排序 lst 中的元素。
lst.sort(comp)使用给定比较操作排序 lst 中的元素。
lst.unique()调用 erase 删除相同元素的连续拷贝。使用 ==
lst.unique(pred)使用给定的二元谓词。

对于 list 和 forward_list,应优先使用列表成员版本,而不是通用算法。

splice 成员

splice 算法是列表数据结构特有的。

表10.7 list 和 forward_list 的 splice 成员的参数(lst.splice(args)flst.splice_after(args)

参数 (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 中给定范围的元素。
lst2 和 lst(或 flst)可以是相同的链表,但 p 不能指向给定范围内的元素。

链表特有的操作会改变容器

链表特有版本算法和通用版本算法之间的一个重要区别是:链表版本会改变底层的容器。


【C++ primer】目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值