在这里插入图片描述

博主名称:deM0one
⭐️C++专栏: C++


C++ 迭代器与容器操作

本文档基于学习笔记整理,目标读者为 C++ 初学者,内容覆盖:迭代器、范围 for、auto 关键字、迭代器类型、const 迭代器、reserve 与 reverse。

一、引言:为什么要学习迭代器

1.1 从 STL 说起

  • C++ 标准模板库(STL)有六大组件,迭代器是其中之一
  • 容器(如 vector、string)负责存储数据,迭代器负责访问容器中的数据
  • 认识常见类型:vector<int>::iterator、string::iterator

1.2 阅读本文你将掌握(今日学习内容)

  • 什么是迭代器,如何用 begin() / end() 遍历容器
  • 范围 for 与迭代器的关系及区别
  • auto 的用法与限制
  • 迭代器的四种类型
  • const 对象应该使用哪种迭代器
  • reserve 与 reverse 的区别

二、迭代器简介

2.1 迭代器是什么

  • 迭代器是一种访问和遍历容器中元素的方式
  • 它很像指针,但不是指针,只是用法完全跟指针类似:
    • 解引用 *it 取元素
    • ++it 移动到下一个元素
    • 用 it != end() 判断是否遍历结束

2.2 为什么需要迭代器

  • 提供了一种通用的访问方式:所有容器都可以用迭代器访问
  • 提供统一接口、统一访问方式,屏蔽不同容器的内部差异

2.3 用 begin() 与 end() 遍历

  • begin():指向第一个元素
  • end():指向最后一个元素的下一个位置(不指向有效元素)
#include <iostream>
#include <string>
using namespace std;

void test_string2()
{
    string s2("hello world");

    string::iterator it = s2.begin();
    while (it != s2.end())
    {
        cout << *it;
        ++it;
    }
    cout << endl;
}

2.4 size 与 capacity

  • size():当前有效字符的个数
  • capacity():当前容量,注意字符串末尾的 \0 也占容量
  • 图示关系:hello world\0,begin() 指向 h,end() 指向 \0 之后

三、范围 for:另一种访问方式

3.1 基本用法

  • 自动迭代,逐个取出元素:
for (auto ch : s2)   // 字符赋值,自动迭代
    cout << ch;
cout << endl;

for (char ch : s2)   // 显式写出类型
    cout << ch;
cout << endl;

3.2 修改行为:值拷贝

  • 通过迭代器是可以修改容器内容的
  • 但范围 for 默认按值拷贝:循环体内修改 ch 不会改变容器本身的值,要修改的话我们应当将类型设为引用类型

3.3 加引用才能修改

for (auto& ch : s2)   // 加引用
    ch = 'x';         // 才能真正修改容器中的字符

3.4 适用范围与特点

  • 范围 for 适用于容器和数组
  • 特点:写法简单,代码更简洁

四、auto 关键字

4.1 作用:自动推导类型

  • 当类型名称过长时,用 auto 让编译器自动推导,简化代码:
map<string, string> dict;
// map<string, string>::iterator mit = dict.begin();
auto mit = dict.begin();   // 等价写法

4.2 用 typeid 查看类型

  • typeid(变量).name() 可以帮我们查看变量的实际类型
    在这里插入图片描述

4.3 使用规则与限制

  • 必须初始化,否则编译报错,例如:
    // auto e; → error C3531:“e”:类型包含“auto”的符号必须具有初始值设定项
  • 不能定义数组
  • 不能作为参数传参
  • 可以做返回值,但要谨慎使用

4.4 优缺点总结

  • 优点:简化长类型名,代码更简洁
  • 缺点:一定程度上牺牲了可读性,使代码变复杂

五、迭代器的类型

5.1 迭代器一共有四种

迭代器类型方向可否修改
iterator正向可读可写
const_iterator正向只读
reverse_iterator反向可读可写
const_reverse_iterator反向只读
  • 按方向分:正向迭代器、反向迭代器
  • 按权限分:普通迭代器、常量迭代器

5.2 正向迭代器

  • 从前往后遍历:begin() 到 end()

