STL之迭代器及traits编程

迭代器与STL
本文详细介绍了迭代器在STL中的重要角色,包括迭代器的基本概念、如何通过模板特化来处理不同类型的迭代器,以及迭代器的五种关键属性:value_type、difference_type、reference、pointer和iterator_category。
    不论是泛型思维或STL的实际运用,迭代器都扮演着重要的角色。STL的中心思想在于:将数据容器和算法分开,彼此独立设计,最后再以一贴胶着剂将他们撮合在一起,这就是iterator!iterator是一种行为类似指针的对象,而指针的各种行为中最常见也最重要的便是内容提领(dereference) 和成员访问(member access),因此,迭代器最重要的编程工作就是对 operator* 和 operator-> 进行重载工作。

    迭代器相应型别获取的解决方法:利用function template的参数推导(argument deducation)机制,编译器会自动进行模板参数的推导。
template <class I, class T>
void func_impl(I iter, T t){
    T tmp; // 这里T就是迭代器所指之物的型别
    // ... 这里做原本func 应该做的全部工作
}
 
template <class I>
inline
void func(I iter){
    func_impl(iter, *iter); //func的工作全部移往func_impl
}
 
int main()
{
    int i;
    func(&i);
}
    Partial Specialization(偏特化):如果class template拥有一个以上的template 参数,我们可以针对其中某个(数个,但非全部)template 参数进行特化工作。偏特化是提供另一份template定义式,而其本身仍为templatized 。例子,面对一下这么一个类模板:
template <typename T>
class C {...};
我们很容易接受它有一个形式的如下的partial specialization:
template <typename T>
class C<T*> {...};
这个特化版本仅适用于“T为原生指针”的情况。有了这项利器,我们可以针对“迭代器模板参数为指针者”,设计特化版本的迭代器。
template <class I>
struct iterator_traits{ // traits意为“特性”
    typedef typename I::value_type value_type; // I内部定义有自己的value type
};
通过这个traits的作用,萃取出来的value_type就是I::value_type。则我们就可以这样定义函数返回型别:
template <class I>
typename iterator_traits<I>::value_type
func(I ite){ return *ite; }
};
    下面是一个迭代器为原生指针的偏特化版本:
template <class T>
typename iterator_traits<T*>{
    typedef T value_type;
};
    但是,针对“指向常数对象的指针”, iterator_traits<const int*>::value_type 获得的是const int,我们用它声明一个无法赋值的暂时变量也没什么用。因此,如果一个迭代器是一个pointer_to_const,我们应该设法令其value type为一个 non-const型别:
template <class T>
struct iterator_traits<const T*>{
    typedef T value_type;
};
    根据经验,最常用到的迭代器相应型别有五种:value type, difference type, pointer, reference, iterator catagoly。
template <class I>
struct iterator_traits{
    typedef typename I::value_type value_type;
    typedef typename I::iterator_category  iterator_category;
    typedef typename I::difference_type  difference_type;
    typedef typename I::pointer pointer;
    typedef typename I::reference reference;
};
iterator_traits必须针对传入之型别 pointer 及 pointer-to-const 者,设计特化版本。

    迭代器相应型别之一:value type
所谓value type是指迭代器所指对象的型别。任何一个打算与STL算法有完美搭配的class,都应该定义自己的value type内嵌型别。该型别做法如上所述。

    迭代器相应型别之二:difference type
difference type用来表示两个迭代器之间的距离,因此它可以用来表示一个容器的最大容量。如果一个泛型算法提供计数功能,其传回值就必须使用迭代器的 difference type:
template<class I, class T>
typename iterator_traits<I>::difference_type  //返回型别
count(I first, I last, const T& value){
    typename iterator_traits<I>::difference_type n = 0;
    for( ; first != last; ++first)
        if(*first == value) ++n;
    return n;
}
    针对相应型别difference type,traits的如下两个特化版本,以C++内建的ptrdiff_t作为原生指针的difference type:
