Hello everybody!这篇文章主要给大家讲讲vector常用接口的模拟实现,STL库中的实现一层套着一层,十分复杂,目前阶段还不适合看源代码。而模拟实现可以让我们从底层上了解这些接口的原理从而更好的使用这些接口。另外我还会讲一些在vector使用过程中特别容易出错的点!
还有就是这篇文章很有难度,类和对象基础不太好的同学可能看不懂,建议回去复习一下构造,拷贝构造和const引用,然后再来看这篇文章会好很多。当然,这些知识我也写过博客,其中描述的很详细,大家也可以去看看!
1.vector常用接口的模拟实现
这些接口的实现细节和难点我都有注释出来,如有疑问可以发在评论区,我会解答。其中print_vector是我自己在类外面写的函数,不是vector中的接口。
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
namespace bit {
template <class T>
class vector {
public:
typedef T* iterator;
typedef const T* const_iterator;
//在深拷贝构造写出来之前先不要写析构,否则在测试时可能会出现某个对象析构两次而崩溃
~vector() {
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
//这是深拷贝构造
vector(const vector<T>& v) {
reserve(v.capacity());//目的是一次性扩容,防止push_back反复扩容对性能消耗过大!
for (auto& e : v) {
push_back(e);
}
}
//这是构造函数,可以用一个容器的迭代区间构造。
template<class InputIterator>//类模板中可以套函数模板
vector(InputIterator first, InputIterator last) {
reserve(last - first);
while (first != last) {
push_back(*first);
first++;
}
}
//这是构造函数,可以用n个T类型的变量去构造。T()是匿名对象 比如vector(),string()。
//如果T是内置类型,比如int,那么int()就是0,char()是'/0',double()是0.0
vector(size_t n, const T& val = T()) {
reserve(n);
while (n--) {
push_back(val);
}
}
//如果没有这个构造函数的话,用vector<int> v(5,8)构造时,
//编译器会匹配vector(InputIterator first, InputIterator last),而不匹配vector(size_t n, const T& val = T())
vector(int n=0, const T& val = T()) {
reserve(n);
while (n--) {
push_back(val);
}
}
//c++11新增了一个类initializer_list<T>,{1,2,3,4}的类型就是initializer_list<int>,
//这样就支持vector<int> v({1,2,3,4})这样的写法。
vector(std::initializer_list<T> il) {
reserve(il.size());
for (auto& e : il) {
push_back(e);
}
}
iterator begin() {
return _start;
}
iterator end() {
return _finish;
}
const_iterator begin() const{
return _start;
}
const_iterator end() const {
return _finish;
}
size_t size() const{
return _finish - _start;
}
size_t capacity() const{
return _endofstorage - _start;
}
T& operator[](size_t pos) {
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const{
assert(pos < size());
return _start[pos];
}
void reserve(size_t n) {
if (n > capacity()) {
size_t old_size = size();
T* tmp = new T[n];
//如果T是string的话,memcpy就是一个浅拷贝,后面delete[] _start会有问题!
//memcpy(tmp, _start, sizeof(T) * size());
for (size_t i = 0; i < old_size; i++) {
//调用各种容器的赋值,这是深拷贝
tmp[i] = _start[i];
}
delete[] _start;
_start = tmp;
_finish = tmp + old_size;
_endofstorage = tmp + n;
}
}
/*形参有引用最好加上const,否则push_back("abcd"); 就传不了,
因为隐式类型转换后,"abcd"转换成string,这是一个临时对象,临时对象具有常性,无法修改。*/
void push_back(const T& val) {
if (_finish == _endofstorage) {
reserve(capacity() == 0 ? 4 : 2 * capacity());
}
*_finish = val;
_finish++;
}
bool empty() const{
return _start == _finish;
}
void pop_back() {
/*assert(!empty());
_finish--;*/
erase(end()-1);//不可以用--end()。因为end()传值返回时返回的是一个临时对象,临时对象不可修改!
}
iterator erase(iterator pos) {
assert(pos >= _start);
assert(pos <= _finish);
iterator it = pos;
while (it<_finish-1) {
*it = *(it+1);
it++;
}
_finish--;
return pos;
}
void insert(iterator pos,const T& val) {
assert(pos <= _finish);
assert(pos >= _start);
if (_finish == _endofstorage) {
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : 2 * capacity());//扩容时一定要注意迭代器失效问题!及时更新pos
pos = _start + len;
}
iterator it = _finish-1;
while (it >= pos) {
*(it + 1) = *it;
it--;
}
*pos = val;
_finish++;
}
void swap(vector<T>& v) {
std::swap(_start, v.begin());
std::swap(_finish, v.end());
std::swap(_endofstorage, v.begin() + v.capacity());
}
vector<T>& operator= (vector<T> v){
swap(v);
return *this;
}
void resize(size_t n,const T& val=T()) {
if (n > size()) {
reserve(n);
while (_finish < _start + n) {
push_back(val);
}
}
else {
_finish = _start + n;
}
}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _endofstorage = nullptr;
};
template<class T>
void print_vector(vector<T> v) {/*在深拷贝没有写出来之前不要写析构函数, 因为这里的传参是浅拷贝,
函数结束自动调用析构,而main函数结束也会调用析构。对堆上同一块空间析构两次程序会崩溃!*/
typename vector<T>::iterator it = v.begin();
/*这里必须要加typename,告诉编译器后面是一个类型,否则编译器无法识别vector<T>::iterator 是类型还是静态成员变量。*/
while (it != v.end()) {
cout << *it << " ";
it++;
}
cout << endl;
for (auto e : v) {
cout << e << " ";
}
cout << endl;
for (size_t i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
cout << endl;
}
}
2.vector常用接口的使用
灵活使用这些接口需要对类和对象中构造,拷贝构造等函数掌握得非常牢固。构造的多种重载形式我在上面实现的时候都有给出,大家可以在上面一一对应。当然也可以去cplusplus网站中去查看vector各种接口的重载形式。
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
using namespace std;
int main() {
string s1("abcdef");
std::string::iterator it1 = s1.begin();
std::string::iterator it2 = s1.end();
/*vector构造的使用。*/
std::vector<int> v1;
std::vector<int> v2(8, 1);
std::vector<string> vv1(6, "1111111");//先用"1111111"构造string,然后在构造vextor<string>。
std::vector<string> v3(2,s1);
std::vector<string> v4;
std::vector<char> v5(s1.begin(),s1.end());
vector<int> v9({ 1,2,3,4 });//c++11支持
/*vector拷贝构造的使用。*/
vector<int> v6(v2);
vector<int> v7 = v2;
vector<int> v10{ 1,2,3,4 };//c++11支持
vector<int> v8 = {1,2,3,4};//c++11支持,这个数组的类型是initializer_list<T>的类型,要用到构造加拷贝构造
int i = 1;
int b = { 1 };//c++11支持
int c{ 1 };//c++11支持
/*operator= 的使用*/
v8 = v6;
v8 = { 1,2,3,4 };//c++11支持,这是构造加赋值
/*算法库中的find。*/
auto pos=find(v8.begin(), v8.end(), 3);//库中给的是函数模板,在一段迭代区间中查找某值。没找到,返回v8.end().
cout << *pos<<v8[2] << endl;
/*insert的使用:有三种用法。在某个位置插入:1.某个值 2.n个值 3.迭代器区间*/
v8.insert(v8.begin(), 8);
v8.insert(v8.begin(), 5, 8);
v8.insert(v8.begin(), it1, it2);//也可以是自己的迭代器区间。
/*erase的使用*/
/*可以删除某个位置(迭代器),也可以删除某个迭代器区间*/
/*max_size:这个接口没啥意义。*/
/*resize的使用*/
v8.resize(100, 1);//该接口不会缩容,
/*1.如果比size小,则缩小size,第二个参数就没用了。
2.如果>size,<capacity,则增加size,增加的元素用第二个参数初始化。
3.>capacity 扩容加初始化。
4.半缺省,第二个参数可省略。*/
/*reserve的使用*/
v8.reserve(10);//很简单,就是扩容。不会缩容。
/*shrink_to_fit的使用*/
v8.resize(10);
v8.shrink_to_fit();//将空间缩容到size大小。
/*emplace与insert用法相同。*/
/*emplace_back与push_back用法相同。*/
return 0;
}
3.使用vector接口极易出现且难以发现的错误
3.1insert引起的迭代器失效
就用我刚才实现的vector来测试:
#include "vector.h"
using namespace bit;
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 it = v.begin();
cout << *it << endl;
v.insert(it, 3);//发生扩容,迭代器(it)失效,需要及时更新。
cout << *it << endl;
return 0;
}
运行结果:
插入一个值后,发生扩容,it指向的原空间被释放掉,it失效。
对于这个问题,STL库中也没有给出很好的解决办法。
所以我们在使用时记得更新迭代器(这要养成习惯。)
更正代码:
#include "vector.h"
using namespace bit;
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 it = v.begin();
cout << *it << endl;
v.insert(it, 3);//发生扩容,迭代器(it)失效,需要及时更新。
it = v.begin();//更新迭代器
cout << *it << endl;
return 0;
}
运行结果:
3.2erase引起的迭代器失效
依然用刚刚实现的vector做测试
场景一:
#include "vector.h"
using namespace bit;
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(4);
v.push_back(5);
vector<int>::iterator it = v.begin();
//删除偶数
while (it != v.end()) {
if (*it % 2 == 0) {
v.erase(it);
}
it++;
}
print_vector(v);
return 0;
}
运行结果:
偶数没有删完整,是因为删完一个整数时,后面的数据往前移,迭代器往后移,导致跳过了某个数据。
场景二:
#include "vector.h"
using namespace bit;
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(4);
v.push_back(5);
v.push_back(4);
vector<int>::iterator it = v.begin();
//删除偶数
while (it != v.end()) {
if (*it % 2 == 0) {
v.erase(it);
}
it++;
}
print_vector(v);
return 0;
}
运行结果:
这次是程序崩溃,原因与场景一类似,v.end()往前移,it往后移,导致错过了结束条件,野指针解引用,程序崩溃!
解决方案一:
只需要多加一个else就可以很好的解决这个问题。但这样写并不是通解,这种写法只能在Linux下跑,因为Linux下的erase不会缩容,迭代器就不会失效。
如果在VS下,用STL库中的vector,这样写也同样有问题!
在VS下,代码:
#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);
v.push_back(4);
v.push_back(5);
v.push_back(4);
vector<int>::iterator it = v.begin();
//删除偶数
while (it != v.end()) {
if (*it % 2 == 0) {
v.erase(it);
}
else {
it++;
}
}
for (auto e : v) {
cout << e << " ";
}
return 0;
}
运行结果:
在VS下这种写法同样会崩溃,因为VS的迭代器有强制检查,在调用erase过后不让使用迭代器(范围for的底层就是迭代器),否则系统会杀掉该程序。
那么VS为什么要这样设计呢?因为不同平台下,STL库的底层是不太一样的,有的平台的erase不会缩容,有的平台的erase可能会缩容,一旦erase发生了缩容,迭代器指向的空间被释放掉,迭代器就失效了,所以VS规定在erase过后不能使用迭代器!
那么怎么解决这个问题呢?
同样的,还是需要及时更新迭代器。
通过查STL库我们发现,erase有一个返回值,是一个迭代器,它指向刚刚删除元素的下一个位置。换句话说就是库中的erase帮帮我们更新了迭代器,只要我们能利用上这个返回值,就能通过VS的强制检查,程序就可以跑起来啦!
erase引起的迭代器失效的通解代码:
#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);
v.push_back(4);
v.push_back(5);
v.push_back(4);
vector<int>::iterator it = v.begin();
//删除偶数
while (it != v.end()) {
if (*it % 2 == 0) {
it=v.erase(it);
}
else {
it++;
}
}
for (auto e : v) {
cout << e << " ";
}
return 0;
}
运行结果: