C++vector的模拟实现

vector框架
	template <class T>
	class vector {
	public:
	T _start;//(1)
	T _finsih;//(2)
	T _endofstorge;//(3)
};

   ( 1 ) (1) (1)指向当前空间的第一个元素。
   ( 2 ) (2) (2)指向当前空间存储的最后一个元素的下一个位置。
   ( 3 ) (3) (3)指向当前空间的末尾。

默认构造函数
vector()//默认构造函数
			:_start(nullptr),//(1)
			_finish(nullptr),//(2)
			_endofstorge(nullptr)//(3)
		{

		}

   ( 1 ) ( 2 ) ( 3 ) (1)(2)(3) (1)(2)(3)因为一开始没有分配空间,所以三个指针都置为空。

有参构造函数

在这里插入图片描述

template<class InputIterator>
		vector(InputIterator first, InputIterator last)//(1)
			:_start(nullptr),
			_finish(nullptr),
			_endofstorge(nullptr)
		{
			while (first != last)
			{
				push_back(*first);//(2)
				first++;
			}
		}

   ( 1 ) (1) (1)使用一个迭代器区间进行初始化。
   ( 2 ) (2) (2)复用push_back

为什么这里要将这个这个函数设置为函数模板呢?
并且将模板参数的名字取为InputIterator

迭代器其实也有其分类:

1.Input_iterator 2.Output_iterator 这两个没有实际对应的类型
3.forward_iterator (单向迭代器)如: map,unordered_map 迭代器只能++
4.Bidirectional_iterator (双向迭代器) 能够++和-- 如list
5.Randoaccess_iterator (随机迭代器) 能够访问任意位置 如:vector

上面是父类,下面是子类,子类包含了父类的功能。除此之外迭代器都具有解引用这类功能

迭代器的分类有什么作用呢?
我们查看下算法库中的sort
在这里插入图片描述
sort要求传入随机迭代器,这时传入双向迭代器,单向迭代器就不满足要求了,如list就不支持sort算法。

又如reverse
在这里插入图片描述
其迭代器的类型为双向迭代器,但是也可以传入随机迭代器,因为随机迭代器具有双向迭代器的全部功能。

总结:迭代器的分类其实是一种引导的功能,告诉你该算法可以使用哪些迭代器,哪些不能用。

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

   ( 1 ) (1) (1)_finish指向当前空间存储元素的最后一个元素的下一个位置,所以直接减去_start就能得到当前空间存储了多少个数据。

capacity
int capacity() const
		{
			return _endofstorge - _start;
		}
内置数据类型的构造函数

在C++中自定义类型具有构造函数,为了兼容,内置类型也具有构造函数,如int的构造函数int()
在这里插入图片描述

const修饰的匿名对象

匿名对象的生命周期为当前行,出了该行以后即被回收。
但是如果const修饰以后呢?
结论:const修饰的匿名对象生命周期得到延长,直至函数执行完毕
证明:通过调试模式, 当执行到23行时,仍然没有执行person的析构函数,main函数运行完才执行析构。
在这里插入图片描述

reserve
void reserve(int newcapacity)//这里capacity是存储的个数,不是字节数
		{
			T *tmp = new T[newcapacity];
			
			int sz = size();//(1)
			if (_start)
			{
				//memcpy(tmp, _start, sizeof(T)*newcapacity);//(2)
				for (int i = 0; i < sz; i++)
					tmp[i] = _start[i];//(3)
			}
			
			delete[]_start;//(4)
			
			_start = tmp;//(5)
			_finish = _start + sz;//(6)
			_endofstorge = _start + newcapacity;//(7)
		}

   ( 1 ) (1) (1)需要提前记录下旧空间存储数据的个数,用于后面更新_finish。关于为什么要提前记录。这里我们画图理解。
因为后面更新_finish需要_finish距离_start的距离,但是因为先更新的_start,导致后面通过size()函数获得的数据个数是错误的,就无法得到_finish正确的位置
在这里插入图片描述
   ( 2 ) (2) (2)这里不能使用memcpy,如果单纯的对两个string进行拷贝,会导致两个string类的_str指针指向的空间一致,这会导致浅拷贝。在释放空间的时候会造成错误。
在这里插入图片描述
   ( 3 ) (3) (3)浅拷贝引发的问题使用深拷贝进行解决,对vector的每个数据都进行赋值拷贝。
   ( 4 ) (4) (4)释放原空间。
   ( 5 ) ( 6 ) ( 7 ) (5)(6)(7) (5)(6)(7)分别更新_start,_finish,_endofstorage