template <class I>
struct iterator_traits{
    typedef typename I::difference_type difference_type;
};
//原生指针的偏特化版本
template <class T>
struct iterator_traits<T*>{
    typedef ptrdiff_t difference_type;
};
//原生指针的 point-to-const 偏特化版本 
template <class T>
struct iterator_traits<const T*>{
    typedef ptrdiff_t difference_type;
};
现在,任何时候当我们需要任何迭代器I的difference type,可以这么写:
typename iterator_traits<I>::diffenence_type

    迭代器相应型别之三:reference type
从迭代器所指之物的内容是否允许改变的角度观之,迭代器分为两种:不允许改变“所指对象之内容”者,称为constant iterators;允许改变“所指对象之内容”者,称为mutable iterators。当我们对一个mutable iteraors进行提领操作时,获得的不应该是一个右值,而应该是一个左值。该型别和下一个pointer类似,具体实现见pointer实现。

    迭代器相应型别之四:pointer type
template <class I>
struct iterator_traits{
    typedef typename I::pointer pointer;
    typedef typename I::reference reference;
};
//原生指针的偏特化版本
template <class T>
struct iterator_traits<T*>{
    typedef T* pointer;
    typedef T& reference;
};
//原生指针的 point-to-const 偏特化版本 
template <class T>
struct iterator_traits<const T*>{
    typedef const T* pointer;
    typedef const T& reference;
};
    迭代器相应型别之五:iterator_category
根据移动特性及施行操作,迭代器被分为五类:
  • Input Iterator:这种迭代器所指的对象,不允许外界改变。只读!
  • Output Iterator:只写!
  • Forward Iterator:允许“写入型”算法在此种迭代器所形成的区间上进行读写操作
  • Bidirectional Iterator:可双向移动
  • Random Access Iterator:前三种支持operator++,第四种再加上operator--;这种则涵盖所有指针算术能力,包括p+n, p-n, p[n], p1-p2, p1<p2
这五种迭代器的分类与从属关系(非继承,只是概念和强化)如下:

定义五个classes,代表五种迭代器类型:
struct input_iterator_tag{};
struct output_iterator_tag{};
struct forward_iterator_tag : public input_iterator_tag{};
struct bidirectional_tag : public forward_iterator_tag{};
struct random_access_iterator_tag : public bidirectional_iterator_tag{};
这样我们可以做以下测试:
#include <iostream>
struct A{};
struct B1 : public A {};
struct B2 : public A {};

template<class I>
void
func(I& p, A){std::cout << "A version" << std::endl;};

template<class I>
void
func(I& p, B2){std::cout << "B2 version" << std::endl;};

int main()
{
int * p;

func(p, A());
func(p, B1());
func(p, B2());

return 0;
}
输出结果如下:

我们可以利用这种继承关系,对每一种迭代器类型设计不同的算法。

为了符合规范,任何迭代器都应该提供五个内嵌相应型别,以利于traits萃取,否则便是自别于整个STL架构,可能无法与其他STL组件顺利搭配。STL提供了一个iterator class,如果每个新设计的迭代器都继承自它,就可保证符合STL所需之规范:
template <class Category, 
          class T, 
          class Distance = ptrdiff_t, 
          class Pointer=T*, 
          class Reference= T&>
