目录
介绍
stl中的vector是表示可变大小数组的序列容器
- 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效
- 但又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理
本质讲,vector使用动态分配数组来存储它的元素
- 当新元素插入时候,这个数组需要被重新分配大小
- 扩容方法 -- 分配一个新的数组,然后将全部元素移到这个数组
vector分配空间策略 -- vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大
- 不同的库采用不同的策略
- 但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的
- 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效
成员函数的参数基本都是使用迭代器,而不是下标来表示位置,且成员变量也是指针
模拟实现
注意点
- 构造函数,以及为了支持不同对象迭代器的模板构造函数
- reserve(支持对象的深拷贝 -> 赋值重载来拷贝),resize(匿名对象做缺省值,防止元素是自定义类型)
- 反正就是要注意写的T有可能是自定义类型的 -> 一定要进行深拷贝,对于三个成员变量的处理!!!
- insert和erase的迭代器失效问题(用返回值解决)
迭代器失效
失效的原因有很多种
- 一种是 -- 迭代器底层对应指针 所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)
- 扩容就会导致野指针的问题
- 一种是 -- 使用了失效的迭代器
- 例如erase后,该位置之后的迭代器都会失效(虽然我现在还不明白为啥要这么搞,g++下是可以跑的,但vs下好像做了封装,反正判定它就是失效了,一旦使用就报错,需要用返回值来更新迭代器
- 但明明返回的还是原来传过来的参数,但可能这样意义不同了吧,一个是失效的迭代器,一个是下一位置的迭代器,不懂为什么一定要这样搞 = (
代码
#include <iostream>
#include <assert.h>
#include <string.h>
#include <string>
using namespace std;
namespace bit
{
template <class T>
class myvector
{
public:
// Vector的迭代器是一个原生指针
typedef T *iterator;
typedef const T *const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
// construct and destroy
myvector()
: _start(nullptr), _finish(nullptr), _endOfStorage(nullptr){};
myvector(int n, const T &value = T()) // 如果这里是size_t类型的n
// 当传入参数都是整数时,会认为俩都是int,优先匹配两个参数是同样类型的模板函数
// 但int无法解引用,就会报错
// 所以这里的类型改为int,就可以匹配上了
: _start(nullptr), _finish(nullptr), _endOfStorage(nullptr)
{
_start = new T[n];
for (size_t i = 0; i < n; i++)
{
_start[i] = value;
}
_finish = _endOfStorage = _start + n;
}
template <class InputIterator> // 模板函数,为了支持其他类型的迭代器(string等)
myvector(InputIterator first, InputIterator last)
{
int n = last - first;
_finish = _start = new T[n];
_endOfStorage = _start + n;
for (auto begin = first; begin != last; ++begin)
{
push_back(*begin);
}
}
myvector(const myvector<T> &v)
: _start(nullptr), _finish(nullptr), _endOfStorage(nullptr)
{
reserve(v.capacity());
// auto it = v.cbegin();
// for (; it != v.cend(); ++it)
// {
// push_back(*it);
// }
for(auto it: v){
push_back(it);
}
}
myvector<T> &operator=(myvector<T> v) // v传进来首先会进行拷贝构造,所以形参和实参空间不同
{
swap(v);
return *this;
}
~myvector()
{
delete[] _start;
_start = _finish = _endOfStorage = nullptr;
}
// capacity
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endOfStorage - _start;
}
void reserve(size_t n)
{
if (n > capacity())
{
int sz = size();
T *tmp = new T[n];
// memcpy(tmp,_start,sizeof(T)*sz); //如果vector中的元素是自定义类型(string/vector),那这里就是浅拷贝(按字节拷贝)
// 为了进行深拷贝,可以去赋值重载(内部是深拷贝)(拷贝构造不行,无法调用)
for (size_t i = 0; i < sz; ++i)
{
tmp[i] = _start[i];
}
delete[] _start;
_start = tmp;
_finish = _start + sz;
_endOfStorage = _start + n;
}
}
void resize(size_t n, const T &value = T()) // T() -- 匿名对象(调用默认构造)(对于自定义类型/对象)
// 对于内置类型,c++在模板出来之后,对内置类型做了升级,也可以调用默认构造了
{
if (n > size())
{
reserve(n);
for (iterator begin = _finish; begin != _endOfStorage; begin++)
{
*begin = value;
}
_finish = _start + n;
}
else
{
_finish = _start + n;
}
}
T &operator[](size_t pos)
{
assert(pos >= 0 && pos < size());
return *(_start + pos);
}
const T &operator[](size_t pos) const
{
assert(pos >= 0 && pos < size());
return *(_start + pos);
}
void push_back(const T &x)
{
if (size() == capacity())
{
reserve(size() + 1);
}
*(_finish++) = x;
}
void pop_back()
{
_start = erase(_start);
}
void swap(myvector<T> &v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endOfStorage, v._endOfStorage);
}
iterator insert(iterator pos, const T &x)
{
assert(pos >= _start && pos <= _finish);
int len = pos - _start;
if (size() == capacity())
{
reserve(size() + 1);
}
pos = _start + len; // 如果扩容了,原pos指向的空间就已经被释放掉了,所以需要更新pos
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
return pos; // 防止外部传入的pos失效,返回值可以让它更新
}
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
for (iterator begin = pos + 1; begin != _finish; ++begin)
{
*(begin - 1) = *begin;
}
_finish--;
return pos; // vs中只要使用了原pos,就会报错
// g++没有管,但尽量不要使用,虽然这里有返回值来进行迭代器更新,但难免会有疏漏(不同平台上的行为不同,所以不要再使用)
}
private:
iterator _start; // 指向数据块的开始
iterator _finish; // 指向有效数据的尾
iterator _endOfStorage; // 指向存储容量的尾
};
};
算法题示例
只出现一次的数字
题目
137. 只出现一次的数字 II - 力扣(LeetCode)
思路
这里运用了位运算(真的很难想到啊呜呜)
- 题目不是说除了一个数字之外,其他都是三个绑定在一起出现吗
- 而且每一位都是 0 / 1 ,所以必定1 / 0 俩数字里面,会有一个出现的次数不是3的倍数,而那个数字就是要找元素的该位数字
- 找到该位后,只要拿到所在的位数,就能还原出该位代表的十进制大小
代码
int singleNumber(vector<int>& nums) {
int ans = 0;
for (int i = 0; i < 32; ++i) { //遍历所有元素的每一位
int tmp=0;
for(auto c:nums){
tmp+=((c>>i)&1); //得到所有元素第i位的和
}
if(tmp%3==1){ //如果该总和不是3的倍数,说明该位是1(因为0不影响大小)
ans|=(1<<i); //将ans的该位还原出来
}
}
return ans;
}
电话号码组合
题目
思路
按照这样的规律拿到序列,很像将数字代表的字母展开,然后进行深搜
- 为了可以返回后可以成功拿到新的序列,需要将" 此时已经有的序列 "当做参数,不然return后就没辣
- 除此之外,因为字母顺序对应的是数字顺序,所以也需要将" 此时利用的数字是第几位 "记录下来
代码
void work(string arr[],string digits,int level,string ret,vector<string>& v){
if(level==digits.size()){ //序列字符数=数字位数,说明已经遍历完成一次
v.push_back(ret);
return;
}
int count=digits[level]-'0'; //拿到此时对应的数字
for(int i=0;i<arr[count].size();++i){
work(arr,digits,level+1,ret+arr[count][i],v);
//向已有序列加入对应数字的字母(返回后就可以拿到该数字对应的新的字母)
}
}
vector<string> letterCombinations(string digits) {
vector<string> ret;
if(digits!=""){
string arr[]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
work(arr,digits,0,"",ret);
//记录此时对应的数字是第几位 -- 0,以及此时序列是多少 -- " "
}
return ret;
}