STL之vector模拟实现

vector类实现

vector结构:
在这里插入图片描述
如上图,vector的结构中,包含3个成员变量:
_start:指向vector元素的起始位置
_finish:指向vector元素的结束位置
_end_of_storage:指向vector元素可用空间的位置
为了和库里面的vector区分开,使用命名空间delia将 vector类和库里vector隔离开

namespace delia
{
    template<class T>//vector里面的元素可能是int,可能是double……所以使用类模板
	class vector
	{
    private:
        iterator _start;
		iterator _finish;
		iterator _end_of_storage;
    }
}

实现以下vector相关内容:
在这里插入图片描述

1.vector类构造

vector类有3种常见的构造函数
1、无参构造函数,构造空容器

        vector()//将成员变量都初始化成空指针
	        :_start(nullptr)
	        , _finish(nullptr)
	        , _end_of_storage(nullptr)
	    {}

2、范围构造函数。函数模板InputIterator是输入迭代器类型,并且类型不确定,用first到last之间的元素尾插到容器中

    
		template<class InputIterator>
		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<int> v1;
        //v1.push_back(0);
        //v1.push_back(1);
        //v1.push_back(2);
        //v1.push_back(3);
    
        //vector<int> 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<int> 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<int> v2(3,5);向v2中插入3个5

2.拷贝构造

假如不写vector类的拷贝构造函数,那么编译器自动生成的默认拷贝构造函数只能完成浅拷贝,vector类的3个成员变量的类型都是T*,如果T是内置类型,那么拷贝OK;但如果T是自定义类型,那么拷贝对象和被拷贝对象指向同一块空间,后定义的先析构,这块空间会被释放两次,程序就会崩掉
1)传统的拷贝构造

        //v2(v1)
		vector(const vector<T>& 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<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(v.capacity());//开与v一样大小的空间
 
			//逐个尾插
			for (auto& e: v)
			{
				push_back(e);
			}
		}

3.赋值运算符重载

(1)传统的赋值运算符重载

        vector<T> operator=(vector<T> 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<T> v)
		{
			::swap(_start, v._start);
			::swap(_finish, v._finish);
			::swap(_end_of_storage, v._end_of_storage);
		}        
 
        vector<T> operator=(vector<T> v)
		{
			swap(v);//直接交换*this和v的内容
			return *this;
		}

4.析构函数

        ~vector()
		{
			if (_start)
			{
				delete[] _start;//释放空间
			}
			_start = _finish = _end_of_storage = nullptr;//置空
		}

5.迭代器

(1)普通迭代器

        iterator begin()
		{
			return _start;
		}
 
		iterator end()
		{
			return _finish;
		}

(2)const迭代器

		const_iterator begin() const
		{
			return _start;
		}
 
		const_iterator end() const
		{
			return _finish;
		}

6.operator[ ]

        //普通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];
		}

7.size( )

	size_t size() const
	{
		return _finish - _start;//结束位置-起始位置
	}

8.capacity( )

		size_t capacity() const
		{
			return _end_of_storage - _start;//可用空间-起始位置
		}

9.empty( )

        bool empty()
		{
			return _start == _finish;//起始空间是否为结束空间
		}

10.reserve( )

开空间,扩展_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();
			}
		}

11.resize( )

(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++;
				}
			}
		}

12.push_back( )

尾插时,需要:

(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;
		}

13.pop_back( )

尾删:

(1)判空

(2)直接更新大小

        void pop_back()
		{
			assert(!empty());
			_finish--;
		}

14.insert( )

在固定位置插入元素,需要考虑迭代器失效的问题

(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;
		}

15.erase( )

(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;
		}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值