S09顺序容器

S09顺序容器


一、顺序容器概述

1、顺序容器类型

vector        //可变大小数组,支持快速随机访问,在尾部之外的位置插入/删除元素很慢
deque         //双端队列,支持快速随机访问,在头/尾位置插入/删除元素很快
list          //双向链表,只支持双向顺序访问,在任何位置插入/删除元素很快
forward_list  //单向链表,只支持单向顺序访问,在任何位置插入/删除元素很快
array         //固定大小数组,支持快速随机访问,不能添加/删除元素
string        //与vector相似但专门用于保存字符,支持快速随机访问,在尾部插入/删除很快

注意:尽可能使用新标准库的容器

二、容器库概览

1、一般每个容器都定义在一个头文件中,文件名与类型名相同,如list在list头文件中

2、容器库及支持的操作

类型别名    
iterator            //此容器类型的迭代器类型
const_iterator
size_type           //无符号整型,足够保存此种容器类型最大可能容器的大小
difference_type     //整型,足够保存两个迭代器之间的距离
value_type          //元素类型
reference           //相当于value_type&
const_reference     

赋值与交换   
c1 = c2             //将c1中的元素替换为c2中的元素
c1 = {a, b, ...}    //将c1中的元素替换为列表中的元素
a.swap(b)           //交换a与b的元素
swap(a,b)           //相当于a.swap(b)或b.swap(a)

大小  
c.size()            //c中元素的数量(不支持forward_list)
c.max_size()        //c中可保存的最大数目
c.empty()           //c中若有元素则范围true否则返回false

添加与删除(不同容器中函数接口可能不同)
c.insert(args)      //将args中的元素拷贝进c
c.emplace(inits)    //使用inits构造c中的一个元素
c.erase(args)       //删除args指定的元素
c.clear()           //删除c中所有元素,返回void

关系元算符   
==/!=               //所有容器都支持
</<=/>/>=           //无序关联容器不支持

获取迭代器   
c.begin()/c.end()   //获取头元素和尾后元素的指针
c.cbegin()/c.cend() //const_iterator

反向(不支持forward_list)
reverse_iterator    //按逆序寻址元素的迭代器
const_reverse_iterator  
c.rbegin()/c.rend() //获取尾元素和头前元素的指针
c.crbegin()/c.crend()
---------------------------------------------------------------
| before_first_elem | elem_1 | ... | elem_n | after_last_elem |
---------------------------------------------------------------
          |              |              |            |
       c.rend()      c.begin()      c.rbegin()    c.end()

注意:反向容器的迭代器递增对应着反向,递减对应着正向

vector<int> a{ 0,1,2,3,4,5 };
for (auto st = a.rbegin(), ed = a.rend(); st != ed; ++st){...}  //++则从5->0,而--从5->?会出错

3、迭代器:标准容器类型上的所有迭代器都通过解引用来访问元素,且当满足以下两个条件时构成范围的迭代器
(1)指向同一个容器中的元素或尾后元素
(2)end不在begin之前,即反复递增begin一定可以达到end

int ia[] = { 0,1,1,2,3,5,8,13,21,55,89 };
vector<int> ivec.assign(ia,ia+11);            //可以实现数组拷贝到向量中

注意:指针是迭代器的一种,迭代器是指针抽象提升后的概念

4、容器定义和初始化
(1)新容器创建为另一个容器的拷贝时,可以直接拷贝整个容器或拷贝由迭代器指定的范围内元素

  • 直接拷贝:要求两个容器的类型及其元素类型必须严格匹配
  • 范围迭代器拷贝:不要求容器类型相同,元素类型只要转换后能匹配也可以
list<string> authors = {...};
vector<const char*> articles = {...};
list<string> list2(authors);      //正确,完全匹配
deque<string> authlist(authors);  //错误,容器类型不匹配
vector<string> words(aricles);    //错误,元素类型不匹配
forward_list<string> words(articles.begin(), articles.end());   //正确,const char*会转换为string

(2)如果元素类型是内置类型或者有默认构造函数,则可以只提供容器大小的参数,若没有默认构造函数则必须显式提供初始值,另外只有顺序容器接受大小参数,关联容器没有
(3)array:具有固定大小,定义时除了提供元素类型还要提供固定大小值,在使用其别名时也要提供大小,大小是类型的一部分

