当前位置: 首页 > news >正文

C++之vector

在C++中,vector是一个重要的类,本质是一个顺序表。

一、vector的使用:

1.vector的创建与访问

vector是一个用模板写的顺序表,所以在使用的时候创建变量需要表明插入的数据是什么类型的。

vector<int> v1,<>里面表明数据类型。

vector也可以直接传数据去构造:vector<int> v2(10, 1);//一个数组有10个1

也可以使用已经初始化好的对象去构造另外的对象:vector<int> v3(++v2.begin(),--v2.end());//用v2去初始化v3,但是不要第一个和最后一个

vector有三种方式可以访问,v[i]就可以访问对应下标位置的数据

vector<int> v2(10, 1);//一个数组有10个1

vector<int> v3(++v2.begin(),--v2.end());//用v2去初始化v3,但是不要第一个和最后一个
//访问v3:
for (int i = 0; i < v3.size(); i++)
{
	cout << v3[i] << " ";
}
cout << endl;

也可以使用迭代器和范围for的方式进行访问:

//迭代器方式访问:
vector<int>::iterator it = v3.begin();
while (it != v3.end())
{
	cout << *it << " ";
	it++;
}
cout << endl;

//范围for遍历:
for (auto e : v3)
{
	cout << e << " ";
}
cout << endl;

2 . vector其他接口的使用:

(1)size和capacity:用于返回vector对象里面数据的个数和空间大小。

(2)reserve:用于开空间但是不缩容。

void test_vector()
{
	//TestVectorExpand();
	//reserve 开空间,但是不缩容
	vector<int> v(10, 1);
	v.reserve(20);
	cout << v.size() << endl;
	cout << v.capacity() << endl;

	v.reserve(15);
	cout << v. size() << endl;
	cout << v.capacity() << endl;
	
	v.reserve(5);
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	//结果是capacity的值一直不变,一开始是20,后面就不会比20小
}

 (3)resize:resize(n),n>size插入数据,空间不够就扩容  n<size删除数据。

void test_vector()
{
	//resize(n),n>size插入数据,空间不够就扩容  n<size删除数据
	vector<int> v(10, 1);
	v.reserve(20);
	cout << v.size() << endl;
	cout << v.capacity() << endl;

	v.resize(15, 2);//在后面补了5个数据
	cout << v.size() << endl;
	cout << v.capacity() << endl;

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

	v.resize(5);//直接删到剩5个
	cout << v.size() << endl;
	cout << v.capacity() << endl;
}

 可以看到当n>size时会自动扩容并且在后面的空间插入数据。n<size时删除数据但是容量不变。\

(4)插入:插入有直接插入insert和尾插pushback,insert第一个参数需要传插入数据的位置。

void test_vector()
{
	vector<int> v(10, 1);
	v.push_back(2);//尾插一个2
	v.insert(v.begin(), 0);//头插一个0
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	v.insert(v.begin() + 3, 10);//第三个位置插入一个10
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

(5)vector与string和vector的嵌套使用:

vector是一个顺序表,就可以使用string类型的变量。

vector<string> v1;
string s1("xxxxxx");
v1.push_back(s1);

也可以直接创建变量然后构造:v1.push_back("yyyyyy");这样子的原理是const char*隐式类型转换为string。

遍历一遍:

for (const auto& e : v1)//3.所以加引用,不改变v1就加const
{
	cout << e << endl;//1.变换成迭代器取*it里面是v1的每个值,每个值都是string
}
cout << endl;

如果是用vector嵌套vector就可以类比为一个二维数组:

//二维数组
//10*5里面是1
vector<int> v(5,1);//一个v有5个1
vector<vector<int>> vv(10,v);//一个vv有十个v
vv[2][1] = 2;//修改其中的值为2,但其实是联系两个[]的operator调用

 同时也可以用二维数组的访问方式访问vv。

二、模拟实现vector:

(1) 首先创建一个自己的命名空间然后在命名空间内实现vector,为了配合封装其他数据就得使用模板来写,类的成员要有头指针指向第一个数据和尾部的数据指针指向最后一个数据和一个末尾指针指向空间末尾。

namespace Rin
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
}

