建议将vector的模拟实现写在头文件中,测试使用部分写在.cpp文件中
vector是类模板,被封装在命名空间中
部分源码:(删除某些内容后)
vector模拟实现的代码:
#include<assert.h>
namespace djx
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()const
{
return _start;
}
const_iterator end()const
{
return _finish;
}
vector()//一定要写,因为我们已经写了拷贝构造了,编译器不会生成默认的构造函数,当要使用无参的构造时如果我们没写,就没有,会报错
{}
vector(const vector<T>& v)
{
reserve(v.capacity());
for (auto& e : v)
{
push_back(e);
}
}
vector(size_t n, const T& val = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
first++;
}
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
vector<T>& operator=(vector<T> tmp)
{
swap(tmp);
return *this;
}
~vector()
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
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 sz = size();
T* tmp = new T[n];
if (_start)
{
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;
}
}
void push_back(const T& val)
{
/* if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = val;
_finish++;*/
insert(end(), val);//复用insert
}
void resize(size_t n,const T&val=T())
{
if (n <= size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish < _start + n)
{
*_finish = val;
_finish++;
}
}
}
void insert(iterator pos, const T& val)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _endofstorage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
iterator end = _finish-1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = val;
_finish++;
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator begin = pos + 1;
while (begin < _finish)
{
*(begin - 1) = *begin;
begin++;
}
_finish--;
return pos;
}
size_t capacity() const
{
return _endofstorage - _start;
}
size_t size()const
{
return _finish - _start;
}
private:
iterator _start = nullptr;//都会走构造,拷贝构造的初始化列表
iterator _finish = nullptr;//给缺省值,就不用我们在构造,拷贝构造函数的初始化列表给值
iterator _endofstorage = nullptr;
};
}
vector的构造函数:
1 无参的构造
vector()
{}
每个在对象中的成员都要走初始化列表(它们定义的地方),对于内置类型的成员编译器不做处理,所以我们需要手动给值去初始化,但是每个构造函数要是都在初始化列表的地方给这三个成员:_start _finish _endofstorage 初始值,重复写太麻烦了,所以可以直接在成员们声明的地方给缺省值,在它们走初始化列表的时候编译器会用上缺省值去初始化它们
无参的构造即使没有写什么内容也一定要显示出来,否则因为我们写了其他的构造,拷贝构造函数,编译器不再生成默认的构造函数,当要调用无参的构造函数时,又没有就会报错
2 拷贝构造(深拷贝)
vector(const vector<T>& v)
{
reserve(v.capacity());
for (auto& e : v)
{
push_back(e);
}
}
假设我们要用v去拷贝构造生成v2
v2的空间要和v一样大,数据元素要一样,但是各自独立
复用push_back:push_back插入数据分方式是赋值,自定义类型的数据它们的赋值重载函数都是深拷贝,所以完美契合,但是push_back在空间不够的情况下还会自动扩容,若是我们全部使用push_back完成扩容+插入数据,所以会导致最终v2的空间大小和v不一样
解决方法:用我们实现的reserve提前一次性开和v一样大的空间,再复用push_back就仅仅是插入数据了
3 用n个val初始化
vector(size_t n, const T& val = T())
{
reserve(n);//提前开n个空间
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
1 val可以是内置类型如int,double,也可以是自定义类型如string,vector<int>等
val给缺省值:匿名对象调用构造函数初始化,匿名对象再拷贝构造生成val(实现的拷贝构造是深拷贝)
cosnt会延长匿名对象的生命周期,当val销毁,匿名对象生命周期结束
如果给val传参,val就是实参的拷贝,不传参,val就是匿名对象的拷贝(val初始化过了)
2 复用push_back插入数据
push_back插入数据使用赋值,对于自定义类型如string的数据,赋值重载都是深拷贝
push_back负责扩容+插入数据
注意:push_back我们实现的是空间不够就扩至原来的两倍
(若是第一次插入,空间为0,就扩容至4个空间)
所以如果全部依靠push_back插入数据的自动扩容,就会存在空间可能多于n个情况
所以我们一开始就用reserve一次性开n个空间,那么push_back就只有插入数据的作用了
3 内置类型理论上没有默认构造函数 ,但是随着模板的引入,内置类型必须要有,因为模板是针对泛型,所以内置类型其实也有构造函数,实例:
void test()
{
int i = 0;
int j(1);//构造
int k = int(2);//匿名对象先调用构造再拷贝构造k
cout << i << " " << j << " " << k << endl;
}
vector(int n, const T& val = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
重载另一个版本是因为当有以下代码时会报错:
djx::vector<int> v(10, 0);
我们的本意是用10个0去初始化v
上一个版本的n是size_t类型,T可以推导为int类型
但是若是我们没有写这个版本,且存在用迭代器区间初始化的构造函数时,它的两个形参类型都可以推导成int,所以编译器会调用这个构造函数,会报错
当我们写了此版本之后,编译器会调用这个版本,因为只用推导T的类型为int,两个形参类型都是int
4 用一段迭代器区间初始化
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
first++;
}
}
设计成模板,vector<int>存int类型的数据,可以用存储int类型的对象的迭代器去初始化,也可以用string类对象的迭代器区间去初始化
测试:
void test()
{
djx::vector<string> v(10, "xxx");
for (auto e : v)
{
cout << e << endl;
}
djx::vector<int> v2;
v2.push_back(10);
v2.push_back(20);
v2.push_back(30);
v2.push_back(40);
djx::vector<int> v3(v2.begin(), v2.end());
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
string str("hello");
djx::vector<int> v4(str.begin(), str.end());
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
}
vector的析构函数:
~vector()
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
push_back:
void push_back(const T& val)
{
if (_finish == _endofstorage)//扩容
{
reserve(capacity() == 0 ? 4 : capacity() * 2);//第一次插入数据就使用push_back,此时的capacity()==0,括4个,不能是capacity()*2,因为还是0
}
*_finish = val;//赋值
_finish++;
//insert(end(), val);//或者复用insert
}
获取size,capacity:
size_t capacity() const
{
return _endofstorage - _start;
}
size_t size()const
{
return _finish - _start;
}
迭代器:
typedef T* iterator;//非const对象的迭代器
typedef const T* const_iterator;//const对象的迭代器
iterator begin()//非const对象调用
{
return _start;
}
iterator end()//非const对象调用
{
return _finish;
}
const_iterator begin()const//const对象调用
{
return _start;
}
const_iterator end()const//const对象调用
{
return _finish;
}
[]运算符重载:
T& operator[](size_t pos)//非const对象调用 读/写
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const//const对象调用,只读
{
assert(pos < size());
return _start[pos];
}
测试1:
void test1()
{
djx::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
djx::vector<int>::iterator it = v.begin();
while (it != v.end())
{
*it *= 10;
cout << *it << " ";
it++;
}
cout << endl;
for (auto e : v)//有迭代器就可以使用范围for
{
cout << e << " ";
}
cout << endl;
}
resize:
void resize(size_t n,const T&val=T())
{
if (n <= size())//删除效果,保留前n个
{
_finish = _start + n;
}
else
{
reserve(n);//如果要扩容就扩,空间足够就不会扩
while (_finish < _start + n)
{
*_finish = val;
_finish++;
}
}
}
reserve:
void reserve(size_t n)
{
if (n > capacity())//如fesize复用reserve时,n可能<capacity(),空间够,无需扩容
{
size_t sz = size();//保留原空间的数据个数
T* tmp = new T[n];//新开一块空间
if (_start)//原空间有数据,则深拷贝数据,否则不拷贝
{
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];//赋值(深拷贝)
}
delete[] _start;//释放旧空间
}
_start = tmp;//更新
_finish = _start + sz;
_endofstorage = _start + n;
}
}
注意1:reserve开空间因为是重新找一块空间,拷贝数据后释放原空间,所以_start,_finish,_endofstorage全部都要更新
_start=tmp,它指向新空间的起始位置
_finish = _start+size(),原本应该是要这样写的,但是会先调用size(),执行_finish-_start得到原空间的数据个数,但是现在_start已经指向新空间了,旧空间的_finish-新空间的_start是得不到旧空间的数据个数的,所以在_start改变指向之前,要先保留一下旧空间的数据个数size()
注意2:拷贝数据不能使用memcpy,因为是浅拷贝,当vector里面的数据是string类型时,新空间的数据和旧空间的数据之间,分别共用同一块资源空间,释放旧空间,那么这些共享的资源空间也一并释放了
所以可以使用赋值运算符重载,完成深拷贝
测试2:
void test2()
{
djx::vector<int> v;
v.resize(10);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
djx::vector<int*> v2;
v2.resize(10);
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
djx::vector<string> v3;
v3.resize(10,"xxx");
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
}
insert:
void insert(iterator pos, const T& val)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _endofstorage)//检查是否需要扩容
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;//扩容后需要更新pos
}
iterator end = _finish-1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = val;
_finish++;
}
insert涉及迭代器失效的问题,因为insert在插入数据之前要检查是否需要扩容,一旦使用reserve扩容就会释放原有空间,pos是指向原有空间某个数据位置的迭代器,释放原有空间后,pos指向的原有空间的某个位置就不可以在访问了,它是野指针
解决方法:pos在扩容后更新,_start _finish _endofstorage都已经在新空间有所指向,pos也要指向新空间的某个位置
但是pos是传值传参,所以外面的pos并没有被真正改变,所以不可以再次使用外面的pos,它已经失效了
实例:
void test()
{
djx::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
auto it = v.begin() + 2;
v.insert(it,20);//插入第5个数,先扩容,it失效
//v.insert(it, 30);//不可以再次使用it,使用就崩了
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
测试3:
void test3()
{
djx::vector<int> v;
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);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
djx::vector<int>::iterator it = v.begin() + 2;
v.insert(it, 20);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.insert(v.begin() + 3, 30);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
erase:
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator begin = pos + 1;
while (begin < _finish)
{
*(begin - 1) = *begin;
begin++;
}
_finish--;
return pos;//返回删除数据的下一个位置
}
erase之后迭代器pos也失效了,如果迭代器失效就不能再使用这个迭代器,如果使用了,结果是未定义的
如利用erase (没有返回值的erase)删除所有的偶数:
以下代码程序崩溃:
若数组数据是 1 2 3 4 5 :正常
1 2 3 4 5 6 崩溃
2 2 3 4 5 结果不对
void test()
{
djx::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(5);
v.push_back(6);
auto it = v.begin();
while (it != v.end())
{
//vs2019强制检查,erase之后认为it失效了,不能再访问,访问就报错
if (*it % 2 == 0)
{
v.erase(it);
}
it++;
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
解决方案:使用前给it重新赋值
库中的erase有返回值,返回的是删除数据的下一个位置
改进以上删除偶数的代码:
void test()
{
djx::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
it = v.erase(it);
}
else
{
it++;
}
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
测试4:
void test4()
{
djx::vector<int> v;
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);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.erase(v.begin());
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.erase(v.begin() + 3);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
赋值重载:
vector<T>& operator=(vector<T> tmp)
{
swap(tmp);
return *this;
}
tmp是实参拷贝构造生成的,拷贝构造是深拷贝
再用swap交换*this对象和tmp对象,tmp销毁时调用析构函数,释放*this原本的的空间+资源空间
测试5:
void test7()
{
djx::vector<int> v;
v.push_back(1);
v.push_back(1);
v.push_back(1);
v.push_back(1);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
djx::vector<int> v2(v);
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
djx::vector<int> v3;
v3.push_back(10);
v3.push_back(20);
v3.push_back(30);
v3.push_back(40);
v = v3;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}