C++ STL array随机访问迭代器(精讲版)

在《C++ STL迭代器(iterator)》一节中,已经对迭代器做了详细的介绍,STL 为 array 容器配备了随机访问迭代器,该类迭代器是功能最强大的迭代器。本节将详细介绍 array 容器的迭代器的用法。

在 array 容器的模板类中,和随机访问迭代器相关的成员函数如表 1 所示。

表 1 array 支持迭代器的成员函数
成员函数 功能
begin() 返回指向容器中第一个元素的正向迭代器;如果是 const 类型容器,在该函数返回的是常量正向迭代器。
end() 返回指向容器最后一个元素之后一个位置的正向迭代器;如果是 const 类型容器,在该函数返回的是常量正向迭代器。此函数通常和 begin() 搭配使用。
rbegin() 返回指向最后一个元素的反向迭代器;如果是 const 类型容器,在该函数返回的是常量反向迭代器。
rend() 返回指向第一个元素之前一个位置的反向迭代器。如果是 const 类型容器,在该函数返回的是常量反向迭代器。此函数通常和 rbegin() 搭配使用。
cbegin() 和 begin() 功能类似,只不过其返回的迭代器类型为常量正向迭代器,不能用于修改元素。
cend() 和 end() 功能相同,只不过其返回的迭代器类型为常量正向迭代器,不能用于修改元素。
crbegin() 和 rbegin() 功能相同,只不过其返回的迭代器类型为常量反向迭代器,不能用于修改元素。
crend() 和 rend() 功能相同,只不过其返回的迭代器类型为常量反向迭代器,不能用于修改元素。

除此之外,C++ 11 标准新增的 begin() 和 end() 函数,当操作对象为 array 容器时,也和迭代器有关,其功能分别和表 1 中的 begin()、end() 成员函数相同,具有用法本节后续会做详细介绍。

这些成员函数的具体功能如图 2 所示。

迭代器的具体功能示意图
图 2 迭代器的具体功能示意图

可以看到,根据它们的功能并结合实际场景的需要,这些成员函数通常是成对使用的,即 begin()/end()、rbegin()/rend()、cbegin()/cend()、crbegin()/crend() 各自成对搭配使用。不仅如此,这 4 对中 begin()/end() 和 cbegin()/cend()、rbegin()/rend() 和 crbegin()/crend() 的功能大致是相同的(如图 2 所示),唯一的区别就在于其返回的迭代器能否用来修改元素值。

值得一提的是,以上函数在实际使用时,其返回值类型都可以使用 auto 关键字代替,编译器可以自行判断出该迭代器的类型。

begin()/end() 和 cbegin()/cend()

array 容器模板类中的 begin() 和 end() 成员函数返回的都是正向迭代器,它们分别指向「首元素」和「尾元素+1」 的位置。在实际使用时,我们可以利用它们实现初始化容器或者遍历容器中元素的操作。

例如,可以在循环中显式地使用迭代器来初始化 values 容器的值:
#include <iostream>
//需要引入 array 头文件
#include <array>
using namespace std;
int main()
{
    array<int, 5>values;
    int h = 1;
    auto first = values.begin();
    auto last = values.end();
    //初始化 values 容器为{1,2,3,4,5}
    while (first != last)
    {
        *first = h;
        ++first;
        h++;
    }
  
    first = values.begin();
    while (first != last)
    {
        cout << *first << " ";
        ++first;
    }
    return 0; 
}
输出结果为:

1 2 3 4 5

可以看出,迭代器对象是由 array 对象的成员函数 begin() 和 end() 返回的。我们可以像使用普通指针那样上使用迭代器对象。比如代码中,在保存了元素值后,使用前缀 ++ 运算符对 first 进行自增,当 first 等于 end 时,所有的元素都被设完值,循环结束。

与此同时,还可以使用全局的 begin() 和 end() 函数来从容器中获取迭代器,因为当操作对象为 array 容器时,它们和 begin()/end() 成员函数是通用的。所以上面代码中,first 和 last 还可以像下面这样定义:
auto first = std::begin(values);
auto last = std::end (values);
这样,容器中的一段元素可以由迭代器指定,这让我们有了对它们使用算法的可能。

需要注意的是,STL 标准库,不是只有 array 容器,当迭代器指向容器中的一个特定元素时,它们不会保留任何关于容器本身的信息,所以我们无法从迭代器中判断,它是指向 array 容器还是指向 vector 容器(该容器后续会讲)。


除此之外,array 模板类还提供了 cbegin() 和 cend() 成员函数,它们和 begin()/end() 唯一不同的是,前者返回的是 const 类型的正向迭代器,这就意味着,有 cbegin() 和 cend() 成员函数返回的迭代器,可以用来遍历容器内的元素,也可以访问元素,但是不能对所存储的元素进行修改。

举个例子:
#include <iostream>
//需要引入 array 头文件
#include <array>
using namespace std;
int main()
{
    array<int, 5>values{1,2,3,4,5};
    int h = 1;
    auto first = values.cbegin();
    auto last = values.cend();
   
    //由于 *first 为 const 类型,不能用来修改元素
    //*first = 10;
   
    //遍历容器并输出容器中所有元素
    while (first != last)
    {
        //可以使用 const 类型迭代器访问元素
        cout << *first << " ";
        ++first;
    }
    return 0;
}
此程序的第 14 行代码中,我们尝试使用 first 迭代器修改 values 容器中的值,如果取消注释并运行此程序,编译器会提示你“不能给常量赋值”,即 *first 是 const 类型常量,所以这么做是不对的。但 17~22 行代码遍历并访问容器的行为,是允许的。

rbegin()/rend() 和 crbegin()/crend()

array 模板类中还提供了 rbegin()/rend() 和 crbegin()/crend() 成员函数,它们每对都可以分别得到指向最一个元素和第一个元素前一个位置的随机访问迭代器,又称它们为反向迭代器(如图 2 所示)。

需要注意的是,在使用反向迭代器进行 ++ 或 -- 运算时,++ 指的是迭代器向左移动一位,-- 指的是迭代器向右移动一位,即这两个运算符的功能也“互换”了。

反向迭代器用于以逆序的方式处理元素。例如:
#include <iostream>
//需要引入 array 头文件
#include <array>
using namespace std;
int main()
{
    array<int, 5>values;
    int h = 1;
    auto first = values.rbegin();
    auto last = values.rend(); 
    //初始化 values 容器为 {5,4,3,2,1}
    while (first != last)
    {
        *first = h;
        ++first;
        h++;
    }
    //重新遍历容器,并输入各个元素
    first = values.rbegin();
    while (first != last)
    {
        cout << *first << " ";
        ++first;
    }
    return 0;
}
运行结果为:

1 2 3 4 5

可以看到,从最后一个元素开始循环,不仅完成了容器的初始化,还遍历输出了容器中的所有元素。结束迭代器指向第一个元素之前的位置,所以当 first 指向第一个元素并 +1 后,循环就结朿了。

在反向迭代器上使用 ++ 递增运算符,会让迭代器用一种和普通正向迭代器移动方向相反的方式移动。

当然,在上面程序中,我们也可以使用 for 循环
for (auto first = values.rbegin(); first != values.rend(); ++first) {
    cout << *first << " ";
}

crbegin()/crend() 组合和 rbegin()/crend() 组合的功能唯一的区别在于,前者返回的迭代器为 const 类型,即不能用来修改容器中的元素,除此之外在使用上和后者完全相同。

有关 crbegin()/crend() 成员函数,这里不再给出具体实例,有兴趣的读者,可自行编写代码进行测试。