C++Primer:第九章 顺序容器 笔记

1、概述

1、顺序容器,顾名思义,就是能支持快速顺序访问元素的能力。根据实现不同,在以下两个方面有所差异

1)添加、删除元素的代价

2)非顺序访问的代价。

2、对比


注:

双端队列:双端队列(deque,全名double-ended queue)是一个限定插入和删除操作的数据结构,具有队列和栈的性质双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。

双端队列是限定插入和删除操作在表的两端进行的线性表。这两端分别称做端点1和端点2。也可像栈一样,可以用一个铁道转轨网络来比喻双端队列

3、与内置数组比,array是更安全、更易于使用的数组类型。

4、forward_list的目标是达到与最好的手写单向链表结构相当的性能。它没有size操作。

5、强调:C++新库的容器性能非常好。

6、容器选择基本原则

1)通常,vector是最好的选择---多用vector

2)如果程序有很多小元素,且空间的额外开销很重要,则不要使用list或forward_list。

3)如果要求随机访问,应该使用vector或deque

4)如果要求在中间插入、删除数据,则用list或forward_list。

5)如果要在头、尾插入,但不会在中间插入,则用deque

6)如果只有在读取输入时才需要在容器中间位置插入数据,随后访问需要随机,则:

    确认是否真需要在中间插入。例如,如果要求插入的数据排序,在插入时找位置,不如插入完毕,一次用sort函数来排序。

    如果必须插入,可以先入list,完毕后拷贝到vector。相当于两两配合,结合使用。

7)最佳实践:如果不确定使用哪种,则推荐使用vector和list。公共的操作:使用迭代器,避免随机访问。


2、容器库

1、每个容器的头文件,与容器名相同。容器都是模板类。

定义例子:

list<sales> //保存sales对象的list
deque<double> //保存double的deque

2、顺序容器可保存任意类型的元素,还可以嵌套,即一个容器,其元素是另一个容器。

vector<vector<string>> lines;

3、不同容器也有一些自己的要求。容器操作列表(太长,不一一列举了,讲到时候看吧):

1)类型别名

iterator:此容器类型的迭代器类型

const_iterator:只读迭代器。

size_type:足够保存此种容器类型最大可能容器的大小。

difference_type:两个迭代器之间的距离

value_type:元素类型

reference:元素左值类型(啥意思?后面看有没有解释),与value_type&含义相同。

const_reference :元素const左值类型(const value_type&)。

2)构造函数

C c;  默认构造函数,构造空容器

C c1(c2);   构造c2的拷贝c1

C c(b,e); 构造c,将迭代器b,e之间的元素拷贝到c(array不支持)

C c{a,b,c,...}; 列表初始化

.....抄这个太麻烦,还是先看有用的,再抄吧。

2.1、迭代器

1、在3.4节介绍了迭代器的基本内容,这里是拔高一下。所以先得补充一下3.4的一些基础知识。

2、迭代器提供对对象的间接访问。其对象就是容器中的元素或string对象中的字符。(string特别拿出来说事情

3、迭代器可以移动,就是指向不同的元素

1)有效:指向某个元素,或者容器中尾元素的下一个(这个特别,但有效)

2)无效:其他情况,就是没指向

4、有迭代器的类型可返回迭代器成员,如begin(返回第一个元素)和end(指向尾元素之后,没啥意义,仅仅是一个标志)成员。

auto b=v.begin(),e = v.end();
容器尾空时,begin=end。

5、几个迭代器操作

*iter    返回迭代器iter所指向元素的引用(有点像指针)

iter->mem  解引用iter并获取该元素名为mem的成员,等价于(*iter).mem

++/-- iter:向前、向后移动iter

iter1==iter2/iter1!=iter2:判断相同或不同

6、例子,把string的第一个字母改为大写

string s("some thing");
if(s.begin() != s.end())//判断s不为空
{
    auto it = s.begin();//指向首位的迭代器
    *it = toupper(*it); //赋值
}

另一个例子,结合迭代器移动

for(auto it = s.begin(); it!=s.end()&&!isspace(*it);++it)//isspace,判断字符不为空字符
    *it=toupper(*it);

注意:所有迭代器都定义了==和!=,但不一定都定义了<,所以判断是否到尾部,用it!=s.end(),比it<s.end()要合理。


6、迭代器类型

1)一般来说,不能也无需知道迭代器的精确类型。标准库使用iterator和const_iterator来表示迭代器的类型。