array<int, 10>                 //类型为:保存10个int的数组
array<int, 10>::size_type I;   //不能省略10而写成array<int>::size_type会出错

5、容器赋值与交换

assign操作(不适用于array和关联容器,仅顺序容器,且允许类不同但元素相容的赋值)
seq.assign(b, e)    //将seq的元素换为迭代器b/e范围内的元素,b/e不能指向seq自身
seq.assign(il)      //seq的元素替换为初始化列表il的元素
seq.assign(n,t)     //seq的元素替换为n个值为t的元素

注意:赋值一般(除了swap)会导致指向左侧容器的迭代器、指针、引用失效

注意:swap一般(除了array和string类型的容器)不会导致失效,因为元素本身并没有被移动,假定一个迭代器指向vec1[3],vec1与vec2交换后该迭代器实际指向vec2[3]

6、容器关系运算符:只有容器类型一样,元素类型一样时才可以用关系运算符,且比较过程类似于string的比较,逐个元素调用关系运算符

注意:容器的关系运算符基于元素的关系运算符,只有元素定义了关系运算,且非无序关联容器才可以比较

三、顺序容器操作

1、顺序容器主要操作参考《C++primer 5th》p.305

2、向容器添加/删除元素可能会导致所有指向容器的迭代器、引用和指针失效

注意:由于向迭代器添加/删除元素可能会导致失效,因此必须保证每次改变容器的操作之后都正确的重新定位迭代器

3、容器初始化或插入新元素时,都是使用了原始对象的副本放入容器,故容器元素是拷贝(后面会讲到std::move和右值引用)

4、使用emplace操作,push_front/insert/push_back将元素类型的对象传递给容器,并拷贝到容器中,而对应的emplace_front/emplace/emplace_back则是将参数传递给元素类型的构造函数,使用参数在容器管理的内存中直接构造元素,因此传递的参数必须符合元素类型的构造函数

vector<Sales_data> c;
c.emplace_back("9-999", 25, 15.99);          //正确,将根据参数构造出一个新元素,基于Sales_data的构造函数
c.emplace_back();                            //正确,使用了Sales_data的默认构造函数
c.push_back("9-999", 25, 15.99);             //错误
c.push_back(Sales_data("9-999", 25, 15.99)); //正确,临时生成了Sales_data对象并复制进容器

5、访问元素:不能在容器是空时访问元素,程序员应确保访问的元素存在

c.back()    //返回c中尾元素的引用,若是const的容器则返回const的引用,下同
c.front()   //返回c中首元素的引用
c[n]        //返回c中下标为n的元素的引用,注意下标不能越界,越界时发生运行时错误直接退出
c.at(n)     //返回c中下标为n的元素的引用,注意下标不能越界,尽可能用at(),下标越界先抛出异常

if(!c.empty())
{
    auto val1 = *c.begin(), val2 = c.front();  //由于auto变量用引用初始化时直接会用本体,因此没有&的含义
    auto last = c.end();
    auto val3 = *(--last), val4 = c.back();    //c不是forward_list则正确,val3和val4是尾元素的拷贝
    auto &val5 = c.front();                    //val5才是首元素的引用
}

6、删除元素:不能在元素不存在时删除,程序员应确保删除的元素存在

c.pop_back()    //删除c中尾元素,返回void
c.pop_front()   //删除c中首元素,返回void
c.erase(p)      //删除迭代器p所指的元素,返回被删元素之后元素的迭代器
c.erase(b, e)   //删除迭代器b和e之间所有的元素[b,e),若b==e则什么也不做,返回e指向元素的迭代器,可以认为是返回e
c.clear()       //删除所有元素,返回void

注意:由于数据结构本身的特性,不同的容器支持的操作有差别,forward_list诸多操作有特殊的实现,主要是before_begin/insert_after/emplace_after/erase_after

7、改变容器大小:当减小时多余元素会被删除,当扩大时不足元素会被补齐,只改变元素量size,不改变容量capacity,注意区分capacitysize

c.resize(n)      //调整c的大小为n个元素,新元素进行值初始化
c.resize(n, t)   //调整c的大小为n个元素,新元素初始化为t
四、vector对象是如何增长的

1、管理容量

