1. 简单了解vector
- vector:矢量,本质是一个可扩容的顺序表。
- 因为vector采用连续的存储空间来存储数据。所以vector访问元素非常高效,尾插和尾删效率也还行,但是其他不在末尾的插入和删除操作效率低。
- 空间配置器完成内存配置与释放,对象构造和析构的工作。
- vector的模板参数T不仅可以是内置类型,也可以是自定义类型。
- 那么vector可以代替string吗?
不能。
(1)结构不同。string末尾有’\0’,可以更好地兼容C语言,vector得手动添加‘\0’;
(2)string接口更丰富,有很多专用接口:+=、比较大小的接口。vector比较大小没有意义。
2. vector的常用接口
由于STL中很多容器的接口与string类似,功能类似,所以只讲重点的接口。
- 构造
例子
- 迭代器
vector的迭代器底层是指针,或者对指针进行封装。
例子
- 容量
- 尾插尾删
例子
- 插入删除
插入和删除,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
- 交换
vector有swap接口,专门用来交换vector对象。
- 访问
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. 练习
//思路: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];
}
};
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;
}
};
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;
}
};