[C++] The use of vector and iterator invalidation

Preface
After the previous study of string, we have mastered many string class functions. Many class functions in vector are similar to those in string. For example, the use of iterators is the same in all containers. Here we no longer Introduction, let's learn some commonly used functions of the vector class.

1. Document introduction of vector

2. Vector represents the sequence container of variable-sized array in C++, and the header file <vector> needs to be included when using it. Just like arrays, vector also uses continuous storage space to store elements.


1. The constructor of vector

There are four main constructors of vector, let's demonstrate them one by one

insert image description here

The first is the default construction with no parameters, the second is that we can use n vals to initialize the vector, the third is the copy construction of the vector, and the fourth is the initial construction using an iterator.

int main()
{
    
    
	vector<int> v1;
	v1.push_back(1); //push_back的作用就是插入一个元素
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	vector<int> v2(4); //用4个匿名对象进行初始化
	vector<int> v3(4, 9); //用4个9进行初始化
	vector<int> v4(++v1.begin(), --v1.end());//用迭代器进行进行初始化
	vector<int> v5(v3); //拷贝构造
	return 0;
}

Observe and monitor:

insert image description here

Two, capacity-related functions

insert image description here
Let's look at the first three first:

int main()
{
    
    
	vector<int> v1(10, 2);
	cout << "size:" << v1.size() << endl;
	cout << "capacity:" << v1.capacity() << endl;
	cout << "empty:" << v1.empty() << endl;
	return 0;
}

insert image description here

Then let's look at the function first reserve. The function of the reserve function is to open up a new space to change capacity, but it will not change size.

  • When reservein the function nis less than the original capacity, this function does nothing.
  • When reservein the function is nlarger than the original capacity, the capacity will be expanded, and the original data inside will not be changed.

Let's look at the following piece of code:

int main()
{
    
    
	vector<int> v1(10, 2);
	v1.reserve(5);
	v1.reserve(15);
	return 0;
}

Observing the monitoring window, we will find that the function does nothing when executing reserve(5), and the function will expand when executing reserve(15).

insert image description here

insert image description here

reserveIt is only responsible for opening up space. If you know how much space you need to use, reserveyou can alleviate the cost defect of vector capacity expansion.

Now let's look at resizethe function again, resizethe function will not only change capacitybut also change size.

  • If resizethe parameter nis smaller than the original one size, the previous one will be kept n, and the latter data will be destroyed, but capacityunchanged.

  • If resisethe parameter nis larger than the original one , the space behind sizewill be initialized with the second parameter untilsizesize == capacity

  • If resisethe parameter nis larger than the original capacity, it will expand and initialize the unused space.

Let's look at the following piece of code:

int main()
{
    
    
	vector<int> v1(10, 2);
	v1.resize(5);
	v1.resize(15,7);
	return 0;
}

insert image description here

insert image description here

insert image description here

The discussion of vector space growth problem in

Regarding the vector space growth mechanism, we can use the following code to test.

