【第九章】顺序容器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34536551/article/details/78750019

● 元素在顺序容器中的顺序与其加入容器时的位置相对应

关联容器中元素的位置由元素相关联的关键字值决定。

● 在 deque两端添加或删除元素与listforward_list添加删除的速度相当。

● 与内置数组相比,array 是一种更加安全、更容易使用的数组类型,array对象的大小是固定的,array不支持添加删除元素以及改变容器大小的操作。

● 注意: forward_list 没有 size 操作,因为保存或计算其大小就会比手写链表多出额外的开销。forward_list 还不支持“反向容器的成员”forward_list 迭代器不支持递减运算符

forward_listlist 不支持 迭代器支持的算术运算, 只有“string、 vector 、deque 、array、”这些顺序容器类型能够使用, 我们不能将它们用于其他任何容器类型的迭代器。

注意: forward_list 支持empty、max_size,但不支持size。

forward_list不支持 push_back、emplace_back、 pop_back、insert、emplace、erase操作。

vector 和 string 不支持 push_front、 和 emplace_front、 pop_front.

shrink_to_fit()函数只适用于 vector 和 string


vector <string> svec;

svec.insert(svec.begin(),"Hello!"); //我们可以使用insert 。将元素插入到第一个元素之前,但是可能很耗时。

● 除了arrayforward_list 之外, 每个顺序容器(包括string类型)都支持push_back

● 向一个vector、string 或 deque 插入元素会使所有指向容器的迭代器、引用和指针失效

● 向一个vectorstring添加元素可能引起整个对象存储空间的重新分配。 重新分配一个对象的存储空间需要分配新的内存, 并将元素从旧的空间移动到新的空间中。

list、forward_list、deque这些容器还支持名为push_front 的类似操作。

vector 、list、deque、string都支持insert成员,forward_list 支持特殊版本的insert成员。

● 包括array在内的每个顺序容器都有一个front成员函数, 而除了forward_list之外的所有顺序容器都有一个back成员函数。 分别返回首元素和尾元素的引用。

array 不支持resize操作来改变容器大小。


确定使用哪种顺序容器


这里写图片描述

● 注意 : 容器均定义模板类,我们必须提供额外的信息来生成特定的容器类型。 对大多数,但不是所有容器,还需要提供额外的元素类型信息


迭代器范围


一个迭代器范围由一对迭代器表示, 两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置。 通常被称为beginend它们标记了容器中元素的一个范围。

● 注意: beginend 必须指向相同的容器。 end 可以与begin 指向相同的位置, 但不能指向begin 之前的位置


这里写图片描述


容器类型成员


● 通过类型别名, 我们可以在不了解容器中元素类型的情况下使用它。 如果需要元素类型, 可以使用容器的value_type。 如果需要元素类型的一个引用,可以使用referenceconst_reference


begin 和 end 成员


注意:const 指针和引用类似,可以将一个普通的iterator 转换为对应的 const_iterator, 但反之不行。


容器定义和初始化


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

这里写图片描述


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


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

   可以直接拷贝整个容器  //两个容器类型和元素类型必须匹配

  //不要求两个容器类型和元素类型必须匹配,只要能将要拷贝的元素转换为要初始化的容器的元素类型即可。
  或者(除array外)拷贝由一个迭代器对指定的元素范围进行拷贝。 

接受两个迭代器参数的构造函数用这两个迭代器表示我们想要拷贝的一个元素范围 : 新容器的大小与范围中元素的数目相同。 新容器中的每个元素都用范围中对应元素的值进行初始化.

● 由于两个迭代器表示一个范围, 因此可以使用这种构造函数来拷贝一个容器中的子序列:


vector<string> authors = { "Milton","Shakespeare","Austen" };
auto it = authors.begin() + 2;// it表示authors中的一个元素
deque<string> authVector(authors.begin(), it); //拷贝元素,直到但不包括it指向的元素

● 在C++11新标准中允许我们对一个容器进行列表初始化, 除了array之外的容器类型,初始化列表隐含地指定容器的大小: 容器将包含与初始值一样多的元素个数。


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


● 如果元素类型是内置类型或者是具有默认构造函数的类类型,可以只为构造函数提供一个容器大小参数。

如果元素类型没有默认构造函数,除了大小参数外, 还必须指定一个显式的元素初始值