c.shrink_to_fit()//将capacity()减少为与size()相同大小,适用vector/string/deque(只是请求,不保证退还内存)
c.capacity()     //不重新分配内存空间的话,c可以储存的元素数量,适用vector/string
c.reserve(n)     //分配至少能容纳n个元素的内存空间,适用vector/string

注意:当n大于当前capacity时,reserve至少分配与n一样大的内存空间;当n小于等于当前capacity时,reserve什么也不做,因此reserve不会减少容器占用内存,类似的resize也同样如此

五、额外的string操作

1、构造string的其他方法

string s(cp,n)           //s是cp指向的数组前n个字符的拷贝
string s(s2, pos2)       //s是string s2从下标pos2开始的字符的拷贝
string s(s2, pos2, len2) //s是string s2从下标pos2开始,长度为min(len2, s2.size()-pos2)的字符的拷贝
s.substr(pos, n)         //返回一个string,包含s中从pos开始的n个字符的拷贝,pos默认为0,n默认为s.szie()-pos

2、操作string的其他方法,参考《C++ primer 5th》p.323,主要是

insert/erase/assign/append/replace

3、搜索string的其他方法,参考《C++ primer 5th》p.325,主要是

find/rfind
find_first_of/find_last_of
find_first_not_of/find_last_not_of

4、compare函数及其各重载形式,参考《C++ primer 5th》p.327

5、数值转换

to_string(val)   //返回val的string表示
stoi(s, p, b)    //s为需要转换的string,要求必须以数值中可能出现的字符为开头,转换为整数
stol(s, p, b)    //返回值类型如to后的字母表示的类型,例如stoi返回int,stol返回long
stoul(s, p, b)   //b表示基数,默认为10,p是size_t*指针,保存s中第一个非数值字符的下标,默认为0即不保存下标
stoll(s, p, b)
stoull(s, p, b)
stof(s, p)
stod(s, p)
stold(s, p)
六、容器适配器

1、适配器:一种能使某种事物的行为看起来像另一种事物的机制,一个容器适配器接受一种已有的容器类型,使其行为看起来像另一种不同的类型

2、容器适配器的种类及默认接受的已有容器类型

stack           //默认用deque实现,定义在stack头文件中
queue           //默认用deque实现,定义在queue头文件中
priority_queue  //默认用vector实现,定义在queue头文件中

3、每个适配器都定义了两个构造函数
(1)默认构造函数:创建空对象,接受一个容器,拷贝该容器来初始化适配器

deque<int> deq;
stack<int> stk(deq);    //从deq拷贝元素到stk初始化一个新的stack

(2)重载默认容器的构造函数:将一个命名的顺序容器作为第二个类型参数来重载默认容器类型

stack<string, vector<string>> str_stk;   //stack默认用deque实现,这里重载,用vector来实现stack

注意:对于一个给定的适配器,可以使用的容器是有限制的;且必须用适配器提供的操作,不能直接使用容器提供的操作,例如stk.push(x)可以,而不能stk.push_back(x)

4、栈适配器

stack<int> s;          //默认基于deque,也可以用list或vector
s.pop()                //删除栈顶元素,但不返回该删除的元素
s.push(item)           //创建一个新元素压入栈顶,可以从item而来或是由args构造
s.emplace(args)        //根据args构造元素加入s
s.top()                //返回栈顶元素,但不将该元素弹出栈

5、队列适配器

queue<int> q;          //默认基于deque,也可以用list或vector
priority_queue<int> q; //默认基于vector,也可以用deque
q.pop()                //删除queue的首元素或priority_queue的最高优先级元素,但不返回此元素
q.front()              //返回首元素,但不删除该元素,只适用queue
q.back()               //返回尾元素,但不删除该元素,只适用queue
q.top()                //返回最高优先级元素,但不删除该元素,只适用priority_queue
q.push(item)           //创建一个新元素放入queue末尾或插入priority_queue合适的位置
q.emplace(args)        //根据args构造元素加入q

注意:对于priority_queue,默认根据<进行优先级排序,如果需要自定义元素之间的比较,则要重载这个默认排序

priority_queue<T> priqueue;                                    //默认构造优先队列的方式,基于T类型的<操作
priority_queue<T, vector<T>, decltype(comp)*> priqueue(comp);  //在comp函数中自定义排序规则的优先队列
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值