C++——vector模拟实现

目录

 vecotr模拟实现 

insert 

erase 

拷贝构造 

思考题 

resize 

vector>深浅拷贝问题 

习题 电话号码的字母组合 

 习题 删除有序数组中的重复项


 

 vecotr模拟实现 

 源代码里面,核心成员是下面红框三个

 观察这里的代码发现这里的迭代器都是原生指针

 

 

#include<iostream>
#include<assert.h>
using namespace std;

namespace my_space
{
	template<class T>
	class vector
	{
	public:
		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;
		}

		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}

		~vector()
		{
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}

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

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

			return _start[pos];
		}

		T& operator[](size_t pos)
		{
			assert(pos < size());

			return _start[pos];
		}

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

		void reserve(size_t 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;
				_end_of_storage = _start + n;
			}
		}

		void push_back(const T& x)//加const防止隐式类型转换的中间常量传参错误,引用可避免深拷贝
		{
				if (_finish == _end_of_storage)
				{
					reserve(capacity() == 0 ? 4 : capacity() * 2);
				}

				*_finish = x;
				++_finish;
		}
		void pop_back()
		{
			assert(_finish > _start);
			--_finish;
		}
	

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};
};

insert 

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

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

			// 挪动数据
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;

			++_finish;
		}

 这种写法有一个缺陷

在3的前面插入30,此时程序正常运行

 屏蔽掉一条语句后,会出错

 这是因为扩容时出现了问题,pos本来是指向start中的,扩容之后start被销毁,新空间是tmp,pos就成了野指针

 pos失效了

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

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

			// 挪动数据
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;

			++_finish;
		}

这样就不会出错了,用相对位置记住pos之前所在位置

如果连续插入有时会报错,有时则不会 

这是因为pos的修改不会影响p,pos是形参,p有可能会失效,有可能不会失效取决于有没有扩容,所以在p位置插入数据后,不要再去访问p,因为有可能会失效

但是如果用引用,就跟库里面的insert不一致了,我们尽量要跟库里面保持一致

 

还有一种情况,当空间足够时,我们插入数据也会崩,也会导致迭代器失效

 

it指向2时满足条件 ,在2的前面插入4

然后++it,it仍然指向2,这样每次++it会导致it一直指向2,所以程序会崩

修改方法就是用一个变量来接收insert的返回值,库里面的insert,会返回新插入数据的下标,我们接收这个下标跟库里面的insert保持一致 

这样就可以

如果时偶数,对it++俩次即可 

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

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

			// 挪动数据
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;

			++_finish;
			return pos;
		}
void test_vector1()
	{
		vector<int> v;
		v.reserve(10);
		v.push_back(1);
		v.push_back(2);
		v.push_back(4);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		auto it = v.begin();
		while (it != v.end())
		{
			if (*it % 2 == 0)
			{
				it = v.insert(it, *it * 2);
				++it;
			}
			++it;
			
		}
		for (size_t i = 0; i < v.size(); ++i)
		{
			cout << v[i] << " ";
		}
		cout << endl;
	}

去掉reserve扩容之后,插入很多数据,程序仍然可以正常运行

erase 

		void erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos <= _finish);
			iterator begin = pos + 1;
			while (begin < _finish)
			{
				*(begin - 1) = *begin;
				++begin;
			}
			--_finish;
		}

 如果earse设置了缩容,缩容是以时间换空间,效率较低,缩容也会存在迭代器失效问题

使用上面代码删除所有的偶数

此时正常打印

 但这种情况有崩溃

 此时又没有删掉

 

当it走到2时,用4去覆盖掉it ,然后++it,it指向了3

 it指向4之后,5过去覆盖,之后++it,it指向finish

这时因为it错开了第一个4,++导致it快了一步

同理

it指向2,3把it覆盖了,++it,然后it指向了4

 指向4之后,finish又覆盖4,++it,it成了野指针

所以会崩溃 

 所以:insert/erase pos位置,不要随便访问pos,一定要更新,直接访问可能会让迭代器失效

解决办法,使用时加个else语句即可

拷贝构造 

 

这里默认的拷贝构造是浅拷贝,程序结束会析构俩次,所以崩溃

我们这需要自己设置一个深拷贝

传统写法:

	vector(const vector<T>& v)
		{
			_start = new T[v.size()];
			memcpy(_start, v._start, sizeof(T) * v.size());
			_finish = _start + v.size();
			_end_of_storage = _start + v.size();
		}

也可这样写

vector(const vector<T>& v)
			:_start(nullptr),
			_finish(nullptr),
			_end_of_storage(nullptr)
		{
			reserve(v.size());
			for (const auto& e : v)
			{
				push_back(e);
			}
		}

现代写法:

 vector里面有一个构造是支持迭代器区间构造

 之前我们已经有了类模板

 现在我们在类模板,成员函数里面再加一个模板

也就是说现在的这个拷贝构造函数里面可以用双重模板,在定义模板是因为我们需要一个迭代区间,而之前我们已经有了iterator,现在是InputIterator,这样写代表我们不仅可以用T类型模板,iterator迭代器,也可以用其他模板和其他迭代器,这样比较灵活

 便于我们实现这种情况

 当前函数写这样运行的时候会直接报错,因为如果编译器没有对s进行初始化,push_back要调用reverse,删除旧数组空间的时候会报错

  因此我们要对s进行初始化

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

 现代写法:

	template <class InputIterator>
		vector(InputIterator first, InputIterator last)
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}

		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);
		}
	vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}