(2)实现插入:

插入之前空间不够需要扩容,扩容前需要判断空间是否满了,就需要判断数据末尾指针和空间末尾指针是否相等,相等的话传需要扩容的数据个数,如果空间大小为0则扩容4个数据的空间大小,否则就扩容2倍数据空间的大小。在扩容函数里面,使用new开辟新空间再用memcpy将原来的空间内容拷贝到新空间里去,然后就将新的_finish和_start进行赋值指向新的空间,这时候需要一个函数来返回原来三个指针的相对位置以方便后续的赋值:

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

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

因为担心后续使用的对象不能改变就在后面加上const。 完成这一切之后就可以实现reserve函数了。_

void reserve(size_t n)
{
	if (n > capacity())
	{
		//T* tmp = (iterator)realloc(_start, sizeof(T) * n);
		T* tmp = new T[n];
		memcpy (tmp, _start, size() * sizeof(T));//将原来_start里面的内容拷贝到tmp里去

		delete[] _start;

        _start = tmp;
		_finish = _start + size();
		_end_of_storage = _start + n;
	}
}

但是这样写是错误的,程序会崩溃,因为一开始扩容的时候vector里的空间是0,那么_start和_finish指向的一块空间,大小是一样的,所以size()返回的值是0,实现size()就是为了返回原来的相对位置好对_finish进行修改,但是先把tmp赋值给_start将会导致size()传的是已经改变了位置的_start和不变的_finish直接的相对位置,就会发生指针越界的情况导致程序崩溃。所以正确写法应当是先确定_finish,再确定其他两个。如下:

void reserve(size_t n)
{
	if (n > capacity())
	{
		T* tmp = new T[n];
		memcpy (tmp, _start, size() * sizeof(T));//将原来_start里面的内容拷贝到tmp里去

		delete[] _start;

		_finish = tmp + size();
		_start = tmp;
		_end_of_storage = _start + n;
	}
}

扩容完成后将数据传给末尾数据指针,然后++。

void push_back(const T& x)
{

	if (_finish == _end_of_storage)//空间满了扩容扩2倍
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}
	*_finish = x;
	_finish++;
}

因为传入的数据不需要修改,所以用const修饰,如果是string类型的,为了减少拷贝构造的调用次数就传引用。

接下来就是直接插入insert,指定位置插入后后面的数据需要一次挪动然后赋值:

void insert(iterator pos, const T& x)
{
	//扩容
	if (_finish == _end_of_storage)//空间满了扩容扩2倍
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}

	iterator end = _finish - 1;

	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}

	*pos = x;
	_finish++;
}

但是这样写还是会出问题,迭代器会失效,仔细看扩容部分,扩完容之后数据会指向新的空间,但是原来的pos还指向着别的空间,所以会失效,正确写法:

void insert(iterator pos, const T& x)
{
	//扩容
	if (_finish == _end_of_storage)//空间满了扩容扩2倍
	{
		size_t len = pos - _start;//不写这两句迭代器会失效
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + len;//要让pos指向新空间
	}

	iterator end = _finish - 1;

	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}

	*pos = x;
	_finish++;
}

再测试之前,为了满足迭代器需求,先把begin等函数实现一下:

iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

为了模拟数组的[ ]接口,顺便operator写一下[ ]:

T& operator[](size_t i)
{
	assert(i < size());

	return _start[i];
}

 测试之前写个打印函数查看结果: 

