迭代器范围
迭代器范围这个概念是标准库的基础。
迭代器范围
C++ 语言使用一对迭代器标记迭代器范围(iterator range),这两个迭代器分别指向同一个容器中的两个元素或超出末端的下一位置,通常将它们命名为 first 和 last,或 beg 和 end,用于标记容器中的一段元素范围。
尽管 last 和 end 这两个名字很常见,但是它们却容易引起误解。其实第二个迭代器从来都不是指向元素范围的最后一个元素,而是指向最后一个元素的下一位置。该范围内的元素包括迭代器 first 指向的元素,以及从 first 开始一直到迭代器 last 指向的位置之前的所有元素。如果两个迭代器相等,则迭代器范围为空。
此类元素范围称为左闭合区间(left-inclusive interval),其标准表示方式为:
// to be read as: includes first and each element up to but not including last [ first, last )
表示范围从 first 开始,到 last 结束,但不包括 last。迭代器 last 可以等于 first,或者指向 first 标记的元素后面的某个元素,但绝对不能指向 first 标记的元素前面的元素。
- 使用左闭合区间的编程意义
- 当 first 与 last 相等时,迭代器范围为空。
- 当 first 与不相等时,迭代器范围内至少有一个元素,而且 first 指向该区间中的第一元素。此外,通过若干次自增运算可以使 first 的值不断增大,直到 first == last 为止。
这两个性质意味着程序员可以安全地编写如下的循环,通过测试迭代器处理一段元素:
while (first != last) { // safe to use *first because we know there is at least one element ++first; }
假设 first 和 last 标记了一段有效的迭代器范围,于是我们知道要么 first == last,这是退出循环的情况;要么该区间非空,first 指向其第一个元素。因为 while 循环条件处理了空区间情况,所以对此无须再特别处理。当迭代器范围非空时,循环至少执行一次。由于循环体每次循环就给 first 加 1,因此循环必定会终止。而且在循环内可确保 *first 是安全的:它必然指向 first 和 last 之间非空区间内的某个特定元素。
使迭代器失效的容器操作
一些容器操作会修改容器的内在状态或移动容器内的元素。这样的操作使所有指向被移动的元素的迭代器失效,也可能同时使其他迭代器失效。使用无效迭代器是没有定义的,可能会导致与悬垂指针相同的问题。
例如,每种容器都定义了一个或多个 erase 函数。这些函数提供了删除容器元素的功能。任何指向已删除元素的迭代器都具有无效值,毕竟,该迭代器指向了容器中不再存在的元素。
使用迭代器编写程序时,必须留意哪些操作会使迭代器失效。使用无效迭代器将会导致严重的运行时错误。
使用迭代器时,通常可以编写程序使得要求迭代器有效的代码范围相对较短。然后,在该范围内,严格检查每一条语句,判断是否有元素添加或删除,从而相应地调整迭代器的值。
示列代码
查找元素的值,并返回指向找到的元素的迭代器。确保程序在要寻找的元素不存在时也能正确工作
#include <iostream> #include <vector> #include <iterator> // 函数模板,接受一对迭代器和一个整数值 // 返回指向找到的元素的迭代器,如果未找到则返回结束迭代器 template<typename Iterator, typename T> Iterator findValueByComparison(Iterator begin, Iterator end, T value) { while (begin != end) { if (*begin == value) { return begin; // 找到值,返回迭代器 } ++begin; // 移动到下一个元素 } return end; // 未找到值,返回结束迭代器 } // 示例用法 int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; int valueToFind = 6; // 调用函数并传入迭代器和要查找的值 typename std::vector<int>::iterator it = findValueByComparison(vec.begin(), vec.end(), valueToFind); // 检查是否找到并输出结果 if (it != vec.end()) { std::cout << "Found the value: " << *it << " at position: " << std::distance(vec.begin(), it) << std::endl; } else { std::cout << "Value not found." << std::endl; } return 0; }
从标准输入设备读入若干 string 对象,并将它们存储在一个 vector 对象中,然后输出该 vector 对象中的所有元素
- vector
#include <iostream> #include <string> #include <vector> int main() { std::string line; std::vector<std::string> vec; // 读取输入直到输入结束(例如,用户输入 Ctrl+D 或 Ctrl+Z) while (std::getline(std::cin, line)) { vec.push_back(line); // 将读取的字符串存储到 vector 中 } // 使用迭代器遍历 vector 并输出所有元素 std::vector<std::string>::iterator it; for (it = vec.begin(); it != vec.end(); ++it) { std::cout << *it << std::endl; // 输出当前迭代器指向的字符串 } return 0; }
这个程序首先定义了一个 string 类型的变量 line 用于存储从标准输入读取的每一行文本,以及一个 vector 类型的变量 vec 用于存储这些字符串。
程序使用 std::getline 函数在 while 循环中不断读取输入,直到用户发送文件结束信号(EOF)。每次读取一行文本后,就使用 push_back 方法将其添加到 vec 中。
之后,程序使用一个 for 循环和迭代器 it 遍历 vec 中的所有元素,并使用 std::cout 输出每个元素。
- list
#include <iostream> #include <string> #include <list> int main() { std::string line; std::list<std::string> lst; // 使用 list 存储字符串 // 读取输入直到输入结束 while (std::getline(std::cin, line)) { lst.push_back(line); // 将读取的字符串存储到 list 中 } // 使用迭代器遍历 list 并输出所有元素 for (auto it = lst.begin(); it != lst.end(); ++it) { std::cout << *it << std::endl; // 输出当前迭代器指向的字符串 } return 0; }
这个程序与使用 std::vector 的版本非常相似,但这里使用了 std::list 来存储字符串。std::list 是一个双向链表,它提供了不同于 std::vector 的特性,例如在列表中间插入或删除元素时,std::list 通常比 std::vector 更高效。
在读取输入和输出元素的部分,我们使用了基于范围的 for 循环(range-based for loop),这是 C++11 引入的特性,它使得遍历容器更加简洁。这个循环自动处理迭代器的增加,并在每次迭代中将当前元素的引用赋给变量 it。
请注意,std::list 的迭代器不支持随机访问,因此不能使用索引访问元素,也不能使用 std::advance 来移动迭代器到特定位置。但是,它们可以用于双向遍历(向前和向后)。