Part10 泛型程序设计与C++标准模板库10.4顺序容器
顺序容器
向量(vector)
双端队列(deque)
列表(list)
单向链表(forward_list) (以上四种在逻辑上可看作是一个长度可扩展的数组)
数组(array)
元素线性排列,可以随时在指定位置插入元素和删除元素。
必须符合Assignable这一概念(即具有公有的拷贝构造函数并可以用“=”赋值)。
array对象的大小固定,forward_list有特殊的添加和删除操作。
顺序容器的接口(不包含单向链表(forward_list)和数组(array))
构造函数
赋值函数
assign
插入函数
insert, pushfront(只对list和deque), pushback,emplace,emplace_front
删除函数
erase,clear,popfront(只对list和deque) ,popback,emplace_back
首尾元素的直接访问
front,back
改变大小
resize
//10-4顺序容器的基本操作 #include<iostream> #include<list> #include<deque> #include<iterator> using namespace std; template <class T> //输出指定的顺序容器的元素 void printContainer(const char* msg, const T& s) { cout << msg << ": "; copy(s.begin(), s.end(), ostream_iterator<int>(cout, " ")); cout << endl; } int main() { //从标准输入读入10个整数,将它们分别从s的头部加入 deque<int> s; for (int i = 0; i < 10; i++) { int x; cin >> x; s.push_front(x); } printContainer("deque at first", s); //用s容器的内容的逆序构造列表容器l list<int> l(s.rbegin(), s.rend()); printContainer("list at first", l); //将列表容器l的每相邻两个元素顺序颠倒 list<int>::iterator iter = l.begin(); while (iter != l.end()) { int v = *iter; iter = l.erase(iter); l.insert(++iter, v); } printContainer("list at last", l); //用列表容器l的内容给s赋值,将s输出 s.assign(l.begin(), l.end()); printContainer("deque at last", s); return 0; }
2顺序容器的特征
顺序容器:向量、双端队列、列表、单向链表、数组
向量(Vector)
特点
一个可以扩展的动态数组
随机访问、在尾部插入或删除元素快
在中间或头部插入或删除元素慢
向量的容量
容量(capacity):实际分配空间的大小
s.capacity() :返回当前容量
s.reserve(n):若容量小于n,则对s进行扩展,使其容量至少为n
双端队列(deque)
特点
在两端插入或删除元素快
在中间插入或删除元素慢
随机访问较快,但比向量容器慢
//10-5奇偶排序,先按照从大到小顺序输出奇数,再按照从小到大顺序输出偶数。 #include<iostream> #include<iterator> #include<vector> #include<deque> #include<algorithm> using namespace std; int main(){ istream_iterator<int> i1(cin), i2; //建立一对输入流迭代器 vector<int> s1(i1, i2); //通过输入流迭代器从标准输入流中输入数据 sort(s1.begin(), s1.end()); deque<int> s2; for(vector<int>::iterator iter = s1.begin(); iter != s1.end(); ++iter){ if(*iter % 2 == 0) //偶数放到尾部 s2.push_back(*iter); else //奇数放到头部 s2.push_front(*iter); } //将s2的结果输出 copy(s2.begin(), s2.end(), ostream_iterator<int>(cout, " ")); cout << endl; return 0; }
列表(list)
特点
在任意位置插入和删除元素都很快
不支持随机访问
接合(splice)操作
s1.splice(p, s2, q1, q2):将s2中[q1, q2)移动到s1中p所指向元素之前
//list举例 #include<iostream> #include<iterator> #include<list> #include<string> #include<algorithm> using namespace std; int main(){ string names1[] = {"Alice", "Helen", "Lucy", "Susan"}; string names2[] = {"Bob", "David", "Levin", "Mike"}; list<string> s1(names1,names1 + 4); //用names1数组的内容构造列表s1 list<string> s2(names2,names2 + 4); //用names2数组的内容构造列表s2 s2.splice(s2.end(), s1, s1.begin()); //将s1的第一个元素放到s2的最后 list<string>::iterator iter1 = s1.begin(); //iter1指向s1首 advance(iter1,2); // iter1前进2个元素,指向s1第3个元素 list<string>::iterator iter2 = s2.begin(); //iter2指向s2首 ++iter2; //指向s2第二个元素 list<string>::iterator iter3 = iter2; advance(iter3,2); //指向s2的第四个元素 s1.splice(iter1, s2, iter2, iter3); //将[iter2, iter3)范围内的结点接到s1中iter1指向的结点前 copy(s1.begin(), s1.end(), ostream_iterator<string>(cout, " ")); cout << endl; copy(s2.begin(), s2.end(), ostream_iterator<string>(cout, " ")); cout << endl; return 0; }
单向链表(forward_list)
单向链表每个结点只有指向下个结点的指针,没有简单的方法来获取一个结点的前驱;
未定义insert、emplace和erase操作,而定义了insertafter、emplaceafter和erase_after操作,其参数与list的insert、emplace和erase相同,
但并不是插入或删除迭代器p1所指的元素,而是对p1所指元素之后的结点进行操作;
不支持size操作。
数组(array)
array是对内置数组的封装,提供了更安全,更方便的使用数组的方式
array的对象的大小是固定的,定义时除了需要指定元素类型,还需要指定容器大小。
不能动态地改变容器大小
顺序容器的比较
如果需要执行大量的随机访问操作,而且当扩展容器时只需要向容器尾部加入新的元素,就应当选择向量容器vector;
如果需要少量的随机访问操作,需要在容器两端插入或删除元素,则应当选择双端队列容器deque;
如果不需要对容器进行随机访问,但是需要在中间位置插入或者删除元素,就应当选择列表容器list或forward_list;
如果需要数组,array相对于内置数组类型而言,是一种更安全、更容易使用的数组类型。
3顺序容器的插入迭代器与适配器
顺序容器的插入迭代器
用于向容器头部、尾部或中间指定位置插入元素的迭代器
包括前插迭代器(frontinserter)、后插迭代器(backinsrter)和任意位置插入迭代器(inserter)
例:
list<int> s;
back_inserter iter(s);
*(iter++) = 5; //通过iter把5插入s末尾
顺序容器的适配器
以顺序容器为基础构建一些常用数据结构,是对顺序容器的封装
栈(stack):最先压入的元素最后被弹出
队列(queue):最先压入的元素最先被弹出
优先级队列(priority_queue):最“大”的元素最先被弹出
栈和队列模板
栈模板
template <class T, class Sequence = deque<T> > class stack;
队列模板
template <class T, class FrontInsertionSequence = deque<T> > class queue;
栈可以用任何一种顺序容器作为基础容器,而队列只允许用前插顺序容器(双端队列或列表)
栈和队列共同支持的操作
s1 op s2 op可以是==、!=、<、<=、>、>=之一,它会对两个容器适配器之间的元素按字典序进行比较
s.size() 返回s的元素个数
s.empty() 返回s是否为空
s.push(t) 将元素t压入到s中
s.pop() 将一个元素从s中弹出,对于栈来说,每次弹出的是最后被压入的元素,而对于队列,每次被弹出的是最先被压入的元素
不支持迭代器,因为它们不允许对任意元素进行访问
栈和队列不同的操作
栈的操作
s.top() 返回栈顶元素的引用
队列操作
s.front() 获得队头元素的引用
s.back() 获得队尾元素的引用
//例10-7 利用栈反向输出单词 #include<iostream> #include<stack> #include<string> using namespace std; int main(){ stack<char> s; string str; cin >> str; for(string::iterator iter = str.begin(); iter != str.end(); ++iter) s.push(*iter); //将字符串的每个元素顺序压入栈中 while(!s.empty()){ cout << s.top(); s.pop(); } cout << endl; return 0; }
优先级队列
优先级队列也像栈和队列一样支持元素的压入和弹出,但元素弹出的顺序与元素的大小有关,每次弹出的总是容器中最“大”的一个元素。
template <class T, class Sequence = vector<T> > class priority_queue;
优先级队列的基础容器必须是支持随机访问的顺序容器。
支持栈和队列的size、empty、push、pop几个成员函数,用法与栈和队列相同。
优先级队列并不支持比较操作。
与栈类似,优先级队列提供一个top函数,可以获得下一个即将被弹出元素(即最“大”的元素)的引用。