【C++初阶】模拟实现vector

在这里插入图片描述

个人主页:@Weraphael
✍作者简介:目前学习C++和算法
✈️专栏:C++航路
希望大家多多支持,咱一起进步!
如果文章对你有帮助的话
欢迎 评论 点赞 收藏 加关注✨


目录

  • 一、简单剖析vector的源码
  • 二、准备工作
  • 三、模拟实现vector常见操作
      • 3.1 无参的默认构造
      • 3.2 获取容量
      • 3.3 获取元素个数
      • 3.4 扩容 + memcpy的浅拷贝问题
      • 3.5 尾插
      • 3.6 迭代器
      • 3.7 析构函数
      • 3.8 operator[]
      • 3.9 插入insert
      • 3.10 删除某个位置的元素erase
      • 3.11 尾删pop_back
      • 3.12 增加/缩减有效数据resize
      • 3.13 拷贝构造 + memcpy浅拷贝
      • 3.14 交换
      • 3.15 赋值运算符重载
      • 3.16 用n个val构造
      • 3.17 用迭代器区间初始化
  • 四、模拟实现源码

一、简单剖析vector的源码

我们知道,vector是一个动态数组,它使用连续的内存存储元素。当我们向vector中插入元素时,如果导致当前内存不足以容纳所有元素,vector会重新分配更大的内存空间,并将所有元素复制到新的内存中。因此 ,vector的结构和string是一样的:

template<class T>

class vector
{
	T* _str;
	size_t _size;
	size_t _capacity;
};

然而vector的源码还是和以上有所差别的(详细源码在评论区):

template <class T,class Alloc = alloc>
class vector
{
public:
	typedef T* iterator;
	
private:
	iterator start;
	iterator finish;
	iterator end_of_storage;
 }

如上所示,我们并不知道源码中的成员变量startfinishend_of_storage是什么,只能知道它们是指针。因此接下来我们得去看它的成员函数,想要快速了解应该重点看构造函数以及插入接口:

  vector() 
  :start(0), 
  finish(0), 
  end_of_storage(0) 
  {}
  
  vector(size_type n, const T& value) 
  { 
  		fill_initialize(n, value); 
  }
  
  vector(int n, const T& value) 
  { 
  		fill_initialize(n, value); 
  }
  
  vector(long n, const T& value) 
  { 	
  		fill_initialize(n, value); 
  }
  
  explicit vector(size_type n) 
  { 
  		fill_initialize(n, T()); 
  }

尴尬的是构造函数看不出什么所以然来,因此接下来可以看插入接口:

 // 尾插
 void push_back(const T* x)
 {
	if (finish != end_of_storage)
	{
		construct(finish, x);
		++finish;
	}
	else
	{
		insert_aux(end(), x);
	}
 }

有插入操作必定涉及到扩容:

 void reserve(size_type n) 
 {
    if (capacity() < n) 
    {
       const size_type old_size = size();
       iterator tmp = allocate_and_copy(n, start, finish);
       destroy(start, finish);
       deallocate();
       start = tmp;
       finish = tmp + old_size;
       end_of_storage = start + n;
    }
 }

从上我们就猜出:

  • start:从名字上来看可能是指向起始位置的指针
  • finish:从尾插可以看出,相当于指向数据的有效个数的指针
  • end_of_storage:从扩容接口可以看出,相当于指向当前容量的指针
    【C++初阶】模拟实现vector_第1张图片

二、准备工作

为了方便管理代码,分两个文件来写:

  • Test.cpp - 测试代码逻辑
  • vector.h - 模拟实现vector
    【C++初阶】模拟实现vector_第2张图片

三、模拟实现vector常见操作

3.1 无参的默认构造

namespace wj
{
	template<class T>

	class vector
	{
		typedef T* iterator;

	public:
		// 无参的默认构造
		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storage(nullptr)
		{}

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};
}
  • 为了防止与库的vector冲突,要重新写一个命名空间域wj
  • 要注意初始化列表的顺序,是按照成员变量的顺序来赋值的!

3.2 获取容量

size_t capacity() const
{
	return _end_of_storage - _start;
}

指针 - 指针返回的是元素个数。
【C++初阶】模拟实现vector_第3张图片

3.3 获取元素个数

size_t size() const
{
	return _finish - _start;
}

3.4 扩容 + memcpy的浅拷贝问题

vector的扩容并不是原地扩容,而是会重新分配更大的内存空间,并将所有元素复制到新的内存中。并且一般都是只扩容,不缩容。

void reserve(size_t n)
{
	// n > 当前容量则需要扩容
	if (n > capacity())
	{
		size_t sz = size();
		T* tmp = new T[n];
		if (_start)
		{
			// 如果原空间不为空,则拷贝数据
			memcpy(tmp, _start, sizeof(T) * sz);				
			// 释放原空间
			delete[] _start;
		}
		// 再指向新空间
		_start = tmp;
		_finish = _start + sz;
		// 不能像下面这么写
		// 原因是_start指向新空间后size就失效了(可以看看size的实现)
		// 所以应该提前记录size的大小
		/*_finish = _start + size();*/

		_end_of_storage = _start + n;
	}
}

