【C++】vector

本文详细介绍了C++STL中的vector容器,包括其基本概念、常用接口(如构造、迭代器、容量操作等)、简单实现以及扩容和拷贝问题的处理。同时,通过LeetCode题目展示了vector在实际编程中的应用。
摘要由CSDN通过智能技术生成


1. 简单了解vector

在这里插入图片描述

  1. vector:矢量,本质是一个可扩容的顺序表。
  2. 因为vector采用连续的存储空间来存储数据。所以vector访问元素非常高效,尾插和尾删效率也还行,但是其他不在末尾的插入和删除操作效率低。
  3. 空间配置器完成内存配置与释放,对象构造和析构的工作。
  4. vector的模板参数T不仅可以是内置类型,也可以是自定义类型。
    在这里插入图片描述
  5. 那么vector可以代替string吗?
    不能。
    (1)结构不同。string末尾有’\0’,可以更好地兼容C语言,vector得手动添加‘\0’;
    (2)string接口更丰富,有很多专用接口:+=、比较大小的接口。vector比较大小没有意义。

2. vector的常用接口

由于STL中很多容器的接口与string类似,功能类似,所以只讲重点的接口。

  1. 构造

在这里插入图片描述

例子
在这里插入图片描述
在这里插入图片描述

  1. 迭代器

vector的迭代器底层是指针,或者对指针进行封装。
在这里插入图片描述
例子
在这里插入图片描述

  1. 容量

在这里插入图片描述
在这里插入图片描述

  1. 尾插尾删

在这里插入图片描述
例子
在这里插入图片描述

  1. 插入删除

在这里插入图片描述
在这里插入图片描述
插入和删除,pos传参都是迭代器。
例子

	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.insert(v.begin(), 0);
	v.insert(v.begin()+1, 0);
	v.erase(v.begin() + 1);
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
//程序运行结果:0 1 2 3 4

如果要删除指定的元素,怎么办?vector没有find接口,但算法有find。
在这里插入图片描述
find可以在迭代器区间找想要的值,如果找到,就返回其迭代器;找不到,就返回last(end())。注意:迭代器区间是左闭右开的,[first,last)。

	//找到值为2的元素,并删除
	vector<int>::iterator vit = find(v.begin(), v.end(), 2);
	v.erase(vit);
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout<<endl;
//运行结果:0 1 3 4
  1. 交换

在这里插入图片描述
vector有swap接口,专门用来交换vector对象。
在这里插入图片描述

  1. 访问
	vector<int> v1(10,1);
	for (size_t i = 0; i < v1.size(); i++)
	{
		cout << v1[i] << " ";
	}
	cout << endl;
//运行结果1 1 1 1 1 1 1 1 1 1

3. 简单实现vector

#include<iostream>
#include<assert.h>
using namespace std;
namespace zn
{
	template<class T>
	class vector
	{
		typedef T* iterator;
		typedef const T* const_iterator;
	private:
		iterator _start;//指向序列的首元素
		iterator _finish;//指向序列的最后一个元素的下一个位置,相当于size
		iterator _endOfStorage;//指向序列的容量空间的下一个位置,相当于capacity
	public:
		//构造
		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_endOfStorage(nullptr)
		{}
		vector(size_t n, const T& value = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(_start[i]);
			}
		}
		vector(const vector<T>&v)
			:_start(nullptr)
			,_finish(nullptr)
			,_endOfStorage(nullptr)
		{
			reserve(v.capacity());
			iterator it = v.begin();
			while (it != v.end())
			{
				push_back(*it);
			}
		}
		//交换
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endOfStorage, v._endOfStorage);
		}
		//赋值
		vector& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}


		//析构
		~vector()
		{
			//还得判断是否是null。因为可能存在vector对象只调用默认构造,
			//没有操作就调用析构的情况。
			if (_start)
			{
				delete[]_start;
				_start = _finish = _endOfStorage;
			}
		}
		//迭代器
		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];
		}
		//扩容
		size_t size()
		{
			return _finish - _start;
		}
		size_t capacity()
		{
			return _endOfStorage - _start;
		}
		//这里用memcpy还有问题,后面讲
		void reserve(size_t n)
		{
			if (n > 0)
			{
				int len = _start - _finish;
				T* tmp = new T[n];
				memcpy(tmp, _start, sizeof(T) * size());
				delete[] _start;
				_start = tmp;
				//此时_finish和_endOfStorage还指向原来的旧空间
				_finish = _start + len;
				_endOfStorage = _start + n;
			}
		}
		//尾插
		void push_back(const T& val)
		{
			if (size() == capacity())
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = val;
			++_finish;
		}
		//插入
		iterator insert(iterator pos,const T&x)
		{
			assert(pos >= _start && pos < _finish);
			if (size() == capacity())
			{
				//如果扩容,就会出现问题:迭代器失效。
				//迭代器失效:扩容后,迭代器还指向旧空间。这里扩容后还指向旧空间。
				//解决方法:将pos指向新的空间的相对位置。
				size_t relaPos = pos - _start;
				size_t len = _finish - _start;
				T* tmp = new T[capacity() * 2];
				memcpy(tmp, _start, sizeof(T) * size());
				_start = tmp;
				_finish = _start + len;
				_endOfStorage = _start + capacity() * 2;
				pos = _start + relaPos;
			}
			iterator end = _finish-1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;
			//返回可能更新的迭代器,防止再被使用时失效了
			return pos;
		}
		//删除
		void erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);
			iterator it = pos + 1;
			while (it != end())
			{
				*(it - 1) = *it;
				++it;
			}
			--_finish;
		}
		//------------------------------------------------------------------
		//(1)删除,涉及到迭代器失效。
		//(2)迭代器失效:当删除某个迭代器指向的值后,该迭代器后面的值往前挪一位,
		//此时迭代器不再指向原来的值,迭代器就失效了。
		//(3)如果要删除的迭代器刚好指向最后一个元素,删除后再对该迭代器进行访问,
		//就会报错,因为越界。
		//(4)因此,虽然迭代器失效,代码不一定崩溃,但其运行结果肯定不对。在一些
		//平台,删除所导致的迭代器失效是不报错。
		//(5)总结:在使用insert、erase等操作后,不要使用原来的迭代器参数,如果
		//一定要使用,就返回新的迭代器迭代器,对迭代器进行重新赋值。
		//------------------------------------------------------------------
		
		//尾删
		void pop_back()
		{
			erase(--end());
		}
		//调整大小
		//T()是匿名对象,T是自定义类型,会调用默认构造,
		//如果是内置类型?内置类型升级,有默认构造函数,
		//也会调用默认构造
		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;
				}
			}
		}
	};
};


