STL序列式容器


整体文章目录: Standard Template Library基础知识
序列容器,即以线性排列(类似普通数组的存储方式)来存储某一指定类型(例如 int、double 等)的数据,需要特殊说明的是,该类容器并不会自动对存储的元素按照值的大小进行排序。

(1) 容器类型

容器类型说明
array<T,N>(数组容器)表示可以存储 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<T> 和 queue<T> 本质上也属于序列容器,只不过它们都是在 deque 容器的
基础上改头换面而成,通常更习惯称它们为容器适配器。

图 1 说明了可供使用的序列容器以及它们之间的区别。

(2) 图解容器类型

在这里插入图片描述

(3)容器包含的函数

表 2 展示了 array、vector 和 deque 容器的函数成员,它们中至少有两个容器实现了同样的函数成员。(列表中 - 表明对应的容器并没有定义这个函数。)
在这里插入图片描述
在这里插入图片描述

(4) array(数组容器)

array 容器就是在 C++ 普通数组的基础上,添加了一些成员函数和全局函数。代码展示统一放到最后面

1. array初始化

#include<array>//array容器以类模板的形式定义在 <array> 头文件
using namesapce std;//并位于命名空间 std 中
//下面是array初始的三种方式
//1.第一种创建具有 10 个 double 类型元素的 array 容器
array<double, 10> values;
//2.将所有的元素初始化为 0 或者和默认元素类型等效的值:
array<double,10> values{};//double被初始化0.0
//3.对元素进行初始化
array<double, 10> values {0.5,1.0,1.5,,2.0};//其余元素初始化为0.0

2. array成员函数

在这里插入图片描述
另外,在<array>头文件中还重载了 get() 全局函数,该重载函数的功能是访问容器中指定的元素,并返回该元素的引用。它是一个辅助函数,能够获取到容器的第 n 个元素。需要注意的是,该模板函数中,参数的实参必须是一个在编译时可以确定的常量表达式,所以它不能是一个循环变量。

3. array随机访问迭代器

在这里插入图片描述
在这里插入图片描述
这些成员函数通常是成对使用的,即 begin()/end()、rbegin()/rend()、cbegin()/cend()、crbegin()/crend() 各自成对搭配使用。

4. 具体用法代码概览及运行截图

