vector of STL C ontainer

Posted by Emperor_Jackal on Thu, 03 Feb 2022 18:50:48 +0100

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

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:

Documentation of vector s:

  1. A vector is a sequence container that represents an array of variable sizes.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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 namefunction
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 NameInterface 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:

  1. 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.
  2. 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.
  3. 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 NameInterface 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 NameInterface Role
iterator insert(iterator pos, const T& xInsert 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 presented

Niuke:

leetcode:

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:

  1. Illegal access to freed memory
  2. The meaning of iterators has changed

For operations that vector s may fail their iterators:

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

  1. Standing at the angle of a two-dimensional array, create five rows, that is, five one-dimensional arrays
vv.resize(5);
  1. 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);
}

6. What else to say

  1. Simulating stl is a tedious and time-consuming process, but it helps us understand pointers, data structures, object-oriented programming, logical thinking, and code profoundly

  2. STL Source Analysis--Hou Ji, plan to read it

  3. All code for this blog: github,gitee

Topics: C++ Container STL