[C++] Basic use of vector

Isn't the road to climb more exciting than standing at the top?
Insert image description here



1. The 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 in theory 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.
When using vector, you need to instantiate the class template. Because the passed template parameters are different, the element type stored in the vector will change. Therefore, when using vector, you need to instantiate the class template 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.

Insert image description here

void test_vector1()
{
    
    
	string s;
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	//string和vector底层都是用数组实现的,所以他们都支持迭代器、范围for、下标+[]的遍历方式
	for (size_t i = 0; i < v.size(); i++)
	{
    
    
		cout << v[i] << " ";//string和vector的底层都是数组,所以可以使用[],但list就不能使用[]了,所以万能的方法是迭代器。
	}
	cout << endl;

	vector<int>::iterator it = v.begin();//iterator实际是某种类型的重定义,在使用时要指定好类域。
	while (it != v.end())
	{
    
    
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto e : v)//不就是迭代器吗?
	{
    
    
		cout << e << " ";
	}
	cout << endl;

	cout << v.max_size() << endl;//int是10亿多,因为int占4个字节,42亿÷4。
	cout << s.max_size() << endl;//max_size的大小是数据的个数,我的编译器的char是21亿多。不用管他,这接口没价值。

	//vector<char> vstr;
	//string str;
	//vector<char>不能替代string,即使两者都是字符数组也不行,因为string有\0
}

2. Vector expansion operation

1.resize() (default value is anonymous object)&& reserve()

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

2.
Regarding the reserve function, the official has not set it 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.

Insert image description here

3.
Some people may think that shrinking only requires discarding the remaining space, but in fact it is not that simple. You can see from the fact that the free space cannot be released in two free phases during the C language stage. A piece of space that has been applied for is It is an independent entity. You cannot say that you reserve part of the space and discard the remaining part. This is not acceptable. It is essentially related to the memory management of the operating system. If you are interested in this part of knowledge, you can go and study it.

4.
However, it is worth noting that on the surface, shrinking seems to reduce the space usage. It wants to improve the efficiency of the program, but in fact it does not improve the efficiency. Shrinking requires off-site shrinking, and requires reopening space and copying. Data is expensive, so it is generally not recommended to reduce the space.

Insert image description here
5.
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.

Insert image description here

void test_vector2()
{
    
    
	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大小显示就可以了。
}

2.reserve expansion mechanism on g++ and vs

1.
The expansion mechanism uses 1.5 times the size on VS, and 2 times the size on g++. For space expansion, if it is set too large, it will cause a waste of space. If it is set small, it will not be enough, and it will lead to frequent expansion and performance degradation. loss, and 2 times the size can be said to be just right. As for why Microsoft engineers chose 1.5 for expansion, it was due to certain factors affecting the memory.

Insert image description here

void test_vector_expand()//扩容机制大概是1.5倍进行扩容
{
    
    
	size_t sz;
	vector<int> v;

	//v.reserve(100);//已知开辟空间大小时,我们应该调用reserve来提前预留空间,进行扩容。

	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. Design concepts of reserve remote expansion and shrink_to_fit remote shrinking

1.
The design concept of reserve is not to reduce the capacity. Even if you manually call reserve to reduce the capacity, the compiler will not care about you. The size of the space will never change, and the value of capacity will always remain unchanged. Such a design The idea is essentially to trade space for time, because off-site scaling requires opening space and copying data, which is a waste of time.

2.
On the contrary, shrink_to_fit is a shrink function, which forcibly reduces the capacity size to a value that matches the size. Its design concept is to exchange space for time, but the mobile phone or PC used by daily people actually has enough space. Yes, what is not enough is time, so it is better not to use this function, unless you will definitely not insert data later and will not perform any modify operations, then you can try to return the space to the operating system to reduce the space usage rate.

void test_vector7()
{
    
    
	vector<int> v;
	v.reserve(10);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	
	//C++不会太推荐使用malloc来进行空间的初始化了,因为很有可能存在自定义类型对象没有初始化的问题,如果用new会自动调用构造。

	//设计理念就是不去缩容,因为异地缩容的代价很大,所以就算你用reserve或是resize调整大小以改变capacity,但编译器不会管你。
	// 因为它的设计理念不允许它这么做,而遇到shrink_to_fit就没辙了,因为他是缩容函数!!!
	//---不动空间,不去缩容,以空间换时间的设计理念,因为缩容虽然空间资源多了,但是时间就长了,为了提高时间,就用空间换。
	//shrink_to_fit就是反面的函数,进行了缩容。
	v.reserve(3);
	cout << v.size() << endl;
	cout << v.capacity() << endl;

	//设计理念:以时间换空间,一般缩容都是异地缩容,代价不小,一般不要轻易使用。通常情况下,我们是不缺空间的,缺的是时间。
	v.shrink_to_fit();//缩容函数,代价很大,通常的缩容的方式,就是找一块新的较小的空间,然后将原有数据拷贝进去
	

	v.resize(3);
	cout << v.size() << endl;
	cout << v.capacity() << endl;

	v.clear();//clear都是不动空间的
}

4.vector and malloc implement dynamically developed two-dimensional arrays respectively

Yang Hui triangle

1.
For C language implementation, a return value and two output parameters are needed to return to the background interface. The first parameter represents the size of the two-dimensional array. For this question, we know the size of the returned two-dimensional array, but You may not know other questions, but leetcode's background test cases are designed uniformly. In order to be compatible with other questions that do not know the size of the returned array, output parameters are used here to control them. The same is true for the second parameter.

2.
The two-dimensional array, the elements in the two-dimensional array, and the number of elements in the one-dimensional array in the two-dimensional array need to be returned. These arrays need to be malloced.

//后台实现的地方:int returnSize=0;int returnColumnSizes[];
//grenerate(num,&returnSize,&returnColumnSizes);//函数调用
int** generate(int numRows, int* returnSize, int** returnColumnSizes) 
{
    
    
    int** p = (int**)malloc(numRows * sizeof(int*));
    *returnSize = numRows;
    *returnColumnSizes = (int*)malloc(sizeof(int) * numRows);
    for (int i = 0; i < numRows; i++)
    {
    
    
        p[i] = (int*)malloc(sizeof(int) * (i + 1));//给每一个二维数组的元素动态开辟一个空间
        (*returnColumnSizes)[i] = i + 1;
        p[i][0] = p[i][i] = 1;
        //for(int j = i; j < i + 1; j++)//条件控制有问题
        //for (int j = 0; j < i + 1 ; j++)
        for (int j = 1; j < i ; j++)
        {
    
    
            if (p[i][j]!=1)
            {
    
    
                p[i][j]=p[i-1][j-1]+p[i-1][j];
            }
        }
    }
    return p;
}

3.
For vector, we don’t need to do dynamic development ourselves. We can control the space size of the container through resize, and there is no need for malloc to dynamically open it. 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;

    }
};