#include <iostream>
#include<array>
using namespace std;
int main()
{
    array<int, 10> nums{};//初始化为0
    array<int, 10> 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<<endl;
    for (int i = 0; i < nums.size(); ++i) {
        cout << nums.at(i)<<" ";
    }
    cout << endl;
    // 下面是几种访问元素的方式
    cout << "nums[0] is " << nums[0] << endl;
    cout << "nums[1] is " << nums.at(1) << endl;
    cout << "nums[2] is " << get<2>(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;

}

在这里插入图片描述

(5)vector(向量容器)

array 实现的是静态数组(容量固定的数组),而 vector 实现的是一个动态数组,即可以进行元素的插入和删除,在此过程中,vector 会动态调整所占用的内存空间,整个过程无需人工干预。
🔥该容器擅长在尾部插入或删除元素🔥
vector不是存储bool类型元素的vector容器
在实际场景中需要使用 vector 这样的存储结构,可以选择使用 deque
deque 容器几乎具有 vecotr 容器全部的功能(拥有的成员方法也仅差 reserve() 和 capacity()),
而且更重要的是,deque 容器可以正常存储 bool 类型元素。

1. vector初始化

#include <iostream>
#include<vector>
using namespace std;
int main()
{
    //下面是几种初始化方法
    //1.
    vector<int>nums1;//成功创建一个空的vector容器。因为容器中没有元素,所以没有为其分配空间。
    //当添加第一个元素(比如使用 push_back() 函数)时,vector 会自动分配内存。4
    nums1.reserve(10);//通过调用 reserve() 成员函数来增加容器的容量。该函数细节在函数成员节讲解
     //2.
    vector<int>nums2{ 10,20,30,40,50 }; //在创建的同时指定初始值以及元素个数
    //3.
    vector<int>nums3(10); //指定元素个数,元素值是默认元素类型等效的值
    /*注意:圆括号() 和大括号 {} 是有区别的,前者(例如(10) )表示元素的个数
    而后者(例如 {20} ) 则表示 vector 容器中只有一个元素 20。*/
    //4.
    vector<int>nums4(10,2);//不想用 0 作为默认值,也可以指定一个其它值
    //圆括号() 中的 2 个参数,既可以是常量,也可以用变量来表示
    //5.
    int tempCapacity = 10; int tempValue = 2;
    vector<int>nums5(tempCapacity, tempValue);
    //6.
    //通过存储元素类型相同的其它 vector 容器,也可以创建新的 vector 容器
    vector<int>nums6(nums5);
    //在此基础上,如果不想复制其它容器中所有的元素,可以用一对指针或者迭代器来指定初始值的范围
    int array[] = { 1,2,3 };
    std::vector<int>values(array, array + 2);//values 将保存{1,2}
    std::vector<int>value1{ 1,2,3,4,5 };
    std::vector<int>value2(std::begin(value1), std::begin(value1) + 3);//value2保存{1,2,3}
    //由此,value2 容器中就包含了 {1,2,3} 这 3 个元素。
}

2. vector成员函数

在这里插入图片描述

  1. vector 容器还有一个 std::swap(x , y) 非成员函数(其中 x 和 y 是存储相同类型元素的 vector 容器),它和 swap() 成员函数的功能完全相同,仅使用语法上有差异。
  2. 注意,如果 vector 的容量在执行reserve()之前,已经大于或等于 指定参数 个元素,那么这条语句什么也不做;另外,调用 reserve() 不会影响已存储的元素,也不会生成任何元素,即 values 容器内此时仍然没有任何元素。
    还需注意的是,如果调用 reserve() 来增加容器容量,之前创建好的任何迭代器(例如开始迭代器和结束迭代器)都可能会失效,这是因为,为了增加容器的容量,vector 容器的元素可能已经被复制或移到了新的内存地址。所以后续再使用这些迭代器时,最好重新生成一下。
  3. emplace_back() 和 push_back() 的区别,就在于底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
  4. insert()
    在这里插入图片描述
#include <iostream> 
#include <vector> 
#include <array> 
using namespace std;
int main()
{
    std::vector<int> 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<int,3>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;
}
  1. emplace() 每次只能插入一个元素,而不是多个。 emplace() 在插入元素时,是在容器的指定位置直接构造元素,而不是先单独生成,再将其复制(或移动)到容器中。
    std::vector<int> demo1{1,2};
    //emplace() 每次只能插入一个 int 类型元素
    demo1.emplace(demo1.begin(), 3);//{3,1,2}
  1. 删除元素的方法
    在这里插入图片描述
//删除元素{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 容器在申请更多内存的同时,容器中的所有元素可能会被复制或移动到新的内存地址,这会导致之前创建的迭代器失效。

4. 具体用法代码概览及运行截图

#include <iostream>
#include<vector>
#include<array>
#include<algorithm>
using namespace std;
int main()
{
    vector<int>nums1{ 1,2,3,4,5 };
    //几种访问元素的方式
    for (int i = 0; i < nums1.size(); ++i) {
        cout << nums1.at(i)<<" ";
    }
    cout << endl;
    for (vector<int>::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<int, 1> 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<int>::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;
}

在这里插入图片描述

(6)deque(双端队列容器)

1. deque初始化

deque 容器中存储元素并不能保证所有元素都存储到连续的内存空间中。
🔥当需要向序列两端频繁的添加或删除元素时,应首选 deque 容器。🔥
初始化和vector容器一样

2. deque成员函数

在这里插入图片描述

  1. insert()函数与vector类似

3. deque随机访问迭代器

在这里插入图片描述
在这里插入图片描述
首先需要注意的一点是,迭代器的功能是遍历容器,在遍历的同时可以访问(甚至修改)容器中的元素,但迭代器不能用来初始化空的 deque 容器。
除此之外,当向 deque 容器添加元素时,deque 容器会申请更多的内存空间,同时其包含的所有元素可能会被复制或移动到新的内存地址(原来占用的内存会释放),这会导致之前创建的迭代器失效。

4. 具体用法代码概览及运行截图

#include <iostream>
#include<deque>
#include<vector>
using namespace std;
int main()
{
	deque<int> 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<int> 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;
	}
}

在这里插入图片描述

(7)list(双向链表容器)

list 容器中的元素可以分散存储在内存空间里,而不是必须存储在一整块连续的内存空间中。

在这里插入图片描述
🔥实际场景中,如何需要对序列进行大量添加或删除元素的操作,而直接访问元素的需求却很少,这种情况建议使用 list 容器存储序列。🔥

1. list初始化

初始化和其他容器大体相似

2. list成员函数

在这里插入图片描述

  1. splice()用法在这里插入图片描述
    splice() 成员方法移动元素的方式是,将存储该元素的节点从 list 容器底层的链表中摘除,然后再链接到当前 list 容器底层的链表中。这意味着,当使用 splice() 成员方法将 x 容器中的元素添加到当前容器的同时,该元素会从 x 容器中删除。
#include<iostream>
#include<list>
using namespace std;
int main() {
	list<int> l1{ 1,2,3 };
	for (auto it = l1.begin(); it != l1.end(); ++it) {
		cout << *it << " " ;
	}
	cout << endl;
	list<int> 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<int>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;
	/*虽然1不在l1里面了,但是myIterator仍然指向1,但是不能进行其他操作,否则
	就会报错,因为指向不清了*/
	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<iostream>
#include<list>
using namespace std;
bool myFunction(int temp) {
	return {temp==2};
}
int main() {
	list<int> l1{ 0,0,0,0,0,1,1,2,2 };
	list<int> 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)//传入一个二元谓词函数
#include <iostream>
#include <list>
using namespace std;
//二元谓词函数
bool demo(double first, double second)
{
    //从头进行遍历,直到满足条件first才会编程
    return (int(first) == int(second));
}

int main()
{
    list<double> 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;
}

3. list双向迭代器

假设 p1 和 p2 都是双向迭代器,则它们支持使用

  • ++p1、 p1++、 p1–、 p1++、 *p1、 p1==p2 以及 p1!=p2 运算符,

但不支持以下操作(其中 i 为整数):

  1. p1[i]:不能通过下标访问 list 容器中指定位置处的元素。
  2. p1-=i、 p1+=i、 p1+i 、p1-i:双向迭代器 p1 不支持使用 -=、+=、+、- 运算符。
  3. p1<p2、 p1>p2、 p1<=p2、 p1>=p2:双向迭代器 p1、p2 不支持使用 <、 >、 <=、 >= 比较运算符。

list 容器在进行插入(insert())、接合(splice())等操作时,都不会造成原有的 list 迭代器失效,甚至进行删除操作,而只有指向被删除元素的迭代器失效,其他迭代器不受任何影响。

4. 具体用法代码概览及运行截图

#include<iostream>
#include<list>
using namespace std;
bool myFunction(int temp) {
	return {temp==2};
}
int main() {
	list<int> 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;
}

在这里插入图片描述

(8)forward_list(单向链表容器)

因为与list类似,不再介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

撑一把纸伞.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值