C++ Primer笔记9---chapter9 顺序容器
Posted Ston.V
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ Primer笔记9---chapter9 顺序容器相关的知识,希望对你有一定的参考价值。
1.顺序容器就是不依赖元素的值,而是与元素加入容器时的位置相对应
vector 可变大小数组,支持随机访问,在尾部之外插入或删除很慢 deque 双端队列,支持随机访问,在头尾插入速度很快 list 双向链表,只能顺序访问,在任何位置插入删除都很快 forward_list 单向链表,只能顺序访问,在任何位置插入删除都很快 array 固定大小数组 string 与vector相似,专门保存字符
2.迭代器类别(可以用auto自动判):
普通迭代器,例如vector<int>::iterator,(begin,end)
const迭代器,例如vector<int>::const_iterator,(cbegin,cend);
反向迭代器,例如vector<int>::reverse_iterator,(rbegin,rend,此时迭代器执行自增操作会得到上一个元素)
当然也会有类似vector<int>::const_reverse_iterator,(crbegin,crend)
对于常量容器的迭代器始终是const版本的,使用rbegin/rend得到的迭代器也是const版本,如果不需要写访问时,就应该使用cbegin和cend
3.有一种容器的定义和初始化方式以前来时忽略:C c(b,e);其中b和e都是迭代器,c初始化为b和e指定范围内的元素拷贝。
当创建一个容器为另一个容器的拷贝时,两个容器的类型和元素类型必须匹配;
但当传递迭代器范围来拷贝时就不要求这二者相同了,只要求被拷贝的元素能够转化为初始化容器的元素类型
list <string> authors = {"12","32","35"}; vector<const char *>articles = {"423","423","rw"}; //开始进行定义并初始化 list<string> list2(authors); //正确:直接拷贝容器,类型匹配 deque<string> authorList1(authors); //错误:直接拷贝容器,但是容器类型不匹配 vector<string> words(articles); //错误:直接拷贝容器,但是元素类型不匹配 //正确,迭代器范围拷贝,元素类型能转化即可 forward_list<string> words(articles.begin(),articles.end());
4.顺序容器可以接受大小参数,如果元素类型没有默认构造函数(提供初始值),那么出了大小参数外,还必须指定一个显示的元素初始值。
vector<int>(10,1); //10和1 forward_list<int>(10); //10个0
只有顺序元素才能接受大小参数,关联容器不支持。
5.array类型:
固定数组,当定义一个array时除了指定元素类型,还要必须指定容器大小
array<int ,42>; //类型为保存42个int的数组 array<string,10>;
虽然我们不能对内置数组类型进行拷贝或者对象赋值,但是array并没有此限制(面试可能会考),但是需要注意array的拷贝需要元素类型和大小都一样,因为大小也是array类型的一部分
int digs[10]={0,10,312,3213,3123,123,123,312}; int copy[10]=digs; //错误 array<int ,10>digits = {0,10,312,3213,3123,123,123,312}; array<int ,10> copy=digits; //正确:数组类型匹配即合法
注意:array不能够使用花括号列表赋值(其他顺序容器可以),也不支持assign,因此右边运算对象的大小可能与左边的大小不一样;但是初始化是可以用花括号列表的,默认值后面为0(赋值和初始化的区别)
6.赋值与swap:
6.1 赋值运算符赋值:需要容器类型和元素类型一致(array还需要大小一致,array不支持花括号赋值)
6.2 使用assign:仅顺序容器,不适用于关联容器和array(类似初始化中小括号的用法)
seq.assign(b,e) 将seq中的元素替换为迭代器b和e所表示范围的元素,b和e不能指向seq中的元素 seq.assign(il) 将seq中的元素替换为初始化列表il中的元素 seq.assign(n,t) 将seq中的元素替换为n个t list<string>names; vector<const char *>oldstyle; names = oldstyle; //错误:类型不匹配 names.assign(oldstyle.cbegin(),old.cend()); //正确,可以将const char*转化为string
6.3 使用swap:交换两个相同类型容器的内容,通常可直接拷贝快的多(array除外)。
除了array外,交换两个容器的内容操作就很快完成,其中的元素本身并未交换,交换的是两个容器内部的数据结构。因此除了string外,指向容器的迭代器、引用、指针在swap操作后并不会失效,他们仍然指向swap操作之前的元素,但是如今这些元素已经属于不同容器了。
vector<string> svec1(10); //it指向svec1.begin() auto it=svec1.begin(); vector<string> svec2(24); //调用swap后,svec1有24个string元素,svec2有10个元素 //如今it没有失效,指向svec2.begin(),但是实际上他指向的元素没有变,元素所属的容器变了(元素值没变,元素变了) swap(svec1,svec2);
对string容器调用swap会导致迭代器、指针、引用失效
swap两个array会真正交换他们的元素,因此交换两个array元素的时间与元素数目成正比,而他们迭代器、指针、引用所绑定的元素没有改变,但是实际的元素值改变了
swap既有成员函数版本,也有非成员版本,统一使用非成员版本有利于泛型编程。
7.每个容器都支持相等运算(==或!=);除了无序关联容器外的所有容器都支持关系运算符(比较方法类似于string对象的比较);容器的比较只有相同类型的才能进行比较;只有容器中的元素也定义了相应的比较运算时,我们才能够用关系运算符比较两个容器
8.向顺序容器添加元素(除了array)
8.1 操作列表
这些操作会改变容器大小,array不支持这些操作
forward_list有自己的insert和emplace
forward_list不支持push_back和emplace_back
vector和string不支持push_front和emplace_front(但是可以用insert模拟,虽然开销更大)
c.push_back(t)
c.emplace_back(args)
在c的尾部插入;返回void c.push_front(t)
c.push_front(args)
在c的头部插入;返回void c.insert(p,t)
c.insert(p,args)
在迭代器p之前插入;返回指向新添加元素的迭代器 c.insert(p,n,t) 在迭代器p之前插入n个t;若n为0返回p,否则返回指向新添加的第一个元素的迭代器 c.insert(p,b,e) 在迭代器p之前插入迭代器范围b~e之间的元素,其中b和e不能指向c中的元素;若列表为空返回p,否则返回指向新添加的第一个元素的迭代器 c.insert(p,il) il是一个花括号包围的元素值列表,插入迭代器p之前;若列表为空返回p,否则返回指向新添加的第一个元素的迭代器 向vector,string,deque中插入元素会使所有指向容器的迭代器,指针,引用失效
8.2 返回值作用
可以使用返回值在某个特定位置不断插入元素
list<string> lst; auto it=lst.begin(); while(cin>>word) it=lst.insert(it,word); //这里两句作用等价循环调用push_front
8.3 早vector或者string的尾部之外,或者是一个deque首位之外的任何位置添加元素都需要添加元素,可能引起整个存储空间的重新分配
8.4 emplace操作
类似于类的隐式转换的作用
//这两句等价 //在容器管理的内存空间中直接创建对象,然后插入尾部 c.emplace_back("978-4234234",25,15.99); //创建一个局部临时对象,然后压入容器 c.push_back((Sales_data("978-4234234",25,15.99)));
9.在顺序容器中访问元素
at和下标操作只适用于string,vector,deque和string
back不适用于forward_list
c.back() 返回c中尾元素的引用,等价于*(--c.end());要注意c是否为空 c.front() 返回c中首元素的引用,等价于*(c.begin());要注意c是否为空 c[n] 返回下标元素的引用;要注意n是否越界 c.at(n) 返回下表元素的引用;注意n是否越界 使用back和front一定注意c是否为空,就和数组越界一样要注意
10.在顺序容器中删除元素
这些操作会改变容器大小,不适用于array
forward_list有特殊版本的erase
forward_list不支持pop_back;vector和string不支持pop_front
c.pop_back() 删除尾元素;返回空;注意c是否为空 c.pop_front() 删除首元素;返回空;注意c是否为空 c.erase(p) 删除迭代器p所指元素;返回被删除元素的后一个元素的迭代器(可能是尾后迭代器end) c.erase(b,e) 删除迭代器范围b~e之间的元素;返回指向最后一个被删的之后的第一个元素的迭代器(其实就是e) c.clear() 清空;返回空
11.特殊的forward_list操作
在链表中,插入删除元素是需要访问后继和前驱的,但是单向链表无法的到前驱,因此在一个forward_list中添加或者删除元素是通过改变给定元素之后的元素来完成的,所有用的都是insert_after、emplace_after、erase_after。(用法与之前的其实一样,就是名字多了个after,实际操作的不是给定迭代器之前的元素而是之后的元素)
另外forward_list还定义了一个首前迭代器before_begin,这个迭代器允许我们在链表首元素之前并不存在的“元素”之后添加或者删除元素。(亦即在首元素之前添加、删除元素)
由于操作的是给定迭代器之后元素的,需要注意,这有个删除列表中奇数的code
#include <iostream> #include <forward_list> using namespace std; int main(){ forward_list<int> flst={1,2,3,4,5,6,7,8,9,90}; auto prev=flst.before_begin(); auto curr=flst.begin(); while(curr!=flst.end()){ if(*curr%2) //因为删除的是给定的元素后面的迭代器,所以即使此时prev是首前迭代器也没事 //返回的就是prev之后的,直接就是curr curr=flst.erase_after(prev); else{ prev=curr; ++curr; } } for(auto const &a:flst) cout<<a<<" "; cout<<endl; return 0; }
12.改变容器大小:resize (两种参数形式)
list<int> ilst(10,42); //10个42 ilst.resize(15); //之前有10个元素,再在后面加5个0 ilst.resiez(25,-1); //之前有15个元素,再在后面加10个-1 ilist.resize(5); //之前有25个元素,删除掉末尾的20个元素
13.容器操作可能会使迭代器、指针、引用失效,最小化要求迭代器必须保持有效的程序片段是一个好办法,每个改变容器操作之后必须重新定位迭代器,这个建议对vector、string、deque尤为重要
在循环中调用insert或者erase中,更新迭代器较容器,这些操作都返回迭代器(直接插头插尾不返回),可以用返回值更新。不要保存end迭代器,它很容易改变,使用end()获取的操作都很快。
#include <vector> using namespace std; int main(){ //删除偶数元素,复制奇数元素 vector<int> vec={1,2,3,4,5,6,7,8,9,90}; auto it=vec.begin(); //会改变元素,不要保存end迭代器,使用end()开销很小 while(it!=vec.end()){ if(*it%2){ //it目前指向新添加的元素,因为是在指定元素之前添加的,指向下一个元素时需要+2 it=vec.insert(it,*it); it+=2; }else{ //直接删除就好,会自动指向下一个元素 it=vec.erase(it); } } return 0; }
14.vector对象如何增长(面试考题)
vector和string的实现通常会分配比新的空间的需求更大的内存空间,这样就不需要每次添加新的元素都重新分配容器的内存空间。(可能会移动元素,但不至于频繁重新分配内存,只有迫不得已时才会重新分配内存)
管理容量的成员函数
capacity操作可以告诉我们在不扩张内存空间的情况下可以容纳多少个元素,reserve操作允许我们通知容器他应该准备保存多少个元素(研究生分配导师时问过我)
shrink_to_fit只适用于vector、string和deque
capacity和reserve只适用于vector和string
c.shrink_to_fit() 请将capacity()减少为size()相同大小 c.capacity() 不重新分配内存空间的话,c可以保存多少个元素 c.reserve(n) 分配至少能容纳n个元素的内存空间 reserve并不改变容器中元素的数量,仅影响vector预先分配多大的内存空间(理解为reserve是设置,capacity是读取就行)类似的,resize只会改变容器中元素的数目,而不是容器的容量(size保存元素的数目,capacity保存容器在不分配新的内存空间的情况下最多能保存多少个元素)
如果需求大小小于等于当前容量,reserve什么也不做。这样reserve永远也不会减小容器占用的内存空间
调用shrink_to_fit可以退回不需要的内存空间,但在具体实现中,该函数也可能忽略此请求,即调用该函数并不保证一定会退回内存空间
15.额外的string操作&适配器
https://blog.csdn.net/hejiegoubao/article/details/104682956
都是死的,忘了再查就行。
//从s1[pos]处一直复制到尾给s string s(s1,pos); //从s1[pos]处赋值len个字符给s string s(s1,pos,len); //对于const char*的cp,第二个参数n指的是从头开始多少个字符,无法指定起始位置 string s(cp,n);
//substr(pos,len) string s("hello world"); string s1=s.substr(6); //s1=world,省略了计数值,默认到字符尾
insert和erase还提供下标的版本;append & replace & find(以及其变形) & compare & 数值转换 & 适配器(stack,queue,prioritty_queue)
以上是关于C++ Primer笔记9---chapter9 顺序容器的主要内容,如果未能解决你的问题,请参考以下文章
C++ Primer笔记16---chapter13 代码实例
C++ Primer笔记16---chapter13 代码实例
C++ Primer笔记15---chapter13 拷贝控制2