[C++ Elementary] 10. The use and simulation of vector

1. Introduction to vector

Documentation introduction of 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.

2. The use of vector

Just check the usage documentation if necessary. Here are some common usages (similar to the usage in string, but repeat them!)

2.1 push_back and traversal

insert image description here

2.2 reserve and resize

insert image description here
Why are the reserves in the string class and the vector class not shrinking?
Because both the string class and the vector class need to insert data frequently, shrinking is also costly, and inserting data after shrinking requires frequent expansion, so reserve only implements expansion and has no effect on shrinking
insert image description here

Interface implements const and non-const conditions

insert image description here

2.3 [ ] and at

insert image description here
Defects in the assertion: the assertion in the release version will be invalid. Although the code must first pass the debug version test and then be released in the release version, this situation also needs attention, because out-of-bounds access, if it is modified, it is a very serious situation ( For example: amount data changes)

2.4 Assign initializes and fills in data

insert image description here

2.5 find in the algorithm library

insert image description here

2.6 swap interface

insert image description here

2.7 Shrink function (not recommended)

insert image description here
Usage scenario: When we know that we have opened up too much space and do not need to use so much space at all, we will use shrinkage and return it to the operating system for other programs to use

3. Vector related exercises

3.1 Numbers that appear only once

insert image description here

3.2 Yanghui triangle

insert image description here
Therefore, it is usually recommended to use C++ to write
insert image description here
the understanding of the second-level pointer into the feeling of a two-dimensional array (although it is not a two-dimensional array, but an operator[ ] for objects)

4. Simulation implementation of vector

insert image description here

4.1 Members of the vector class

namespace hx
{
    
    
	template <class T>
	class vector {
    
    
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};
}

4.2 Iterator ranges and operator[]

		iterator begin()
		{
    
    
			return _start;
		}
		iterator end()
		{
    
    
			return _finish;
		}

		const_iterator begin() const
		{
    
    
			return _start;
		}
		const_iterator end() const
		{
    
    
			return _finish;
		}
		
		T& operator[](size_t pos)
		{
    
    
			assert(pos < size());
			return _start[pos] ;
		}
		const T& operator[](size_t pos) const
		{
    
    
			assert(pos < size());
			return _start[pos];
		}

Because both [ ] and iterators may be read-only, the const interface also needs to be implemented

4.3 size and capacity

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

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

The values ​​of size and capacity can be obtained by subtracting the two pointers

4.4 No-argument constructor

		// 无参构造函数
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
    
    }

The no-argument constructor here can be implemented without display, because the compiler also uses nullptr to initialize these values ​​by default when implementing the no-argument construction
(when do you need to use the display implementation? – when there is a resource application, there is no resource application Use 0/NULL/nullptr for built-in types, and call its constructor for custom types)

4.5 push_back

		void push_back(const T& x)
		{
    
    
			if (size() == capacity())
			{
    
    
				int newCapacity = capacity() == 0 ? 4 : 2 * capacity();
				//扩容
				reserve(newCapacity);
			}
			*_finish = x;
			_finish++;
		}

4.6 reserve (wrong version) - use memcpy

insert image description here
So how to solve this problem? –Record the original size and add it to _finish before expansion

		void reserve(size_t n)
		{
    
    
			if (n > capacity())
			{
    
    
				// 记录oldSize
				size_t oldSize = _finish - _start;
				T* tmp = new T[n];
				if (_start)
				{
    
    
					memcpy(tmp, _start, sizeof(T) * size());
					delete[] _start;
				}
				_start = tmp;
				// 加上oldSize
				_finish = _start + oldSize;
				_endofstorage = _start + n;
			}
		}

insert image description here

4.7 resize

		//val采用默认值:若T类型为自定义类型调用其构造函数,若T为内置类型用0或者nullptr初始化
		//当然传参了就用传过来的参数
		void resize(size_t n,T val = T())
		{
    
    
			//扩容
			if (n > capacity())
			{
    
    
				reserve(n);
			}
			if (n > size())
			{
    
    
				//要插入数据
				while (_finish < _start + n)
				{
    
    
					*_finish = val;
					++_finish;
				}
			}
			else
			{
    
    
				// 删除数据
				_finish = _start + n;
			}
		}

4.8 pop_back

		bool empty() const
		{
    
    
			return _finish == _start;
		}
		void pop_back()
		{
    
    
			assert(!empty());
			--_finish;
		}

Iterator invalidation problem (emphasis)

4.9 insert

Problem 1: Wild pointer problem

insert image description here
The pos iterator failure problem caused by capacity expansion needs to be updated to point to pos

		// 解决野指针问题
		void insert(iterator pos, const T& val)
		{
    
    
			assert(pos >= _start);
			assert(pos < _finish);

			if (_finish == _endofstorage)
			{
    
    
				size_t len = pos - _start;
				size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newCapacity);

				// 扩容会导致pos迭代器失效,需要更新处理一下
				pos = _start + len;
			}

			// 挪动数据
			iterator end = _finish - 1;
			while (end >= pos)
			{
    
    
				*(end + 1) = *end;
				--end;
			}

			*pos = val;
			++_finish;
		}

