您的位置:首页 > 编程语言 > C语言/C++

C++ 学习笔记(9)顺序容器、容器适配器

2018-01-28 17:35 661 查看

C++ 学习笔记(9)顺序容器

参考书籍:《C++ Primer 5th》

API:容器库

9.1 顺序容器概述



string 和 vector 元素保存在连续的内存空间中。所以用下标计算地址时非常快。

forward list 的设计目的是达到与手写单项列表数据结构相当的性能。

9.2 容器库概述

class A
{
public:
string name;
A(string str) { name = str; }
};

void main()
{
vector<A> v1(2, A("abc"));  // 使用构造函数初始化所有元素。
}




9.2.1 迭代器

迭代器范围(iterator range):由两个迭代器表示,指向同一容器元素或元素之后的位置(begin 和 end)。

9.2.3 begin 和 end 成员

vector<A> v1;

auto it1 = v1.begin();  // it1 是 vector<A>::iterator 类型
auto it2 = v1.cbegin(); // it2 是 vector<A>::const_iterator 类型


9.2.4 容器定义和初始化



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

直接拷贝:要求容器类型相同,其元素类型也相同。

由迭代器指定元素范围:不要求容器类型相同,元素类型也可以不同(但能要转换)。

list<string> str1 = { "abc","jjj","2333" };
vector<const char *> ccp = { "fuck","that","shit" };

// 直接拷贝
list<string> str2(str1);        // 正确,容器类型相同,元素类型相同。
vector<string> vec(str1);       // 错误,容器类型不同。
list<const char *> ccp2(ccp);   // 错误,元素类型不同。

// 迭代器拷贝
forward_list<string> words(ccp.begin(), ccp.end());     // 正确,容器类型可以不同,元素类型不同但可以转换。


array具有固定大小,初始化时需要同时类型和指定大小。

内置数组无法进行拷贝或对象赋值,但是array可以。

int digs[3] = { 1,2,3 };
int cpy[3] = digs;                  // 错误,内置数组不支持拷贝或赋值

array<int, 3> digits = {1,3,5};
array<int, 3> copy = digits;        // 正确,但要求类型要完全相同


9.2.5 赋值和swap



swap交换两个相同类型容器的内容。元素本身并没有交换,交换的是两个容器的内部数据结构(即不对元素拷贝、插入、删除)。这样指向内容迭代器、引用和指针都在swap操作后不会失效。

swap交换两个array会真正交换元素(迭代器、指针和引用指向的位置不变,但是内容变了,所以是错误的),交换的时间和array中元素数目成正比。

9.2.6 容器大小操作

forward_list 支持max_size 和 empty,但不支持size。

9.2.7 关系运算符

类似string:

两个容器元素对应相等时,容器相等。

容器大小不同,但小的都对应相等时,数目少的大于数目多的。

两个容器比较结果也取决于第一个不同的元素比较结果。

9.3 顺序容器操作

9.3.1 向顺序容器中添加元素



对于vector和string,除了尾部,其他位置添加元素都需要移动元素。还可能引起整个对象存储空间重新分配。

对于deque,除了首尾,都要移动元素。

insert返回的是指向新插入元素的迭代器。

当用一个对象来初始化容器时,或将一个对象插入到容器(push或insert),实际上放入到容器中的对象值的拷贝,而非本身。

使用emplace操作是构造而非拷贝。直接在容器中构造,不会产生临时对象。

class A
{
public:
string name = "nothing";
A() = default;
A(string str) { name = str; }
};

void main()
{
vector<A> vecA;
vecA.emplace_back("shit");  // 在容器内部中直接构造元素,调用了A(string str)的构造函数。
vecA.emplace_back();        // 添加了使用默认构造函数的对象。

system("pause");
}


9.3.2 访问元素



front、back、下标、at:返回的都是引用。

vector<string> svec;
cout << svec[0];        // 运行错误。
cout << svec.at(0);     // 抛出out_of_range异常。


9.3.3 删除元素



erase删除元素后,返回指向删除元素后位置的迭代器。

9.3.4 特殊的forward_list操作



在删除一个元素时,会影响到前一个元素的信息(即指向下一个元素的地址)。要删除一个元素,就要在前一个元素调用erase_after。

9.3.5 改变容器大小



9.3.6 容器操作可能使迭代器失效

容器添加元素:

vector 或 string:

重新分配空间时:迭代器、指针引用都失效。

没有重新分配空间:插入之前都有效,之后的都失效。

deque:

插入首尾之外:都会失效。

插入首位置:迭代器失效,指针引用仍有效。

list 或 forward_list:

一直有效。

容器删除元素(所有被删除的元素的迭代器、指针、引用都会失效):

vector 或 string:

之前的都有效,之后的都失效。

deque:

删除首尾之外:都失效。

删除尾元素:迭代器失效,指针引用有效。

删除首元素:都有效。

list 或 forward_list:

一直有效。

9.4 vector 对象是如何增长的



每次需要分配新内存空间时,将当前容量翻倍。

如果需求小于等于当前容量,reserve什么都不做。

调用shrink_to_fit只是一个请求,标准库并不保证退还内存。

仅在插入或调用resize 或 reserve 时,才有可能重新分配空间。即删除(或全部清除)时,不会改变容量大小。

9.5 额外的string 操作

9.5.1 构造string的其他方法

原来介绍的string:C++ 学习笔记(3)命名空间using、字符串、string、vector、迭代器、数组



用const char*构造创建string时,字符串必须以空字符作为结尾,因为拷贝操作在遇到空字符时才会停止。



9.5.2 改变string的其他方法

replace是调用erase和insert的一种简写。





9.5.3 string 搜索操作

npos:const_string::size_type类型,初始值-1(无符号型,即最大值)。



9.5.5 数值转换

// 找到字符串第一个数值,获取其数字字符串,转换成浮点数。
string s = "pi = 3.14";
float d = stod(s.substr(s.find_first_of("+-.0123456789")));




9.6 容器适配器

适配器(adaptor):标准库的一个通用概念。容器、迭代器和函数都适用。本质上是一种机制,能使某种事物的行为看起来像另外一种事物一样。(就像任天堂给switch加个几块纸板一样。)



默认情况下,stack和queue都是基于deque实现的,priority_queue是在vector。

适配器需要有添加和删除能力(要求是顺序容器)。

stack要求拥有back、push_back、pop_back:

vector 、deque 、list。

queue要求拥有back、push_back、front、pop_front:

deque、list。

priority_queue要求拥有front、push_back、pop_back,同时要有随机访问的能力:

vector 、deque。



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: