[C++] STL_vector use and common interface simulation implementation

insert image description here

1. Introduction to vector

vector document introduction

  1. A vector is a sequence container representing a variable-sized array.
  2. 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.
  3. 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.
  4. 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.
  5. Therefore, vector takes up more storage space, in order to gain the ability to manage storage space, and grow dynamically in an efficient way.
  6. 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. The use of vector

Vector is very important in practice. In practice, we only need to be familiar with common interfaces. The following lists which interfaces should be mastered and simulated.

2.1 Definition of vector

( constructor ) constructor declaration Interface Description
vector() (emphasis) No parameter construction
vector(size_type n, const value_type& val = value_type() Construct and initialize n vals
vector (const vector& x); (emphasis) copy construction
vector (InputIterator first, InputIterator last); Initialize construction using iterators

Code:

template<class T>
    class vector
    {
    
    
    public:
    	typedef T* iterator;//typedef愿意给别人用就放在public,不想就放在private
        typedef const T* const_iterator;

    	vector()
        {
    
    }

        vector(int n, const T& value = T())
        {
    
    
            reserve(n);
            for (size_t i = 0; i < n; i++)
            {
    
    
                push_back(value);
            }
        }

        template<class InputIterator>

        vector(InputIterator first, InputIterator last)
        {
    
    
            while (first != last)
            {
    
    
                push_back(*first);
                ++first;
            }   
        }

        vector(const vector<T>& v)
        {
    
    
            reserve(v.capacity());
            for (auto& e : v)
            {
    
    
                push_back(e);
            }
        }

    private:
    iterator _start = nullptr; // 指向数据块的开始
    iterator _finish = nullptr; // 指向有效数据的尾
    iterator _endOfStorage = nullptr; // 指向存储容量的尾
};

2.2 Use of vector iterators

The bottom layer of the iterator in vector is also a raw pointer.

The use of iterators Interface Description
begin + end (emphasis) 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

insert image description here
Simulation implementation:

typedef T* iterator;//typedef愿意给别人用就放在public,不想就放在private
typedef const T* const_iterator;

iterator begin()
{
    
    
    return _start;
}

iterator end()
{
    
    
    return _finish;
}

const_iterator begin() const
{
    
    
    return _start;
}

const_iterator end() const
{
    
    
    return _finish;
}

Use:
Iterators are generally used in traversal, let's take a look.

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    
    
	vector<int> v;
	//我们这里使用push_back来插入数据
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	//迭代器方式遍历
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
    
    
		cout << *it << " ";
		++it;
	}

	return 0;
}

insert image description here

2.3 The space growth problem of vector

capacity space Interface Description
size Get the number of data
capacity Get the size of the capacity
empty Determine whether it is empty
resize (emphasis) Change the size of the vector
reserve (emphasis) Change the capacity of the vector

reserve interface:
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.

void reserve(size_t n)//reserve只扩不缩
{
    
    
    if (n > capacity())
    {
    
    
        T* tmp = new T[n];
        size_t sz = size();//这里必须先记下sz,_finish要是直接+size()会出问题
                           //_start指的是新空间,调用size(),size()内部会出问题
                           //因此先记下来后面用最合适
        if (_start)
        {
    
    
            //memcpy是浅拷贝,会出问题
            //memcpy(tmp, _start, sizeof(T) * sz);
            for (size_t i = 0; i < size(); i++)
            {
    
    
                tmp[i] = _start[i];
            }
            delete[] _start;
        }

        _start = tmp;
        _finish = _start + sz;
        _endOfStorage = _start + n;
    }
}

resize interface:
resize will also be initialized while opening the space, which will affect the size.

void resize(size_t n, const T& value = T())//匿名对象/临时对象具有常性,需要const修饰
{
    
    
    if (n <= size())//缩容
    {
    
    
        _finish = _start + n;
    }
    else
    {
    
    
        reserve(n);//这里可以不用判断是否要扩容,reserve里面会判断

        while (_finish < _start + n)
        {
    
    
            *_finish = value;
            ++_finish;
        }
    }
}

Several other interfaces are relatively simple and can be implemented directly:

size_t size() const
{
    
    
    return _finish - _start;
}

size_t capacity() const
{
    
    
    return _endOfStorage - _start;
}

bool empty()
{
    
    
	return _finish - _start == 0;
}

Notice:

There is a difference when expanding the capacity, the capacity under VS is increased by 1.5 times, and the capacity of g++ is increased 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.
Let's test it out:

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    
    
	vector<int> v;
	size_t sz = v.capacity();

	for (size_t i = 0; i < 100; i++)
	{
    
    
		v.push_back(i);
		if (sz != v.capacity())
		{
    
    
			sz = v.capacity();
			cout << "capacity changed:" << sz << endl;
		}
	}

	return 0;
}

insert image description here
insert image description here

3. Add, delete, check and modify vector

Vector add, delete, check and modify Interface Description
push_back (emphasis) tail plug
pop_back (emphasis) tail delete
find look up
insert Insert val before position
erase Delete the data at the position
swap Exchange the data space of two vectors
operator[] (emphasis) access like an array

3.1 push_back (emphasis)

We sort out the ideas of the tail plug:
1. First judge whether the capacity is full, and if it is full, expand the capacity first. Note here, whether it is empty when inserting the tail, use the three-wood operator to judge here, if it is empty, expand 4 spaces first, otherwise expand by 2 times.
2. Tail plug, then ++_finish.

void push_back(const T& x)
{
    
    
    if (_finish == _endOfStorage)
    {
    
    
        reserve(capacity() == 0 ? 4 : 2 * capacity());
    }

    *_finish = x;
    ++_finish;
}

3.2 pop_back (emphasis)

When deleting the tail, we still first judge
that we need to judge empty this time, use the assertion assert(_finish - _start != 0), and then delete the tail, let _finish– be fine, and directly overwrite the next time the tail is inserted .

void pop_back()
{
    
    
    assert(_finish-_start != 0);

    --_finish;
    //erase(end() - 1);
}

3.3 operator[] (emphasis)

The overload of [] is to return the data at the pos position, which is relatively simple and directly kills.
Here we give two interfaces, one is read-only and the other can be modified.

T& operator[](size_t pos)//写
{
    
    
    assert(pos < size());//判断位置是否合法
    return _start[pos];
}

const T& operator[](size_t pos)const//读
{
    
    
    assert(pos < size());
    return _start[pos];
}

3.4 insert

Insert is to insert a data at pos position.
Ideas:
1. First judge whether the position of pos is legal;
2. If it is judged to be full, it needs to be expanded. When expanding, it needs to pay attention to the problem of iterator failure;
3. Because data is inserted, there will be moving data, so the data needs to be moved first , we move the data one position back from the back to the front
, and move it to the pos position; 4. Then insert data into the pos position, and finally return to the pos position.

iterator insert(iterator pos, const T& x)
{
    
    
    assert(pos >= _start);
    assert(pos <= _finish);

    if (_finish == _endOfStorage)
    {
    
    
        size_t len = pos - _start;//先记下_start到pos位置的距离,因为扩容后迭代器pos就会失效
        reserve(capacity() == 0 ? 4 : 2 * capacity());
        pos = _start + len;//新的空间需要更新迭代器pos
    }

    iterator end = _finish - 1;
    //挪动数据
    while (end >= pos)
    {
    
    
        *(end + 1) = *end;
        --end;
    }

    *pos = x;
    ++_finish;

    return pos;
}

3.5 erase

Erase is to delete the data at pos position.
Ideas:
1. Determine whether the pos position is legal;
2. Move the data, move the data forward sequentially from the pos position to the end , and directly overwrite the data at the pos position with the data of pos+1;
3. –_finish, return to the pos position That's it.

iterator erase(iterator pos)
{
    
    
    assert(pos >= _start);
    assert(pos < _finish);

    iterator it = pos + 1;
    //挪动数据
    while (it < _endOfStorage)
    {
    
    
        *(it - 1) = *it;
        ++it;
    }
    --_finish;

    return pos;
}

3.6 swap

Our vector's swap can be realized by directly applying the library function's swap.

void swap(vector<T>& v)
{
    
    
    std::swap(_start, v._start);
    std::swap(_finish, v._finish);
    std::swap(_endOfStorage, v._endOfStorage);
}

*** End of article ***

Guess you like

Origin blog.csdn.net/Ljy_cx_21_4_3/article/details/132500200