[C++] Introduction to vector basic interface

vector interface directory:

1. Preliminary introduction to vector

1.1The connection and difference between vector and string

1.2 Source code parameters of vector

2. Four constructions of vector (default + filled element + iterator + copy construction)

3. Vecto’s expansion operation and mechanism

3.1resize (old friend, just look at string if you don’t know how) && reserve

 3.2 Reserve expansion mechanism

3.3 vector and malloc implement dynamically opened two-dimensional arrays respectively

118. Yang Hui Triangle - LeetCode

4. Three traversal methods

4.1operator[] checking mechanism for out-of-bounds access (a classic code error)

4.2 Three traversal methods 

 5. Vector modification operations

5.1 Use of assign and iterator

5.2 The combined use of insert and find (there is no find in the vector library)

inset && find example: 

5.3 Three swaps outside the class, within the class, and the algorithm library (the compiler has worked hard)

6. Issues you need to pay attention to when reading the source code


1. Preliminary introduction to vector

1.1The connection and difference between vector and string

1. The bottom layer of vector is also implemented using a dynamic sequence table, which is the same as string, but string stores strings by default, while vector has more powerful functions. Vector can not only store characters, but theoretically all built-in types and custom All types can be stored. The content of vector can be an object of a custom type or a variable of a built-in type .

2. The class template needs to be instantiated when using vector. Because the passed template parameters are different, the element type stored in the vector will change, so when using vector , the class template must be instantiated explicitly .


The second parameter of the class template is the space configurator . We will learn this later. Moreover, this parameter has a default value. We only use this default value, so when using vector, we only need to pay attention to the first parameter. Just one parameter is enough.

1.2 Source code parameters of vector


2. Four constructions of vector (default + filled element + iterator + copy construction)

Vector provides four construction methods - no parameter construction, n val construction, iterator interval construction and copy construction :

 

It should be noted that the iterator interval construction is a function template, that is, we can use other classes to construct vector objects:


3. Vecto’s expansion operation and mechanism

3.1resize (old friend, just look at string if you don’t know how) && reserve

1. For string and vector, reserve and resize are unique because their bottom layers are implemented by dynamic sequence tables. List does not have reserve and resize because its bottom layer is a linked list.

2. As for the reserve function, it is not officially set to be compatible with the function of shrinking . It is clearly stipulated that this function should be called in other situations, such as when the reserved space is smaller than the current situation. It will not cause space reallocation, which means that the capacity of the container vector will not be affected.

  

Why won't it scale down?

Because when we scale down, we don’t just cut it and throw it away. We have to re-open space and scale it off-site. On the surface, shrinking seems to reduce space usage, and it wants to improve the efficiency of the program, but in fact it does not improve efficiency. Shrinking requires off-site shrinking, which requires reopening space and copying data, which is expensive, so It is generally not recommended to shrink the space. Reserve does not shrink the capacity. In fact, it is also an operation of exchanging space for time.

3. The resize of vector and the resize of string also have three situations , but the function of vector is obviously more robust than that of string. The string type can only be used for characters, while vector uses resize to initialize spatial data. Any defined type can be initialized by calling the corresponding copy constructor, so its function is more robust. By default, integer types are initialized to 0, and pointer types are initialized to null pointers.

4. The default value is T() , an anonymous object of T. T may be a custom type or an intrinsic type. It can be considered that the built-in type has a constructor and the compiler will handle it specially. 

void test_vector()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	//resize和reserve对于vector和string是独有的,对于list而言,就没有reserve和resize

	cout << v.capacity() << endl;
	v.reserve(10);
	cout << v.capacity() << endl;
	v.reserve(4);
	cout << v.capacity() << endl;//并不会缩容,缩容并不会提高效率,缩容是有代价的,某种程度上就是以空间换时间。

	v.resize(8);//int、指针等这些内置类型的默认构造就把他们初始化为0和空指针这些。
	v.resize(15, 1);
	v.resize(3);//不缩容,也是采用惰性删除的方式,将size调整为3就可以了,显示数组内容的时候按照size大小显示就可以了。
}

 3.2 Reserve expansion mechanism

The expansion mechanism of vector is the same as that of string, because they are both dynamically growing arrays : under my VSCode, the expansion is about 2 times, and under Linux g++, it is the standard double expansion. The test examples are as follows:

