Table of contents
2.3 Vector space growth problem
2.4 Add, delete, modify and check vector
2.5 Vector iterator invalidation problem
3. Vector depth analysis and simulation implementation
3.1 Simulation implementation (can be skipped)
1. Introduction to Vector
- A vector is a sequence container representing a variable-sized array.
- Just like arrays, vectors also use contiguous storage space to store elements. That means that the elements of the vector can be accessed using subscripts, which is as efficient as an array. But unlike an array, its size can be changed dynamically, and its size will be automatically handled by the container.
- Essentially, vector uses a dynamically allocated array to store its elements. When new elements are inserted, the array needs to be resized in order to increase storage space. It does this by allocating a new array and then moving all elements into this array. In terms of time, this is a relatively expensive task, because the vector does not resize each time a new element is added to the container.
- vector allocation space strategy: vector will allocate some additional space to accommodate possible growth, because the storage space is larger than the actual storage space required. Different libraries employ different strategies for weighing space usage and reallocation. But in any case, the reallocation should be of logarithmically growing interval size, so that inserting an element at the end is done in constant time complexity.
- Therefore, vector takes up more storage space, in order to gain the ability to manage storage space, and grow dynamically in an efficient way.
- Compared with other dynamic sequence containers (deque, list and forward_list), vector is more efficient when accessing elements, and adding and removing elements at the end is relatively efficient. For other deletion and insertion operations that are not at the end, it is less efficient. Better than list and forward_list unified iterators and references.
2. Common use of Vector
2.1 vector constructor
(constructor) constructor declaration | Interface Description |
vector() | No parameter construction |
vector (size_type n,const value_type&val=value_type()) | Construct and initialize n vals |
vector (const vector& x)
|
copy construction |
vector (InputIterator fifirst, InputIterator last)
|
The iterator is initialized and constructed |
2.2 Using vector iterators
Logical location diagram:
The use of iterators | Interface Description |
begin+end |
Get the iterator/const_iterator
of the first data position
, get the iterator/const_iterator of the next position of the last data
|
rbegin+rend |
Get the reverse_iterator
of the last data position
, get the reverse_iterator of the previous position of the first data
|
Simple usage example:
void test1()
{
vector<int> V;
V.push_back(1);
V.push_back(2);
V.push_back(3);
V.push_back(4);
vector<int>::iterator it = V.begin();
while (it != V.end())
{
cout << *it << ' ';
++it;
}
cout << endl;
//反向迭代器
vector<int>::reverse_iterator rit = V.rbegin();
while (rit != V.rend())
{
cout << *rit << ' ';
++rit;
}
cout << endl;
}
2.3 Vector space growth problem
capacity space | Interface Description |
size | Get the number of data |
capacity | Get the size of the capacity |
empty | Determine whether it is empty |
resize | Change the size of the vector |
reserve | Change the capacity of the vector |
Test the default expansion mechanism of vector:
void TestVectorExpand()
{
size_t sz;
vector<int> v;
sz = v.capacity();
cout << "making v grow:\n";
for (int i = 0; i < 100; ++i)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
Run under vs:
Run under gcc:
- The capacity code is run under vs and g++ respectively, and you will find that the capacity under vs increases by 1.5 times, and g++ increases by 2 times . Don't think that the capacity of vector is doubled, and the specific increase is defined according to specific needs. vs is the PJ version STL, and g++ is the SGI version STL.
Reserve is only responsible for opening up space. If you know how much space is needed, reserve can alleviate the cost defect of vector capacity expansion. Resize will also be initialized while opening the space, which will affect the size.
void test2()
{
vector<int> v;
// set some initial content:
for (int i = 1; i < 10; i++)
v.push_back(i);
v.resize(5);
v.resize(8, 100);
v.resize(12);
cout << v.size() << endl;
cout << "v contains:";
for (size_t i = 0; i < v.size(); i++)
cout << ' ' << v[i];
cout << endl;
}
Use reserve to set the capacity in advance:
2.4 Add, delete, modify and check vector
Vector additions, deletions, modifications and checks | Interface Description |
push_back | tail plug |
pop_back | tail delete |
find | lookup (algorithm module implementation, not vector member interface) |
insert | Insert val before pos |
erase | Delete pos location data |
swap | Swap two vector data spaces |
operator[ ] | Subscript access like an array |
Test code:
void test3()
{
vector<int> v{ 1,2,3,4 };//列表方式初始化,C++11新语法
v.push_back(5);
v.push_back(6);
v.pop_back();
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << ' ';
}
cout << endl;
vector<int>::iterator pos = find(v.begin(), v.end(), 3);
if (pos != v.end())
{
v.insert(pos, 0);
}
pos = find(v.begin(), v.end(), 3);
v.erase(pos);
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << ' ';
}
cout << endl;
}
2.5 Vector iterator invalidation problem
Careful friends, you may have found that the find I circled above has been searched once. This is because if you no longer assign find to pos, you will not accept the return value of the iterator after insert and continue to erase, which will cause the iterator to fail.
The main function of the iterator is to let the algorithm not care about the underlying data structure. The underlying layer is actually a pointer, or it encapsulates the pointer . For example, the iterator of vector is the original pointer T* . Therefore , the invalidation of the iterator actually means that the space pointed to by the corresponding pointer at the bottom of the iterator is destroyed, and a piece of space that has been released is used, resulting in a program crash (that is, if you continue to use the invalid iterator, the program may crash. ).
Valid operations on a vector that may invalidate its iterators are:
- Operations that cause changes in the underlying space may cause the iterator to fail, such as: resize, reserve, insert, assign, push_back, etc.
After expansion, the original pos has become invalid and becomes a wild pointer.
If the insert is not expanded, pos points to the inserted element.
- Delete the element at the specified position - erase
exercise:
The correct output of the following program is ( )
int main() { int ar[] ={1,2,3,4,0,5,6,7,8,9}; int n = sizeof(ar) / sizeof(int); vector<int> v(ar, ar+n); vector<int>::iterator it = v.begin(); while(it != v.end()) { if(*it != 0) cout<<*it; else v.erase(it); it++; } return 0; }
A. The program crashes
B.1 2 3 4 5 0 6 7 8 9
C.1 2 3 4 5 6 7 8 9
D.1 2 3 4 6 7 8 9
Answer: When the value of the iterator is 0, it will be deleted at this time. If the iterator is not reassigned after deletion, the original iterator will become invalid. At this time, ++ for an invalid iterator will cause program crash. So the answer is A
Which of the following descriptions about iterator invalidation is wrong ( )
The insertion operation of A.vector will definitely cause the iterator to fail
The insertion operation of B.vector may not invalidate the iterator
C. The deletion operation of vector will only invalidate the iterator pointing to the deleted element and the following
The deletion operation of D.vector will only invalidate the iterator pointing to the deleted element
Answer: If the insertion operation of vector causes the underlying space to be re-opened, the iterator will become invalid. If there is enough space, then the iteration is considered invalid, because the relative position of the data has changed, and it no longer points to the previous position.
If the vector is deleted, the current element must be invalid, and the subsequent elements will involve moving data, so the iterator behind the deleted element will also be invalid.
So the answer is BD
vector<int>::iterator pos = find(v.begin(), v.end(), 3);
if (pos != v.end())
{
pos=v.insert(pos, 0);
//pos当前指向0
}
//删除3
++pos;
v.erase(pos);
3. Vector depth analysis and simulation implementation
3.1 Simulation implementation (can be skipped)
Private member variables: consistent with the above figure
iterator _start;
iterator _finish;
iterator _end_of_storage;
Constructor:
Copy constructor: (modern writing)
swap:
Assignment operator overloading: (modern writing)
Destructor:
iterators:
size and capacity:
reserve: (Why not use memcpy will be solved later)
resize:
operator[ ]:
insert:
erase:
push_back, pop_back(): (reuse insert, erase)
3.2 Analysis without memcpy
memcpy is a shallow copy
1. memcpy is a binary format copy of memory, which copies the contents of one memory space to another memory space intact.2. If you copy an element of a custom type, memcpy is efficient and error-free, but if you copy an element of a custom type and resource management is involved in the element of the custom type, an error will occur, because the copy of memcpy It is actually a shallow copy.
Assuming a vector<vector<int>> type, actual memory allocation:
memcpy allocation: (shallow copy, release space twice, the second time causes memory leak for wild pointer)
Take a deep copy (implement it yourself):