resize
void resize(int newcapacity, const T &x = T())//(1)
		{
			if (newcapacity > capacity())//(2)
			{
				reserve(newcapacity);
				while (_finish!=_endofstorge)
				{
					*_finish = x;
					_finish++;
				}

			}
			else if (capacity() >= newcapacity)//(3)
			{
				_finish = _start + newcapacity;
			}
		}

   ( 1 ) (1) (1)因为resize会对扩容的部门进行初始化,而vector存储的数据类型有很多种,因此使用默认的构造函数作为初始化的默认值。
这里的T& x=T();牵扯到自定义类型有点不好理解,我们进行画图理解。
假设要创建一个三行三列的二维vector
在这里插入图片描述
   ( 2 ) (2) (2)扩容的空间大于原空间,将多余的空间进行初始化。
   ( 3 ) (3) (3)扩容的空间小于原空间,更新_finish就好

迭代器
		typedef T* iterator;
		typedef  const T* const_iterator;

   ( 1 ) (1) (1)普通版本

iterator begin()
		{
			return _start;//(1)
		}

		iterator end()
		{
			return _finish;//(2)
		}

   ( 1 ) (1) (1)返回第一个元素。
   ( 2 ) (2) (2)返回最后一个元素的下一个位置。

   ( 1 ) (1) (1)const版本

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

拷贝构造函数传统写法
vector(const vector<T> &v)
		{
			_start = new T[v.capacity()];//(1)
			_finish = _start + v.size();//(2)
			_endofstorge = _start + v.capacity();//(3)
			//memcpy(_start, v._start, sizeof(T)*v.size());碰到string这种自定义类型的时候浅拷贝导致同一块空间被析构两次。//(4)
			for (int i = 0; i < v.size(); i++)//(5)
				_start[i] = v._start[i];
		}

   ( 1 ) (1) (1)申请新空间。
   ( 2 ) ( 3 ) (2)(3) (2)(3)更新_finish_endofstorage
   ( 4 ) (4) (4)这里不能使用memcpy,和reserver一样,当vector存储的数据类型是自定义类型string的时候,这里就是一个浅拷贝,而每个对象出来作用域就会被析构,就会导致堆区内存重复释放。
   ( 5 ) (5) (5)浅拷贝的问题需要通过深拷贝进行解决。

拷贝构造函数现代写法
	void swap(vector<T>&v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorge, v._endofstorge);
		}
		vector(const vector<T>&v)
			:_start(nullptr),//(1)
			_finish(nullptr),//(2)
			_endofstorge(nullptr)//(3)
		{
			vector<T> tmp(v.begin(),v.end());//(4)
			swap(tmp);//(5)
		}

   ( 1 ) ( 2 ) ( 3 ) (1)(2)(3) (1)(2)(3)因为一开始指向的空间是随机值,这样在释放空间的时候会导致非法访问,所以需要置空。
   ( 4 ) (4) (4)复用有参构造函数
   ( 5 ) (5) (5)直接进行空间交换就好,tmp是个临时对象,出了函数作用域会被析构,空间会被释放。

find
	iterator find(iterator first,iterator last,T val )
		{
			while (first!=last)
			{
				if (*first == val)//(1)
					return first;
				first++;
			}
			return last;//(2)
		}

   ( 1 ) (1) (1)找到了就返回该元素的迭代器位置
   ( 2 ) (2) (2)没找到返回末尾迭代器

insert迭代器失效

在了解什么是迭代器失效之前,我们先看一下vectorinsert的模拟实现。
我们知道,插入数据的时候需要扩容。并且需要从插入位置开始挪动数据。
原因正是出在这,扩容以后_start,_finish,_endofstorage都指向了新空间的位置。但是pos指向的还是原空间,但是原空间已经被释放,这时pos就是一个野指针。挪动数据的时候就会失败。
在这里插入图片描述

	void insert(iterator pos,T val)
		{
			if (_finish == _endofstorge)//这里扩容会导致迭代器失效
			{
				
				reserve(capacity() == 0 ? 4 : capacity() * 2);
							}
			iterator right = _finish;
			iterator left = pos;//因为增容,pos原来指向的空间会被释放,所以需要计算出新的pos的位置
			while (left < right)
			{
				(*right) = *(right - 1);
				right--;
			}
			*pos = val;
			_finish++;

					}

解决代码如下:

	void insert(iterator pos,T val)
		{
			if (_finish == _endofstorge)//这里扩容会导致迭代器失效
			{
				int sz = pos - _start;//(1)
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + sz;//(2)
			}
			iterator right = _finish;
			iterator left = pos;//因为增容,pos原来指向的空间会被释放,所以需要计算出新的pos的位置
			while (left < right)
			{
				(*right) = *(right - 1);
				right--;
			}
			*pos = val;
			_finish++;

		
		}

