容器库概览

容器类型上的操作形成了一种层次:

  • 某些操作是所有容器类型都提供的。
  • 另一些操作针对顺序容器、关联容器或无序容器。
  • 还有一些操作只适用于一小部分容器。

一般每个容器都定义在一个头文件之中,文件名与类型名相同,即 deque 定义在头文件 deque 中,list 定义在 list 中,依次类推。

容器都是模板类,对大多数,但不是所有容器,还需要提供额外的元素类型信息。

对容器可以保存的元素类型的限制

顺序容器几乎可以保存任意类型的元素,特别是,可以定义一个容器,其元素类型是另一容器。

vector<vector<string>> lines;

交旧的编译器可能需要在两个尖括号之间键入空格:

vector<vector<string> > lines;

顺序容器构造函数的一个版本是接受容器大小参数,它使用了元素类型的默认构造函数,但某些类没有默认的构造函数,可以定义一个保存这种类型对象的容器,但构造这种容器时不能只传递一个元素数目参数:

//假设 noDefault 是一个没有默认构造函数的类型
vector<noDefault> v1(10, init);     //正确,提供了元素初始化器
vector<noDefault> v1(10);       //错误,必须提供一个元素初始化器
类型别名 说明
iterator 此容器类型的迭代器类型
const_iterator 可以读取元素,但不能修改元素的迭代器类型
size_type 无符号整数类型,足够保存此种容器类型最大可能容器的大小
difference_type 带符号整数类型,足够保存两个迭代器之间的大小
value_type 元素类型
reference 元素的左值类型,与value_type
const_reference 元素的const左值类型,即const value_type&
构造函数 说明
C c 默认构造函数,构造空容器
C c1(c2) 构造c2的拷贝c1
C c(b,e) 构造c,将迭代器b和e指定的范围内的元素拷贝到c ( array不支持)
C c{a,b,c...} 列表初始化c
赋值与swap 说明
c1 = c2 将c1中的元素替换为c2中的元素
c1 = {a,b,c...} 将c1中的元素替换为列表元素( array不支持)
a.swap(b) 交换a和b的元素
swap(a,b) 与a.swap(b)等价
大小 说明
c.size() c中元素的数目,不支持forward_list
c.max_size() c可保存的最大元素数目
c.empty() 若c中存储了元素,返回false,否则返回true
添加、删除元素(不适用于array) 说明
c.insert(args) 将args的元素拷贝进c
c.emplace(inits) 使用inits构造c中的一个元素
c.erase(args) 删除args指定的元素
c.clear() 删除c中所有元素,返回void
关系运算符 说明
==,!= 所有容器都支持相等、不等运算符
<,<=,>,>= 关系运算符,无序关联容器不支持
获取迭代器 说明
c.begin(),c.end() 返回指向c首元素和尾元素之后位置的迭代器
c.cbegin(),c.cend() 返回const_iterator
反向容器的额外成员(forward_list不支持) 说明
reverse_iterator 按逆序寻址元素的迭代器
const_reverse_iterator 不能修改元素的逆序迭代器
c.rbegin(),c.rend() 返回指向c的尾元素和首元素之前位置的迭代器
c.crbegin(),c.crend() 返回const_reverse_iterator

迭代器

迭代器范围

一个迭代器的范围由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者尾元素之后的位置,这两个迭代器通常被称为 beginend。这种元素范围称为左闭右开区间,即 [begin,end)

两个迭代器构成范围迭代器的要求:

  • 它们指向同一个容器中的元素,或者是容器最后一个元素之后的位置。
  • 可以通过反复递增 begin 来到达 end,也就是说,end 不应该在 begin 之前。

假定 beginend 构成一个合法的迭代器范围:

  • 如果 beginend 相等,则范围为空。
  • 如果 beginend不相等,则范围至少包含一个元素,且begin指向范围中的第一个元素。
  • 可以对 begin 递增若干次,使得 begin == end

因此可以采用一个循环来处理一个元素范围:

while(begin != end){
    *begin = val;
    ++begin;
}

容器类型成员

大多数容器还提供反向迭代器,反向迭代器就是一种反向遍历容器的迭代器,与正向迭代器相比,各种操作的含义都是颠倒的,例如,对一个反向迭代器执行 ++,会得到上一个元素。