void print_vector(const vector<T>& v)//作为函数模板可以同时用于多种类型的打印
{
	//规定,无法在没有实例化的类模板里面取东西,编译器不能区分这里的const_iterator
	//是类型还是静态成员变量
	//vector<T> ::const_iterator it = v.begin();//const vector*没办法传给vector*,得在前面加const
	typename vector<T> ::const_iterator it = v.begin();//这样才可以识别
	//它认为你加typename就是类型,没加typename就是静态成员变量
	//或者直接用auto
	//auto it = v.begin();//会自动推到出来v.begin()
	while (it != v.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

在类外重新定义一个打印函数,用const vector<T>& v作为函数模板可以同时用于多种类型的打印。 但是C++规定,无法在没有实例化的类模板里面取东西,编译器不能区分这里的const_iterator是类型还是静态成员变量,也就是不能直接vector<T> ::const_iterator it = v.begin();

但是可以vector<int> ::const_iterator it = v.begin();,因为一个是不确定的一个是确定的,这是一种规定。如果想用的话就得写typename,typename vector<T> ::const_iterator it = v.begin();这样才可以识别。也可以用auto它可以直接识别。

最后测试:

void test_vector1()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	for (size_t i = 0; i < v.size(); i++)
	{
		cout << v[i] << " ";
	}
	cout << endl;

	vector<int> ::iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	vector<double> vv;
	vv.push_back(1.1);
	vv.push_back(2.2);
	vv.push_back(3.3);
	vv.push_back(4.4);
	vv.push_back(5.5);

	print_vector(vv);
}

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);

	v.insert(v.begin() + 2, 30);

	print_vector(v);
}

(3)实现resize:

 当参数<size()时就缩容,_finish = _start + n;,如果>size()就扩容,然后赋值,需要注意的是赋值的那个参数,由于传的数据可能是内置类型或者是自定义类型,所以需要给缺省值指向默认构造函数:void resize(size_t n, const T& val = T()),这样子的话就会调用默认构造函数,自定义类型也有默认构造。

int i = int();
int j = int(1);
int k(2);

 


void resize(size_t n, const T& val = T())//给缺省值,调用默认构造
{
	if (n < size())
	{
			_finish = _start + n;
	}
	else
	{
		reserve(n);
		while (_finish < _start + n)
		{
				*_finish = val;
				_finish++;
		}
	}
}

再对打印函数优化整理一下:

template<class Container>
void print_container(const Container& v)
{
	auto it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

测试一下:

void test_vector3()
{
    vector<int> v;
	v.resize(10, 1);
	print_container(v);
	v.reserve(20);
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	v.resize(15, 2);
	print_container(v);
	v.resize(25, 3);
	print_container(v);
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	v.resize(5);
	print_container(v);
}

 (4)接下来就是实现一个类基本的拷贝构造和析构函数了,由于它自己默认的拷贝构造函数只能实现数据的浅拷贝,析构的话会对一块空间发生两次析构会崩溃,所以需要自己写。

写之前先写构造:

vector()
{ }

简短一两句话,走的是初始化列表。 

vector(const vector<T>& v)
{
	reserve(v.size());
	for (auto& e : v)
	{
		push_back(e);
	}
}

默认构造函数第一个参数默认是this指针,先对左操作数进行扩容然后用范围for对右操作数进行遍历,用尾插将右操作数的数据插入this指针里的空间。有默认构造函数就有operator=,但是不一样的是左操作数很可能有内容所以需要释放掉:

void clear()
{
	_finish = _start;//清理掉里面的数据
}

释放完成后扩容然后和拷贝构造一样的操作。最后需要将值返回给左操作数。 

vector<T> operator=(const vector<T>& v)
{
	if (this != &v)
	{
		clear();//释放掉左操作数
		reserve(v.size());
		for (auto& e : v)
		{
			push_back(e);
		}
	}
	return *this;
}

测试一下:

void test_vector4()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	print_container(v1);

	vector<int> v2 = v1;
	print_container(v2);

	vector<int> v3;
	v3 = v2;
	print_container(v3);
}

(5)memcpy的问题:

void test_vector5()
{
	vector<string> v;
	v.push_back("1111111111111111111111");
	v.push_back("1111111111111111111111");
	v.push_back("1111111111111111111111");
	v.push_back("1111111111111111111111");
	print_container(v);//前面运行正常

	v.push_back("1111111111111111111111");
	print_container(v);//多插入一行就出问题了
}

前面运行正常,但是多加了一行之后程序运行就出了问题,会出现乱码,所以应该是扩容出了问题。原因在于memcpy虽然在拷贝vector<string> v的时候是深拷贝,但是在拷贝v里面的string类型数据时却对string的指针进行了浅拷贝, 这个浅拷贝导致下一步delete[ ]先调用析构函数,每个string也会调用析构函数把string指向的空间释放了,就把原本时1111111111的内容变成随机值,随机值由于编码的原因要按字符串显示会变成乱码,就是复杂的汉字,然后再调用operator delete[ ]再调用delete最后调用free把_start里面的空间释放掉。不仅是vector<string>会这样,vector<vector>也会这样。所以就得对拷贝的那一块进行修改,改为:

