벡터, 벡터 시뮬레이션 구현에 대한 STL 세부 정보[C++]

여기에 이미지 설명 삽입

벡터 멤버 변수

여기에 이미지 설명 삽입
_start는 컨테이너의 헤드를 가리키고, _finish는 컨테이너에서 유효한 데이터 의 다음 위치를 가리키고, _endofstorage는 전체 컨테이너의 끝을 가리킵니다.

기본 멤버 함수

건설자

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

복사 공사

먼저 컨테이너와 같은 크기의 공간을 연 다음 컨테이너의 데이터를 하나씩 복사하고 마지막으로 _finish 및 _endofstorage 값을 업데이트합니다.

딥 카피 버전 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
		}

알아채다:memcpy 기능을 사용할 수 없습니다.,
벡터에 저장된 데이터가 빌트인 타입이거나 딥카피가 필요 없는 커스텀 타입이라면 memcpy 함수를 사용할 수 있지만
벡터에 저장된 데이터가 딥카피가 필요한 커스텀 타입인 경우 deep copy, memcpy는 사용할 수 없습니다.
예 를 들어
벡터가 저장된 경우 데이터가 문자열 클래스인 경우

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

memcpy 함수가 복사 구성을 수행하면 예약 함수가 요청한 tmp에 저장된 각 문자열 멤버 변수의 값은 벡터의 각 해당 문자열 멤버와 동일한 문자열 공간을 가리킵니다.
여기에 이미지 설명 삽입

delete는 공간을 해제합니다. 사용자 정의 유형인 경우 배열의 각 개체의 소멸자를 차례로 호출한 다음 전체 공간을 해제합니다. 즉, tmp는 이제 해제된 공간의 조각, 즉 tmp는 와일드 포인터입니다.
여기에 이미지 설명 삽입

요약:
문제: 벡터는 깊은 복사이지만 벡터 공간에 저장된 개체는 문자열 배열입니다. memcpy를 사용하면 문자열 개체의 얕은 복사본이 생성됩니다.

해결 방법:

		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 

			  } 
		}

여기에 이미지 설명 삽입

결론: 벡터에 저장된 요소 유형이 내장 유형(int)이거나 얕은 복사 사용자 정의 유형(Date)인 경우 복사 생성에 memcpy 함수를 사용할 수 있지만 벡터에 저장된 요소 유형이 a인 경우 깊은 복사 사용자 지정 유형(문자열), memcpy 함수를 사용할 수 없습니다.

딥 카피 버전 2:

범위(또는 다른 순회 방법)를 사용하여 컨테이너 v를 순회하고 순회 프로세스 중에 컨테이너 v에 저장된 데이터를 하나씩 삽입합니다.

		//拷贝构造第二种版本(深拷贝)
		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中
			}
		}

참고 : 범위 for를 사용하여 컨테이너 v를 순회하는 과정에서 변수 e는 각 데이터의 복사본이며 e의 꼬리는 구성된 컨테이너에 삽입됩니다. 컨테이너 v에 저장된 데이터가 문자열 클래스라도 e가 복사될 때 문자열의 복사 구성(deep copy)이 자동으로 호출되므로 memcpy를 사용할 때와 유사한 문제를 피할 수 있습니다.

할당 연산자 오버로드 함수

물론 벡터의 대입 연산자 오버로딩도 깊은 복사 문제를 수반하며 여기에서 깊은 복사를 작성하는 두 가지 방법을 제공합니다.

먼저 원래 공간을 해제한 다음 컨테이너 v와 동일한 크기의 공간을 연 다음 컨테이너 v의 데이터를 하나씩 복사하고 마지막으로 _finish 및 _endofstorage 값을 업데이트합니다.

딥 카피 버전 1

		//赋值重载版本一
		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;
		 }

우선, 참조 매개변수는 rvalue 매개변수를 전달할 때 사용되지 않습니다. 이는 벡터의 복사 생성자를 간접적으로 호출한 다음 이 사본에 의해 생성된 컨테이너 v를 lvalue로 교환할 수 있기 때문입니다. 이는 할당 작업을 완료하는 것과 같습니다. . 컨테이너 v는 함수 호출이 끝나면 자동으로 소멸됩니다.

딥 카피 버전 2(권장)

  //赋值重载版本二
		 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;
		}

버전 2에 대한 이해:
여기에 이미지 설명 삽입

오물 소각로

