目录
vector类实现
1.vector类构造
2.拷贝构造
3.赋值运算符重载
4.析构函数
5.迭代器
6.operator[ ]
7.size( )
8.capacity( )
9.empty( )
10.reserve( )
11.resize( )
12.push_back( )
13.pop_back( )
14.insert( )
15.erase( )
vector结构:
如上图,vector的结构中,包含3个成员变量:
_start:指向vector元素的起始位置
_finish:指向vector元素的结束位置
_end_of_storage:指向vector元素可用空间的位置
为了和库里面的vector区分开,使用命名空间delia将 vector类和库里vector隔离开
namespace delia
{
template//vector里面的元素可能是int,可能是double……所以使用类模板
class vector
{
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
}
}
实现以下vector相关内容:
vector类有3种常见的构造函数
(1)无参构造函数,构造空容器
vector()//将成员变量都初始化成空指针
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
(2)范围构造函数。函数模板InputIterator是输入迭代器类型,并且类型不确定,用first到last之间的元素尾插到容器中
template
vector(InputIterator first, InputIterator last)
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
while (first != last)
{
if (first)
{
push_back(*first);
first++;
}
}
}
//e.g:
//vector v1;
//v1.push_back(0);
//v1.push_back(1);
//v1.push_back(2);
//v1.push_back(3);
//vector v2(v1.begin(),v1.begin()+2); 向v2插入v1的前2个元素
(3) 填充构造函数。向容器中插入n个值为val的元素
vector(size_t n, const T& val)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(n);
while (n)
{
push_back(val);
n--;
}
}
//e.g:
//vector v2(3,5);向v2中插入3个5
但是编译时会提示:
这是因为当使用两个参数进行构造时,编译器会优先匹配构造函数(2),对*first解引用会报错。所以需要再实现两个不同参数类型的(3)的构造函数重载:
vector(int n, const T val)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(n);
while (n)
{
push_back(val);
n--;
}
}
vector(long n, const T val)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(n);
while (n)
{
push_back(val);
n--;
}
}
//e.g:
//vector v2(3,5);向v2中插入3个5
假如不写vector类的拷贝构造函数,那么编译器自动生成的默认拷贝构造函数只能完成浅拷贝,vector类的3个成员变量的类型都是T*,如果T是内置类型,那么拷贝OK;但如果T是自定义类型,那么拷贝对象和被拷贝对象指向同一块空间,后定义的先析构,这块空间会被释放两次,程序就会崩掉。
(1)传统的拷贝构造
//v2(v1)
vector(const vector& v)
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
//1.申请空间
_start = new T[v.capacity()];
//2.拷贝数据
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
//3.更新大小及容量
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
为什么2.拷贝数据时不使用memcpy呢?
memcpy(_start, v._start, sizeof(T) * v.size());
①如果T是内置类型,使用memcpy拷贝完全OK,析构时不需要清理资源;
②如果T是自定义类型,假如T的类型为Stack自定义类型,那么使用memcpy会把v的地址拷贝给this,对于v的每一个元素,对象st2的成员变量_a拷贝的是st1的成员变量_a指针,即把st1的_a指针的值,拷贝给了st2的_a,那么两个指针的值是一样的,st1的_a和st2的_a指向同一块空间:
typedef int STDataType;
class Stack
{
private:
STDataType* _a;
int _size;
int _capacity;
};
int main()
{
Stack st1;
Satck st2(st1);
}
拷贝stack对象
拷贝v中的所有元素:
在析构时,vector的每个元素析构两次,那么同一块空间会被释放两次,程序会崩,因此,使用以下代码直接将vector元素值拷贝过来即可
//2.拷贝数据
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
(2)现代的拷贝构造:开空间+逐个尾插
使用现代的拷贝构造时必须初始化,否则_start、_finish、_end_of_storage都是随机值,拷贝数据时可能会导致越界。如果T是自定义类型,那么会调用T的拷贝构造函数进行深拷贝
vector(const vector& v)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(v.capacity());//开与v一样大小的空间
//逐个尾插
for (auto& e: v)
{
push_back(e);
}
}
(1)传统的赋值运算符重载
vector operator=(vector v)
{
if (this != &v)
{
//1.清理空间,让空间变干净
delete[] _start;
//2.申请空间
_start = new T[v.capacity()];
//3.拷贝数据
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
//4.更新大小及容量
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
}
(2)现代的赋值运算符重载函数
void swap(vector v)
{
::swap(_start, v._start);
::swap(_finish, v._finish);
::swap(_end_of_storage, v._end_of_storage);
}
vector operator=(vector v)
{
swap(v);//直接交换*this和v的内容
return *this;
}
~vector()
{
if (_start)
{
delete[] _start;//释放空间
}
_start = _finish = _end_of_storage = nullptr;//置空
}
(1)普通迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
(2)const迭代器
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
//普通operator[]
T& operator[](size_t i)
{
assert(i < size());//断言i是否合法
return _start[i];
}
//const operator[]
const T& operator[](size_t i) const
{
assert(i < size());
return _start[i];
}
size_t size() const
{
return _finish - _start;//结束位置-起始位置
}
size_t capacity() const
{
return _end_of_storage - _start;//可用空间-起始位置
}
bool empty()
{
return _start == _finish;//起始空间是否为结束空间
}
开空间,扩展_capacity,_size不变
(1)保存对象大小
(2)申请新空间
(3)拷贝字符串
(4)释放旧空间
(6)更新新空间的大小及容量
void reserve(size_t n)
{
size_t sz = size();//1.保存对象的大小
if (n > capacity())
{
T* tmp = new T[n];//2.申请新空间
if (_start)
{
//3.拷贝数据,memcpy是浅拷贝,会把旧空间地址又拷贝过去,不能用memcpy
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
delete[] _start;//4.释放旧空间
}
_start = tmp;//5.指向新空间
//6.更新新空间的大小及容量
_finish = _start + sz;
_end_of_storage = _start + capacity();
}
}
(1)当resize的大小比原来小,说明空间够,只需要修改大小即可
(2)当resize的大小比原来大,说明空间不够,同时也说明容量可能不够,要判断是否需要申请容量
void resize(size_t n, T val = T())
{
//1.当resize的大小比原来小,说明空间够,只需要修改大小即可
if (n < size())
{
_finish = _start + n;
}
//2.当resize的大小比原来大,空间不够
else
{
//是否需要扩容
if (n > capacity)
{
reserve(n);
}
//赋值
while (_finish < _start + n)
{
*_finish = val;
_finish++;
}
}
}
尾插时,需要:
(1)判断增容
(2)赋值
(3)更新大小
void push_back(const T& x)
{
//1.判断是否需要增容
if (_finish == _end_of_storage)
{
size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
}
//2.赋值
*_finish = x;
//3.更新大小
++_finish;
}
尾删:
(1)判空
(2)直接更新大小
void pop_back()
{
assert(!empty());
_finish--;
}
在固定位置插入元素,需要考虑迭代器失效的问题
(1)判断是否需要扩容+保存并更新迭代器(增容时就要先保存pos的位置,扩容后要更新迭代器)
(2) 挪数据
(3)插入数据
(4)更新pos位置
void insert(iterator& pos, const T& x)
{
//1.判断容量是否够用,增容+更新迭代器
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
size_t newCapapcity = capacity()==0 ? 4 : capacity() * 2;
//更新pos,解决增容后pos失效的问题
pos = _start + len;
}
//2.pos位及之后的数据往后挪
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
//3.插入数据
*pos = x;
_finish++;
//4.由于pos位置的元素向后挪动了一位,pos也要向后挪动一位指向原来指向的元素的位置
pos = pos + 1;
}
(1)将pos位置及之后的元素向后挪
(2)更新大小
iterator erase(iterator pos)
{
assert(pos);
//1.将pos位置及之后的元素向后挪
iterator it = pos + 1;
while (it < _finish)
{
*(it - 1) = *it;
it++;
}
//2.更新大小
_finish--;
return pos;
}