如果需要使用元素类型,则可以使用容器的 value_type

如果需要元素类型的一个引用,可以使用 referenceconst_reference

begin 和 end 成员

beginend 操作生成指向容器第一个元素和尾后元素之后的位置的迭代器。这两个迭代器最常用的就是形成一个包含容器所有元素的迭代器范围。

r 开头的版本返回反向迭代器。

c 开头的版本返回 const 迭代器。

list<sting> a = { "Milton","Shakespeare","Austen" };
auto it1 = a.begin();   //list<string>::iterator
auto it2 = a.rbegin();  //list<string>::reverse_iterator
auto it3 = a.cbegin();  //list<string>::const_iterator
auto it4 = a.crbegin(); //list<string>::const_reverse_iterator

autobeginend 结合使用时,获取的迭代器类型依赖于容器类型:

//显示指定类型
list<string>::iterator it5 = a.begin();
list<string>::const_iterator it6 = a.begin();

auto it7 = a.begin();   //仅当a是const时,it7是const_iterator
auto it8 = a.cbegin();  //it8 是const_iterator

不需要写访问时,应该使用 cbegincend

容器定义和初始化

每个容器类型都定义了一个默认构造函数,除了 array 之外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器大小和元素初始值的参数。

将一个容器初始化为另一容器的拷贝

将一个新容器创建为另一个容器的拷贝方法有两种:

  • 直接拷贝整个容器。
  • 拷贝由一个迭代器对指定的元素范围(array除外)。

为了创建一个容器为另一个容器的拷贝,两个容器的类型及其元素类型必须匹配。

传递迭代器参数来拷贝一个范围时,就不要求容器类型相同,而且新、旧容器中的元素类型也可以不同,只要能将要拷贝的元素转换为要初始化的容器的元素类型即可。

list<sting> 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> words(articles.begin(), articles.end());

//假设it表示articles的一个元素,可以拷贝一个指定的范围
forward_list<string> words2(articles.begin(), it));

列表初始化

list<sting> authors = { "Milton","Shakespeare","Austen" };
vector<const char*> articles = {"a","an","the"};

对于除 array之外的容器类型,列表初始化还隐含的指定了容器的大小,容器将包含与初始值一样多的元素。

与顺序容器大小相关的构造函数

除了与关联容器相同的构造函数之外,顺序容器(array除外)还提供了另一个构造函数,它接受一个容器大小和一个元素初始值(可选),如果不提供初始元素值,则标准会创建一个值初始化:

vector<int> ivec(10, -1);
list<string> svec(10,"hi");
forward_list<int> ivec(10); //10个元素,每个元素都是0
deque<string> svec(10); //10个元素,每个元素都是空的string
  • 如果元素类型是内置类型或者具有默认构造函数的类型,可以只为构造函数提供一个容器大小。
  • 如果元素类型没有默认的构造函数,除了提供容器大小参数外,还必须指定一个显示的元素初始值。
  • 只有顺序容器的构造函数接受大小参数,关联容器并不支持。

标准库 array 具有固定大小

当定义一个 array 时,除了需要指定元素类型,还需要指定容器的大小。

array<int,42>;        //保存42个int的数组
array<string,42>;    //保存42个string的数组

为了使用 array 类型,必须同时指定元素类型和大小:

array<int,42>::size_type i;     //数组类型包括元素的类型和大小
array<int>::size_type j;        //错误,array<int>不是一个类型

一个默认构造的 array 是空的:它包含了与其大小一样多的元素,元素被执行默认初始化。

如果对 array 进行列表初始化,初始值的数目必须等于或者小于 array 的大小。如果初始值数目小于 array 的大小,则它们被用来初始化 array 靠前的元素,剩余的元素执行值初始化。如果元素类型是一个类类型,那么该类必须有默认的构造函数,以使值初始化能够进行。

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

内置数组不能执行拷贝或赋值,但是array不受此限制:

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;        //正确,只要数组类型匹配即可

赋值和 swap

赋值运算符将其左边容器中的全部元素替换为右边容器中的元素的拷贝:

c1 = c2;    //c1的 内容替换为c2中的元素拷贝
c1 = {a,b,c}    //赋值后,c1的大小为3   

