<C++> STL_vector

1. Introduction to vector

  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

The definition of vector

Method 1: Construct an empty container of a certain type.

// vector()   无参构造
vector<int> v1; //构造int类型的空容器

Method 2: Construct a certain type of container containing n vals.

//vector(size_type n, const value_type& val = value_type())
vector<int> v2(10, 2); //构造含有10个2的int类型容器

Method 3: Copy constructs a replica of a certain type of container.

//vector (const vector& x)   拷贝构造
vector<int> v3(v2); //拷贝构造int类型的v2容器的复制品

Method 4: Use an iterator to copy and construct a certain piece of content.

//vector (InputIterator first, InputIterator last)   使用迭代器进行初始化构造
vector<int> v4(v2.begin(), v2.end()); //使用迭代器拷贝构造v2容器的某一段内容

Note: This method can also be used to copy a certain content of other containers.

string s("hello world");
vector<char> v5(s.begin(), s.end()); //拷贝构造string对象的某一段内容

The space growth problem of vector

size and capacity

Obtain the number of effective elements in the current container through the size function, and obtain the maximum capacity of the current container through the capacity function.

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

int main() {
    
    
    vector<int> v(10, 2);
    cout << v.size() << endl;    //获取当前容器中的有效元素个数    10
    cout << v.capacity() << endl;//获取当前容器的最大容量      10
    return 0;
}

resize and reserve

Change the maximum capacity of the container through the reserve function, and the resize function changes the number of valid elements in the container.

Reserve rules:

1. When the given value is greater than the current capacity of the container, expand the capacity to this value.
 2. When the given value is less than the current capacity of the container, do nothing.

Resize rules:

1. When the given value is greater than the current size of the container, expand the size to this value, and the expanded element is the second given value. If not given, it defaults to 0.
 2. When the given value is smaller than the current size of the container, reduce the size to this value.

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

int main() {
    
    
    vector<int> v;
    for (int i = 0; i < 10; i++) {
    
    
        v.push_back(i);
    }
    v.resize(5);
    v.resize(8, 100);
    v.resize(12);
    // vector<int> v2(10,0);  //这也可以 ,构造开空间,不推荐,有些情况不适用
    cout << "v contains:";
    for (size_t i = 0; i < v.size(); i++) {
    
    
        cout << ' ' << v[i];// 0 1 2 3 4 100 100 100 0 0 0 0
    }
    cout << endl;

    v.resize(8);                 //修改size值
    v.reserve(8);                //小于当前容量,什么都不做
    cout << v.size() << endl;    // 8
    cout << v.capacity() << endl;// 16
    cout << v.empty() << endl;   // 0
}

The iterator of vector uses

begin and end

The forward iterator of the first element in the container can be obtained through the begin function, and the forward iterator of the last position of the last element in the container can be obtained through the end function.

rbegin and rend

The rbegin function can be used to get the reverse iterator of the last element in the container, and the rend function can be used to get the reverse iterator of the previous position of the first element in the container.
insert image description here

Example:

void PrintVector(const vector<int> &v) {
    
    
    // const对象使用const迭代器进行遍历打印
    // vector<int>::iterator it = v.begin();   //err 必须const_迭代器
    vector<int>::const_iterator it = v.begin();
    while (it != v.end()) {
    
    
        cout << *it << " ";
        it++;
    }
    cout << endl;
}

// vector的迭代器遍历
int main() {
    
    
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);
    for (size_t i = 0; i < v.size(); i++) {
    
    
        cout << v[i] << " ";// 1 2 3 4 5
    }
    cout << endl;

    vector<int>::iterator it = v.begin();
    while (it != v.end()) {
    
    
        cout << *it << " ";// 1 2 3 4 5
        it++;
    }
    cout << endl;
    // C++11 范围for,底层是由迭代器实现的
    for (auto e: v) {
    
    
        cout << e << " ";// 1 2 3 4 5
    }
    cout << endl;

    // 使用迭代器进行修改
    it = v.begin();
    while (it != v.end()) {
    
    
        (*it) *= 2;
        it++;
    }

    for (auto e: v) {
    
    
        cout << e << " ";// 2 4 6 8 10
    }
    cout << endl;

    // 使用反向迭代器进行遍历打印
    vector<int>::reverse_iterator rit = v.rbegin();
    while (rit != v.rend()) {
    
    
        cout << *rit << " ";// 10 8 6 4 2
        rit++;
    }
    cout << endl;

    PrintVector(v);// 2 4 6 8 10
    
    return 0;
}

