vector

vector容器:可以存储任意类型的元素,较之于string只能存储类型char。底层都是动态的顺序表
stl的容器都有一些同名的方法,实现功能都是类似的比如:一般都有size(),push_back(),empty()。。。。
连续的内存,故而支持随机访问,操作起来和在C语言中的数组差不多,当然vector< int >的对象有很多成员方法,使用起来更便捷。
vector_第1张图片
容器是模版实现的,所以在vector创建对象要给出模版参数 vector< int >
构造方法:

//简写版本,去掉了最后一个参数默认的分配器,以及函数头部的关键字
vector<T>();

vector<T> (size_type n, const T& val = T());

vector<T>(const vector& x);

简单使用

	vector<int> arr;//空int容器
	arr.resize(10);
	vector<int> v1(10);
	vector<int> v2(2, 10);//两个元素,并且元素的值=10
	//调试后可以看到vector会把所有元素初始化为0,如果不指定值
	for (int i = 0; i < v1.size(); i++);
	for (auto x : v2);//范围for
	for (auto& x : v2);//加上引用,可以对vector中的元素进行修改
	auto it = v1.begin();//使用迭代器
	while (it != v1.end())
		it++;
	it + 1;//迭代器可以像指针一样+整数,就像指针移动一样
	//int pos = it;//但不能当做下标

二维数组:

	int n, m;
	cin >> n >> m;
	vector<vector<int>> vi(n, vector<int>(m));//vector创建二维数组arr[n][m]
	//这样就实现了C/C++语言无法创建动态二维数组

vector< T > ( size_t n, const T& val = T() );
参考这个构造函数去理解为什么这样做也是合理的
此时 T:vector< int >
n:n
val:vector< int >(m)存储的值是一维数组,
const vector< int >& val = vector< int >(m);
这是一个匿名对象赋给了val

	v1.push_back(10);//数组添加元素
	v1[1] = 99;//其实操作就跟数组差不多
	//数组的操作很多都会涉及到排序
	sort(v1.begin(), v2.end());
	sort(v1.begin(), v2.end(), greater<int>());
	//第三个参数是可以使函数对象,函数指针
	template <class T> struct greater : binary_function <T,T,bool> {
	bool operator() (const T& x, const T& y) const {return x>y;}
	};
	仿函数重载函数调用运算符()
	在functional头文件中还有一些类似的模版:
	less,less_equal,
	greater_equal,
	equal_to,not_equal_to
	因为是模版所以使用时要给出模版参数
	*/

	bool myfunction (int i,int j) { return (i<j); }

	struct myclass {
	bool operator() (int i,int j) { return (i<j);}
	} myobject;

用vector存储自定义类型airplane

#include
using namespace std;
#include
class airplane
{
	int need;
	int have;
	int come;
};

int main()
{
	int n;
	cin >> n;
	auto arr = new airplane[n];
	vector<airplane> arr2(5);
}

vector也是1.5被扩容(不同编译器实现不同)

#include
#include

using namespace std;
int main()
{
	vector<int> v;
	int cap = v.capacity();
	cout << cap<<" ";
	for (int i = 0; i < 100; i++)
	{
		v.push_back(i);
		if (cap != v.capacity())
		{
			cap = v.capacity();
			cout << cap << " ";
		}
	}
	//容量变化:0 1 2 3 4 6 9 13 19 28 42 63 94 141
	return 0;
}

迭代器:
类似于指针 || 对指针的封装
对于vector来说,迭代器就是 typedef T* iterator
获取迭代器 auto it = v.begin();
迭代器失效:迭代器越界了,指向的地址原本数据转移了
其实这是从指针的角度理解,实际迭代器既然是封装,那么就不会想指针那么干,指针比较底层直接
vector_第2张图片
预防:在所有可能会导致迭代器失效的操作之后,需要使用迭代器时给迭代器重新赋值

vector不同于string,其中的insert,erase操作的参数只能传递迭代器
vector_第3张图片

为什么要这样设计呢?

我理解的是,因为string类确定要存储的就是char,而vector容器类是根据模版参数来实例化容器类的,无法确定其类型,所以要使用对应的迭代器,迭代器是带有类型的指针,也只能这样处理。

模拟实现vector:

#pragma once

//模拟实现vector
#include