加上(1)和(2)代码以后就可以正常插入数据了。
   ( 1 ) (1) (1)提前计算pos的位置
   ( 2 ) (2) (2)扩容完成后更新pos

这时候insert在插入数据的时候迭代器不会失效了,但是还有一种场景迭代器失效的场景存在:
如果我想打印插入的数据呢?这时候实参传递的it迭代器会不会失效呢?
答案是会的,在前面我们只是对pos迭代器进行了矫正,但是传参过程是一个值传参,形参的改变不会影响实参。也就是说it还是指向旧空间。
解决办法:返回值进行接收,it接收insert函数返回pos更新以后的位置。

	void test10()//测试insert
	{
		vector<int> v1;
		v1.push_back(10);
		v1.push_back(20);
		v1.push_back(30);
		v1.push_back(40);
		auto it=v1.find(v1.begin(), v1.end(), 30);
		if (it != v1.end())
		{
			v1.insert(it, 50);//(1)
			cout << *it << endl;
		}
		
		
		
	}

   ( 1 ) (1) (1)外部迭代器仍然是失效的
正确代码:

iterator insert(iterator pos,T val)
		{
			if (_finish == _endofstorge)//这里扩容会导致迭代器失效
			{
				int sz = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + sz;
			}
			iterator right = _finish;
			iterator left = pos;//因为增容,pos原来指向的空间会被释放,所以需要计算出新的pos的位置
			while (left < right)
			{
				(*right) = *(right - 1);
				right--;
			}
			*pos = val;
			_finish++;

			return pos;
		}

void test10()//测试insert
	{
		vector<int> v1;
		v1.push_back(10);
		v1.push_back(20);
		v1.push_back(30);
		v1.push_back(40);
		auto it=v1.find(v1.begin(), v1.end(), 30);
		if (it != v1.end())
		{
			it=v1.insert(it, 50);//(1)
			cout << *it << endl;
		}
		
		
		
	}

   ( 1 ) (1) (1)接收pos更新以后位置的迭代器

erase
iterator erase(iterator pos)
		{
			//删除pos位置的数据
			iterator left = pos;
			iterator right = _finish;
			while (left<right)//(1)
			{
				(*left) = *(left + 1);
				left++;
			}
			_finish--;
			return pos;
		}

   ( 1 ) (1) (1)挪动数据。
   ( 1 ) (1) (1)别忘了 更新_finish的位置。

总结:
1.拷贝构造传统写法和reseve一定要注意使用,避开浅拷贝带来的问题。
2.insert带来的迭代器失效也需要认真画图思考一下。
3.在模拟实现的时候,迭代器,模板参数T,typdef以后很容易让人搞乱,大家可以多想一想,并可以根据下图进行思考。
在这里插入图片描述

匿名对象

在C++中提供了匿名对象,匿名对象的生命周期在当前行,当前行执行完以后,空间就会被编译器释放:
在这里插入图片描述
当匿名对象被const修饰以后,生命周期会延长至所在函数的作用域。
在这里插入图片描述

  • 12
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
引用和提供了关于实现vector的两种方法。其中,引用展示了一个使用reserve和push_back方法的示例,而引用展示了一个使用new和memcpy函数的示例。这两种方法都是常见的实现vector的方式。 在第一种方法中,通过reserve函数可以预留足够的内存空间,然后使用push_back函数逐个将元素添加到vector中。这种方法的好处是可以避免不必要的内存重分配,提高了效率。 而第二种方法使用new操作符在堆上分配内存空间,并使用memcpy函数将已有的vector对象的数据复制到新的内存空间中。通过这种方式,可以实现深拷贝,即两个vector对象拥有独立的内存空间。这种方法的好处是可以在不修改原始vector对象的情况下创建一个新的vector对象。 除了以上两种方法,还可以使用其他方式实现vector类。例如,可以使用动态数组来实现vector的底层数据结构,然后通过成员函数实现vector的各种操作,如增加、删除、查找等。 总结来说,c语言模拟实现vector的关键是动态内存管理和对元素的增删改查操作。可以使用预留空间和逐个添加元素的方式,也可以使用动态数组和复制数据的方式来实现vector类。具体的实现方式可以根据需求和实际情况选择。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C++——vector模拟实现](https://blog.csdn.net/weixin_49449676/article/details/126813526)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凤梨罐头@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值