Addition, deletion, modification of vector

push_back和pop_back

void push_back(const T& value);

push_backvalueThe function adds a value to std::vectorthe end of . It automatically resizes the container to accommodate new elements.

void pop_back();

pop_backThe function std::vectorremoves the last element from the end of . It does not return the removed element, it simply reduces the size of the container by one unit.

int main() {
    
    
    vector<int> v;
    // vector<int> v{1,2,3,4};  //C++11支持
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.pop_back();
    auto it = v.begin();
    while (it != v.end()) {
    
    
        cout << *it << " ";// 1 2 3
        it++;
    }
    cout << endl;

    return 0;
}

insert and erase

iterator insert(iterator pos, const T& value);

insertThe function inserts a value before the position pointed to by valuethe specified iterator pos. It returns an iterator pointing to the newly inserted element. After insertion, other elements are shifted back to make room.

iterator erase(iterator pos);
iterator erase(iterator first, iterator last);

eraseFunctions are used to remove an element or a range of elements std::vectorfrom it . The first version takes an iterator posthat will remove the element at that position, and returns an iterator pointing to the next element. The second version accepts two iterators firstand lastit will delete the elements in this range and return an iterator pointing to the one after the deleted element.

int main() {
    
    
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.insert(v.begin(), 0);//在容器开头插入0
    v.insert(v.begin(), 5, -1);//在容器开头插入5个-1

    v.erase(v.begin());//删除容器中的第一个元素
    v.erase(v.begin(), v.begin() + 5);//删除在该迭代器区间内的元素(左闭右开)

    return 0;
}

The above is the way to insert or delete elements by position. If you want to insert or delete by value (insert or delete at a specific value position), you need to use the find function.

find

std::findThe function is used to find a specific value in the container and returns an iterator pointing to the first matching element found. end()If no matching element is found, an iterator to the container is returned . This function can be used for various STL containers

template<class InputIterator, class T>
InputIterator find (InputIterator first, InputIterator last, const T& val);

find[first, last)The function finds elements with a value of within the specified range val. Returns an iterator to a matching element if found, or returns if not found last.

int main() {
    
    
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    vector<int>::iterator pos = find(v.begin(), v.end(), 2);//获取值为2的元素的迭代器

    v.insert(pos, 10);//在2的位置插入10

    pos = find(v.begin(), v.end(), 3);//获取值为3的元素的迭代器

    v.erase(pos);//删除3

    return 0;
}

Note: The find function is implemented in the algorithm module (algorithm), not a member function of vector.

element access

The overload of the [ ] operator is implemented in the vector, so we can also access the elements in the container through "subscript + [ ]".

at function :

reference at(size_type pos);
const_reference at(size_type pos) const;

atA function is used to access an element at a specified position and return a reference to that element. An exception is thrown if the position is outside the bounds of the vector std::out_of_range.

data function :

T* data() noexcept;
const T* data() const noexcept;

dataThe function returns a pointer to the first element in the vector. This gives you direct access to the vector's underlying array, but be careful when using it with insufficient range.

front function :

reference front();
const_reference front() const;

frontThe function returns a reference to the first element of the vector.

back function :

reference back();
const_reference back() const;

backThe function returns a reference to the last element of the vector.

Example:

int main() {
    
    
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);

    cout << v[1] << endl;// 2
    // cout<<v[7]<<endl;  //越界不报错,打印0
    cout << v.at(1) << endl;// 2
    // cout<<v.at(7)<<endl;   // 越界报错
    cout << v.data() << endl; // 返回C格式地址,0xec1740
    cout << v.front() << endl;// 1
    cout << v.back() << endl; // 5

    return 0;
}

assign/clear/swap

template <class T>
void assign(size_type n, const T& value);

assignfunction is used to assign new element values ​​to a vector, replacing the old contents in the vector.

void clear();

