Effective Modern C++[实践]->优先使用 const_iterator 而非 iterator

  1. 只要有可能就应该使用const
  2. 优先使用 const_iterator 而不是 iterator。任何时候只要你需要一个迭代器而其指涉内容没有修改必要时,你就应该使用const_iterator
  3. 在最大泛型代码中, 相对于它们的成员函数部分, 优先使用 begin endrbegin 等的非成员函数版本。因其更通用。

迭代器

何谓c++迭代器

迭代器是c++标准模板库(STL)的6大组成(容器、算法、迭代器、函数对象、适配器、内存分配器)之一。迭代器用于指向STL容器类的内存地址。为了更好地理解,在某种程度上,可以将它们与指针联系起来。

迭代器充当了将算法连接到STL容器的桥梁,并允许修改容器内的数据。它们允许您遍历容器、访问和赋值,并在它们上运行不同的操作符,以获得所需的结果。

根据迭代器的功能,它们可以分为五个主要类别:

  1. 输入迭代器(Input Iterators): 它们是所有迭代器中最弱的,而且功能非常有限。它们只能用在单遍算法中,即那些顺序处理容器的算法,这样就不会有任何元素被访问超过一次。
  2. 输出迭代器(Output Iterators):就像输入迭代器一样,它们的功能也非常有限,只能在单遍算法中使用,但不是用于访问元素,而是用于分配元素。
  3. 前向迭代器(Forward Iterator): 它们比输入和输出迭代器的层次更高,包含这两个迭代器中的所有特性。但是,正如它的名字所暗示的那样,它们也只能向前移动,而且也是一次一步。
  4. 双向迭代器(Bidirectional Iterators): 它们拥有前向迭代器的所有特性,同时克服了前向迭代器的缺点,因为它们可以向两个方向移动,这就是为什么它们被称为双向的原因。
  5. 随机访问迭代器(Random-Access Iterators): 随机访问迭代器实现了双向迭代器的所有功能,并且还具有非顺序访问范围的能力: 通过向迭代器应用偏移量值,可以直接访问远处的元素,而无需遍历中间的所有元素。这些迭代器具有与标准指针类似的功能(指针是此类别的迭代器)。
类别 特性 有效表达
所有类别 复制构造 复制赋值运算符 析构X b(a);
b = a;
可以递增 ++a
a++
随机访问 双向 向前 输入 支持平等/不平等比较 a == b
a != b
可以作为 右值 *a
a->m
输出 可以作为 左值
(仅适用于 可变迭代器类型
*a = t
*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 

迭代器的基操

  1. begin():返回一个指向容器第一个元素的指针。这个指针可以指向容器的任何一个方向,因此它是双向的。
  2. 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;
}
  1. 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;
}
  1. next(iterator i,int n):返回一个迭代器,它指向从当前元素递增迭代器指针之后得到的元素。
  2. 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;
}
  1. 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;
    }
}

迭代器的好处

  1. 编程方便: 迭代器在编写代码时为您提供轻松和便利。 例如,在使用迭代器时,您不必担心容器的大小。 您可以轻松地遍历容器并使用 end() 方法获取最后一个元素。 您甚至可以更改容器元素,而无需移动或完成所有工作。
  2. 代码可重用性
  3. 动态处理:在迭代器的帮助下,您可以动态分配和删除内存。 容器的这种动态处理可以防止内存浪费。 您可以根据需要和要求轻松更改容器的大小。

C++98 中 const_iterator 不好用

const_iterator无法强转至iterator

  1. 《Effective_STL》条款26: 尽量用iterator代替const_iteratorreverse_iteratorconst_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,还是这货。

  1. 迭代器无法从const到非const转换。

iteratorconst_iterator、从iteratorreverse_iterator和从reverse_iteratorconst_reverse_iterator可以进行隐式转换。并且,reverse_iterator可以通过调用其base成员函数转换为iteratorconst_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的常量性很麻烦,且不通用

distanceadvanceconst_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 (iter 和const_iter)定义合适的类型,使用std::advancestd::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

  1. 且看cbegincendbeginend,他们可以返回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. 再看inserterase,他们也可以接收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提供了返回常量迭代器的成员函数 cbegincend,但是这并不通用,因为除了标准容器外,其实还有一些类似于容器的数据结构,这些结构没有 beginend等这类方法。而在标准化过程中的短识,c++11仅添加了非成员函数的beginend,但是并没有提供cbegincendrbegin等,如下图所示:

在这里插入图片描述

对此可添加如下代码:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-西门吹雪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值