- (1) 容器类型
- (2) 图解容器类型
- (3)容器包含的函数
- (4) array(数组容器)
- 1. array初始化
- 2. array成员函数
- 3. array随机访问迭代器
- 4. 具体用法代码概览及运行截图
- (5)vector(向量容器)
- 1. vector初始化
- 2. vector成员函数
- 3. vector随机访问迭代器
- 4. 具体用法代码概览及运行截图
- (6)deque(双端队列容器)
- 1. deque初始化
- 2. deque成员函数
- 3. deque随机访问迭代器
- 4. 具体用法代码概览及运行截图
- (7)list(双向链表容器)
- 1. list初始化
- 2. list成员函数
- 3. list双向迭代器
- 4. 具体用法代码概览及运行截图
- (8)forward_list(单向链表容器)
整体文章目录: Standard Template Library基础知识
序列容器,即以线性排列(类似普通数组的存储方式)来存储某一指定类型(例如 int、double 等)的数据,需要特殊说明的是,该类容器并不会自动对存储的元素按照值的大小进行排序。 (1) 容器类型
| 容器类型 | 说明 |
|---|---|
| array | 表示可以存储 N 个 T 类型的元素,是 C++ 本身提供的一种容器。此类容器一旦建立,其长度就是固定不变的,这意味着不能增加或删除元素,只能改变某个元素的值; |
| vector(向量容器) | 用来存放 T 类型的元素,是一个长度可变的序列容器,即在存储空间不足时,会自动申请更多的内存。使用此容器,在尾部增加或删除元素的效率最高(时间复杂度为 O(1) 常数阶),在其它位置插入或删除元素效率较差(时间复杂度为 O(n) 线性阶,其中 n 为容器中元素的个数); |
| deque(双端队列容器) | 和 vector 非常相似,区别在于使用该容器不仅尾部插入和删除元素高效,在头部插入或删除元素也同样高效,时间复杂度都是 O(1) 常数阶,但是在容器中某一位置处插入或删除元素,时间复杂度为 O(n) 线性阶; |
| list(链表容器) | 是一个长度可变的、由 T 类型元素组成的序列,它以双向链表的形式组织元素,在这个序列的任何地方都可以高效地增加或删除元素(时间复杂度都为常数阶 O(1)),但访问容器中任意元素的速度要比前三种容器慢,这是因为 list 必须从第一个元素或最后一个元素开始访问,需要沿着链表移动,直到到达想要的元素。 |
| forward_list(正向链表容器) | 和 list 容器非常类似,只不过它以单链表的形式组织元素,它内部的元素只能从第一个元素开始访问,是一类比链表容器快、更节省内存的容器。 |
注意,其实除此之外,stack和 queue 本质上也属于序列容器,只不过它们都是在 deque 容器的 基础上改头换面而成,通常更习惯称它们为容器适配器。
图 1 说明了可供使用的序列容器以及它们之间的区别。
(2) 图解容器类型 (3)容器包含的函数表 2 展示了 array、vector 和 deque 容器的函数成员,它们中至少有两个容器实现了同样的函数成员。(列表中 - 表明对应的容器并没有定义这个函数。)
array 容器就是在 C++ 普通数组的基础上,添加了一些成员函数和全局函数。代码展示统一放到最后面
1. array初始化#include//array容器以类模板的形式定义在 头文件 using namesapce std;//并位于命名空间 std 中 //下面是array初始的三种方式 //1.第一种创建具有 10 个 double 类型元素的 array 容器 array2. array成员函数values; //2.将所有的元素初始化为 0 或者和默认元素类型等效的值: array values{};//double被初始化0.0 //3.对元素进行初始化 array values {0.5,1.0,1.5,,2.0};//其余元素初始化为0.0
另外,在<array>头文件中还重载了 get() 全局函数,该重载函数的功能是访问容器中指定的元素,并返回该元素的引用。它是一个辅助函数,能够获取到容器的第 n 个元素。需要注意的是,该模板函数中,参数的实参必须是一个在编译时可以确定的常量表达式,所以它不能是一个循环变量。
这些成员函数通常是成对使用的,即 begin()/end()、rbegin()/rend()、cbegin()/cend()、crbegin()/crend() 各自成对搭配使用。
#include(5)vector(向量容器)#include using namespace std; int main() { array nums{};//初始化为0 array counts{};//初始化为0 for (int i = 0; i < nums.size(); ++i) { nums.at(i)=i;//依次设置0-9 } //逐个访问输出 for (auto it = nums.begin(); it != nums.end(); it++) { cout << *it << " "; } cout< (nums) << endl; //倒序输出 for (auto it = nums.rbegin(); it != nums.rend(); ++it) { cout << *it << " "; } cout << endl; nums.front() = 10;//讲首个元素修改为10 nums.back() = 20;//将末尾元素修改为20 for (auto it = nums.begin(); it != nums.end(); it++) { cout << *it << " "; } cout << endl; nums.fill(100);//将元素都修改为100 for (auto it = nums.begin(); it != nums.end(); it++) { cout << *it << " "; } cout << endl; for (int i = 0; i < nums.size(); ++i) { *(counts.data() + i) = i + 10;//couts.data()返回首个元素的指针,begin()返回首个元素的随机访问迭代器。功能类似但不完全相同哦 cout << *(counts.data() + i)<<" "; } cout << endl; nums.swap(counts);//交换两个容器 for (auto it = nums.begin(); it != nums.end(); ++it) cout << *it << " "; cout << endl; for (auto it = counts.begin(); it != counts.end(); ++it) cout << *it << " "; cout << endl; }
array 实现的是静态数组(容量固定的数组),而 vector 实现的是一个动态数组,即可以进行元素的插入和删除,在此过程中,vector 会动态调整所占用的内存空间,整个过程无需人工干预。
该容器擅长在尾部插入或删除元素
vector不是存储bool类型元素的vector容器
在实际场景中需要使用 vector 这样的存储结构,可以选择使用 deque
deque 容器几乎具有 vecotr 容器全部的功能(拥有的成员方法也仅差 reserve() 和 capacity()),
而且更重要的是,deque 容器可以正常存储 bool 类型元素。
#include2. vector成员函数#include using namespace std; int main() { //下面是几种初始化方法 //1. vector nums1;//成功创建一个空的vector容器。因为容器中没有元素,所以没有为其分配空间。 //当添加第一个元素(比如使用 push_back() 函数)时,vector 会自动分配内存。4 nums1.reserve(10);//通过调用 reserve() 成员函数来增加容器的容量。该函数细节在函数成员节讲解 //2. vector nums2{ 10,20,30,40,50 }; //在创建的同时指定初始值以及元素个数 //3. vector nums3(10); //指定元素个数,元素值是默认元素类型等效的值 //4. vector nums4(10,2);//不想用 0 作为默认值,也可以指定一个其它值 //圆括号() 中的 2 个参数,既可以是常量,也可以用变量来表示 //5. int tempCapacity = 10; int tempValue = 2; vector nums5(tempCapacity, tempValue); //6. //通过存储元素类型相同的其它 vector 容器,也可以创建新的 vector 容器 vector nums6(nums5); //在此基础上,如果不想复制其它容器中所有的元素,可以用一对指针或者迭代器来指定初始值的范围 int array[] = { 1,2,3 }; std::vector values(array, array + 2);//values 将保存{1,2} std::vector value1{ 1,2,3,4,5 }; std::vector value2(std::begin(value1), std::begin(value1) + 3);//value2保存{1,2,3} //由此,value2 容器中就包含了 {1,2,3} 这 3 个元素。 }
- vector 容器还有一个 std::swap(x , y) 非成员函数(其中 x 和 y 是存储相同类型元素的 vector 容器),它和 swap() 成员函数的功能完全相同,仅使用语法上有差异。
- 注意,如果 vector 的容量在执行reserve()之前,已经大于或等于 指定参数 个元素,那么这条语句什么也不做;另外,调用 reserve() 不会影响已存储的元素,也不会生成任何元素,即 values 容器内此时仍然没有任何元素。
还需注意的是,如果调用 reserve() 来增加容器容量,之前创建好的任何迭代器(例如开始迭代器和结束迭代器)都可能会失效,这是因为,为了增加容器的容量,vector 容器的元素可能已经被复制或移到了新的内存地址。所以后续再使用这些迭代器时,最好重新生成一下。 - emplace_back() 和 push_back() 的区别,就在于底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
- insert()
#include#include #include using namespace std; int main() { std::vector demo{1,2}; //第一种格式用法 demo.insert(demo.begin() + 1, 3);//{1,3,2} //第二种格式用法 demo.insert(demo.end(), 2, 5);//{1,3,2,5,5} //第三种格式用法 std::array test{ 7,8,9 }; demo.insert(demo.end(), test.begin(), test.end());//{1,3,2,5,5,7,8,9} //第四种格式用法 demo.insert(demo.end(), { 10,11 });//{1,3,2,5,5,7,8,9,10,11} return 0; }
- emplace() 每次只能插入一个元素,而不是多个。 emplace() 在插入元素时,是在容器的指定位置直接构造元素,而不是先单独生成,再将其复制(或移动)到容器中。
std::vectordemo1{1,2}; //emplace() 每次只能插入一个 int 类型元素 demo1.emplace(demo1.begin(), 3);//{3,1,2}
- 删除元素的方法
//删除元素{0,1,2,3,4,5,6,7,7,8,9,10}
nums1.pop_back();//第一种方式{0,1,2,3,4,5,6,7,7,8,9}
nums1.erase(nums1.end()-1 );//第二种方式{0,1,2,3,4,5,6,7,7,8}
swap(nums1.at(nums1.size()-2), nums1.at(nums1.size()-1));//{0,1,2,3,4,5,6,7,8,7}
nums1.pop_back();//{0,1,2,3,4,5,6,7,8}
//remove后面算法章节专门介绍
nums1.clear();
if (nums1.empty()) cout << "The container is empty." << endl;
3. vector随机访问迭代器
和 array 容器不同,vector 容器可以随着存储元素的增加,自行申请更多的存储空间。因此,在创建 vector 对象时,我们可以直接创建一个空的 vector 容器,并不会影响后续使用该容器。但这会产生一个问题,即在初始化空的 vector 容器时,不能使用迭代器。
除此之外,vector 容器在申请更多内存的同时,容器中的所有元素可能会被复制或移动到新的内存地址,这会导致之前创建的迭代器失效。
#include(6)deque(双端队列容器) 1. deque初始化#include #include #include using namespace std; int main() { vector nums1{ 1,2,3,4,5 }; //几种访问元素的方式 for (int i = 0; i < nums1.size(); ++i) { cout << nums1.at(i)<<" "; } cout << endl; for (vector ::iterator it = nums1.begin(); it != nums1.end();++it) { cout << *it << " "; } cout << endl; cout << "nums1[0] is " << nums1[0] << endl; cout << "nums1[1] is " << nums1.at(1)<< endl; //添加元素 //emplace_back() nums1.emplace_back(6);//{1,2,3,4,5,6} //insert() nums1.insert(nums1.begin(), 0);//第一种用法{0,1,2,3,4,5,6} nums1.insert(nums1.end(), 2, 7);//第二种用法{0,1,2,3,4,5,6,7,7} array test{ 8 }; nums1.insert(nums1.end(), test.begin(), test.end());//第三种用法{0,1,2,3,4,5,6,7,7,8} nums1.insert(nums1.end(), { 9,10 });//第四种用法{0,1,2,3,4,5,6,7,7,8,9,10} for (vector ::iterator it = nums1.begin(); it != nums1.end(); ++it) cout << *it << " "; cout << endl; //删除元素{0,1,2,3,4,5,6,7,7,8,9,10} nums1.pop_back();//第一种方式{0,1,2,3,4,5,6,7,7,8,9} nums1.erase(nums1.end()-1 );//第二种方式{0,1,2,3,4,5,6,7,7,8} swap(nums1.at(nums1.size()-2), nums1.at(nums1.size()-1));//{0,1,2,3,4,5,6,7,8,7} nums1.pop_back();//{0,1,2,3,4,5,6,7,8} //remove后面算法章节专门介绍 nums1.clear(); if (nums1.empty()) cout << "The container is empty." << endl; }
deque 容器中存储元素并不能保证所有元素都存储到连续的内存空间中。
当需要向序列两端频繁的添加或删除元素时,应首选 deque 容器。
初始化和vector容器一样
- insert()函数与vector类似
首先需要注意的一点是,迭代器的功能是遍历容器,在遍历的同时可以访问(甚至修改)容器中的元素,但迭代器不能用来初始化空的 deque 容器。
除此之外,当向 deque 容器添加元素时,deque 容器会申请更多的内存空间,同时其包含的所有元素可能会被复制或移动到新的内存地址(原来占用的内存会释放),这会导致之前创建的迭代器失效。
#include(7)list(双向链表容器)#include #include using namespace std; int main() { deque d1; //插入元素 d1.emplace_front(1);//在头部添加元素{1} d1.emplace_back(2);//在尾部添加元素{1,2} d1.insert(d1.begin(), 0);//{0,1,2} d1.insert(d1.end(), 2, 3);//{0,1,2,3,3} vector a = { 4,5,6 }; d1.insert(d1.end(), a.begin(), a.end());//{0,1,2,3,3,4,5,6} d1.insert(d1.end(), { 7,8 });//{0,1,2,3,3,4,5,6,7,8} for (auto it = d1.begin(); it != d1.end(); ++it) { cout << *it << " "; } cout << endl; //删除元素 d1.erase(d1.begin());//{1,2,3,3,4,5,6,7,8} d1.erase(d1.begin()+3);//{1,2,3,4,5,6,7,8} for (auto it = d1.begin(); it != d1.end(); ++it) { cout << *it << " "; } cout << endl; d1.erase(d1.begin(), d1.end());//等同于d1.clear(),删除[left,right)内的元素 if (d1.empty()) { cout << "容器已经清空" << endl; } }
list 容器中的元素可以分散存储在内存空间里,而不是必须存储在一整块连续的内存空间中。
实际场景中,如何需要对序列进行大量添加或删除元素的操作,而直接访问元素的需求却很少,这种情况建议使用 list 容器存储序列。
初始化和其他容器大体相似
2. list成员函数- splice()用法
splice() 成员方法移动元素的方式是,将存储该元素的节点从 list 容器底层的链表中摘除,然后再链接到当前 list 容器底层的链表中。这意味着,当使用 splice() 成员方法将 x 容器中的元素添加到当前容器的同时,该元素会从 x 容器中删除。
#include#include using namespace std; int main() { list
l1{ 1,2,3 }; for (auto it = l1.begin(); it != l1.end(); ++it) { cout << *it << " " ; } cout << endl; list l2(3); auto myIterator = l1.begin(); //作为参数的链表会做出相应的改变 //第一种用法,myIterator指向的元素不变,而不是指向的位置不变,指向位置跟着元素移动 l1.splice(myIterator, l2);//{0,0,0,1,2,3} for (auto it = l1.begin(); it != l1.end(); ++it) { cout << *it << " "; } cout << endl; cout << *myIterator << endl; if (l2.empty()) { cout << "容器为空" << endl; } //第二种用法 list l3; l3.splice(l3.begin(), l1, myIterator);//l3{1} cout << "l1:" << endl; for (auto it = l1.begin(); it != l1.end(); ++it) { cout << *it << " "; } cout << endl; cout << "l3:" << endl; for (auto it = l3.begin(); it != l3.end(); ++it) { cout << *it << " "; } cout << endl; cout << *myIterator << endl; auto pointer = l1.begin(); pointer++; pointer++; pointer++;//因为双向迭代器没有+3,所以只能++3次 l3.splice(l3.end(), l1,pointer, l1.end()); cout << "l1:" << endl; for (auto it = l1.begin(); it != l1.end(); ++it) { cout << *it << " "; } cout << endl; cout << "l3:" << endl; for (auto it = l3.begin(); it != l3.end(); ++it) { cout << *it << " "; } }
2.remove_if()
全局函数: remove_if(iterator begin,iterator end,回调函数)
list成员函数: remove_if(回调函数)//默认查找整体
前两个参数表示迭代的起始位置和对应的停止位置。
其中第三个参数,回调函数返回值为bool类型,如果返回真值,则将当前所指元素移动到容器最后。
remove_if()返回值是被移动到最后面的元素的首位置。
因为只是把要删除的元素移动到最后面,没有实现真正意义上的删除,所以这个函数常常搭配erase()函数使用。
下面是一个例子:
#include#include using namespace std; bool myFunction(int temp) { return {temp==2}; } int main() { list
l1{ 0,0,0,0,0,1,1,2,2 }; list l2{ 0,0,0,0,0,1,1,2,2 }; cout << "l1:" << endl; for (auto it = l1.begin(); it != l1.end(); ++it) { cout << *it << " "; } cout << endl; cout << "l2:" << endl; for (auto it = l2.begin(); it != l2.end(); ++it) { cout << *it << " "; } cout << endl; l1.remove_if(myFunction); cout << "l1:" << endl; for (auto it = l1.begin(); it != l1.end(); ++it) { cout << *it << " "; } cout << endl; l2.erase(remove_if(l2.begin(), l2.end(), myFunction), l2.end()); cout << "l2:" << endl; for (auto it = l2.begin(); it != l2.end(); ++it) { cout << *it << " "; } }
3. unique()函数
void unique() void unique(BinaryPredicate)//传入一个二元谓词函数
#include3. list双向迭代器#include using namespace std; //二元谓词函数 bool demo(double first, double second) { //从头进行遍历,直到满足条件first才会编程 return (int(first) == int(second)); } int main() { list
mylist{ 1,1.2,1.2,3,4,4.5,4.6 }; //删除相邻重复的元素,仅保留一份 mylist.unique();//{1, 1.2, 3, 4, 4.5, 4.6} for (auto it = mylist.begin(); it != mylist.end(); ++it) cout << *it << ' '; cout << endl; //demo 为二元谓词函数,是我们自定义的去重规则 mylist.unique(demo); for (auto it = mylist.begin(); it != mylist.end(); ++it) std::cout << *it << ' '; return 0; }
假设 p1 和 p2 都是双向迭代器,则它们支持使用
- ++p1、 p1++、 p1–、 p1++、 *p1、 p1==p2 以及 p1!=p2 运算符,
但不支持以下操作(其中 i 为整数):
- p1[i]:不能通过下标访问 list 容器中指定位置处的元素。
- p1-=i、 p1+=i、 p1+i 、p1-i:双向迭代器 p1 不支持使用 -=、+=、+、- 运算符。
- p1
p2、 p1<=p2、 p1>=p2:双向迭代器 p1、p2 不支持使用 <、 >、 <=、 >= 比较运算符。
list 容器在进行插入(insert())、接合(splice())等操作时,都不会造成原有的 list 迭代器失效,甚至进行删除操作,而只有指向被删除元素的迭代器失效,其他迭代器不受任何影响。
4. 具体用法代码概览及运行截图#include(8)forward_list(单向链表容器)#include using namespace std; bool myFunction(int temp) { return {temp==2}; } int main() { list
l1{ 0,0,0,0,0,1,1,2,2 }; l1.emplace_front(100);//{ 100,0,0,0,0,0,1,1,2,2 } l1.emplace_back(200);//{ 100,0,0,0,0,0,1,1,2,2,200} for (auto it = l1.begin(); it != l1.end(); ++it) { cout << *it << " "; } cout << endl; l1.remove(0);//{ 100,1,1,2,2,200} for (auto it = l1.begin(); it != l1.end(); ++it) { cout << *it << " "; } cout << endl; l1.erase(remove_if(l1.begin(),l1.end(),myFunction),l1.end());//{100,1,1,200} for (auto it = l1.begin(); it != l1.end(); ++it) { cout << *it << " "; } cout << endl; l1.unique();//{100,1,200} for (auto it = l1.begin(); it != l1.end(); ++it) { cout << *it << " "; } cout << endl; }
因为与list类似,不再介绍。