clearfunction to empty all elements in a vector, resetting its size to zero, but leaving the allocated memory space so that subsequent element insertions can reuse that memory.

template <class T>
void swap(T& a, T& b);

std::swapFunctions are used to swap the values ​​of two variables. It is a generic function that exchanges values ​​between variables of different types. std::swapFunctions are useful because they can perform an exchange of values ​​without the need for temporary variables, thus increasing the efficiency of your code.

Example:

int main() {
    
    
    vector<int> v1 = {
    
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    vector<int> v2 = {
    
    11, 12, 13, 14, 15};
    v1.assign(v2.begin(), v2.end());
    for (auto e: v1) {
    
    
        cout << e << " ";// 11 12 13 14 15
    }
    cout << endl;

    vector<int> v3 = {
    
    1, 2, 3, 4, 5};
    v3.assign(5, 0);
    for (auto e: v3) {
    
    
        cout << e << " ";// 0 0 0 0 0
    }
    cout << endl;

    // clear清理
    v1.clear();
    for (auto e: v1) {
    
    
        cout << e << " ";// 什么都没有打印
    }
    cout << endl;
    cout << v1.capacity() << endl;// 10;
    cout << v1.size() << endl;    // 0

    // 交换v2和v3  v2为11 12 13 14 15  v3为0 0 0 0 0
    v2.swap(v3);
    for (auto e: v3) {
    
    
        cout << e << " ";// 11 12 13 14 15
    }
    cout << endl;

    return 0;
}

3. Vector iterator invalidation problem

The main function of the iterator is to allow the algorithm to not care about the underlying data structure. The underlying data structure is actually a pointer, or a pointer is encapsulated. For example, the iterator of vector is the original ecological 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. ).

Example of Iterator Invalidation Problem

Example one:

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

int main() {
    
    
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);
    //v: 1 2 3 4 5
    vector<int>::iterator pos = find(v.begin(), v.end(), 2);//获取值为2的元素的迭代器
    v.insert(pos, 10);                                      //在值为2的元素的位置插入10
    //v: 1 10 2 3 4 5
    v.erase(pos);//删除元素2 ???error(迭代器失效)
    //v: 1 2 3 4 5
    return 0;
}

In this code, we intend to use the iterator of element 2 to insert a 10 at the position of 2 in the original sequence, and then delete 2, but what we actually get is a pointer to 2, when we insert 10 at the position of 2 , the pointer points to 10, so what we delete later is actually 10, not 2.

Example two:

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

int main() {
    
    
    vector<int> v;
    for (size_t i = 1; i <= 6; i++) {
    
    
        v.push_back(i);
    }
    vector<int>::iterator it = v.begin();
    while (it != v.end()) {
    
    
        if (*it % 2 == 0)//删除容器当中的全部偶数
        {
    
    
            v.erase(it);
        }
        it++;
    }
    return 0;
}

The code does not seem to have any errors, but if you draw a picture and analyze it carefully, you will find the problem with the code. The iterator accesses the memory space that does not belong to the container, causing the program to crash.

insert image description here

Not only that, but when the iterator traverses the elements in the container to make judgments, it does not make judgments on elements 1, 3, and 5.

Iterator invalidation solution

When using iterators, always remember one sentence: before each use, reassign the iterator.

Example 1 solution:

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

int main() {
    
    
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);
    //v: 1 2 3 4 5
    vector<int>::iterator pos = find(v.begin(), v.end(), 2);//获取值为2的元素的迭代器
    v.insert(pos, 10);                                      //在值为2的元素的位置插入10
    //v: 1 10 2 3 4 5
    pos = find(v.begin(), v.end(), 2);//重新获取值为2的元素的迭代器
    v.erase(pos);                     //删除元素2
    //v: 1 10 3 4 5
    return 0;
}

Example 2 solution:

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

int main() {
    
    
    vector<int> v;
    for (size_t i = 1; i <= 6; i++) {
    
    
        v.push_back(i);
    }
    vector<int>::iterator it = v.begin();
    while (it != v.end()) {
    
    
        if (*it % 2 == 0)//删除容器当中的全部偶数
        {
    
    
            it = v.erase(it);//删除后获取下一个元素的迭代器
        } else {
    
    
            it++;//是奇数则it++
        }
    }
    return 0;
}

