One of the first articles of C++ lets you master vector (simulation implementation)

insert image description here

1. Why do you want to simulate vector?

insert image description here

The purpose of simulating the implementation of vector is to deeply understand and learn the working principle and implementation details of the vector container in the C++ standard library.
Vector is one of the most commonly used containers in the C++ standard library. It provides the function of dynamic array, and has the characteristics of automatic expansion and memory management, which makes it very convenient to use.

Simulating the implementation of vector has the following advantages:

  1. Learning data structures and algorithms : Implementing vectors requires operations such as adding, deleting, checking, and modifying dynamic arrays, which allows developers to learn relevant knowledge about data structures and algorithms.
  2. Understand memory management : Vector needs to manage dynamic memory allocation and deallocation. It is important to understand how to use pointers, dynamically allocate memory, and prevent memory leaks.
  3. Understand template programming : vector is a general container that can store elements of different types. The simulation implementation of vector needs to involve template programming, so that developers can better understand the template mechanism in C++.
  4. Improve programming skills : By implementing vector, developers can exercise their programming skills and code design capabilities, and at the same time discover some potential problems and room for improvement.

Although the vector container has been provided in the C++ standard library, the simulated implementation of vector is very helpful for learning and understanding the underlying implementation of C++ as well as algorithms and data structures. In actual development, we usually use the vector in the standard library, because it has been fully tested and optimized, and has better performance and stability. However, by simulating the implementation of vector, we can better understand the principles and design ideas behind it.

2. What issues should be paid attention to when simulating and implementing vector?

When simulating and implementing vector, you need to pay attention to the following key issues:

  1. Dynamic memory management : vector is a dynamic array that needs to dynamically allocate memory at runtime.To ensure that memory is allocated and freed correctly when inserting and deleting elements, avoiding memory leaks and dangling pointers
  2. Automatic expansion : vector has the function of automatic expansion,When the number of elements in the container exceeds the current capacity, it is necessary to reallocate a larger memory space and copy the original elements to the new memory space
  3. Template programming : vector is a general container,Can store elements of different types. Therefore, it is necessary to use template programming when simulating and implementing vector to ensure that the container can be applied to different types of data.
  4. iterator : vector shouldProvides an iterator for accessing elements in the container. Iterators should support various operations on pointers, such as pointer arithmetic, dereferencing, etc.
  5. Member functions and interfaces : mock implementations of vector shouldProvides member functions and interfaces similar to vector in the standard library, such as push_back, pop_back, size, capacity, etc.
  6. Exception safety : In the process of simulating the implementation of vector,To ensure exception handling, to avoid memory leaks or data structure destruction due to exceptions.
  7. Performance optimization : The vector implemented by the simulation may not be as good as the vector in the standard library, but there are still some things to considerperformance optimizationmethods, such as avoiding frequent memory allocation and release, reducing unnecessary copies, etc.
  8. Test : During the implementation process, sufficient testing should be carried out to ensure that various functions and interfaces of vector canwork correctly, and canHandle various edge cases

In general, the simulated implementation of vector needs to consider issues such as data structure, memory management, template programming, exception handling, and performance optimization to ensure that the implemented container can work stably and efficiently. Simulating the implementation of vector is a good exercise for learning and exercising programming skills, and at the same time, it can also give a deeper understanding of the principles and design ideas of containers in the C++ standard library.

3.vector simulation implementation

3.1 Member variable definition of namespace vector

To define member variables, we must first understand what are the member variables of vector. Although it is very similar to our sequence table in the data structure, it has many differences. Three very important member variables in vector are :

iterator _start: This is pointing toiterator to the first element, which represents the starting position of a valid element in the container.
iterator _finish: This is pointing toiterator to the next position of the last element, that is, beyond the valid range. It is used to indicate the end position of the elements already stored in the container.
iterator _end_of_storage: This is pointing toiterator to the end position of the allocated memory space, which is the end position of the memory space actually allocated by the container. This location may change as the container expands.

This definition is to separate the memory management and element access of the container. Through these three iterators, vector can effectively manage the memory space of the container and the access of elements.

