STL details about vector, vector simulation implementation [C++]

insert image description here

vector member variable

insert image description here
_start points to the head of the container, _finish points to the next position of valid data in the container , and _endofstorage points to the end of the entire container

default member function

Constructor

		//构造函数
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
    
    }

copy construction

First open up a space with the same size as the container, then copy the data in the container one by one, and finally update the values ​​of _finish and _endofstorage.

Deep copy version 1:

		//拷贝构造(深拷贝)
		vector(const vector<T> & v)
			//v1= v
			//vector( vector * this ,const vector<T>& v)

		{
    
    
			//开空间
			this->_start =  new T[size(T) * v.capacity()];
			//拷贝数据
			memcpy(this->_start, v._start, sizeof(T) * v.size()  );
			this->_finish = v._start + v.size();//更新_finish
			this->_endofstorage = v._start + v.capacity();//更新 _endofstorage
		}

Notice:Cannot use memcpy function,
if the data stored in the vector is a built-in type or a custom type that does not need to be deep copied, the memcpy function can be used,
but when the data stored in the vector is a custom type that needs to be deep copied, memcpy cannot be used. For
example,
if the vector is stored When the data is string class

void test_vector9()
	{
    
    
		vector<string> v;
		v.push_back("111111111111111");
		v.push_back("222222222222222");
		v.push_back("333333333333333");
		v.push_back("444444444444444");
		v.push_back("555555555555555");//memcpy拷贝出现了问题

		for (auto & e : v) //string拷贝代价比较大
		{
    
    
			cout << e << " ";
		}
		cout << endl;
	}

If the memcpy function performs copy construction, then the value of each string member variable stored in the tmp requested by the reserve function points to the same string space as each corresponding string member in the vector.
insert image description here

delete releases space. If it is a custom type, the destructor of each object in the array is called in turn, and then the entire space is released. That is to say, tmp now points to a piece of released space, that is, tmp is a wild pointer
insert image description here

Summary:
Problem: vector is a deep copy, but the object stored in the vector space is an array of strings. Using memcpy results in a shallow copy of the string object

How to solve:

		void reserve( size_t n)
		{
    
    
 			if (n > capacity())//扩容 
			{
    
    
				size_t sz = size();//用sz记录size 
				T * tmp = new T[n];
				if (_start != nullptr) //如果原空间不为空再拷贝数据
				{
    
    
					//memcpy(tmp, _start, sizeof(T) * sz);//将_start的数据拷贝到tmp中
					for (size_t i = 0; i < sz; ++i)
					{
    
    
						tmp[i] = _start[i];//调用string的赋值重载进行深拷贝
					}
					delete[] _start;//释放_start的空间 
				}

				_start = tmp; //将tmp的地址给_start,以便_finish和_endofstorage的更新
				_finish = _start + sz;//更新_finish
				_endofstorage = _start + n;//更新_endofstorage 

			  } 
		}

insert image description here

Conclusion: If the element type stored in the vector is a built-in type (int) or a shallow copy custom type (Date), you can use the memcpy function for copy construction, but if the element type stored in the vector is a deep copy custom type (string), the memcpy function cannot be used

Deep copy version 2:

Use range for (or other traversal methods) to traverse the container v, and insert the data stored in the container v one by one during the traversal process.

		//拷贝构造第二种版本(深拷贝)
		vector( const vector<T>& v)
			//v1=v
			//vector( vector *this , const vector<T> v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
    
    
			//开空间
			reserve(v.capacity());

			//拷贝数据
			for (auto e : v)
			{
    
    
				push_back(e); //将v的数据插入到v1中
			}
		}

Note : In the process of traversing the container v using the range for, the variable e is a copy of each data, and then the tail of e is inserted into the constructed container. Even if the data stored in the container v is of the string class, the copy construction (deep copy) of the string will be automatically invoked when e is copied, so problems similar to those when using memcpy can be avoided.

assignment operator overloaded function

Of course, the assignment operator overloading of vector also involves the problem of deep copying. We also provide two ways of writing deep copying here:

Release the original space first, then open up a space with the same size as container v, then copy the data in container v one by one, and finally update the values ​​of _finish and _endofstorage.