● 注意: 只有顺序容器的构造函数才接受大小参数,关联容器并不支持。


标准库 array 具有固定大小


● 与内置数组一样,标准库array的大小也是类型的一部分。 当定义一个array时,必须指定元素类型和容器大小。

● 由于大小是array 类型的一部分,array 不支持普通的容器构造函数。

● 与其他容器不同的是, 一个默认构造的array 是非空的: 它包含了与其大小一样多的元素。 这些元素都被默认初始化。

● 如果我们对array 进行列表初始化, 初始值的数目必须等于或小于array的大小。

如果初始值数目小于array的大小,则它们被用来初始化array 靠前的元素,所有剩余元素都会进行值初始化。

在这两种情况下,如果元素类型是一个类类型, 那么该类必须有一个默认构造函数, 以使值初始化能够进行。

● 注意: 虽然我们不能对内置数组进行拷贝或者对象赋值操作,但array并无此限制

int digs[10] = { 0,1,2,3,4,5,6,7,8,9 };
int cpy[10] = digs; //错误
array<int,10>digits = { 0 };
array<int, 10> copy = digits; //正确,只要数组类型匹配即合法

digs = digits ;// 替换digs 中的元素
digits = { 0 }; // 错误:不能将一个花括号列表赋予数组

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


赋值与swap


● 注意: 如果两个容器原来大小不同,赋值运算后两者的大小都与右边容器的原大小相同。

这里写图片描述


使用 assign(关联容器和array除外)


assign 成员: 允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。assign 操作用参数所指定的元素替换(拷贝)左边容器中的所有元素。 assign的参数决定了容器中将有多少个元素以及它们的值都是什么。

● 注意 : 由于旧元素被替换,因此传递给assign 的迭代器不能指向调用assign的容器


使用 swap


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

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

● 元素不会被移动的事实意味着, 指向容器的迭代器、引用和指针swap之后,都不会失效。 它们仍指向swap 操作之前所指向的那些元素。

但是,在swap之后,这些元素已经属于不同的容器了。

注意: 与其它容器不同的是,对一个string 调用swap 会导致迭代器、引用和指针失效。

注意: 与其它容器不同, swap 两个array真正交换它们的元素。 交换两个array所需的时间array 中元素的数目正比

对于array,在swap操作之后 ,指针、引用和迭代器所绑定的元素保持不变, 但元素值已经与另一个array中对应元素的值进行了交换

每个容器类型都有三个与大小相关的操作: size、empty、max_size。

注意: forward_list 支持empty、max_size,但不支持size。


关系运算符


● 每个容器类型都支持相等运算符( == 和 != ); 除了无序关联容器外的所有容器都支持关系运算符——关系运算符左右两边的运算对象必须是容器类型相同和元素类型相同。

● 比较两个容器实际上是进行元素的逐对比较。


容器的关系运算符使用元素的关系运算符完成比较


注意: 只有当其元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符比较两个容器。

● 容器的相等运算符是使用元素的“ == ”运算符实现比较的, 而其他关系运算符是使用元素的“ < ” 运算符实现比较的。

如果元素类型不支持所需运算符, 那么保存这种元素的容器就不能使用相应的关系运算符。


向顺序容器添加元素


注意:array 外, 所有标准库容器都提供灵活的内存管理。在运行时可以动态添加或删除元素来改变容器大小

这里写图片描述


使用push_back


这里写图片描述


在容器中的特定位置添加元素(insert)


● 每个insert函数的第一个参数都接受一个迭代器。 该迭代器指出了在什么位置放置新元素。 它可以指向容器中的任何位置,包括容器尾部之后的下一个位置。


插入范围内元素


list <string> slist;

//运行时错误:迭代器表示要拷贝的范围,不能指向与目的位置相同的容器
slist.insert(slist.begin(),slist.begin(),slist.end());

使用 insert 的返回值


● 通过使用insert的返回值, 可以在容器中一个特定的位置反复插入元素。

list <string> lst;
auto iter=lst.begin();
while(cin>>word)
{
  iter=lst.insert(iter,word); //等价于调用 push_front
}

使用 emplace 操作


● 当我们调用一个emplace 成员函数时,则是将参数传递给元素类型的构造函数。 emplace 成员使用这些参数在容器管理的内存空间中直接构造元素。

而调用push_back 、insert push_front、这样的操作时,则会创建一个局部临时对象,并将其压入容器中。

