一般来说,每个容器都定义在与其名称相同的头文件中。
顺序容器几乎可以保存任意类型的元素。特别是我们可以定义一个容器,其元素的类型是另一个容器,例如:
vector<vector<string>> lines;
迭代器
与容器一样,迭代器有着公共的接口:如果一个迭代器提供某个操作,那么所有提供相同操作的迭代器对这个操作的实现方式都是相同的。注意:forwar_list迭代器不支持递减运算操作(--)。
一个迭代器的范围由一对迭代器表示,这两个迭代器通常被称为begin和end。begin指向的是同一个容器中的元素,end是指向尾元素之后的位置。这种元素范围被称为左闭合区间,其标准数学描述为[begin,end) 表示范围自begin开始,于end之前结束。迭代器begin和end必须指向相同的容器。end可以与begin指向相同的位置,但不能指向begin之前的位置。
标准库使用左闭合范围是因为这种范围有三种方便的性质。假定begin和end构成一个合法的迭代器范围,则
1)如果begin和end相等,则范围为空
2)如果begin和end不等,则范围至少包含一个元素,且begin指向范围中的第一个元素
3)我们可以对begin递增若干次,使得begin==end
容器类型成员
反向迭代器就是一种反向遍历容器的迭代器,与正向迭代器相比,各种操作的含义发生了颠倒。
通过类型别名我们可以再不了解容器中元素类型的情况下使用它。如果需要元素类型,可以使用容器的value_type。如果需要元素类型的引用,可以使用reference或const_reference 例如:
//iter是通过list<string>定义得一个迭代器类型
list<string>::iterator iter;
//count是通过vector<int>定义的一个difference_type类型
vector<int>::difference_type count;
begin和end成员
begin和end操作生成指向容器中第一个元素和尾元素之后位置的迭代器。这两个迭代器最常见的用途是形成一个包含容器中所有元素的迭代器范围。
begin和end有多个版本:带r的版本返回反向迭代器;以c开头的版本则返回const迭代器。
容器定义与初始化
每个容器都定义了一个默认构造函数。除了array之外,其它容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器大小和元素初始值的参数。
为了创建一个容器为另一个容器的拷贝,两个容器的类型及其元素类型必须相同。不过,当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同了,而且,新容器与原容器中的元素类型也可以不同,只要能将要拷贝的元素转换为要初始化的容器的元素类型即可。例如:
list<string> authors = {"Milton","shakespeare","Austen"};
vector<const char*> articles = {"a","an","the"};
list<string> list2(authors); //正确 类型匹配
deque<string> authlist(authors); //错误 容器类型不匹配
vector<string> words(articles); //错误 容器类型不匹配
//正确 可以将const char*元素转换成string
forward_list<string> word(articles.begin(), articles.end());
在新标准中,我们可以对一个容器进行列表初始化,
list<string> authors = {"Milton","shakespeare","Austen"};
vector<const char*> articles = {"a","an","the"};
当这样做时,我们就显示地指定了容器中每个元素的值。对于除了array之外的容器类型,初始化列表还隐含地指定了容器的大小:容器将包含与初始值一样多的元素。
除了与关联容器相同的构造函数外,顺序容器(array除外)还提供另一个构造函数,它接受一个容器大小和一个(可选的)元素初始值。如果我们不提供元素初始值,则标准库会自动创建一个值初始化器:
vector<int> ivec(10, -1); //10个int元素,每个都初始化为 -1
list<string> svec(10, "hi"); //10个string 每个都初始化为hi
forward_list<int> fvec(10); //10个int元素 每个都初始化为0
deque<string> dvec(10);//10个string 每个都是空string
只有顺序容器的构造函数才接受大小参数,关联容器并不支持。
标准库array具有固定大小
与内置数组一样,标准库array的大小也是类型的一部分。当定义一个array时,除了指定元素类型还要指定容器大小:
array<int, 42> //保存42个int的数组
array<string,10> //保存10个string的数组
}
为了使用array类型,我们必须同时指定元素的类型和大小:
array<int, 10>::size_type i; //数组类型包括元素类型和大小
array<int>::size_type j; //错误: array<int>不是一个类型
array<int, 10> ia1; //10个默认初始化的int
array<int, 10> ia2 = {0,1,2,3,4,5,6,7,8,9}; //列表初始化
array<int, 10> ia3 = {42}; //ia3[0]为42,剩余元素为0
int digs[10] = {0,1,2,3,4,5,6,7,8,9};
int cpy[10] = digs; //错误 内置数组不支持拷贝或赋值
array<int,10> digits= { 0,1,2,3,4,5,6,7,8,9 };
array<int, 10> copy = digits; //正确 只要数组类型匹配即合法
赋值,使用assign(仅顺序容器)
顺序容器(array除外)还定义了一个名为assign的成员,允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。assign操作用参数所指定的元素(的拷贝)替换左边容器中的所有元素。例如:
list<string> names;
vector<const char*> oldstyle;
names = oldstyle; //错误 容器类型不匹配
//正确:可以将const char * 转换成string
names.assign(oldstyle.cbegin(), oldstyle.cend());
由于旧元素被替换,因此传递给assign的迭代器不能指向调用assign的容器。
assign的第二个版本接受一个整型值和一个元素值。它用指定数目且具有相同给定值的元素替换容器中原有的元素:
使用swap
list<string> slist1(1); //1 个元素 为空string
slist1.assign(10, "hiya"); //10个元素,每个都是hiya
//等价于 slist1.clear();
//后跟 slist1.insert(slist1.begin(),10,"hiya");
swap操作交换两个相同类型容器的内容,例如:
vector<string> svec1(10);
vector<string> svec2(24);
swap(svec1, svec2);
元素不会移动的事实意味着,除string外,指向容器的迭代器、引用和指针在swap操作之后不会失效,但是它们指向的元素已经属于不同的容器了。
对一个string调用swap会导致迭代器、引用和指针失效。
与其他容器不同,swap两个array会真正交换它们的元素。因此交换两个array所需的时间与array中元素的数目成正比。因此,对于array,在swap操作之后,迭代器、引用和指针所绑定的元素保持不变,但元素值已经与另一个array中对应元素的值进行了交换。
在新标准库中,容器既提供成员函数版本的swap,也提供非成员函数版本的swap。
关系运算符
每个容器都支持相等运算符(==和!=);除了无序关联容器外所有容器都支持关系运算符(>,>=,<,<=)。关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。
比较两个容器实际上是进行元素的逐对比较:
1)如果两个容器具有相同大小且所有元素都两两对应相等,则两个容器相等,否则两个容器不相等。
2)如果两个容器大小不同,但较小容器中每个元素都等于较大容器中的对应元素,则较小容器小于较大容器
3)如果两个容器都不是另一个容器的前缀子序列,则它们的比较结果取决于第一个不相等元素的比较结果。
容器的相等运算符实际上是使用元素的==运算符实现的,而其他关系运算符是使用元素的<运算符。如果元素类型不支持所需运算符,那么保存这种元素的容器就不能使用相应的关系运算符。