C++——vector容器的基本使用和模拟实现

1、vector的介绍

  1. vector是表示可变大小数组的序列容器。
  2.  就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自 动处理。
  3.  本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是 一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
  4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
  5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增 长。
  6. 与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起lists和 forward_lists统一的迭代器和引用更好。

注意:string与vector类似,但也有不同之处。string是存放字符的动态数组,vector是存放一个T类型的动态数组,本质都是一个顺序表。并且我们使用vector时需要加头文件#include

 2、vector的常用接口

构造函数

void test_vector1()
{
	vector v1;
	vector v2(10, 8);//开十个空间都赋值8
	vector v3(++v2.begin(), --v2.end());//用自身的迭代器区间构造
	vector v4(v3);//拷贝构造

	string s("hello world");
	vector v5(s.begin(), s.end());//用string的迭代器区间构造
}

尾插和尾删

vector v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	v.pop_back();
	v.pop_back();
	v.pop_back();
	v.pop_back();

由于头删和头插效率太低了,vector并没有提供相应的接口。

遍历和[]访问

vector可支持[]下标访问,并且可以改变vector中的值,至于遍历,我们可以用下标遍历,也可以使用范围for来遍历。

//遍历
	for (size_t i = 0; i < v.size(); i++)
	{
		v[i] += 1;//可修改
		cout << v[i] << " ";
	}
	cout << endl;

	//
	vector::iterator it = v.begin();
	//迭代器 it指向vector的第一个数据
	while (it != v.end())
	{
		*it -= 1;
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//范围for 本质也是迭代器
	for (auto e : v)
		//把v中的每个数据遍历的时候给给e
	{
		cout << e << " ";
	}
	cout << endl;

	//int a[] = { 1,2,3 };
	原生指针就是天然的迭代器,数组支持范围for,会被替换指针
	//for (int* p = a; p < a + sizeof(a) / sizeof(int); p++)
	//{}

reserve和resize的使用

    v.reserve(100);   //扩容并只改变capacity空间
	v.resize(100);    //扩容不仅改变capacity空间并且改变size
	v.resize(100, 5); //扩容+初始化 或 删除数据(类比string)
	v.resize(2);      //截取数据但不改变capacity

find和assign的使用

vector::iterator ret = find(v.begin(), v.end(), 3);
	//auto ret = find(v.begin(), v.end(), 3);
	//如果没找到find返回第二个参数
    //如果找到了返回该数下标
    vector v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	v.assign(10, 5);//给v的前十个值上全部赋值为5,并且会覆盖原来的值

clear、capacity、size、max_size的使用


	cout <<"max_size:"<< v.max_size() << endl;//数据所能存放的最大容量
	cout <<"capacity:"<< v.capacity() << endl;//当前容量
	cout <<"size:"<< v.size() << endl;//当前数据个数

	v.clear();//只清理size的值不改变capacity
	cout << "-------------------";
	cout << "max_size:" << v.max_size() << endl;
	cout << "capacity:" << v.capacity() << endl;
	cout << "size:" << v.size() << endl;

insert、erase的使用

    vector v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	//v.assign(10, 5);
	//覆盖之前已有的值

	vector::iterator ret = find(v.begin(), v.end(), 3);
	//auto ret = find(v.begin(), v.end(), 3);
	//如果没找到find返回第二个参数
	if (ret != v.end())
	{
		cout << "找到了" << endl;
		v.insert(ret, 30);
		//v.erase(ret);
		//不能在这删,因为ret失效了
		//这个迭代器失效问题,我们后面会细讲
	}
	v.insert(v.begin(), 30);//在v.begin()的前面插入30也就是头插
	vector::iterator pos = find(v.begin(), v.end(), 300);
	if (pos != v.end())
		//找不到的时候就会返回v.end
		//因此找不到的时候不会进入
	{
		v.erase(pos);//删除pos下标的值
		
	}

增容方式

int main()
{
	size_t sz;
	std::vector foo;
	sz = foo.capacity();
	std::cout << "making foo trow;\n";
	for (int i = 0; i < 100; i++)
	{
		foo.push_back(i);
		if (sz != foo.capacity())
		{
			sz = foo.capacity();
			std::cout << "capacity changed:" << sz << '\n';
		}
	}
}

C++——vector容器的基本使用和模拟实现_第1张图片

 由上图可知vs下vector是以大约1.5增容的。而Linux下g++是以2倍增容的。

vector相关OJ题

1、只出现一次的数

力扣

class Solution {
public:
    int singleNumber(vector& nums) 
    {
        int value=0;
        for(auto e : nums)
        {
            value^=e;//将nums中的值全部互相异或,这样的话相同的数据刚好抵消
        }
        return value;//返回剩下的那个与其他值不相等的数据

    }
};

2、杨辉三角形

力扣

杨辉三角形
class Solution {
public:
	vector> generate(int numRows)
	{
		vector> vv;
		vv.resize(numRows);//先给外层vector开内存
		for (size_t i = 0; i < numRows; i++)
		{
			vv[i].resize(i + 1);//给每一个小vector开内存
			vv[i][0] = vv[i][vv[i].size() - 1] = 1;//小vector中的头尾设为1
		}
		for (size_t i = 0; i < vv.size(); i++)
		{
			for (size_t j = 0; j < vv[i].size(); j++)
			{
				if (vv[i][j] == 0)
				{
					vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
				}
			}
		}
		return vv;
	}
};

3、电话号码的字母组合(重点题,代码中有详细注释)

//回溯力扣题:《电话号码的字母组合》
class Solution {
	string arr[10] = { "","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz" };
	//将0-9(其中0和1不给值)中的每个数字映射一个字符串
public:
	//最关键的一个函数体,会反复使用
	void _letterCombinations(const string& digits, size_t i,
		string combinStr, vector& strV)
		//传入digits的引用并且加const为了防止改变digits并且减少拷贝,digits就是我们传入的字符串
		//传入i为了遍历digits中的每个字符串,因为digits字符串的每个字符还代表这一个字符串:例如2->"abc"
		//combinStr是为了去存放每次递归后的字符串组合
		//strV为了存放所有情况遍历完全之后的字符串总和
	{
		if (i == digits.size())
			//由于i是从0开始的,倘若此时i已经与digits的长度相等那么说明i已经越界了也代表digits的字符串已经遍历完全了
		{
			strV.push_back(combinStr);
			//将我们组合好的combinStr尾插到strV中,strV也就是最终我们需要返回的那个字符串
			return;//立即返回,只要执行该语句,该函数将不再执行接下来的语句,因为此时就需要返回
		}
		string str = arr[digits[i] - '0'];
		//定义一个新的字符串str并且让str中存入我们需要遍历的字符串
		//digits[i]是访问到我们输入字符串中的某个字符
		//digits-'0'为了得到arr的下标   使其得到digits字符串中每个字符中的字符串
		//最后让它赋值给str我们就可以遍历str将其进行组合
		for (size_t j = 0; j < str.size(); j++)//遍历str进行组合,并且继续递归调用_letterCombination函数
		{
			_letterCombinations(digits, i + 1, combinStr + str[j], strV);
			//传入digits   为了继续访问它的字符中的字符串
			//传入i+1  为了访问digits中的下一个字符中的字符串 并且这样的话i不会发生改变 以便下次可以直接继续使用i+1
			//传入 combinStr+str[j]  为了将我们得到的str中的字符尾插到combinStr中存放起来,等遍历结束可以尾插到strV中
			//传入 strV   如果遍历结束可直接把combinStr中的值直接传入strV中
		}
	}
	vector letterCombinations(string digits)
		//digits是我们所选择的字符串
	{
		string combinStr;//定义一个combinStr字符串
		vector strV;//定义一个vector容器里面的值是string值
		if (digits.empty())//为了考虑我们的digits为空的情况下
			return strV;//若为空 直接返回

		//开始递归调用此函数
		_letterCombinations(digits, 0, combinStr, strV);

		return strV;//递归调用完成后返回
	}
};

图解:

C++——vector容器的基本使用和模拟实现_第2张图片

3、vector的模拟实现

vector的成员变量

private:
	iterator _start; // 指向数据块的开始
	iterator _finish; // 指向有效数据的尾
	iterator _endofStorage; // 指向存储容量的尾

C++——vector容器的基本使用和模拟实现_第3张图片

构造函数

默认构造

vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{}

迭代器构造

//一个类模板的成员函数,又可以是一个函数模板
		template
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

拷贝构造

//v2(v1)
		//现代写法
		vector(const vector& v)
		{
			vector tmp(v.begin(),v.end());
            
            //没有模拟swap之前可用下列写法
			/*swap(_start, tmp._start);
			swap(_finish, tmp._finish);
			swap(_endofstorage, tmp._endofstorage);*/

			//this->swap(tmp);
			swap(tmp);
		}
v2(v1)传统写法
		//vector(const vector& v)
		//	//拷贝构造(深拷贝)
		//{
		//	_start = new T[v.capacity()];
		//	_finish = _start + v.size();
		//	_endofstorage = _start + v.capacity();

		//  memcpy是浅拷贝
		//	memcpy(_start, v._start, v.size() * sizeof(T));
		//}

这里传统写法不是很推荐,因为会用到memcpy,因为memcpy会用到浅拷贝。

析构函数

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

赋值运算符重载函数

//v1=v2
		//现代写法
		vector& operator=(vector v)
		{

            //未模拟swap时用下列方法
			/*swap(_start, v._start);
			swap(_finish, v._finish);
			swap(_endofstorage, v._endofstorage);*/


			swap(v);

			return *this;
		}

迭代器

public:
		typedef T* iterator;
		typedef const T* const_iterator;

begin()和end()


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

容量函数

size()和capacity()


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

reserve

void reserve(size_t n)
		{
			if (n > capacity())
			{
				//扩容
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy是浅拷贝
					//memcpy(tmp, _start, sizeof(T) * size());
					for (size_t i = 0; i < sz; i++)
					{
						//T 是int,一个一个拷贝没问题
						//T 是string,一个一个拷贝调用是T的深拷贝复制,也没问题
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + sz;
				/*_finish = tmp + size();
				_start = tmp;*/
				_endofstorage = _start + n;

			}
		}

resize


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

empty

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

增删查改

push_back

void push_back(const T& x)
		{
			if (_finish == _endofstorage)
			{
				扩容
				//size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
				size_t sz = size();
				//T* tmp = new T[newCapacity];
				//if (_start)
				//{
				//	memcpy(tmp, _start, sizeof(T) * size());
				//	delete[] _start;
				//}
				_start = tmp;
				_finish = _start + sz;
				//_finish = tmp + size();
				//_start = tmp;
				//_endofstorage = _start + newCapacity;

				reserve(capacity() == 0 ? 4 : capacity() * 2);
			} 
			*_finish = x;
			_finish++;
		}

pop_back

void pop_back()
		{
			assert(_finish > _start);//检查是否为空

			_finish--;
		}

insert

iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start);
			assert(pos <= _finish);
			//满了就扩容
			if (_finish == _endofstorage)
			{
				//扩容
				//扩容会导致pos失效,扩容需要更新一下pos
				size_t len = pos - _start;//计算一下扩容前pos与_start直接的距离以便以计算新pos
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;//得到新pos的位置
			}

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

			return pos;
		}