5.3 反向迭代器

  • 从后往前遍历,能够倒着遍历:rbegin() 到 rend()
string::reverse_iterator rit = s2.rbegin();
while (rit != s2.rend())
{
    cout << *rit;
    ++rit;
}
cout << endl;

5.4 常量迭代器

  • 只能读取数据,不能修改数据

六、const 对象与 const 迭代器

6.1 为什么 const 对象要用 const 迭代器

  • 普通迭代器可读可写
  • const 对象的成员不可修改,因此必须使用 const 迭代器访问

6.2 代码演示

const string s3("hello world");

// string::const_iterator cit = s3.begin();
auto cit = s3.begin();          // 自动推导为 const_iterator
while (cit != s3.end())
{
    cout << *cit;
    ++cit;
}
cout << endl;

// string::const_reverse_iterator rcit = s3.rbegin();
auto rcit = s3.rbegin();        // 自动推导为 const_reverse_iterator
while (rcit != s3.rend())
{
    cout << *rcit;
    // *rcit += 2;              // 编译报错:const 迭代器不能修改
}
cout << endl;

6.3 C++98 与 C++11 的接口变化

  • 以 std::string::begin 为例:
    • C++98:iterator begin();
    • C++11:新增重载 const_iterator begin() const;
  • 因此 const 对象调用 begin() 会得到 const_iterator

七、reserve 与 reverse 的区分

7.1 一句话区分

  • reserve:提前分配(预留)内存空间
  • reverse:逆置(反转)容器中元素的顺序
  • 两者是完全不同的概念,注意不要混淆

7.2 reserve 的作用

  • 提前开好空间,避免频繁扩容带来的重新分配

7.3 观察 capacity 的变化(扩容实验)

void TestPushBack()
{
    string s;
    s.reserve(100);          // 预留 100 个空间

    size_t sz = s.size();
    size_t cap = s.capacity();
    cout << "capacity changed: " << sz << " " << cap << endl;

    // 不断插入,观察 capacity 何时变化
    for (int i = 0; i < 199; ++i)
    {
        s.push_back('c');
        if (sz != s.size())
        {
            sz = s.size();
            cap = s.capacity();
            cout << "capacity changed: " << sz << " " << cap << endl;
        }
    }
}

7.4 reserve 的特性与平台差异

  • 可以扩容
  • 缩容是平台相关的,行为不确定
  • 可以确定的是:reserve 一定不会修改内容
  • VS 平台:
    • 传入小于 size 的值:容量不变
    • 传入小于 capacity 的值:容量不变
  • Linux 平台:
    • 传入小于 capacity 的值:会缩容,最多缩到 size
    • 传入比 capacity 大的值:会尝试按对齐规则扩容

八、综合示例代码

将本文内容串起来:正向、反向、常量迭代器 + auto + 范围 for 的综合演示。

#include <iostream>
#include <string>
#include <vector>
using namespace std;

int main()
{
    vector<int> vec = {1, 2, 3, 4, 5};

    // 正向迭代器 + auto
    for (auto it = vec.begin(); it != vec.end(); ++it)
        cout << *it << " ";
    cout << endl;

    // const 对象使用 const 迭代器
    const vector<int>& const_vec = vec;
    for (auto cit = const_vec.begin(); cit != const_vec.end(); ++cit)
        cout << *cit << " ";
    cout << endl;

    // 范围 for:加引用可修改
    for (auto& v : vec)
        v *= 2;
    for (const auto& v : vec)
        cout << v << " ";
    cout << endl;

    return 0;
}

九、总结

  • 通过迭代器,我们可以方便地遍历和管理容器中的数据
  • auto 关键字的引入使代码更简洁易读,但要注意初始化、数组、传参等限制
  • reserve 和 reverse 各有特定用途,需要根据实际需求选择
  • 注意区分不同类型的迭代器及其适用场景(正向/反向、普通/常量),确保程序的正确性和效率
  • const 对象必须使用 const 迭代器;范围 for 默认按值拷贝,修改容器要加引用

任何的建议和批评欢迎指出,我们共同进步!

更多推荐