[STL] 벡터 시뮬레이션 상세 구현

목차

1. 준비

二,push_back  

1. 참고문헌에 대하여

2. 파라미터 const 수정

 다시 채우다

셋째, 반복자 구현

넷, Pop_back

다섯, 삽입

1. 보충 - 반복자 무효화

여섯, erase

세븐, 생성자 

1. 이터레이터 구성 

2. 기타 구조물

3. 복사 공사 

1) 전통적인 글쓰기 방식

2) 모던 라이팅(함수 재사용성 향상) 

여덟, 할당 기호 과부하

아홉, 크기 조정


 

1. 준비

      준비 작업에서는 이전에 배운 네임스페이스 및  클래스 템플릿 에 대한 지식이 필요하며 STL 소스 코드를 구현하기 전에 구현 방법을 학습해야 합니다.

구현을 시작하기 전에 벡터 프레임워크에 익숙해지도록 합시다.

// 头文件
#include <iostream>
#include <vector>
using namespace std;

namespace my_vector  // 里面我们使用的类也叫vector,命名空间隔离,避免与STL中的vector命名重复
{
	template <class T>  // 这里缺了内存池的模板,后面再学习
	class vector
	{
		typedef T* iterator;  // vector在物理空间上是一段连续的空间,所有这里的迭代器就是指针
	public:

	private:
		iterator _start;  // 迭代器开始位置
		iterator _finish;  // 当前迭代器指向位置,相当于size
		iterator end_of_storage;  // 该段迭代器的最终位置
	};
}

// 测试文件///
#include "my_vector.h"

int main()
{
	my_vector::vector<int> p1; // 使用自己的vector需要进行标识命名空间,否则用的就是STL中vector
	return 0;
}

二,push_back  

void push_back(const T& data) // 这里有关 两个问题的探讨,1. const 修饰; 2. 引用
		{
		}

     논의해야 할 두 가지 사항은 다음과 같습니다 . 1.  매개변수 const 수정    2. 참조에 대해        

인용에 대한 한마디

1. 참고문헌에 대하여

   우리의 데이터는 int이고 char는 괜찮고 참조를 사용하지 않으며 값 복사는 나쁘지 않지만 매개 변수 string class, vector<T> 등입니다 . 너무 많은 성능 . 따라서 여기에서 참조를 선택합니다.

2. 파라미터 const 수정

   다음 장면을 보자

int main()
{
	my_vector::vector<int> p1;   // 使用自己的vector需要进行标识命名空间,否则用的就是STL中的vector

    // 场景一:参数是 变量对象
	int a = 10;
	p1.push_back(a);  // ok的

	// 场景二:参数是临时变量,或者匿名对象
	p1.push_back(10);  // 挪,没有const 修饰,无法接收临时数据
	my_vector::vector<string> p2;
	p2.push_back(string("xxxxx"));
	return 0;
}

 

결론: 1. 벡터가 다양한 데이터 유형에 대해 상대적으로 높은 성능을 가질 수 있도록 하려면 참조가 필요하며 2. 데이터의 const 수정은 데이터의 호환성을 향상시킬 수 있습니다.