这里传参的时候不进行引用传参,他会调用拷贝构造,根据我们写的拷贝可知,实参会被删除,保留形参,v就是保留下来的形参,然后跟一交换即可,这样比较方便

思考题 

 

     vector(size_t n, const T& val = T())  插入n个val,val是T类型

使用N个vakue去构造,T()是T类型的匿名对象,匿名对象就要调用默认构造,如果是Date这些自定义类型,就需要我们人为的把构造函数写好,如果T是int,int也有自己的默认构造,内置类型也有自己的默认构造

vector(size_t n, const T& val = T())
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storage(nullptr)
		{
			reserve(n);
			for (int i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

 

 当这样初始化会报错

 报错信息在这一行:非法的间接寻址

 当编译器进行类型匹配的时候,如果只传了一个参数,编译器会把那个参数传给最左边的形参(前面博客有说过)

而传俩个参数都是int类型,编译器在进行类型匹配的时候会找最匹配的类型来匹配

一个参数是unsigned int,一个是int

 对于这里传参传的是int,int,这个对拷贝构造来说特别匹配,first 和last都是int,然后对int解引用就报错

 而下面这个不报错因为,对于拷贝构造来说不匹配,所以它进不去拷贝构造

resize 

void resize(size_t n, const T& val = T())
		{
			if (n > capacity())
			{
				reserve(n);
			}
			if (n > _finish)
			{
				while (_finish < _start + n)
				{
					*finish = val;
					++_finish;
				}
			}
			else
			{
				_finish = _start + n;
			}
		}

 reserve对_finish不是很敏感,resize可以改变_finish

vector<vevtor<T>>深浅拷贝问题 

	vector(const vector<T>& v) //拷贝构造函数
		{
			_start = new T[v.size()];
			memcpy(_start, v._start, sizeof(T) * v.size());
			_finish = _start + v.size();
			_end_of_storage = _start + v.size();
		}
class Solution {
	public:
		vector<vector<int>> generate(int numRows) {
			vector<vector<int>> vv;
			vv.resize(numRows);
			for (int i = 0; i < vv.size(); ++i)
			{
				vv[i].resize(i + 1, 0);
				vv[i].front() = vv[i].back() = 1;
			}
			for (size_t i = 0; i < vv.size(); ++i)
			{
				for (size_t j = 0; j < vv[i].size(); ++j)
				{
					if (vv[i][j] == 0)
					{
						vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
					}
				}
			}
			return vv;
		}
	};
	void test_vector1()
	{
		Solution().generate(5);
	}

 上面代码是拷贝构造函数以及杨辉三角的调用

程序直接崩溃

 这是因为在函数返回的时候要调用一次拷贝构造,而该拷贝构造对外面是深拷贝,对里面是浅拷贝

 

这种拷贝方式只是对最外层完成了慎深拷贝,而对于里层确实浅拷贝

 

问题出在了memcpy上,memcpy是一个字节一个字节往过拷贝,导致这里里层的_start指向同一块空间,析构的时候会析构俩次,因此这里不能用memcpy

这样修改拷贝构造,程序即可正常运行

vector(const vector<T>& v)
		{
			_start = new T[v.size()];
		//	memcpy(_start, v._start, sizeof(T) * v.size());
			for (int i = 0; i < v.size(); ++i)
			{
				_start[i] = v._start[i];
			}
			_finish = _start + v.size();
			_end_of_storage = _start + v.size();
		}

防止reserve出现这种问题,对reserve也可进行修改

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

习题 电话号码的字母组合 

 17. 电话号码的字母组合 - 力扣(LeetCode)

class Solution {
    char *numToStr[10]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public:

void Combine(string digits,int di,vector<string>& retv,string CombinStr)
{
    if(di==digits.size())
    {
        retv.push_back(CombinStr);
        return ;
    }
    int num=digits[di]-'0';
    string str=numToStr[num];
    for(auto ch:str)
    {
        Combine(digits,di+1,retv,CombinStr+ch);
    }
}
    vector<string> letterCombinations(string digits) {
 vector<string> v;
  if(digits.empty())
 return v;
 string str;

 Combine(digits,0,v,str);
 return v;
    }
};

 str是一个用来临时存储组合的结果,digits是用户输入的数字,di是下标,如digits是“238”,di=0,就代表表示字符2,所以要给-'0',把每个按键对应的字母存到一个数组中,通过下标来访问

 习题 删除有序数组中的重复项

26. 删除有序数组中的重复项 - 力扣(LeetCode)

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
int src=0;
int dst=0;
while(src<nums.size())
{
    if(nums[src]==nums[dst])
    {
        ++src;
    }
    else
    {
   ++dst;
        nums[dst]=nums[src];
        src++;
      
    }
}
return dst+1;
    }
};

评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

头发没有代码多

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

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

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

打赏作者

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

抵扣说明:

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

余额充值