以上代码看似没有问题,其实漏洞百出:

【C++初阶】模拟实现vector_第4张图片

虽然打印出来了,但是程序还是存在bug

这是一个隐藏的深拷贝:

  • memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
  • 如果拷贝的是内置类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到动态内存开辟时,就会出错,因为memcpy的拷贝实际是浅拷贝

【C++初阶】模拟实现vector_第5张图片

尾插"55555"就要进行扩容,memcpy拷贝数据到新空间;接下来delete旧空间,而delete的底层先是执行析构函数,完成对象(_str)中资源的清理,然后再释放对象的空间。

接着_start就会指向新拷贝的空间,然后tmp出了作用域会再次调用析构函数,导致_str所指向的内容被析构了两次,因此导致程序报错。

解决方法是:遍历赋值调用赋值重载,实现深拷贝。

void reserve(size_t n)
{
	// n > 当前容量则需要扩容
	if (n > capacity())
	{
		size_t sz = size();
		T* tmp = new T[n];
		if (_start)
		{
					
			for (size_t i = 0; i < sz; i++)
			{
			// 解决方法:调用赋值重载,实现深拷贝
				tmp[i] = _start[i]; 
			}
			// 释放原空间
			delete[] _start;
		}
		// 再指向新空间
		_start = tmp;
		_finish = _start + sz;

		_end_of_storage = _start + n;
	}
}

3.5 尾插

void push_back(const T& x) 
{
	// 当插入一个数据之前要判断是否扩容
	if (_finish == _end_of_storage)
	{
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	// 尾插
	*_finish = x; 
	++_finish;
}

3.6 迭代器

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

3.7 析构函数

// 7. 析构
~vector()
{
	if (_start)
	{
		delete[] _start;
		_start = _finish = _end_of_storage = nullptr;
	}
}

vector底层实际上自己实现了一个空间配置器(内存池),而内存池有点复杂,我们选择使用newdelete代替,因此要写析构函数
【C++初阶】模拟实现vector_第6张图片

3.8 operator[]

// 可读可写
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.9 插入insert

实现insert在某个位置插入

【C++初阶】模拟实现vector_第7张图片

特别要注意扩容部分:vector的扩容会导致迭代器失效

iterator insert(iterator pos, const T& x)
{
	// 判断pos的有效性
	assert(pos >= _start && pos <= _finish);
	
	// 可能存在扩容
	if (_finish == _end_of_storage)
	{
		// 防止迭代器失效
		// 提前记录pos的位置
		size_t len = pos - _start; 
		
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);

		// 防止迭代器失效
		// 还原新空间pos的位置
		pos = _start + len; 
	}
	
	// 挪动数据(从最后一个数据往后挪)
	// 1. 记录最后一个数据的位置
	iterator end = _finish - 1;
	// 2, 向后挪动
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	// 3. 插入数据
	*pos = x;
	++_finish;
	
	return pos;
}

【挪动数据】

【C++初阶】模拟实现vector_第8张图片

3.10 删除某个位置的元素erase

【C++初阶】模拟实现vector_第9张图片

iterator erase(iterator pos)
{
	// 检查下标的有效性
	assert(pos >= _start && pos < _finish);
	
	// 元素向前移
	iterator it = pos + 1;
	while (it != _finish)
	{
		*(it - 1) = *it;
		++it;
	}
	--_finish;

	return pos;
}

【删除数据】

【C++初阶】模拟实现vector_第10张图片

3.11 尾删pop_back

void pop_back()
{
	if (_start)
	{
		--_finish;
	}
}

3.12 增加/缩减有效数据resize

void resize(size_t n, const T& val = T()) 
{
	// 相当于删除数据
	if (n < size())
	{
		// 改变_finish即可
		_finish = _start + n;
	}
	else // 否则增加数据
	{
		// 可能存在扩容
		reserve(n);
		while (_finish != _start + n)
		{
			// 填值
			*_finish = val;
			++_finish;
		}
	}
}
  • 为什么第二个参数是:const T& val = T()
    因为resize不给第二个参数默认是0,但缺省值不能直接给0,如果直接给0只有int是适用的,而如果Tstring就不对了。因此这里可以给一个 匿名对象。若Tstring就会调用它的默认构造。

但有人奇怪如果T是内置类型能编译的过吗?理论上是不行的,因为内置类型没有构造函数。但是有了模板以后,C++对内置类型进行了升级,内置类型也支持构造函数

【C++初阶】模拟实现vector_第11张图片

3.13 拷贝构造 + memcpy浅拷贝