earse


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

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

swap

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

下标访问

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

完整代码

#pragma once

namespace mwb
{
	template
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
		
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{}

		// v2(v1)传统写法
		//vector(const vector& v)
		//	//拷贝构造(深拷贝)
		//{
		//	_start = new T[v.capacity()];
		//	_finish = _start + v.size();
		//	_endofstorage = _start + v.capacity();

		//  memcpy是浅拷贝
		//	memcpy(_start, v._start, v.size() * sizeof(T));
		//}
		
		//一个类模板的成员函数,又可以是一个函数模板
		template
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}
		void swap(vector& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}
		//v2(v1)
		//现代写法
		vector(const vector& v)
		{
			vector tmp(v.begin(),v.end());
			/*swap(_start, tmp._start);
			swap(_finish, tmp._finish);
			swap(_endofstorage, tmp._endofstorage);*/
			//this->swap(tmp);
			swap(tmp);
		}
		//v1=v2
		//现代写法
		vector& operator=(vector v)
		{
			/*swap(_start, v._start);
			swap(_finish, v._finish);
			swap(_endofstorage, v._endofstorage);*/
			swap(v);

			return *this;
		}
		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _endofstorage = nullptr;
			}
		}
		const_iterator begin() const
		{
			return _start;
		}
		const_iterator end() const
		{
			return _finish;
		}
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		T& operator[](size_t i)
		{
			assert(i < size());
			return _start[i];
		}
		size_t size() const
		{
			return _finish - _start;
		}
		size_t capacity() const
		{
			return _endofstorage - _start;
		}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				//扩容
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy是浅拷贝
					//memcpy(tmp, _start, sizeof(T) * size());
					for (size_t i = 0; i < sz; i++)
					{
						//T 是int,一个一个拷贝没问题
						//T 是string,一个一个拷贝调用是T的深拷贝复制,也没问题
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + sz;
				/*_finish = tmp + size();
				_start = tmp;*/
				_endofstorage = _start + n;

			}
		}
		void resize(size_t n, const T& val = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())
				{
					reserve(n);
				}
				while (_finish != _start + n)
				{
					*_finish = val;
					_finish++;
				}
			}
		}
		iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start);
			assert(pos <= _finish);
			//满了就扩容
			if (_finish == _endofstorage)
			{
				//扩容
				//扩容会导致pos失效,扩容需要更新一下pos
				size_t len = pos - _start;//计算一下扩容前pos与_start直接的距离以便以计算新pos
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;//得到新pos的位置
			}

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

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

			iterator begin = pos + 1;
			while (begin < _finish)
			{
				*(begin - 1) = *begin;
				begin++;
			}
			_finish--;
			return pos;
		}
		void push_back(const T& x)
		{
			if (_finish == _endofstorage)
			{
				扩容
				//size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
				size_t sz = size();
				//T* tmp = new T[newCapacity];
				//if (_start)
				//{
				//	memcpy(tmp, _start, sizeof(T) * size());
				//	delete[] _start;
				//}
				_start = tmp;
				_finish = _start + sz;
				//_finish = tmp + size();
				//_start = tmp;
				//_endofstorage = _start + newCapacity;

				reserve(capacity() == 0 ? 4 : capacity() * 2);
			} 
			*_finish = x;
			_finish++;
		}
		void pop_back()
		{
			assert(_finish > _start);//检查是否为空

			_finish--;
		}
	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};
}

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