1. 迭代器的基本概念
-
迭代器是什么?
迭代器可以看作是一种特殊的指针,用于访问容器中的元素。一个迭代器要么指向容器中的某个有效元素,要么指向"尾后位置"(one past the end),即容器最后一个元素之后的位置。尾后迭代器本身没有实际意义,仅用作循环终止的标记。
-
获取迭代器
大多数标准容器都提供两个成员函数:
begin()
和end()
。begin()
返回指向容器中第一个元素的迭代器。end()
返回一个尾后迭代器,即指向容器最后一个元素之后的位置。
如果容器为空,则这两个函数返回相同的迭代器。
例如,针对一个 vector:
cpp
#include <vector>
#include <iostream>
using std::vector;
using std::cout;
using std::endl;
int main() {
vector<int> v = {1, 2, 3, 4, 5};
auto it_begin = v.begin(); // it_begin 指向第一个元素
auto it_end = v.end(); // it_end 是尾后迭代器
// 输出第一个元素
if (it_begin != it_end)
cout << "First element: " << *it_begin << endl;
return 0;
}
2. 迭代器的常用操作
标准库容器的迭代器通常支持以下几种基本运算符:
运算符 | 说明 |
---|---|
*iter |
解引用,返回迭代器 iter 所指元素的引用。 |
iter->mem |
相当于 (*iter).mem ,先解引用 iter,然后访问该元素的成员 mem。 |
++iter |
令 iter 指向容器中的下一个元素。 |
--iter |
令 iter 指向容器中的上一个元素。 |
iter1 == iter2 |
判断两个迭代器是否相等(例如,是否指向同一元素或同一容器的尾后位置)。 |
iter1 != iter2 |
判断两个迭代器是否不相等。 |
注意:
尝试解引用一个无效迭代器(例如尾后迭代器或未经初始化的迭代器)会导致未定义行为。
3. 迭代器的使用示例
3.1 访问和修改元素
假设我们有一个 string 对象,我们可以利用迭代器来修改其中的字符。以下示例将 string 中的第一个字符转换为大写:
cpp
#include <iostream>
#include <string>
#include <cctype> // 包含 toupper
using std::string;
using std::cout;
using std::endl;
int main() {
string s("some string");
// 检查字符串是否为空(begin() 和 end() 相等时为空)
if (s.begin() != s.end()) {
auto it = s.begin(); // it 指向第一个字符
*it = toupper(*it); // 修改第一个字符为大写
}
cout << s << endl; // 输出: "Some string"
return 0;
}
3.2 使用迭代器遍历容器
利用范围 for 循环时,也可以直接用迭代器,但有时我们需要手动控制迭代器的移动。例如,下面的代码将 string 中第一个单词转换为大写(遍历直到遇到空白字符):
cpp
#include <iostream>
#include <string>
#include <cctype>
using std::string;
using std::cout;
using std::endl;
int main() {
string s("some string");
// 使用迭代器遍历字符串中的字符,直到遇到空白字符
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it) {
*it = toupper(*it);
}
cout << s << endl; // 输出: "SOME string"
return 0;
}
3.3 结合箭头运算符访问成员
对于存放类对象的容器,迭代器可以用于访问对象的成员。箭头运算符 ->
可将解引用和成员访问合并为一,例如:
cpp
#include <iostream>
#include <vector>
#include <string>
using std::vector;
using std::string;
using std::cout;
using std::endl;
int main() {
vector<string> text = {"Line one", "Line two", ""};
// 使用 const_iterator 遍历,直到遇到空字符串(段落分隔符)
for (auto it = text.cbegin(); it != text.cend() && !it->empty(); ++it) {
cout << *it << endl;
}
return 0;
}
这里,it->empty()
等价于 (*it).empty()
,用于检查迭代器所指字符串是否为空。
4. 迭代器类型
每个标准库容器都定义了两种迭代器类型:
- iterator:可读写,允许修改容器中元素。
- const_iterator:只读,保证不能通过迭代器修改元素。
例如,对于 vector:
cpp
vector<int> v = {1, 2, 3};
vector<int>::iterator it1 = v.begin(); // 可读写迭代器
vector<int>::const_iterator it2 = v.begin(); // 只读迭代器
如果 vector 对象本身是常量,则只能使用 const_iterator:
cpp
const vector<int> cv = {1, 2, 3};
auto cit = cv.begin(); // 类型为 vector<int>::const_iterator
另外,C++11 还提供了 cbegin()
和 cend()
,无论容器是否为常量,这两个函数都返回 const_iterator,适用于只需读操作的场景。
5. 迭代器失效
需要特别注意的是,当对 vector 进行插入、删除或其他可能改变其容量的操作时,原有迭代器可能会失效。失效的迭代器不再指向正确的元素,任何对其的访问都会导致未定义行为。因此,在使用迭代器的循环体中,不要修改容器的大小。
例如,下面的代码在使用范围 for 遍历时尝试调用 push_back,就会导致迭代器失效:
cpp
#include <iostream>
#include <vector>
using std::vector;
using std::cout;
using std::endl;
int main() {
vector<int> v = {1, 2, 3};
// 错误示例:在范围 for 循环中修改容器大小
for (auto &x : v) {
cout << x << " ";
v.push_back(x); // 修改容器,可能使迭代器失效
}
cout << endl;
return 0;
}
在编写涉及迭代器的循环时,必须确保循环过程中不改变容器的大小。
6. 总结
-
迭代器提供统一访问方式 :
迭代器允许遍历所有标准容器和类似容器(例如 string)的元素,是一种比下标运算符更通用的访问方式。
-
获取迭代器 :
通过容器的
begin()
和end()
成员函数获取迭代器,begin()
指向第一个元素,end()
指向尾后位置。对于只读操作,建议使用 const_iterator 或 cbegin/cend。 -
迭代器运算 :
使用
*iter
解引用迭代器以获取元素,++iter
令迭代器前进,iter->mem
可访问迭代器所指对象的成员。 -
遍历与修改 :
使用范围 for 语句可以简洁地遍历容器,但若需修改元素,必须使用引用形式(如 for(auto &c : container))。
-
迭代器失效问题 :
在循环中不应修改容器大小,因为可能导致迭代器失效,从而引起未定义行为。
通过深入理解迭代器的基本概念与操作,你可以在各种容器中灵活、安全地访问和操作元素。迭代器不仅与指针类似,而且为泛型编程提供了统一接口,是现代 C++ 编程中不可或缺的工具。
参考资料
- cppreference.com 关于 std::vector 和 std::string 的迭代器说明
- 各大 C++ 编码规范(如 Google C++ Style Guide)中对迭代器使用的建议
希望这篇博客能帮助你全面了解迭代器的概念和使用方法,从而在实际编程中高效、正确地访问和操作容器内的数据。