顺序容器
- 为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应。
顺序容器类型
vector
deque
list
forward_list
array
string
- 除了固定大小的array外,其他容器都提供高效、灵活的内存管理。
- forward_list和array是新C++标准增加的类型。
- string vector将元素保存在连续的内存空间中。添加元素有时可能需要分配额外的存储空间。
- list forward_list 额外内存开销很大
- deque 支持快速的随机访问,中间添加或删除代价很高。两端速度与list,forward_list相当。
- 只有输入时需要在容器中间位置插入元素,随后随机访问:确定是否需要再容器中间位置添加元素,vector追加数据,再sort重排,再追加。
或考虑输入阶段使用list,输入完成将list内容拷贝到一个vector中。
- 不确定使用哪种容器,可以只是用vector,list,使用迭代器,不适用下标操作,避免随机访问。
list<Sales_data>
deque<double>
vector<vector<string>> lines;
容器操作
iterator
const_iterator
size_type
difference_type
value_type
reference
const_reference
list<string>::iterator iter;
vector<int>::difference_type count;
//构造函数
C c; //默认构造函数,构造空容器
C c1(c2);或C c1 = c2; //构造c2的拷贝c1
C c(b, e) //构造c,将迭代器b和e指定范围内的所有元素拷贝到c
C c(a, b, c...) //列表初始化c
C c(n) //只支持顺序容器,且不包括array,包含n个元素,这些元素进行了值初始化
C c(n, t) //包含n个初始值为t的元素
list<string> authors = {"milton","shake","austem"};
vector<const char*> articles = {"a","an","the"};
list<string> list2(authors);
deque<string> authList(authors);//错:容器类型不匹配
vector<string> words(articles);//容器类型不匹配
//可将const char*元素转换为string
forward_list<string> words(articles.begin(),articles.end());
vector<int> ivec(10,-1);
list<string> svec(10,"hi");
forward_list<int> ivec(10);
deque<string> svec(10);
//定义array时要指定元素类型和大小
array<int,42>
array<string,10>
array<int,10>::size_type i;
array<int,10> ia1;//10个默认初始化的int
array<int,10> ia2 = {0,1,2,3,4,5,6,7,8,9};
array<int,10> ia3 = {42}; //ia3[0]为42,剩余元素为0
//array对内置数组类型拷贝或对象赋值不限制
int digs[10] = {0,1,2,3,4,5,6,7,8,9};
int cpy[10] = digs;//错误:内置数组不支持拷贝或赋值
array<int,10> digits = {0,1,2,3,4,5,6,7,8,9};
array<int,10> copy = digits;//正确
- 只有顺序容器的构造函数才接受大小参数,关联容器并不支持。
- array具有固定大小。
- 和其他容器不同,默认构造的array是非空的。
- 直接复制:将一个容器复制给另一个容器时,类型必须匹配:容器类型和元素类型都必须相同。
- 使用迭代器复制:不要求容器类型相同,容器内的元素类型也可以不同。
c1 = c2;
c1 = {a, b, c...}
c1.swap(c2)
swap(c1, c2)
c.assign(b, e)
c.assign(il)
c.assign(n, r)
array<int,10> a1 = {0,1,2,3,4,5,6,7,8,9};
array<int,10> a2 = {0};
a1 = a2;
a2 = {0};
list<string> names;
vector<const char*> oldstyle;
names = oldstyle;
names.assign(oldstyle.cbegin(),oldstyle.cend());
list<string> slist1(1);
slist1.assign(10,"hiya");
vector<string> svec1(10);
vector<string> svec2(55);
swap(svec1,svec2);
c.size()
c.max_size()
c.empty()
c.push_back(t)
c.emplace_back(args)
c.push_front(t)
c.emplace_front(args)
c.insert(p, t)
c.emplace(p, args)
c.inset(p, n, t)
c.insert(p, b, e)
c.insert(p, il)
string word;
while(cin>>word)
container.push_back(word);
void pluralize(size_t cnt,string& word){
if(cnt>1)
word.push_back('s');
}
list<int> ilist;
for(size_t ix=0;ix!=4;++ix)
ilist.push_front(ix);
ilist.insert(iter,"hllo");
vector<string> svec;
list<string> slist;
slist.insert(slist.begin(),"hello");
svec.insert(svec.begin(),"hello");
svec.insert(svec.end(),19,"anna");
vector<stirng> v = {"","",""};
slist.insert(slist.begin(),v.end()-2,v.end());
slist.insert(slist.end(),{"","",""});
list<string> lst;
auto iter = lst.begin();
while(cin>>word)
iter = lst.insert(iter,word);
c.emplace_back("",23,4.4);
c.push_back("",33,44);
c.push_back(Sales_data("",33,3));
c.emplace_back();
c.emplace(iter,"555");
c.empace_front("33",4,3);
c.begin(), c.end()
c.cbegin(), c.cend()
list<string> a = {"","",""};
auto it1 = a.begin();
auto it2 = a.rbegin();
auto it3 = a.cbegin();
auto it4 = a.crbegin();
list<string>::iterator it5 = a.begin();
list<string>::const_iterator it6 = a.begin();
auto it7 = a.begin();
auto it8 = a.cbegin();
reverse_iterator
const_reverse_iterator
c.rbegin(), c.rend()
c.crbegin(), c.crend()
访问元素
- 包括array在内每个顺序容器都有一个front成员函数,除forward_list外所有顺序容器都有一个back成员函数。这两个操作分别返回首元素和尾元素的引用。
c.back()
c.front()
c[n]
c.at(n)
if(!c.empty()){
auto val = *c.begin(),val2 = c.front();
auto last = c.end()
auto val3 = *(--last);
auto val4 = c.back();
}
- 访问成员函数返回的是引用(在容器中访问元素的成员函数,即front,back,at和下标,返回的都是引用)。若容器是const对象,返回值则为const引用。
if(!c.empty()){
c.front() = 42;
auto &v = c.back();
v = 1024;
auto v2 = c.back();
v2 = 0;
}
vector<string> svec;
cout<<svec[0];
cout<<svec.at(0);
删除元素
c.pop_back()
c.pop_front()
c.erase(p)
c.erase(b, e)
c.clear()
- forward_list有特殊版本的erase
- forward_list不支持pop_back
- vector和string不支持pop_front
list<int> lst = {0,1,2,3,4,5,6,7,8,9};
auto it = lst.begin();
while(it!=lst.end())
if(*it % 2)
it = lst.erase(it);
else:
++it;
slist.clear();
slist.erase(slist.begin(),slist.end());
特殊的forward_list操作
- 为了添加或删除一个元素,需要访问其前驱,以便改变前驱的链接,单向链表中没有简单的方法来获取一个元素的前驱,所以,要通过改变给定元素之后的元素来完成。
- 删除elem3应用指向elem2的迭代器调用erase_after.
- forward_list定义了before_begin,它返回一个首前迭代器,这个迭代器允许我们在链表首元素之前添加或删除元素。
lst.before_begin()
lst.cbefore_begin()
lst.insert_after(p, t)
lst.insert_after(p, n, t)
lst.insert_after(p, b, e)
lst.insert_after(p, il)
emplace_after(p, args)
lst.erase_after(p)
lst.erase_after(b, e)
- 当在forward_list中添加或删除元素时,必须关注两个迭代器:一个指向我们要处理的元素,另一个指向其前驱。
forward_list<int> flst = {0,1,2,3,4,5,6,7,8,9};
auto prev = flst.before_begin();
auto curr = flst.begin();
while(curr != flst.end())
if(*curr % 2)
curr = flst.erase_after(prev);
else
prev = curr;
++curr;
改变容器的大小
c.resize(n)
c.resize(n, t)
list<int> ilist(10,42);
ilist.resize(15);多5个0
ilist.resize(25,-1);
ilist.resize(5);
容器操作可能使迭代器失效
- 在向容器添加元素后:
- 如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针、引用都会失效。若存储空间未重新分配,指向插入位置前有效,后面无效。
- 对于deque,插入到除首尾位置之外的任何位置都会导致指向容器的迭代器、指针、引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在元素的引用和指针不会失效。
- 对于list和forward_list,指向容器的迭代器、指针和引用依然有效。
- 在从一个容器中删除元素后:
- 对于list和forward_list,指向容器其他位置的迭代器、引用和指针仍然有效。
- 对于deque,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、指针、引用都会失效;如果是删除deque的尾元素,则尾后迭代器会失效,但其他不受影响;如果删除的是deque的头元素,这些也不会受影响。
- 对于vector和string,指向被删元素之前的迭代器、引用、指针仍然有效。
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);
}
}
auto begin = v.begin(),end = v.end();
while(begin!=end){
++begin;
begin = v.insert(begin,42);
++begin;
}
while(begin!=v.end()){
++begin;
auto begin = v.insert(begin,42);
++begin;
}
vector对象是如何增长的
- vector和string在内存中是连续保存的,如果原先分配的内存位置已经使用完,则需要重新分配新空间,将已有元素从就位置移动到新空间中,然后添加新元素。再释放旧空间。很慢,所以通常会分配比新的空间需求更大的内存空间。容器预留这些控件作为备份,可用来保存更多的新元素。使用此策略后,其扩张操作通常比list和deque还要快。
c.shrink_to_fit()
c.capacity()
c.reverse(n)
capacity和size
vector<int> ivec;
cout<<ivec.size()<<ivec.capacity()<<endl;
for(vector<int>::size_type ix = 0;ix!=24;++ix){
ivec.push_back(ix);
}
cout<<ivec.size()<<ivec.capacity()<<endl;
ivec.reverse(50);
cout<<ivec.size()<<ivec.capacity()<<endl;
while(ivec.size() != ivec.capacity()){
ivec.push_back(0);
}
cout<<ivec.size()<<ivec.capacity()<<endl;
ivec.push_back(42);
cout<<ivec.size()<<ivec.capacity()<<endl;
ivec.shrink_to_fit();
cout<<ivec.size()<<ivec.capacity()<<endl;
额外的string操作
构造string的其他方法
string s(cp, n)
string s(s2, pos2)
string s(s2, pos2, len2)
const char* cp = "hello world!!!";
char noNull[] = {'h','i'};
string s1(cp);
string s2(noNull,2);
string s3(noNUll);
string s4(cp+6,5);
string s5(s1,6,5);
string s6(s1,6);
string s7(s1,6,20);
string s8(s1,16);
substr操作
s.substr(pos, n)
改变string的其他方法
s.insert(pos, args)
s.erase(pos, len)
s.assign(args)
s.append(args)
s.replace(range, args)
string搜索操作
- 每个搜索操作都返回一个string::size_type值,表示匹配发生位置的下标。如果搜索失败则返回一个名为string::npos的static成员(类型是string::size_type,初始化值是-1,也就是string最大的可能大小)。
s.find(args)
s.rfind(args)
s.find_first_of(args)
s.find_last_of(args)
s.find_first_not_of(args)
s.find_first_not_of(args)
c, pos
s2, pos
cp, pos
cp, pos, n
string::size_type pos = 0;
while((pos = name.find_first_of(numbers,pos)) != string::npos){
cout<<pos<<name[pos]<<endl;
++pos;
}
compare函数
- 逻辑类似于C标准库的strcmp函数,根据s是等于、大于还是小于参数指定的字符串,s.compare返回0、正数或负数。
s2
pos1, n1, s2
pos1, n1, s2, pos2, n2
cp
pos1, n1, cp
pos1, n1, cp, n2
数值转换
to_string(val)
stoi(s, p, b)
stol(s, p, b)
stoul(s, p, b)
stoll(s, p, b)
stoull(s, p, b)
stof(s, p)
stod(s, p)
stold(s, p)
容器适配器
- 三个顺序容器适配器:stack,queue,priority_queue
- 适配器是使一事物的行为类似于另一事物的行为的一种机制,例如stack可以使任何一种顺序容器以栈的方式工作。
适配器的通用操作和类型
size_type //一种类型,须以保存当前类型的最大对象的大小
value_type //元素类型
container_type //实现适配器的底层容器类型
A a; //创建一个名为a的空适配器
A a(c) //创建一个名为a的适配器,带有容器c的一个拷贝
关系运算符 //每个适配器都支持所有关系运算符:==、!=、<、 <=、>、>=这些运算符返回底层容器的比较结果
a.empty() //若a包含任何元素,返回false;否则返回true
a.size() //返回a中的元素数目
swap(a, b) //交换a和b的内容,a和b必须有相同类型,包括底层容器类型也必须相同
a.swap(b) //同上
定义一个适配器
- 每个适配器都定义两个构造函数:默认构造函数创建一个空对象,接受一个容器的构造函数拷贝该容器来初始化适配器。
deque<int> deq;
stack<int> stk(deq);
- 默认情况下,stack和queue是基于deque实现的,priority_queue是在vector之上实现的。
stack<string,vector<string>> str_stk;
stack<string,vector<string>> str_stk2(svec);
- 对于一个给定的适配器,可以使用哪些容器是有限制的。所有适配器都要求具有添加和删除元素的能力。所以适配器不能构造在array之上。也不能用forward_list来构造适配器。
- stack要求push_back,pop_back,back操作,因此可以使用除array,forward_list之外的任何容器类型来构造.
- queue适配器要求back,push_back,front,push_front,可构造于list和deque上,不能基于vector.
- priority_queue要求front,push_back,pop_back和随机访问能力,可以构造于vector或deque上,不能基于list构造。
栈适配器 stack类型定义在stack头文件中
stack<int> intStack;
for(size_t ix = 0;ix != 10;++ix){
intStack.push(ix);
}
while(!intStack.empty()){
int value = intStack.top();
intStack.pop();
}
s.pop()
s.push(item)
s.emplace(args)
s.top()
队列适配器
- queue和priority_queue适配器定义在queue头文件中。
q.pop()
q.front()
q.back()
q.top()
q.push(item)
q.emplace(args)
- 标准queue使用一种先进先出的存储和访问策略。
- priority_queue允许我们为队列中的元素建立优先级。新加入的元素会排在所有优先级比它低的已有元素之前。标准库在元素类型上使用<运算符来确定相对优先级。