namespace gyx
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
	private:
		iterator start, finish, endofstorage;
		//这些迭代器作为底层操作的指针
	public:
		vector()
			:start(nullptr)
			, finish(nullptr)
			, endofstorage(nullptr)
		{}
		vector(int n, const T& val = T())
		{
			start = new T[n];
			for (size_t i = 0; i < n; ++i)
				start[i] = val;
			
			finish = start + n;
			endofstorage = finish;
		}



		template<class Iterator>
		vector(Iterator first, Iterator last)
		{
			auto it = first;

			size_t n = 0;
			while (it != last)
			{
				++it;
				n++;
			}

			start = new T[n];
			while (first != last)
			{
				*finish = *first;
				++first;
				++finish;
			}
		}

		vector(const vector<T>& v)//拷贝构造
		{
			start = new T[v.size()];
			for (size_t i = 0; i < v.size(); ++i)
				start[i] = v[i];

			finish = start + n;
			endofstorage = finish;
		}

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

		~vector()
		{
			if (start)
			{
				delete[] start;
				start = finish = endofstorage = nullptr;
			}
		}
		iterator begin(){ return start; }
		iterator end(){ return finish; }

		size_t size()const{ return finish - start; }
		size_t capacity()const{ return endofstorage - start; }
		bool empty()const{ return start == finish; }

		void reserve(size_t newcapacity)//扩容或者缩,一般都是扩
		{
			size_t oldcap = capacity();
			if (newcapacity > oldcap)
			{
				T* temp = new T[newcapacity];

				if (start)
				{
					for (size_t i = 0; i < size(); ++i)
						temp[i] = start[i];

					delete[] start;
				}
				size_t sz = size();
				start = temp;
				finish = start + sz;
				endofstorage = start + newcapacity;
			}
		}

		void resize(size_t newsize, const T& val = T())
		{
			size_t oldsize = size();
			if (newsize < oldsize)
				finish = start + newsize;

			else
			{
				if (newsize>capacity())
					reserve(newsize);

				for (size_t i = oldsize; i < newsize; ++i)
					start[i] = val;

				finish = start + newsize;

			}
		}

		T& front(){ return *start; }
		const T& front()const{ return *start; }
		T& back(){ return *(finish - 1); }
		const T& back()const{ return *(finish - 1); }

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

		T& at(size_t index)
		{
			// assert(index < size());
			if (index >= size())
			{
				throw out_of_range("vector at method: index out_of_range");
			}

			return start[index];
		}

		const T& at(size_t index)const
		{
			if (index >= size())
			{
				throw out_of_range("vector at method: index out_of_range");
			}

			return start[index];
		}

		iterator erase(iterator first, iterator last)
		{
			auto copy_first = first;
			auto copy_last = last;
			size_t last - first;
			while (copy_last != end())
			{
				*copy_first = *copy_last;
				++copy_first;
				++copy_last;
			}
			finish -= n;
			return first;
		}


		void clear()
		{
			erase(begin(), end());
		}

		void swap(vector<T>& v)
		{
			std::swap(start, v.start);
			std::swap(finish, v, finish);
			std::swap(endofstorage, v.endofstorage);
		}

		void push_back(const T& val)
		{
			if (finish == endofstorage)
			{
				reserve(capacity() * 2);
			}

			*finish = val;
			++finish;
		}

		void pop_back()
		{
			if (empty())
				return;

			--finish;
		}

		iterator insert(iterator pos, const T& val)
		{
			if (pos<begin() || pos>end())//位置不合法
				return end();

			if (finish == endofstorage)
			{
				size_t len = pos - start;
				reserve(capacity() * 2+3);
				pos = start + len;//扩容后呀更新pos,否则会有迭代器失效的可能
			}

			auto it = finish - 1;
			while (it >= pos)
			{
				*(it + 1) = *it;
				it--;
			}
			*pos = val;//这句有问题吗?扩容之后pos位置的迭代器就失效了,这样赋值是有问题的
			finish++;

			return pos;
		}

	};
}

vector_第4张图片
实现原理使用是三个迭代器(指针),来管理这段连续内存,注意在扩容后原先传入的迭代器失效问题。例如:在insert中,之前传入 的pos位置,在reserve扩容后很有可能申请了另一片连续的空间,那么之前传入的pos位置就失效了,要更新,所以在代码中的设计是用len保存步长(距离start的长度,来动态定位pos),如果没有reserve前后的那两行代码,测试下面代码就会报错

#include"MYvector.h"
using namespace gyx;
int main()
{
	vector<int> arr;
	for (int i = 0; i < 100; i++)
	{
		arr.insert(arr.begin(), i);
		std::cout << i << "     " << arr.begin();
	}
	return 0;
}

对于传入的自定义类型,要有无参构造,或者全缺省构造,因为在new申请空间时T* temp = new T[newcapacity];
只能调用无参构造,对于每一个存储的对象,在单独去用赋值运算符传递一个新的单独构造的对象。

拷贝元素:memcpy是浅拷贝
如果vector中放置的元素中涉及到资源管理时候,采用memecpy的浅拷贝是会出问题

memcpy(temp, start, sizeof(T) * size());

将容器当中的数据一个个拷贝过来时不能使用memcpy函数,当vector存储的数据是内置类型或无需进行深拷贝的自定义类型时,使用memcpy函数是没什么问题的,但当vector存储的数据是需要进行深拷贝的自定义类型时,使用memcpy函数的弊端就体现出来了。例如,当vector存储的数据是string类的时候。

你可能感兴趣的:(C++,c++,开发语言)