第一个赋值运算后,左边容器将与右边容器相等,如果两个容器的原来大小不同,赋值运算后的两者的大小都与右边容器的原大小相同。

array 类型允许赋值,赋值符号左右两边的运算对象必须具有相同的类型。

array<int,10> a1 = {0,1,2,3,4,5,6,7,8,9};
array<int,10> a1 = {0}; //所有元素的值都是0
a1 = a2;
a2 = {0};   //错误,不能将一个花括号列表赋予数组

由于右边运算对象的大小可能与左边运算对象的大小不同,因此 array 不支持 assign,也不允许用花括号包围的值列表进行赋值。

使用 assign (仅顺序容器)

顺序容器(array 除外)还定义了一个 assign 成员,允许我们从一个不同但类型相容的类型赋值,或者从容器的一个子序列赋值。assign 操作用参数所指定的元素替换左边容器中的所有元素:

list<string> names;
vector<const char*> oldstyle;
names = oldstyle; //错误,容器类型不匹配

names.assign(oldstyle.cbegin(),oldstyle.cend()); //正确,可以将const char* 转换成string

由于就的元素被代替,因此传递给 assign 的迭代器不能指向调用 assign 的容器。

assign 的第二个版本接受一个整型值和一个元素,它用指定书目且具有相同给定值的元素替换容器中的原有元素:

list<string> slist1(1); //1个元素,指定为空的string
slist1.assign(10,"hi"); //10个元素,每个都是 "hi"--

使用 swap

swap 操作交换两个相同类型容器的内容,调用swap之后,两个容器中的元素将会交换:

vector<string> svec1(10);
vector<string> svec2(24);

swap(svec1,svec2); //svec1将包含24个元素,svec2将包含10个元素

交换两个容器内容的操作保证会很快,元素本身并未交换,swap 只是交换了两个容器的内部数据结构。

array 外,swap 不对任何元素进行拷贝、删除或插入操作,因此能够保证在常数时间内完成。

元素不会被移动的事实意味着,除 string 之外,指向容器的迭代器、引用和指针在 swap 操作后不会失效。它们仍然指向 swap 之前所指向的那些元素,但是在 swap 之后,这些元素已经属于不同的容器了假定 iterswap 之前指向 svec1[3]string,那么 swap 之后它指向 svec2[3] 的元素。

与其他容器不同,对一个 string 调用 swap 会导致迭代器、引用和指针失效。

与其他容器不同, swap 两个 array 会真正交换它们的元素,因此,交换两个 array 所需的时间与 array 中元素的数目成正比。对于 array,在 swap 操作之后,指针、引用和迭代器所绑定的元素保持不变,但元素值已经与另一个 array 中对象的元素值进行了交换。

在新标准库中,容器既提供成员函数版本的 swap,也提供了非成员版本的 swap。统一使用非成员版本的swap 是一个好习惯。

容器大小操作

每个容器类型都有三个与大小相关的操作:

  • 成员函数 size 返回容器中元素的数目。
  • emptysize为0时返回 true,否则返回 false
  • max_size 返回一个等于或大于该类型容器所能容纳的最大元素数的值。

forward_list 支持 max_sizeempty,但不支持 size

关系运算符

  • 只有当其元素类型也定义了相应的比较运算符时,才可以使用关系运算符来比较两个容器。
  • 每个容器都支持 相等运算符(==)和不等运算符(!=)。
  • 除了无序关联容器外所有的容器都支持关系运算符 (>、>=、<、<=)。
  • 关系运算符左右两边的运算对象必须是相同的容器类型,且必须保存相同类型的元素。
  • 比较两个容器实际上是进行元素的逐对比较,这些运算符的工作方式与string的关系运算符类似:
    • 如果两个容器具有相同的大小且所有的元素对应相等,则两个容器相等,否则两个容器不相等。
    • 如果两个容器大小不同,但较小的容器中每个元素都等于较大容器中对应的元素,则较小的容器小于较大的容器。
    • 如果两个容器都不是另一个容器的前缀子序列,则它们的比较结果取决于第一个不相等的元素的比较结果。

猜你喜欢

转载自www.cnblogs.com/xiaojianliu/p/12497159.html