[STL]vector使用介绍
注:文内代码均在Visual Studio 2013下进行测试,不同的编译器下在扩容大小等方面可能有所不同,但不影响各接口函数的使用。
文章目录
- [STL]vector使用介绍
- 1. vector介绍
- 2. 构造函数
- 3. 迭代器相关函数
- begin函数和end函数的使用
- rbegin和rend函数的使用
- 4. 容量相关函数
- size函数和capacity函数
- empty函数
- reserve函数和resize函数 (扩容函数)
- shrink_to_fit函数
- 5. 数据修改函数
- push_back函数
- insert函数和erase函数
- 迭代器失效问题
- clear函数
- 6. 数据访问函数
- []运算符重载
1. vector介绍
- vector是表示可变大小数组的序列容器。
- vector就像数组一样,对其元素使用连续的存储位置,这意味着也可以用下标的方式访问数据,并且与在数组中一样高效。
- vector与数组不同的是,它们的大小可以动态变化,它们的存储由容器自动处理。
- vector使用动态分配的数组来存储其元素,在插入新元素时原有空间不足,会重新分配空间,并将原有数据拷贝至新空间。
- vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因此存储空间比实际需要的存储空间一般更大。不同的库采用不同的策略权衡空间的使用和重新分配,以至于在末尾插入一个元素的时候是在常数的时间复杂度完成的。
- 由于vector采用连续的空间来存储元素,与其他动态序列容器相比,vector在访问元素的时候更加高效,在其末尾添加和删除元素相对高效,而对于不在其末尾进行的删除和插入操作效率则相对较低。
2. 构造函数
vector构造函数声明:
(1)构造函数定义示例:
vector<int> v; //构造一个int类型的空容器
由于这是一个全缺省的函数,因此不需要传入参数(默认构造函数),并且生成的容器是没有申请空间的。
(2)构造函数定义示例:
vector<int> v(10, 1); //构造一个可以存储10个int类型的容器,并且将数据都初始化成1
(3)构造函数定义示例:
vector<int> v1(10, 1);
vector<int> v2(v1.begin(), v1.end()); //构造一个存储int类型空容器v2,然后将v1的数据依次插入
由于是一个模板类型,因此只要迭代器指向的数据可以存入到该vector中都可以使用这个构造函数。比如:
string s("hello world");
vector<int> v(v1.begin(), v1.end()); //由于char类型可以存入int类型数据的空间,因此也是合法
(4)构造函数定义示例:
vector<int> v1(10, 1);
vector<int> v2(v1); //通过v1拷贝构造v2
此函数是拷贝构造函数。
3. 迭代器相关函数
begin函数和end函数的使用
begin函数会返回一个正向迭代器,该迭代器指向vector存放的第一个数据的位置。
end函数会返回一个正向迭代器,该迭代器指向vector存放的最后一个数据后的第一个数据的位置。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(10, 2);
//正向迭代器遍历容器
vector<int>::iterator it = v.begin(); //定义存放int类型数据的vector的正向迭代器it接受begin函数的返回值
while (it != v.end())
{
cout << *it << " "; //使用*操作符获取迭代器指向的数据
it++; //使迭代器指向下一个数据的位置
}
cout << endl;
return 0;
}
另外begin函数和end函数也重载了返回只读正向迭代器的版本,和正向迭代器的区别就是不能通过该迭代器修改指向的数据。
vector<int>::const_iterator cit = v.begin(); //定义存放int类型数据的vector的只读正向迭代器cit接受begin函数的返回值
rbegin和rend函数的使用
rbegin函数会返回一个反向迭代器,该迭代器指向vector存放的最后一个数据的位置。
rend函数会返回一个正向迭代器,该迭代器指向vector存放的第一个数据前的第一个数据的位置。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(10, 2);
//反向迭代器遍历容器
vector<int>::reverse_iterator rit = v.rbegin();
while (rit != v.rend())
{
cout << *rit << " ";
rit++;
}
cout << endl;
return 0;
}
同样的rbegin函数和rend函数也重载了返回只读反向迭代器的版本。
vector<int>::const_reverse_iterator crit = v.rbegin();
4. 容量相关函数
size函数和capacity函数
size函数会返回容器所存储的有效数据个数,capacity函数会返回容器的容量。
**注意:**容量和有效数据个数不一定是相等的,比如容器的容量是存储十个数据,但是容器内可能只存储了5个有效数据。但是容器存储的有效数据个数不可能大于容量,那样就是越界存储了。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(10, 2);
cout << v.size() << endl; //输出容器存储的有效数据个数
cout << v.capacity() << endl; //输出容器的容量
return 0;
}
empty函数
返回一个bool值,如果容器为空返回真,容器不为空返回假。
vector<int> v;
cout << v.empty() << endl; //返回值为真
reserve函数和resize函数 (扩容函数)
reserve函数和resize函数都能给容器扩容,reserve函数只是改变容器容量大小,resize函数不仅改变容量大小,还将容器的有效数据个数和容量保持一致。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
v.reserve(10);
cout << v.size() << endl; //输出为0
cout << v.capacity() << endl; //输出为10
return 0;
}
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
v.resize(10);
cout << v.size() << endl; //输出为10
cout << v.capacity() << endl; //输出为10
return 0;
}
另外如果reserve函数和resize函数传入的参数小于容器容量,两个函数的处理方式也是不同的,reserve函数什么都不做,resize函数会将有效数据个数改为参数大小,容器容量不变。
关于resize函数还需要补充一点:
第一个参数大于有效数据个数时,会将原有有效数据后的所有数据改为第二个参数的值。如果第二个参数未指定,则改用默认构造函数生成的匿名对象。
shrink_to_fit函数
shrink_to_fit函数会将容器容量缩小为有效数据个数的大小。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
v.resize(10);
cout << v.capacity() << endl;
v.shrink_to_fit();
cout << v.capacity() << endl;
return 0;
}
**注意:**C/C++不支持将申请的空间里的一部分单独释放,因此缩容的实现是申请一块新的空间,然后将数据拷贝,代价很大,不建议使用。
5. 数据修改函数
push_back函数
push_back函数是将数据尾插到容器内,也就是插入到end函数返回的迭代器指向的位置,如果容器容量不够,容器会按照一定规则进行扩容。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(4, 1);
cout << v.size() << endl; //输出为4
cout << v.capacity() << endl; //输出为4
v.push_back(5);
cout << v.size() << endl; //输出为5
cout << v.capacity() << endl; //输出为6
return 0;
}
insert函数和erase函数
由于insert函数和erase函数使用与迭代器有关,因此需要一个获取指向指定数据的迭代器的函数:find函数。
vector中没有实现find函数,find函数是实现在algorithm库中的,是一个模板实现的通用函数,不止vector,所有STL中的容器都能使用。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
vector<int>::iterator it = find(v.begin(), v.end(), 2); //获取指向数据2的迭代器
return 0;
}
insert函数可以在迭代器指向的位置插入数据。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
vector<int>::iterator it = find(v.begin(), v.end(), 2);
v.insert(it, 66);
return 0;
}
erase函数能够将迭代器指向的数据删除。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
vector<int>::iterator it = find(v.begin(), v.end(), 2);
v.insert(it, 66);
it = find(v.begin(), v.end(), 66);
v.erase(it);
return 0;
}
迭代器失效问题
insert函数:
情况1:由于容器扩容导致的迭代器指向的数据位置不正确的失效问题。
插入前为了找到正确的插入位置,使用迭代器pos指向插入的位置,由于容器满了,容器扩容到新的空间,而pos指向原有的位置导致pos迭代器失效。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator pos = find(v.begin(), v.end(), 1);
v.insert(pos, 66);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.insert(pos, 6); //扩容使得迭代器pos失效,程序报错
return 0;
}
解决办法:
由于insert函数的返回值是一个迭代器,该迭代器指向插入数据的位置,也就是说如果扩容了会指向新的空间里的插入位置,用pos接受insert函数的返回值即可。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator pos = find(v.begin(), v.end(), 1);
pos = v.insert(pos, 66);//pos接受返回值避免迭代器失效
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.insert(pos, 6);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
return 0;
}
情况2:由于插入后指向不变导致的迭代器指向的数据不正确的失效问题。
插入前为了找到正确的插入位置,使用迭代器pos指向插入的位置,由于插入后pos指向的空间不变,因此pos不再指向开始指向的数据。
解决办法:
重新对pos进行赋值(insert函数的返回的迭代器指向的是插入的数据的位置)。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
vector<int>::iterator pos = find(v.begin(), v.end(), 1);
v.insert(pos, 66);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
pos = find(v.begin(), v.end(), 1);
(*pos)++;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
return 0;
}
**总结:**由于容器的内部实现我们不知道,因此什么情况下扩容,这是不取决于编译器的,也就是不可控的,因此使用完insert我们最好默认pos是失效的,避免代码出现问题。
erase函数
erase函数造成的迭代器失效是因为迭代器指向的位置在删除后不存在有效数据了,导致迭代器指向有效数据外的位置导致迭代器失效。
但是erase函数的问题不止于此,由于不同编译器的STL实现不同,erase函数的使用结果具有不确定性。
我们可以看下一段代码:
#include <iostream>//错误示例
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator pos = find(v.begin(), v.end(), 2);
v.erase(pos);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
*(pos) += 10;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
return 0;
}
首先这段代码在Visual Studio系列的编译器下是绝对报错的,因为Visual Studio使用的STL版本下vector的迭代器使用类封装的,迭代器的使用有着严格的检测,因此在Visual Studio下erase函数使用的迭代器如果不重新赋值,是不能使用的,如果使用时会断言报错的。注意报错的原因是因为使用pos调用了erase函数,然后没重新赋值又对pos使用,不是pos的指向无效数据区才报错。
将这段代码在Linux下进行测试(Linux使用的是g++编译器):
Linux下是不会报错的,并且在利用pos调用了erase函数后,访问pos修改数据都没报错,这与Linux实现迭代器使用的是原生指针有关,Linux的实现下只有迭代器指向无效数据区的访问才报错。总之仅仅是g++和Visual Studio下就发生了不同结果。
解决办法:
由于是因为结果不确定造成的迭代器失效,因此需要在迭代器使用后重新赋值,由于erase函数会返回指向插入位置的迭代器,因此可以选择使用接受返回值来解决。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator pos = find(v.begin(), v.end(), 2);
pos = v.erase(pos);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
*(pos) += 10;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
return 0;
}
**总结:**由于erase函数的运行结果具有不确定性,因此我们必须认为erase函数使用的迭代器在使用后就失效了,需要重新赋值使用。
clear函数
将容器内数据清空。(实际是将容器有效数据个数修改为0)
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(100, 6);
v.clear();
return 0;
}
6. 数据访问函数
[]运算符重载
前面提到了,由于vector可以像数组一样使用,就是因为重载了[]运算符,不同于数组的是它会对越界访问进行检查。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
for (size_t i = 0; i < v.size(); ++i)
{
cout << v[i] << " ";
}
cout << endl; //输出结果为 1 2 3 4
return 0;
}