0 - Preface
reference resources:
http://c.biancheng.net/view/6749.html
http://c.biancheng.net/view/6803.html
Underlying implementation mechanism of C++ vector (STL vector) (easy to understand)
C++ STL vector add elements (push_back() and empty)_ Back () detailed explanation
Several ways of deleting elements in C++ STL vector (super detailed)
1 - what is a vector
Vector container is one of the most commonly used containers in STL. It is very similar to array container and can be regarded as an "upgraded version" of C + + ordinary array. The difference is that array implements a static array (an array with fixed capacity), while vector implements a dynamic array, that is, elements can be inserted and deleted. In this process, vector will dynamically adjust the memory space occupied, and there is no need for manual intervention in the whole process.
Vector is often called vector container, because it is good at inserting or deleting elements at the tail, which can be completed in constant time, and the time complexity is O(1); For inserting or deleting elements in the head or middle of the container, it takes longer (moving elements takes time), and the time complexity is linear order O(n).
2 - create vector
#include <vector> using namespace std; //Create an empty vector vector<double> values; //Initialize while creating vector<int> primes {2, 3, 5, 7, 11, 13, 17, 19}; //Only the initial size is specified when creating, and 0 is filled by default vector<double> values(20); //When creating, specify the initial size as num and fill it with the specified value vector<double> values(num, value); //copy construction vector<char>value1(5, 'c'); vector<char>value2(value1); //Copy construction - truncation range int array[]={1,2,3}; vector<int>values(array, array+2);//values will save {1,2} vector<int>value1{1,2,3,4,5}; vector<int>value2(std::begin(value1),std::begin(value1)+3);//value2 save {1,2,3}
Note that if the vctor container is not initialized, no space is allocated to it because there are no elements in the container. When the first element is added (such as using the push_back() function), the vector automatically allocates memory.
3 - vector member function
4 - vector iterator
When the above functions are actually used, their return value types can be replaced by the auto keyword, and the compiler can determine the type of the iterator by itself.
At the same time, you can also use the global begin() and end() functions to get iterations from the container
vector<int>values{1,2,3,4,5}; auto first = values.begin(); auto end = values.end(); //Equivalent to the following statement auto first = std::begin(values); auto end = std::end (values);
The only difference between cbegin()/cend() member functions and begin()/end() is that the former returns a const type forward iterator, which means that the iterators returned by cbegin() and cend() member functions can be used to traverse the elements in the container or access the elements, but the stored elements cannot be modified.
The vector template class also provides rbegin() and rend() member functions, which respectively represent random access iterators pointing to the last element and the previous position of the first element, also known as reverse iterators. It should be noted that when using the reverse iterator for + + or – operations, + + refers to the iterator moving one bit to the left, - refers to the iterator moving one bit to the right, that is, the functions of the two operators are "interchanged".
In addition, when the vector container requests more memory, all elements in the container may be copied or moved to a new memory address, which will invalidate the iterator previously created. Therefore, to be on the safe side, whenever the capacity of the vector container changes, we need to reinitialize the iterator we created earlier
#include <iostream> #include <vector> using namespace std; int main() { vector<int>values{1,2,3}; cout << "values Address of the first element of the container:" << values.data() << endl; auto first = values.begin(); auto end = values.end(); //Increase the capacity of values values.reserve(20); cout << "values Address of the first element of the container:" << values.data() << endl; first = values.begin(); end = values.end(); while (first != end) { cout << *first ; ++first; } return 0; } //Output: values Address of the first element of the container: 0164 DBE8 values Address of the first element of the container: 01645560 123
5 - access vector element
vector<int> values{1,2,3,4,5}; //Normal array subscript cout << values[0] << endl; //at() member function values.at(0) = values.at(1) + values.at(2) + values.at(3) + values.at(4); //Head and tail elements values.front() = 10; values.back() = 20; //data() member function cout << *(values.data() + 2) << endl;//Output the value of the third element in the container
-
Container name [n] is a way to obtain elements. It is necessary to ensure that the value of subscript n does not exceed the capacity of the container (which can be obtained through the capacity() member function), otherwise an out of bounds access error will occur.
-
Like the array container, the vector container also provides the at() member function. When the index passed to at() is out of bounds, STD:: out will be thrown_ of_ Range exception.
-
The function of the data() member function is to return a pointer to the first element in the container
Since the iterator of vector is the most advanced random access iterator, there are three ways to traverse container elements:
//Traversal mode ① for(i = v.begin(); i != v.end(); ++i) cout << *i; //Traversal mode ②: bidirectional iterators do not support comparison with "<" for(i = v.begin(); i < v.end(); ++i) cout << *i; //Traversal mode ③: bidirectional iterators do not support random access to elements with subscripts for(int i=0; i<v.size(); ++i) cout << v[i];
Note that the traversal mode ③ must use size() instead of capacity(). The capacity of the vector container (expressed in capacity) refers to the maximum number of elements that can be saved by the container without allocating more memory; The size of the vector container (expressed in size) refers to the number of elements it actually contains.
6 - underlying implementation mechanism of vector
vector source code: the main content is three iterators
//_ Alloc represents the memory allocator. This parameter hardly needs our attention template <class _Ty, class _Alloc = allocator<_Ty>> class vector{ ... protected: pointer _Myfirst; pointer _Mylast; pointer _Myend; };
- Myfirst refers to the starting byte position of the vector container object;
- Mylast refers to the last byte of the current last element;
- Myend points to the last byte of the memory space occupied by the entire vector container
On this basis, combining the three iterators in pairs can also express different meanings, such as:
- _ Myfirst and_ Mylast can be used to represent the memory space currently used in the vector container;
- _ Mylast and_ Myend can be used to represent the current free memory space of the vector container;
- _ Myfirst and_ Myend can be used to represent the capacity of the vector container.
For an empty vector container, Myfirst, Mylast, and Myend are null because there is no space allocation for any element.
When the size and capacity of the vector are equal (size==capacity), that is, when it is fully loaded, if you add elements to it, the vector needs to be expanded. The expansion process of vector container needs to go through the following three steps:
- Completely discard the existing memory space and re apply for more memory space;
- Move the data in the old memory space to the new memory space in the original order;
- Finally, free up the old memory space.
When the vector container is expanded, the amount of more memory space requested by different compilers is different. Take VS as an example, it will expand the capacity of the existing container by 50%.
7 - push_back empty_back
vector<int> values{}; values.push_back(1); values.emplace_back(1);
push_back() emplace_back() adds elements at the end of the vector. The difference is that the underlying implementation mechanism is different.
- push_back() when adding an element to the tail of the container, the element will be created first, and then the element will be copied or moved to the container (if it is copied, the previously created element will be destroyed later);
- emplace_ When back () is implemented, it creates this element directly at the end of the container, eliminating the process of copying or moving elements
Obviously, do the same thing, push_ The underlying implementation process of back() is better than that of empty()_ Back () is more cumbersome, in other words, empty_ Back() is more efficient than push()_ Back () high. Therefore, in actual use, it is recommended that you give priority to use empty_ back().
Note, empty_ Back() is a new addition to C++ 11.
8 - insert empty
The vector container provides two member functions, insert() and replace (), which are used to insert elements at the specified location of the container
insert():
#include <iostream> #include <vector> #include <array> using namespace std; int main() { std::vector<int> demo{1,2}; //First format usage demo.insert(demo.begin() + 1, 3);//{1,3,2} //Second format usage demo.insert(demo.end(), 2, 5);//{1,3,2,5,5} //Third format usage std::array<int,3>test{ 7,8,9 }; demo.insert(demo.end(), test.begin(), test.end());//{1,3,2,5,5,7,8,9} //Fourth format usage demo.insert(demo.end(), { 10,11 });//{1,3,2,5,5,7,8,9,10,11} }
emplace():
Empty () is a new member function added to the C++ 11 standard, which is used to insert a new element before the specified position of the vector container. Again, empty () can only insert one element at a time, not multiple.
std::vector<int> demo1{1,2}; //Empty() can only insert one int type element at a time demo1.emplace(demo1.begin(), 3);
Since both empty () and insert() can insert new elements into the vector container, who is more efficient? The answer is empty (). When inserting an element, replace () constructs the element directly at the specified position of the container, rather than generating it separately and then copying (or moving) it to the container. Therefore, in practical use, it is recommended that you give priority to use empty ().
9 - delete element
In addition to the member functions provided by itself, you can also use some global functions to complete the deletion operation
- When erase() function deletes an element, it will move the subsequent elements in the deletion position forward one after another, and reduce the size of the container by 1.
//Syntax format iterator erase (pos); //Delete a single element vector<int>demo{ 1,2,3,4,5 }; auto iter = demo.erase(demo.begin() + 1);//Delete element 2 and iter points to element 3 //Delete range element vector<int> demo{ 1,2,3,4,5 }; auto iter = demo.erase(demo.begin()+1, demo.end() - 2);//Delete 2 3, it=er points to element 4
- If you don't care about the order of elements in the container, you can combine swap() and pop_ The back() function can also delete the element at the specified position in the container. The swap() function is defined in the header files < algorithm > and < utility >, and one of them can be introduced when using.
vector<int>demo{ 1,2,3,4,5 }; swap(demo[1],demo[4]); demo.pop_back();
- If you want to delete all elements in the container with the same value as the specified element, you can use the remove() function, which is defined in the < algorithm > header file. Note that after the remove () function is executed on the container, since the function does not change the original size and capacity of the container, the previous method cannot be used to traverse the container, but the iterator returned by remove () needs to be used to complete the correct traversal as in the following program.
vector<int>demo{ 1,3,3,4,3,5 }; auto iter = std::remove(demo.begin(), demo.end(), 3);//Delete all element 3, and iter points to element 4 for (auto first = demo.begin(); first < iter;++first) { cout << *first << " "; }
The implementation principle of remove () is that when traversing the elements in the container, once the target element is encountered, mark it, and then continue traversing until a non target element is found, that is, use this element to cover the first marked position, and mark the position of the non target element at the same time, waiting for a new non target element to cover it. Therefore, if all the elements of the demo container in the above program are output, the result is 1.45.43.5, that is, the position pointed by the iterator returned by remove() is useless data until the end.
Since multiple specified elements in the demo container are deleted through the remove() function, the size and capacity of the container have not changed, and the previously stored elements are retained in the remaining positions. We can use the erase() member function to delete these "useless" elements. When remove () is used to delete the specified element in the container, it is often used with the erase() member function.
vector<int>demo{ 1,3,3,4,3,5 }; auto iter = std::remove(demo.begin(), demo.end(), 3); demo.erase(iter, demo.end());
- If you want to delete all the elements in the container, you can use the clear() member function
vector<int>demo{ 1,3,3,4,3,5 }; demo.clear();
10 - free memory
After calling erase() or clear() to delete the element, the size() of the vector does decrease, but the memory originally occupied by the deleted element has not been released. If it is not released, it may cause a waste of memory. Using swap() can free up extra memory without destructing the vector.
reference resources: The swap() function in std::vector uses parsing and de duplication
11 - vector< bool >
Vector < bool > is not a vector container that stores bool type elements
The reasons are described in detail in the following blog post:
reference resources: On the particularity of vector -- why is it not an STL container