1, Foreword
vector source code is too complex to look at. The essence and general framework and important functions are listed here and analyzed.
2, vecotr framework construction
Before implementing vector, you need to understand this figure. Similar to string, you will also record the size and capacity
In this array, there are three pointers pointing to the starting position start, the data ending position finish and the end of the space capacity_ of_ The storage location has spare space. If the added data exceeds the capacity, the capacity needs to be increased
1. vector introduction
1. vector Is a variable size array sequence container 2. Like arrays, they are spatially contiguous 3. Use dynamic allocation array to store elements; When increasing capacity, it does not reallocate the size every time, but allocates some additional space to accommodate possible growth(finish and endofstorage between). vector It takes up more storage space. In order to obtain the ability to manage storage space and grow dynamically in an effective way. 4. Compared with other dynamic sequence containers( deques, lists and forward_lists), vector It is more efficient to access elements, and it is relatively efficient to add and delete elements at the end. For other delete and insert operations that are not at the end, the efficiency is lower lists and forward_lists Uniform iterators and references are better.
2. Framework
The following is the general framework of vector, which is very similar to the sequence table, and realizes addition, deletion, query and modification
namespace vectorSimu{ template<class T> class vector{ public: typedef T* iterator; iterator begin(); iterator end(); //Default member function vector(); vecotr(size_t n, const T& val); vector(const vector<T>& val); //copy construction //Size and rongliang size_t size(); size_t capacity(); //Capacity expansion void reserve(size_t n); void resize(size_t n, const T& val = T()); //Read container information T& operator[](size_t pos); //Modification and deletion void push_back(const T& val); void pop_back(); void insert(iterator pos, const T& val); iterator erase(iterator pos); void swap(vector<T>& v); //Deconstruction ~vector(); private: iterator _start; iterator _finish; iterator _endofstorage; }; }
3. Constructor
Construct a function without parameters and set its members to nullptr
vector() : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) {}
With parameter construction, the call can support n such Vals of T type initially. First call reserve to judge whether n exceeds the current capacity. If it exceeds the current capacity, reallocate a group of N sizes of space
vector(size_t n, const T& val = T()) :_start(nullptr) ,_finish(nullptr) ,_endofstorage(nullptr) { reserve(n); //Determine whether n exceeds capacity reserve(n); for(int i=0; i<n; ++i){ push_back(val); } }
We can also use interval iterators to construct and tail the data
//Class template to member function, and you can redefine template parameters template <class InputIterator> vector(InputIterator first, InputIterator last){ while(first != last){ push_back(*first); ++first; } }
4. Copy structure
Traditional writing method I:
vector(const vector<T>& v){ _start = new T[v.capacity()]; memcpy(_start, v._start, sizeof(T)*v.size()); _finish = _start + v.size(); _endofstorage = _start + v.capacity(); }
Traditional writing method 2:
vector(const vector<T>& v) : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { reserve(v.capacity()); for(const auto& e: v){ push_back(e); } }
The modern method of deep copy is different from the traditional one. The traditional way of writing is to open up a new set of space directly, and then store the original stored data in the new space. Modern writing is done by copying to temporary variables.
vector(const vector<T>& v) : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { vector<T> tmp(v.begin(), v.end()); this->swap(tmp); } void swap(vector<T>& v){ ::swap(_start, v._start); ::swap(_finish, v._finish); ::swap(_endofstorage, v._endofstorage); }
5. Assignment overload
In traditional writing, this method has the same effect as copy construction
vector<T>& operator=(const vector<T>& v){ if(this != &v){ delete [] _start; _start = _finish = _endofstorage = nullptr; reserve(v.capacity()); for(const auto& e:v){ push_back(e); } } return *this; }
In modern writing, the formal parameters are copied once, and then the this class is exchanged with the copied class
vector<T>& operator=(vector<T> v){ swap(v); return *this; }
6. Iterator function
In fact, the begin ning of the iterator encapsulates the first address of this type, and the end corresponds to the end of the type
typedef T* iterator; iterator begin(){ return _start; } iterator end(){ return _finish; }
7. size and capacity
This is also relatively simple. You can calculate the address
size_t size(){ return _finish - _start; } //Total capacity size size_t capacity(){ return _endofstorage - _start; }
8. Capacity expansion
When we push back the information to the vector, we need to judge whether we need to increase the capacity. If it is larger than the current container, we can open a new group of space and release the original space
void reserve(size_t n){ if(n>capacity()){ size_t sz = size(); T* tmp = new T[n]; if(_start){ //If the beginning is not empty for(size_t i=0; i<sz; ++i){ tmp[i] = _start[i]; } delete[] _start; //Release original container data } _start = tmp; _finish = _start + sz; _endofstorage = _start + n; } }
There are three situations to consider when reallocating size
In the first case, when the size of the reallocation is less than size, that is, the first address to N, the rest will be trimmed
In the second case, when N is greater than the current capacity, you need to reserve() new space again and initialize it to the given val value
In the third case, when N is less than the current capacity and larger than size, the remaining complement is the given val value
void resize(size_t n, const T& val = T()){ if(n < size()){ _finish = _start + n; }else{ if(n > capacity()){ reserve(n); } while(_finish < n + _start){ *_finish = val; ++_finish; } } }
9. pushback and popback
It should be noted here that the first push needs to determine whether the finish pointer is in the same position as the capacity pointer, so the capacity needs to be expanded
//Modification and deletion void push_back(const T& val){ if(_finish == _endofstorage){ size_t newcapacity = capacity()==0 ? 4 : capacity() * 2; reserve(newcapacity); } *_finish = val; _finish++; }
pop data is also relatively simple. Move the finish pointer forward directly
void pop_back() { assert(!empty()); --_finish; }
10. Insert and delete specified location data
Before inserting, you need to judge whether the pos is between start and finish, otherwise an error will be reported
Before inserting data, you need to judge whether the capacity is sufficient
When inserting data in the middle, the data behind the pos needs to move backward in turn. This algorithm is not efficient, so it is not recommended
iterator insert(iterator pos, const T& val){ assert(pos >= _start && pos<=_finish); if(_finish == _endofstorage){ size_t len = pos - _start; size_t newcapacity = capacity() == 0 ? 4: capacity()*2; reserve(newcapacity); pos = _start + len; } iterator end = _finish - 1; while(end >= pos){ *(end+1) = *end; --end; } *pos = val; ++_finish; return pos; }
Remove all data after pos
iterator erase(iterator pos){ assert(pos>=_start && pos < _finish); iterator it = pos + 1; while(it != _finish){ *(it-1) = *it; ++it; } --_finish; return pos; }
11. Access by subscript
//Read container information T& operator[](size_t pos){ assert(pos < size()); return _start[pos]; }
Test to verify
3, Complete code
Gitee link 🔗 🔗 🔗
👉 👉 👉 vector simulation 👈 👈 👈
It's not easy to create. If the article helps you, please like it for three times:)