Preface
C++ introduces the idea of object-oriented, and a class can better manage and manipulate some data structures than C.
In C, we use fixed-length arrays and dynamic arrays from malloc to maintain a continuous collection of the same type of data
In C++, based on the idea of object-oriented, classes for managing spatially continuous homogeneous data collections emerge. Essentially, vector s are sequence containers encapsulated in variable-size arrays.
Catalog
-
2.vector's Common Interfaces and Simulated Implementation
Common Constructions for 2.1 vector Class Objects
Capacity operations on 2.2 vector s class objects
2.3 vector class object to get the interface of elements and iterators
2.4 vector class object modification element interface
1. Introduction to vectors
When we learn STL, documentation is our tool. Learning to check documentation will make learning more productive. Here are two C++ documentation websites:- Official website: www.cppreference.com
- Common websites (updated to C++11): www.cplusplus.com
Documentation of vector s:
- A vector is a sequence container that represents an array of variable sizes.
- Like arrays, vectors use continuous storage to store elements. This means that the elements of the vectors can be accessed with subscripts as efficiently as arrays. Unlike arrays, however, their size can be dynamically changed, and their size is automatically handled by the container.
- Essentially, a vector uses a dynamically allocated array to store its elements. When a new element is inserted, the array needs to be reallocated to increase storage space. This is done by assigning a new array and moving all elements to it. This is a relatively expensive task in terms of time, because the vectors do not reassign size every time a new element is added to the container.
- vector allocation space policy: Vectors allocate some extra space to accommodate possible growth because storage is larger than what is actually needed. Different libraries use different strategies to balance the use and reallocation of space. However, reallocation should be logarithmic in size so that an element is inserted at the end at a constant time complexity.
- As a result, vector s take up more storage space in order to gain the ability to manage storage space and grow dynamically in an efficient way.
- Compared to other dynamic sequence containers (deques, lists and forward_lists), vector s are more efficient at accessing elements, and adding and deleting elements at the end is relatively efficient. Other delete and insert operations that are not at the end are less efficient.
STL is an example of generic programming, and our vector s class is naturally a class template
template <class T> class vector{ //...... };
The template parameter T is obviously the type of element we want to insert into this dynamic array, either built-in type data such as int, char, or custom type data such as string, vector s
vector<int> v1; vector<char> v2; vector<vector<int>> v3; vector<string> v4;
Looking at the member variables of the vector class again, we know that string uses:
size_t _size; ------>Number of valid characters size_t _capacity; ------>Open up total space size T* _str; ------>String Pointer
To maintain dynamically exploited space and string properties
Of course, vectors can apply all three of the above variables, but this time we use three pointers to maintain vectors for the same purpose:
T* _start; ------>Point to data block header T* _finish; ------>Point to the end of valid data (that is, the address next to the last valid element) T* _endOfStorage; ------>Point to the end of the storage space
So make an analogy:
_size == _finish - _start _capacity == _endOfStorage - _start _str = _start
2.vector's Common Interfaces and Simulated Implementation
Common Constructions for 2.1 string Class Objects
Function name | function |
---|---|
vector() | Parametric construction |
vector(int n, const T& x = T()) | Construct and initialize n x (x defaults to 0) |
template <class InputInerator> vector(InputInerator first, InputInerator last) | Initialization construction using iterators |
vector(const vector&v) | copy constructor |
~vector() | Destructor |
operator= | Assigning overload assigns one vector object to another |
Use of multiple constructors:
void Teststring() { std::vector<int> first; // Parametric construction std::vector<int> second (4,100); //Construct and initialize 4 100 std::vector<int> third (second.begin(),second.end()); // Constructing with the iterator interval of second s std::vector<int> fourth (third); // copy construction }
It is worth noting that since the iterator of the vectors is the pointer to T, the pointer is the natural iterator of the vectors, so we can initialize the vectors using the front and back pointers of the array as the left and right intervals of the iterator
int arr[] = {1,2,3,4,5,56,1}; vector<int> a(arr,arr + 2);
Simulated implementation of interface:
- Constructor
//non-parameter constructor basic_string(const T* s = "") :_size(strlen(s)) ,_capacity(strlen(s)) { _str = new T[_size + 1]; strcpy(_str, s); }
//Construct and initialize n x vector(int n, const T& x = T()) :_start(new T[n]) , _finish(_start + n) , _endOfStorage(_start + n) { for (size_t i = 0; i < n; i++) { _start[i] = x; } }
//Construct with Iterator Intervals template <class InputInerator> vector(InputInerator first, InputInerator last) : _start(nullptr) , _finish(nullptr) , _endOfStorage(nullptr) { while (first != last) { push_back(*first); first++; } }
- copy construction
vector(const vector& v) :_start(nullptr) , _finish(nullptr) , _endOfStorage(nullptr) { vector tmp(v.cbegin(), v.cend()); swap(tmp); }
- Destructor
//Destructor ~vector() { delete[] _start; _start = nullptr; _finish = nullptr; _endOfStorage = nullptr; }
- Assignment overload
//Assignment overload vector& operator=(vector v) { swap(v); return *this; }
Capacity operations on 2.2 vector s class objects
Interface Name | Interface Role |
---|---|
size() | Returns the number of elements in an array |
capacity() | Returns the current size of space allocated to the array |
empty() | Determine if the array is empty |
reserve(n) | Reset the size allocated to the array space to reserve space for the array |
resize(n, x) | Reset the array elements to n, and fill the extra space with x |
Be careful:
- For array compatibility, capacity increases by 1.5 times under vs and g++ by 2 times. Vs is the PJ version of STL, and g++ is the SGI version of STL.
- Reserve is only responsible for opening up space, and if you know how much space you need, reserve can alleviate the cost flaw of vector s.
- resize is also initialized when space is open, affecting size.
Simulated implementation of interface:
- size()
//size() interface size_t size() const { return _finish - _start; }
- capacity()
//capacity() interface size_t capacity() const { return _endOfStorage - _start; }
- empty()
// Send blank bool empty() { return size() == 0; }
- reserve(n)
void reserve(size_t n) { size_t sz = size(); T* tmp = new T[n]; memcpy(tmp, _start, size()*sizeof(T)); delete[] _start; _start = tmp; _finish = _start + sz; _endOfStorage = _start + n; }
- resize(n, x)
void resize(size_t n, const T& x = T()) { if (n <= size()) { _finish = _start + n; } else { if (n > capacity()) { reserve(n); } for (size_t i = size(); i < n; i++) { _start[i] = x; _finish++; } } }
2.3 vector class object to get the interface of elements and iterators
Interface Name | Interface Role |
---|---|
operator[ ] | Return element of pos position |
begin() | Returns the iterator for the first valid element |
end() | Returns the iterator at the next position of the last element |
Simulated implementation of interface:
- operator[]
//Overload [] T& operator[](size_t pos) { assert(pos < size()); return _start[pos]; } const T& operator[](size_t pos) const { assert(pos < size()); return _start[pos]; }
- begin() and end()
typedef T* iterator; typedef const T* const_iterator; iterator begin() { return _start; } iterator end() { return _finish; } const_iterator cbegin() const { return _start; } const_iterator cend() const { return _finish; }
2.4 string class object modification element interface
Interface Name | Interface Role |
---|---|
iterator insert(iterator pos, const T& x | Insert an element at pos address |
push_back() | End 1 element |
erase() | Delete element |
pop_back() | Delete an element |
clear() | Clear all elements and set size to zero |
swap() | Swap two class objects |
Simulated implementation of interface:
- insert()
//Insert Element iterator insert(iterator pos, const T& x) { assert(pos >= _start && pos <= _finish); if (_finish == _endOfStorage) { size_t len = pos - _start; size_t new_capacity = (_start == _finish) ? 4 : 2 * (_endOfStorage - _start); reserve(new_capacity); pos = _start + len; } for (iterator i = _finish; i >= pos + 1; i--) { *i = *(i - 1); } *pos = x; _finish++; return pos; }
- push_back()
//push_back inserts an element void push_back(const T& x) { insert(_finish, x); }
- erase()
iterator erase(iterator pos) { assert(!empty()); for (vector::iterator i = pos; i < end() - 1; i++) { *i = *(i + 1); } _finish--; return pos; }
- pop_back()
void pop_back() { erase(end()); }
- clear()
void clear() { erase(begin(), end()); }
- swap()
void swap(vector& v2) { ::swap(_start, v2._start); ::swap(_finish, v2._finish); ::swap(_endOfStorage, v2._endOfStorage); }
3. How to Familiarize with Interface - Refresh Title
Brushing questions must be the best way for beginners to master STL. Substituting questions for learning is twice as effective. Several vector s'exercises are presentedNiuke:
- Numbers in arrays that occur more than half the time (3 practices)
- Maximum sum of continuous subarrays (dp dynamic programming)
leetcode:
- Numbers that appear only once
- Yang Hui Triangle
- Delete duplicates of ordered array species
- Number II appears only once
- Number III appears only once
- Combination of phone numbers (depth first search)
4.vector's Iterator Failure Problem
The main function of an iterator is to enable the algorithm to ignore the underlying data structure, which is actually a pointer, or to encapsulate the pointer, for example, the iterator of a vector is the native pointer T*So when an iterator fails, the space pointed to by the corresponding pointer at the bottom of the iterator is destroyed and a freed space is used, with the consequence that if the invalid iterator continues to be used, the program may crash
There are two manifestations of iterator failure:
- Illegal access to freed memory
- The meaning of iterators has changed
For operations that vector s may fail their iterators:
- Operations that cause changes in their underlying space are likely iterator failures, such as resize, reserve, insert, assign, push_back, etc.
#include <iostream> using namespace std; #include <vector> int main() { vector<int> v{1,2,3,4,5,6}; auto it = v.begin(); // Increase the number of effective elements to 100, fill the extra positions with 8, and the bottom will expand during operation // v.resize(100, 8); // The purpose of reserve is to change the size of the expansion without changing the number of valid elements, which may change the underlying capacity during operation. // v.reserve(100); // During insertion of elements, expansion may occur, resulting in release of the original space // v.insert(v.begin(), 0); // v.push_back(8); // Reassigning vector s may cause underlying capacity changes v.assign(100, 8); /* Cause of error: All of the above actions may cause the vectors to expand, that is, the old space of the underlying principle of the vectors is released. It also uses the old space between releases when printing, and when it operates on it iterators, it actually operates on a piece that has already been released Space, which causes code to crash at runtime. Solution: After the above, if you want to continue working on elements in the vector s through iterators, just give it a new one Assignment is sufficient. */ while(it != v.end()) { cout<< *it << " " ; ++it; } cout<<endl; return 0; }
- Delete operation of specified location element - erase
#include <iostream> using namespace std; #include <vector> int main() { int a[] = { 1, 2, 3, 4 }; vector<int> v(a, a + sizeof(a) / sizeof(int)); // find the iterator s at three locations vector<int>::iterator pos = find(v.begin(), v.end(), 3); // Delete the data at the pos location, invalidating the pos iterator. v.erase(pos); cout << *pos << endl; // This will result in illegal access return 0; }
When erase deletes a pos position element, the element following the pos position moves forward without causing a change in the underlying space, and iterators should not theoretically fail
However, if a pos happens to be the last element, it happens to be the end position after deletion, and the end position has no elements, then pos is invalid. Therefore, when an element is deleted anywhere in a vector s, the vs considers the location iterator invalid.
The function of the code below is to remove even numbers from the array, where code one has an iterator failure problem and code two is correct
❗❗❗❗❗ Iterator Failure Solution: Re-assign iterators before use ❗❗❗❗❗
Code One
int main() { vector<int> v{ 1, 2, 3, 4 }; auto it = v.begin(); while (it != v.end()) { if (*it % 2 == 0) v.erase(it); ++it; } return 0; }
Code 2
int main() { vector<int> v{ 1, 2, 3, 4 }; auto it = v.begin(); while (it != v.end()) { if (*it % 2 == 0) it = v.erase(it); //Re-assign to Iterator else ++it; } return 0;
5. Two-dimensional dynamic arrays
The idea of building a two-dimensional dynamic array is the same as what you've learned before:Just think of each row as a one-dimensional array
vector<vector<int>> vv;
Suppose we construct a 5 × 2-D array of 5 and assign all elements to 1
- Standing at the angle of a two-dimensional array, create five rows, that is, five one-dimensional arrays
vv.resize(5);
- Stand at the angle of a one-dimensional array, giving five 1 for each row
for(int i = 0; i < vv.size(); i++) { vv[i].resize(5, 1); }