一个容器就是一些特定类型对象的集合。而顺序容器为我们提供了控制元素的存储和访问顺序的能力,这个顺序与元素加入容器的顺序有关。
下面列出了标准库中的顺序容器
string和vector将元素保存在连续的内存空间中,所以 由元素下标来计算其地址的速度非常块,但在容器中间插入或删除元素就会非常耗时。而list和forward_list在任何位置插入或删除元素都很块,但不支持随机访问:访问一个元素需要遍历整个容器,而且这两种容器的额外内存开销也很大。
deque是一种更为复杂的数据结构。支持快速随机访问,在中间位置插入或删除元素的代价可能很高,但在两端插入或删除元素速度很快。
注意,forward_list没有size操作。
那么问题来了,当我们需要容器时选择哪个容器呢?
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
ifstream in("dic.txt");
vector<string> file_text, dics;
list<string> list_dics;
if (in.is_open()) {
string lines;
while (getline(in,lines))
{
//cout << lines << endl;
string str;
for (auto c : lines) {
if (isalpha(c)) {
str += c;
}
else if (str.size() > 0) {
//cout << str << endl;
dics.push_back(str);
list_dics.push_back(str);
str = "";
}
}
}
}
/*
for (auto c : dics) {
if (list_dics.size() == 0)
list_dics.push_back(c);
else {
auto it = list_dics.begin();
for (int i = 0; i < list_dics.size(); i++,it++) {
if (c >=*it) {
list_dics.insert(it, c);
break;
}
}
}
//cout << c << endl;
}*/
list_dics.sort();
for (auto c : list_dics) {
cout << c << endl;
}
}
以上代码是用list按字典顺序存储从文件中读取的单词。
本来我在考虑怎么按字典顺序在list中插入单词,但后面发现list自带排序方法sort,直接调用sort就可以让他自动排序,可以排序是区分大小写的,这样就和字典顺序有区别了。
这里我们将介绍所有容器都使用的操作。通常每个容器都定义在一个头文件中,如deque定义在头文件deque中,list定义在头文件list中。容器均定义为模板类。顺序容器几乎可以保存任意类型的元素。元素本身可以是另一种容器。
下面是通用的容器操作:
迭代器范围:由一对迭代器表示,两个迭代器通常被称为begin和end,或者first和last。标记了容器中元素的范围。第二个迭代器last是指向尾元素之后的位置。这点也可以理解,(比如容器的迭代器c.end()表示的就是指向最后一个元素之后一个单位的位置的指针,这样的好处是方便我们遍历容器)。这种元素范围成为左闭合区间,标准数学描述为
[begin,end)
注意begin和end必须是同一容器的迭代器,且end指向的是begin之后的位置,也可以指向同一个位置。
这时有
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
vector<int> vi;
for (int i = 0; i <= 100; i++)
vi.push_back(i);
int* beg = &(vi[20]), * en = &(vi[60]);
bool tes = test(beg, en, 46);
cout << tes << endl;
}
练习9.5
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
vector<int> vi;
for (int i = 0; i <= 100; i++)
vi.push_back(i);
int* beg = &(vi[20]), * en = &(vi[60]);
int* tes = test(beg, en, 46);
if (tes == nullptr)
cout << "can not find" << endl;
else
cout << *tes << endl;
}
每个容器都定义了多种类型,还有类型别名,类型别名使得我们可以在不了解容器中元素类型的情况下使用它,如元素类型value_type,元素类型的引用reference或const_reference。
分别是指向容器第一个元素和尾元素之后位置的迭代器,我们以及很熟悉了就不用再介绍了。
对于array
array<int,42>;
array<string,10>;
array<int,10>::sizetype i;
array<int,10>ia1;
array<int,10>digits={0,1,2,3,4,5,6,7,8,9};
array<int,10>copy=digits;
我们直接看下面的实例
vector<int> v1={1,3,5,7,9,12};
vector<int> v2={1,3,9};
vector<int> v3={1,3,5,7};
vector<int> v4={1,3,5,7,9,12};
v1<v2//true ->5<9
v1<v3//false ->size
v1==v4//true
v1==v2//false
下面这些操作array不支持,forward_list有自己版本的insert和emplace,forward_list不支持push_back和emplace_back
其中,emplace操作是将参数传递给元素类型的构造器。
c.emplace_back("11111",25,15.99);
c.push_back(Sales_data("11111",25,15.99));
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
deque<string> get;
string str;
while (cin >> str)
get.push_back(str);
for (auto it = get.begin(); it != get.end(); it++)
cout << *it << endl;
}
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
deque<string> get;
list<string>get_list;
string str;
while (cin >> str)
get_list.push_back(str);
for (auto it = get_list.begin(); it != get_list.end(); it++)
cout << *it << endl;
}
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
list<int>ilist = { 1,2,3,4,5,6,7,8,9,10 };
deque<int>d1, d2;
for (auto c : ilist) {
if (c % 2 == 0)
d1.push_back(c);
else
d2.push_back(c);
}
cout << "c1:" << endl;
for (auto c : d1)
cout << c << endl;
cout << "c2:" << endl;
for (auto c : d2)
cout << c << endl;
}
每个顺序容器都有一个front成员函数和back(除了forward_list)成员函数,分别返回首元素和尾元素的引用。另外,*c[n]或c.at(n)返回下表为n的元素的引用。
array不支持,forward_list有特殊版本的erase且不支持pop_back;vector和string不支持pop_front
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
int ia[] = { 0,1,1,2,3,5,8,13,21,55,89 };
vector<int>vi;
list<int>li;
for (auto c : ia) {
li.push_back(c);
vi.push_back(c);
}
auto vb = vi.begin();
auto lb = li.begin();
while (vb != vi.end()) {
if (*vb % 2 == 0)
vb = vi.erase(vb);
else
++vb;
}
while (lb != li.end()) {
if (*lb % 2 == 1)
lb = li.erase(lb);
else
++lb;
}
cout << "vector:" << endl;
for (auto c : vi)
cout << c << endl;
cout << "list:" << endl;
for (auto c : li)
cout << c << endl;
}
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
int ia[] = { 0,1,1,2,3,5,8,13,21,55,89 };
forward_list<int>fi;
for (auto c : ia)
fi.push_front(c);
auto it = fi.before_begin();
auto itt = fi.begin();
while (itt != fi.end()) {
if (*itt % 2 == 1) {
itt = fi.erase_after(it);
}
else {
it = itt;
itt++;
}
}
for (auto c : fi)
cout << c << endl;
}
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
void fun(forward_list<string>& fs, string s1, string s2) {
auto bb = fs.before_begin();
auto b = fs.begin();
while (b != fs.end()) {
if (*b == s1) {
fs.insert_after(bb,1, s2);
//cout << "------" << endl;
return ;
}
else {
bb = b;
b++;
}
}
fs.push_front(s2);
}
int main() {
forward_list<string> ss = {"reo","reoreoreo","reoreoreoreo","reoreoreoreoreoreo"};
fun(ss, "reoreoreo-", "The world");
for (auto c : ss)
cout << c << endl;
}
resize不适用array
向容器添加元素后
删除元素,指向被删元素的迭代器、指针和引用都失效。
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
list<int>li = { 0,1,2,3,4,5,6,7,8,9 };
forward_list<int>fli = { 0,1,2,3,4,5,6,7,8,9 };
auto it = li.begin();
while (it != li.end())
{
if (*it % 2) {
it = li.insert(it, *it);
++it; ++it;
}
else {
it = li.erase(it);
}
}
auto itt = fli.begin();
auto ittt = fli.before_begin();
while (itt !=fli.end())
{
if (*itt % 2) {
itt = fli.insert_after(ittt, *itt);
++itt;
ittt = itt;
++itt;
}
else {
itt = fli.erase_after(ittt);
}
}
for (auto c : li)
cout << c << endl;
cout << "---" << endl;
for (auto c : fli)
cout << c << endl;
}
对于vector和string,我们知道,容器中元素是连续存储的,且容器大小是可变的,试想如果容器中的空间满了,此时再向其中添加新的元素又要保证连续存储,容器只能分配新的更大的空间并把原有的元素全部搬到新的空间中同时释放旧存储空间。如果每次添加新元素都需要重新分配空间,那性能将会很慢的,为此,标准库实现者的策略是当需要分配新的空间时给它分配比所需的空间更大的空间,这样一来,它的性能表现高效了很多,扩张操作通常比list和deque要快。
管理容器的成员函数
shrink_to_fit只适用于vector、string和deque
capacity和reserve只适用于vecor和string
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
vector<int>vi;
for (int i = 0; i < 100; i++) {
vi.push_back(i);
cout << vi.capacity() << endl;
}
vi.shrink_to_fit();
cout << vi.capacity() << endl;
}
const char *cp="Hello world!!!";
char nonull[]={'H','i'};
string s1(cp);
string s2(nonull,2);
string s3(s1,6);
string s4(s1,6,20);
substr操作返回一个string,它是原始string的一部分或全部的拷贝。
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
vector<char>chs = { 'T','h','e',' ','W','o','r','l','d' };
char test[] = { 'T','h','e',' ','W','o','r','l','d' };
string str;
for (auto c : chs)
str += c;
string strs(str);
string s1(test,size(test));
cout << strs << endl;
cout << s1 << endl;
}
s.insert(pos,args):在pos之前插入args指定的字符,pos是下标时返回一个指向s的引用,pos是迭代器时返回指向第一个插入字符的迭代器
s.erase(pos,len):删除从pos开始的len个字符,len省略则删除从pos开始到s末尾的所有字符,返回一个指向s的引用
s.assign(args):替换
s.append(args):追加,返回指向s的引用
s.replace(rangs,args):范围rangs内的字符替换为args
其中args可以是下列形式之一,append和assign可以是以下所有形式,而replace和insert允许的args依赖于range和range
str
str,pos,len
cp,len
cp
n,c
b,e
初始化列表
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
void fun(string& s1, string old, string ne) {
auto it = s1.begin();
int pos = 0;
while (it != s1.end()) {
if (*it == *(old.begin())) {
auto itt = it;
auto oit = old.begin();
int a = 0;
while (oit != old.end()) {
if (*oit == *itt)
a++;
itt++; oit++;
}
if (a == old.size()) {
cout << "ok" << endl;
--it;
s1.erase(pos, old.size());
++it;
it=s1.insert(it, ne.begin(),ne.end());
}
}
++it;
++pos;//放后面
}
}
int main() {
string test = "test the program and thourgh";
string str;
fun(test, "thourgh", "thur");
cout << test << endl;
}
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
void fun(string& s1, string old, string ne) {
auto it = s1.begin();
int pos = 0;
while (it != s1.end()) {
if (*it == *(old.begin())) {
auto itt = it;
auto oit = old.begin();
int a = 0;
while (oit != old.end()) {
if (*oit == *itt)
a++;
itt++; oit++;
}
if (a == old.size()) {
cout << "ok" << endl;
//--it;
//s1.erase(pos, old.size());
//++it;
//it = s1.insert(it, ne.begin(), ne.end());
s1.replace(pos, old.size(), ne);//pos,len,string
}
}
++it;
++pos;//放后面
}
}
int main() {
string test = "test the program and thourgh";
string str;
fun(test, "thourgh", "thur");
cout << test << endl;
}
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
void fun(string& s1, string first, string last) {
s1.insert(s1.begin(), first.begin(),first.end());
s1.append(last);
}
int main() {
string test = "Xie";
string str;
fun(test, "Mr.", "sir");
cout << test << endl;
}
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
void fun(string& s1, string first, string last) {
s1.insert(0,first);
s1.insert(s1.size(), last);
}
int main() {
string test = "Xie";
string str;
fun(test, "Mr.", "sir");
cout << test << endl;
}
返回指定字符出现的下标,未找到则返回npos
其中args为以下形式之一
#include
#include
#include
#include
#include
#include
#include
#include
int main() {
string test = "ab2c3d7R4E6";
string str="0123456789";
int a = test.find_first_of(str);
int b = test.find_first_not_of(str);
for (int i = 0; i < 10; i++) {
cout << test.find_first_of(str[i]) << endl;
}
cout << a << " " << b << endl;
}
s.compare的几种参数形式
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
vector<string> nums = { "213","23423","234234" };
int a = 0;
for (auto c : nums) {
a += stoi(c, 0);
cout << stoi(c, 0)<<endl;
}
cout << a << endl;
}
除了顺序容器外,标准库还定义了三个顺序容器适配器:stack、queue和priority_queue。一个适配器是一种机制,能使某种事物的行为看起来像另一种事物一样。如stack适配器接受一种顺序容器(除array和forward_list),并使其操作起来像stack一样。
下面列出所有适配器都支持的操作和类型
栈适配器stack
stack类型定义在头文件stack中。栈默认基于deque实现,也可以在list和vector之上实现
栈操作
如使用stack的一个简单的例子
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
void fun(string& s1, string old, string ne) {
string str = "abcdefg";
str.replace(2,2, "test");//pos,len
cout << str << endl;
}
int main() {
vector<string>sv = { "test","asdfasdf" };
stack<string, vector<string>>str(sv);
str.push("asdfasd");
str.push("asdf");
while (!str.empty()) {
string a = str.top();
str.pop();
cout << a << endl;
}
}
另外,看到习题9.52,题目意思上可能是让用stack判断一段(字符串形式的)表达式中是否有括号,如果有括号则去掉括号并将括号中的运算结果替换原表达式中括号中的内容,如果题目要求真是如此,那么这道题出得是有问题的作为这一小节后的习题来说,而且这样的难度也大大增加了。我个人猜测(结合这本书的英文原文)这道题考察的应该是:用stack检测一段字符串中是否有括号(存在左括号时有没有与之相匹配的右括号),有的话将括号弹出,最后得到没有括号的表达式。根据这个要求,我的解法如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
string str = "test(test)--(test)--((test))";
stack<char, string>stst(str),stt,ss,sss;
bool has = false;
while (!stst.empty()) {
char a = stst.top();
//cout << a << endl;
if (a == ')' && !has) {
has = true;
}else if(has && a!='('){
stt.push(a);
cout << a << endl;
}
else if (has && a == '(') {
has = false;
while (!stt.empty()) {
sss.push(stt.top());
stt.pop();
}
while (!sss.empty()) {
ss.push(sss.top());
sss.pop();
}
}else if (!has) {
ss.push(a);
}
stst.pop();
}
string s;
while (!ss.empty()) {
char a = ss.top();
s += a;
ss.pop();
}
cout << s << endl;
}
另外,还可以参考一位大佬对这个问题的看法 https://blog.csdn.net/cxm2643199642/article/details/104459535
队列适配器queue
queue和priority_queue定义在queue头文件中。queue默认基于deque实现,也可用于list或vector,priority_queue默认基于vector实现,也可用deque实现