When the number of elements in the vector exceeds the capacity of the current memory space, an expansion operation will be performed. At this point, a larger memory space will be reallocated, and the existing elements will be copied from the old memory to the new memory, and then the values ​​of the _start, _finish and _end_of_storage iterators will be updated to correctly point to the new memory space.

The way to define these three iterators enables vector to maintain the consistency of memory management and element access under different operations. They decouple memory allocation from element access, making vector implementations more flexible and efficient.

So in the first step we need to define the iterator and the corresponding iterator members

namespace xzq//为了和原vector做区分,放在不同名的命名空间内
{
    
    
	template<class T>//模板命名
	class vector
	{
    
    
	public:
		typedef T* iterator;             //变量模板迭代器指针
		typedef const T* const_iterator; //常量模板迭代器指针
	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};
}

3.2 Definition of iterator member functions begin() and end()

iterator begin()
{
    
    
	return _start;
}

iterator end()
{
    
    
	return _finish;
}

const_iterator begin() const
{
    
    
	return _start;
}

const_iterator end() const
{
    
    
	return _finish;
}

iterator begin(): Returns an iterator (non-const version) pointing to the first element in the container.

iterator end(): Returns an iterator pointing to the position after the last element in the container (non-const version).

const_iterator begin() const: Returns an iterator pointing to the first element in the container (const version, for constant objects).

const_iterator end() const: Returns an iterator pointing to the position after the last element in the container (const version, for constant objects).

These functions allow you to iterate through the elements of a vector. The difference is that the iterator returned by the non-const version of the function can be used to modify the element, while the iterator returned by the const version of the function can only be used to access the element and cannot be modified.

3.3 Constructors, copy constructors, and destructors

3.3.1 Constructor

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

This is adefault constructorAn implementation that initializes an empty vector object. In this constructor, passInitialize member variables _start, _finish and _end_of_storage as nullptr, indicating that the vector objectNo memory space is allocated and no elements are stored

This constructor is used when creating an empty vector object, because there are no elements to store at the beginning, so the memory space pointers are all set to nullptr. Then in the vector operation, when elements need to be added, the memory space will be allocated according to the needs, and the values ​​of these pointers will be gradually adjusted.

In short, the purpose of this constructor is to create an empty vector object and initialize the internal iterator members to prepare for subsequent operations.

3.3.2 Copy constructor

vector(const vector<T>& v)Three writing methods
The first writing method: directly allocate memory and assign values ​​​​one by one

vector(const vector<T>& v)
{
    
    
	_start = new T[v.size()]; // v.capacity()也可以
	//memcpy(_start, v._start, sizeof(T)*v.size());
	for (size_t i = 0; i < v.size(); ++i)
	{
    
    
		_start[i] = v._start[i];
	}
	_finish = _start + v.size();
	_end_of_storage = _start + v.size();
}

The suggestion here is not to use the memcpy function. memcpy will not call the copy constructor of the element type, which may cause uninitialized or released memory problems. It is only applicable to POD (Plain Old Data) types, for non-POD types (such as types containing pointers, custom constructors, etc.), using memcpy may cause errors.

Advantages :

Intuitive : It clearly shows how to manually allocate memory and assign elements one by one.

Disadvantages :

Repeated operations : Need to assign elements one by one, there may be some unnecessary repeated operations.
Memory Leaks : If an exception occurs between allocating memory and assigning a value, it can cause a memory leak.

The second way of writing: using push_back and reserve to achieve

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

Advantages :

Avoid repeated operations : use push_back to directly insert elements, avoiding repeated operations of assigning values ​​one by one.

Disadvantages :

Performance issues : Using push_back may allocate and release memory multiple times, resulting in poor performance.
Possible exceptions : If memory allocation in push_back fails, an exception may result.

The third way of writing: use temporary vector to exchange

vector(const vector<T>& v)
    :_start(nullptr)
    , _finish(nullptr)
    , _end_of_storage(nullptr)
{
    
    
    vector<T> tmp(v.begin(), v.end());
    swap(tmp);
}