For example 2, we can receive the return value of the erase function (the erase function returns the new position of the element after the deleted element), and control the logic of the code: when the element is deleted, continue to judge the element at this position (because the element at this position It has been updated and needs to be judged again).

4.vector simulation implementation

insert image description here

insert image description here

Mock implementation of vector's core framework interface

#pragma once
#include <algorithm>
#include <cassert>
#include <functional>
#include <iostream>
#include <string.h>
#include <string>
using namespace std;

template<class T>
class vector {
    
    
public:
    typedef T *iterator;
    typedef const T *const_iterator;
    vector()
        : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {
    
    
    }

    vector(size_t n, const T &val = T())
        : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {
    
    
        reserve(n);
        for (size_t i = 0; i < n; i++) {
    
    
            push_back(val);
        }
    }
    // 重载构造函数
    vector(int n, const T &val = T())
        : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {
    
    
        reserve(n);
        for (int i = 0; i < n; i++) {
    
    
            push_back(val);
        }
    }

    // 迭代器构造函数
    template<class InputIterator>
    vector(InputIterator first, InputIterator last) {
    
    
        while (first != last) {
    
    
            push_back(*first);
            first++;
        }
    }

    vector(const vector<T> &v) {
    
    
        _start = new T[v.capacity()];
        // memcpy(_start, v._start, sizeof(T) * v.size());
        // memcpy会还会导致深拷贝里的深拷贝问题。
        // 对于自定义类型,自定义类型里的内存,会被复制,那么拷贝构造的会指向同一块空间,会因调用析构函数出现报错。
        // 在进行深拷贝时,应该确保源vector的元素数量小于或等于目标vector的容量,否则会发生缓冲区溢出。
        if (v.size() <= capacity())// 确保源向量的大小小于或等于目标向量的容量
        {
    
    
            for (size_t i = 0; i < v.size(); i++) {
    
    
                // new (_start + i) T(v._start[i]);
                //  调用拷贝构造函数进行深拷贝
                _start[i] = v._start[i];
                // 进行_start[i]赋值。赋值对于自定义类型,会额外开空间,进行深拷贝。这样就解决了深拷贝里的深拷贝
            }
            _finish = _start + v.size();
        } else// 如果源向量的大小大于目标向量的容量,只拷贝目标向量能容纳的部分
        {
    
    
            for (size_t i = 0; i < capacity(); i++) {
    
    
                _start[i] = v._start[i];
            }
            _finish = _start + capacity();
        }
        _end_of_storage = _start + v.capacity();
    }

    iterator begin() {
    
    
        return _start;
    }

    iterator end() {
    
    
        return _finish;
    }

    // 这里必须加上const,两个begin函数,只是参数返回类型不同
    // 编译器无法根据函数返回类型来区分函数,
    // 因为函数重载不允许仅仅因为返回类型不同而进行区分。这将导致编译错误。
    // 加入const修饰符,以便编译器可以正确地区分这两个函数
    const_iterator begin() const {
    
    
        return _start;
    }

    const_iterator end() const {
    
    
        return _finish;
    }

    void reserve(size_t n) {
    
    
        int sz = size();
        // n>capacity才需要扩容,否则n<capacity可能会缩容
        if (n > capacity()) {
    
    
            T *tmp = new T[n];
            // 将_start的内存,拷贝到tmp中
            if (_start) {
    
    
                // memcpy是一种浅拷贝函数,这里会引起内存问题 ,因为后面_start要释放,调用析构函数
                // memcpy(tmp, _start, sizeof(T*) * size());
                for (size_t i = 0; i < size(); i++) {
    
    
                    // 手动深拷贝
                    tmp[i] = _start[i];
                }
                delete[] _start;
            }
            _start = tmp;
            _finish = _start + sz;
            // 这里_start内存发生了变化,所以_finish需要重新初始化,加上原来的sz即可
            // 不可以加上现在的size(),因为_start发生了变化,size()得不到想要的结果
            _end_of_storage = _start + n;
        }
    }