insert image description here
So since there is a problem that the iterator becomes invalid after capacity expansion, and the external it can no longer be used after the failure, why don't we use references to pass parameters when designing the insert interface?

insert(iterator& pos, const T& val)

Do not pass references to prevent the internal possibility of destroying the structure of my original container through the reference of this iterator. There is such a risk, so we need to avoid risks. The iterator is just a tool compared to the container, and the insert interface is not
implemented
in the library. Pass by reference, so we must be consistent with the library when simulating the implementation

4.10 erase

Before simulating the implementation, first look at the following phenomena:
insert image description here
Based on the above phenomena, we think that it is invalid and cannot be used after calling erase.
insert image description here
insert image description here
Let's take a look at the idea of ​​the library function:
insert image description here

insert image description here
The simulation is implemented as follows:

		iterator erase(iterator pos)
		{
    
    
			assert(pos >= _start);
			assert(pos < _finish);

			iterator begin = pos;
			while (begin < _finish - 1)
			{
    
    
				*begin = *(begin + 1);
				++begin;
			}
			--_finish;

			return pos;
		}

The erase interface has a return value: returns the position where the element is deleted (because it has been deleted, ++ is not required)

4.11 clear

		void clear()
		{
    
    
			_finish = _start;
		}

Clear will not clear the space, so _finish and _start cannot be set to nullptr, otherwise it is a wild pointer
(at this time _endofstorage is still in its original position, _finsh and _start have been lost)

4.12 swap

		void swap(vector<T>& v)
		{
    
    
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}

4.13 Constructor Supplement

n T object construction - wrong version

		//拿n个T来初始化vector
		vector(size_t n,const T& val = T())
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{
    
    
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
    
    
				push_back(val);
			}
		}

Using the method of interface multiplexing, why do we initialize _start, _finish, and _endofstorage here?
Because the reserve will record the value of oldSize=_finish - _start before the expansion, so that the subsequent _finish can find its own distance from _start
because it will be used in the reserve, so it needs to be initialized to nullptr first, otherwise it is a random value oldSize is also random value, there will be problems with expansion

iterator range construction

//迭代器区间初始化
template <class InputIterator>
vector(InputIterator first, InputIterator last)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
    
    
	while (first != last)
	{
    
    
		push_back(*first);
		first++;
	}
}

It is also a way of interface reuse, but when this interface is implemented, we look at the following situation:
insert image description here
So how to solve this problem? -- Change n to int type

n T object construction - correct version

		vector(int n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
    
    
			reserve(n);
			for (int i = 0; i < n; i++)
			{
    
    
				push_back(val);
			}
		}

Change n to int type, then the compiler will give priority to matching ready-made templates when matching templates, instead of deducing templates (compiler mechanism). At this time, initializing with 10 1s will match the int,Tinterface

4.14 Copy construction

Method 1: Traditional way of writing

Method 2: Reuse interface

		// 复用接口
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
    
    
			reserve(v.size());
			for (auto e : v)
			{
    
    
				push_back(e);
			}
		}

Method 3: Modern writing

		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
    
    
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);
		}

Use tmp to construct a T object, initialize the current object with nullptr, and then exchange this and tmp. At this time, the tmp object is nullptr, and no error will be reported when the function stack frame is destroyed.

4.15 operator= (assignment overload)

		//赋值重载
		vector<T>& operator=(vector<T> v)
		{
    
    
			swap(v);
			return *this;
		}

The parameter in the assignment overload must be passed by value, not by reference,
because pass by value will call copy construction, and construct a temporary object v to exchange with this pointer object, and this temporary object is exchanged to this After the object does not need the space released, kill two birds with one stone.
If you pass parameters by reference, assignment overload will replace the previous data with incorrect data (the data in this object – garbage data), and the gain is not worth the loss. It is clearly an assignment but it is realized as an
exchange

4.16 Destructors

		//必须实现 默认生成的析构函数对于内置类型直接置为0(nullptr)
		//空间还未释放 内存泄漏
		~vector()
		{
    
    
			delete[] _start;
			_start = _finish = _endofstorage = nullptr;
		}

Key issues (deep shallow copy issues - key points)

4.17 reserve correct version – using deep copy

insert image description here
insert image description here

		// 正确版reserve -- 深拷贝
		void reserve(size_t n)
		{
    
    
			if (n > capacity())
			{
    
    
				size_t oldSize = _finish - _start;
				T* tmp = new T[n];
				if (_start)
				{
    
    
					//memcpy(tmp, _start, sizeof(T) * size());
					for (size_t i = 0; i < oldSize; ++i)
					{
    
    
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + oldSize;
				_endofstorage = _start + n;
			}
		}

Assignment overloading (deep copy) for objects inside each container – interface reuse

Guess you like

Origin blog.csdn.net/weixin_60915103/article/details/130819640