void TestVectorExpand()
{
    
    
	size_t sz;
	vector<int> v;
	//记录第一次的capacity的值
	sz = v.capacity();
	cout << "making v grow:\n";
	for (int i = 0; i < 100; ++i)
	{
    
    
		//挨个插入100个元素
		v.push_back(i);
		//如果容量发生了变化,就重新更新sz的数据,并打印sz
		if (sz != v.capacity())
		{
    
    
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

Let's take a look windowsat the expansion mechanism:

insert image description here

We will find that windowsthe platform is 1.5doubled!

Let's take a look at Linuxthe space growth under the platform:

insert image description here
We found that it is 2double expansion.

The reason is that the STL versions used by the two platforms are different, vs is the PJ version STL, and g++ is the SGI version STL.

3. Access to vector data

insert image description here

Code demo:

int main()
{
    
    
	vector<int> v1;
	v1.push_back(0);
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	cout << v1[2] << endl;
	cout << v1.at(2) << endl;
	cout << v1.front() << endl;
	cout << v1.back() << endl;
	int* pi = v1.data();
	pi[3] = 5;
	cout << pi[3] << endl;
	return 0;
}

insert image description here

4. Addition, deletion and modification of vector

insert image description here

1. assign function

insert image description here
The first function uses an iterator range for assignment, and the second function uses nelements for assignment.

int main()
{
    
    
	vector<int> v1;
	v1.push_back(0);
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	vector<int>::iterator it1 = v1.begin();
	while (it1 != v1.end())
	{
    
    
		cout << *it1;
		++it1;
	}
	cout << endl;

	vector<int> v2(2, 7);
	vector<int>::iterator it2 = v2.begin();
	while (it2 != v2.end())
	{
    
    
		cout << *it2 ;
		++it2;
	}
	cout << endl;
	
	//普通赋值
	v2.assign(3, 2);
	for (auto& e : v2)
	{
    
    
		cout << e ;
	}
	cout << endl;
	
	//用迭代器区间赋值
	v2.assign(v1.begin(), v1.end());
	for (auto& e : v2)
	{
    
    
		cout << e;
	}
	cout << endl;
	return 0;
}

insert image description here

2. insert function

insert image description here
The first function is to 1insert an element before the pos position, and the second function is to ninsert an element before the pos position

int main()
{
    
    
	vector<int> v1;
	v1.push_back(0);
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	v1.pop_back();
	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	cout << endl;
	v1.insert(++v1.begin(), 2, 7);
	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	cout << endl;
	return 0;
}

insert image description here

3. Erase function

insert image description here

The first function is to delete the pos iterator position. The second is to delete all elements in the iterator range

int main()
{
    
    
	vector<int> v1;
	v1.push_back(0);
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	cout << endl;

	v1.erase(++v1.begin(), --v1.end());

	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	cout << endl;
	return 0;
}

insert image description here

4. swap function

insert image description here

int main()
{
    
    
	vector<int> v1;
	v1.push_back(0);
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	cout << endl;

	vector<int> v2(9, 7);
	v1.swap(v2);

	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	cout << endl;
	return 0;
}

insert image description here

5. clear function

insert image description here

int main()
{
    
    
	vector<int> v1;
	v1.push_back(0);
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	cout << endl;
	v1.clear();
	cout << "size:" << v1.size() << endl;
	cout << "capacity :" << v1.capacity() << endl;
	return 0;
}

insert image description here

6. find function

The find function is not a member function in vector, but a function in an algorithm library in C++. Its function is to help us find the data we want.

insert image description here
Function parameters : two iterators, determine one interval, and the last parameter is the element we want to find.

Return value : When the element is found, return the iterator of the position, if not found, return lastthe iterator.

( Note: All iterator ranges are left-closed and right-open, so returning the last iterator means that it cannot be found )

int main()
{
    
    
	vector<int> v1;
	v1.push_back(0);
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	//删除元素 2
	vector<int>::iterator it = find(v1.begin(), v1.end(), 2);
	v1.erase(it);
	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	return 0;
}

insert image description here

Five, 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 layer is actually a pointer, or it encapsulates the pointer .

The iterator invalidation means that the space pointed to by the corresponding pointer at the bottom of the iterator is destroyed, and using a piece of space that has been released may cause undefined consequences or program crashes.

However, since the STL version vsused g++is the PJ version and the SGI version, the performance of the iterator invalidation is not the same in the two versions.

There are two main ways to invalidate an iterator:

  • Iterator invalidation due to element deletion.
  • Iterator invalidation due to capacity change

Let's first look at the following iterator invalidation caused by deleting elements:

int main()
{
    
    
	vector<int> v1{
    
     1,2,3,4,5 };
	vector<int>::iterator it = v1.begin();
	//删除it指向的元素
	v1.erase(it);
	//再对it指向的元素进行自增
	(*it)++;
	for (auto& e : v1)
	{
    
    
		cout << e ;
	}
	cout << endl;
	return 0;
}

eraseAfter deleting itthe position element, itthe element after the position will move forward , but itit is unreasonable for us to visit it again, because at first we wanted to itpoint to the element 1, the element 1disappeared, and we should not itvisit it anymore.

The PJ version of the above code under vs will directly report an error, while the SGI version under g++ will access the next element 2.

  • When executed under vs, (*it)++an error will be reported directly:
    insert image description here

  • When executed under g++, (*it)++it will directly access the element moved to the position later, resulting in the 2following 3:

    insert image description here

Seeing this, you may think that the iterator here can also be regarded as not invalid. It is reasonable to itdirectly access elements under g++, but when the above code deletes the last element , it is still executed under 5g++ (*it)++There was a transgression!

So for this, itif we still want to use it, we have to reassign the iterator , which is also a general method to solve the iterator invalidation.

Let's look at another iterator failure caused by capacity change:

int main()
{
    
    
	vector<int> v1{
    
     1,2,3,4 };
	//it是未扩容之前的迭代器
	vector<int>::iterator it = v1.begin();

	v1.resize(50,0);
	//v1.begin()是扩容后的迭代器
	while (it != v1.end())
	{
    
    
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}
  • When executed under vs, an error will be reported directly:
    insert image description here

  • An error will also be executed when executing under g++:

    insert image description here
    many out-of-bounds accesses have occurred.

The reason for the failure of the iterator here is that we v1have expanded the iterator, itbut it points to the iterator at the starting position before the expansion. After the expansion, the memory address has changed, but itit has not been updated in time. We also have the potential to cause a lot of out-of-bounds behavior by using previous iterators.

insert image description here

For the second type of iterator failure, as long as the operation that causes the underlying space to change, it may be an iterator failure, such as: resize, reserve, insert, assign, push_back, etc.
Therefore, when using functions and iterators that may cause changes in their underlying space at the same time, pay attention to the update of the iterator! !

Guess you like

Origin blog.csdn.net/qq_65207641/article/details/129818586