当前位置: 首页 > news >正文

C++(初阶)(十)——vector模拟实现

vector

  • vector
      • 构造
      • 尾插(删)和扩容
      • inert(插入)
      • 迭代器失效
      • erase(删除)
      • resize(调整空间)
      • 深浅拷贝
      • 迭代器
      • 拷贝和赋值(v2(v1)和v1 = v3)
      • 多个数据插入
      • 迭代器区间初始化

(constructor)构造函数声明接口说明
vector()(重点)无参构造
vector(size_type n, const value_type& val =value_type())构造并初始化n个val
vector (const vector& x); (重点)拷贝构造
vector (InputIterator first, InputIterator last);使用迭代器进行初始化构

构造

namespace A
{
	//存储在vector的值可能是int或者string 不同类型,写为模版
	template<class T>
	class vector
	{
	public:
		//若为私有,访问时需要突破类域,因此重命名为公有
		typedef T* iterator;
        
		//构造
		vector()
		{ }
        
		//析构
		~vector()
		{
			if (_start)
				delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}
		
	private:
        //给缺省值,缺省值主要是给没有显⽰在初始化列表初始化的成员使⽤的,不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表则用这个缺省值初始化
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};	
}

先实现一些简单的功能

	//求空间大小
	size_t capacity()
	{
		//指针差值
		return _end_of_storage - _start;
	}

	//有效元素个数
	size_t size()
	{
		return _finish - _start;
	}

	//重载[],下标访问
	T& operator[](size_t i)
	{
		assert(i < size());
		return _start[i];
	}

尾插(删)和扩容

实现尾部插入之前,需要判断先判断空间的大小,如果大小不够,需要扩容。

而关于扩容,并非是只在尾插时使用,所以需要在内部再次判断。

步骤;1,直接new与所需空间大小相同的空间即n。

2,即拷贝原空间的数据到新空间,并delete原空间。

但是我们需要注意的一点是在拷贝原空间的数据时,使用memcpy对内置类型的拷贝时满足我们的需求,但是在对自定义类型的拷贝时是值拷贝,也就是浅拷贝,不符合需求,此时解决办法是遍历原空间依次赋给新空间。

3,更新首尾指针和空间大小。

	//扩容
	void reserve(size_t n)
	{
		if (n > capacity())
		{
			//记录旧的size,因为_start更新后,size的值会改变
			size_t old_size = size();
			T* tmp = new T[n];
			if (_start)
			{
				//memcpy(tmp, _start, sizeof(T) * size());
				for (size_t i = 0; i < old_size; i++)
				{
					tmp[i] = _start[i];
				}
				delete[] _start;
			}
			_start = tmp;
			_finish = _start + old_size;
			_end_of_storage = _start + n;
		}
	}

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

	//尾删
	void pop_back()
	{
		assert(_start < _finish);
		--_finish;
	}

顺序表

resize 开空间+初始化

reservve 单纯开空间

emplace和insert功能类似,插入,细节有差别

vector< char >不能替代string,string兼容c语言。

1,string 数组后面有’ \0 ',vector< char >没有。

2,string += 字符或者字符串,即插入字符,字符串,string的find可以查找子串

3,vector< char >可以调用算法库的查找,未找到return last;

如何看源码

1,了解功能

2,抓核心枝干

时间换空间:顺序表缩容逻辑(不划算)

空间换时间:(比较划算)

inert(插入)