void Vector_Capacity() {
    vector<int> v;
	size_t 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';
		}
	}
}

3.3 vector and malloc implement dynamically opened two-dimensional arrays respectively

118. Yang Hui Triangle - LeetCode

For vectors, we don’t need to do dynamic development ourselves. We can control the space size of the container through resize without malloc. Therefore, for dynamically developed two-dimensional arrays, vector is actually much simpler.

 

 

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> vv;
        vv.resize(numRows);
        //第二个参数不传就是匿名对象,会自动调用容器中元素的构造函数。内置类型或自定义类型的构造。
        for(size_t i=0; i<vv.size(); i++)
        {
            vv[i].resize(i+1, 0);//给每一个vector<int>容器预留好空间并进行初始化
            vv[i][0] = vv[i][vv[i].size() - 1] = 1;
        }
        for(int i=0; i<vv.size(); i++)
        {
            for(int j=0; j<vv[i].size(); j++)
            {
                if(vv[i][j]==0)
                {
                    vv[i][j]=vv[i-1][j]+vv[i-1][j-1];
                }
            }
        }
        return vv;

    }
};

4. Three traversal methods

4.1operator[] checking mechanism for out-of-bounds access (a classic code error)

The code shown below is a classic mistake. After we use reserve to expand, we use [ ] and subscripts to access container elements (this is wrong). After the expansion, the right to use the space does belong to us, but the operator The out-of-bounds access check mechanism of [ ] caused the crash of our program, assert(pos<size), so for element access, resize is used to adjust the size, and the main function of reserve is to reserve in advance Space, called when the space is not enough, so

The context used here is somewhat inappropriate.

4.2 Three traversal methods 

for (size_t i = 0; i < v.size(); ++i)
	{
		cout << v[i] << " ";
	}
	cout << endl;
 vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
    for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

 5. Vector modification operations

5.1 Use of assign and iterator

1. There are two ways to use assign

One is to use n values ​​to cover the container elements.

One is to use the elements in the iterator range to overwrite the container elements . The iterator here adopts the form of template, because the iterator type may not only be a vector type, but also other container types, so the template generic method is used here.

2. In fact, iterators are very convenient to use. Since the bottom layer of vector is a continuous sequence list, we can control the range of iterator assignments through pointers ± integers , so using iterators as parameters is very flexible.

void test_vector5()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);

	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	v.assign(10, 1);
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	vector<int> v1;
	v1.push_back(10);
	v1.push_back(20);
	v1.push_back(30);

	v.assign(v1.begin(), v1.end());
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	string str="hello world";
    vector<char> vs;
	vs.assign(str.begin(), str.end());
	for (auto e : vs)
	{
		cout << e << " ";
	}
	cout << endl;

	vs.assign(++str.begin(), --str.end());
	for (auto e : vs)
	{
		cout << e << " ";
	}
	cout << endl;
}

5.2 The combined use of insert and find (there is no find in the vector library)

const value_type is const T

 

Why does find use left closing and right opening? (I think there are four reasons)

 4. The end() of the iterator also points to the next position of the last element, and is also closed on the left and open on the right.

Okay, let's get back to the topic

1. For a structure like a sequence table, the efficiency of head insertion and head deletion is very low, so vector only provides push_back and pop_back. When it is inevitable to encounter head insertion and head deletion, you can occasionally use insert and erase. Head insertion and deletion are performed, and the parameters of insert and erase use iterator types as parameters because iterators are more universal.

2. If you want to insert  at a certain element position of the vector (inserting at specific positions such as 1, 2, etc. does not require find) , you definitely need to use the find interface  , but in fact, the default member function of vector does not have the find interface. ,Why is this? Because most containers use the search interface, that is, find, C++ directly puts this interface into the algorithm library to implement a function template. The implementation of this function is actually relatively simple. Just traverse the iterator and return The iterator at the corresponding position is enough, so this function is not separately used as a member function of a certain class, but is directly placed in the algorithm library. So you have to add a header file

inset && find example: 

Example 1:

Insert a single element using iterator insert (iterator position, const value_type& val):

#include <iostream>
#include <vector>