This way of writing is based on the copy construction of another iterator type.

Advantages :

Exchange technique : By constructing a temporary vector and then exchanging the current vector and the temporary vector, repeated memory operations are avoided.

Disadvantages :

Additional memory required : Additional memory is required to construct the temporary vector.
Not intuitive : It may not be intuitive, you need to understand the exchange mechanism of swap.

Considering,The third way of writing may be the best choice, because it avoids repeated memory operations, and at the same time ensures exception safety by using the swap trick. However, the second way of writing is also a good choice, especially when high performance is not required and the code is clear and easy to understand. As for the first way of writing, it may not be recommended due to possible memory leaks and unsafe exceptions.

template <class InputIterator>
vector(InputIterator first, InputIterator last)

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

This constructor templateImplemented initialization of vector by iterator range. It iterates over the input iterator range, inserting each element into the vector via push_back. This way is very suitable for handling the initialization of the container, regardless of the number and type of elements in the container, it is convenient to initialize a new vector through the iterator range. This constructor takes advantage of the generality provided by iterators, making the code more flexible and applicable to various element types, including built-in types and user-defined types. This constructor template fully demonstrates the idea of ​​generic programming in C++, making the code applicable to different situations and data types.

The previous copy construction reuses this type of iterator copy construction.

vector(size_t n, const T& val = T())

vector(size_t n, const T& val = T())
    :_start(nullptr)
    , _finish(nullptr)
    , _end_of_storage(nullptr)
{
    
    
    reserve(n);
    for (size_t i = 0; i < n; ++i)
    {
    
    
        push_back(val);
    }
}

This constructor implementsInitialize the vector by repeatedly inserting the same value. It accepts two parameters, the first parameter is the number of elements n to be initialized, and the second parameter is the initial value val of the element. The function of this constructor is to create a vector containing n same value val. Inserting the same value into the container through a loop realizes the requirement of quickly initializing the same value. by default,The value of val is initialized using the default constructor, so it can be omitted when calling

This constructor can improve the efficiency of initialization in some scenarios, especially when a large number of elements with the same value need to be initialized, the overhead of repeatedly calling push_back is avoided, and the value is directly copied in memory.

But it is better to add the following version

vector(int n, const T& val = T())
    :_start(nullptr)
    , _finish(nullptr)
    , _end_of_storage(nullptr)
{
    
    
    reserve(n);
    for (size_t i = 0; i < n; ++i)
    {
    
    
        push_back(val);
    }
}

prevent the following from happening

vector<int> v2(3, 10);

In this way, when copy construction is called, it may be called first template <class InputIterator> vector(InputIterator first, InputIterator last), because the two operands are of the same type, when the compiler finds a matching function in the class, it will find a closer match, and an error will occur at this time, soIt is best to add the following int version, you may wonder, why not use the int version directly, because the definition of the vector copy function is an unsigned integer.
insert image description here

3.3.3 Destructors

~vector()
{
    
    
    delete[] _start;  // 释放存储元素的内存块
    _start = _finish = _end_of_storage = nullptr;  // 将成员指针置为 nullptr
}

In the destructor, firstUse delete[] to delete the memory block that stores the element_start, which frees the memory used by all elements stored in the vector. then,Set all three member pointers of _start, _finish, and _end_of_storage to nullptr, to avoid problems with calling them again after the destructor has finished executing.

The role of this destructor is to ensure that when the vector object is destroyed, the occupied memory is released to avoid memory leaks.

3.4 Capacity function, element access function and addition, deletion and modification function

3.4.1 Capacity function

capacity()

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

The capacity() member function returns the capacity of the storage space allocated inside the vector, that is, the number of elements that can currently be accommodated, rather than the number of elements currently stored (obtained by using size()). The implementation of this function is simple, itReturns the difference obtained by subtracting the _start pointer from the _end_of_storage pointer, that isThe number of elements that can be accommodated in the currently allocated storage space. This is because in the implementation of vector, continuous storage space is allocated between _start and _end_of_storage, so the distance between the two represents the capacity.