vector<int>::iterator it;//it能读写vector<int>的元素
string::iterator it2;//it2能读写string对象中的字符
vector<int>::const_iterator it3;//it3只能读元素,不能写元素
string::const_iterator i4;//it4只能读字符,不能写字符。

7、begin和end运算符

概念上面已经说了,这里讲点其他的

1)如果对象是常量,begin和end返回const_iterator;否则,返回iterator。

2)但有时这种个默认行为不太好。用cbegin和cend直接获取常量迭代器

8、(*it).empty();   //先对it解引用,再判断其元素是否为空。

9、也可以用->,把解引用和访问成员放在一起。

10、任何改变vector对象容量的操作,都会导致迭代器失效。

11、迭代器运算,支持:

iter +/-n

iter +/-=n

iter1-iter2

>,>=,<,<=

-------------------

以下回归到第九章

8、迭代器闭合区间[begin end)

1)如果begin==end,则范围为空

2)如果不等,则至少包含一个元素

3)递增begin,可到达end

2.2容器类型成员(看成是用于容器的专用类型,定义变量使用)

size_type:看做无符号整数,表示容器大小。机器无关。

iterator:

const_iterator:

反向迭代器:与正向迭代器相比,操作含义发生了点到。对反向迭代器++,会得到上一个元素。

value_type:元素类型

reference/const_reference:元素类型的引用(value_type& /const value_type&)

为需要使用这些类型,必须显式使用其类名。

list<string>::iterator iter;
vector<int>::difference_type count;

2.3begin 和end成员

这个前面讲多了。记录几个关键信息

1)r版本返回反向迭代器,c版本返回const迭代器

2)可以将一个普通的iterator转换为const_iterator,反之不行。

2.4 容器定义和初始化

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

2、操作列表

拷贝构造

C c;   默认构造函数。如果C是一个array,则c中元素按默认方式初始化(不太理解);否则c为空。

C c1(c2);

C c2 = c2;   c1初始化为c2的拷贝。c1c2是相同类型的容器,且保存相同的元素类型。对于array类型,还要求容器大小相同

C c(b,e)   c初始化为迭代器b和e指定范围中的元素拷贝。类型必须相容(注意,这里和上面的拷贝初始化有一些区别,没有要求类型、元素类型相同)。array不适用

list<string> authors = {"aaa","bbb","ccc"};
vector<const char*> articles = {"a","an","the"}

list<string> list2(authors);//正确,类型、元素类型相同,用拷贝构造
deque<string> list3(authors);  //错误,容器类型不同。
vector<string> words(articles);//错误,容器类型不匹配
//正确,const char*可转换为string,相容
forward_list<string> words(articles.begin(),articles.end());
//拷贝构造,直到(但不包括)it)
deque<string> authList(authors.begin(),it);

C c{a,b,c,...}

C c={a,b,c...}  c初始化为列表中元素的拷贝。列表中元素类型必须与C相容(这里是相容即可)。对于array,列表中元素数目必须小于等于array大小。任何遗漏的都进行值初始化(内置类型,为0;否则默认初始化)

列表初始化,隐式指定了容器大小(array除外)

只有顺序容器(不包括array)的构造函数才能接受大小参数

C seq(n)    seq包含n个元素。元素进行了值初始化;次构造函数是explicit的。(string不适用)

C seq(n,t)   n个初始化为t的值。

vector<int> ivec(10,1);//10个int,每个都初始化为1
list<string> svec(10,"aa");//10个string,每个都是“aa”
forward_list<int> ivec(10);//10个元素,每个都是0
deque<string> sevc(10);//10个元素,每个都是空string

如果元素类型是内置类型或有默认构造函数,只传容器大小参数即可。否则,需要指定初值。

3、array

array比较特殊。因为array大小是不能变得,所以除了指定类型,还指定大小

array<int, 30>
array<string,10>

大小是array类型的一部分

array<int,10>::size_type i;//如果没有10这个参数,就错误了。
array<int,10> ia1;//10个int ,默认值
array<int,10> ia2={0,1,2,3,4,5,6,7,8,9};//元素个数要与定义一致
array<int,10> ia3={1,2};//前两个用列表值初始化,后面都为0

不能对内置类型数组拷贝,但可以对array进行拷贝,对比以下:

int digs[10]={0,1,2,3,4,5,6,7,8,9};
int digs2[10]=digs;//错误,内置类型数组不能拷贝