4. 拷贝问题

在上面的模拟实现vector中,提到memcpy来扩容有问题,由于当时不方便,拿到这里来讨论。

void test()
{
	vector<string> vs;
	vs.push_back("1234");
	vs.push_back("2345");
	vs.push_back("3456");
	vs.push_back("4567");
	//开始扩容
	vs.push_back("5678");
}

修正前
在这里插入图片描述
修正

void reserve(size_t n)
{
	if (n > 0)
	{
		int len = _start - _finish;
		T* tmp = new T[n];
		for(size_t i = 0;i<size();i++)
		{
			tmp[i] = _start[i];//调用string的赋值,是深拷贝
		}
		delete[] _start;
		_start = tmp;
		//此时_finish和_endOfStorage还指向原来的旧空间
		_finish = _start + len;
		_endOfStorage = _start + n;
	}
}

修正后
在这里插入图片描述


5. 练习

  1. LeetCode136:只出现一次的数字
//思路:a ^ a = 0,0 ^ b = b,a ^ b ^ a = a ^ a ^ b = 0 ^ b = b(交换律)。
//数组中其他数字出现两次,只有一次数字出现一次,将他们全部异或,就得到那个出现一次的数。
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        for(size_t i = 1;i<nums.size();i++)
        {
            nums[0]^=nums[i];
        }
        return nums[0];
    }
};
  1. LeetCode118:杨辉三角
class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        //思路:第i行开辟i+1个空间
        vector<vector<int>> vv;
        //开辟n行
        vv.resize(numRows);//开辟空间的同时改变size,为下面访问奠定基础,而reserve只是开辟空间,size没有改变
        for(size_t i = 0;i<numRows;i++)
        {
            //每i行开辟i+1个空间
            vv[i].resize(i+1,1);
            vv[i][0] = vv[i][i] = 1;
        }
        for(size_t i = 2;i<numRows;i++)   
        {
            for(size_t j = 1;j<vv[i].size()-1;j++)
            {
                vv[i][j] = vv[i-1][j-1]+vv[i-1][j];
            }
        }
        return vv;
    }
};
  1. LeetCode17:电话号码字母组合
class Solution {
public:
    const char* ch[10] = { "","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    //level记录digits中第几个号码,tmp用来累加每个号码对应的字母,最后当level
    //==号码个数时,tmp已经记录好一组字符串了,这时就可以加到vs。
    void Combine(string digits,int level,string tmp,vector<string>& vs)
    {
        if(level == digits.size())
        {
            vs.push_back(tmp);
            return ;
        }
        int num = digits[level] - '0';
        string str = ch[num];
        for(size_t i = 0;i<str.size();i++)
        {
            Combine(digits,level+1,tmp+str[i],vs);
        }
    }
    vector<string> letterCombinations(string digits) {
        //思路:利用多路递归来实现
        vector<string> vs;
        if(digits.empty())
        {
            return vs;
        }
        string tmp;
        Combine(digits,0,tmp,vs);
        return vs;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值