struct iterator{
    typedef Category iterator_category;
    typedef T value_type;
    typedef Distance difference_type;
    typedef Pointer pointer;
    typedef Reference reference;
};
iterator class 不含任何成员,纯粹是型别定义,所以继承它没有额外负担;由于后三个参数都有默认值,故新的迭代器只需提供前两个参数即可
<think>我们被要求详细解释C++ STL迭代器的使用方法和原理。根据提供的引用,我们可以总结出以下关键点: 1. 迭代器的作用:提供一种统一的方法来访问容器中的元素,而不需要暴露容器的内部结构。这样,算法可以独立于容器类型工作。 2. 迭代器指针的关系:迭代器是泛化的指针,但并非所有迭代器都是指针。迭代器抽象了指针的行为,使得它可以用于不同的数据结构(如链表、树等)。 3. 迭代器的实现:根据容器的底层数据结构,迭代器可能被实现为原生态指针(如vector)或封装指针的类(如list)。迭代器类需要重载一些操作符,如解引用(*)、箭头(->)、自增(++)、自减(--)以及相等性比较(==, !=)等。 4. 迭代器的种类:包括输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器等,不同种类的迭代器支持的操作不同。 5. 迭代器的失效:当容器发生改变(如插入或删除元素)时,某些迭代器可能会失效,继续使用它们会导致未定义行为。 我们将从以下几个方面展开: 一、迭代器的基本概念 二、迭代器的种类及其支持的操作 三、迭代器的实现原理(以vector和list为例) 四、迭代器的使用方法和注意事项 五、迭代器失效问题 下面开始详细说明: 一、迭代器的基本概念 迭代器STL的核心概念之一,它提供了访问容器中元素的方法,同时隐藏了容器的内部实现细节。迭代器类似于指针,可以指向容器中的某个元素,并能够通过解引用来访问该元素。迭代器之间可以通过自增或自减操作移动到相邻元素。 二、迭代器的种类及其支持的操作 STL定义了五种迭代器类别,每种支持的操作不同: 1. 输入迭代器(Input Iterator):只读,支持++、==、!=、*(读取)和->操作。只能单向移动(向前)。 2. 输出迭代器(Output Iterator):只写,支持++、*(写入)。只能单向移动(向前)。 3. 前向迭代器(Forward Iterator):可读写,支持输入和输出迭代器的所有操作,并且可以多次遍历同一个序列。例如单向链表(forward_list)的迭代器。 4. 双向迭代器(Bidirectional Iterator):支持前向迭代器的所有操作,还支持--(向后移动)。例如list、set、map的迭代器。 5. 随机访问迭代器(Random Access Iterator):支持双向迭代器的所有操作,还支持迭代器的算术运算(如+、-、+=、-=)、比较运算符(<、>、<=、>=)和下标访问([])。例如vector、deque、array的迭代器。 三、迭代器的实现原理 1. vector的迭代器:通常实现为原生态指针(T*),因为vector的元素在连续内存中,指针本身就满足随机访问迭代器的要求。 2. list的迭代器:由于list是双向链表,其迭代器需要封装链表节点指针,并重载相应的操作符。例如: - operator*():返回节点数据的引用。 - operator->():返回节点数据的指针。 - operator++()和operator++(int):移动到下一个节点。 - operator--()和operator--(int):移动到上一个节点。 - operator==和operator!=:比较两个迭代器是否指向同一个节点。 四、迭代器的使用方法和注意事项 1. 获取迭代器:每个容器都提供了begin()和end()成员函数,分别返回指向第一个元素和尾后元素(最后一个元素的下一个位置)的迭代器。 2. 遍历容器:通常使用循环,从begin()开始,直到end()结束。 ```cpp for (auto it = container.begin(); it != container.end(); ++it) { // 使用*it访问元素 } ``` 或者使用范围for循环(C++11起): ```cpp for (auto& element : container) { // 直接使用element } ``` 3. 使用迭代器时要注意其有效性:当容器发生改变(如插入、删除元素)时,迭代器可能会失效。例如,在vector中插入元素可能导致所有迭代器失效(因为重新分配内存),而在list中插入元素不会使迭代器失效(除了被删除元素的迭代器)。 五、迭代器失效问题 引用[3]提到,向容器插入或删除元素可能会改变容器中迭代器的有效性。例如: - vector:插入元素可能导致重新分配内存,使得所有迭代器、指针和引用失效。如果没有重新分配(如空间足够),则插入点之后的迭代器会失效。 - list:插入和删除操作不会使迭代器失效(除了被删除元素的迭代器)。 因此,在修改容器时,需要特别注意迭代器的有效性,避免使用失效的迭代器。 总结:迭代器STL中连接容器和算法的纽带,它提供了统一的访问接口,使得算法可以独立于容器类型。理解迭代器的种类、实现原理以及失效规则对于正确使用STL至关重要。 §§相关问题§§ 1. 迭代器失效的常见场景有哪些? 2. 如何设计一个自定义容器的迭代器? 3. STL迭代器泛型算法如何协作? 4. 迭代器指针相比有哪些优缺点?</think>### C++ STL迭代器使用原理详解 #### 一、迭代器的作用意义 迭代器STL的核心组件,作为**容器算法之间的桥梁**(如`std::sort`、`std::find`等),它提供统一的访问接口,使算法无需关心底层容器实现细节。虽然指针可完成类似功能,但迭代器具有以下独特价值: 1. **泛型编程支持**:通过抽象容器访问方式,实现“一次编写,适用于所有容器” 2. **安全性增强**:封装指针操作,避免越界等错误 3. **适配复杂结构**:对链表、树等非连续结构提供类指针接口[^2] #### 二、迭代器的实现原理 迭代器的实现方式取决于容器底层数据结构: 1. **连续内存容器(vector, array)** - 直接封装原生指针(`T*`) - 支持随机访问(`operator[]`, `+n`等) ```cpp // vector迭代器简化实现 template<class T> class vector_iterator { T* ptr; public: T& operator*() { return *ptr; } // 解引用 iterator& operator++() { ++ptr; return *this; } // 前向移动 }; ``` 2. **链表容器(list, forward_list)** - 封装链表节点指针 - 需重载双向移动操作(`operator--`) ```cpp // list迭代器简化实现 template<class T> class list_iterator { ListNode<T>* node; public: T& operator*() { return node->data; } iterator& operator++() { node = node->next; return *this; } iterator& operator--() { node = node->prev; return *this; } // 双向移动 }; ``` 3. **关联容器(map, set)** - 通常基于红黑树实现 - 迭代器按中序遍历顺序移动 #### 三、迭代器类型操作 | 迭代器类型 | 支持操作 | 对应容器示例 | |--------------------|-----------------------------------|--------------------------| | 输入迭代器 | `++`, `*`, `->`, `==` | `istream_iterator` | | 输出迭代器 | `++`, `*` | `ostream_iterator` | | 前向迭代器 | 输入迭代器所有操作 | `forward_list` | | 双向迭代器 | 前向迭代器 + `--` | `list`, `map`, `set` | | 随机访问迭代器 | 双向迭代器 + `+n`, `[]`, `<` 等 | `vector`, `array`, `deque` | #### 四、关键使用技巧 1. **获取迭代器** ```cpp vector<int> vec = {1,2,3}; auto begin = vec.begin(); // 指向首元素 auto end = vec.end(); // 指向尾后位置(哨兵) ``` 2. **遍历容器** ```cpp // 传统方式 for(auto it = vec.begin(); it != vec.end(); ++it) { cout << *it << " "; } // C++11范围for循环(本质使用迭代器) for(int val : vec) { cout << val << " "; } ``` 3. **算法结合** ```cpp #include <algorithm> // 在[begin, end)区间查找元素 auto pos = find(vec.begin(), vec.end(), 2); if(pos != vec.end()) { cout << "Found at position: " << distance(vec.begin(), pos); } ``` #### 五、迭代器失效问题 容器修改可能导致迭代器失效: 1. **vector/string** - 插入元素可能引起扩容,**所有迭代器失效** - 删除元素使**被删位置之后**的迭代器失效 ```cpp vector<int> v = {1,2,3}; auto it = v.begin() + 1; v.push_back(4); // 可能扩容,it失效! ``` 2. **list/map/set** - 插入不会使迭代器失效 - 删除仅使**被删元素的迭代器**失效 ```cpp list<int> lst = {1,2,3}; auto it = ++lst.begin(); lst.erase(it); // 仅it失效,其他迭代器安全 ``` > **重要原则**:修改容器后应重新获取迭代器[^3] #### 六、迭代器特征萃取(iterator_traitsSTL通过`iterator_traits`提取迭代器属性: ```cpp template<typename I> void algorithm(I first, I last) { // 获取迭代器距离类型 typename iterator_traits<I>::difference_type n = distance(first, last); // 获取元素引用类型 typename iterator_traits<I>::reference ref = *first; } ``` 特征包含五种类型:`value_type`, `difference_type`, `pointer`, `reference`, `iterator_category`[^4]。 #### 七、迭代器适配器 1. **反向迭代器**:`rbegin()` → `rend()` 2. **插入迭代器**:`back_inserter(vec)` 用于`copy`等算法 3. **流迭代器**:`istream_iterator<int>(cin)` 从输入流读取 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值