模拟c++ vector的实现

文章目录

    • vector里面怎么放的?
    • vector{}成员函数和成员变量
      • reverse()增容
        • 一个增容问题
        • 改后写法
        • 补充
      • 三种遍历方式方式
      • 构造函数
      • 析构函数
      • 拷贝构造
        • 为什么要进行深拷贝?
      • insert
      • 迭代器
      • erase
      • 赋值
        • swap
      • []访问
      • pop_back
      • push_back
      • size()和capacity()
    • vector类外面的函数
      • 打印函数
  • 其他

vector里面怎么放的?

三个成员函数;
都是指针类型;
_start
_finish
end_of_storage
模拟c++ vector的实现_第1张图片
迭代器end(),也就是返回的finisn,最后一个元素的后一个位置

vector{}成员函数和成员变量

reverse()增容

		//reserve()
		//增容,先开辟新的空间,再把原空间释放(防止内存泄露),最后把指向新开辟的空间,成员变量也要改变;
		//空间被释放时,要注意:释放后的空间就不去用了,size(),会调用到旧空间;
		void reserve(size_t n)
		{
			size_t sz = size();
			if (n > capacity())
			{
				T* tmp = new T[n];//string 扩容要n+1,因为最后还要放一个/0

				//防止start直接是空,是空的话memcpy会报错
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * n);//长度写成size()行不行?
					//当vector时要用赋值,memcpy不行;
					//用string的operator=赋值是深拷贝,深拷贝就是会再开辟一个临时对象的空间;
					delete[] _start;
				}


				//_finish = _start + size();
				//_end_of_storage = _start + size();
				_start = tmp;
				_finish = _start + sz;
				end_of_storage = _start + n;
			}
		}
一个增容问题

问题出现在delete[] _start这里;
如果vector,这里面的T的类型是string,那么会出现问题;
mencpy(tmp,_start, sizeof(T)*sz),怎么拷贝的?是将_start的值赋值给tmp;
模拟c++ vector的实现_第2张图片
普通的数、字符串都没问题,就是有指针这种,指向一块空间就会有问题;
模拟c++ vector的实现_第3张图片
memcpy将里面两个指针的内容赋值成一样了,一个指针释放后空间后,另一个就不能用了;

改后写法

memcpy用opertor,赋值,这个带有深拷贝的来替换掉;
这样如果是vector,那么每个vector的元素string都是深拷贝;

		void reserve(size_t n)
		{
			size_t sz = size();
			if (n > capacity())
			{
				T* tmp = new T[n];//string 扩容要n+1,因为最后还要放一个/0

				//防止start直接是空,是空的话memcpy会报错
				if (_start)
				{
                    for (size_t i = 0; i < size(); i++)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}


				//_finish = _start + size();
				//_end_of_storage = _start + size();
				_start = tmp;
				_finish = _start + sz;
				end_of_storage = _start + n;
			}
		}
补充

delete和free的区别就是delete会调析构函数;
new和malloc区别是,new会调用构造函数;

三种遍历方式方式

for循环这个遍历方式本质用的是迭代器

//第一种迭代器
vector<T>::iterator it = v.begin();
while(it != v.end())
{
    cout << *it << " ";
    it++;
}
cout << endl;

//第二种[]
for(int i = 0; i < v.size(); i++)
{
    cout << v[i] << " ";
}
cout << endl;

//第三种for循环
for(auto e : v)
{
    cout << e << " ";
}
cout << endl;


构造函数

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

析构函数

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

拷贝构造

深拷贝要先开辟一个空间;
这里调用的v.capacity(),首先v是const,所以他的capacity也应改是const,不然他的权限就被放大了就会报错;
所以定义capacity后面都要再加个const;

		//拷贝构造,深拷贝v2(v1)
		//第二种办法就是初始化v2,然后扩容到v1的容量、再把v1的值赋值过去
		vector(const vector<T>& v)//
			:_start(nullptr)//初始化列表
			,_finish(nullptr)
			,end_of_storage(nullptr)
		{
			//先扩容
			reserve(v.capacity());

			//用两个迭代器进行遍历然后赋值
			iterator it = begin();
			const_iterator vit = v.cbegin();
			while (vit != v.cend())
			{
				*it++ = *vit++;
			}
			_finish = it;

		}

    	//第一种方法
		/*vector(const vector& v)
		{
			//先开辟空间
			_start = new T[v.capacity()];
			_finish = _start;//后面遍历的时候再把_finsh调到正确位置
			end_of_storage = _start + v.capacity();

			for (size_t i = 0; i < v.size(); i++)
			{
				*_finish = v[i];
				finish++;
			}
			
		}*/
为什么要进行深拷贝?

因为浅拷贝,对于指针来说就是直接两个指针指向一个空间了,那其中一个指针如果说释放空间,另一个就变成野指针了;
如果没有数组指针这些需要开辟空间的,那就不用深拷贝;
深拷贝,就是自己先开个空间,再把东西拷贝过来,防止那边空间释放了;

insert

