容器(Containers)简介
容器复制编程中非常常用的结构:动态阵列(vector)、队列(queue)、堆栈(stack)、堆(priority_queue)、链接列表(list)、树(set)、关联阵列(map)。。。
许多容器具有多个成员功能,并具有共同的功能。决定为特定需要使用哪种类型的容器通常不仅取决于容器提供的功能,还取决于其某些成员的效率(复杂性)。序列容器尤其如此,在插入/删除元素和访问元素之间提供不同的复杂性权衡。堆栈、队列和priority_queue作为容器适配器实施。容器适配器不是完整的容器类别,而是依靠其中一个容器类别(如退货或列表)来处理元素的类别。底层容器的封装方式是,容器适配器的成员可以独立于所使用的底层容器类别访问其元素。
array
头文件:#include <array>
template < class T, size_t N > class array;
#include <iostream>
#include <array>
using namespace std;
int main() {
array<int,5> myarray = { 2, 16, 77, 34, 50 }; //定义
cout << "begin 顺序迭代: ";
for ( auto it = myarray.begin(); it != myarray.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
cout << "rbegin 逆序迭代: ";
for ( auto rit=myarray.rbegin() ; rit < myarray.rend(); ++rit ) //逆序迭代器rbegin、rend
cout << ' ' << *rit;
cout << endl;
cout << "cbegin 常量指针顺序迭代: ";
for ( auto it = myarray.cbegin(); it != myarray.cend(); ++it ) //迭代器返回的指针为常量类型cbegin、cend
std::cout << ' ' << *it; // 常量指针const_iterator *it 不能改变
cout << endl;
cout << "crbegin 常量指针逆序迭代: ";
for ( auto rit=myarray.crbegin() ; rit < myarray.crend(); ++rit ) //指针为常量类型逆序crbegin、crend
std::cout << ' ' << *rit; // cannot modify *rit
cout << endl;
cout << "begin 访问第2-1个元素: " << *(myarray.begin()+1) << endl;
cout << "rend 访问第2-1个元素: " << *(myarray.rend()-2) << endl;
cout << "size 访问数组的大小: " << myarray.size() << endl; //返回的是元素个数
cout << "max_size 访问数组的大小: " << myarray.max_size() << endl; //返回的也是元素个数=size
cout << "sizeof访问数组的大小: " << sizeof(myarray) << endl; //返回的是占用的字节数(元素个数*sizeof(int))
cout << "myarray " << (myarray.empty()?"is empty":"is not empty") << endl; //empty判断数组是否为空
cout << "[] 操作符读写元素: " << myarray[2] << endl;
cout << "front 读写第一个元素: " << myarray.front() << endl;
cout << "back 读最后一个元素: " << myarray.back() << endl;
cout << "data 函数和 begin函数相同" << endl;
cout << "fill 函数讲数组的所有元素设置为某一值" << endl;
array<int,5> cparray; //定义
cparray.fill(2); //fill 函数讲数组的所有元素设置为某一值
cout << "swap 函数交换两个相同类型和大小的数组" << endl;
swap(myarray, cparray); //交换两个变量
cout << "swap后的myarray: ";
for ( auto it = myarray.begin(); it != myarray.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
if(myarray<cparray) //比较操作符
{
cout << "myarray的字典序小于cparray" <<endl;
}
}
vector
头文件: #include <vector>
template < class T, class Alloc = allocator > class vector; // generic template
vector是可以改变大小的序列容器,使用连续存储位置,与数组一样高效。
构造函数
与array不同的是声明一个vector实际上是一个类的实例化对象,因此有相应的构造函数。
//default (1)
explicit vector (const allocator_type& alloc = allocator_type());
vector<int> first; // empty vector of ints
//fill (2)
explicit vector (size_type n);
vector (size_type n, const value_type& val,
const allocator_type& alloc = allocator_type());
vector<int> second (4,100); // four ints with value 100
//range (3)
template <class InputIterator>
vector (InputIterator first, InputIterator last,
const allocator_type& alloc = allocator_type());
vector<int> third (second.begin(),second.end()); // iterating through second
//copy (4)
vector (const vector& x);
vector (const vector& x, const allocator_type& alloc);
vector<int> fourth (third); // a copy of third
//move (5)
vector (vector&& x);
vector (vector&& x, const allocator_type& alloc);
//initializer list (6)
vector (initializer_list<value_type> il,
const allocator_type& alloc = allocator_type());
vector可以改变其大小,我们通过size函数可以得到当前数组的元素个数,通过capacity得到分配给当前数组存储大小的个数。通过resize函数改变size大小,通过reserve函数改变capacity大小,reserve只能增加capacity的大小,不能减少。只有通过shrink_to_fit才能将capacity减少至size。始终capacity大于等于size。并且当resize大于当前capacity时,capacity变为原来的size两倍或者更大。
对于vector而言,array拥有的函数vector都拥有,除此之外,vector还拥有:
vector<int> myvector = { 2, 16, 77, 34, 50 }; //定义
cout << "begin 顺序迭代: ";
for ( auto it = myvector.begin(); it != myvector.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
cout << "rbegin 逆序迭代: ";
for ( auto rit=myvector.rbegin() ; rit < myvector.rend(); ++rit ) //逆序迭代器rbegin、rend
cout << ' ' << *rit;
cout << endl;
cout << "cbegin 常量指针顺序迭代: ";
for ( auto it = myvector.cbegin(); it != myvector.cend(); ++it ) //迭代器返回的指针为常量类型cbegin、cend
std::cout << ' ' << *it; // 常量指针const_iterator *it 不能改变
cout << endl;
cout << "crbegin 常量指针逆序迭代: ";
for ( auto rit=myvector.crbegin() ; rit < myvector.crend(); ++rit ) //指针为常量类型逆序crbegin、crend
std::cout << ' ' << *rit; // cannot modify *rit
cout << endl;
cout << "begin 访问第2-1个元素: " << *(myvector.begin()+1) << endl;
cout << "rend 访问第2-1个元素: " << *(myvector.rend()-2) << endl;
cout << "size 访问数组的大小: " << myvector.size() << endl; //返回的是元素个数
cout << "max_size 访问数组的大小: " << myvector.max_size() << endl; //返回与array不同,是真实的最大个数
cout << "sizeof访问数组的大小: " << sizeof(myvector) << endl; //返回的永远等于24,应该是指针类的大小,后面再研究
cout << "myvector " << (myvector.empty()?"is empty":"is not empty") << endl; //empty判断数组是否为空
cout << "[] 操作符读写元素: " << myvector[2] << endl;
cout << "front 读写第一个元素: " << myvector.front() << endl;
cout << "back 读写最后一个元素: " << myvector.back() << endl;
cout << "data 函数和 begin函数相同" << endl;
cout << "fill 函数讲数组的所有元素设置为某一值" << endl;
vector<int> cpvector(5,2); //定义
//cpvector.fill(2); //vector没有fill函数
cout << "swap 函数交换两个相同类型和大小的数组" << endl;
swap(myvector, cpvector); //交换两个变量
cout << "swap后的myvector: ";
for ( auto it = myvector.begin(); it != myvector.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
if(myvector<cpvector) //比较操作符
{
cout << "myvector的字典序小于cpvector" <<endl;
}
/**************以下是array没有的*******************/
myvector.resize(4); //变小则删除多余的,保留剩下的
myvector.resize(6,100); //变大则保留原来的,增加多余的为指定值100
myvector.resize(15,31); 变大则保留原来的,增加多余的为指定值0
cout << "resize后顺序迭代: ";
for ( auto it = myvector.begin(); it != myvector.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
cout << "1. capacity of myvector: " << myvector.capacity() << '\n'; //capacity返回当前分配给向量的存储空间的大小
myvector.resize(10);
std::cout << "2.resize capacity of myvector: " << myvector.capacity() << '\n';
myvector.shrink_to_fit(); //shrink_to_fit要求容器减少其容量,以适应其size大小
std::cout << "3.shrink_to_fit capacity of myvector: " << myvector.capacity() << '\n';
myvector.reserve(11); //reserve只能增加内存分配,不能减少。
std::cout << "4.reserve(11) capacity of myvector: " << myvector.capacity() << '\n';
//通过以下程序我们可以发现当给vector扩容时,其扩充的大小大于上次vector的size两倍。
std::vector<int>::size_type sz; //int类型
std::vector<int> foo;
sz = foo.capacity();
std::cout << "capacity changed: ";
for (int i=0; i<20; ++i) {
foo.push_back(i); //push_back 函数在数组末尾增加一个元素。pop_back删除末尾的元素
if (sz!=foo.capacity()) {
sz = foo.capacity();
std::cout << " " << sz;
}
}//capacity扩容研究
cout << endl;
myvector = { 2, 16, 77, 34, 50 };
myvector.assign(3,5); //初始化为3个值为5的元素
cout << "1.assign后顺序迭代: ";
for ( auto it = myvector.begin(); it != myvector.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
foo = { 2, 16, 77, 34, 50 };
myvector.assign(foo.begin()+1,foo.end()-1); //元素的指针
cout << "2.assign后顺序迭代: ";
for ( auto it = myvector.begin(); it != myvector.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
std::vector<int>::iterator it; //定义一个迭代器
it = myvector.insert ( myvector.begin()+1, 200); //insert方法一1 返回插入点的指针
myvector.insert (it,2,300); //insert方法2
int myarray [] = { 501,502,503 };
myvector.insert (myvector.end(), myarray, myarray+3); //insert方法三
cout << "insert后顺序迭代: ";
for ( auto it = myvector.begin(); it != myvector.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
myvector.erase (myvector.begin()); //erase删除某一个元素
myvector.erase (myvector.begin()+2,myvector.begin()+4); //erase删除某一区间元素
cout << "erase后顺序迭代: ";
for ( auto it = myvector.begin(); it != myvector.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
foo.assign(3,100); // three ints with a value of 100
std::vector<int> bar (5,200); // five ints with a value of 200
foo.swap(bar); //swap直接交换两个数组,以及capacity也会交换
myvector.clear(); //clear 函数清楚所有元素,但是不改变capacity
vector<int>().swap(myvector); //swap该方法可以替代clear,并且capacity会置零
myvector.emplace ( myvector.end(), 111 ); //emplace在某个位置插入指定值,与insert有什么区别?
myvector.emplace_back (222); //emplace_back在末尾插入指定值,与push_back有什么区别?
deque
双端队列(Double-ended queue,缩写为Deque)是一个大小可以动态变化(Dynamic size)且可以在两端扩展或收缩的顺序容器。顺序容器中的元素按照严格的线性顺序排序。可以通过元素在序列中的位置访问对应的元素。不同的库可能会按不同的方式来实现双端队列,通常实现为某种形式的动态数组。但不管通过哪种方式,双端队列都允许通过随机迭代器直接访问各个元素,且内部的存储空间会按需求自动地扩展或收缩。容器实际分配的内存数超过容纳当前所有有效元素所需的,因为额外的内存将被未来增长的部分所使用。就因为这点,当插入元素时,容器不需要太频繁地分配内存。因此,双端队列提供了类似向量(std::vector)的功能,且不仅可以在容器末尾,还可以在容器开头高效地插入或删除元素。但是,相比向量,双端队列不保证内部的元素是按连续的存储空间存储的, 因此,不允许对指针直接做偏移操作来直接访问元素。 在内部,双端队列与向量的工作方式完全不同:向量使用单数组数据结构,在元素增加的过程中,需要偶尔的内存重分配,而双端队列中的元素被零散地保存在不同的存储块中,容器内部会保存一些必要的数据使得可以以恒定时间及一个统一的顺序接口直接访问任意元素。因此,双端队列的内部实现比向量的稍稍复杂一点,但这也使得它在一些特定环境下可以更高效地增长,特别是对于非常长的序列,内存重分配的代价是及其高昂的。对于大量涉及在除了起始或末尾以外的其它任意位置插入或删除元素的操作,相比列表(std::list)及正向列表(std::forward_list),deque 所表现出的性能是极差的,且操作前后的迭代器、引用的一致性较低。
总结deque与vector的区别:
- 1.deque可以沿两个方向扩容。vector只能沿一个方向扩容;
- 2.deque的内存分配不保证内存连续,因此不能使用指针加偏移的方式访问元素,但是扩容时开销较小。vector保证分配的内存连续,因此扩容时开销更大。
- 3.deque没有capacity和reserve函数,但是有push_front和pop_front以及emplace_front函数.
以下是deque的相关用法:
deque<int> mydeque = { 2, 3, 4, 5, 6 }; //定义
cout << "begin 顺序迭代: ";
for ( auto it = mydeque.begin(); it != mydeque.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
cout << "rbegin 逆序迭代: ";
for ( auto rit=mydeque.rbegin() ; rit < mydeque.rend(); ++rit ) //逆序迭代器rbegin、rend
cout << ' ' << *rit;
cout << endl;
cout << "cbegin 常量指针顺序迭代: ";
for ( auto it = mydeque.cbegin(); it != mydeque.cend(); ++it ) //迭代器返回的指针为常量类型cbegin、cend
std::cout << ' ' << *it; // 常量指针const_iterator *it 不能改变
cout << endl;
cout << "crbegin 常量指针逆序迭代: ";
for ( auto rit=mydeque.crbegin() ; rit < mydeque.crend(); ++rit ) //指针为常量类型逆序crbegin、crend
std::cout << ' ' << *rit; // cannot modify *rit
cout << endl;
cout << "begin 不能访问第2-1个元素: " << *(mydeque.begin()+2) << endl; //存储不保证连续,这种访问是合法的
cout << "rend 不能访问第2-1个元素: " << *(mydeque.rend()-2) << endl; //存储不保证连续,这种访问是合法的
cout << "size 访问数组的大小: " << mydeque.size() << endl; //返回的是元素个数
cout << "max_size 访问数组的大小: " << mydeque.max_size() << endl; //返回与array不同,是真实的最大个数
cout << "sizeof访问数组的大小: " << sizeof(mydeque) << endl; //返回的永远等于80,应该是指针类的大小,后面再研究
cout << "mydeque " << (mydeque.empty()?"is empty":"is not empty") << endl; //empty判断数组是否为空
cout << "[] 操作符读写元素: " << mydeque[2] << endl;
cout << "front 读写第一个元素: " << mydeque.front() << endl;
cout << "back 读写最后一个元素: " << mydeque.back() << endl;
cout << "data 函数和 begin函数相同" << endl;
deque<int> cpdeque(5,2); //定义
//cpdeque.fill(2); //deque没有fill函数
cout << "swap 函数交换两个相同类型和大小的数组" << endl;
swap(mydeque, cpdeque); //交换两个变量
cout << "swap后的mydeque: ";
for ( auto it = mydeque.begin(); it != mydeque.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
if(mydeque<cpdeque) //比较操作符
{
cout << "mydeque的字典序小于cpdeque" <<endl;
}
mydeque.resize(4); //变小则删除多余的,保留剩下的
mydeque.resize(6,100); //变大则保留原来的,增加多余的为指定值100
mydeque.resize(15,31); 变大则保留原来的,增加多余的为指定值0
cout << "resize后顺序迭代: ";
for ( auto it = mydeque.begin(); it != mydeque.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
mydeque = { 2, 16, 77, 34, 50 };
mydeque.assign(3,5); //初始化为3个值为5的元素
cout << "1.assign后顺序迭代: ";
for ( auto it = mydeque.begin(); it != mydeque.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
deque<int> foo = { 2, 16, 77, 34, 50 };
mydeque.assign(foo.begin()+1,foo.end()-1); //元素的指针
cout << "2.assign后顺序迭代: ";
for ( auto it = mydeque.begin(); it != mydeque.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
std::deque<int>::iterator it; //定义一个迭代器
it = mydeque.insert ( mydeque.begin()+1, 200); //insert方法一1 返回插入点的指针
mydeque.insert (it,2,300); //insert方法2
int myarray [] = { 501,502,503 };
mydeque.insert (mydeque.end(), myarray, myarray+3); //insert方法三
cout << "insert后顺序迭代: ";
for ( auto it = mydeque.begin(); it != mydeque.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
mydeque.erase (mydeque.begin()); //erase删除某一个元素
mydeque.erase (mydeque.begin()+2,mydeque.begin()+4); //erase删除某一区间元素
cout << "erase后顺序迭代: ";
for ( auto it = mydeque.begin(); it != mydeque.end(); ++it ) //顺序迭代器begin、end
cout << ' ' << *it;
cout << endl;
foo.assign(3,100); // three ints with a value of 100
std::deque<int> bar (5,200); // five ints with a value of 200
foo.swap(bar); //swap直接交换两个数组
mydeque.clear(); //clear 函数清楚所有元素
deque<int>().swap(mydeque); //swap该方法可以替代clear,并且capacity会置零
mydeque.emplace ( mydeque.end(), 111 ); //emplace在某个位置插入指定值,与insert有什么区别?
mydeque.emplace_back (222); //emplace_back在末尾插入指定值,与push_back有什么区别?
forward_list
list
stack
queue
priority_queue
set
multiset
map
multimap
unordered_set
unordered_multiset
unordered_map
unordered_multimap
这些后面写…