int main() {
    std::vector<int> myVector = {1, 2, 4, 5};
    std::vector<int>::iterator it = myVector.begin() + 2; // 指向位置 4 的迭代器

    myVector.insert(it, 3); // 在位置 4 插入元素 3

    for (int num : myVector) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

Example two:

Use to void insert (iterator position, size_type n, const value_type& val)insert multiple identical elements: 

#include <iostream>
#include <vector>

int main() {
    std::vector<int> myVector = {1, 2, 5};
    std::vector<int>::iterator it = myVector.begin() + 2; // 指向位置 5 的迭代器

    myVector.insert(it, 3, 4); // 在位置 5 插入 3 个元素值为 4 的元素

    for (int num : myVector) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

Example three:

Use to template <class InputIterator> void insert (iterator position, InputIterator first, InputIterator last)insert a range of elements: 

#include <iostream>
#include <vector>
#include <iterator>

int main() {
    std::vector<int> myVector = {1, 2, 7};
    std::vector<int> newElements = {3, 4, 5};

    std::vector<int>::iterator it = myVector.begin() + 2; // 指向位置 7 的迭代器

    // 在位置 7 插入 newElements 中的所有元素
    myVector.insert(it, newElements.begin(), newElements.end());

    for (int num : myVector) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

 Example 4: (This one covers the most knowledge points)
Use find to find the position of the element and insert it

void test_vector6()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
    cout << "v的元素:" ;
    for (auto e : v)
	{
		cout << e << " ";
	}
    cout <<endl;
	v.insert(v.begin(), 4);
    cout << "第一次插入" << endl;
    for (auto e : v)
	{
		cout << e << " ";
	}
    cout << endl;
	v.insert(v.begin()+2, 4);
    cout << "第二次插入" << endl;
    for (auto e : v)
	{
		cout << e << " ";
	}
    cout << endl;
	// 没有find成员
	//vector<int>::iterator it = v.find(3);

    std::vector<int> myVector = {1, 2, 3, 4, 5};
    int target = 3;
    cout << "myvector的元素:" ;
    for (auto e : v)
	{
		cout << e << " ";
	}
    cout << endl;
    // 使用 std::find 在 vector 中查找元素 target
    std::vector<int>::iterator it = std::find(myVector.begin(), myVector.end(), target);
	if (it != myVector.end()) // 如果没找到的话 find返回的就是end 也就是元素的下一个位置
	{
		myVector.insert(it, 30);//这个if很重要
	}
    cout << "第三次插入" << endl;
	for (auto e : myVector)
	{
		cout << e << " ";
	}
	cout << endl;
}

5.3 Three swaps outside the class, within the class, and the algorithm library (the compiler has worked hard)

Swap in the vector class is used to exchange two objects. In the swap implementation, std's swap is called to exchange built-in types. However, C++ has good intentions. If you accidentally use the swap format in std, it will also It doesn't matter, because there is a swap matching vector outside the class, so the swap outside the class will be called first. C++ does not want you to call the swap of the algorithm library, because if the exchange type is a custom type, the swap of the algorithm library Swap will perform three deep copies, which is extremely costly. Therefore, in order to prevent you from calling the swap of the algorithm library, C++ not only defines swap within the class, but also defines the instantiated swap outside the class. When calling, the latest swap will be called first. Matching swap .

void test_vector8()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	vector<int> v1;
	v1.swap(v);
	swap(v1, v);//一不小心这样用呢?那也不会去调用算法库里面的三次深拷贝的swap
	//这里会优先匹配vector的类外成员函数,既然有vector作为类型实例化出来的swap函数模板,就没有必要调用算法库里面的模板进行实例化
	//template <class T, class Alloc>
	//void swap(vector<T, Alloc>&x, vector<T, Alloc>&y);
}

 


6. Issues you need to pay attention to when reading the source code

1. How to look at the source code framework: extract the class member variables first and see what functions the member function declarations implement. If you want to see the implementation, go to the .c file to extract the specific functions and see them.

2. The principle of reading some books is the same as reading the source code. You need to peel off the cocoons and don’t think about getting back everything you saw in the first time. If you think this book or source code is very good, you can Read it many times and learn it step by step. After a period of time, after your knowledge reserve has improved, you may have new and different feelings when you read the book or source code again, so don't think about everything in one go. Understand it. It is good to understand 70%-80% in the first pass. If you want to learn more solidly, then increase the number of passes.

 I hope to be helpful! !

Happy Mid-Autumn Festival to everyone! !

 

 

Guess you like

Origin blog.csdn.net/weixin_62985813/article/details/133377351