目录
一、vector的介绍
二、vector的常用接口
1.构造函数和赋值重载
1.1构造函数
1.2赋值重载
2.析构函数
3.迭代器相关操作函数
3.1 begin()
3.2 end()
3.3 rbegin()
3.4 rend()
4.容器元素个数和容量操作函数
4.1 size()
4.2 resize()
4.3 capacity()
4.4 reserve()
4.5 empty()
5.获取容器元素的函数
5.1 operator[]()
5.2 at()
6.修改容器的函数
6.1 insert()
6.2 erase()
6.3 push_back()
6.4 pop_back()
6.5 swap()
三、总结
一、vector的介绍
vector是STL的八大容器之一,是表示大小可变数组的序列容器,与数据结构中的动态顺序顺序表本质相同。但是vector是用C++的类模板来实现的,封装了很多常用的功能函数,如构造函数、begin()、end()等函数,在平常的使用中可以简化代码,方便我们更好的解决问题。
1. vector是表示可变大小数组的序列容器。
2.vector也采用的连续存储空间来存储元素。可以采用下标对vector的元素进行访问,和数组一样高效。它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,如果容量不够,会异地扩容。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务。
4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配(例如:vs和g++编译器的扩容策略就不相同)。
5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6. 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低(需要移动数组部分元素)。
二、vector的常用接口
1.构造函数和赋值重载
1.1构造函数
vector类重载了多个构造函数,结合上图进行说明:
注:上图函数参数类型中,const allocator_type& alloc = allocator_type()是内存池的缺省参数(内存池简单来说就是一块提前申请好的内存,在使用空间时不用再去内存申请空间,可以节省时间);size_type就是我们常用的size_t,即unsigned int类型;value_type是模板类型,value_type()是匿名对象,调用默认构造函数初始化。
(1)无参构造函数:构造一个容量为空的vector容器。
(2)半缺省构造函数:构造一个size等于n的vector容器,容器的每个元素初始化为val。
(3)构造一个元素个数等于[first,last)区间内元素个数,元素值等于[first,last)区间内对应元素值的vector容器。
(4)拷贝构造函数:将vector容器x拷贝一份,构造一个新vector容器。
具体调用如下:
void VectorTest1()
{vector<int> v1; //(1)vector<int> v2(10, 2); //(2)string s = "hello world!"; vector<int> v3(s.begin(), s.end()); //(3)vector<int> v4(v2); //(4)
}
1.2赋值重载
赋值重载在结果上和拷贝构造相同,都是得到一个与参数对象相同的容器;区别在于:拷贝构造在容器定义时调用,相当于容器初始化;赋值重载是对已经定义好了的容器进行拷贝赋值,相当于容器赋值。
具体调用如下:
void VectorTest2()
{vector<int> v1(10, 2);vector<int> v2; //默认构造v2 = v1; //赋值重载
}
2.析构函数
构造函数是构造类对象,析构函数则是释放对象资源(堆区上的资源)。
构造函数不需要显式调用,程序结束时编译器会自动调用。
3.迭代器相关操作函数
3.1 begin()
begin()函数对于STL容器来说含义几乎相同,都是返回指向首元素的迭代器。
3.2 end()
end()函数对于STL容器来说含义几乎相同,都是返回指向最后一个元素的下一个位置的迭代器。
begin()和end()的具体调用如下:
void VectorTest3()
{vector<int> v1;v1.push_back(1);v1.push_back(3);v1.push_back(5);v1.push_back(7);v1.push_back(9);vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";++it;}cout << endl;//范围for遍历容器数组for (auto e : v1){cout << e << " ";}cout << endl;
}
注:由于vector支持迭代器,所以也可以用范围for来实现容器数组的遍历(只能顺序遍历)。
3.3 rbegin()
3.4 rend()
rbegin()和rend()本质与begin()和end()相似,不同的是它们是逆序的;rbegin()返回的是指向最后一个元素的迭代器,rend()返回的是指向首元素的前一个位置的迭代器,且返回的迭代器是反向迭代器;反向迭代器的方向与迭代器相反,即进行++操作时是向容器的头部(元素下标更小的方向)移动。
rbegin()和rend()的具体调用如下:
void VectorTest4()
{vector<int> v1;v1.push_back(2);v1.push_back(4);v1.push_back(6);v1.push_back(8);v1.push_back(10);vector<int>::reverse_iterator it = v1.rbegin();while (it != v1.rend()){cout << *it << " ";++it;}cout << endl;//范围for遍历容器数组for (auto e : v1){cout << e << " ";}cout << endl;
}
注:因为第一次打印是反向迭代器的运用,第二次打印使用的范围for(只能顺序打印),所以打印的顺序是完全相反的。
4.容器元素个数和容量操作函数
4.1 size()
返回vector容器的元素个数。
4.2 resize()
修改vector容器的元素个数为n,当元素个数相较于原本的更大时,增加的元素全部初始化为value_type(),即调用默认构造来进行初始化(此场景下,内置类型也有默认构造)。
4.3 capacity()
返回vector容器的容量大小。
4.4 reserve()
修改容器的容量大小为n,当n大于原本的容量大小时,进行异地扩容的操作。因为在调用push_back()和insert()函数进行元素插入时,容器的容量可能不够,此时编译器会进行异地扩容的操作,导致时间消耗大。因此,当你知道你待完成任务要使用的容器的最大容量时,可以调用reserve()函数提前扩容,以避免多次扩容带来的消耗,来提高性能。
4.5 empty()
判断容器是否为空(即容器的元素个数即size是否为0)。
上述函数的具体调用:
void VectorTest5()
{vector<int> v;cout << v.empty() << endl;//判空cout << v.size() << endl;cout << v.capacity() << endl;v.resize(10);v.reserve(15);cout << v.size() << endl;cout << v.capacity() << endl;//范围for遍历容器数组for (auto e : v){cout << e << " ";}cout << endl;v.push_back(2);v.push_back(4);v.push_back(6);v.push_back(8);v.push_back(10);//范围for遍历容器数组for (auto e : v){cout << e << " ";}cout << endl;cout << v.capacity() << endl;cout << v.empty() << endl;//判空
}
5.获取容器元素的函数
5.1 operator[]()
重载运算符[],因为vector容器是动态数组,是一块连续的存储空间,因此可以通过下标来访问元素。函数返回下标的对应容器元素(第一个可读可写,第二个可读不可写)。
具体调用如下:
void VectorTest6()
{vector<int> v;v.reserve(10);v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);v.push_back(7);v.push_back(8);v.push_back(9);v.push_back(10);for (size_t i = 0;i < v.size();i++)//for (size_t i = 0;i <= v.size();i++) //程序中断{cout << v[i] << " ";v[i] *= 2;}cout << endl;for (auto e : v){cout << e << " ";}cout << endl;
}
当以上程序中第一个for循环中的控制部分改为i <=v. size()时,因为越界访问,程序运行失败。
5.2 at()
函数功能与上述[]运算符重载相同,也是通过下标获得容器元素。
具体调用如下:
void VectorTest7()
{vector<int> v;v.reserve(10);v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);v.push_back(7);v.push_back(8);v.push_back(9);v.push_back(10);for (size_t i = 0;i < v.size();i++)//for (size_t i = 0;i <= v.size();i++) //程序中断{cout << v.at(i) << " ";v.at(i) *= 2;}cout << endl;//范围for遍历数组for (auto e : v){cout << e << " ";}cout << endl;}
当以上程序中第一个for循环中的控制部分改为i <=v. size()时,因为越界访问,程序运行失败。
综上,operator[]和at()都能通过下标对容器数组元素进行访问和修改(const修饰的函数不能修改数组元素),唯一不同的就是对越界访问时的处理方式不同(最终结果都是程序中断,但是operator[]的更果断直接)。
6.修改容器的函数
6.1 insert()
在positon指向的位置插入新元素(一个或多个),插入时如果由于容量不够而导致编译器调用reserve()异地扩容的话,positon会因为指向非法空间而导致迭代器失效;如果没扩容,由于position的含义已经发生改变,仍然默认迭代器失效。故,不论insert函数是否在内部调用了reserve()函数进行扩容,只要调用了insert()函数进行元素插入,就一定会牵涉到迭代器失效的问题,(底层实现使得我们使用失效的迭代器时会直接报错,如下是使用失效迭代器时的调试信息)。
唯一的解决办法就是改变迭代器的值,即通过赋值操作赋予它新的含义。(底层实现是直接将其变为空指针,使我们无法使用)。
注意:插入一个元素时,insert()函数返回类型是迭代器,其他两个重载的成员函数的返回类型是void。
测试代码:
void VectorTest8()
{vector<int> v;v.reserve(10);v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);cout << v.size() << endl;cout << v.capacity() << endl;vector<int>::iterator it = v.begin() + 2;v.insert(it, 2);//该insert()函数可以接受返回值,也可以不接收;用it接收可以解决迭代器失效的问题//it = v.insert(it, 2);//以下两行对迭代器it的使用都会导致程序异常退出,迭代器失效后不能使用//(*it)++;//it++;//v.insert(it,10,2);//也会导致迭代器失效,且该insert()函数的返回值是void,不能通过接受返回值来更新迭代器it的值从而解决迭代器失效的问题//可以把迭代器失效的问题和指针联想成相似的东西://迭代器失效相当于指针被置为nullptr,此时不能使用它进行诸如解引用之类的操作//解决办法就是赋予其一个新的有效的值(更新迭代器)for (auto e : v){cout << e << " ";}cout << endl;}
使用失效迭代器代码的调试结果如下:
6.2 erase()
erase()函数的不同重载版本的返回值都是迭代器,返回值是指向被删除元素的下一个位置。由于可能删除了容器中最后的所有元素,返回的迭代器指向end()指向的下一个位置,容易导致越界访问,需要特别注意。其次,第一个erase()重载函数,函数参数是一个迭代器形参,删除元素后,该迭代器失效,无法使用(可以通过赋新值来解决失效问题,同insert()函数)。
代码测试:
void VectorTest9()
{vector<int> v;v.reserve(10);v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);cout << v.size() << endl;cout << v.capacity() << endl;vector<int>::iterator it = v.begin() + 2;it = v.erase(it);//返回指向被删除元素的下一个元素,如果没有用it接收返回值,it是失效的迭代器//it = v.erase(v.begin() + 3, v.end());//it返回end()的下一个位置,下面的*it会越界访问,导致程序运行中断(*it)++;for (auto e : v){cout << e << " ";}cout << endl;
}
综上,迭代器失效的类型有:
a.由于插入元素导致元素整体后移(尾插一视同仁),从而导致指向原空间的迭代器不再有效(插入元素前进行扩容操作的话,迭代器指向的空间是非法的,也会导致迭代器失效)
b.由于删除元素导致元素整体前移(尾删一视同仁),迭代器不再指向想指向的元素,从而导致迭代器失效
即迭代器在内存重新分配时将失效(它所指向的元素在该操作后会发生变化)。
(注意:以上是vs上调用的库是这么规定的,linux使用的不是同一个库,与迭代器相关的规定有所不同)
6.3 push_back()
尾插,相当于insert(v.end())。
6.4 pop_back()
尾删,相当于erase(v.end()-1)。尾删不会改变容器的容量capacity。
功能测试代码:
void VectorTest10()
{vector<int> v;v.reserve(5);v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);cout << v.size() << endl;cout << v.capacity() << endl;for (auto e : v){cout << e << " ";}cout << endl;v.pop_back();v.pop_back();cout << v.size() << endl;cout << v.capacity() << endl;for (auto e : v){cout << e << " ";}cout << endl;
}
6.5 swap()
交换两个容器的内容(包括容器的元素和容器个数以及容量大小),vector的接口函数swap()函数的效率比直接调用std::swap()函数来交换两个vector容器要高得多(前者是交换指针的指向,后者是调用拷贝构造+赋值重载来实现,还需要产生中间容器)。
模拟实现vector::swap():
void swap(vector<T>& v)
{std::swap(_start, v._start);//_start指向容器数组的首元素std::swap(_finish, v._finish);//_finish指向容器的最后一个容器的下一个位置std::swap(_end_of_storage, v._end_of_storage);//_end_of_storage指向容器装满时最后一个元 素的下一个位置
}
6.6 clear()
清空容器,容量大小不变。
功能测试:
void VectorTest11()
{vector<int> v1;vector<int> v2;cout << "交换前:" << endl;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);v1.push_back(100);for (auto e : v1){cout << e << " ";}cout << endl;v2.push_back(2);v2.push_back(4);v2.push_back(6);v2.push_back(8);v2.push_back(10);for (auto e : v2){cout << e << " ";}cout << endl;v1.swap( v2);//vector的接口函数swap的形参只有一个,std::swap()有两个形参cout << "交换后:" << endl;for (auto e : v1){cout << e << " ";}cout << endl;for (auto e : v2){cout << e << " ";}cout << endl;v1.clear();cout << v1.size() << endl;cout << v1.capacity() << endl;v2.clear();cout << v2.size() << endl;cout << v2.capacity() << endl;}
三、总结
vector是STL的八大容器之一,内部数据结构是动态数组,支持元素的随机访问(通过下标访问)。相比用C语言实现顺序表而言,这里很好的体现了类封装的优势,可以在类内定义函数,对外提供函数接口,方便用户调用常用功能函数来进行平时的代码设计和实现,提高效率。
在本篇博客中,在测试各个常用函数接口的函数功能时都是用的vector<int>,即容器元素为int类型的vector容器,得益于模板,vector容器可以实例化成各种类型的vector容器(容器的元素可以是内置类型和自定义类型,自定义类型自然也包括各大容器本身,在解题时有时候会巧妙的运用到,主要是考察我们对容器的理解和运用)。
vector容器的模拟实现可以加深我们对其的理解,大家感兴趣的可以自己尝试,我就不再多做说明。如果本文有陈述不正确的地方,欢迎各位斧正!