    void resize(size_t n, T val = T())// //T()默认构造,匿名对象,对于自定义类型,和内置类型比如int都会初始化
    {
    
    
        if (n < size()) {
    
    
            // 缩容
            _finish = _start + n;
        } else {
    
    
            if (n > capacity()) {
    
    
                reserve(n);
            }
            while (_finish != _start + n) {
    
    
                *_finish = val;
                _finish++;
            }
        }
    }

    void push_back(const T &x) {
    
    
        if (_finish == _end_of_storage) {
    
    
            // 扩容  一种是一开始都为NULL,另一种是需要扩容
            reserve(capacity() == 0 ? 4 : capacity() * 2);
        }

        *_finish = x;// 可能发生空指针解引用
        _finish++;
    }

    void pop_back() {
    
    
        if (!empty())
            _finish--;
    }

    void insert(iterator pos, const T &val = T()) {
    
    
        assert(pos >= _start);
        assert(pos <= _finish);
        if (_finish == _end_of_storage) {
    
    
            // 迭代器失效问题!
            // reserve扩容,会释放掉旧空间,那么pos位置也会被释放,需要更新pos,解决pos失效的问题
            // 那么pos的位置怎么算呢,相对位置! 算出pos之前的相对_start的相对位置
            int len = pos - _start;
            reserve(capacity() == 0 ? 4 : capacity() * 2);
            // 更新pos
            pos = _start + len;
        }

        iterator end = _finish - 1;
        while (end >= pos) {
    
    
            *(end + 1) = *end;
            end--;
        }
        *pos = val;
        _finish++;
    }

    // erase后迭代器需要更新,因为迭代器指向的在删除后,会改变指向
    iterator erase(iterator pos) {
    
    
        assert(pos >= _start);
        assert(pos < _finish);
        iterator start = pos + 1;
        while (start != _finish) {
    
    
            *(start - 1) = *(start);
            ++start;
        }
        _finish--;

        // 返回pos的下一个地址,pos的下一个地址的值被赋值给上一个,所以还是返回pos。
        return pos;
    }

    bool empty() const {
    
    
        return _start == _finish;
    }

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

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

    T &operator[](size_t pos) {
    
    
        return _start[pos];
    }

    const T &operator[](size_t pos) const {
    
    
        return _start[pos];
    }

    ~vector() {
    
    
        delete[] _start;
        _start = nullptr;
        _finish = nullptr;
        _end_of_storage = nullptr;
    }

private:
    // vector的成员变量为3个迭代器
    iterator _start = nullptr;
    iterator _finish = nullptr;
    iterator _end_of_storage = nullptr;
    //_start 为开始下标0
    //_finish 为数值的下一个位置
    //_end_of_storage 为容量
};

Copy problem using memcpy

Assuming that the reserve interface in the simulated vector is memcpycopied by using, what will happen to the following code?

int main() {
    
    
    vector<string> v;
    v.push_back("1111");
    v.push_back("2222");
    v.push_back("3333");
    return 0;
}

problem analysis:

  1. memcpyIt is a binary format copy of the memory, copying the contents of one memory space to another memory space intact
  2. If the elements of the built-in type are copied, memcpyit is efficient and error-free, but if the elements of the custom type are copied, and resource management is involved in the custom type elements, errors will occur, because the memcpycopy is actually a shallow copy.

insert image description here

Conclusion: If the object involves resource management, you must not use memcpy to copy between objects, because memcpy is a shallow copy, otherwise it may cause memory leaks or even program crashes.

Dynamic two-dimensional array comprehension

#include <vector>
using namespace std;
// 以杨慧三角的前n行为例:假设n为5
void test2vector(size_t n) {
    
    
    vector<vector<int>> vv(n);
    for (size_t i = 0; i < n; ++i)
        vv[i].resize(i + 1, 1);
    // 给杨慧三角出第一列和对角线的所有元素赋值
    for (int i = 2; i < n; ++i) {
    
    
        for (int j = 1; j < i; ++j) {
    
    
            vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
        }
    }
}

Construct a dynamic two-dimensional array of vv. There are a total of n elements in vv, each element is of vector type, and each row does not contain any elements. If n is 5, it is as follows: After the elements in vv are filled, the figure is as
insert image description here
follows Shown:

insert image description here

Guess you like

Origin blog.csdn.net/ikun66666/article/details/132517569