array<int,10> digits={0,1,2,3,4,5,6,7,8,9};
array<int,10> copy =digits;//类型匹配即可拷贝赋值

2.5赋值和swap


1、赋值运算符将左边容器中的全部元素替换为右边容器中元素的拷贝。注意:左边容器的大小会随赋值,变成与右边一致。

2、assign(仅顺序容器,且array除外

解决不相同但相容的赋值问题。也可以赋值子序列。参数也需要调用迭代器。有两种参数形式。

list<string> name;
vector<const char*> oldstyle;
names = oldstyle;//错误,类型不相同
names.assign(oldstyle.cbegin(),oldstyle.cend());//正确,类型相容。

也可以接受一个整形和一个元素值。

list<string> slist1(1);//1个元素,空string
slist1.assign(10,"123");//10个元素,都是123

3、swap

1)交换实际是交换两个容器的数据结构(array是异类。)

vector<string> svec1(10);
vector<string> svec2(20);
swap(svec1,svec2)

此时,svec1长度20,svec2是10,交换了。

2)除array外,swap不对任何元素进行拷贝、删除或插入,可保证在常数时间完成。

元素不会被移动:除string外(string特殊,相关成员会失效),指向容器的迭代器、引用和指针在swap之后都不会失效,仍然指向swap操作之前所指向的那些元素。

array也是个例外:它真正会交换所有元素(有点傻,所以性能与array大小有关)。但迭代器、引用和指针还是与绑定元素保持关联。

统一使用非成员版本是好习惯

2.6容器大小

1、1个例外,其容器都有三个大小相关操作

size:返回容器中数目。例外就是forward_list不支持。

empty:判断容器是否为空

max_size:返回大于等于该类型容器所能容纳最大元素数的值


2.7关系运算符

1、==/!=/>/</>=/<=:相同类型+相同类型元素

2、所谓比较,就是对元素进行一一对比。

一般有这种需求吗?


3、顺序容器操作

3.1向顺序容器添加元素

1、几个约束

1)array显然不能再添加了,其他都可以。

2)forward_list有自己专有版本的insert和emplace

3)forward_list不支持push_back和emplace_back

4)vector和string不支持push_front和emplace_front。

5)向vector、string或deque插入元素会导致指向容器的迭代器、引用和指针失效。


2、push_back:追加元素到尾部

string word;
while(cin>>word)
    container.push_back(word);//从输入获取字符串,向尾部追加

string是一个字符容器,也可以这样操作

    

string str;
str.push_back('a');//等同于str+='a'

注意:当用一个对象来初始化容器,或将一个对象插入到容器中时,实际放入容器中的是对象的拷贝,而不是对象本身。放入后,两者之间无其他关联,相互不影响。


3、push_front:插入到头部

4、insert:插入到特定位置

5、emplace:构造而不是拷贝元素。

---上面这些操作的细节,在真正使用时查询即可,不在这里记录了。

3.2访问元素

1、访问元素操作

at和下标只适用于string、vector、deque、array

back不适用于fordward_list

c.back()  :尾元素的引用,若c为空,函数行未定义

c.front():首元素,一样,如果为空,未定义

------所以,使用之前需要对c.empty()做判断

c[n]:下标访问,越界后,未定义。

c.at[n]:也是下标访问,不过比c[n]多的是,越界后会抛出out_of_range异常。所以,为确保安全,可以用at。

3.3 删除元素

1、一样,array不适用

2、forward_list有特殊版本的erase(有加就有删)

3、forward_list不支持pop_back;vector 和string不支持pop_front(也是与前面添加对应)

4、c.pop_back()/c.pop_front():删除尾/首元素,如果c为空,未定义

5、c.erase(p): 删除迭代器p所指定的元素。

6、c.erase(b,e):删除迭代器所指范围

7、c.clear():删除所有元素

注意:删除前,要自己检查元素存在

3.4 forward_list的特殊性

1、因为这家伙是个单向链表,所以需要独特处理。因为是单向,所以只能站在前面处理后面的,所有都是一系列的after。

lst.before_begin():有点类似迭代器end,返回最后元素之后,这个是首元素之前。实际是想用他的next的概念。

lst.cbefore_begin()

lst.insert_after(p,t)/lst.insert_after(p,n,t)/lst.insert_after(p,b,e)/lst.insert_after(p,i1)

emplace_after(p,args)