//pos是一个指针,是iterator即T*类型的
void insert(iterator pos, const T& x)
{
    //位置不能越界
	assert(pos >= _start);
	assert(pos <= _finish);
	//1,判断空间,不够扩容
	//2,拷贝原数据

	if (_finish == _end_of_storage)
	{
		size_t newcapacity = _end_of_storage == 0 ? 4 : 2 * capacity();
		reserve(newcapacity);
	}

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

迭代器失效

代码看似没有问题,其实在扩容后会形成迭代器失效,pos的位置已经丢失,

所以需要想办法得到扩容后新的pos的位置。

迭代器失效在vs下会强制检查,迭代器失效后不能访问,访问会崩溃报错。

失效原因:可能是底层成为野指针,即缩容逻辑,也可能是删除后,位置更改。

g++下没有强制检查。

失效后不要访问,重新赋值后再访问。

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

erase(删除)

关于erase也存在迭代器失效的问题,解决办法是返回一个迭代器,在删除后接收。

//.c

//不返回值
//迭代器失效在vs下会强制检查,迭代器失效后不能访问,访问会崩溃报错。
	void erase(iterator pos)
	{
		assert(pos >= _start);
		assert(pos <= _finish);

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

		//更新pos,否则迭代器会失效
		return pos;
	}


//返回迭代器
	iterator erase(iterator pos)
	{
		assert(pos >= _start);
		assert(pos <= _finish);

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

		//更新pos,否则迭代器会失效
		return pos;
	}

//------------------------------------------
//test

A::vector<int> v1 = { 1,2,2,2,3,4,5,6,6,6 };

for (auto e : v1)
{
	cout << e << " ";
}
cout << endl;

A::vector<int>::iterator it = v1.begin();
while (it != v1.end())
{
	if (*it % 2 == 0)
	{
		v1.erase(it);
	}
	else
	{
		it++;
	}
}

for (auto e : v1)
{
	cout << e << " ";
}
cout << endl;

resize(调整空间)

比原来空间大

比如说,原来数据为:vector< int > v = { 1,2,3,4,5 };

使用resize(10),如果不给值,会默认赋值为0

会将数据置为1,2,3,4,5,0,0,0,0,0

再如resize(10,2)

会变为1,2,3,4,5,2,2,2,2,2

比原来空间小

resize(2)

变为1,2

	void resize(size_t n, T val = T())
	{
		if (n > size())
		{
			//插入数据,先扩容
			reserve(n);
			while (_start + n != _finish)
			{
				*_finish = val;
				++_finish;
			}
		}
		else
		{
			_finish = _start + n;
		}
	}

深浅拷贝

在这里插入图片描述

memcpy是一种值拷贝。对深拷贝的自定义类型会进行浅拷贝,然后就会发生问题,

对于内置类型是没有问题的。

解决办法是,一个一个赋值解决。

迭代器

一般情况下我们会实现两种迭代器,一种是普通版本的迭代器,可以修改其本身,也可以修改指向的对象;另外一种是const修饰的迭代器,其本身可以修改,指向对象不可以修改。

需要注意的一点是迭代器的end()是指向结尾数据的下一个位置,我们此处的_finish正好满足条件,所以直接放回即可。

	typedef T* iterator;
	typedef const T* const_iterator;

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

	iterator end()
	{
		return _finish;
	}

	//const迭代器
	const_iterator begin() const
	{
		return _start;
	}
	const_iterator end() const
	{
		return _finish;
	}

拷贝和赋值(v2(v1)和v1 = v3)

	//v2(v1)
	vector(vector<T>& v)
	{
        //申请和v1相同的空间大小,使用范围for(支持迭代器就支持范围for)尾插数据到新空间即可。 
		reserve(v.capacity());
		for (auto& e : v)
		{
			push_back(e);
		}
	}

	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 = v3
	vector<T>& operator=(vector<T> v)
	{
		//直接交换即可,因为拷贝构造已经形成
		swap(v);

		return *this;
	}

多个数据插入

	//对val的类型不确定,给缺省值,是匿名对象
	vector(size_t n, const T& val = T())
	{
		reserve(n);
		for (size_t i = 0; i < n; i++)
		{
			push_back(val);
		}
	}

但是如果仅仅只写这一种的话,在我们实现对迭代器区间初始化时,会发生错误匹配的情况,是模板实例化时的问题,

可以想到的是把size_t类型修改为int类型,这样就可以解决,但是这样有引发新的问题,对于无符号整型类型没办法处理,所以直接两个都留下,构成了函数重载。

	//为了解决模版实例化的问题
	vector(int n, const T& val = T())
	{
		reserve(n);
		for (int i = 0; i < n; i++)
		{
			push_back(val);
		}
	}

	//c++11
	vector(initializer_list<T> il)
	{
		reserve(il.size());
		//此处用引用,因为T的类型不确定
		for (auto& e : il)
		{
			push_back(e);
		}
	}

迭代器区间初始化

	//迭代器区间初始化
	//写为模版,可以初始化非iterator的类型
	template <class InputIterator>
	vector(InputIterator first, InputIterator last)
	{
		while (first != last)
		{
			push_back(*first);
			++first;
		}
	}

	iterator erase(iterator pos)
	{
		assert(pos >= _start);
		assert(pos <= _finish);

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

		//更新pos,否则迭代器会失效
		return pos;
	}

相关文章:

  • 什么是RACI矩阵,应用在什么场景?
  • R语言空间水文气象数据分析:从插值到动态可视化
  • CUDA 与 OpenCL 对比
  • Porting Layer - 跨平台函数接口封装(RTOS/Windows)- C语言
  • 智慧医疗数据集
  • 跨境多商户供销电商系统:三位一体生态,赋能企业全球化无界合作
  • 在MDK新版本中添加AC5编译器支持
  • 演员徐梓辰正式加入创星演员出道计划,开启演艺新纪元
  • vmware、centos: 快照、redis集群克隆、启动异常
  • 流程控制语句练习题总结
  • OJ--第N个泰波那契数列
  • 钢板矫平机:重塑材料加工新标杆
  • 刻意练习:如何从新手到大师
  • VS Code-i18n Ally国际化插件
  • Firebase崩溃:DialogFragment/BottomSheetDialogFragment缺乏无参构造函数
  • 智能指针和STL库学习思维导图和练习
  • 4.7学习总结 java集合进阶
  • 信息系统项目管理师-第十二章-项目质量管理
  • 搭建复现环境
  • 06.unity 游戏开发-unity2D工程的创建及使用方式和区别
  • 建设厅职业资格中心网站/免费b2b推广网站
  • 1688网站建设方案书模板/百度信息流怎么投放
  • 做设计找图片的网站有哪些/淘宝店铺运营
  • 做网站要是要求吗/app地推接单平台
  • 南昌做网站要多少钱/百度售后客服电话24小时
  • wordpress 大站点/苏州搜索引擎排名优化商家