컨테이너를 파괴할 때는 먼저 컨테이너가 빈 컨테이너인지 판단하여 비어 있으면 파괴 작업이 필요하지 않으며, 비어 있지 않으면 컨테이너가 데이터를 저장하는 공간을 먼저 해제한 다음 컨테이너의 각 멤버 변수를 설정합니다. 컨테이너를 그냥 널 포인터로.

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

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

반복자

여기에 이미지 설명 삽입

시작하다

벡터의 시작 함수는 컨테이너의 첫 번째 주소를 반환합니다.

일반 버전

        iterator begin()
		{
    
    
			return _start;
		}
	

const 버전

	 const_iterator begin() const
		{
    
    
			return _start;
		}

end 함수는 컨테이너의 유효한 데이터에서 다음 데이터의 주소를 반환합니다.

일반 버전

		 iterator end()
		 {
    
    
			 return _finish;
		 }

const 버전

	 const_iterator end() const
		 {
    
    
			 return _finish;
		 }

크기와 용량

두 포인터를 뺀 결과, 즉 두 포인터 사이의 해당 타입의 데이터 개수이므로 크기는 _finish - _start로, 용량은 _endofstorage - _start로 얻을 수 있다.

여기에 이미지 설명 삽입

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

크기 조정

1. n > size 일 때

 size를 n으로 확장, 확장된 데이터는 val, val이 주어지지 않으면 기본값 사용

 2. n < size 일 때

_finish의 방향을 변경하여 직접 크기 줄이기 컨테이너를 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++;
				}
			 }
		
		 }

참고 : C++은 내장형을 클래스로 간주하고 기본 생성자도 있으므로 resize 함수의 매개변수 val에 기본값을 설정할 때 T( )로 설정한다.

예약하다

1. n>capacity(), 용량을 n 이상으로 확장합니다.
2. n<capacity(), 아무것도 하지 않습니다.

	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 

			  } 
		}

참고:
1 작업을 수행하기 전에 현재 컨테이너의 유효한 데이터 수를 미리 기록해야 합니다.

2 컨테이너의 데이터를 복사할 때 memcpy 기능을 사용하여 복사할 수 없습니다.

[ ]

const 버전

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

		}

일반 버전


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

		 }

푸시백

데이터를 삽입하려면 먼저 컨테이너가 가득 차 있는지 확인해야 하며, 가득 차 있으면 용량을 먼저 늘리고 _finish가 가리키는 위치에 데이터의 끝을 삽입한 다음 _finish++를 입력해야 합니다.

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

팝백

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

끼워 넣다

insert 함수는 주어진 iterator의 pos 위치에 데이터를 삽입할 수 있으며, 데이터를 삽입하기 전에 용량을 늘릴 필요가 있는지 판단한 후 pos 위치와 후속 데이터를 한 비트 뒤로 이동시켜 pos 위치를 남깁니다. 마지막으로 데이터를 pos 위치에 삽입할 수 있습니다.

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

이터레이터는 삽입 후 유효하지 않게 될 수 있습니다
솔루션: 다음에 이터레이터를 사용하기 전에 이터레이터를 재할당하십시오.

삭제

erase 함수는 주어진 iterator의 pos 위치에 있는 데이터를 삭제할 수 있으며, 데이터를 삭제하기 전에 컨테이너가 비어 있는 것으로 해제되었는지 판단해야 하며, 비어 있으면 assertion을 해야 합니다. pos 위치 뒤의 데이터를 1 비트 앞으로 직접 이동합니다 . pos 위치의 데이터를 덮어쓰면 됩니다.

		 //错误的版本
		 //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 사용시 iterator 무효화 문제가 있을 수 있음
해결 방법 : erase 함수의 반환 값을 받을 수 있음

교환

swap 함수는 두 컨테이너의 데이터를 교환하는데 사용되며 라이브러리에서 직접 swap 함수를 호출하여 두 컨테이너의 멤버 변수를 교환할 수 있습니다.

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

참고 : 여기에서 라이브러리의 스왑 템플릿 함수를 호출하려면 스왑 함수 앞에 "std::"를 추가하여 C++ 표준 라이브러리에서 스왑 함수를 찾도록 컴파일러에 지시해야 합니다. 구현 중인 스왑 기능을 호출합니다(근접성 원칙).

이 글이 도움이 되셨다면 손가락을 움직여 좋아요, 수집, 전달 그리고 Xi Ling에게 많은 관심을 가져주세요
. ! !

여기에 이미지 설명 삽입

추천

출처blog.csdn.net/qq_73478334/article/details/131995121