cpp-STL-迭代器的用法(一)

50 阅读5分钟

摘要:stl迭代器的用法很重要,可以很大程度上便利编程。本文介绍了使用迭代器相关的函数进行填充,导航和初始化等操作,本文可以作为一个速查本。


C++ STL 迭代器操作:填充、迭代器导航与初始化

引言

STL 的全拼是 Standard Template Library,中文翻译为  “标准模板库”

它是 C++ 编程语言标准库的重要组成部分,提供了一系列可复用的通用组件。

核心组成部分:

STL 主要包含四大组件:

  1. 容器 - 用于存储数据的通用数据结构。

    • 例如:vector(动态数组), list(链表), map(关联数组), set(集合)等。
  2. 算法 - 用于操作容器中数据的函数模板。

    • 例如:sort(排序), find(查找), copy(复制)等。
  3. 迭代器 - 类似于指针的对象,用于在容器中遍历元素,是连接容器和算法的桥梁。

  4. 函数对象 - 行为类似函数的对象,可以作为算法的某种策略。

在 C++ 开发中,STL(标准模板库)是我们日常工作中不可或缺的工具。然而,面对容器操作时,常常会对各种填充方法、迭代器导航和初始化方式记不住,所以本文简要总结了一下日常用到的迭代器相关的函数。

一、Vector 指定范围填充的多种方式

1.1 使用 std::fill() 函数(推荐)

std::fill() 是填充容器范围的首选方法,它简洁高效且意图明确。

#include <algorithm>
#include <vector>

std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9};

// 填充索引 [2, 5) 的范围为 99
std::fill(vec.begin() + 2, vec.begin() + 5, 99);
// 结果:1, 2, 99, 99, 99, 6, 7, 8, 9

1.2 使用 std::fill_n() 函数

当你需要从某个位置开始填充指定数量的元素时,std::fill_n() 更加合适。

// 从索引3开始,填充4个元素为77
std::fill_n(vec.begin() + 3, 4, 77);
// 结果:1, 2, 3, 77, 77, 77, 77, 8, 9

很像 string类的 substr(start, count) 方法。

1.3 手动循环填充

递归简单场景下可以直接使用循环:

for (int i = start; i < end; ++i) {
    vec[i] = value;
}

1.4 重要注意事项

  • 范围表示:始终使用左闭右开区间 [start, end)
  • 边界检查:确保索引不越界
  • 性能考虑:标准库函数通常比手动循环更高效

二、迭代器位置访问与导航

2.1 访问下一个位置的三种方式

2.1.1 使用 std::next() 函数(推荐)
#include <iterator>
#include <vector>

std::vector<int> vec = {10, 20, 30, 40, 50};
auto it = vec.begin() + 1;  // 指向20

// 获取下一个位置
auto next_it = std::next(it);
std::cout << *next_it;  // 输出:30

// 指定步长前进
auto future_it = std::next(it, 3);
std::cout << *future_it;  // 输出:50
2.1.2 使用 + 运算符
auto next_it = it + 1;        // 下一个位置
auto future_it = it + 3;      // 后面第三个位置
2.1.3 使用递增运算符
auto next_it = ++it;  // 前置递增,it 和 next_it 都指向新位置

2.2 访问前一个位置

使用 std::prev() 函数可以安全地获取前一个位置:

auto it = vec.begin() + 3;  // 指向40
auto prev_it = std::prev(it);      // 指向30
auto prev_two = std::prev(it, 2);  // 指向20

三、std::prev()std::next() 的深入探讨

3.1 跨容器支持情况

容器类型迭代器类别std::next()std::prev()+ 运算符
std::vector随机访问
std::list双向
std::forward_list前向
std::deque随机访问
std::set/map双向

3.2 在 List 中的使用

#include <list>
#include <iterator>

std::list<int> mylist = {10, 20, 30, 40, 50};
auto it = mylist.begin();
std::advance(it, 2);  // 移动到30的位置

// list 不支持 + 运算符,但可以用 next/prev
auto prev_it = std::prev(it, 2);  // 指向10
auto next_it = std::next(it, 2);  // 指向50

下面是另外的一个迭代器操作函数 advance和next的区别

特性std::advancestd::next
返回值无 (void)新的迭代器
是否修改原迭代器✅ 是❌ 否
使用场景移动现有迭代器获取新迭代器而不修改原迭代器
性能直接修改,可能更高效需要创建副本
安全性不检测边界不检测边界

distance能检测两个迭代器的距离,但是前提是,迭代器本身没有失效。

3.3 在 Set/Map 中的使用

#include <set>
#include <iterator>

std::set<int> myset = {10, 20, 30, 40, 50};
auto it = myset.find(30);

if (it != myset.end()) {
    auto prev_it = std::prev(it);  // 指向20
    auto next_it = std::next(it);  // 指向40
}

四、std::list 初始化方法大全

4.1 基础初始化方式

#include <list>

// 1. 空列表
std::list<int> list1;

// 2. 指定大小
std::list<int> list2(5);           // 5个元素,默认值0
std::list<int> list3(5, 42);       // 5个元素,每个都是42

// 3. 初始化列表(推荐)
std::list<int> list4 = {1, 2, 3, 4, 5};
std::list<std::string> list5{"apple", "banana", "cherry"};

// 4. 从其他容器复制
std::list<int> original = {1, 2, 3};
std::list<int> list6(original);                    // 拷贝构造
std::list<int> list7(original.begin(), original.end());  // 迭代器范围

4.2 从不同类型容器初始化

// 从数组初始化
int arr[] = {10, 20, 30, 40};
std::list<int> from_array(arr, arr + 4);

// 从vector初始化
std::vector<int> vec = {5, 6, 7, 8, 9};
std::list<int> from_vector(vec.begin(), vec.end());

4.3 复杂数据类型初始化

class Person {
public:
    std::string name;
    int age;
    
    Person(std::string n, int a) : name(n), age(a) {}
};

std::list<Person> people = {
    Person("Alice", 25),
    Person("Bob", 30),
    Person("Charlie", 35)
};

4.4 使用 assign() 重新初始化

std::list<int> mylist;

mylist.assign(5, 10);           // {10, 10, 10, 10, 10}
mylist.assign({1, 2, 3, 4, 5}); // {1, 2, 3, 4, 5}

std::vector<int> vec = {6, 7, 8};
mylist.assign(vec.begin(), vec.end());  // {6, 7, 8}

五、最佳实践总结

5.1 填充操作

  • 首选std::fill() 用于范围填充
  • 备选std::fill_n() 用于指定数量填充
  • 避免:手动循环,除非有特殊需求

5.2 迭代器导航

  • 通用方案std::next()std::prev()
  • 随机访问容器:可以使用 + 运算符
  • 重要原则:始终进行边界检查

5.3 容器初始化

  • 简单情况:使用初始化列表 {}
  • 重复值:使用 (size, value) 构造函数
  • 复杂场景:使用迭代器范围或 assign() 方法

5.4 通用建议

  1. 优先使用标准库算法,它们经过优化且更安全
  2. 理解迭代器类别,选择合适的方法
  3. 始终考虑边界情况,避免未定义行为
  4. 保持代码一致性,在团队中使用统一的编码风格

结语

stl的方法很多,记住几个常用的就行,随用随查。