Deep copy version one

		//赋值重载版本一
		vector<T>  &  operator=(vector<T> v)
			//v1=v
		//	vector<T>& operator=(vector<T> *this , vector<T> v)
		{
    
    
			//释放原来的空间
			delete [] _start;
			//开辟空间 
			_start = new T[v.capacity()];
			//拷贝数据 
			for (size_t i =0 ; i < v.size(); ++ i)
			{
    
     
				_start[i]= v[i];
			}
			//更行相关边界条件
			_finish = _start + v.size();
			_endofstorage = _start + v.capacity();
			return *this;
		 }

First of all, the reference parameter is not used when passing the rvalue parameter, because this can indirectly call the copy constructor of the vector, and then exchange the container v constructed by this copy with the lvalue, which is equivalent to completing the assignment operation. The container v will be automatically destroyed at the end of the function call.

Deep copy version 2 (recommended)

  //赋值重载版本二
		 vector<T> & operator= ( vector<T> v) //编译器接收右值的时候自动调用拷贝构造函数
			 //vector<T>& operator= ( vector<T> *this ,vector<T> v)
			 //v1=v
		{
    
    
			 //this->swap(v)
			 swap(v);//v1 和v交换
			 return *this;
		}

Understanding of version two:
insert image description here

destructor

When destructing a container, first judge whether the container is an empty container. If it is empty, no destructing operation is required. If it is not empty, first release the space in which the container stores data, and then set each member variable of the container to Just a null pointer.

	//析构函数
		~vector()
		{
    
    
			//_start==nulllptr 就不需要析构了
			if (_start != nullptr)
			{
    
    

				delete[]_start;
				_start = _finish = _endofstorage = nullptr;
			}
		}

iterator

insert image description here

begin

The begin function in vector returns the first address of the container

regular version

        iterator begin()
		{
    
    
			return _start;
		}
	

const version

	 const_iterator begin() const
		{
    
    
			return _start;
		}

end

The end function returns the address of the next data in the valid data in the container.

regular version

		 iterator end()
		 {
    
    
			 return _finish;
		 }

const version

	 const_iterator end() const
		 {
    
    
			 return _finish;
		 }

size and capacity

The result of the subtraction of two pointers, that is, the number of data of the corresponding type between the two pointers, so the size can be obtained by _finish - _start, and the capacity can be obtained by _endofstorage - _start.

insert image description here

size_t size()const
{
    
    
	return _finish - _start; //返回容器当中有效数据的个数
}
size_t capacity()const
{
    
    
	return _endofstorage - _start; //返回当前容器的最大容量
}

resize

1. When n > size

 , expand the size to n, and the expanded data is val. If val is not given, use the default value

 2. When n < size,

change the direction of _finish, and directly reduce the size of the container to n.

		 void resize(  size_t n ,  const T& val =  T()   )//缺省值是匿名对象,c++对内置类型进行了升级
		 {
    
    
			 //n<size 缩容
			 if (n < size())
			 {
    
    
				 _finish = _start + n;
		     }
			 else	 //扩容
			 {
    
    
				 reserve(n);
				 //插入数据
				 while (_finish!=_start+n)
				 {
    
    
					 *_finish = val;
					 _finish++;
				}
			 }
		
		 }

Note : C++ regards built-in types as classes, and they also have default constructors, so when setting the default value for the parameter val of the resize function, set it to T( )

reserve

1. n>capacity(), expand the capacity to n or greater than n.
2. n<capacity(), do nothing.

	void reserve( size_t n)
		{
    
    
			if (n > capacity())//扩容 
			{
    
    
				size_t sz = size();//用sz记录size 
				T * tmp = new T[n];
				if (_start != nullptr) //如果原空间不为空再拷贝数据
				{
    
    
					memcpy(tmp, _start, sizeof(T) * sz);//将_start的数据拷贝到tmp中
					delete[] _start;//释放_start的空间 
				}

				_start = tmp; //将tmp的地址给_start,以便_finish和_endofstorage的更新
				_finish = _start + sz;//更新_finish
				_endofstorage = _start + n;//更新_endofstorage 

			  } 
		}

