vector类的模拟实现
- 一、经典的vector类问题
- 1.1 前期准备
- 二、vector的默认成员函数
- 2.1 构造函数
- 2.1.1 无参构造
- 2.1.2 构造具有n个对象值为val的容器(数据类型为模板类型T)
- 2.1.3 拷贝构造
- 2.2 swap(operator=需要用)
- 2.3 复制重载operator=
- 2.4 析构函数
- 三、vector的三种遍历方式
- 3.1 size和capacity
- 3.2 operator[]遍历
- 3.3 迭代器iterator遍历和范围for
- 四、vector相关的增容和删除
- 4.1 reserve
- 4.2 resize
- 4.3 push_back
- 4.4 pop_back
- 4.5 insert
- 4.6 erase
- 4.7 empty
- 五、完整代码
- 5.1 vector.h
- 5.2 test.cpp
一、经典的vector类问题
C++ STL中的vector就类似于C语言当中的数组,但是vector又拥有很多数组没有的接口,使用起来更加方便。
相比于STL中的string,vector可以定义不同的数据类型。
1.1 前期准备
迭代器的本质可以暂时看作是指针,模拟实现vector,需要定义三个指针:指向起始位置_start,指向最后一个有效元素的下一个位置_finish,指向容器最后一个位置_end_of_storage
namespace zl { //自定义命名空间template<class T> //定义模板类型class vector{public:typedef T* iterator; //迭代器 等价于 T 类型指针private:iterator _start; //空间起始位置iterator _finish; //最后一个有效元素的下一个位置iterator _end_of_storage; //最后一个容量空间位置};
}
二、vector的默认成员函数
2.1 构造函数
2.1.1 无参构造
构造一个空vector,size和capacity为0,将_start,_finish,_end_of_storage都置为空指针即可
vector():_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{}
2.1.2 构造具有n个对象值为val的容器(数据类型为模板类型T)
vector(size_t n, const T& val = T())
{:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)reserve(n);for (size_t i = 0; i < n; ++i){push_back(val);}
}vector(int n, const T& val = T())
{:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)reserve(n);for (int i = 0; i < n; ++i){push_back(val);}
}
2.1.3 拷贝构造
传统写法
(1)新开辟一块和v同样容量的空间,更新_startt,_finish,_end_of_storage
(2)将v中的数据拷贝到新开辟的空间中
vector(const vector<T>& v)
{:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)//reserve(v.capacity());_start = new T[v.capacity()];//memcpy(_start, v._start, sizeof(T) * v.size());for (size_t i = 0; i < v.size(); ++i){_start[i] = v._start[i];}_finish = _start + v.size();_end_of_storage = _start + v.capacity();
}
现代写法
使用范围for进行遍历,变量e是v中元素的拷贝,如果v中元素是需要深拷贝的自定义类型,会调用拷贝构造函数构造e,从而使e和v中元素所指向的空间不一样(auto& e:v也可以,因为push_back在实现的时候还会调用深拷贝类型的赋值运算符重载)
vector(const vector<T>& v)
{:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)reserve(v.capacity());for (auto e : v){push_back(e);}
}
新写法
vector(const vector<T>& v)
{vector<T> tmp(v.begin(), v.end()); //定义临时对象--调用迭代器构造方法swap(tmp); //进行资源交换
}
2.2 swap(operator=需要用)
调用全局的swap,交换指针,其为内置类型,无需深拷贝,代价相比于直接调用库里面函数交换比较小
void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}
2.3 复制重载operator=
传统写法
(1)释放原空间,新开一块容量和v一样大的空间,更新_start,_finish,_end_of_storage
(2)将v中的数据拷贝到新空间中
vector<T>& operator=(const vector<T>& v)
{if (this != &v){delete[]_start; // 释放原空间_start = new T[v.capacity()]; // 开辟新空间for (size_t i = 0; i < v.size(); i++) // 拷贝数据{_start[i] = v[i];}_finish = _start + v.size(); // 更新_finish_endofstorage = _start + v.capacity(); // 更新_capacity}return *this;
}
现代写法
(1)调用拷贝构造函数生成tmp对象
(2)分别交换tmp和this的_start,_finish,_end_of_storage
vector<T>& operator=(const vector<T>& v)
{if (this != &v) // 防止自己给自己赋值{vector<T> tmp(v); // 拷贝构造tmp对象swap(tmp); // 交换_start,_finish, _endofstorage}return *this;
}
vector<T>& operator=(vector<T> v) // 拷贝构造v对象
{swap(v); // 交换_start,_finish, _endofstoragereturn *this;
}
2.4 析构函数
将空间释放掉,_start,_finish,_end_of_storage置为空指针
~vector()
{delete[] _start;_start = _finish = _end_of_storage = nullptr;
}
三、vector的三种遍历方式
3.1 size和capacity
size返回容器中有效数据的个数,用_finish-_start即可
size_t size() const
{return _finish - _start;
}
capacity返回容器的容量,用_end_of_storage-_start即可
size_t capacity() const
{return _end_of_storage - _start;
}
3.2 operator[]遍历
返回底层数组对应位置的引用即可,需要用const和非const两个版本
T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}const T& operator[](size_t pos) const
{assert(pos < size());return _start[pos];
}
3.3 迭代器iterator遍历和范围for
迭代器也需要写const和非const两个版本,且一个容器只要支持迭代器,就可以用范围for操作,因为范围for本质也是迭代器
iterator begin()
{return _start;
}iterator end()
{return _finish;
}const_iterator begin() const
{return _start;
}const_iterator end() const
{return _finish;
}
四、vector相关的增容和删除
4.1 reserve
(1)当n大于对象当前的capacity时,将capacity扩大到n或大于n
(2)当n小于对象当前的capacity时,什么也不做
增容前就需要算出size的大小
因为增容涉及新和旧空间的问题,比如_finish=tmp+size(),而size的求法是用_finish-_start,但_start已指向新空间,可_finish还是指向旧空间,两块不连续的空间相减就是随机值了,故要在增容之前就把大小算出来,不然增容之后的大小就是随机值了。
reserve需要更新_finish
因为_finish是指针,如果一个对象它上来就reserve保存一定容量,然后直接扩容,但是插入数据肯定涉及*_finish操作,若不更新_finish,_finish初始化为nullptr就非法访问内存了,故reserve中也要更改_finish。
不能使用memcpy拷贝数据
因为增容中使用了memcpy,memcpy导致了问题的出现,因为memcpy是按字节拷贝的,它本质上造成了浅拷贝问题,memcpy使新旧空间都指向了一份数据,旧空间释放后,它指向的对应数据也被释放,而新空间指针还是指向这份旧空间,这就造成了非法访问,所以新空间与旧空间不应该指向同一份数据,应该不用memcopy,写成深拷贝(将旧空间的每个值赋值给新空间对应的每个值就好了,就很实现深拷贝了,指向不同的数据)
void reserve(size_t n)
{if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (_start){//memcpy(tmp, _start, sizeof(T) * size());for (size_t i = 0; i < sz; ++i){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = tmp + sz;_end_of_storage = tmp + n;}
}
4.2 resize
(1)当n<size时,直接将_finish=_start+n(将有效数据长度缩小)即可
(2)当size<n<=capacity时,将有效数据的长度增加到n,增加出来的有效数据内容是val
(3)当n>capacity时,先调用上面的reserve函数进行增容,再将有效数据的长度增加到n,增加出来的有效数据内容是val
void resize(size_t n,T val=T()){if (n < size()){//删除数据_finish = _start + n;}else{if (n > capacity())reserve(n);while (_finish != _start + n){*_finish = val;++_finish;}}}
4.3 push_back
尾插数据,首先要检查是否已满,已满则进行增容,增容后尾插即可
void push_back(const T& x)
{if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;
}
4.4 pop_back
尾插时,首先要判断容器是否为空,若为空,则断言报错,不为空,_finish–即可
void pop_back()
{assert(!empty());--_finish;
}
4.5 insert
(1)容量不够,先增容,增容前先记录pos-_start的值,否则增容之后,pos还指向原来已经被释放的空间
(2)将pos位置往后的数据往后挪一位,在pos位置插入值val
为什么需要提前计算出len
有迭代器失效的问题,因为pos是迭代器类型,扩容前指向旧空间的某一位置,而reserve调用后会扩容,而我们是扩容完才插入数据的,此时pos无效,因为旧空间已经释放了,它这个迭代器还指向那里就失效了,故我们要更新pos位置,使它指向新空间,所以要先算len,即原pos的位置
//迭代器失效:扩容引起,野指针问题
iterator insert(iterator pos, const T& val)
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);//扩容后更新pos,解决pos失效的问题pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = val;++_finish;return pos;
}
4.6 erase
erase是返回被删除数据的下一个位置,当要被删除的数据被删除了,erase原地不动的话,就已自动指向了下一位置,因为那个数据被删除了
iterator erase(iterator pos)
{assert(pos >= _start);assert(pos < _finish);iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;return pos;
}
4.7 empty
判断是否为空
bool empty()
{return _start == _finish;
}
五、完整代码
5.1 vector.h
#pragma once
#include <assert.h>namespace zl
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;vector()/*:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)*/{}vector(size_t n, const T& val = T()){/*:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)*/reserve(n);for (size_t i = 0; i < n; ++i){push_back(val);}}vector(int n, const T& val = T()){/*:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)*/reserve(n);for (int i = 0; i < n; ++i){push_back(val);}}/*vector(const vector<T>& v){reserve(v.capacity());for (auto e : v){push_back(e);}}*///vector(const vector<T>& v)//{// //reserve(v.capacity());// _start = new T[v.capacity()];// //memcpy(_start, v._start, sizeof(T) * v.size());// for (size_t i = 0; i < v.size(); ++i)// {// _start[i] = v._start[i];// }// _finish = _start + v.size();// _end_of_storage = _start + v.capacity();//}vector(const vector<T>& v){vector<T> tmp(v.begin(), v.end());swap(tmp);}//void swap(vector& v)void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}//v1=v2//vector& operator=(vector v)vector<T>& operator=(vector<T> v){swap(v);return *this;}//[first,last)template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}~vector(){delete[] _start;_start = _finish = _end_of_storage = nullptr;}iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}void resize(size_t n,T val=T()){if (n < size()){//删除数据_finish = _start + n;}else{if (n > capacity())reserve(n);while (_finish != _start + n){*_finish = val;++_finish;}}}void reserve(size_t n){if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (_start){//memcpy(tmp, _start, sizeof(T) * size());for (size_t i = 0; i < sz; ++i){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = tmp + sz;_end_of_storage = tmp + n;}}void push_back(const T& x){if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;}void pop_back(){assert(!empty());--_finish;}//迭代器失效:扩容引起,野指针问题iterator insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);//扩容后更新pos,解决pos失效的问题pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = val;++_finish;return pos;}iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;return pos;}size_t capacity() const{return _end_of_storage - _start;}size_t size() const{return _finish - _start;}bool empty(){return _start == _finish;}T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}private:iterator _start= nullptr;iterator _finish= nullptr;iterator _end_of_storage= nullptr;};void func(const vector<int>& v){for (size_t i = 0; i < v.size(); ++i){cout << v[i] << " ";}cout << endl;vector<int>::const_iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;}void test_vector1(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);//v1.push_back(5);func(v1);for (size_t i = 0; i < v1.size(); ++i){cout << v1[i] << " ";}cout << endl;v1.pop_back();v1.pop_back();v1.pop_back();//v1.pop_back();//v1.pop_back();//v1.pop_back();vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";++it;}cout << endl;for (auto e : v1){cout << e << " ";}cout << endl;}//template<class T>//void f()//{// T x = T();// cout << x << endl;//}//void test_vector2()//{// //内置类型有没有构造函数// //int i = int();// //int j = int(1);// int* pi = int* ();// f<int>();// f<int*>();// f<double>();//}void test_vector2(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);cout << v1.size() << endl;cout << v1.capacity() << endl;v1.resize(10);cout << v1.size() << endl;cout << v1.capacity() << endl;func(v1);v1.resize(3);func(v1);}void test_vector3(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);//v1.push_back(5);func(v1);v1.insert(v1.begin(), 0);func(v1);auto pos = find(v1.begin(), v1.end(), 3);if (pos != v1.end()){//v1.insert(pos, 30);pos=v1.insert(pos, 30);}func(v1);//insert以后我们认为pos失效了,不能再使用//(*pos)++;func(v1);}void test_vector4(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);//v1.push_back(5);func(v1);auto pos = find(v1.begin(), v1.end(), 3);if (pos != v1.end()){v1.erase(pos);}//erase以后,pos是否失效//pos失效了,不能访问并且进行强制检查//失效,不要访问,行为结果未定义 根据具体编译器实现有关,在g++中没报错 但是在vs下进行报错//(*pos)++;func(v1);}void test_vector5(){vector<int> v1;v1.push_back(10);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);func(v1);//要求删除所有的偶数vector<int>::iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){it=v1.erase(it);}else{++it;}}func(v1);}void test_vector6(){//vector<int> v1(10u,5); //u表示这个字面量为无符号vector<int> v1(10,5); func(v1);vector<int> v2(v1.begin() + 1, v1.end() - 1);for (auto e : v2){cout << e << " ";}cout << endl;std::string s1("hello");vector<int> v3(s1.begin(), s1.end());for (auto e : v3){cout << e << " ";}cout << endl;int a[] = {30,20,10};vector<int> v4(a,a+3);for (auto e : v4){cout << e << " ";}cout << endl;v1.insert(v1.begin(), 10);for (auto e : v1){cout << e << " ";}cout << endl;sort(v1.begin(), v1.end());for (auto e : v1){cout << e << " ";}cout << endl;for (auto e : a){cout << e << " ";}cout << endl;//sort(a, a + sizeof(a) / sizeof(int)); //默认是升序< 降序>//greater<int> g;//sort(a, a + sizeof(a) / sizeof(int), g);sort(a, a + sizeof(a) / sizeof(int), greater<int> ());sort(a, a + sizeof(a) / sizeof(int)); for (auto e : a){cout << e << " ";}cout << endl;}void test_vector7(){vector<int> v1(10, 5);for (auto e : v1){cout << e << " ";}cout << endl;vector<int> v2(v1);for (auto e : v2){cout << e << " ";}cout << endl;vector<std::string> v3(3, "1111111111111111");for (auto e : v3){cout << e << " ";}cout << endl;vector<std::string> v4(v3);for (auto e : v4){cout << e << " ";}cout << endl;}}
5.2 test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<functional>
#include<string>
#include<vector>
using namespace std;
#include "vector.h"class A
{
public:A(){cout << "A()" << endl;}~A(){cout << "A()" << endl;}};int main()
{/*vector<int>::iterator it;cout << typeid(it).name() << endl;*///zl::test_vector1();//zl::test_vector2();//zl::test_vector3();//zl::test_vector4();//zl::test_vector5();//zl::test_vector6();zl::test_vector7();//A a1;A(); //匿名对象生命周期只在当前一行,因为这行之后没人会用它了//const A& xx = A(); //const引用会延长匿名对象的生命周期到引用对象域结束,因为以后用xx就是用匿名对象//A a2;return 0;
}