다음은 push_back + operator[] 에 대한 구현 코드  입니다 .

        size_t size() const  // 针对被const修饰的对象,使其兼容
		{
			return _finish - _start;
		}

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

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* new_start = new T[n];
			  if (_start) // 如果旧空间不是空,则需要拷贝备份
			  {
				  // memcpy(new_start, _start, sizeof(T) * size());  为啥不直接选择memcopy
                  // 这里需要重点讲解
                   for (size_t i = 0; i < n; i++)
				  {
					  new_start[i] = _start[i];  // 如果是自定义类型,则会调用其operator
				  }
				  delete[] _start;
			  }
			  _finish = new_start + size();
			  _start = new_start;
			  end_of_storage = new_start + n;
		    }
		}

		T& operator[](size_t pos)
		{
			assert(pos < size());   //进行越界判断 
			return _start[pos];
		}


		void push_back(const T& data)   // 这里有关 两个问题的探讨,1. const 修饰; 2. 引用
		{
			if (_finish == end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = data;
			_finish++;
		}

 다시 채우다

memcpy 코드를 사용하지 않는 이유는 무엇입니까?
 

T 에 대해 int 와 같은 내장 유형을 사용해 왔다 면 찾기가 어려울 것입니다. 데이터가 사용자 지정 유형 인 경우 외부 레이어는 깊은 복사본이고 내부 레이어는 여전히 얕은 복사본 입니다 .

같은 방식으로 전통적인 글쓰기 방식의 복사 구성도 개선할 수 있습니다(복사 구성은 아래에 있습니다).

셋째, 반복자 구현

       

 STL에서 두 가지 버전의 이터레이터가 있음을 알 수 있습니다. 차이점은 개체가 const 개체인지 여부입니다.

분석: const 멤버 함수에서 이것의 표시 성능은 다음과 같습니다. const    T& const this 콘텐츠 수정을 허용하지 않는 축소 권한 이므로 일부 기능을 구현해야 할 때 2개의 버전이 필요합니다.

약속하다:

        
        iterator begin()
		{
			return _start;
		}

        iterator end()
		{
			return _finish;
		}

        void func(const vector<T>& v)
		{
			const_iterator it = v.begin();
			while (it != v.end())
			{
				cout << *it << endl;
				it++;
			}
		}

iterator의 const 버전이 없으며 const 객체를 수신할 때 이 유형 오류가 발생할 수 있습니다

 변경은 간단하며 전체 구현은 다음과 같습니다.

        iterator begin()
		{
			return _start;
		}

		iterator begin() const
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		iterator end() const
		{
			return _finish;
		}

begin() 함수뿐만 아니라 다른 멤버 함수도 특정 상황에서 const 수정 버전이 필요합니다 .

 iterator begin과 end의 구현이 완료되었으므로 const 객체를 마주했을 때 for의 범위를 해결할 수 있습니다. (기본 범위는 반복자 액세스로 대체됨)

약속하다:

    my_vector::vector<int> p1;
    const my_vector::vector<int> p2;

	for (cosnt auto& i : p1)  // 调用普通迭代器  (这里的const与&,兼顾效率与兼容性)
	{
		cout << p1[i] << " ";
	}

	for (cosnt auto& i : p2)  // 调用const迭代器
	{
		cout << p1[i] << " ";
	}

넷, Pop_back

        void Pop_back()
		{
			assert(_finish > _start);
			_finish--;
		}

다섯, 삽입

     삽입에 관해서는 C언어로 삽입한다는 발상이 나쁘지 않다.

         void insert(iterator pos, const T& val)
		{
			assert(pos >= _start);
			if (size() + 1 >= capacity())
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
	
			if (pos < _finish)
			{
				iterator end = _finish;
				while ( end != pos)
				{
					*end = *(end - 1);
					end--;
				}
				*pos = val;
				_finish++;
			}
			else
			{
				push_back(val);
			}
		}

여기에 광산이 있습니다 , 모두가 그것을 꺼낼 수 있는지 봅시다? 반복자가 유효하지 않으며 위의 코드가 개선되었습니다. 

1. 보충 - 반복자 무효화

     앞에서 작성한 코드를 기반으로 다음 코드를 실험해 봅시다.

void func1()
{
	my_vector::vector<int> p2;
	p2.push_back(1);
	p2.push_back(2);
	p2.push_back(3);
	p2.push_back(4);
	p2.push_back(5);

	auto pos = find(p2.begin(), p2.end(), 3);
	p2.insert(pos, 100); // insert一般搭配find算法,一般情况下是不知道数据的迭代器
	for (const auto& n : p2)
	{
		cout << n << " ";
	}
}

 지금은 실행에 문제가 없지만 p2.push_back(5)를 주석 처리한 후에 실행할 것입니다 . 밝혀지다:

 

???!!! 버그가 있다??!!!  

 오류의 원인은 비밀이 아닙니다.

        이 시나리오에서는 삽입과 확장이 모두 필요합니다. 반복자 pos는 본질적으로 포인터입니다.확장 후 pos는 이전 공간의 위치를 ​​유지하고 pos 데이터는 업데이트되지 않으며 와일드 포인터가 잘못되었습니다.

수정 방법도 매우 간단하며 pos 업데이트 메커니즘을 개선합니다.

 자, 반복자 무효화가 우리에게 말하려는 것은 무엇입니까?   

 반복자 무효화—— 데이터를 삽입한 후 pos가 유효하지 않을 수 있습니다. pos를 사용하여 데이터에 액세스하지 마십시오.

여기에 질문이 있을 수 있습니다 . 우리는 이미 pos를 업데이트하지 않았습니까? 

답변: 삽입 기능에서 수정되고 값이 전달되며 라인 매개변수는 실제 매개변수에 영향을 미치지 않습니다 . (그런 질문을 하는 저를 용서해주세요[I can't smile])

또 어떤 분들은 제 pass pos 참고 해서 참고하겠습니다 이런 환경에서 이 문제는 확실히 풀릴 수 있는데 제 대답은 STL과 일관성 있게 해보고 디자인을 수정하는 것입니다 프레임 워크는 종종 당연한 문제입니다.몸 전체를 움직여도 여전히 파악할 수 없습니다.

여섯, erase

   즉, 데이터를 앞으로 교체하는 것은 상대적으로 간단합니다. 예: vector<int> p1(10, 2); 초기 10 데이터, 초기 값은 2입니다.

        // STL中要求erase返回被删除位置的下一个位置的迭代器
		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

			while (pos + 1 != _finish)
			{
				*pos = *(pos + 1);
				pos++;
			}
			_finish--;
			return pos;
		}

 반복자 무효화에 문제가 있습니까? 결과: 우리가 구현한 지우기는 이터레이터를 무효화하지 않습니다. STL 라이브러리에 있는 것은 어떻습니까? 대답은 컴파일러를 보는 것입니다. (우리는 STL이 모든 사람에게 C++ 라이브러리 함수의 기능적 표준을 구현하도록 지시하는 것을 목표로 하는 표준 라이브러리라는 것을 알고 있습니다. 특정 구현은 컴파일러가 구현하는 방법에 따라 다릅니다. 예를 들어 일부 컴파일러는 지우기를 구현할 때 축소될 수 있으므로 반복 장치 실패할 수 있음)

 반복자 무효화를 요약하면 : 삽입/지우기의 pos 위치를 업데이트해야 합니다. 직접 액세스하지 마십시오. 예상치 못한 결과가 발생할 수 있습니다 .

세븐, 생성자 

      처음에는 간단한 생성자를 작성했습니다. 반복자 생성, 복사 생성을 추가합니다.

1. 이터레이터 구성 

       template <class inputIterator>  // 提供一个接收参数迭代器的新模板
 25       vector (inputIterator first, inputIterator last)
 26             :_start(nullptr)
 27             ,_finish(nullptr)
 28             ,end_of_storage(nullptr)
 29       {
 30               while (first != last)
 31               {
 32                  push_back(*first);
 33                  first++;
 34               }                                                                                                     
 35       }

여기서 이것은 데이터를 초기화해야 하는 생성자 함수라는 점에 유의해야 합니다 . 잊지 마세요.

2. 기타 구조물

         그것을 깨닫고 한 번에 여러 데이터를 초기화하십시오. 

         vector(size_t n , const T& val = T())                                                                       
        {                                                                                                           
           for (size_t i = 0; i < n; i++)
          {
            push_back(val);                                                                                         
          }                                                                                                        
        }        

  

그러나 이렇게 작성하면 버그가 발생합니다. 즉, 두 매개 변수가 모두 int일 때 반복자 구성에 모호성이 있을 것 입니다. 컴퓨터가 가장 일치하는 함수를 찾기 때문에 반복자 생성자를 호출하고 후속 액세스는 반복자로 int를 사용하여 오류가 발생합니다. 

조정하는 방법?

 사실 해결 방법은 매우 간단합니다.

방법 1: size_t를 int로 변경

벡터(int n , const T& val = T()) 

방법 2: size_t를 유지하고 int 생성자를 오버로드합니다.

  vector(size_t n , const T& val = T())                                                                       
        {                                                                                                           
           for (size_t i = 0; i < n; i++)
          {
            push_back(val);                                                                                         
          }                                                                                                        
        }        

       vector(int n , const T& val = T())                                                                       
        {                                                                                                           
           for (size_t i = 0; i < n; i++)
          {
            push_back(val);                                                                                         
          }                                                                                                        
        }      

3. 복사 공사 

1) 전통적인 글쓰기 방식

         // 传统写法           
E> 56     vector (const my_vector::vector<T>& v)       
   57       :_start(nullptr)              
   58       ,_finish(nullptr)           
   59       ,end_of_storage(nullptr)    
   60     {                                                      
   61        reserve(v.size());                                  
   62        // memcpy(_start, v._start, sizeof(T) * v.size() ); 
   63         for (size_t i = 0; i < v.size(); i++ )
   64         {
   65           _start[i] = v[i];                                                                                       
   66         }                                                                         
   67        _finish += v.size();                                                       
   68     }                        

2) Modern Writing ( 함수 재사용성 향상

     void swap(my_vector::vector<T>& order)
 38     {
 39       std::swap(_start, order._start);
 40       std::swap(_finish, order._finish);
 41       std::swap(end_of_storage, order.end_of_storage);
 42     }
 43 
 44     // 拷贝构造
 45     vector (const my_vector::vector<T>& v )                                                                         
 46       : _start(nullptr)
 47       , _finish(nullptr)
 48       , end_of_storage(nullptr)
 49     {
 50       my_vector::vector<T> tmp(v.begin(), v.end());
 51       // 借用迭代器构造,然后交换数据。
 52       swap(tmp);
 53     }

여덟, 할당 기호 과부하

다음은 오류가 발생하기 쉬운 지점 에 대한 분석입니다  . 일부 학생들은 p2 = p1 & p2(Data)를 구별할 수 없습니다.

    void t()                                                                               
 81 {                                                                                      
 82   vector<int> p1 = 10; // 这是一个p1的初始化,等价于p1(10)
 83   vector<int> p2;                        
 84   p2 = p1;          // p2已经存在,这才是调用赋值重载函数                                                                                                                                                                  
 86 }      

이 할당 오버로딩과 관련하여 다음과 같이 작성하는 것이 더 쉽습니다. 

      vector<T>& operator= (vector<T> v)  // 不添加&是故意的
195     {                                                                                          
196        swap(v); 
197        return *this;                                                                                                
198     }  

내부 아이디어는 다음과 같습니다.

아홉, 크기 조정

   데이터의 크기를 조정하는 기능은 기존 객체의 크기를 수정하는 문제를 해결합니다.

        // 调整数据大小       
132     void resize(size_t n , const T& val = T())                                                                      
133     {
134        if (n > capacity())
135        {
136          reserve(n);
137        }
138 
139        if (n > size())
140        {
141          // 开始填充数据
142          while ( _finish < _start + n )
143          {
144            *_finish = val;
145            _finish++;
146          }
147        }else 
148        {
149          _finish = _start + n;
150        }
151     }


발문

   이 섹션은 여기입니다. 검색해 주셔서 감사합니다. 제안 사항이 있으면 댓글 영역에 댓글을 환영합니다. 친구에게 이익을 가져다 주면 좋아하는 것을 남겨주세요. 좋아하는 것과 우려하는 것은 블로거가 될 것입니다 원동력 주인의 창조의 .

추천

출처blog.csdn.net/qq_72112924/article/details/131616414