lst.erase_after(p)/lst.erase_aftger(b,e)

例如:传递erase_after参数prev,则该调用将prev之后的元素删除。返回值指向序列中下一个元素。prev保持不变。

3.5改变容器大小

1、array不支持。改小,容器后面的元素被删除。改大,新元素增加在后面。

c.resize(n)

c.resize(n,t)

如果为提供元素值参数,则采用值初始化。如果容器保存的是类类型元素,且是增大容器,必须提供初始值/默认构造函数。

2、注意:会造成迭代器失效。个人觉得,不需要记这么细致,都按照list/forward_list有效、其它无效来处理保险,重新定位。尾部迭代器更有问题,不要保存,每次重新调用。

添加元素:

vector/string:空间重新分配,则迭代器、指针、引用都失效;未重新分配,插入位置之前的有效,之后的失效(这种情况下,实际也不好用了。当失效处理会保险一点

deque:插入除首位位置,迭代器、指针、引用都会失效。在首尾添加元素,迭代器失效,其他两个还有效。

list/forward_list:都有效

删除元素:

list/forward_list:都有效

deque:首尾之外删除,都失效;删除deque尾元素,则尾后迭代器失效,但其他不受影响;如果删除首元素,都不收影响。

vector/string:被删除元素之前的有效。当删除元素时:尾迭代器总是无效。


4、vector对象是如何增长的

1、对vector和string,要保证存储空间的连续性。在空间不够时,会申请更大的空间,预留一些,以提升性能。

2、几个操作,体现出vector/string的预分配的概念

c.shrink_to_fit()   将capacity()减小到等于size()

c.capacity()   不重新分配空间的话,可保存的最大元素数

c.reserve(n)   分配至少能容纳n个元素的空间

5、额外的string操作

大概了解一下,用的时候查就可以了。

1、构造方法---直接看例子就可以了。

const char *p = "hello world!!";//以空字符结束
char noNULL[]={'h','i'};  //不以空字符结束

string s1(cp);//拷贝cp,遇到空字符停止   s1=="hello world!!"
string s2(noNULL,2);//从noNULL拷贝2个字符  s2=="hi"
string s3(noNULL);//错误,因为noNULL不是以空字符结束
string s4(cp+6,5);//从cp[6]开始拷贝5个字符。  s4=="world"
string s5(s1,6,5);//从s1[6]开始拷贝5个字符    s5=="world"
string s6(s1,6);//从s1[6]开始,拷贝到末尾     s6="world!!"
string s7(s1,6,20);//从s1[6]开始,只拷贝到末尾  s7=="world!!"
string s8(s1,16);//out_of_range异常

2、substr

s.substr(pos,n);   返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值是0,n的默认值是s.size()-pos,即拷贝从pos开始的所有字符。


3、支持顺序容器的赋值运算符、assign、insert、erase外,还有接受下标的版本。

----具体后面再实验

4、append和replace函数

append:在string尾部插入。可以用insert插入最后,也可以直接用append。

string s("C++ primer"),s2=s;
s.insert(s.size(),"5th ."
s2.append(" 5th.");//s==s2

replace:替换。

5.3string搜索

s.find(args);    s中找args第一次出现的位置

s.rfind(args);    s中args最后一次出现的位置

s.find_first_of(args);   在s中查找args中任何一个字符第一次出现的位置

s.find_last_of(args);在s中查找args中任何一个字符最后一次出现的位置

s.find_first_not_of(args); 在s中查找第一个不在args中的字符

s.find_last_not_of(args);在s中查找最后一个不在args中的字符

args格式:

c,pos   从s中位置pos开始查找字符c。pos默认为0

s2,pos 从s中位置pos开始查找字符串s2.pos默认为0

cp,mpos 从s中位置pos开始查找指针cp指向的以空字符结尾的C风格字符串。pos默认为0

cp,pos,n 从s中位置pos开始查找指针cp指向的数组前n个字符。pos和n无默认值。


5.4compare比较函数

到时候查

5.5数值转换

到时候查


6、容器适配器

1、标准库还定义了三个顺序容器适配器:stack、queue、priority_queue。

2、本质上,适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。

3、stack适配器接受一个顺序容器(array和forwared_list除外),使其操作像一个stack

可以理解成,用顺序容器,实现了新功能的容器,有对应的操作。




---------------------------

本章基本到此,细节用的时候还需要查询。

2018.5.16  22:07

深圳

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值