要手动写拷贝构造,默认不写的话是浅拷贝。成员变量会指向同一块空间,会导致析构两次的问题

// v1已知
// vector v2(v1);

vector(const vector<T>& v) //v就是v1
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	// 1. 让v2开一块和v1一样大的空间
	_start = new T[v.capacity()];
	// 2. 拷贝数据
	memcpy(_start, v._start, sizeof(T) * v.size());
	// 3. 调整
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();	
}

reserve接口讲解到:使用memcpy导致string对象的浅拷贝,因此要改掉memcpy

vector(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	// 以下是传统写法
	_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();
}

还有一种写法可以避免以上的问题:

// v1已知
// vector v2(v1);

vector(const vector<T>& v) // v1就是v
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	// 让v2开一块和v1一样大的空间
	reserve(v.capacity());
	for (auto x : v)
	{
		// 往v2插入数据
		push_back(x);
	}
}

3.14 交换

void swap(vector<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end_of_storage, v._end_of_storage);
}

3.15 赋值运算符重载

// v1已知,v2也已知
// v1 = v2

// 传参时,v2传值会调用拷贝构造(深拷贝)
vector<T>& operator=(vector<T> v) 
{
	// this就是v1
	// 直接交换即可
	this->swap(v);
	return *this;
}

3.16 用n个val构造

vector(size_t n, const T& val = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	resize(n, val);
}

可以复用resize,但之前要对成员变量初始化,因为内置类型的指针默认是随机值,不初始化会造成野指针问题。或者可以直接在成员变量给缺省值。

3.17 用迭代器区间初始化

vector(iterator first, iterator last)
{
	while (first != last)
	{
		push_back(*first); 
		++first;
	}
}

很多人会写出以上代码,但是这样写就写死了,因为这样写只能用vector的迭代器初始化

因此,我们在一个类中还可以再套模板

template<class InputIterator> //一个类模板还可以再套模板
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first); 
		++first;
	}
}

【C++初阶】模拟实现vector_第12张图片

以上代码出现了非法的间接寻址,意思就是不是指针或者不是解引用的对象被解引用,因此以上测试代码调用了迭代器区间初始化的函数。

可是为什么会调用迭代器区间初始化的函数呢?

首先以上两个参数是101,编译器会认为是int类型的,本应该调用用n个val构造,但是其第一个参数是无符号的整型,而迭代器区间初始化的参数是是模板,因此编译器会调用更匹配的函数(迭代器区间初始化),而迭代器本应类似一个指针,这里却是一个普通的变量,解引用就导致了非法的间接寻址。

解决方案:函数重载(库里也是这样实现的)

 // 用n个val构造
vector(size_t n, const T& val = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	resize(n, val);
}
		
vector(int n, const T& val = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{		
		resize(n, val);	
}

四、模拟实现源码

#pragma once

#include

namespace wj
{
	template<class T>

	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		// 默认构造函数(
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}

		
		//  尾插
		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			*_finish = x;
			++_finish;
		}

		//  capacity()
		size_t capacity() const
		{
			return _end_of_storage - _start;
		}

		// size()
		size_t size() const
		{
			return _finish - _start;
		}

		// 迭代器
		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}


		// 析构
		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _end_of_storage = nullptr;
			}
		}

		// operator[]
		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}

		const T& operator[](size_t pos) const
		{
			assert(pos < size());
			return _start[pos];
		}

		// 插入insert
		iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start && pos <= _finish);

			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start; 
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
				pos = _start + len; 
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;

			return pos;
		}

		// 10. 删除erase
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);

			iterator it = pos + 1;
			while (it != _finish)
			{
				*(it - 1) = *it;
				++it;
			}
			--_finish;

			return pos;
		}

		// 11. 尾删
		void pop_back()
		{
			if (_start)
			{
				--_finish;
			}
		}

		// 12. 增加/缩减有效数据
		void resize(size_t n, const T& val = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				while (_finish != _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}

		//  赋值运算符重载
		vector<T>& operator=(vector<T> v) 
		{
			this->swap(v);
			return *this;
		}

		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}

		// reserve
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					for (size_t i = 0; i < sz; i++)
					{
						tmp[i] = _start[i]; 
					}
					delete[] _start; 
				}
				_start = tmp;
				_finish = _start + sz;

				_end_of_storage = _start + n;
			}
		}

		// 拷贝构造
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			_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 v2(v1) 
		/*vector(const vector& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(v.capacity());
			for (auto x : v)
			{
				push_back(x);
			}
		}*/

		// 用n个val构造
		vector(size_t n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			resize(n, val);
		}

		vector(int n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			resize(n, val);
		}

		// 用迭代器区间初始化

		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
	private:
		iterator _start; 
		iterator _finish; 
		iterator _end_of_storage; 
	};
}

你可能感兴趣的:(C++,c++,学习,笔记,开发语言)