注:文内代码均在Visual Studio 2013下进行测试,不同的编译器下在扩容大小等方面可能有所不同,但不影响各接口函数的使用。
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
此函数是拷贝构造函数。
begin函数会返回一个正向迭代器,该迭代器指向vector存放的第一个数据的位置。
end函数会返回一个正向迭代器,该迭代器指向vector存放的最后一个数据后的第一个数据的位置。
#include
#include
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函数会返回一个反向迭代器,该迭代器指向vector存放的最后一个数据的位置。
rend函数会返回一个正向迭代器,该迭代器指向vector存放的第一个数据前的第一个数据的位置。
#include
#include
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();
size函数会返回容器所存储的有效数据个数,capacity函数会返回容器的容量。
**注意:**容量和有效数据个数不一定是相等的,比如容器的容量是存储十个数据,但是容器内可能只存储了5个有效数据。但是容器存储的有效数据个数不可能大于容量,那样就是越界存储了。
#include
#include
using namespace std;
int main()
{
vector<int> v(10, 2);
cout << v.size() << endl; //输出容器存储的有效数据个数
cout << v.capacity() << endl; //输出容器的容量
return 0;
}
返回一个bool值,如果容器为空返回真,容器不为空返回假。
vector<int> v;
cout << v.empty() << endl; //返回值为真
reserve函数和resize函数都能给容器扩容,reserve函数只是改变容器容量大小,resize函数不仅改变容量大小,还将容器的有效数据个数和容量保持一致。
#include
#include
using namespace std;
int main()
{
vector<int> v;
v.reserve(10);
cout << v.size() << endl; //输出为0
cout << v.capacity() << endl; //输出为10
return 0;
}
#include
#include
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函数会将容器容量缩小为有效数据个数的大小。
#include
#include
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++不支持将申请的空间里的一部分单独释放,因此缩容的实现是申请一块新的空间,然后将数据拷贝,代价很大,不建议使用。
push_back函数是将数据尾插到容器内,也就是插入到end函数返回的迭代器指向的位置,如果容器容量不够,容器会按照一定规则进行扩容。
#include
#include
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函数使用与迭代器有关,因此需要一个获取指向指定数据的迭代器的函数:find函数。
vector中没有实现find函数,find函数是实现在algorithm库中的,是一个模板实现的通用函数,不止vector,所有STL中的容器都能使用。
#include
#include
#include
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
#include
#include
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
#include
#include
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
#include
#include
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
#include
#include
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
#include
#include
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 //错误示例
#include
#include
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
#include
#include
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函数使用的迭代器在使用后就失效了,需要重新赋值使用。
将容器内数据清空。(实际是将容器有效数据个数修改为0)
#include
#include
using namespace std;
int main()
{
vector<int> v(100, 6);
v.clear();
return 0;
}
前面提到了,由于vector可以像数组一样使用,就是因为重载了[]运算符,不同于数组的是它会对越界访问进行检查。
#include
#include
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;
}