Note:
1 Before performing operations, it is necessary to record the number of valid data in the current container in advance.

2 When copying the data in the container, the memcpy function cannot be used to copy

[ ]

const version

		 const T & operator[] (size_t pos) const
			// const T& operator[] (  T const * this,size_t pos) 
		{
    
    
			 assert(pos < size());
			 return  _start[pos];

		}

regular version


		  T& operator[] (size_t pos) 
			 // const T& operator[] (  T  * this,size_t pos) 
		 {
    
    
			 assert(pos < size() );
			 return  _start[pos];

		 }

push_back

To end-insert data, you must first determine whether the container is full. If it is full, you need to increase the capacity first, then insert the end of the data to the position pointed to by _finish, and then _finish++

void push_back(const T & x )
		{
    
    
			//如果容量满了
			if (_finish == _endofstorage)
				//扩容
			{
    
    
				size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
				reserve (newcapacity);//扩容
			}
			*_finish = x;
			_finish++;//_finish指针后移
		}

pop_back

		void pop_back()
		{
    
    
			erase(  --end()  );
		}

insert

The insert function can insert data at the pos position of the given iterator. Before inserting the data, it is judged whether the capacity needs to be increased, and then the pos position and the subsequent data are moved backward one bit to leave the pos position for insertion. Finally, the The data can be inserted into the pos position.

		 iterator insert(iterator pos, const T& x)
		 {
    
    
			 assert(pos >= _start && pos <= _finish);	
			 //如果容量满了,需要扩容 
			 if (_finish == _endofstorage)
			 {
    
    
				 size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
				 //扩容会开辟一段新的空间 ,把数据从原空间拷贝到新空间,并且释放原空间,但是此时pos这个迭代器还是指向原空间
				 //会导致pos迭代器失效 —更新pos迭代器
				 size_t len = pos - _start;
				 reserve(newcapacity);
				 pos = _start + len;
			  }
			 //容量未满
			 iterator end = _finish -1;
			 //挪动数据
			 while (end>=pos)
			 {
    
    
				 *(end + 1) = *(end);
  				 --end;
			 }
			 //插入数据 
			 (*pos) = x;
			 _finish++;
			 return pos;
		 }

The iterator may become invalid after insert.
Solution: Just reassign the iterator before using the iterator next time.

erase

The erase function can delete the data at the pos position of the given iterator. Before deleting the data, it needs to judge that the container is released as empty. If it is empty, it needs to make an assertion. When deleting the data, it directly moves the data after the pos position forward by one bit . Just overwrite the data at the pos position

		 //错误的版本
		 //void erase(iterator pos)
		 //{
    
    
			// assert(pos >= _start && pos < _finish);
			// iterator it = pos + 1;
			// while (it != _finish)//挪动数据
			// {
    
    
			//	 *(it - 1) = *(it);
			//	 it++;
			// }
			// _finish--;
		 //}

		 //正确的版本
		 iterator erase(iterator pos)
		 {
    
    
			 assert(pos >= _start && pos < _finish);
			 iterator it = pos + 1;
			 while (it!= _finish)//挪动数据
			 {
    
    
				 *(it - 1) = *(it);
				 it++;
			 }
			 _finish--;
			 return pos;
		
		  }

Erase may have the problem of iterator invalidation when using it.
Solution: We can receive the return value of the erase function (the erase function returns the new position of the element after the deleted element)

swap

The swap function is used to exchange the data of two containers. We can directly call the swap function in the library to exchange the member variables of the two containers.

		void swap(vector<T> & v)//交换数据
		{
    
    
			std::swap(_start , v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}

Note : To call the swap template function in the library here, you need to add "std::" before the swap function to tell the compiler to look for the swap function in the C++ standard library, otherwise the compiler will think that you are calling the swap that is being implemented function (proximity principle).

If you think this article is helpful to you, you might as well move your fingers to like, collect and forward, and give Xi Ling a big attention. Every
support of yours will be transformed into the driving force for me to move forward! ! !

insert image description here

Guess you like

Origin blog.csdn.net/qq_73478334/article/details/131995121
Recommended