for (size_t i = 0; i < oldsize; i++)
{
                tmp[i] = _start[i];
}

这样子由于tmp和_start里面的数据是string类型的,里面的赋值就会调用string的operator=,释放掉原来的空间拷贝数据给新空间,所以就满足了string的深拷贝。

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t oldsize = size();
		//T* tmp = (iterator)realloc(_start, sizeof(T) * n);
		T* tmp = new T[n];
		//memcpy (tmp, _start, size() * sizeof(T));//将原来_start里面的内容拷贝到tmp里去
		for (size_t i = 0; i < oldsize; i++)
		{
			tmp[i] = _start[i];
		}

		delete[] _start;
		//_finish = _start + size();
		_finish = tmp + size();
		_start = tmp;
		_end_of_storage = _start + n;
	}
}

最后再把删除和判空函数写一下:

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

void push_back(const T& x)
{

	if (_finish == _end_of_storage)//空间满了扩容扩2倍
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}
	*_finish = x;
	_finish++;
}

最后就是整个vector的实现代码:

#include <iostream>
#include <assert.h>
using namespace std;

namespace Rin
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		vector()
		{ }

		vector(const vector<T>& v)
		{
			reserve(v.size());
			for (auto& e : v)
			{
				push_back(e);
			}
		}

		//类模板里面的成员函数还可以继续是函数模板
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)//迭代器区间构造
		{
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

		void clear()
		{
			_finish = _start;//清理掉里面的数据
		}

		vector<T> operator=(const vector<T>& v)
		{
			if (this != &v)
			{
				clear();//释放掉左操作数
				reserve(v.size());
				for (auto& e : v)
				{
					push_back(e);
				}
			}
			return *this;
		}

		//void swap(vector<T>& v)
		//{
		//	std::swap(_start, v._start);
		//	std::swap(_finish, v._finish);
		//	std::swap(_end_of_storage, v._end_of_storage);
		//}
		//vector<T>& operator=(vector<T> v)//现代写法
		//{
		//	swap(v);

		//	return *this;
		//}
		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _end_of_storage = nullptr;
			}
		}

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin()const
		{
			return _start;
		}

		const_iterator end()const
		{
			return _finish;
		}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t oldsize = size();
				//T* tmp = (iterator)realloc(_start, sizeof(T) * n);
				T* tmp = new T[n];
				//memcpy (tmp, _start, size() * sizeof(T));//将原来_start里面的内容拷贝到tmp里去
				for (size_t i = 0; i < oldsize; i++)
				{
					tmp[i] = _start[i];
				}

				delete[] _start;
				//_finish = _start + size();
				_finish = tmp + size();
				_start = tmp;
				_end_of_storage = _start + n;
			}
		}

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

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

		/*size_t size()cosnt
		{
			return _finish - _start;
		}

		size_t capacity()const
		{
			return _end_of_storage - _start;
		}*/

		void resize(size_t n, const T& val = T())//给缺省值,调用默认构造
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				while (_finish < _start + n)
				{
					*_finish = val;
					_finish++;
				}
			}
		}

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

		void push_back(const T& x)
		{

			if (_finish == _end_of_storage)//空间满了扩容扩2倍
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = x;
			_finish++;
		}

		void pop_back()
		{
			assert(empty);
			--_finish;
		}
		/*void pop_back(T& x)
		{
			assert(!empty());
			_finish--;
		} */

		void insert(iterator pos, const T& x)
		{
			//扩容
			if (_finish == _end_of_storage)//空间满了扩容扩2倍
			{
				size_t len = pos - _start;//不写这两句迭代器会失效
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;//要让pos指向新空间
			}

			iterator end = _finish - 1;

			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}

			*pos = x;
			_finish++;
		}
		T& operator[](size_t i)
		{
			assert(i < size());

			return _start[i];
		}
		const T& operator[](size_t i)const
		{
			assert(i < size());

			return _start[i];
		}
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};

	template<class T>
	void print_vector(const vector<T>& v)//作为函数模板可以同时用于多种类型的打印
	{
		//规定,无法在没有实例化的类模板里面取东西,编译器不能区分这里的const_iterator
		//是类型还是静态成员变量
		//vector<T> ::const_iterator it = v.begin();//const vector*没办法传给vector*,得在前面加const
		typename vector<T> ::const_iterator it = v.begin();//这样才可以识别
		//它认为你加typename就是类型,没加typename就是静态成员变量
		//或者直接用auto
		//auto it = v.begin();//会自动推到出来v.begin()
		while (it != v.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}

	template<class Container>
	void print_container(const Container& v)
	{
		auto it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;

		//for (auto e : v)
		//{
		//	cout << e << " ";
		//}
		//cout << endl;
	}

	void test_vector1()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);

		for (size_t i = 0; i < v.size(); i++)
		{
			cout << v[i] << " ";
		}
		cout << endl;

		vector<int> ::iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<double> vv;
		vv.push_back(1.1);
		vv.push_back(2.2);
		vv.push_back(3.3);
		vv.push_back(4.4);
		vv.push_back(5.5);

		print_vector(vv);
	}

	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);

		v.insert(v.begin() + 2, 30);

		print_vector(v);
	}

	void test_vector3()
	{
		//内置类型也有默认构造
		int i = int();
		int j = int(1);
		int k(2);

		vector<int> v;
		v.resize(10, 1);
		print_container(v);
		v.reserve(20);
		cout << v.size() << endl;
		cout << v.capacity() << endl;
		v.resize(15, 2);
		print_container(v);
		v.resize(25, 3);
		print_container(v);
		cout << v.size() << endl;
		cout << v.capacity() << endl;
		v.resize(5);
		print_container(v);
	}

	void test_vector4()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		print_container(v1);

		vector<int> v2 = v1;
		print_container(v2);

		vector<int> v3;
		v3 = v2;
		print_container(v3);
	}

	void test_vector5()
	{
		vector<string> v;
		v.push_back("1111111111111111111111");
		v.push_back("1111111111111111111111");
		v.push_back("1111111111111111111111");
		v.push_back("1111111111111111111111");
		print_container(v);//前面运行正常

		//v.push_back("1111111111111111111111");
		//print_container(v);//多插入一行就出问题了
		//问题出在扩容的memcpy
	}
}