● 注意: emplace 函数的参数根据元素类型而变化 , 参数类型元素类型的构造函数相匹配emplace 函数实在容器中直接构造(创建)元素。


访问元素


这里写图片描述

● 如果容器中没有元素,访问操作的结果是未定义的。

● 以上在容器中访问元素的成员函数返回的都是引用

● 如果我们使用auto变量来保存这些函数的返回值, 并且希望使用此变量来改变元素的值, 必须记得将变量定义引用类型。

● 下标运算符接受一个下标参数,返回容器中该位置的元素的引用

● 如果我们希望确保下标是合法的,可以使用at 成员函数at 成员函数类似下标运算符, 但如果下标越界at 会抛出一个out_of_range异常。

● 注意: 访问元素的成员函数之前, 必须确保该位置非空, 如果容器为空, 该操作的行为将是未定义的。


删除元素


这里写图片描述

● 注意: 删除元素的成员函数并不检查其参数。在删除元素之前,程序员必须确保它们是存在的。

● 注意: 如果你需要删除元素的值,就必须在执行删除操作之前保存它。


改变容器大小


这里写图片描述

● 如果当前大小大于所要求的新大小, 容器后部的元素会被删除; 如果当前大小小于新大小,会将新元素添加到容器后部。

resize 操作接受一个可选的元素值参数, 用来初始化添加到容器中的元素。 如果调用者未提供此参数, 新元素进行值初始化。

● 如果容器保存的是类类型元素, 且resize向容器添加新元素, 则我们必须提供初始值, 或者元素类型必须提供一个默认的构造函数。


编写改变容器的循环程序


注意: 添加删除vector、string、或者deque元素的循环程序必须考虑迭代器、引用和指针可能失效的问题。 程序必须保证每个循环中都更新迭代器、引用或指针。

如果循环中调用的是inserterase, 那么更新迭代器很容易, 因为这些操作都返回迭代器。


不要保存end 返回的迭代器


注意:当我们添加删除vectorstring的元素后,或在deque首元素之外的任何位置添加删除元素后, 原来end 返回的迭代器总是会失效。

注意:如果在一个循环程序中插入删除deque、vector、string 中的元素, 不要缓存end返回的迭代器,必须在每次插入删除操作后重新调用end(),**而不能在循环之前保存end返回的迭代器,一直当作容器末尾使用。


Vector 对象是如何增长的


● 当不得不获取新的内存空间时,vectorstring的实现通常会分配比新的空间需求更大的内存空间。容器预留这些空间作为备用, 可以用来保存更多的新元素。这样,就不需要每次添加新元素都重新分配容器的内存空间了。

vector在每次重新分配内存空间时都要移动所有元素——将已有元素从旧位置移动到新的空间中,然后添加新元素,释放旧存储空间。

这里写图片描述

注意: reserve 并不改变容器中元素的数量, 它仅影响vector预先分配多大的内存空间。

● 只有当需要的内存空间超过当前容量时,reserve调用才会改变vector容量。

如果需求大小大于当前容量reserve至少分配与需求一样大的内存空间(可能更大)。

注意: 如果需求大小小于或等于当前容量reverse什么也不做。 当需求大小小于当前容量时,容器不会退回内存空间。 因此, 在调用reverse之后, capacity将会大于或等于传递给reverse参数。

● 注意: 调用reserve永远不会减少容器占用的内存空间resize 成员函数只改变容器中元素的数目,而不是容器的容量。 不能使用resize减少容器预留的内存空间。

● 注意: 调用shrink_to_fit来要求deque、vector 或 string 返回不需要的内存空间。 此函数指出我们不再需要任何多余的内存空间。 但是可能调用shrink_to_fit 也并不保证一定退回内存空间。


capacity 和 size


● 注意: 只要没有操作需求超出vector的容量, vector就不能重新分配内存空间。

● 注意: vector 实现采用的策略似乎是在每次需要分配新内存空间时将当前容量翻倍。

● 只有在执行insert 操作时 size 与 capacity 相等, 或者调用 resizereserve 时给定的大小超过当前capacity时, vector 才可能重新分配内存空间。 会分配多少超过给定容量的额外空间,取决于具体实现。

猜你喜欢

转载自blog.csdn.net/qq_34536551/article/details/78750019
今日推荐