文章目录
标准模板库简介
C++标准模板库(Standard Template Library,STL)是一个标准化的模板化的对象容器库,包含多种数据结构和算法,STL的核心包括以下三个组件:
- 容器类:容器类是管理序列的类,是容纳一组对象或对象集的类。通过容器类提供的成员函数,可以实现诸如向序列中插入元素,删除元素,查找元素等操作,这些成员函数通过返回迭代器来指定元素在容器中的位置
- 迭代器:迭代器是面向对象版本的指针,它可以用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集
- 算法:算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作
这三个组件都带有丰富的预定义函数,帮助我们通过简单的方式处理复杂的任务
容器类
容器类分为三大类:顺序容器、关联容器和容器适配器
其中顺序容器与关联容器称为第一类容器。另外有四种容器称为近容器:C语言风格数组、字符串string、操作1/0标志值的bitset和进行高速数学矢量运算的valarray,近容器虽然提供与第一类容器类似的功能,但没有第一类容器的全部功能
STL为容器提供类似的接口,许多基本操作是所有容器都适用的
所有标准容器库共有的函数有如下:
只在第一类容器中的函数有如下:
vector容器
参考:https://zh.cppreference.com/w/cpp/container/vector
vector(向量)是一个封装了动态大小数组的顺序容器(Sequence Container)。跟任意其它类型容器一样,它能够存放各种类型的对象。可以简单的认为,向量是一个能够存放任意类型的动态数组。
vector具有如下特点:
- vector是表示可以改变大小的数组的序列容器
- vector与数组一样,元素使用连续的存储空间,就可以使用常规指针,指向其元素,使用偏移量来访问存储空间中的元素
- vector与数组不同的是,vector的大小可以动态变化,容器会自动扩容存储空间
- vector使用一个动态分配的连续存储空间存储元素。在插入新元素时存储空间可能需要重新分配,以便增大大小,这意味着分配一个新存储空间要将所有元素移动到其中。就处理时间而言,这是一项相对昂贵的任务,因此,向量在扩容时有时会预留一定大小的存储空间,这样vector不必每次向容器中添加元素都重新分配,即vector容器可以额外分配一些存储空间以适应可能的增长,因此容器的实际容量可能大于严格需要的存储容量
- vector与C数组相比,向量消耗更多的内存,以换取管理存储和高效动态增长的能力
- 与其他动态序列容器(deques, list和forward_list)相比,vector可以相对高效地对元素进行访问、删除和尾部添加,但对于在末尾以外的位置插入或删除元素,其性能较差
vector的使用方式如下:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ar = {12, 23, 34, 45, 56};
vector<int>::iterator it = ar.begin();
for(;it != ar.end();++it)
{
cout<<*it<<endl;
}
return 0;
}
vector也可以同C数组那样去访问元素,如下:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ar = {12, 23, 34, 45, 56};
int n = ar.size();
for(int i = 0; i< n; ++i)
{
cout<<arr[i]<<endl;//也可以:cout<<ar.at(i)<<endl;
}
return 0;
}
vector的构造函数
所以在使用vector的时候,有如下多种初始化方式:
#include <vector>
using namespace std;
int main()
{
int arr[] = {1,2,3,4,5,6};
int n = sizeof(arr)/sizeof(arr[0]);
vector<int> veca;
vector<int> vecb(10);//给vecb10个整型的初始值
vector<int> vecc(10,23);//给vecb 10个23
vector<int> vecd(arr,arr+n);//用ar到ar+n地址之间的数据去初始化vecd
vector<int> vecf = {12,2,3,43,4,6};//C11标准
vector<int> vecg {12,2,3,43,4,6};
}
vector元素的访问
访问元素示例:
#include <vector>
#include <iostream>
using namespace std;
int main()
{
vector<int> vec = {0,1,2,3,4,5,6 };
int a = vec.at(1); //访问下标为1的元素
int b = vec[1];//访问下标为1的元素
int c = vec.front();//访问第一个元素
int d = vec.back();//访问最后一个元素
int* e = vec.data();//返回指向容器中第一个元素的指针
return 0;
}
vector迭代器
迭代函数 | 功能 |
---|---|
iterator begin() | 返回指向起始的迭代器 |
iterator end() | 返回指向末尾的迭代器 |
reverse_iterator rbegin() | 返回指向起始的逆向迭代器 |
reverse_iterator rend() | 返回指向末尾的逆向迭代器 |
正向迭代器例:
逆向迭代器例:
正向迭代器与逆向迭代器都分为普通迭代器与常性迭代器,普通迭代器不但可以读值也可以改值,而常性迭代器就只可以读取容器里元素的值但是不能改变容器里元素的值
迭代器有越界检查,一旦越界就会抛出异常,但C数组里指针并没有这个特性,所以迭代器比指针更安全
迭代器失效问题
对于如下代码:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vec;
for(int i = 0; i<10; i++)
{
vec.push_back(i);
}
vector<int>::iterator it = vec.begin();
cout<<*it<<endl;
vec.insert(it,100);//it位置前插入100
cout<<*it<<endl;
}
第二个cout会输出失败,因为在插入元素过后,迭代器it就失效了。如下图,在插入元素的时候,可能原来空间不够用,vector会新开一个空间来存储元素,但是it迭代器仍旧迭代的是旧的那个空间,所以it迭代器已然失效,输出它迭代的内容就会失败
注意:插入数据的时候,容器空间可能够用,系统并未开空间,但是迭代器仍然失效,因为用户并不清除插入数据的时候系统有没有增空间,为了安全性,只要插入数据或调用了push_back或pop_back,迭代器就会失效,所以在使用完这几个函数之后,要更新迭代器
vector容量
容量函数 | 功能 |
---|---|
bool empty() const | 检查容器是否为空 |
size_type size() const | 返回容纳的元素数 |
size_type max_size() const | 返回可容纳的最大元素数 |
void reserve(size_type new_cap) | 预留存储空间 |
void capacity() cosnt | 返回当前容器的容量 |
void shrink_to_fit() | 通过释放未使用的内存减少内存的开销 |
vector容器可以额外分配一些存储空间以适应可能的增长,因此容器的实际容量可能大于严格需要的存储容量, 如下代码:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vec;
for (int i = 0; i < 10; i++)
{
vec.push_back(i + 10);
cout <<"size: "<< vec.size() << "\t" <<"capacity: "<< vec.capacity() << endl;
}
return 0;
}
运行结果中,容量的值因为会预留存储空间所以会大于等于当前元素个数
关于max_size函数,如下代码,分别计算出对于int型和double型的可容纳的最大元素个数:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> ivec; vector<double> dvec; cout<<"int: "<<ivec.max_size()<<endl; cout<<"double: "<<dvec.max_size()<<endl; }
max_size函数返回的结果其实是STL认为堆区的可分配存储空间的大小除以元素类型的大小,STL认为堆区有4G空间可分配(其实达不到4G),4G/4B = 1073741824, 4G/8B = 536870912
其实给C进程的虚拟地址空间有4G,堆区只占一部分(2G左右),示意图如下:
vector修改器
clear函数只清楚容器里的元素,并不会改变容器的容量,如下代码:
#include <vector>
#include <iostream>
using namespace std;
int main()
{
vector<int> vec;
for (int i = 0; i < 100; i++)
{
vec.push_back(i);
}
cout << "size: " << vec.size() <<"\t"<< "capacity: " << vec.capacity() << endl<<endl;
vec.clear();
cout << "After clear:" << endl<<endl;
cout << "size: " << vec.size() << "\t"<<"capacity: " << vec.capacity() << endl<<endl;
}
运行结果:clear过后,元素个数为0了,容量不变
但是对于shrink_to_fit函数,它会压缩空间至适当尺寸,容器的容量也会跟着减少,例如下代码
#include <vector>
#include <iostream>
using namespace std;
int main()
{
vector<int> vec;
for (int i = 0; i < 100; i++)
{
vec.push_back(i);
}
cout << "size: " << vec.size() <<"\t"<< "capacity: " << vec.capacity() << endl<<endl;
vec.shrink_to_fit();
cout << "After shrink_to_fit:" << endl << endl;
cout << "size: " << vec.size() << "\t" << "capacity: " << vec.capacity() << endl << endl;
}
运行结果如下:
shrink_to_fit会回收容器多余的空间,使得容器容量与元素个数相匹配
注意:过多地调用shrink_to_fit会产生多的内存碎片
insert函数的使用:
insert函数例:
#include <vector>
#include <iostream>
using namespace std;
template<class T>
void ForwardPrint(const vector<T> & vec)//打印vec里的元素
{
cout << "size: " << vec.size() << "\t" << "capacity: " << vec.capacity() << endl;
typename vector<int>::const_iterator it = vec.begin();
for (; it != vec.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
int main()
{
vector<int> vec = { 1,2,3,4,5,6 };
vector<int>::iterator it = vec.begin();
vec.insert(it, 10);//it位置前插入10
//vec.insert(it,10,23); //it位置前插入10个23
//vec.insert(it,ar,ar+n);//it位置前插入数组ar到ar+n的元素
//vec.insert(it,{2,3,5,7,8,212,4});//it位置前插入给出的列表
ForwardPrint(vec);
}
erase函数的使用:
vector容器成员类型
vector定义二维数组:vector<vector<int>> arr;
vector中reserve,resize,assign的区别
reserve
void reserve(size_type n);
reserve函数用来给vector预分配存储区大小,即capacity的值,但是没有给这段内存进行初始化。reserve的参数n是推荐预分配内存的大小,实际分配的可能等于或大于这个值。
当调用函数时,n的值如果大于capacity的值,就会重新分配内存,使得容量大于n,这样,当调用push_back函数使得size超过原来默认分配的capacity值时避免了内存重分配开销
需要注意的是:reserve函数分配出来的内存空间未初始化对象,只是表示vector可以利用这部分空间,但vector不能有效访问这些内存空间,如果访问就会出现越界现象,导致程序崩溃
resize
void resize(const size_type Newsize);
void resize(const size_type Newsize, const T & val);
resize函数重新分配大小,并且创建对象。
当Newsize小于当前size()值的时候,vector首先会减少size()值保存前Newsize个元素,然后将超出Newsize的元素删除(remove and destory),但是容器容量并未减少,仍是原来的容量,如下图:
当Newsize大于当前size()值的时候,vector会插入相应数量的元素使得size()的值达到Newsize,并对这些元素进行初始化,如果调用上面的第二个resize函数,指定val,vector会用val去初始化这些新插入的元素
当Newsize大于capacity()值的时候,会自动重新分配内存存储空间
assign
void assign(size_type n, const T & value);
void assign(Input first, Input last);
assign函数是将n个值为value的元素赋值到vector容器中,或者将first到last区间的元素赋值到当前vector容器中,这个容器会清除掉vector中以前的内容
这三个函数都有增加容量的能力,但没有减少容量的能力,只有shrink_to_fit才有减少容量的能力
函数 | 功能 |
---|---|
reserve | 预分配空间,未初始化 |
resize | 重新调整空间大小,会初始化空间 |
assign | 清除掉以前内容,放置新内容 |