顺序容器:为程序员提供了控制元素存储和访问顺序的能力。(无序容器)
1.顺序容器的概述
A.顺序容器的类型
vector:可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。
deque:双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。
list:双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快。
forword_list:单向链表。只支持单向顺序访问。在链表的任何位置进行插入/删除操作速度都很快。(无size())
array:固定大小数组。支持快速随机访问。不能添加和删除元素。
string:与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度快。
根据以上顺序容器的类型介绍,从而习题9.1答案就很简单了:list(字母序是任何位置插入)、deque(头部尾部插入/删除)、vector(可变容器)。
2.容器库
A.迭代器
迭代器中beigin是指容器首元素,end是指容器的最后一个元素的后一个位置;
B.容器的拷贝
容器的拷贝有两种操作方法:一是直接拷贝整个容器,二是拷贝一个迭代器对指定的元素范围。前者需要两个容器的类型和元素类型必须匹配;后者不要求容器类型相同元素类型也可不同,但需要元素转换。
例子:
list<string> authors ={"Milton","Shakespeare","Austen"};
vector<const char*> atricles={"a","an","the"};
list<string> list2(authors);
//正确,类型与元素都匹配;
forward_list<string> words(articles.begin(), articles.end());// 正确, 元素转换
C.赋值和swap
赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效。而swap操作容器的内容交换不会导致指向容器的迭代器、引用和指针失效并且高效(array、string类型的除外)。
D.容器大小比较
容器大小的比较与前面章节讲的字符串string的比较一样,首先是字符大小比较(出现第一个不同字符大的容器大,都相同则容器大小相同),然后是容器长度(元素前部分都相同,即一个容器的元素都在另一个容器的前半部分,元素多容器大)。
3.顺序容器的操作
顺序容器与关联容器的不同之处在于两者组织元素的方式。例如:元素的存储、访问、添加和删除。
A.容器元素添加的操作
push_back:将元素添加到容器的尾部(array和forward_list除外)
push_front:将元素添加到容器的头部(list、forward_list 和deque容器支持)
insert:将元素添加到容器的特定位置(insert(a,b)将b元素插入到a元素指定位置的前面)
插入单个元素例子:
vector<string> svec;
list<string> slist;
slist.insert(slist.begin(),"hello!"); //在list链表首元素之前插入hello!这个元素;
svec.insert(svec.begin(),"hello!");//在vector首元素之前插入hello!这个元素;
插入范围内元素例子:
svec.insert(svec.end(),10,"Anna");//将10个Anna元素插入到svec的末尾;
注意:end表示的是svec最有一个元素后一个位置,从而也就是插入到末尾;
习题9.18、9.19、9.20程序解答:
#include <iostream>
#include<deque>
#include<list>
using namespace std;
int main()
{
deque<string> string1={"wangjin","wangjin1","wangjing2"};
for(auto it=string1.begin();it!=string1.end();++it) //打印deque容器;
cout<<*it<<endl;
list<string> string2(string1.begin(),string1.end());
for(auto it1=string2.begin();it1!=string2.end();++it1) //打印list容器;
cout<<*it1<<endl;
//上面是9.18和9.19习题程序;
list<int> oe={1,2,3,4,5,6,7,8,9,0};
deque<int> odd,even;
for(auto it2=oe.begin();it2!=oe.end();++it2)
{
if((*it2)%2==0)
even.insert(even.end(),*it2);
else
odd.insert(odd.end(),*it2);
}
cout<<"deque偶数元素为:"<<endl;
for(auto it3=even.begin();it3!=even.end();++it3) //打印deque偶数容器;
{
cout<<*it3<<",";
}
cout<<endl<<"deque奇数元素为:"<<endl;
for(auto it4=odd.begin();it4!=odd.end();++it4) //打印deque奇数容器;
{
cout<<*it4<<",";
}
cout<<endl; //上面为练习9.20习题
return 0;
}
显示结果:
B.emplace操作
C++11引入的新的三个标准:emplace_front、emplace和emplace_back,这些操作是直接构造,而不是拷贝元素;分别对应push_front、push和push_back;
例子:
c.emplace_back("978-56",25,15.99);//直接构造对象,压入容器;
c.push_back(Sales_data("978-56",25,15.99));//创建临时对象,再压入容器;
emplace函数在容器中直接构造元素。传递给emplace函数的参数必须与元素类型的构造函数相匹配。
C.删除元素
删除元素的操作:
c.pop_back():删除c中尾元素。c为空,则未定义。
c.pop_front():删除c中首元素。c为空,则未定义。
c.erase(p) :删除迭代器p所指定的元素,返回一个指向被删元素之后的迭代器;若p是尾元素,则返回尾后迭代器。
c.erase(b,e):删除迭代器b和e所指定范围内的元素。返回一个指向被删元素之后的迭代器;
c.clear():删除c中的所有元素。返回void;
注意:迭代器删除元素,都是返回删除元素的后一个位置的迭代器;
有关特殊的forward_list操作:
学过链表就能很快理解它删除和添加元素对整个链表的影响,书本中做了很详细的讲解,引用了before_begin()、insert_after();
D.改变容器的大小
resize(a):a表示整个容器数据的大小,它可以通过a的大小来增大和缩小容器;
c.resize(n):调整c的大小为n个元素。若n<c.size,则多出的元素被丢弃。
c.resize(n,t):调整c的大小为n个元素。任何新添加的元素的初始化值为t。
例子:
list<int> ilist(10,42) ; //10个int:每个的值为42;即list初始化有10个42的容器;
ilist.resize(15); //本来有10个元素,现在扩大了容器:增加了5个为0的元素;
ilist.resize(25,-1);
//继续扩大容器:增加了(25-10)10个为-1的元素;
ilist.resize(5) ; //从ilist末尾删除20个元素,最终容器只有:5个为42的元素;
E.不要保存end返回的迭代器
原因:当我们删除/添加vector或string的元素后,或在deque中首元素之外的任何位置添加/删除元素后,原来end返回的迭代器总是会失效。因此,添加或删除元素的循环程序必须反复调用end,而不是在循环之前保存end返回的迭代器,一直当做容器末尾使用。
4.vector对象是如何增长的
结果:容器通过预留空间作为备用,可用来保存更多的新元素。标准库采用了这种可以减少容器空间重新分配次数的策略。
A.管理容量的成员函数
容器大小管理操作:
c.shrink_to_fit()
:将capacity()减少为与size()相同大小;
它的适用范围:vector、string和deque;
c.capacity() :不重新分配内存空间的话,c可以保存多少元素;
c.reserve()
:分配至少能容纳n个元素的内存空间;
它们两个的适用范围:vector、string;
注意:reserve并不改变容器中元素的数量,它仅影响vector预先分配多大的内存。
B.capacity和size
它们的区别:capacity>=size;capacity-size=预留空间的大小;根据这两个总结的公式就能够更好的理解它们。
例子习题9.39和9.38的题目程序:
#include <iostream>
#include<vector>
#include<string>
using namespace std;
int main()
{
vector<string> svec;
svec.reserve(1024);
cout << "capacity1:" <<svec.capacity()<< endl; //之前的capacity;
string word={"0"};
for(auto it=0;it<256;it++) //将it=256,512,1000,1024分别运行程序
svec.push_back(word);
svec.resize(svec.size()+svec.size()/2);
cout << "capacity2:" <<svec.capacity()<< endl; //插入数据后的capacity;
return 0;
}
显示结果:
分别为(插入256,512,1000,1024)对应的capacity:1024,1024,2000,2048;
5.额外的string操作
A.构造string的其他方法
string s(cp,n) : s是cp指向数组中前n个字符的拷贝。数组长度须不小于n;
string s(s2,pos2):s是string s2从下标pos2开始的字符的拷贝。
string s(s2,pos2,len2):s是string s2从下标pos2开始len2个字符的拷贝。若pos2>s2.size(),构造函数的行为未定义。不管len2多大,构造函数最多拷贝s2.size()-pos2个字符。
例子:
const char* cp="Hello world!!!" ;
char nonull[]={"H","i"};
string s1(cp) ;//拷贝cp中的字符直到遇到空字符;s1=="Hello world!!!";
string s2(cp+6,5);// 从cp[6]开始拷贝5个字符;s2="world";
string s3(s1,6,20);//只拷贝到s1的末尾,s3="world!!!";
string s4(s1,16);//s1的下标小于16,从而抛出一个out_of_range异常;
B.substr操作
s.substr(pos,n):返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值为0,n的默认值为s.size()-pos,即拷贝从pos开始的所有字符;
与上面string的操作相似,就不在举例说明;
C.append和replace函数
s.append(args) : 将args追加到s。返回一个指向s的引用。
s.replace(range,args): 删除s中范围range内的字符,替换为args指定的字符。range或者是一个下标和一个长度,或者是一个对指向s的迭代器。返回一个指向s的引用。
例子:
string s("C++ primer"),s2=s;//将s和s2初始化为"C++ primer";
s.insert(s.size(),"4th Ed.");
s2.append(" 4th Ed.");//等价s.insert(s.size(),"4th Ed.");s2==s,s=="C++ primer 4th Ed."
replace操作是调用erase和insert的一种简单形式:
s.erase(11,3); //s=="C++ primer Ed."
s.insert(11,"5th");
//s=="C++ primer 5th Ed."
s2.replace(11,3,"5th");
//s==s2;等价于上面两个语句;
D.string 搜素操作
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中的字符;
例子:
string river("Mississippi");
auto first_pos=river.find("is"); //返回1
auto last_pos=river.rfind("is");// 返回4
E.数值转换
string和数值之间的转换:
to_string(val) :一组重载函数,返回数值val的string表示。val可以是任何算术类型。
stoi(l,ul,ll,ull)(s,p,b):返回s的起始字串的数值,(红色标记的)返回值类型分别是int,long,unsigned long,long long,unsigned long long。b表示转换所用的基数,默认值为10。p是size_t指针,用来保存s中第一个非数值字符的下标,p默认为0,即函数不保存下标。
stof(d,ld)(s,p): 返回s的起始子串的数值,返回类型分别是float,double,long double。p参数的作用与整数转换函数中一样;
由于我用的codeblacks不支持stoi从而,我编写的程序编译不通过;下次换个支持C++11特性的软件试试;
总结C++11特性:
1.forward_list和array
array是固定大小的数组,因此,它不支持添加/删除以及改变容器大小的操作。而forward_list的设计目标是达到与最好的手写的单向链表数据结构相当的性能。它没有size操作,因为保存和计算需要额外的开销。前面也做了介绍。
2.cbegin和cend
他们和begin,end类似,只不过前面加了const ;它们返回const迭代器;当不需要写访问时,就用cbegin和end比较好。
3.列表初始化
容器列表的初始化在前面的程序中都用到了,前面章节也讲解过;
list<string> aythors={"Mislton","Shakespeare"};
4.容器的非成员版本swap
本章节对swap和赋值进行了比较分析,swap操作将容器的内容交换不会导致指向容器的迭代器、引用和指针失效。这是它的一个很大的优点。
5.insert函数
本章做了重点讲解,它在新的标准下,接受元素个数或范围的insert版本返回指向第一个新加入元素的迭代器。
6.emplace操作
本章也做了详细的讲解,通过前面的例子能够更好的理解和运用它。
7.shrink_to_fit
管理容量成员的函数,它在新标准下,它可要求deque、vector和string退回不需要的内存空间。(简单来讲,就是将预留空间去掉和size()大小相同)
8.string的数值转换
上面有详细的讲解,本想通过程序更好的理解它,我用的codeblacks不支持stoi等函数,只能下次尝试其他支持c++11特性的软件。