引言
在string部分,我们详细的介绍了各个接口的使用,虽然其不属于STL的一部分,但是接口与STL中的容器相似,所以我们在学习使用vector等STL容器的使用时,就会简单许多:
戳我康string的使用详解哦
vector介绍
vector是可变大小数组序列容器:
vector的底层是一块动态申请连续的空间,与数组类似,vector可以通过下标高效的访问数组中的元素;同时由于是动态申请的空间,可以根据需求扩容,弥补了数组大小固定的缺陷;
但是,在进行插入时,vector会根据需要自动扩容,这个过程中需要重新申请一块空间并拷贝数据,会十分影响效率。所以库中的vector会在申请空间时预先多申请一些空间,并以2倍或1.5倍的扩容规则来以减少拷贝行为的发生;
vector尾插与尾删的效率非常高,但是其在任意位置插入与删除时,尤其是头插与头删时,就需要挪动数据,效率相对会低很多。所以vector的接口就直接没有支持push_front
与pop_front
。所以vector更适合多次尾插尾删,并且需要经常随机访问其中元素的数据的存储;
vector是一个类模板,可以支持存储任意类型的数据:
接口使用
与string类似,vector也有默认成员函数、迭代器、容量、元素访问、数据修改等接口(使用库vector时需要包含头文件#include<vector>
:
默认成员函数
构造
构造函数部分重载有,无参构造、n个指定元素构造、迭代器区间构造以及拷贝构造:
由于vector是一个类模板,所以在使用vector来实例化对象是,就需要显式指定模板参数,如vector<int>
:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v1; //无参初始化
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
vector<int> v2(10, 6); //使用10个6初始化
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
vector<int> v3(v2.begin(), v2.end()); //使用迭代器区间初始化
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
vector<int> v4(v3); //拷贝构造
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
}
析构
析构函数在该对象生命周期结束时由编译器自动调用
赋值重载
将v2
赋值给v1
:
int main()
{
vector<int> v1;
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
vector<int> v2(10, 6);
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
v1 = v2;
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
迭代器
vector的迭代器用法与string相同,并且他们的底层都是原生指针。我们可以使用迭代器访问容器中的元素,也可以使用范围for遍历容器中的元素。
begin
返回首元素位置的迭代器,end
返回尾元素下一个位置的迭代器、rbegin
返回尾元素的反向迭代器、rend
返回首元素前一个位置的反向迭代器。后面的cbegin
、cend
、crbegin
、crend
都是返回其对应的const迭代器,但是前面的函数都有重载const版本。
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
cout << *v1.begin() << endl;
cout << *(v1.end() - 1) << endl;
cout << *v1.rbegin() << endl;
cout << *(v1.rend() - 1) << endl;
return 0;
}
容量
与string类似,size
返回容器中元素的个数;resize
用于修改容器中元素的个数;capacity
返回容器的容量;empty
判断容器是否为空;reserve
用于修改容器的容量(关于C++11新增的接口先暂时不做介绍):
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
for (auto e : v1)
{
cout << e << " ";
}
cout << "size:" << v1.size() << " ";
cout << "capacity:" << v1.capacity() << " ";
cout << endl;
v1.reserve(25);//使用reserve修改容量
for (auto e : v1)
{
cout << e << " ";
}
cout << "size:" << v1.size() << " ";
cout << "capacity:" << v1.capacity() << " ";
cout << endl;
v1.resize(3);//使用resize修改数据数量
for (auto e : v1)
{
cout << e << " ";
}
cout << "size:" << v1.size() << " ";
cout << "capacity:" << v1.capacity() << " ";
cout << endl;
v1.resize(10, 6);//使用reserve增大数据数量,并使用6补足
for (auto e : v1)
{
cout << e << " ";
}
cout << "size:" << v1.size() << " ";
cout << "capacity:" << v1.capacity() << " ";
cout << endl;
return 0;
}
元素访问
operator[]
与at
都可以通过下标访问对应位置的元素,不同的是:operator[]
当传递的下标越界时,就会崩溃并直接终止程序;当at
传递的下标越界时,会抛异常,可以被try-catch
捕获,而不会导致程序终止。
front
会返回首元素的引用、back
会返回尾元素的引用。
正常访问:
//正常访问时
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
cout << v1[3] << endl;
cout << v1.at(3) << endl;
cout << v1.front() << endl;
cout << v1.back() << endl;
return 0;
}
越界访问:
//越界访问
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
//at越界访问
try
{
cout << v1.at(6) << endl;
}
catch(const exception& e)
{
cout << e.what() << endl;
}
cout << "访问继续" << endl;
//operator[]越界访问
try
{
cout << v1[6] << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
cout << "访问继续" << endl;
return 0;
}
数据修改
对于数据修改,与string类似:
push_back
用于在序列尾插入一个元素;
pop_back
用于在序列尾删除一个元素(由于vector中头插与头删的效率过低,所以不提供接口):
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
v1.pop_back();
v1.pop_back();
v1.pop_back();
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
return 0;
}
insert
用于在序列中的任一位置(迭代器)插入一个元素、n个指定元素或一段迭代器区间中的元素;
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
vector<int> v2;
v2.insert(v2.begin(), 6); //相当于头插6
v2.insert(v2.end(), 7); //相当于尾插7
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
v2.insert(v2.end(), 5, 0); //尾插5个0
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
v2.insert(v2.end(), v1.begin(), v1.end()); //尾插一个v1
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
return 0;
}
erase
用于在序列的任一位置删除一个元素,或删除一段迭代器区间中的元素;
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
v1.erase(v1.begin() + 1); //删除第二个元素
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
v1.erase(v1.begin() + 1, v1.end() - 1); //删除一个迭代器区间
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
return 0;
}
swap
用于交换两个vector中的数据;
clear
用于清理vector中的数据(不改变容量):
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
vector<int> v2(6, 6);
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
//交换两个vector的数据
v1.swap(v2);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
//清除两个vector中的数据
v1.clear();
v2.clear();
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
return 0;
}
迭代器失效问题
我们在使用容器时,常需要通过迭代器来访问元素,他起着指针的作用,本质上也就是原生指针或是指针的封装。我们在使用指针时,如果处理不当,就会遇到访问野指针的错误。对于迭代器也存在着这样的问题,即迭代器失效:
在容器中,对其底层空间的改变通常会导致迭代器失效,例如一些vector接口就有可能会改变其底层的空间:resize
、reserve
、insert
、assign
、push_back
等这些接口都可能会发生扩容而释放原空间,从而使指向原空间的一些指针成为野指针而发生迭代器失效(后面模拟实现后会对此有更好的理解)。访问失效的迭代器,即访问野指针,会导致程序崩溃:
int main()
{
vector<int> v1(6, 6);
vector<int>::iterator it = v1.begin() + 1; //指向第二个元素
v1.reserve(100); //使用reserve将其扩容至100,会发生底层空间的替换
cout << *it << endl; //再次访问这个迭代器就会导致程序崩溃
return 0;
}
所以在使用有可能发生扩容的接口后,就不能再使用之前的迭代器了。
另外,erase
、pop_back
等接口虽然不会替换容器的底层空间,但是可能会使原来的迭代器的指向发生改变,当指向一块未定义的位置时,也会发生失效。
总结
到此,关于vector的介绍以及接口的使用就介绍完了
STL的容器接口在使用时都有很多相似之处,在后面了解到更多的容器后就会体会到
如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出
如果本文对你有帮助,希望一键三连哦
希望与大家共同进步哦