3. Vector element access operations

1. The checking mechanism of operator[] and at for out-of-bounds access (a classic code error)

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

Insert image description here

2.
For the use of at, the out-of-bounds access check mechanism used is to throw an exception. After catch catches the exception, we can print out the exception information. We can see that the exception information is an invalid vector subscript, which also refers to the passed subscript. It is useless. In fact, the subscript position exceeds the size.

Insert image description here

void test_vector4()
{
    
    
	vector<int> v;
	v.reserve(10);
	//这是一段经典的错误代码。reserve改变的是capacity主要解决的是插入数据时涉及到的扩容问题。
	//resize改变的是size,平常对于vector的容量增加,还是resize多一点,resize可以直接包揽reserve的活,并且除此之外还能初始化空间。
	for (size_t i = 0; i < 10; i++)
	{
    
    
		//v[i] = i;
		//对于[]的使用实际会有一个assert的越界断言的检查。assert(i<v.size()),你的下标不能超过size。
		//虽然reserve的确把空间开辟好了,你也能用这个空间,但是[]他有size和下标的越界检查,所以你的程序就会报错。
		//reserve=开空间+初始化(有默认值)
		v.at(i) = i;//抛异常

		//断言报错真正的问题是在于,release版本下面,断言就失效了,断言在release版本下面是不起作用的。
	}
}
int main()
{
    
    
	//test_vector1();
	//test_vector2();
	//test_vector_expand();
	try
	{
    
    
		//test_vector6();
	}
	catch (const exception& e)
	{
    
    //在这个地方捕获异常然后进行打印
		cout << e.what() << endl;//报错valid vector subscript,无效的vector下标
	}
	//test_vector4();
	//test_vector5();
	//test_vector6();
	test_vector7();

	//string算是STL的启蒙,string的源码我们就不看了
	return 0;
}

4. Vector modification operations

1.The combined use of assign and iterators

1.
There are two ways to use assign. One is to use n values ​​to cover the container elements, and the other is to use the elements in the iterator range to cover the container elements. The iterator here adopts the template form, because the iterator The type may not only be a vector type, but also other container types, so template generics are used here.

Insert image description here

2.
And iterators are actually 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;
	//void assign(size_type n, const value_type & val);前后类型分别为size_t和模板参数T的类型typedef,那就是int类型
	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);
	//template <class InputIterator> void assign(InputIterator first, InputIterator last);
	v.assign(v1.begin(), v1.end());
	for (auto e : v)
	{
    
    
		cout << e << " ";
	}
	cout << endl;

	string str("hello world");
	v.assign(str.begin(), str.end());//assign里面的迭代器类型是不确定的,既有可能是他自己的iterator也有可能是其他容器的迭代器类型。
	for (auto e : v)
	{
    
    
		cout << e << " ";
	}
	cout << endl;

	v.assign(++str.begin(), --str.end());//你可以控制迭代器的区间,指定assign的容器元素内容的长度。
	for (auto e : v)
	{
    
    
		cout << e << " ";
	}
	cout << endl;

}

2. Use insert and find together

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. To perform head insertion and head deletion, and the parameters of insert and erase both use iterator types as parameters, because iterators are more universal.

2.
If you want to insert at a certain position in the vector, 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.

Insert image description here

void test_vector6()//测试insert和find
{
    
    
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);

	v.insert(v.begin(), 4);
	v.insert(v.begin() + 2, 4);
	//vector的迭代器能够直接+2,源自于vector的底层是连续的空间,迭代器也就是连续的,而list的底层不是连续空间,而是一个个的节点,
	//所以迭代器就不能++来进行使用了

	//如果要在vector里面的数字3位置插入一个元素的话:std::find,find的实现就是遍历一遍迭代器,找到了就返回对应位置的迭代器。
	//而vector、list、deque等容器都会用到find,所以find直接实现一个模板即可。
	vector<int>::iterator it = std::find(v.begin(), v.end(), 3);
	//string没有实现find的原因是string不仅仅要找某一个字符,而且还要找一个字串,所以算法库的find就不怎么适用,string就自己造轮子
	v.insert(it, 30);
	for (auto e : v)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
}

3. Three swaps outside the class, within the class, and algorithm library

1.
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 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 algorithm The swap of the library will be deep copied three times, 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, which will be given priority when calling. Call the best 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);
}

5. 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.

Guess you like

Origin blog.csdn.net/erridjsis/article/details/129225059