实现框架
一、基本的结构雏形
首先我们将所需的成员变量一一罗列出来
//为了避免和库里的vector产生冲突,在自己的命名空间内实现vector namespace vec { template<class T>//通过模板能够实现不同类型的数据存储 class vector { public: typedef T* iterator; /* 各类函数功能实现 */ private: iterator _start; //指向容器中的起始位置 iterator _finish; //指向容器中最后一个有效数据的下一个位置 iterator _end_of_storage; //指向容器中现有容量的位置 }; }
二、默认成员函数
1.构造函数
1.无参构造
对于vector的无参构造,我们只需要将三个成员变量置为空指针即可。
//构造函数 --- 无参构造 vector() //初始化成员列表 :_start(nullptr) , _finish(nullptr) , _end_of_storage(nullptr) {}
2.迭代器区间构造
当我们想要以某个对象的区间来进行初始化时,就需要用到模板了。它既可以是类模板,也可以是函数模板。
例如:
用一个常量字符串来构造vector
const char* p = "hello"; vector<char>v(p, p + 2);
用一个数组来构造vector
int a[5] = { 1,2,3,4,5 }; vector<int>v1(a, a + sizeof(a) / sizeof(a[0]));
用一个string类来构造vector
string s1("hello"); vector<char>v2(s1.begin(), s1.end());
//构造函数 --- 迭代器区间构造 template <class InputIterator>//既是一个类模板的成员函数,又可以是一个函数模板 vector(InputIterator first, InputIterator last) :_start(nullptr) , _finish(nullptr) , _end_of_storage(nullptr) { while (first != last) { push_back(*first);//尾插 ++first; } }
2.拷贝构造
对于拷贝构造函数,因为涉及到深浅拷贝的问题,我们这里提供传统写法与现代写法。
1.传统写法
/*用v1去拷贝构造v2*/ //v2(v1) //传统写法1 vector(const vector<T>& v) { _start = new T[v.capacity()]; //让v2开辟一块和v1一样大小的空间 _finish = _start + v.size(); _end_of_storage = _start + v.capacity(); memcpy(_start, v._start, v.size() * sizeof(T)); } //v2(v1) //传统写法2 vector(const vector<T>& v) { _start = new T[v.capacity()]; //让v2开辟一块和v1一样大小的空间 for (size_t i = 0; i < v.size(); i++) { _start[i] = v[i];//通过循环进行赋值 } //最后调整_finish和_end_of_storage的大小 _finish = _start + v.size(); _end_of_storage = _start + v.capacity(); }
以上两种写法存在样的区别呢?
写法1:依然存在浅拷贝的问题;写法2彻底完成了深拷贝的问题;
从代码上来看,两者的区别在于写法1对于数据的拷贝采用的是memcpy函数,写法2对于数据的拷贝采用的是for循环进行赋值拷贝;两者在拷贝数据的方式上对于内置类型或不需要进行深拷贝的自定义类型,完全是满足要求的;但是当vector存储的是string时,一定存在问题。
vector<string>v1存储了5个数据,每个数据都是string类,vector<string>v2(v1),v2也开辟了5个空间,并且在memcpy下完成拷贝,但是它们却指向了同一块空间,在调用析构函数时,就会导致同一块空间释放多次,最终内存泄露。
对于写法2,它会去调用string类的赋值重载函数进行一个深拷贝
2.现代写法
//v2(v) //现代写法 vector(const vector<T>& v)//也支持vector(const vector& v) :_start(nullptr) , _finish(nullptr) , _end_of_storage(nullptr) { vector<T> tmp(v.begin(), v.end()); //通过迭代器区间对tmp进行拷贝 swap(tmp); //交换v2和tmp的数据 //this->swap(tmp); }
3.赋值运算符重载函数
和拷贝构造一样,使用memcpy时存在浅拷贝的问题
1.传统写法
//v1 = v2 //传统写法 vector<T>& operator=(const vector<T>& v) { if (this != &v) //防止自己给自己赋值 { delete[] _start; //先将v1的空间释放掉 _start = new T[v.capacity()]; //再开辟一块和v2一样大小的空间 for (size_t i = 0; i < v.size(); i++) { _start[i] = v[i];//通过循环进行赋值 } //最后调整_finish和_end_of_storage的大小 _finish = _start + v.size(); _end_of_storage = _start + v.capacity(); } return *this; }
2.现代写法
//v1 = v2 //现代写法 vector<T>& operator=(vector<T> v)//v2传给v,实际上拷贝构造一个和v2一样的v { swap(v); //然后直接交换数据即可 return *this; }
4.析构函数
//析构函数 ~vector() { if (_start)//防止对空指针进行释放 { delete[] _start; _start = _finish = _end_of_storage = nullptr; } }
5.操作符重载函数
const T& operator[](size_t i) const { assert(i < size()); return _start[i]; } T& operator[](size_t i) { assert(i < size()); return _start[i]; }
三、迭代器相关的函数
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; //返回的是容器最后一个数据的下一个位置 }