优先使用const_iterator而非iterator
- 只要有可能就应该使用
const
- 优先使用
const_iterator
而不是iterator
。任何时候只要你需要一个迭代器而其指涉内容没有修改必要时,你就应该使用const_iterator
- 在最大泛型代码中, 相对于它们的成员函数部分, 优先使用
begin
、end
、rbegin
等的非成员函数版本。因其更通用。
迭代器
何谓c++
迭代器
迭代器是
c++
标准模板库(STL
)的6大组成(容器、算法、迭代器、函数对象、适配器、内存分配器)之一。迭代器用于指向STL
容器类的内存地址。为了更好地理解,在某种程度上,可以将它们与指针联系起来。
迭代器充当了将算法连接到STL容器的桥梁,并允许修改容器内的数据。它们允许您遍历容器、访问和赋值,并在它们上运行不同的操作符,以获得所需的结果。
根据迭代器的功能,它们可以分为五个主要类别:
- 输入迭代器(
Input Iterators
): 它们是所有迭代器中最弱的,而且功能非常有限。它们只能用在单遍算法中,即那些顺序处理容器的算法,这样就不会有任何元素被访问超过一次。 - 输出迭代器(
Output Iterators
):就像输入迭代器一样,它们的功能也非常有限,只能在单遍算法中使用,但不是用于访问元素,而是用于分配元素。 - 前向迭代器(
Forward Iterator
): 它们比输入和输出迭代器的层次更高,包含这两个迭代器中的所有特性。但是,正如它的名字所暗示的那样,它们也只能向前移动,而且也是一次一步。 - 双向迭代器(
Bidirectional Iterators
): 它们拥有前向迭代器的所有特性,同时克服了前向迭代器的缺点,因为它们可以向两个方向移动,这就是为什么它们被称为双向的原因。 - 随机访问迭代器(
Random-Access Iterators
): 随机访问迭代器实现了双向迭代器的所有功能,并且还具有非顺序访问范围的能力: 通过向迭代器应用偏移量值,可以直接访问远处的元素,而无需遍历中间的所有元素。这些迭代器具有与标准指针类似的功能(指针是此类别的迭代器)。
类别 | 特性 | 有效表达 | |||
---|---|---|---|---|---|
所有类别 | 复制构造 、 复制赋值运算符 和 析构 | X b(a); | |||
可以递增 | ++a | ||||
随机访问 | 双向 | 向前 | 输入 | 支持平等/不平等比较 | a == b |
可以作为 右值 | *a | ||||
输出 | 可以作为 左值 (仅适用于 可变迭代器类型 ) | *a = t | |||
默认可构建 | X a;X(); | ||||
多遍:解引用和递增都不会影响可解引用性 | { b=a; *a++; *b; } | ||||
可以递减 | --a;a--;*a--; | ||||
支持算术运算符 + 和 - | a + n;n + a;a - n;a - b; | ||||
不等式比较( < 、 > 、 <= 和 >= 迭代器之间 | a<b a>b a<=b a>=b | ||||
支持复合赋值操作 += 和 -= | a +=n a -=n | ||||
支持偏移解引用运算符 ( [] ) | a[n] |
示例:
#include <algorithm>
#include <iostream>
#include <map>
#include <set>
#include <string>
using namespace std;
void print_map(std::string_view comment, const std::map<string, int> &m,int type) {
std::cout << comment ;
switch (type) {
case 98: {
for (std::map<std::string, int>::const_iterator it = m.begin();
it != m.end(); it++) {
std::cout << it->first << " = " << it->second << "; ";
}
} break;
case 11: {
for (const auto &n : m) {
std::cout << n.first << " = " << n.second << "; ";
}
} break;
case 17: {
for (const auto &[key, value] : m) {
std::cout << '[' << key << "] = " << value << "; ";
}
} break;
}
cout<<endl;
}
int main() {
set<map<string, int> > set1;
set <map <string, int >> :: iterator ps;
set1.insert({{"1", 1}});
set1.insert({{"2", 2}});
set1.insert({{"3", 3}});
set1.insert({{"4", 4}});
std::cout << "c++98 迭代" <<endl;
for (ps = set1.begin(); ps != set1.end(); ps++) {
print_map(" ", *ps,98);
}
cout<<endl;
std::cout << "c++11 迭代" <<endl;
for (ps = set1.begin(); ps != set1.end(); ps++) {
print_map(" ", *ps,11);
}
cout<<endl;
std::cout << "c++17 迭代" <<endl;
for (ps = set1.begin(); ps != set1.end(); ps++) {
print_map(" ", *ps,98);
}
}
打印输出:
c++98 迭代
1 = 1;
2 = 2;
3 = 3;
4 = 4;
c++11 迭代
1 = 1;
2 = 2;
3 = 3;
4 = 4;
c++17 迭代
1 = 1;
2 = 2;
3 = 3;
4 = 4;
迭代器的主要用途
- 迭代器的主要目标是访问 STL 容器元素并对它们执行某些操作。
- 容器的内部结构无关紧要,因为迭代器为它们提供了通用用法。
- 迭代器算法不依赖于容器类型。
- 迭代器可用于迭代容器元素。 它还可以提供对这些元素的访问以修改它们的值。
- 迭代器遵循 STL 容器类的通用方法。无须了解不同容器的不同迭代器。
语法
<Container_Type> :: iterator;
<Container_Type> :: const_iterator;
Container _ Type:
此参数是为其声明迭代器的容器的类型。
容器 | 支持的迭代器 |
---|---|
Vector | 随机访问迭代器 |
List | 双向迭代器 |
Dequeue | 随机访问迭代器 |
Map | 双向迭代器 |
Multimap | 双向迭代器 |
Set | 双向迭代器 |
Multiset | 双向迭代器 |
Stack | 不支持任何迭代器 |
Queue | 不支持任何迭代器 |
Priority-Queue | 不支持任何迭代器 |
迭代器与指针的区别
指针 | 迭代器 |
---|---|
指针指向内存中的一块地址 | 迭代器可能包含一个指针,但它可能是更复杂的东西。例如,迭代器可以迭代文件系统中的数据、分布在许多机器上的数据或以编程方式在本地生成的数据。 一个很好的例子是链表上的迭代器,迭代器将通过位于列表中节点的元素移动,这些节点在 RAM 中的地址可能是分散的。 |
我们可以对指针执行简单的算术运算,如增量、减量、添加整数等。 | 并非所有的迭代器都允许这些操作,例如,我们不能递减一个前向迭代器,或者向一个非随机访问迭代器添加一个整数。 |
类型 T * 的指针可以指向任何类型 T 的对象。 | 迭代器受到更多的限制,只能指向容器 |
我们可以使用 delete 删除指针 | 由于迭代器引用容器中的对象,与指针不同,迭代器没有删除的概念。(容器负责内存管理。) |
#include <iostream>
#include <vector>
using namespace std;
int main(){
vector<int> v1 = {10, 20, 30, 40};
vector<int>::iterator itr;
// 无迭代器的访问vector元素
cout << "没有 iterator的迭代遍历 : ";
for (int j = 0; j < 4; ++j) {
cout << v1[j] << " ";
}
cout << "\n";
// 使用迭代器访问vector元素
cout << "有 iterator的迭代遍历 ";
for (itr = v1.begin(); itr != v1.end(); ++itr) {
cout << *itr << " ";
}
cout << "\n\n";
v1.push_back(50);
cout << "没有 iterator的迭代遍历 : ";
for (int j = 0; j < 5; ++j) {
cout << v1[j] << " ";
}
cout << "\n";
cout << "有 iterator的迭代遍历 ";
for (itr = v1.begin(); itr != v1.end(); ++itr) {
cout << *itr << " ";
}
cout << "\n\n";
return 0;
}
运行结果
没有 iterator的迭代遍历 : 10 20 30 40
有 iterator的迭代遍历 10 20 30 40
没有 iterator的迭代遍历 : 10 20 30 40 50
有 iterator的迭代遍历 10 20 30 40 50
迭代器的基操
begin()
:返回一个指向容器第一个元素的指针。这个指针可以指向容器的任何一个方向,因此它是双向的。end()
:返回一个指向容器最后一个元素之后的元素的指针。这个元素不是真实的,它是一个包含最后一个元素地址的虚拟元素。
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
int main(){
// initialize a vector
vector<int> myVector = {100, 200, 300, 400, 500};
vector<int>::iterator itr;
cout << "The vector contains these elements: ";
for (itr = myVector.begin(); itr < myVector.end(); itr++) {
cout << *itr << " ";
}
cout << "\n\n";
return 0;
}
advance(iterator i ,int distance)
:用于从迭代器的当前位置增加迭代器。它接受一个整数作为参数。Forward ()方法将指针递增到该整数位置。
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
int main(){
vector<int> myVect = {100, 200, 300, 400, 500};
vector<int>::iterator itr;
itr = myVect.begin();
cout << "begin的指向: ";
cout << *itr << " ";
cout << "\n\n";
advance(itr, 2); // 迭代器指向300
cout << "advance后指向 : ";
cout << *itr << " ";
cout << "\n\n";
return 0;
}
next(iterator i,int n)
:返回一个迭代器,它指向从当前元素递增迭代器指针之后得到的元素。pre(iterator i,int n)
:prev() 方法与 next() 方法正好相反。 它返回一个迭代器指针,该指针指向从当前元素递减迭代器后得到的元素。
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
int main(){
vector<int> myVect = {100, 200, 300, 400, 500};
vector<int>::iterator i1 = myVect.begin();
vector<int>::iterator i2 = myVect.end();
auto nextptr = next(i1, 3);
auto prevptr = prev(i2, 3);
cout << "next()后指向 : ";
cout << "\n";
cout << *nextptr << " ";
cout << "\n\n";
cout << "prev() 后指向: ";
cout << "\n";
cout << *prevptr << " ";
cout << "\n\n";
return 0;
}
inserter (Container & x, Iterator it)
:inserter ()
方法是一种特殊类型的迭代器方法。它用于在容器中的指定位置插入元素。如果在指定位置已经有一个元素,那么它还可以覆盖容器中的元素以插入新元素。
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> myVect1, myVect2;
for (int i = 1; i <= 5; i++) {
myVect1.push_back(i);
myVect2.push_back(i + 4);
}
vector<int>::iterator itr = myVect1.begin();
copy(myVect2.begin(), myVect2.end(), inserter(myVect1, itr));
cout << "拷贝myvect2后的myvect1中的元素 :"<< endl;
for (itr = myVect1.begin(); itr != myVect1.end(); ++itr){
cout << " " << *itr;
}
}
迭代器的好处
- 编程方便: 迭代器在编写代码时为您提供轻松和便利。 例如,在使用迭代器时,您不必担心容器的大小。 您可以轻松地遍历容器并使用 end() 方法获取最后一个元素。 您甚至可以更改容器元素,而无需移动或完成所有工作。
- 代码可重用性
- 动态处理:在迭代器的帮助下,您可以动态分配和删除内存。 容器的这种动态处理可以防止内存浪费。 您可以根据需要和要求轻松更改容器的大小。
C++98 中 const_iterator
不好用
const_iterator
无法强转至iterator
- 《Effective_STL》条款26: 尽量用
iterator
代替const_iterator
,reverse_iterator
和const_reverse_iterator
以Vector
为例吧,看下std::vector<T,Allocator>::insert
(1) iterator insert( iterator pos, const T& value );(C++11 前)
(2) void insert( iterator pos, size_type count, const T& value );(C++11 前)
(3) template< class InputIt >
void insert( iterator pos, InputIt first, InputIt last);(C++11 前)
再看下std::vector<T,Allocator>::erase
(1) iterator erase( iterator pos );(C++11 前)
(2) iterator erase( iterator first, iterator last );(C++11 前)
这些方法只接受iterator类型的参数,而不是const_iterator、reverse_iterator或const_reverse_iterator,总是iterator,独宠这个货。虽然容器类支持四种迭代器类型,但其中的一种类型有着其他所没有的特权,那就是iterator
,还是这货。
- 迭代器无法从
const
到非const
转换。
从iterator
到const_iterator
、从iterator
到reverse_iterator
和从reverse_iterator
到const_reverse_iterator
可以进行隐式转换。并且,reverse_iterator
可以通过调用其base
成员函数转换为iterator
。const_reverse_iterator
也可以类似地通过base转换成为const_iterator
。但是没有办法从一个const_iterator
转换得到一个iterator
,也无法从const_reverse_iterator
得到reverse_iterator
。
且看示例:
#include <iostream>
#include <algorithm>
#include <vector>
#include <iterator>
typedef std::vector<int>::iterator IterT;
typedef std::vector<int>::const_iterator ConstIterT;
int main() {
std::vector<int> values;
ConstIterT it = std::find(values.begin(),values.end(),198);
values.insert(static_cast<IterT>(it), 198);
return 0;
}
const_iterator
无法转换为iterator
,强制转换也不行,编译错误如下:
/home/insights/insights.cpp:26:17: error: no matching conversion for static_cast from 'ConstIterT' (aka '__normal_iterator<const int *, std::vector<int> >') to 'IterT' (aka '__normal_iterator<int *, std::vector<int> >')
values.insert(static_cast<IterT>(it), 198);
^~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_iterator.h:984:11: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from '__normal_iterator<std::vector<int>::const_pointer, [...]>' to 'const __normal_iterator<int *, [...]>' for 1st argument
class __normal_iterator
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_iterator.h:1007:7: note: candidate constructor not viable: no known conversion from 'ConstIterT' (aka '__normal_iterator<const int *, std::vector<int> >') to 'int *const' for 1st argument
__normal_iterator(const _Iterator& __i) _GLIBCXX_NOEXCEPT
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_iterator.h:1013:9: note: candidate template ignored: substitution failure [with _Iter = const int *]: no type named '__type' in '__gnu_cxx::__enable_if<false, std::vector<int> >'
__normal_iterator(const __normal_iterator<_Iter,
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_iterator.h:1003:26: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
_GLIBCXX_CONSTEXPR __normal_iterator() _GLIBCXX_NOEXCEPT
^
1 error generated.
移除const_iterators的常量性很麻烦,且不通用
用
distance
和advance
把const_iterator
转化成iterator
一个简短的帖子,演示了如何移除const_iterator的常量性,因为不可能使用const_cast<>
将const_iterator
转换为iterator
。
#include <iostream>
#include <vector>
class A{
public:
A( int x ) { val = x; }
void DoStuff() { std::cout << "val = " << val << std::endl; }
private:
int val;
};
int main(){
A* a[] = { new A( 1 ), new A( 2 ), new A( 3 ),new A( 4 ) };
std::vector<A*> v( a, a + 4 );
for ( std::vector<A*>::const_iterator cit = v.begin(); cit != v.end(); ++cit ){
std::vector<A*>::iterator it = cit;
}
return 0;
}
编译会出错,如下:
<source>:28:40: error: conversion from '__normal_iterator<A* const*,[...]>' to non-scalar type '__normal_iterator<A**,[...]>' requested
28 | std::vector<A*>::iterator it = cit;
|
定义一个迭代器,使其线性移动,直到到达const_iterator所指向的元素。
首先为iterators
和 const_iterators
(ite
r 和const_iter
)定义合适的类型,使用std::advance
和std::distance
来访问迭代器。
#include <iostream>
#include <vector>
class A{
public:
A( int x ) { val = x; }
void DoStuff() { std::cout << "val = " << val << std::endl; }
private:
int val;
};
// 定义我们的迭代器
typedef std::vector<A*>::iterator iter;
typedef std::vector<A*>::const_iterator c_iter;
int main(){
// 用指向A对象的指针初始化vector
A* a[] = { new A( 1 ), new A( 2 ), new A( 3 ), new A( 4 ) };
std::vector<A*> v( a, a + 4 );
// 遍历元素
iter it = v.begin();
for ( c_iter cit = v.begin(); cit != v.end(); ++cit ){
std::advance (it, std::distance<c_iter>(it, cit ) );
(*it)->DoStuff();
}
return 0;
}
c++11后尽可能拥抱const_iterator
吧
获取和使用const_iterator
更easy
- 且看
cbegin
、cend
、begin
、end
,他们可以返回const_iterator
iterator begin();(C++11 前)
iterator begin() noexcept;(C++11 起)(C++20 前)
constexpr iterator begin() noexcept;(C++20 起)
const_iterator begin() const;(C++11 前)
const_iterator begin() const noexcept;(C++11 起)(C++20 前)
constexpr const_iterator begin() const noexcept;(C++20 起)
const_iterator cbegin() const noexcept;(C++11 起)(C++20 前)
constexpr const_iterator cbegin() const noexcept;(C++20 起)
2. 再看insert
和erase
,他们也可以接收const_iterator
再写代码,就可以如下实现:
#include <iostream>
#include <algorithm>
#include <vector>
#include <iterator>
int main() {
std::vector<int> values;
auto it = std::find(values.cbegin(),values.cend(),198);
values.insert(it, 198);
return 0;
}
最通用的代码
#include <iostream>
#include <algorithm>
#include <vector>
#include <iterator>
using namespace std;
template <typename C,typename V>
void findAndInsert(C& container,const V& targetVal,const V& insertVal)
{
using std::cbegin;
using std::cend;
auto it = std::find(cbegin(container),cend(container),targetVal);
container.insert(it,insertVal);
}
int main() {
std::vector<int> values={1,2,3,4,5,7,198,99,1991,200};
findAndInsert(values,198,199);
for (auto ps = values.begin(); ps != values.end(); ps++) {
cout<<" "<< *ps;
}
return 0;
}
以上代码在c++14
中运行结果为 1 2 3 4 5 7 199 198 99 1991 200
;但在c++11
中编译报错如下:
source>: In function 'void findAndInsert(C&, const V&, const V&)':
<source>:12:16: error: 'cbegin' has not been declared in 'std'
12 | using std::cbegin;
| ^~~~~~
<source>:13:16: error: 'cend' has not been declared in 'std'
13 | using std::cend;
| ^~~~
<source>: In instantiation of 'void findAndInsert(C&, const V&, const V&) [with C = std::vector<int>; V = int]':
<source>:21:16: required from here
<source>:14:31: error: 'cbegin' was not declared in this scope
14 | auto it = std::find(cbegin(container),cend(container),targetVal);
| ~~~~~~^~~~~~~~~~~
<source>:14:47: error: 'cend' was not declared in this scope
14 | auto it = std::find(cbegin(container),cend(container),targetVal);
| ~~~~^~~~~~~~~~~
虽然C++11
提供了返回常量迭代器的成员函数 cbegin
和cend
,但是这并不通用,因为除了标准容器外,其实还有一些类似于容器的数据结构,这些结构没有 begin
,end
等这类方法。而在标准化过程中的短识,c++11
仅添加了非成员函数的begin
和end
,但是并没有提供cbegin
、cend
、rbegin
等,如下图所示:
对此可添加如下代码:
template <typename C>
auto cbegin(const C& container)->decltype(std::begin(container))
{
return std::begin(container);
}
修改后的完整代码如下:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
template <typename C>
auto cbegin(const C& container) -> decltype(std::begin(container)) {
return std::begin(container);
}
template <typename C>
auto cend(const C& container) -> decltype(std::end(container)) {
return std::end(container);
}
template <typename C, typename V>
void findAndInsert(C& container, const V& targetVal, const V& insertVal) {
// using std::cbegin;
// using std::cend;
auto it = std::find(cbegin(container), cend(container), targetVal);
container.insert(it, insertVal);
}
int main() {
std::vector<int> values = {1, 2, 3, 4, 5, 7, 198, 99, 1991, 200};
findAndInsert(values, 198, 199);
for (auto ps = values.begin(); ps != values.end(); ps++) {
cout << " " << *ps;
}
return 0;
}
参考
[1] Defining C++ Iterators
[2] Iterators in C++: An Ultimate Guide to Iterators
[3] How to convert const_iterators to iterators using std::distance and std::advance
[4] Varieties of Iterators
[5] std::iterator
[6] Three Guidelines for Effective Iterator Usage
[7] const_iterator
[8] Iterator library
[9] Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library:第4节
[10] iterator