int main()
{
	Rin::test_vector5();
	//Rin::test_vector5();
	return 0;
}

相关文章:

  • 如何在工控机上实现机器视觉检测?
  • Vue05
  • 计算机毕业设计SpringBoot+Vue.js英语知识应用网站(源码+文档+PPT+讲解)
  • 如何下载MinGW-w64到MATLAB
  • 解决Docker Desktop启动后Docker Engine stopped问题
  • 进入DeepSeek部署第一阵营后,奇墨科技推进多元应用场景落地
  • 小红的回文子串
  • CSS 实现波浪效果
  • Ubuntu 下 nginx-1.24.0 源码分析 - ngx_modules
  • 前端Npm面试题及参考答案
  • 深度剖析数据分析职业成长阶梯
  • Ubuntu20.04下各类常用软件及库安装汇总
  • 解锁浏览器内置API,助力跨标签/跨页面数据通信
  • 详解:事务注解 @Transactional
  • 【后端开发面试题】每日 3 题(四)
  • 【Python LeetCode 专题】面试经典 150 题
  • 卷积运算是如何进行的?
  • 详细对比所有开源许可及其不同版本
  • 人工智能之数学基础:线性代数中的特殊矩阵
  • 技术问题汇总:前端怎么往后端传一个日期?
  • 网页制作有什么软件/东莞seo建站排名
  • 建行网站会员/百度关键词优化企业
  • 做设计的公司的网站/宁波seo关键词优化教程
  • 容桂企业网站建设/东莞网站快速排名提升
  • 我想看b站直播间游客怎么看/快速整站排名seo教程
  • 南昌哪里做网站好/新十条优化措施