Array的基本概念
容器类array<>的一份实体,模塑出一个static array。它包覆一个寻常的static C-style array并提供一个STL容器接口。因此你无法借由增加或移除元素而改变其大小
Class array<>自TR1被引入C++11标准库,其概念是在C-style array上包覆一个有用的class
使用前提你必须包含头文件<array>
#include<array>
该类型被定义为一个class template,位于命名空间std中
namespace std{
template<typename T,size_t N>
class array;
}
第一个T为任何被指名的类型,第二个template参数用来指出这个array在其生命周期中拥有的元素个数。Array并不支持指定分配器(allocator)
Array的能力
基于是一个static C-style array的实现,因此Array是一个有序集合,并且支持随机访问,前提是你直到元素位置。Array的迭代器属于随机访问迭代器,所以你可以将任何STL算法运用于它。如果你需要一个有固定元素的序列,class array<>将带来最佳效能。
初始化
对于初始化,class array<>有若干独特语义。第一个例子是,default构造函数并非建立一个空容器。
std::array<int,4>x; //OOPS:元素可能有未定义的初值
注:array<>是唯一一个“无任何东西被指定为初值时,会被预初始化”的容器,这意味着,初值可能会不明确(因被定义的位置而定),而不是0
你可以为它提供一个空白初值列,这种情况下元素保证被初始化,于是对基础类型而言元素初值为0
std::array<int,4>={ };
由于没有提供针对初值列而写的构造函数或assignment操作符,因此“在array声明期间完成初值化”是使用初值列的唯一途径。基于这个原因,你无法使用小括号语法指明初值(这里不同于其他容器类型)
std::array<int,4> a({1,2,3,4}); //ERROR
std::vector<int,4> c({1,2,3,4}); //OK
Class array<>是个聚合体,这个事实表明,用来保存所有元素的那个成员是public。因此对该public成员的任何直接访问都会导致不可预期的行为,也绝对不具有移植性
swap()和Move语义
和其他所有的容器一样,array<>提供swap()操作。因此你可以和一个相同类型的容器交换(元素类型和元素个数都相同)置换彼此的元素。然而,array<>不能仅仅置换其内部pointer。基于这个原因,swap()用于线性复杂度并带来以下影响:iterator和reference不会随着元素的置换而改变所指的容器。所以置换之后,iterator和reference指向原本容器,但指向至不同的元素。
你可以采用array暗自备妥的move语义。
std::array<std::string,10>as1,as2;
as1=std::move(as2);
大小(Size)
Array大小为0是可能的,那就是个没有任何元素的array。这种情况下bagin()和end(),cbegin()和cend(),以及相应的反向迭代器(reverse iterator)会释放出同一value。然而front()和back()的返回值就不明确了。
std::array<T,0>coll; //初始化一个空容器
std::sort(coll.begin(),coll.end()); //没错,但是没有效果
coll[5]=elem; //未定义的行为
std::cout<<coll.front(); //未定义的行为
Array的操作
Class array<>的构造函数
array<T,N>c //default构造函数,建立一个array带有default-initlist元素
array<T,N>c(c2) //copy构造函数,建立一个array的拷贝(所有元素都被拷贝)
array<T,N>c=c2 //copy构造函数,建立一个array的拷贝(所有元素都被拷贝)
array<T,N>c(rc) //move构造函数,取rvalue rv的内容建立一个新的array
array<T,N>c=rv //move构造函数,取rvalue rv的内容建立一个新的array
array<T,N>c=initlist //取初值列的元素为初值,建立一个array
注:由于class array<>是一个聚合体,这些构造函数只是被隐式定义出来。你可以建立array并且指定(不指定)元素初值。default构造函数在默认情况下将元素初始化,这意味着基础类型的初值是不确定的。如果你使用一个初值值列表却没有传递足够元素,剩下的的元素会被其default构造函数建立起来(基础类型的初始值为0)。
再次提醒以下,你不能对array的初始值列表使用小括号形式写法:
std::array<int,4>a({1,2,3,4)}; //ERROR
非更易型操作
c.empty() //返回是否容器为空(相当于size()==0但也许较快)
c.size() //返回元素的数量
c.max_size() //返回元素个数之最大可能量
c1==c2 //返回c1是否等于c2
c1!=c2 //返回c1是否不等于c2
c1>c2 //返回c1是否大于c2
c1<c2 //返回c1是否小于c2
c1>=c2 //返回c1是否大等于c2
c1<=c2 //返回c1是否小等于c2
赋值(Assignment)
c=c2 //将c2的全部元素赋值给c
c=rv //将rv以move assign方式赋值给c的每个元素
c.fill(val) //将val赋值给array的每个元素
c1.swap(c2) //置换c1和c2的数据
swap(c1,c2) //置换c1和c2的数据
在assignment操作符之外,只可使用fill()赋新值给每一个元素,或者使用swap()对另一个array置换元素值。如果使用=和swap(),两个array必须具有相同类型。其次,swap()不保证具备常量复杂度。事实上,所有这些操作都调用元素类型所提供的assignment操作符
元素访问(Element Access)
c[idx] //返回索引idx所指的元素(不检查范围)
c.at() //返回索引idx所指的元素(检查范围,超出范围会抛出out_of_range异常)
c.front()//返回第一个元素
c.back() //返回最末尾的元素
欲访问array内所有元素,你必须使用range-based for 循环,或者特定的操作函数或迭代器。此外由于array提供了一个tuple接口,所以你也可以使用get<>()访问某个元素。
注:对于一个array来说,只有当声明其大小为0它才是空的
std::array<T,4>coll; //有四个执行默认初始化的元素
coll[5]=elem; //未定义的行为
std::cout<<coll.front(); //正确(因为其含有4个元素)
std::array<T,0>coll2; //真正意义上的空容器
std::cout<<coll2.front(); //未定义的行为
所以你必须要能够确保operator []合法,或者使用at()
template<typename T>
void foo(T&coll){
if(coll.size()>5){
coll[5]=...;
}
coll.at(5); //访问失败会抛出out_of_range异常
}
迭代器相关函数(Iterator Function)
c.begin() //返回一个random-access iterator指向第一元素
c.end() //返回一个random-access iterator指向末尾元素的下一位置
c.rbegin() //返回一个reverse random-access iterator指向第一元素
c.rend() //返回一个reverse random-access iterator指向末尾元素的下一位置
c.cbegin() //返回一个const random-access iterator指向第一元素
c.cend() //返回一个const random-access iterator指向末尾元素的下一位置
c.crbegin()//返回一个const reverse random-access iterator指向第一元素
c.crend() //返回一个const reverse random-access iterator指向末尾元素的下一位置
对于array而言,begin(),cbegin(),end()和cend()返回的迭代器往往是寻常pointer,这很好,因为array<>内部使用C-style array存放元素,并使用寻常pointer提供random-access迭代器接口。然而你不可以依赖迭代器是寻常pointer"这个事实。(这个以后再出一篇单独讲,挖一个坑)
只要array保持有效,其迭代器也就保持有效。然而不同于其他容器,swap()乃是将新值赋予iterator,reference和pointer指向的元素身上
把array当成C-Style Array
由于array内部static array实现,它意味着,无论何处,只要你可以使用寻常的C-Style array,你就可以使用array<>。例如你可以使用array持有寻常的C-string的数据
std::array<char,4>a;
strcpy(&a[0],"hello world"); //将常量字符串拷贝入array中
printf("%s\n",&a[0]); //将其中内容打印出来
然而,如果你想直接访问array的元素,你不一定要用表达式&a[0],因为成员函数data()也具备相同用途
std::array<char,41>a;
strcpy(a.data(),"hello world");
printf("%s\n",a.data());
就和static array的使用一样,使用array<>也要谨慎,你必须确保array的大小足够容纳被复制进来的数据,而且如果你把其中的内容当作一个C-string来看,你必须放置一个'\0'元素于末尾。
注:绝对不要以迭代器表现"第一元素的地址"
printf("%s\n",a.begin()); //ERROR
printf("%s\n",a.data()); //OK
异常处理
Array只提供少量逻辑差错检查。C++standard规定“可抛出异常”的成员函数只有at(),它可以说是下标操作符的安全版本。其余被array调用的函数对于异常处理并无特别保证,异常只有可能发生在你copy,move或assign元素值时。swap()有可能抛出异常,因为它执行的是"元素逐次"的置换动作,而那有可能抛出异常。
Tuple接口
Array提供tuple接口。因此你可以通过表达式tuple_size<>::value取得元素数量,用tuple_element<>::type取得特定元素的类型,用get()取得某特定元素。
using FiveStrings=std::array<std::string,5>;
FiveStrings a={"hello ","nico","how","are","you"};
std::tuple_size<FiveStrings>::value; //5
std::tuple_elememt<1,FiveStrings>::type;//std::string
std::get<1>(a); //std::stirng("nice")
Array运用实例
#include<array>
#include<algorithm>
#include<functional>
#include<numeric>
using namespace std;
int main(){
array<int,10>a={11,22,33,44};
for(auto it:a)cout<<it;
cout<<endl;
a.back()=9999999;
a[a.size()-2]=42;
for(auto it:a)cout<<it;
cout<<endl;
cout<<"sum:"
<<accumulate(a.begin(),a.end(),0);
<<endl;
transform(a.begin(),a.end(),a.begin(),negate<int>());
for(auto it:a)cout<<it;
cout<<endl;
}
如上面所展示,可以通过一般性的容器操作接口(operator=,size()和operator[])直接操作容器。由于array也支持begin(),end()以获得迭代器,所以可以任何“调用begin()和end()”的操作
输出结果如下
11 22 33 44 0 0 0 0 0 0
11 22 33 44 0 0 0 0 42 9999999
sum: 10000151
-11 -22 -33 -44 0 0 0 0 -42 -9999999