[C++] Hand-shredded Vector class

Table of contents

1. Vector class framework

2,vector ()

3,pinrt()

4,vector(int n, const T& value = T())

5,vector(const vector& v)

6,vector(InputIterator first, InputIterator last)

7,~vector()

8,iterator begin()

9,iterator end()

10,size() const

11,capacity() const

12,reserve(size_t n)

13,resize(size_t n, const T& value = T())

14,push_back(const T& x)

15,pop_back()

16,insert(iterator pos, const T& x)

17,erase(iterator pos)

18,empty()

19,operator[](size_t pos)

20,operator= (vector v)

21. Summary


Above we got to know the vector class and have a general understanding. Let's implement the framework of the vector class to get better familiar with the vector class and give us a deeper understanding of it; 

1. Vector class framework

Let’s first write the basic framework of a vector class;

namespace newVector
{
	template<class T>
	class vector
	{
	public:

		// Vector的迭代器是一个原生指针
		typedef T* iterator;

	private:

		iterator _start = nullptr; // 指向数据块的开始

		iterator _finish = nullptr; // 指向有效数据的尾

		iterator _endOfStorage = nullptr; // 指向存储容量的尾

	};
}

The vector class can contain many types, so we use templates to represent it to apply various scenarios. The vector class is often represented by iterators. The iterator of vector is actually a native pointer;

_start points to the beginning of the data block, _finish points to the end of the valid data, and _endOfStorage points to the end of the storage capacity;

2,vector ()

vector()
{}

Because we have already given the default value when constructing the framework, there is no need to write it again;

You can see that it has been initialized here;

3,pinrt()

It’s just a printout to facilitate subsequent testing;

		void pinrt()
		{
			for (size_t i = 0; i < size(); i++)
			{
				cout << _start[i] << " ";
			}
		}

4,vector(int n, const T& value = T())

We all use it to initialize n values;

		vector(int n, const T& value = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(value);
			}
		}

Some people don’t know what it means to give T() the default value. First of all, T() is an anonymous object, and the default is the compiler’s initialization of the built-in type; 

have a test:

int main()
{
	newVector::vector<int> v1(5, 8);
	v1.pinrt();
	return 0;
}

Now let's try it without giving the initialized value:

int main()
{
	newVector::vector<int> v1(5);
	v1.pinrt();
	return 0;
}

 Default initialized to 0;

5,vector(const vector<T>& v)

Copy construction (deep copy)

		vector(const vector<T>& v)
		{
			reserve(v.capacity());
			//memcpy(_start, v._start, sizeof(T)*v.size());
			for (size_t i = 0; i < v.size(); i++)
			{
				_start[i] = v._start[i];
			}
			_finish = _start + v.size();
		}

Why not use memcpy? The above one is obviously more convenient, because memcpy is a shallow copy. If T is a string class, an error will be reported because the destructor destructs the same space multiple times, while the following one is a deep copy. Copy, their two _starts will not point to the same space;

int main()
{
	newVector::vector<int> v1(5,8);
	newVector::vector<int> v2(v1);
	v2.pinrt();
	return 0;
}

You can see that it is OK;

6,vector(InputIterator first, InputIterator last)

This should be used in conjunction with the template, which represents a range, as long as it is of the same type;

		template<class InputIterator>

		vector(InputIterator first, InputIterator last)
		{
			reserve(last - first + 1);
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

Expand the capacity first. The intervals like this are closed on the left and open on the right, and then just insert them one by one;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	v1.pinrt();
	return 0;
}

As long as they are of the same type, any array like this will do;

7,~vector()

destructor

		~vector()
		{
			delete[] _start;
			_start = _finish = _endOfStorage = nullptr;
		}

Delete must be followed by [ ], because what is destructed is a continuous space, just treat it as an array, and then empty each iterator;

int main()
{
	newVector::vector<int> v1(5,8);
	v1.~vector();
	v1.pinrt();
	return 0;
}

8,iterator begin()

iterator pointing to the first element

		iterator begin()
		{
			return _start;
		}

Just return to _start directly;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	cout << *v1.begin();
	return 0;
}

9,iterator end()

iterator pointing to the element next to the last element

		iterator end()
		{
			return _finish;
		}

Just return _finish directly;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	cout << *v1.end();
	return 0;
}

Directly random numbers;

Change your mind and try this:

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	cout << *(v1.end()-1);
	return 0;
}

We looked for the previous iterator, and it turned out to be the last number;

10,size() const

Return the number of valid data

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

Just subtract the iterators directly;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	cout << v1.size();
	return 0;
}

11,capacity() const

Return capacity size

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