size()

size_t size() const
{
    
    
    return _finish - _start;
}

The size() function is used to return the number of elements in the vector, which is implemented byCalculate the distance (offset) between the _finish pointer and the _start pointer to get. Since _finish and _start point to the end position and start position of the elements in the vector memory space respectively, the distance between them is the number of elements in the vector. This function returns the number of elements in the vector in O(1) time complexity.

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

The reserve(size_t n) function is used forReserve memory space to ensure that the vector can accommodate at least n elements to reduce frequent memory reallocation. If the current memory space is not enough to accommodate n elements, it will reallocate a new memory space and copy the original elements to the new memory.

This function first checks whether the memory requested to be reserved exceeds the current capacity (capacity). If exceeded, it willReallocate a new memory space and copy the original elements to the new memory. ThenUpdate _start, _finish and _end_of_storage pointers to reflect new memory layout

Note that this functionWill not change the number of elements in the vector, only change the allocation of memory

Here are some differences and precautions for using for loop and memcpy for memory copying :

Data type restrictions :memcpyis copied byte-wise,The constructor of the element type will not be executed. If you store class objects, it may cause unexpected behavior. When using a for loop, the element's copy constructor is called to ensure that each element is properly copied.
Portability :memcpy is a low-level memory copy function, its use maywill be affected by different platforms. Using a for loop is a more general and portable way.
Type safety : usememcpy needs to make sure the elements are sized and laid out properly, otherwise data corruption may result. The for loop, on the other hand, ensures type correctness when copying each element.
Readability : The for loop is easier to understand and maintain, and it is possible to clearly see the operation of each element.

To sum up, if it is a simple data type,Using memcpy may be more efficient, but in cases involving class objects, complex data structures, or type safety, using a for loop is more efficient安全和推荐

resize()

void resize(size_t n, const T& val = T())
{
    
    
    if (n < size()) {
    
    
        // 缩小容器大小
        for (size_t i = n; i < size(); ++i) {
    
    
            _start[i].~T();  // 销毁元素
        }
        _finish = _start + n;
    }
    else if (n > size()) {
    
    
        if (n > capacity()) {
    
    
            // 需要重新分配内存
            T* new_start = new T[n];
            for (size_t i = 0; i < size(); ++i) {
    
    
                new (&new_start[i]) T(std::move(_start[i]));  // 移动构造元素
                _start[i].~T();  // 销毁原来的元素
            }
            delete[] _start;
            _start = new_start;
            _finish = _start + size();
            _end_of_storage = _start + n;
        }
        // 构造新添加的元素
        for (size_t i = size(); i < n; ++i) {
    
    
            new (&_start[i]) T(val);
        }
        _finish = _start + n;
    }
    // 若 n == size() 则不需要进行任何操作
}

first,Function to check if container size needs to be reduced. If n is less than the current size ( size() ), branch to downsize the container.

In this case, the function willCall _start[i].~T() one by one, that is, the destructor of the corresponding position element to destroy the redundant elements. Then,Update the _finish pointer to _start + n, indicating that the container only contains the first n elements. If == n is greater than the current size ==, then the function willCheck if memory needs to be reallocated. If n is greater than the current capacity (capacity()), it will enter the branch to reallocate memory.

In this case, the function willCreate a new memory block new_start,ThenThe elements in the current container are moved and constructed into a new memory block one by one, and the elements in the original position are destroyed at the same time. Then,Release the original memory block _start,andUpdate _start, _finish and _end_of_storage pointers

Whether shrinking the container size or reallocating memory, the function constructs new elements at the end of the container to fill up to n elements.Use new (&_start[i]) T(val) to construct an element at the specified position, where val is the default value passed in. Then, update the _finish pointer to _start + n

3.4.2 Element access functions

operator[]

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

T& operator[](size_t pos)
{
    
    
    assert(pos < size());
    return _start[pos];
}

