邯郸网站建设浩森宇特竞价 推广
vector
- vector
- 构造
- 尾插(删)和扩容
- inert(插入)
- 迭代器失效
- erase(删除)
- resize(调整空间)
- 深浅拷贝
- 迭代器
- 拷贝和赋值(v2(v1)和v1 = v3)
- 多个数据插入
- 迭代器区间初始化
(constructor)构造函数声明 | 接口说明 |
---|---|
vector()(重点) | 无参构造 |
vector(size_type n, const value_type& val =value_type()) | 构造并初始化n个val |
vector (const vector& x); (重点) | 拷贝构造 |
vector (InputIterator first, InputIterator last); | 使用迭代器进行初始化构 |
构造
namespace A
{//存储在vector的值可能是int或者string 不同类型,写为模版template<class T>class vector{public://若为私有,访问时需要突破类域,因此重命名为公有typedef T* iterator;//构造vector(){ }//析构~vector(){if (_start)delete[] _start;_start = _finish = _end_of_storage = nullptr;}private://给缺省值,缺省值主要是给没有显⽰在初始化列表初始化的成员使⽤的,不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表则用这个缺省值初始化iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};
}
先实现一些简单的功能
//求空间大小size_t capacity(){//指针差值return _end_of_storage - _start;}//有效元素个数size_t size(){return _finish - _start;}//重载[],下标访问T& operator[](size_t i){assert(i < size());return _start[i];}
尾插(删)和扩容
实现尾部插入之前,需要判断先判断空间的大小,如果大小不够,需要扩容。
而关于扩容,并非是只在尾插时使用,所以需要在内部再次判断。
步骤;1,直接new与所需空间大小相同的空间即n。
2,即拷贝原空间的数据到新空间,并delete原空间。
但是我们需要注意的一点是在拷贝原空间的数据时,使用memcpy对内置类型的拷贝时满足我们的需求,但是在对自定义类型的拷贝时是值拷贝,也就是浅拷贝,不符合需求,此时解决办法是遍历原空间依次赋给新空间。
3,更新首尾指针和空间大小。
//扩容void reserve(size_t n){if (n > capacity()){//记录旧的size,因为_start更新后,size的值会改变size_t old_size = size();T* tmp = new T[n];if (_start){//memcpy(tmp, _start, sizeof(T) * size());for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + old_size;_end_of_storage = _start + n;}}void push_back(const T& x){if (_finish == _end_of_storage){size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);}*_finish = x;++_finish;}//尾删void pop_back(){assert(_start < _finish);--_finish;}
顺序表
resize 开空间+初始化
reservve 单纯开空间
emplace和insert功能类似,插入,细节有差别
vector< char >不能替代string,string兼容c语言。
1,string 数组后面有’ \0 ',vector< char >没有。
2,string += 字符或者字符串,即插入字符,字符串,string的find可以查找子串
3,vector< char >可以调用算法库的查找,未找到return last;
如何看源码
1,了解功能
2,抓核心枝干
时间换空间:顺序表缩容逻辑(不划算)
空间换时间:(比较划算)
inert(插入)
//pos是一个指针,是iterator即T*类型的
void insert(iterator pos, const T& x)
{//位置不能越界assert(pos >= _start);assert(pos <= _finish);//1,判断空间,不够扩容//2,拷贝原数据if (_finish == _end_of_storage){size_t newcapacity = _end_of_storage == 0 ? 4 : 2 * capacity();reserve(newcapacity);}iterator it = _finish - 1;while (pos >= it){*(it + 1) = *it;--it;}*it = x;++_finish;
}
迭代器失效
代码看似没有问题,其实在扩容后会形成迭代器失效,pos的位置已经丢失,
所以需要想办法得到扩容后新的pos的位置。
迭代器失效在vs下会强制检查,迭代器失效后不能访问,访问会崩溃报错。
失效原因:可能是底层成为野指针,即缩容逻辑,也可能是删除后,位置更改。
g++下没有强制检查。
失效后不要访问,重新赋值后再访问。
if (_finish == _end_of_storage){size_t len = pos - _start;size_t newcapacity = _end_of_storage == 0 ? 4 : 2 * capacity();reserve(newcapacity);pos = len + _start;}
erase(删除)
关于erase也存在迭代器失效的问题,解决办法是返回一个迭代器,在删除后接收。
//.c//不返回值
//迭代器失效在vs下会强制检查,迭代器失效后不能访问,访问会崩溃报错。void erase(iterator pos){assert(pos >= _start);assert(pos <= _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;//更新pos,否则迭代器会失效return pos;}//返回迭代器iterator erase(iterator pos){assert(pos >= _start);assert(pos <= _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;//更新pos,否则迭代器会失效return pos;}//------------------------------------------
//testA::vector<int> v1 = { 1,2,2,2,3,4,5,6,6,6 };for (auto e : v1)
{cout << e << " ";
}
cout << endl;A::vector<int>::iterator it = v1.begin();
while (it != v1.end())
{if (*it % 2 == 0){v1.erase(it);}else{it++;}
}for (auto e : v1)
{cout << e << " ";
}
cout << endl;
resize(调整空间)
比原来空间大
比如说,原来数据为:vector< int > v = { 1,2,3,4,5 };
使用resize(10)
,如果不给值,会默认赋值为0
会将数据置为1,2,3,4,5,0,0,0,0,0
再如resize(10,2)
会变为1,2,3,4,5,2,2,2,2,2
比原来空间小
resize(2)
变为1,2
void resize(size_t n, T val = T()){if (n > size()){//插入数据,先扩容reserve(n);while (_start + n != _finish){*_finish = val;++_finish;}}else{_finish = _start + n;}}
深浅拷贝
memcpy是一种值拷贝。对深拷贝的自定义类型会进行浅拷贝,然后就会发生问题,
对于内置类型是没有问题的。
解决办法是,一个一个赋值解决。
迭代器
一般情况下我们会实现两种迭代器,一种是普通版本的迭代器,可以修改其本身,也可以修改指向的对象;另外一种是const修饰的迭代器,其本身可以修改,指向对象不可以修改。
需要注意的一点是迭代器的end()是指向结尾数据的下一个位置,我们此处的_finish正好满足条件,所以直接放回即可。
typedef T* iterator;typedef const T* const_iterator;//迭代器iterator begin(){return _start;}iterator end(){return _finish;}//const迭代器const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}
拷贝和赋值(v2(v1)和v1 = v3)
//v2(v1)vector(vector<T>& v){//申请和v1相同的空间大小,使用范围for(支持迭代器就支持范围for)尾插数据到新空间即可。 reserve(v.capacity());for (auto& e : v){push_back(e);}}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}//v1 = v3vector<T>& operator=(vector<T> v){//直接交换即可,因为拷贝构造已经形成swap(v);return *this;}
多个数据插入
//对val的类型不确定,给缺省值,是匿名对象vector(size_t n, const T& val = T()){reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}}
但是如果仅仅只写这一种的话,在我们实现对迭代器区间初始化时,会发生错误匹配的情况,是模板实例化时的问题,
可以想到的是把size_t类型修改为int类型,这样就可以解决,但是这样有引发新的问题,对于无符号整型类型没办法处理,所以直接两个都留下,构成了函数重载。
//为了解决模版实例化的问题vector(int n, const T& val = T()){reserve(n);for (int i = 0; i < n; i++){push_back(val);}}//c++11vector(initializer_list<T> il){reserve(il.size());//此处用引用,因为T的类型不确定for (auto& e : il){push_back(e);}}
迭代器区间初始化
//迭代器区间初始化//写为模版,可以初始化非iterator的类型template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}iterator erase(iterator pos){assert(pos >= _start);assert(pos <= _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;//更新pos,否则迭代器会失效return pos;}