前言
经过前面string的学习,我们已经掌握了许多string的类函数,vector中许多类函数与string中的类函数使用起来相似,例如迭代器的使用在所有的容器中使用都一样,这里我们不再介绍,下面我们学习一些vector类的一些常用的函数。
1.vector的文档介绍
2. vector在C++中表示可变大小数组的序列容器,使用时需要包含头文件 < vector > ,就像数组一样,vector也采用的连续存储空间来存储元素。
vector的使用
一、 vector的构造函数二、容量相关的函数vector 空间增长问题在的讨论 三、vector 数据的访问四、vector的增删查改1、assign函数2、insert函数3、erase函数4、swap函数5、clear函数6、find函数 五、vector 迭代器失效问题
一、 vector的构造函数
vector的构造函数主要有四个,下面我们来一 一演示
第一个是无参的默认构造,第二个是我们可以用n个val去初始化vector,第三个也就是vector的拷贝构造,第四个是使用迭代器进行初始化构造。
int main(){vector<int> v1;v1.push_back(1); //push_back的作用就是插入一个元素v1.push_back(2);v1.push_back(3);v1.push_back(4);vector<int> v2(4); //用4个匿名对象进行初始化vector<int> v3(4, 9); //用4个9进行初始化vector<int> v4(++v1.begin(), --v1.end());//用迭代器进行进行初始化vector<int> v5(v3); //拷贝构造return 0;}
观察监视:
二、容量相关的函数
我们先来看看前三个:
int main(){vector<int> v1(10, 2);cout << "size:" << v1.size() << endl;cout << "capacity:" << v1.capacity() << endl;cout << "empty:" << v1.empty() << endl;return 0;}
然后我们先来看reserve
函数,reserve函数的作用就是开辟新的空间改变capacity
,但是不会改变size
。
reserve
函数中的 n
小于原始的容量时,此函数什么也不做。当reserve
函数中的n
大于原始的容量时,就会扩大容量,注意里面原有的数据不做改变。 我们来看下面的一段代码:
int main(){vector<int> v1(10, 2);v1.reserve(5);v1.reserve(15);return 0;}
观察监视窗口我们会发现,执行reserve(5)时函数什么都没有做,执行reserve(15)时函数才会进行扩容。
reserve
只负责开辟空间,如果确定知道需要用多少空间,reserve
可以缓解vector增容的代价缺陷问题。
下面我们再来看看resize
函数,resize
函数不仅会改变capacity
也会改变size
。
如果resize
的参数n
小于原始的size
,那么就会保留前n
个,后面的数据将会被销毁,但是capacity
不变。
如果resise
的参数n
大于原始的size
,就会用第二个参数进行初始化size
后面的空间,直到size == capacity
如果resise
的参数n
大于原始的capacity
,就会进行扩容并初始化未使用的空间。
我们来看下面一段代码:
int main(){vector<int> v1(10, 2);v1.resize(5);v1.resize(15,7);return 0;}
vector 空间增长问题在的讨论
关于vector空间增长机制我们可以用下面的代码来进行测试。
void TestVectorExpand(){size_t sz;vector<int> v;//记录第一次的capacity的值sz = v.capacity();cout << "making v grow:\n";for (int i = 0; i < 100; ++i){//挨个插入100个元素v.push_back(i);//如果容量发生了变化,就重新更新sz的数据,并打印szif (sz != v.capacity()){sz = v.capacity();cout << "capacity changed: " << sz << '\n';}}}
我们先来看一看windows
下的扩容机制:
我们会发现windows
平台下是1.5
倍扩容!
我们再来看看Linux
平台下的空间增长:
我们发现是2
倍扩容。
原因是这两个平台采用的STL版本不同,vs是PJ版本STL,g++是SGI版本STL。
三、vector 数据的访问
代码演示:
int main(){vector<int> v1;v1.push_back(0);v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);cout << v1[2] << endl;cout << v1.at(2) << endl;cout << v1.front() << endl;cout << v1.back() << endl;int* pi = v1.data();pi[3] = 5;cout << pi[3] << endl;return 0;}
四、vector的增删查改
1、assign函数
第一个函数的用一个迭代器区间进行赋值,第二个函数是用n
个元素来进行赋值。
int main(){vector<int> v1;v1.push_back(0);v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);vector<int>::iterator it1 = v1.begin();while (it1 != v1.end()){cout << *it1;++it1;}cout << endl;vector<int> v2(2, 7);vector<int>::iterator it2 = v2.begin();while (it2 != v2.end()){cout << *it2 ;++it2;}cout << endl;//普通赋值v2.assign(3, 2);for (auto& e : v2){cout << e ;}cout << endl;//用迭代器区间赋值v2.assign(v1.begin(), v1.end());for (auto& e : v2){cout << e;}cout << endl;return 0;}
2、insert函数
第一个函数是用1
个元素来进行插入pos位置之前,第二个函数是用n
个元素插入pos位置之前
int main(){vector<int> v1;v1.push_back(0);v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.pop_back();for (auto& e : v1){cout << e;}cout << endl;v1.insert(++v1.begin(), 2, 7);for (auto& e : v1){cout << e;}cout << endl;return 0;}
3、erase函数
第一个函数是删除pos迭代器位置。第二个是删除迭代器区间的所有元素
int main(){vector<int> v1;v1.push_back(0);v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto& e : v1){cout << e;}cout << endl;v1.erase(++v1.begin(), --v1.end());for (auto& e : v1){cout << e;}cout << endl;return 0;}
4、swap函数
int main(){vector<int> v1;v1.push_back(0);v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto& e : v1){cout << e;}cout << endl;vector<int> v2(9, 7);v1.swap(v2);for (auto& e : v1){cout << e;}cout << endl;return 0;}
5、clear函数
int main(){vector<int> v1;v1.push_back(0);v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto& e : v1){cout << e;}cout << endl;v1.clear();cout << "size:" << v1.size() << endl;cout << "capacity :" << v1.capacity() << endl;return 0;}
6、find函数
find函数并不是vector里面的成员函数,而是C++里面的一个算法库里面的函数,其作用就是帮我们寻找我们想要的数据。
函数参数:两个迭代器,确定一个一个区间,最后面的参数是我们想要查找的元素。
返回值:当找到该元素后,返回该位置的迭代器,如果找不到,返回last
迭代器。
(注意:所有的迭代器区间都是左闭右开的,所以返回last迭代器代表找不到)
int main(){vector<int> v1;v1.push_back(0);v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);//删除元素 2vector<int>::iterator it = find(v1.begin(), v1.end(), 2);v1.erase(it);for (auto& e : v1){cout << e;}return 0;}
五、vector 迭代器失效问题
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际上就是一个指针,或者是对指针进行了封装。
迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果可能未定义的或者是程序崩溃
但是由于vs
与g++
采用的STL版本分别是PJ版本和SGI版本,因此对于迭代器的失效问题,在两个版本中的表现也不太相同。
迭代器的失效主要有两种方式:
由于删除元素而导致的迭代器失效。由于容量改变而导致的迭代器失效我们先来看下面由于删除元素而导致的迭代器失效:
int main(){vector<int> v1{ 1,2,3,4,5 };vector<int>::iterator it = v1.begin();//删除it指向的元素v1.erase(it);//再对it指向的元素进行自增(*it)++;for (auto& e : v1){cout << e ;}cout << endl;return 0;}
erase
删除it
位置元素后,it
位置之后的元素会往前搬移,但是我们再对it
进行访问已经不合理了,因为最初时我们是想让it
指向元素 1
,元素1
消失了,我们也不应该再对it
进行访问了。
上面的代码在vs下的PJ版本会直接报错,而在g++下的SGI版本会访问下一个元素2
。
vs下执行(*it)++
时会直接报错:
g++下执行(*it)++
时会直接访问后面移动到该位置上的元素,从而导致了导致2
变成了3
:
看到这里你可能觉得这里的迭代器好像也可以看成不失效啊,g++下还能通过it
直接访问元素也是合理的啊,但是当我们上面的代码删除的是最后一个元素5
,g++下还执行(*it)++
时就发生了越界行为!
因此对于这个it
如果我们还想使用我们就要对迭代器进行重新赋值,这也是解决迭代器失效的通用的方法。
下面我们再来看另外一种由于容量改变而导致的迭代器失效:
int main(){vector<int> v1{ 1,2,3,4 };//it是未扩容之前的迭代器vector<int>::iterator it = v1.begin();v1.resize(50,0);//v1.begin()是扩容后的迭代器while (it != v1.end()){cout << *it << " ";++it;}cout << endl;return 0;}
vs下执行时会直接报错:
g++下执行时也会执行错误:
发生了许多越界访问。
这里迭代器失效的原因:是我们对v1
进行了扩容,而it
是指向没有扩容之前的开始位置的迭代器,扩容以后内存地址发生了变化,而it
没有及时更新。我们还使用以前的迭代器就有可能造成许多越界行为。
对于第二种迭代器失效,只要会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。
因此同时使用可能引起其底层空间改变的函数和迭代器时要注意迭代器的更新!!