1.构造函数
1.初始化值为val的n个对象。( vector<int>(4,3); )
//初始化值为val的n个对象
vector(size_t n, const T& val=T())
{
reserve(n);
size_t i = 0;
for (i; i < n; i++)
{
push_back(val);
}
}
2.根据迭代器区间初始化 (vector<int> v1(v.begin(),v.end() )
//范围构造 写成模板函数为了支持任意迭代器初始化 (注意 非法的参数匹配)eg. vector<int> v(3,3) 都是int 优先匹配模板int
template<typename InpultIterator>
vector(InpultIterator first, InpultIterator last)
{
assert((last - first)>0);
reserve(last - first);
while (first != last)
{
push_back(*first);
first++;
}
}
当我们写完这两个构造函数,让我们来测试测试第一个构造函数。
可以看到这里并没有调用第一个构造函数,而是调用了这个用模板函数实现的构造函数。
这是因为 vector(size_t n, const T& val = T()) 第一个参数并不是int。
而模板函数可以推导出生成参数都为int的函数,更适合v(4,1)传参。
也就是发生了非法的参数匹配。
对于这个问题我们可以这样传参v(4u,1) 第一个参数类型为size_t
或者再写一个函数重载vector(int n, const T& val = T())
//初始化值为val的n个对象
vector(size_t n, const T& val = T())
{
reserve(n);
size_t i = 0;
for (i; i < n; i++)
{
push_back(val);
}
}
vector(int n, const T& val = T())
{
reserve(n);
size_t i = 0;
for (i; i < n; i++)
{
push_back(val);
}
}
3.根据 initializer_list 模板类初始化 (vector<int>{1,2,3})
vector(initializer_list<T> li)
{
reserve(li.size());
for (auto i : li)
{
push_back(i);
}
}
initializer_list是一个特殊的模板类,用于表示一个初始化列表。
C++11会将 { } 括起来的内容识别为常量数组,然后构造成 initializer_list.
因为是常量数组,里面的内容不能修改。类型也要保持一致
有迭代器,能够支持范围for
2.拷贝构造函数
vector(const vector<T>& v)
{
reserve(v.capacity);
size_t j = 0;
for (auto i : v)
{
*(_start + j) = i;
j++;
}
_finish = _start + j;
}
注意要进行深拷贝,不能用memcpy进行拷贝,按字节拷贝是浅拷贝。
3.=运算符重载
我们通过传值传参的方式,拷贝赋给形参v。然后通过swap函数交换this与形参指向的空间。
void swap(const vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
vector<T>& operator=(const vector<T> v)
{
swap(v);
return *this;
}
4.析构函数
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage=nullptr;
}
}
5.size() capacity()
两个指针变量相减返回的是相距的元素个数。
typedef T* iterator;
typedef const T* const_iterator;
size_t size()
{
return _finish - _start;
}
size_t capacity()
{
return _end_of_storage - _start;
}
6.reserve 扩容
扩容的实现是先找一块足够大的空间,再拷贝原数据,最后销毁原空间。
如果我们不先记录oldsize元素个数,扩容后_start _finish就会变成野指针(迭代器失效),
size()不能返回正确的元素个数。
void reserve(size_t n)
{
if (n > capacity())
{
size_t oldsize = _finish - _start;//_start改变后 无法算出元素个数
T* tmp = new T[n];
if (_start)
{
// memcpy(tmp, _start, size() * sizeof(T));//浅拷贝原数据
for (size_t i = 0; i < size(); i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = tmp + oldsize;
_end_of_storage = tmp + n;
}
}
7.[]运算符重载
T& operator[](size_t i)
{
assert(i >= 0 && i < size());
return _start[i];
}
const T& operator[](size_t i) const
{
assert(i >= 0 && i < size());
return _start[i];
}
8.push_back pop_back
void push_back(const T& val)
{
if(_finish==_end_of_storage)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_finish = val;
_finish++;
}
void pop_back()
{
assert(size() != 0);
_finish--;
}
9.insert
这里插入函数的实现也会发生迭代器失效的问题。
扩容后_start _finish都指向了新空间,而pos仍指向原空间。再根据pos插入时,pos就相当于一个野指针。
我们可以先记录pos和_start的相对位置,等扩容_sart改变后,根据原先的相对距离算出pos现在应该指向的位置。
注意:这里pos是用传值传参,在该函数中pos的改变并不影响main函数中pos的值。(因为迭代器没有引用返回,所以传pos引用变量也是不行的)
如果想改变main函数中pos值,我们可以让函数返回pos改变后的值并让main中的pos接收。
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);
size_t oldpos = pos - _start;//扩容 pos仍指向原空间 迭代器失效
if (_finish == _end_of_storage)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
pos = _start + oldpos;//只改变了形参 main函数中pos没变
}
iterator it1 = end();
while (it1 !=pos)
{
*(it1) = *(it1 - 1);
it1--;
}
*(it1) = x;
_finish++;
return pos;
}
10.erase
erase:一个迭代器,指定在任何删除的元素之后剩余的第一个元素,如果不存在这样的元素,则指定指向向量结尾的指针。
同样erase中也存在迭代器失效。
1.erase后编译器可能会进行缩容,导致pos变成野指针。
2.pos指向的是最后一个元素,删除后出现越界。
因为不能确定pos迭代器不会失效,所以C++中规定只要erase(pos) 认为pos迭代器失效。
如果再对pos解引用或者++ ,都是不行的。为了继续使用我们只需要pos=erase(pos) 更新迭代器,指向被删除元素的下一个元素。
iterator erase(iterator pos)
{
assert(_start!=_finish);
assert(pos >= _start && pos < _finish);
size_t len = pos - _start;
iterator it1 = pos+1;
while (it1 != _finish)
{
*(it1 - 1) = *(it1);
it1++;
}
_finish--;
//if (pos == _finish) return nullptr;
return _start + len;//返回删除位置的下一个位置
}