顺序容器操作
向顺序容器添加元素
在一个vector或string的尾部之外的任何位置,或是一个deque的首尾之外的任何位置添加元素,都需要移动元素。而且,向一个vector或string添加元素可能引起整个对象存储空间的重新分配。重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移动到新的空间中
使用push_back
对push_back的调用时在容器尾部创建一个新的元素,将容器的size增大了1。
关键概念:容器元素时拷贝
容器中的元素与提供值得对象之间没有任何关联。随后对容器中元素得任何改变都不会影响到原始对象,反之亦然。
那么问题来了:这样造成的内存浪费应该怎么解决?原始值用new,将他放入容器后delete可以吗?
使用push_front
支持的容器:list, forward_list, deque
功能类似push_back,但是它在容器首部插入元素。
使用insert
支持的容器:vector, list, forward_list, deque, string
slist.insert(iter, "Hi!"); // 将新元素添加到iter之前的位置
slist.insert(iter, 10, "Hi!"); // 将10个新元素添加到iter之前的位置
vector<string> v = {"quasi", "simba", "frollo", "scar"};
// 将v的最后两个元素添加到slist的开始位置
slist.insert(slist.begin(), v.end()-2, v.end());
slist.insert(slist.begin(), {"these", "words", "will"});
// Flase, 迭代器表示要拷贝的范围,不能指向与目的位置相同的容器
slist.insert(slist.begin(), slist.begin(), slist.end());
insert的返回值
list<string> lst;
auto iter = lst.begin();
while(cin >> word)
iter = lst. insert(iter, word); // 等价于调用push_front
insert返回的迭代器恰好指向这个新元素
使用emplace
c++11
引入了三个新成员:emplace_front
,emplace_back
,emplace
,它们分别对应push_front
,push_back
,insert
- push或insert,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中
- emplace,将参数传递给元素类型的构造函数,然后在容器管理的内存空间中直接构造元素
- 在调用emplace_back时,会在容器管理的内存空间中直接创建对象。而调用push_back则会创建一个局部临时对象,将其压入容器中。
访问元素
访问成员函数返回的是引用
if(!c.empty())
{
c.front() = 42; // 将42赋予c中的第一个元素
auto &v = c.back(); // 获得指向最后一个元素的引用
v = 1024; // 改变c中的元素
auto v2 = c.back(); // v2不是一个引用,它是c.back()的一个拷贝
v2 = 0; // 未改变c中的元素
}
下标操作和安全的随机访问
下标运算符接受一个下标参数,返回容器中该位置的元素的引用。
注:下标运算符并不检查下标是否在合法范围内。
解决方法:
如果我们希望确保下标是合法的,可以使用at
成员函数
vector<int> ivec;
cout<<ivec[0]; // 运行时错误,ivec中没有元素!
cout<<ivec.at(0); // 抛出一个out_of_range异常
删除元素
使用pop_front和pop_back
与push_front和push_back的规则类似。但是要注意,pop不能对一个空容器执行弹出操作。
在容器内部删除一个元素
list<int> lst = {0, 1, 2, 3, 4};
auto it = lst.begin();
it = lst.erase(it); // 删除元素,并且返回所删除元素之后的元素(迭代器)
删除多个元素
// 删除两个迭代器表示的范围内的元素
//返回指向最后一个被删元素之后位置的迭代器
it3 = lst.erase(it1, it2); // 调用后, it3 == it2
lst.clear(); 删除所有元素
lst.erase(lst.begin(), lst.end()); // 等价调用
特殊的forward_list操作
单项列表的特点:当添加或删除一个元素时,删除或添加元素之前的那个元素的后继会发生改变。例如,为了删除
e
l
e
m
3
elem_3
elem3,应该用指向
e
l
e
m
2
elem_2
elem2的迭代器调用erase_after。
例子:删除奇数元素
forware_list<int> flst = {0, 1, 2, 3, 4, 5, 6, 7};
auto prev = flst.before_begin(); // 表示flst的“首前元素”
auto curr = flst.begin(); // 表示flst的第一个元素
while(curr != flst.end())
{
if(*curr % 2)
curr = flst.erase_after(prev); // 删除它并且移动curr
else
{
prev = curr; // 移动迭代器curr, 指向下一个元素,prev指向curr之前的元素
++curr;
}
}
改在这里插入代码片
变容器大小
list<int> ilist(10, 42); // 10个int, 每个值都是42
ilist.resize(15); // 将5个值为0的元素添加到ilist的末尾
ilist.resize(25, -1); // 将10个值为-1的元素添加到ilist的末尾
ilist.resize(5); // 从ilist末尾删除20个元素
如果容器保存的是自定义类型元素,且resize向容器添加新元素,则我们必须提供初始值,或者元素类型必须提供一个默认构造函数。
容器操作可能使迭代器失效
向容器中添加元素和从容器中删除元素的操作(一般是从中间删除或添加,除了list和forward_list)可能会使指向容器元素的指针、引用或迭代器失效。
建议:管理迭代器
由于向迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,因此必须保证每次改变容器的操作之后都正确地重新定位迭代器。这个建议对vector、string和deque尤为重要。
额外的string操作
构造string的其他方法
substr操作
substr操作返回一个string,它是原始string的一部分或全部的拷贝。
string s("hello world");
string s2 = s.substr(0, 5); // s2 = hello
string s3 = s.substr(6); // s3 = world
string s4 = s.substr(6, 11); // s4 = world
string s5 = s.substr(12); // 抛出一个out_of_range异常
改变string的其他方法
string搜索操作
compare函数
数值转换
容器适配器
除了顺序容器外,标准库还定义了三个顺序容器适配器:stack
, queue
, priority_queue
一个容器适配器接受一种已有得容器类型,使其行为看起来像一种不同的类型。例如,stack
适配器接受一个顺序容器(如vector或deque),并使其操作起来像一个stack一样。
定义一个适配器
默认情况下,stack和queue是基于deque实现的,priority_queue是在vector之上实现的。
stack<int> stk(deq); // 从deq拷贝元素到stk
// 在vector上实现的空栈
stack<string, vector<string>> str_stk;
// str_stk2在vector上实现,初始化时保存svec的拷贝
stack<string, vector<string>> str_stk2(svec);
stack的使用
queue的使用
priority_queue理解与使用
优先队列是一种比较重要的数据结构,它是有二项队列编写而成的,可以以O(log n)
的效率查找一个队列中的最大值或者最小值,其中是最大值还是最小值是根据创建的优先队列的性质来决定的。
作为队列的一个延伸,优先队列包含在头文件<queue>
中,优先队列有三个参数,其声明形式为:
priority_queue< type, container, function >
这三个参数,后面两个可以省略,第一个不可以。其中:
type:数据类型;
container:实现优先队列的底层容器;
function:元素之间的比较方式;
对于container,要求必须是数组形式实现的容器,例如vector
、deque
,而不能使list
。
在STL中,默认情况下(不加后面两个参数)是以vector
为容器,以 operator<
为比较方式,所以在只使用第一个参数时,优先队列默认是一个最大堆,每次输出的堆顶元素是此时堆中的最大元素。
成员函数:
假设type类型为int,则:
- bool empty() const
返回值为true,说明队列为空; - int size() const
返回优先队列中元素的数量; - void pop()
删除队列顶部的元素,也即根节点 - int top()
返回队列中的顶部元素,但不删除该元素; - void push(int arg)
将元素arg插入到队列之中;
- 大顶堆
//构造一个空的优先队列(此优先队列默认为大顶堆)
priority_queue<int> big_heap;
//另一种构建大顶堆的方法
priority_queue<int,vector<int>,less<int> > big_heap2;
- 小顶堆
//构造一个空的优先队列,此优先队列是一个小顶堆
priority_queue<int,vector<int>,greater<int> > small_heap;
需要注意的是,如果使用less和greater,需要头文件:
#include <functional>
- 基本类型优先队列的例子
#include<iostream>
#include <queue>
using namespace std;
int main()
{
//对于基础类型 默认是大顶堆
priority_queue<int> a;
//等同于 priority_queue<int, vector<int>, less<int> > a;
// 这里一定要有空格,不然成了右移运算符↓↓
priority_queue<int, vector<int>, greater<int> > c; //这样就是小顶堆
priority_queue<string> b;
for (int i = 0; i < 5; i++)
{
a.push(i);
c.push(i);
}
while (!a.empty())
{
cout << a.top() << ' ';
a.pop();
}
cout << endl;
while (!c.empty())
{
cout << c.top() << ' ';
c.pop();
}
cout << endl;
b.push("abc");
b.push("abcd");
b.push("cbd");
while (!b.empty())
{
cout << b.top() << ' ';
b.pop();
}
cout << endl;
return 0;
}
输出:
4 3 2 1 0
0 1 2 3 4
cbd abcd abc
请按任意键继续. . .
- 用pair做优先队列元素的例子:
规则:pair的比较,先比较第一个元素,第一个相等比较第二个。
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main()
{
priority_queue<pair<int, int> > a;
pair<int, int> b(1, 2);
pair<int, int> c(1, 3);
pair<int, int> d(2, 5);
a.push(d);
a.push(c);
a.push(b);
while (!a.empty())
{
cout << a.top().first << ' ' << a.top().second << '\n';
a.pop();
}
}
输出:
2 5
1 3
1 2
请按任意键继续. . .
- 用自定义类型做优先队列元素的例子
#include <iostream>
#include <queue>
using namespace std;
//方法1
struct tmp1 //运算符重载<
{
int x;
tmp1(int a) {x = a;}
bool operator<(const tmp1& a) const
{
return x < a.x; //大顶堆
}
};
//方法2
struct tmp2 //重写仿函数
{
bool operator() (tmp1 a, tmp1 b)
{
return a.x < b.x; //大顶堆
}
};
int main()
{
tmp1 a(1);
tmp1 b(2);
tmp1 c(3);
priority_queue<tmp1> d;
d.push(b);
d.push(c);
d.push(a);
while (!d.empty())
{
cout << d.top().x << '\n';
d.pop();
}
cout << endl;
priority_queue<tmp1, vector<tmp1>, tmp2> f;
f.push(b);
f.push(c);
f.push(a);
while (!f.empty())
{
cout << f.top().x << '\n';
f.pop();
}
}
输出:
3
2
1
3
2
1
请按任意键继续. . .