其中注意有迭代器失效,增容后pos的指向位置要重新给他赋值,不然他指向的增容前的地址、增容会把原来地址释放掉,换一个新的地址;

		//insert,注意要扩容,assert
		//push,尾插可以直接用insert,这里有迭代器失效,assert
		iterator insert(iterator pos,const T& x)
		{
			assert(pos <= _finish); //等于时就是尾插
			if (_finish >= end_of_storage)
			{
				//算出pos和start的距离
				//所以指针减去指针是之间的距离,是size_t类型
				size_t n = pos - start;
				size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
				reverse(newcapacity);

				//如果有增容注意要重置pos,因为这里面有迭代器失效
				//增容后原来的地址被释放了,指向新的空间,但是pos还是指向原来的地址,所以是pos指向的变成随机值会报错
				pos = _start + n;
			}

			//pos以及之后的一个个往后挪动
			iterator it = _finish - 1;
			while (pos <= it)
			{
				*(it + 1) = *it;
				it--;
			}
			*pos = x;
			return pos;
		}

迭代器

暂时可以看成两个指针

		typedef T* iterator;
		typedef const T* const_iterator;
		//迭代器iterator,begin()或者end()
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		//只读迭代器
		const_iterator cbegin() const//只读,const_iterator上面已经定义了是 const iterator
		{
			return _start;
		}

		const_iterator cend() const
		{
			return _finish;
		}

erase

		//erase
		//删除偶数那个例子试一下
		iterator& erase(iterator pos)
		{
			assert(pos < _finish);
			
			iterator begin = pos + 1;
			while (begin < _finish)
			{
				*(begin - 1) = *begin;
				begin++;
			}
			finish--;
			return pos;//这里面pos没有扩容操作,所以可以直接用

		}

赋值

两种写法,第二种是现代写法;
赋值也要用到深拷贝;

		//赋值v3 = v1,深拷贝
		//防止重复赋值判断不是自己给自己赋值、释放原来的空间、开辟新的空间再memcpy;
		//第二种现代写法就是,传值过来时v就是v1的深拷贝,然后直接swap,v3和v的成员变量就行了,但是这样的话就不用传&,引用的值了,不然会改变值的;
		//swap函数得自己写,如果用库里面的swap代价太大,要深拷贝三次,有个tmp这个临时变量,最后还要释放空间;写个swap它内部成员函数的v3.swap(v1);
		//自定义swap时,swap要加个预作用限定符::swap(),这样表示调用的是全局的swap,不会自己调自己;
		vector<T>& operator=(const vector<T>& v)
		{
			//第一种
			/*if (this != &v)
			{
				//先将自己空间的东西西释放掉
				delete[] _start;//finish等不用释放、因为本来就是_start+ v.size(),在同一块空间上
				_start = new T[v.capacity()];//开辟相同的空间

				memcpy(_start, v._start, sizeof(T) * v.size());//拷贝
			}*/

			//第二种
			swap(v);//注意在类里面都有this指针的,就是this->swap(v),this指向调用者本身,v3 = v1,那就是v3调用的swap(v)实际是v3.swap(v)
			return *this;//要支持连续等于,所以要有返回值;
		}

swap

外部的swap,有三次深拷贝,效率太低;
模拟c++ vector的实现_第4张图片
所以用自定义的;可以省去tmp这边的空间;
::swap是调用外部的swap,防止swap自己调用自己;

		//自定义swap
		void swap(vector<T> v) 
		{
			::swap(_start, v._start);
			::swap(_finish, v._finish);
			::swap(end_of_storage, v.end_of_storage);
		}

[]访问

const的也要写;会遇到const vector v; v[]

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

pop_back

		//pop_back,尾部删除,assert
		void pop_back()
		{
			assert(_start < _finish);
			--_finish;

			//换成erase也行
			//erase(end() - 1);
		}

push_back

第二种直接借用insert尾插,更快

		void push_back(const T& s)
		{
			//第一种写法
			if (_finish == end_of_storage)
			{
				size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
				reserve(newcapacity);
			}

			*_finish = s;
			_finish++;


			//第二种
			//insert(_finish,s);
		}

size()和capacity()

		//size()
		size_t size() const
		{
			return _finish - _start;
		}
		//capacity()
		size_t capacity() const //拷贝构造时调用v.capacity(),首先这个v是const所以这里后面要加const
		{
			return end_of_storage - _start;
		}

vector类外面的函数

打印函数

因为在外部调用类,所以在外面是要调用vector,不能继续写模板vector,T要给到具体值的;
在vector{}里面可以写模板的;

	void print_v(const vector<int>& v)//指定int类型,这个是类外面的函数了,调用的话给个确定的类型,然后模板才能正常用,如果再类内部是不用给的直接T
	{
		vector<int>::const_iterator it = v.cbegin();
		while (it != v.cend())
		{
			cout << *it << "";
			it++;
		}
		cout << endl;
	}

其他

int a[10];
memset(a,1,sizeof(int)*10)

memset都是按字节处理,int就是4个字节,一个字节8bit;
如上代码不会将a数组每个元素变成1,是将每个元素上面int类型,一共4个字节,每个字节置成1;

你可能感兴趣的:(c++)