通俗解释:迭代器是什么?

想象一下迭代器就像图书馆的智能导航员 📚:

🎯 核心比喻

数组/容器 = 图书馆的书架
数据元素 = 书架上的书
迭代器 = 智能导航员(知道每本书的位置,能带你找到下一本)

🆚 迭代器 vs 普通指针

特性 普通指针 迭代器
适用范围 数组 所有容器(vector、list、map等)
功能 基础访问 智能遍历,带安全检查
灵活性 固定 适配不同容器结构

💡 实际例子

数组用指针:

int 数组[3] = {10, 20, 30};
int *指针 = 数组;  // 指针指向第一个元素
cout << *指针;     // 输出10
指针++;           // 移动到下一个
cout << *指针;     // 输出20

vector用迭代器:

vector<int> 容器 = {10, 20, 30};
auto 迭代器 = 容器.begin();  // 迭代器指向第一个元素
cout << *迭代器;            // 输出10
++迭代器;                  // 移动到下一个
cout << *迭代器;            // 输出20

*iter 就是 获取迭代器当前指向的那个对象的引用。你可以读取它,也可以修改它。

std::vector<int> v = {10, 20, 30, 40};
std::vector<int>::iterator it = v.begin(); // it 这个“激光笔”指向10

// 使用 *it 来获取它指向的元素
int value = *it; 
std::cout << value; // 输出:10

// 你甚至可以修改它指向的元素
*it = 100; 
// 现在vector变成了:{100, 20, 30, 40}

iter->mem (箭头运算符)

  • 字面意思:访问所指元素的成员。

  • 通俗理解“激光笔正指着一本通讯录,请直接告诉我‘张三’的电话号码是多少?”

这个操作是上一个操作的“快捷方式”,专门用于当容器里的元素是对象结构体,并且你想访问这个对象内部的某个成员(变量或函数)时。

它完全等价于:(*iter).mem

我们来拆解一下:

  1. (*iter):先解引用,拿到迭代器指向的那个对象本身

  2. .mem:然后使用点运算符(.)来访问这个对象的名为mem的成员。

    // 定义一个结构体
    struct Person {
        std::string name;
        int age;
    };
    
    // 创建一个容器,里面放了两个Person对象
    std::vector<Person> people = {{"Alice", 25}, {"Bob", 30}};
    
    // 迭代器it指向第一个Person对象(即{"Alice", 25})
    std::vector<Person>::iterator it = people.begin();
    
    // 现在,我们想获取Alice的名字
    
    // 方法一:分两步走,先用 * 解引用,再用 . 访问成员
    std::string name1 = (*it).name; // 等价于:(“拿到Alice这个人”).名字
    
    // 方法二(更简洁):使用 -> 运算符一步到位
    std::string name2 = it->name; // 输出:Alice
    int age = it->age; // 输出:25
    
    // 它本质上就是 (*it).age 的简写形式,因为写起来更方便。

    迭代器的其他运算符

同样:

具体例子:

这样每次都能排除一半的可能性,很快就能找到答案!

  • ++iter
  • --iter
  • iter1 == iter2
  • iter1 != iter2
  • 改为大写

  • string s("some string");
    auto it = s.begin();
    
    // 错误写法:s 仍然是 "some string"
    toupper(*it);
    cout << s << endl; // 输出: some string
    
    // 正确写法:s 变成 "Some string"
    *it = toupper(*it);
    cout << s << endl; // 输出: Some string

    这行代码实际上做了两件事:

  • toupper(*it):取出 *it 指向的字符(比如 's'),生成一个大写版本('S'),但不会改变原来的 's'

  • *it = ...:把生成的大写字符 'S' 赋值回 *it 指向的位置

  • *it = toupper(*it); 中的 *it = 部分是必不可少的,因为它把转换后的大写字符保存回字符串中对应的位置。没有这个赋值操作,转换就白做了!

  • 为什么必须加 ::

    :: 是作用域解析运算符,它表示我们要访问的是某个类或命名空间内部的类型。

    类比理解:

    想象一下公司里的职位:

  • 不加 :::只说"经理" - 不知道是哪个部门的经理

  • 加 :::说"销售部::经理" - 明确知道是销售部的经理

  • iterator 本身没有意义

  • vector<int>::iterator 明确表示是 vector<int> 这个"部门"的迭代器

  • 更简单的写法:使用 auto

    如果你觉得这种写法太繁琐,C++11 引入了 auto 关键字,可以自动推断类型:

  • vector<int> v = {1, 2, 3};
    string s = "hello";
    
    // 使用 auto 让编译器自动推断类型
    auto it = v.begin();   // 等价于 vector<int>::iterator
    auto it2 = s.begin();  // 等价于 string::iterator
    
    // 对于只读迭代器,使用 cbegin()
    auto it3 = v.cbegin(); // 等价于 vector<int>::const_iterator
    auto it4 = s.cbegin(); // 等价于 string::const_iterator

    通俗理解二分搜索

    比喻:想象你在玩"猜数字"游戏,我心中想一个1-100的数字,你每次猜一个数,我会告诉你"大了"或"小了"。

    笨方法:从1开始一个个猜 → 1, 2, 3, 4...(太慢了!)

    聪明方法(二分搜索)

  • 先猜50

  • 如果我说"大了",你就知道数字在1-49之间

  • 再猜25(1-49的中间)

  • 如果我说"小了",你就知道数字在26-49之间

  • 继续猜中间值...

    #include <iostream>
    #include <vector>
    using namespace std;
    
    int main() {
        vector<int> text = {10, 20, 30, 40, 50, 60, 70};
        int sought = 30;
        
        auto beg = text.begin();
        auto end = text.end();
        auto mid = text.begin() + (end - beg) / 2;
        
        while (mid != end && *mid != sought) {
            if (sought < *mid) {
                end = mid;
            } else {
                beg = mid + 1;
            }
            mid = beg + (end - beg) / 2;
        }
        
        if (mid != end) {
            cout << "Found: " << *mid << " at position " << (mid - text.begin()) << endl;
        } else {
            cout << "Not found" << endl;
        }
        
        return 0;
    }

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