C++——vector模拟实现

目录

 vecotr模拟实现 

insert 

erase 

拷贝构造 

思考题 

resize 

vector>深浅拷贝问题 

习题 电话号码的字母组合 

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


 

 vecotr模拟实现 

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

C++——vector模拟实现_第1张图片

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

C++——vector模拟实现_第2张图片

 C++——vector模拟实现_第3张图片

 C++——vector模拟实现_第4张图片

#include
#include
using namespace std;

namespace my_space
{
	template
	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,此时程序正常运行

C++——vector模拟实现_第5张图片 屏蔽掉一条语句后,会出错

C++——vector模拟实现_第6张图片

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

C++——vector模拟实现_第7张图片

 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之前所在位置

C++——vector模拟实现_第8张图片

C++——vector模拟实现_第9张图片

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

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

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

 C++——vector模拟实现_第10张图片

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

C++——vector模拟实现_第11张图片

 C++——vector模拟实现_第12张图片

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

C++——vector模拟实现_第13张图片

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

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

这样就可以

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

C++——vector模拟实现_第14张图片

C++——vector模拟实现_第15张图片

	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 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扩容之后,插入很多数据,程序仍然可以正常运行

C++——vector模拟实现_第16张图片

erase 

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

C++——vector模拟实现_第17张图片

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

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

此时正常打印

C++——vector模拟实现_第18张图片

 但这种情况有崩溃

C++——vector模拟实现_第19张图片

 此时又没有删掉

C++——vector模拟实现_第20张图片

 C++——vector模拟实现_第21张图片

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

C++——vector模拟实现_第22张图片

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

C++——vector模拟实现_第23张图片

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

同理

C++——vector模拟实现_第24张图片

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

C++——vector模拟实现_第25张图片

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

C++——vector模拟实现_第26张图片

所以会崩溃 

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

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

C++——vector模拟实现_第27张图片

拷贝构造 

 C++——vector模拟实现_第28张图片

C++——vector模拟实现_第29张图片

C++——vector模拟实现_第30张图片

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

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

传统写法:

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

C++——vector模拟实现_第31张图片

也可这样写

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

现代写法:

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

C++——vector模拟实现_第32张图片

 之前我们已经有了类模板

C++——vector模拟实现_第33张图片

C++——vector模拟实现_第34张图片

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

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

 便于我们实现这种情况

C++——vector模拟实现_第35张图片

C++——vector模拟实现_第36张图片

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

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

C++——vector模拟实现_第37张图片

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

C++——vector模拟实现_第38张图片

 现代写法:

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

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

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

思考题 

 C++——vector模拟实现_第39张图片

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

C++——vector模拟实现_第40张图片

 C++——vector模拟实现_第41张图片

 当这样初始化会报错

C++——vector模拟实现_第42张图片

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

C++——vector模拟实现_第43张图片

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

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

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

C++——vector模拟实现_第44张图片

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

C++——vector模拟实现_第45张图片

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

C++——vector模拟实现_第46张图片

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>深浅拷贝问题 

	vector(const vector& 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> generate(int numRows) {
			vector> 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);
	}

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

程序直接崩溃

C++——vector模拟实现_第47张图片

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

C++——vector模拟实现_第48张图片

 C++——vector模拟实现_第49张图片

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

 C++——vector模拟实现_第50张图片

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

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

vector(const vector& 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)

C++——vector模拟实现_第51张图片

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

void Combine(string digits,int di,vector& 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 letterCombinations(string digits) {
 vector 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& nums) {
int src=0;
int dst=0;
while(src

你可能感兴趣的:(C++,c++,leetcode,开发语言)