Detailed explanation of C++Vector class

Table of contents

1. Introduction to Vector

2. Common use of Vector

   2.1 vector constructor

   2.2 Using vector iterators

   2.3 Vector space growth problem

   2.4 Add, delete, modify and check vector

   2.5 Vector iterator invalidation problem

3. Vector depth analysis and simulation implementation

   3.1 Simulation implementation (can be skipped)

   3.2 Analysis without memcpy


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. Common use of Vector

   2.1 vector constructor

(constructor) constructor declaration Interface Description
vector()  No parameter construction
vector (size_type n,const value_type&val=value_type()) Construct and initialize n vals
vector (const vector& x)
copy construction
vector (InputIterator fifirst, InputIterator last)
The iterator is initialized and constructed

   2.2 Using vector iterators

Logical location diagram: 

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

 Simple usage example:

void test1()
	{
		vector<int> V;
		V.push_back(1);
		V.push_back(2);
		V.push_back(3);
		V.push_back(4);
		vector<int>::iterator it = V.begin();
		while (it != V.end())
		{
			cout << *it << ' ';
			++it;
		}
		cout << endl;
		//反向迭代器
		vector<int>::reverse_iterator rit = V.rbegin();
		while (rit != V.rend())
		{
			cout << *rit << ' ';
			++rit;
		}
		cout << endl;
	}

 

   2.3 Vector space growth problem

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

Test the default expansion mechanism of vector:

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

Run under vs:

Run under gcc: 

  •  The capacity code is run under vs and g++ respectively, and you will find that the capacity under vs increases by 1.5 times, and g++ increases 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.
  • 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.
  • Resize will also be initialized while opening the space, which will affect the size.
	void test2()
	{
		vector<int> v;

		// set some initial content:
		for (int i = 1; i < 10; i++)
			v.push_back(i);

		v.resize(5);
		v.resize(8, 100);
		v.resize(12);
		cout << v.size() << endl;
		cout << "v contains:";
		for (size_t i = 0; i < v.size(); i++)
			cout << ' ' << v[i];
		cout << endl;
	}

 Use reserve to set the capacity in advance:

   2.4 Add, delete, modify and check vector

Vector additions, deletions, modifications and checks Interface Description
push_back tail plug
pop_back tail delete
find lookup (algorithm module implementation, not vector member interface)
insert Insert val before pos
erase Delete pos location data
swap Swap two vector data spaces
operator[ ] Subscript access like an array

 Test code:

void test3()
	{
		vector<int> v{ 1,2,3,4 };//列表方式初始化,C++11新语法
		v.push_back(5);
		v.push_back(6);
		v.pop_back();
		for (size_t i = 0; i < v.size(); i++)
		{
			cout << v[i] << ' ';
		}
		cout << endl;
		vector<int>::iterator pos = find(v.begin(), v.end(), 3);
		if (pos != v.end())
		{
			v.insert(pos, 0);
		}
		pos = find(v.begin(), v.end(), 3);
		v.erase(pos);
		for (size_t i = 0; i < v.size(); i++)
		{
			cout << v[i] << ' ';
		}
		cout << endl;

	}

   2.5 Vector iterator invalidation problem

Careful friends, you may have found that the find I circled above has been searched once. This is because if you no longer assign find to pos, you will not accept the return value of the iterator after insert and continue to erase, which will cause the iterator to fail. 

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

Valid operations on a vector that may invalidate its iterators are:

  • Operations that cause changes in the underlying space may cause the iterator to fail, such as: resize, reserve, insert, assign, push_back, etc.

        After expansion, the original pos has become invalid and becomes a wild pointer.

        If the insert is not expanded, pos points to the inserted element.

  • Delete the element at the specified position - erase
        After erase deletes the element at position pos, the elements after position pos will move forward without causing changes in the underlying space. In theory, the iterator should not be invalidated, but: if pos happens to be the last element, after deletion, pos happens to be end position, and the end position has no elements, then pos will be invalid.

exercise:

The correct output of the following program is ( )

int main()

{

	int ar[] ={1,2,3,4,0,5,6,7,8,9};
	int n = sizeof(ar) / sizeof(int);
	vector<int> v(ar, ar+n);
	vector<int>::iterator it = v.begin();
	while(it != v.end())
	{
		if(*it != 0)
			cout<<*it;
		else
			v.erase(it);
		it++;
	}
	return 0;
}

A. The program crashes

B.1 2 3 4 5 0 6 7 8 9

C.1 2 3 4 5 6 7 8 9

D.1 2 3 4 6 7 8 9

 Answer: When the value of the iterator is 0, it will be deleted at this time. If the iterator is not reassigned after deletion, the original iterator will become invalid. At this time, ++ for an invalid iterator will cause program crash. So the answer is A

Which of the following descriptions about iterator invalidation is wrong ( )

The insertion operation of A.vector will definitely cause the iterator to fail

The insertion operation of B.vector may not invalidate the iterator

C. The deletion operation of vector will only invalidate the iterator pointing to the deleted element and the following

The deletion operation of D.vector will only invalidate the iterator pointing to the deleted element

Answer: If the insertion operation of vector causes the underlying space to be re-opened, the iterator will become invalid. If there is enough space, then the iteration is considered invalid, because the relative position of the data has changed, and it no longer points to the previous position.

If the vector is deleted, the current element must be invalid, and the subsequent elements will involve moving data, so the iterator behind the deleted element will also be invalid.

So the answer is BD 

Solution to iterator invalidation: just reassign the iterator before using it
Modify the example code above as follows:
        vector<int>::iterator pos = find(v.begin(), v.end(), 3);
		if (pos != v.end())
		{
			pos=v.insert(pos, 0);
            //pos当前指向0
		}
        //删除3
		++pos;
		v.erase(pos);

3. Vector depth analysis and simulation implementation

   3.1 Simulation implementation (can be skipped)

        Private member variables: consistent with the above figure

		iterator _start;
		iterator _finish;
		iterator _end_of_storage;

        Constructor:

         Copy constructor: (modern writing)

swap:

         Assignment operator overloading: (modern writing)

         Destructor:

         iterators:

         size and capacity:

       reserve: (Why not use memcpy will be solved later)

         resize:

         operator[ ]:

        insert:

         erase:

         push_back, pop_back(): (reuse insert, erase)

   3.2 Analysis without memcpy

        memcpy is a shallow copy

1. memcpy is a binary format copy of memory, which copies the contents of one memory space to another memory space intact.
2. If you copy an element of a custom type, memcpy is efficient and error-free, but if you copy an element of a custom type and resource management is involved in the element of the custom type, an error will occur, because the copy of memcpy It is actually a shallow copy.

 Assuming a vector<vector<int>> type, actual memory allocation:

 memcpy allocation: (shallow copy, release space twice, the second time causes memory leak for wild pointer)

 Take a deep copy (implement it yourself):


Guess you like

Origin blog.csdn.net/bang___bang_/article/details/130335401