These two overloaded operator[] functions are used to access the elements at the specified positions in the vector respectively. one isconst version for getting element values ​​on const objects,the other isnon-const version for getting or modifying element values ​​on non-const objects. These two functions passCheck whether the index pos is within the legal range to ensure no out-of-bounds access. The non-const version returns a non-const reference, which allows the user to modify the value of the elements in the vector through this operator. Whereas the const version returns a const reference, which is used to access the element value on the const object, but does not allow modification.

front()

T& front()
{
    
    
	assert(size() > 0);

	return *_start;
}

First, the function uses the assert macro to check whether the size of the container is greater than 0, ensuring that there is at least one element in the container.

Then, the function returns a reference to the element pointed to by the _start pointer, which is the first element of the container.

back()

T& back()
{
    
    
	assert(size() > 0);

	return *(_finish - 1);
}

First, the function uses the assert macro to check whether the size of the container is greater than 0, ensuring that there is at least one element in the container.

Then, the function returns a reference to the element pointed to by the _finish - 1 pointer, which is the last element of the container.

3.4.3 Add, delete, check and modify functions

push_back()

void push_back(const T& x)
{
    
    
		if (_finish == _end_of_storage)
		{
    
    
			reserve(capacity() == 0 ? 4 : capacity() * 2);
		}

		*_finish = x;
		++_finish;
		//insert(end(), x);
}

First, the functionCheck whether the _finish pointer is equal to _end_of_storage, that is, whether the capacity limit of the current container has been reached. If the capacity limit has been reached, theNot enough space for new element,thereforememory reallocation is required

When reallocating memory, use the reserve function,ifThe current capacity is 0, then allocate space for at least 4 elements, otherwise expand the capacity to twice the current capacity. Then, the functionAssign the new element x to the position pointed by the _finish pointer, which is the end of the current container. Then,Point _finish to the next available location by incrementing the _finish pointer

pop_back()

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

first,The function uses the assert macro to check whether the _finish pointer is greater than the _start pointer. This is an assertion to ensure that no error occurs if _finish is less than or equal to _start. The assertion triggers an error if _finish is not greater than _start, helping to catch problems during development.

then,The function decrements the _finish pointer by 1 so that it points to the second-to-last element in the container, i.e. the last element is deleted

iterator insert()

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

First, the functionUse assert macro to check if pos is between _start and _finish, to ensure that the insertion position is within the legal range. Then, the functionCheck if container is full, that is, whether _finish is equal to _end_of_storage. ifThe container is full and needs to be expanded. firstCalculate the offset len ​​of the insertion position relative to _start, so that pos can be repositioned after insertion to prevent the iterator from being invalidated due to expansion. Then expand the capacity according to the expansion rules,Move _start into newly allocated memory. Before inserting an element into the container, you needMove the element after the insertion position back one position. useA loop starts at _finish - 1 and shifts the elements backward one by one to make room for new elements. WillThe new element x is inserted at the specified position pos. at last,Increment the _finish pointer, indicating that there is one more element in the container, and thenReturns an iterator over the insertion position

iterator erase()

iterator erase(iterator pos)
{
    
    
	assert(pos >= _start);
	assert(pos < _finish);

	iterator begin = pos + 1;
	while (begin < _finish)
	{
    
    
		*(begin - 1) = *begin;
		++begin;
	}

	--_finish;

	return pos;
}

first,The function uses the assert macro to check if pos is between _start and _finish, to ensure that the deletion location is within the legal range.

Then, the functionCreate an iterator named begin whose initial value is the next position of pos, that is, the position after the element to be deleted

Next, use aThe loop moves the begin position and the elements behind it one position forward one by one, overwriting the element to be deleted. This is equivalent to overwriting the element at the deletion position, thereby realizing the deletion operation.

at last,Decrement the _finish pointer, indicating that there is one less element in the container, and thenReturns an iterator over the original delete position

3.4.4 Assignment operator overloading

operator=

vector<T>& operator=(vector<T> v)
{
    
    
    swap(v);
    return *this;
}

