1. 顺序容器概述
- 顺序容器类型:
vector //可变大小数组,支持快速随机访问,在尾部之外的位置插入或删除元素很慢
deque //双端队列,支持快速随机访问,在头尾位置插入或删除元素很快
list //双向链表,支持双向顺序访问,在list任何位置插入或删除元素都很快
forward_list //单向链表,只支持单向顺序访问,在任何位置插入或删除元素都很快
array //固定大小数组,支持快速随机访问,不能添加或删除元素,是一种更安全易用的数组类型
string //与vector类似
//list与forward_list不支持元素的随机访问
- 新标准的容器比旧版本快得多,现代c++ 应当使用标准库容器,而不是更原始的数据结构,如内置数组。
- 确定使用哪种容器类型:一般来说使用vector(其他情况看程序具体要求)。根据占主导地位的操作(执行访问操作多还是插入删除操作多)决定容器类型选择
- 随机访问元素:vector、deque;
- 中间插入元素或删除元素:list、forward_list;
- 头尾插入元素:deque;
2. 容器库概览
- 每个容器都定义在一个头文件中,文件名与类型名相同;
- 容器均定义为模板类;
迭代器
- 迭代器范围
- [begin,end),左闭合区间
容器类型成员
- iterator、const_iterator、size_type、difference_type等等
- begin和end成员
- begin和end有多个版本,带r的返回反向迭代器;带c的返回const迭代器
list<string> a = {"M", "S", "A"};
auto it1 = a.rbegin(); //list<string>::reverse_iterator
auto it2 = a.cbegin(); //list<string>::const_iterator
auto it3 = a.crbegin(); //list<string>::const_reverse_iterator
- auto与begin和end配合使用
auto it = a.begin(); //仅当a是const时,it是const_iterator
容器定义和初始化
- 将一个容器初始化未另一个容器的拷贝
- 直接拷贝整个容器
- 要求容器类型与元素类型必须都相同
- 拷贝由一个迭代器对指定的元素范围
- 不要求容器类型相同,而且元素类型也可以不同,只要能够转换为要初始化的容器的元素类型
- 直接拷贝整个容器
- 当定义一个array时,除了指定元素类型,还要指定容器大小
vector<const char*> articles = {"A","S","K"};
forward_list<string> words(articles.begin(), articles.end());//正确:可以将const char*元素转换为string
- 列表初始化
- 新标准中,我们可以对一个容器进行列表初始化,同时也隐式指定了容器大小(array除外)。
vector<const char*> articles = {"A","S","K"};
list<string> authors = {"zhangsan", "lisi", "wangwu"};
- 如果元素是内置类型或者具有默认构造函数的类类型,可以只为顺序容器提供一个容器大小的参数 ;否则还必须指定一个显式的元素初始值。
- 标准库array具有固定大小
- 使用array必须同时指定元素类型和大小。
- array类似于数组,但支持拷贝和对象赋值
int digs[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int cpy[10] = digs; //错误,内置数组不支持拷贝
array<int, 10> digts = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
array<int, 10> cpy1= digts; //正确
赋值和swap
//赋值运算
swap(c1, c2); //交换c1和c2中的元素,c1和c2的类型必须相同
c1.swap(c2);
//assign操作不适用于关联容器和array
seq.assign(b, e); //将seq中的元素替换为迭代器b和e所表示范围中的元素
seq.assign(il); //将seq中的元素替换为初始化列表il中的元素
seq.assign(n,t); //将seq中的元素替换为n个值为t的元素
//赋值相关运算会导致指向容器内部的迭代器、引用和指针失效;swap不会(容器类型为array和string的除外)
- 使用assign(仅顺序容器)
list<string> names;
vector<const char*> oldstyle;
names = style; //错误,容器类型不匹配
names.assign(oldstyle.begin(), oldstyle.end()); //正确。const char*可以转化为string
- 使用swap
- 对于除array外的所有容器使用swap,都不会使两个容器的元素交换,仅仅交换两个容器的内部数据结构,除string外,指向容器的迭代器、指针、引用在swap操作后仍然指向原来的那些元素,只不过不属于原来的容器了。
- 对于array使用swap,会真正交换它们的元素,但指针、引用和迭代器所绑定的元素不变,但元素值已经进行了交换。
- 新标准中容器既提供了成员函数版本的swap也提供了非成员版本的swap。统一使用非成员版本是个好习惯。
vector<string> svec1(10);
vector<string> svec2(24);
swap(svec1, svec2); //交换后svec1(24),svec2(10)
- 容器大小操作
- 只有当其元素类型定义了相应的比较运算符时,我们才可以使用关系运算符来比较两个容器。
3. 顺序容器操作
向顺序容器添加元素:
/*forward_list有自己专有版本的insert和emplace
forward_list不支持push_back和emplace_back
vector和string不支持push_front和emplace_front
*/
c.push_back(t) //在c的尾部创建一个值为t或由args创建的元素
c.emplace_back(args)
c.push_front(t) //在c的头部创建一个值为t或由args创建的元素
c.emplace_front(args)
c.insert(p,t) //在迭代器p指向的元素之前创建一个值为t或由args创建的元素
c.emplace(p,args)
c.insert(p,n,t) //在迭代器p指向的元素之前插入n个值为t的元素
c.insert(p,b,e) //在迭代器p指向的元素之前插入迭代器b和e指定的范围内的元素
c.insert(p,il) //在迭代器p指向的元素之前元素值列表il中的值
//向一个vector、string或deque插入元素会使所有指向容器的迭代器、引用和指针失效
//注意:当向容器中插入元素时,注意迭代器可能失效,所有需要重新指定迭代器
//1.当我们调用push或insert成员函数时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中
//2.而当我们调用emplace时,则是将参数传递给元素类型的构造函数
- list、forward_list和deque支持push_front操作;
- 有些容器不支持push_front操作,但可以通过insert实现
string word("Hello"); //string为字符容器
word.push_back('!');
vector<string> svec;
list<string> slist;
slist.insert(slist.begin(), "Hello!");
svec.insert(svec.begin(), "Hello!");
list<string> lst;
auto iter = lst.begin();
while(cin>>word)
iter = lst.insert(iter, word); //insert返回值为新插入元素的迭代器
//使用emplace
c.emplace_back("432-432432432", 25, 15.99); //emplace_back使用这些成员在容器管理的内存空间中直接构造元素
访问元素:
//at和下标操作只适用于string、vector、deque和array
//back不适用于forward_list
//back、front、at、[]均返回引用
c.back() <==> *(--c.end()) //返回c中尾元素的引用,若c空,则行为未定义
c.front() <==>*c.begin() //返回c中首元素的引用,若c空,则行为未定义
c[n] //返回c中下标为n的元素的引用,若n>c.size(),则行为未定义
c.at(n) //返回下标为n的元素的引用,若越界,则跑出out_of_range异常
if (!c.empty())
{
c.front() = 42;
auto &v = c.back(); //获得指向最后一个元素的引用
v = 1024; //改变c中的元素
auto v2 = c.back(); //v2不是一个引用,它是c中最后一个元素的拷贝
v2 = 0; //c中元素不改变
}
删除元素:
/*删除元素改变容器大小,不适用于array
forward_list有特殊版本的erase
forward_list不支持pop_back;vector和string不支持pop_front
*/
c.pop_back() //删除c中尾元素
c.pop_front() //删除c中首元素
c.erase(p) //删除迭代器p所指向的元素,返回一个指向被删元素之后元素的迭代器
c.erase(b,e) //删除迭代器b和e所指定范围内的元素,返回一个指向最后一个被删元素之后元素的迭代器
c.clear() //删除c中所有元素
//1.删除deque中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效
//2.指向vector或string中删除点之后位置的迭代器、引用和指针都会失效
特殊的forward_list操作:
//在forward_list中插入或删除元素的操作
lst.before_begin() //返回指向链表首元素之前不存在的元素的迭代器
lst.cbefore_begin() //.......cbefore_begin()返回一个const_iterator
lst.insert_after(p,t) //在迭代器p之后的位置插入元素,t是一个对象,n是数量,b和e是
lst.insert_after(p,n,t) //表示范围的一对迭代器,il是一个元素值列表
lst.insert_after(p,b,e)
lst.insert_after(p,il)
emplace_after(p,args) //使用args在p指定的位置之后创建一个元素,返回一个指向这个新元素的迭代器
lst.erase_after(p) //删除p指向的位置之后的元素,或删除从b之后到e之间的元素
lst.erase_after(b,e) //返回一个指向被删元素之后元素的迭代器
改变容器大小
//resize不适用于array
c.resize(n) //调整c的大小为n个元素,若n<c.size(),则多出的元素被丢弃;否则新元素被初始化
c.resize(n,t) //调整c的大小为n,任何新添加的元素被初始化为t
//1.如果resize缩小容器,则指向被删除元素的迭代器、引用和指针都会失效
//2.对vector、string或deque进行resize可能导致迭代器、引用和指针失效
容器操作可能使迭代器失效
添加元素 | 删除元素 | |
vector | 若空间未重新分配,则指向插入元素之前的迭代器、指针、引用都将有效,之后的失效;若空间重新分配,则所有的都会失效 | 同前 |
string | 若空间未重新分配,则指向插入元素之前的迭代器、指针、引用都将有效,之后的失效;若空间重新分配,则所有的都会失效 | 同前 |
list | 都有效(包括尾后迭代器和首前迭代器) | 除了删除元素的失效,其他的都有效 |
forward_list | 都有效(包括尾后迭代器和首前迭代器) | 除了删除元素的失效,其他的都有效 |
deque | 如果插入到首尾之外的其他位置,则都会失效;如果插入到首尾,则除首尾外都不会失效 | 同前 |
- 建议:管理迭代器
- 向迭代器添加元素和删除元素都可能导致迭代器失效,因此必须每次操作之后维护迭代器,尤其是vector、string、deque。
//删除偶数元素,复制每个奇数元素
vector<int> vi = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto iter = vi.begin();
while(iter!=vi.end())
{
if(*iter%2)
{
iter = vi.insert(iter, *iter); //在给定位置之前插入元素
iter+=2;
}
else
iter = vi.erase(iter); //删除元素后返回下一位置
}
4. vector对象是如何增长的
vector和string管理容量的成员函数
//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的大小。
- shrink_to_fit()将capacity调整至size大小,退回不必要的内存空间。
- 实际上,只要没有操作需求超出vector的容量,vector就不会重新分配内存空间。
5.额外的string操作:
- 构造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开始len2个字符的拷贝
//substr操作
s.substr(pos,n) //返回一个string,包含s中从pos开始的n个字符的拷贝;pos默认值为0
- 改变string:
s.insert(pos,args) //在pos之前插入args指定的字符;接受下标的版本返回一个指向s的引用
//接受迭代器版本返回一个指向第一个插入字符的迭代器
s.erase(pos,len) //删除从位置pos开始的len个字符,返回一个指向s的引用
s.assign(args) //将s中的字符替换为args指定的字符,返回一个指向s的引用
s.append(args) //将args追加到s,返回一个指向s的引用
s.replace(range,args) //删除s中范围range内的字符,替换为args指定的字符
- string搜索操作:
//搜索操作返回制定字符出现的下标,若未找到则返回npos
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中的字符
- 数值转换:
to_string(val) //返回数值val的string表示,val可以是任何算术类型
stoi(s,p,b) //返回值类型分别是int、long、unsigned long、long long、unsigned long long, b表示转换所用的基数,
默认为10,p是size_t指针,用来保存s中第一个非数值字符的下标,默认为0
stol(s,p,b)
stoul(s,p,b)
stoll(s,p,b)
stoull(s,p,b)
stof(s,p) //返回值类型分别是float、double、long double
stod(s,p)
stold(s,p)
int i = 42;
string s = to_string(i); //整数i转化为string
double d = stod(s); //s转换为浮点数
6.容器适配器(stack、queue、priority_queue):
适配器:一个适配器是一种机制,能使某种事物的行为看起来向另外一种事物一样
- 栈适配器:
s.pop() //删除栈顶元素,但不返回该元素值
s.push(item) //创建一个新元素压入栈顶,该元素通过拷贝或移动item而来,或由args构造
s.emplace(args)
s.top() //返回栈顶元素,但不将元素弹出栈
- 队列适配器:
//queue默认基于deque实现,priority_queue默认基于vector实现
q.pop() //删除queue中首元素或priority_queue中最高优先级元素
q.front() //返回首元素或尾元素,其中back只适用于queue
q.back()
q.top() //返回最高优先级元素,只适用于priority_queue
q.push(item) //在queue末尾或priority_queue中恰当位置创建一个元素
q.emplace(args)