2019-04-27-enumerate-with-c-plus-plus.zh.md (2284B)
1 +++ 2 title = "用 C++ 来 enumerate()" 3 draft = false 4 date = 2019-04-27 5 slug = "enumerate-with-c-plus-plus" 6 +++ 7 8 不少编程语言都提供了在迭代容器的同时记录步数的方法,例如 Python 的 `enumerate()` : 9 10 ```python 11 for i, elem in enumerate(v): 12 print(i, elem) 13 ``` 14 15 以及 Rust 里 `std::iter::Iterator` 特性下的 `enumerate()` : 16 17 ```rust 18 for (i, elem) in v.iter().enumerate() { 19 println!("{}, {}", i, elem); 20 } 21 ``` 22 23 这里记录了如何在 C++17 或更新的标准里尽量简洁地实现类似功能的办法。 24 25 第一种方法是使用一个可变的 lambda : 26 27 ```c++ 28 std::for_each(v.begin(), v.end(), 29 [i = 0](auto elem) mutable { 30 std::cout << i << ", " << elem << std::endl; 31 ++i; 32 }); 33 ``` 34 35 这个方法使用于所有能够保证 lambda 有序执行的算法,但是我并不喜欢末尾很可能被混入其他逻辑的 `++i` 。 36 37 第二种方法是在 for 循环中使用结构化绑定: 38 39 ```c++ 40 for (auto [i, elem_it] = std::tuple{0, v.begin()}; elem_it != v.end(); 41 ++i, ++elem_it) { 42 std::cout << i << ", " << *elem_it << std::endl; 43 } 44 ``` 45 46 为了不让编译器默认创建只允许同种内容的 `std::initializer_list` ,我们必须加上 `std::tuple` 。 47 48 第三种最朴实无华的办法是在循环的每一步计算指针距离: 49 50 ```c++ 51 for (auto elem_it = v.begin(); elem_it != v.end(); ++elem_it) { 52 auto i = std::distance(v.begin(), elem_it); 53 std::cout << i << ", " << *elem_it << std::endl; 54 } 55 ``` 56 57 由于这种方法需要我们在两个地方指定初始指针,我更喜欢之前提到的基于计数器的方法。 58 59 在 C++20 中,我们可以在基于范围的 for 循环中加入初始化语句: 60 61 ```c++ 62 for (auto i = 0; auto elem : v) { 63 std::cout << i << ", " << elem << std::endl; 64 i++; 65 } 66 ``` 67 68 新加入的 `<ranges>` 库则提供了一种更加吸引人的实现方法: 69 70 ```c++ 71 for (auto [i, elem] : v | std::views::transform( 72 [i = 0](auto elem) mutable { return std::tuple{i++, elem}; })) { 73 std::cout << i << ", " << elem << std::endl; 74 } 75 ``` 76 77 我最喜欢基于结构化绑定和 `<ranges>` 库的方法。当然如果要是有 `std::views::enumerate` 来一劳永逸地解决这个问题就最好不过了。