This assignment operator overloading is a common way to implement the Copy-and-Swap Idiom in C++, which is used to implement custom assignment operations. The assignment operator accepts a value-passed parameter v, here throughCopy by value to construct a temporary vector object,ThenCall swap(v) to exchange the data of the current object with the temporary object. so,The data of the temporary object will be assigned to the current object, and the temporary object will be destroyed at the end of the function, thus releasing the resources of the original current object.

The advantage of this notation is that it canAvoid the extra overhead caused by copy construction and destruction,therebyImproved efficiency. At the same time, the implementation of the exchange operation usually performs pointer exchange for each member within the class, so the actual copy of the data will not be involved.

In general, this assignment operator implements an efficient assignment operation, which is one of the commonly used implementations in C++.

Vector simulation implements all codes

#pragma once
#include <assert.h>
#include <iostream>

namespace bit
{
    
    
	template<class T>
	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(const vector<T>& v)
		//{
    
    
		//	_start = new T[v.size()]; // v.capacity()也可以
		//	//memcpy(_start, v._start, sizeof(T)*v.size());
		//	for (size_t i = 0; i < v.size(); ++i)
		//	{
    
    
		//		_start[i] = v._start[i];
		//	}
		//	_finish = _start + v.size();
		//	_end_of_storage = _start + v.size();
		//}

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

		vector(int n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
    
    
			reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
    
    
				push_back(val);
			}
		}

		vector(size_t n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
    
    
			reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
    
    
				push_back(val);
			}
		}

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

		void swap(vector<T>& v)
		{
    
    
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}

		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
    
    
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);
		}

		// v1 = v2
		vector<T>& operator=(vector<T> v)
		{
    
    
			swap(v);
			return *this;
		}

		~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);
					for (size_t i = 0; i < sz; ++i)
					{
    
    
						tmp[i] = _start[i];
					}
					delete[] _start;
				}

				_start = tmp;
				_finish = _start + sz;
				_end_of_storage = _start + n;
			}
		}


		void resize(size_t n, const T& val = T())
		{
    
    
			if (n < size()) {
    
    
				// 缩小容器大小
				for (size_t i = n; i < size(); ++i) {
    
    
					_start[i].~T();  // 销毁元素
				}
				_finish = _start + n;
			}
			else if (n > size()) {
    
    
				if (n > capacity()) {
    
    
					// 需要重新分配内存
					T* new_start = new T[n];
					for (size_t i = 0; i < size(); ++i) {
    
    
						new (&new_start[i]) T(std::move(_start[i]));  // 移动构造元素
						_start[i].~T();  // 销毁原来的元素
					}
					delete[] _start;
					_start = new_start;
					_finish = _start + size();
					_end_of_storage = _start + n;
				}
				// 构造新添加的元素
				for (size_t i = size(); i < n; ++i) {
    
    
					new (&_start[i]) T(val);
				}
				_finish = _start + n;
			}
			// 若 n == size() 则不需要进行任何操作
		}

		void push_back(const T& x)
		{
    
    
				if (_finish == _end_of_storage)
				{
    
    
					reserve(capacity() == 0 ? 4 : capacity() * 2);
				}

				*_finish = x;
				++_finish;
			//insert(end(), x);
		}

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

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

		// stl 规定erase返回删除位置下一个位置迭代器
		iterator erase(iterator pos)
		{
    
    
			assert(pos >= _start);
			assert(pos < _finish);

			iterator begin = pos + 1;
			while (begin < _finish)
			{
    
    
				*(begin - 1) = *begin;
				++begin;
			}

			--_finish;
			
			return pos;
		}

		T& front()
		{
    
    
			assert(size() > 0);

			return *_start;
		}

		T& back()
		{
    
    
			assert(size() > 0);

			return *(_finish - 1);
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

}

epilogue

Interested friends can pay attention to the author, if you think the content is good, please give a one-click triple link, you crab crab! ! !
It is not easy to make, please point out if there are any inaccuracies
Thank you for your visit, UU watching is the motivation for me to persevere.
With the catalyst of time, let us all become better people from each other! ! !

Guess you like

Origin blog.csdn.net/kingxzq/article/details/132104190