Just subtract the iterators directly, and the difference is the capacity;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	cout << v1.capacity();
	return 0;
}

12,reserve(size_t n)

Expansion

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				size_t old = size();
				if (_start)
				{
					//memcpy(tmp, _start, old * sizeof(T));
					for (size_t i = 0; i < old; i++)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + old;
				_endOfStorage = _start + n;
			}
		}

It is only used when expansion is needed. First judge, then open up a required space, and then copy and assign values. We still do not use memcpy here because it is a shallow copy, then release the original space, and then reassign the iterator. ;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	cout << v1.capacity() << endl;

	v1.reserve(20);
	cout << v1.capacity() << endl;
	return 0;
}

Clear at a glance;

13,resize(size_t n, const T& value = T())

Change the number of valid data. If it is not enough, it will be filled. You can specify the filling data;

		void resize(size_t n, const T& value = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())
				{
					reserve(n);
				}
				while (_finish!=_start+n)
				{
					push_back(value);
				}
			}
		}

When reducing data, just move _finish forward. When expanding, first expand and then fill;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	cout << v1.size() << endl;

	v1.resize(10);
	cout << v1.size() << endl;

	v1.resize(15, 6);
	cout << v1.size() << endl;
	v1.pinrt();
	return 0;
}

As you can see, when we do not specify padding, it is filled with 0 by default;

14,push_back(const T& x)

tail plug

		void push_back(const T& x)
		{
			if (_finish == _endOfStorage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			*_finish = x;
			_finish++;
		}

First check whether expansion is needed, then assign a value and move _finish back one bit;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr, arr + 5);
	
	v1.push_back(6);
	v1.push_back(7);
	v1.pinrt();
	return 0;
}

 The insertion was very successful;

15,pop_back()

tail delete

		void pop_back()
		{
			assert(size() > 0);
			_finish--;
		}

For an array type like this, just move it directly to the iterator, just _finish and move it forward one bit;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr, arr + 5);
	
	v1.pop_back();
	v1.pop_back();
	v1.pinrt();
	return 0;
}

Removal went very smoothly;

16,insert(iterator pos, const T& x)

Insert at specified position

		iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start && pos <= _finish);
			size_t old = pos - _start;
			if (_finish == _endOfStorage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			pos = _start + old;
			memcpy(pos + 1, pos, (_finish - pos) * sizeof(T));
			*pos = x;
			_finish++;
			return pos;
		}

Here we will face the problem of iterator failure. After expansion, the original pos still points to the old space and becomes invalid, so we need to update pos;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr, arr + 5);
	
	auto pos = find(v1.begin(), v1.end(), 1);
	v1.insert(pos, 9);

	pos= find(v1.begin(), v1.end(), 5);
	v1.insert(pos, 9);
	v1.pinrt();
	return 0;
}

perfect insertion;

17,erase(iterator pos)

Erase specified location

		iterator erase(iterator pos)
		{
			assert(pos >= 0 && pos <= _finish);
			memcpy(pos, pos + 1, sizeof(T)*(_finish - pos));
			_finish--;
			return pos;
		}

First assert and judge, then directly move forward one bit to overwrite, and then update _finish;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr, arr + 5);

	auto pos = find(v1.begin(), v1.end(), 1);
	v1.erase(pos);

	pos = find(v1.begin(), v1.end(), 5);
	v1.erase(pos);
	v1.pinrt();
	return 0;
}

It was also successfully erased;

18,empty()

Call it short

		bool empty()
		{
			return _start == _finish;
		}

Returns true when empty and vice versa;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr, arr + 5);
	cout << v1.empty() << endl;

	newVector::vector<int> v2;
	cout << v2.empty();
	return 0;
}

19,operator[](size_t pos)

Return the value corresponding to the subscript;

		T& operator[](size_t pos)
		{
			assert(pos >= 0 && pos <= size());
			return _start[pos];
		}

Just get the value and return it directly like an array;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr, arr + 5);
	
	cout << v1[0] << " " << v1[4];
	return 0;
}

The writing method is as simple as an array;

20,operator= (vector<T> v)

Assignment, deep copy

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

		vector<T>& operator= (vector<T> v)
		{
			swap(v);
			return *this;
		}

Just exchange it with one hand;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr, arr + 5);
	
	newVector::vector<int> v2 = v1;
	v2.pinrt();
	return 0;
}

 

21. Summary

Let’s make a rough outline first, and there are many branches in it. For example, if we write about erasing a certain data, we can also erase a certain range. It’s up to everyone to explore and consult the documents;

The implementation of the vector class ends here;

come on!

Guess you like

Origin blog.csdn.net/m0_71676870/article/details/135290379
Recommended