vector各函数
#include<iostream>
#include<vector>
using namespace std;namespace lz
{//模拟实现vectortemplate<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//默认成员函数vector(); //构造函数vector(size_t n, const T& val); //构造函数template<class InputIterator>vector(InputIterator first, InputIterator last); //构造函数vector(const vector<T>& v); //拷贝构造函数vector<T>& operator=(const vector<T>& v); //赋值运算符重载函数~vector(); //析构函数//迭代器相关函数iterator begin();iterator end();const_iterator begin()const;const_iterator end()const;//容量和大小相关函数size_t size()const;size_t capacity()const;void reserve(size_t n);void resize(size_t n, const T& val = T());bool empty()const;//修改容器内容相关函数void push_back(const T& x);void pop_back();void insert(iterator pos, const T& x);iterator erase(iterator pos);void swap(vector<T>& v);//访问容器相关函数T& operator[](size_t i);const T& operator[](size_t i)const;private:iterator _start; //指向容器的头iterator _finish; //指向有效数据的尾iterator _endofstorage; //指向容器的尾};
}
分析:
- 为什么需要模板?
答:该类内部可以的成员变量类型可以是任意,所以需要使用类模板,此外类内所有涉及val的函数参数类型都是T。
- 关于模板爆了的错:
在类内声明类外实现,::必须加temeplate<class T>。
::class T不行,会报错,模板声明完,搭配的是vector<T>。
- 成员变量:
答:我们常用[]去访问vector,vector空间是连续的。一般都需要通过迭代器,本质是原生指针。因为存储元素的不确定性,所以定义指针的类型也是T* 。最开始做了:typedef T* iterator。_start、尾元素下一位(并未存储):_finish、最多能放到的位置:_endofstorage-1。 - 关于:size()和capacity():
答:跟上一节string不一样,我们没有对应容量属性,size()直接用finish-start(比如10个元素,我们从1号到10号,finish是尾的下一个,在11号,11-1=10,10是size,所以该式子正确)。而容量:_endofstorage-star是容量,因为数组从0开始放。_finish == _endof,考虑扩容。因为已经超了这最后一个可放位置。 - 关于构造函数:
答:这里有两种构造函数,分别是无参,有两个参(int n,const T &val=T())。半缺省型构造函数。
- 半缺省型构造函数:vector(size_t n, const T& val = T());
分析:
如果第二个参数给值,我们就取引用,不给值就用默认类型。解释T=(),如果是string,就调string的默认构造函数。显然我们常用vector的这个函数知道,比如我们用bool类型,自动填充的是false。总之,这个的意思就是,如果不给值,我们就根据val本身的类型,去通过构造函数填充默认值。此外,例如:int a(10),这样是可以通过编译的。 - 带初始化n个 值的拷贝构造函数:vector(size_t n, const T& val)
参数类型:后面的T()是说val给默认值。调用默认构造函数。
实现过程:
构造参数列表中完成初始化。
函数体内:扩容、循环push_back插值使得每个位置有值。
**经典错误:**这里不慎可能有错,如下图:
用10个1初始化会报错
但是char,就不会报错。
分析:一个参数没问题。第一个参数是int,第二个是char也没问题。而两个int,就出错了。当给一个参数时,半缺省构造函数最匹配,就会调用半缺省构造函数。int、char会调用半缺省。int符合size_t,const T,可以变成char。而用int和int,size_t转换一次匹配上了,const T再转换一次,所以跟半缺省情况需要转换两次。而如果和下面inputIterator做匹配,只需要转换一次,所以下面更匹配,然而下面的函数体不能让代码通过,所以就报错了。那么如果改半缺省为构造函数第一个参数类型为int,原码中本身是size_t(无符号整型)。
解决方案:
我看的视频里面说,给个重载即可,一个int类型重载。
- 拷贝构造函数:
答:在类外实现,只是加前缀vector::拷贝构造函数名,后面别变。
1> 传统写法过程:
开辟连续空间,可以开capacity()也可以开size()大小。
拷贝数据内容,挨个拷贝,以start[i]=v[i]赋值式接收,做深拷贝。
更新_finish和_endOfS,更新_finfish依据size()方法。而 更新endOfS必须和开空间的大小方法一致。至此,可以做如下定义:v2(v1)
2>
总之,主要注意点就是不能用memcpy(),要做深拷贝。
3> 现代写法过程:
先reserve(n),更新了内部成员变量。再像上面一样,做挨个深拷贝。
这里深拷贝可以用:push_back(),push_bac()的实现其实是直接给最后一个位置写值,且是引用类型,如果是对象类型会做深拷贝。使得每个插入赋值做的是深拷贝。
4> 更简洁
做构造函数做tmp,
使用自写swap(),自写swap原理是:交换_start,因为底层是连续空间,交换_start值即可。这里只是涉及指针变化,但是使得拷贝构造函数中实现深拷贝借助这个没问题。因为连续空间方便直接访问。 - 迭代器构造函数
1.>写迭代器构造函数需模板声明,因为如果类型写成iterator模板,那么迭代器构造只能是该类型的迭代器。然而,该vector应该要支持各种迭代器类型的迭代器构造函数。类内可以再用模板。因为外面的迭代器本质是char*,但是你外面调用不同的类型做出的实例化后得到的模板类的类型不一样。总之,使用模板让你可以用STL中各种迭代器类型去初始化构造vector。
2,> 如下代码:我们发现可以用string的迭代器去使vector中的迭代器构造函数去拷贝构造一个vector,如果你输出发现结果是string每个字符的ascall码。但是你用vector就可以输出每个字符本身。
string s("hh aa);
lz::vector<int> v(s.begin(), s.end());
for(auto e:v)
{cout << e << endl;
}
2.> 分析过程:调用时,使用last和first两个不同迭代器类型,也就是vector的首尾位置。所以我们拿到这两个变量,做加法,first最终会到last。
3.> 注意一定要先在构造列表中做初始化,因为后面reserve()中会使用到这些值。
4.>由此,可以再用迭代器构造函数的方式写拷贝构造函数。先创建一个以迭代器构造函数拷贝一个vector<>tmp,再用自己实现的Swap()使得当前对象成员变量与tmp交换,更加省事。
- 析构函数:
答:成员变量迭代器类型,也就是指针。直接释放即可。且置于nullptr。释放_start即可,因为连续。 - push_back():
答:参数必须为const T&x,一定要加const。首先用&效率高,其次,经常push_back(“xxx”),显然参数是个常量字符串,而常量字符串用单参数引用接收用隐式类型转换,产生临时变量,临时变量具有常性。且简单看,T类型是普通类型肯定不能接收常量类型。
回忆:隐式类型转换:因为用其它类型接收某一类型。
double d = 2.2;
int& i = d;
有编译错误,因为d会隐式类型转换。
再回到这个函数上,需要先扩容判断,当endofsotrage == _finish,就reverse()。
9. 关于容量的函数:
- reserve():
基本思路:如果n>capacity()才做扩容,如果符合,再判断之前是否存在元素,如果存在还要把旧的拷贝过来。开辟空间:T* tmp = new T[n];
易错点: 1. 扩容且复制完后,如果用三行代码去规整结构:
_start = tmp;
_finish = _start + size();
_end_of_s = _start + n;
经典错误1.:size():return _finish - _start; start已经通过扩容后新空间的首地址tmp更新了,那么size()返回的size已经用坏味道了。那么怎么办呢?可以在更新_start之前先求size();
因为==_finish在末元素下 一个,整体从0开始,所以+n,到末元素后一==,思路正确。而==_endofs = start + n 也是在最大合法位置的下一个位置==。因整体从0开始,所以它下标是N,然而是第N+1个位置。所以每次扩容时_finish == +endOfs时做扩容。
经典错误2:memcpy():memcpy只适合浅拷贝。
- resize():开空间加初始化。reserve()不会改变size。
麻烦点在如果已经有空间了。
过程:如果n > _capacity 需要扩容加初始化。reserve(n)。
然后之间初始化,n>size():就执行:拷贝从_finish到 start + n位置做*_finish = val。 ++_finish;
如果在size和capacity之间,说明有空间,只需要初始化填数据。如果小于size,改变size即可。n <= size,是做缩小,_finish = start+n;n个元素,而_finish是末尾的下一个,所以赋值start + n。只改变大小_size。
- 重载:
- []:size_t pos:因为要修改内容,所以需要T& 模板引用类型。此外,位置要合法
- = :赋值重载
我没有实现重载方法,在vs2022中都不能通过编译,这个错误很典型。但能引发它的原因可能有很多。如下因为没实现赋值重载,需要的函数声明了,但没实现。
赋值重载过程:
1>简洁写法:通过非引用类型的参数做了拷贝构造,再利用Swap()直接交换参数很当前对象。
2> 参数类型:vector类型,返回值是vector类型。返回当前对象本身。return *this;赋值运算符显然要的是个对象。
- 迭代器:
迭代器必须public,私有的typedef无法访问。因为外部迭代器类型,需要定义出来使用。此外,我在类内声明而类外实现iteartor会有错。如下图:
需要加typename标志后面加的是一个类型。为什么要加这个呢?C->W,W类型是C,W是C的从属名称。W类型并不明确,如图片里面的C::iterator,是模板参数中其它的值,类型不明确,编译器可能编译有歧义,不知道它是类型还是什么函数,所以需要你用typename来做标志,标记后面是个类型。当类外声明迭代器时,需要我们去加上typename。此外,常说的类模板是具体实例化后的类,比如:vector<int>,这样它才算模板类
- const类型迭代器
需要重命名一个const类型,且再写begin()和end(),类型是const_iteartor,参数类型在()后加const。
解释地址:
https://blog.csdn.net/qq_40080842/article/details/127037413?app_version=5.11.1&code=app_1562916241&csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22127037413%22%2C%22source%22%3A%22qq_40080842%22%7D&uLinkId=usr1mkqgl919blen&utm_source=app
此外,++it,没有重载++,it = begin(),也能从返回的位置往后挪到,因为it是int*地址类型,地址++,默认移动一个存储元素大小,所以往后挪一个int。
- CRUD
-
insert()
1,> 返回值和参数:不需要返回值,参数是:size_t pos , const T& x ,内部存模板参数类型的值。
1.>先判断位置是否合法,pos <=_finish,允许等于,做尾插。>_start。
2.> 判断是否需要扩容,_findish == _endofS,_endOfS位置在N+1,然而最大能放N,原始赋值endOfS = _start + n;
3.> 挪动数据,iteartor end = _finish - 1;
挪动范围: end >= pos,意思是从后往前pos位置的也被挪走。
挪动习惯:*(end+1 )= *(end); 这样使得变量最小为0,不会越界。再–end。
观察如下使用场景:
p通过find()查找,但是insert()内部如果发生了扩容,那么从start~endOfS之间内存地址都变了,你的p已经非法了,所以实现时,reserve()之前,要先记录原始len,再更新pos = start + len。
以上,就是一个迭代器失效问题,迭代器失效还会常见于erase()。
此外,再对p做插入,还会报错,虽然内部对pos更新了,但是没有影响到p。因为内部pos是形参,所以建议:实现使用引用做参数。但是尊重源码实现,源码参数不是引用,此外就算给了引用,也没用,因为迭代器begin()返回也不是引用类型,是临时对象,但是如果给迭代器的begin()变成&,所以建议不重复对一个位置做插入。 -
pop_back()
1> 参数是const T&,直接给末尾赋值,本质做深拷贝。
因为底层是连续空间,直接–_finish;表示末尾的下一个往回收。**** -
insert()
1.> 过程:
先判断pos位置是否合法 ,在start~endfS
判断是否需要扩容,需要先保存原始长度len,再去reserve(n),此时内部的_start已经改变,需要手动计算扩容后的pos真实位置。
拷贝原始数据,不要用memcpy(),当内存类型为对象类型,则需要做深拷贝memcpy()只适合默认类型的浅拷贝时使用。
所以这里实现时:_start[i] = v[i]; -
erase()
注意类外实现都需要加上typename,防止歧义,告诉编译器:这是个类型。
1.>过程:
先查位置是否合法两个assert。
当前位置于pos+1,然后以pos-1 = pos去循环赋值。
最后给_finish–。
还可以加缩容判断,当size()为capacity()一半,就缩容。
2.>注意点:不要重复对一个位置删,因为删完做了挪动,当前迭代器位置可能已经变了,可能会跳过一些元素,最终可能报错。
3.>解决思路:删除完返回迭代器位置,且删除过程中加判断,if(符合删除条件){调用erase()} else{it++;}
此外,STL规定erase()返回删除位置的下一个。建议insert和erase之后别访问,可能有各种迭代器失效情况。
- 其它功能:
- find():其实是algorithm中的sort()
自己写的类是否可以用算法库中的sort(),可以用,只有符合迭代器的规范。
- 动态二维数组:使数组支持二维
- 如何保证代码支持二维vector?
先实现取数组头尾的函数:front()、back()。这两个函数的类型是:引用&类型,因为需要修改。如果本身存二维,那么front()结果是一维数组,return _start,就是个一维,因为堆二维解引用一次。back()同理,解引用一次,但是是对(_finish-1)。
分析:vv里面每个数据类型是:vector,把二维的vv返回给ret。首先,ret会去调用拷贝构造,ret的_start会新开辟空间,做深拷贝。然后开辟的空间内部。其中拷贝构造过程中的插值用push_back,push_back做的是深拷贝,深拷贝一个一维vector,而这个一维vector,插入一维vector过程中,也做深拷贝。总之,尤其是拷贝构造,一定要做深拷贝。且reserve过程中,拷贝也保证了不用memcpy。一个个去复制。start[i] = v][i]。或用push_back();
头文件
#pragma once
#define _CRT_SECURE_NO_WARNINGS#include<iostream>
#include<assert.h>
#include<algorithm>
using namespace std;namespace lz
{//模拟实现vectortemplate<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//默认成员函数vector(); //构造函数vector(size_t n, const T& val); //构造函数template<class InputIterator>vector(InputIterator first, InputIterator last) //构造函数:_start(nullptr), _finish(nullptr), _endofstorage(nullptr){while (first != last){push_back(*first);first++;}}vector(const vector<T>& v); //拷贝构造函数vector<T>& operator=(vector<T> v); //赋值运算符重载函数~vector(); //析构函数//迭代器相关函数iterator begin();iterator end();const_iterator begin()const;const_iterator end()const;//容量和大小相关函数size_t size()const;size_t capacity()const;void reserve(size_t n);void resize(size_t n, const T& val = T()){if (n > capacity()){reserve(n);}if (n > size()){// 初始化填值while (_finish < _start + n){*_finish = val;++_finish;}}else_finish = _start + n;}bool empty()const;//修改容器内容相关函数void push_back(const T& x);void pop_back();void insert(iterator pos, const T& x);iterator erase(iterator pos);//访问容器相关函数T& operator[](size_t i);const T& operator[](size_t i)const;// 其它功能void swap(vector<T>& v){std::swap(v._start, _start);std::swap(v._finish, _finish);std::swap(v._endofstorage, _endofstorage);}T& front();T& back();private:iterator _start; //指向容器的头iterator _finish; //指向有效数据的尾iterator _endofstorage; //指向容器的尾};//========构造函数template<class T>vector<T>::vector():_start(nullptr), _finish(nullptr), _endofstorage(nullptr){};//vector<class T>::vector() //{// // 2 12//};//template<class T>//vector<T>::vector(const vector<T>& v)// :_start(nullptr)// , _finish(nullptr)// , _endofstorage(nullptr)//{// _start = new T[v.capacity()]; //开辟一块和容器v大小相同的空间// for (size_t i = 0; i < v.size(); i++) //将容器v当中的数据一个个拷贝过来// {// _start[i] = v[i];// }// _finish = _start + v.size(); //容器有效数据的尾// _endofstorage = _start + v.capacity(); //整个容器的尾// //}// 拷贝构造现代写法//template<class T>//vector<T>::vector(const vector<T>& v)// :_start(nullptr)// , _finish(nullptr)// , _endofstorage(nullptr)//{// reserve(v.capacity()); //调用reserve函数将容器容量设置为与v相同// for (auto& e : v) //将容器v当中的数据一个个尾插过来// {// push_back(e);// }//}//// v2(v) 拷贝构造 现代 简洁写法template<class T>vector<T>::vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){vector<T> tmp(v.begin(), v.end());swap(tmp);}template<class T>vector<T>::vector(size_t n, const T& val):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(n); //调用reserve函数将容器容量设置为nfor (size_t i = 0; i < n; i++) //尾插n个值为val的数据到容器当中{push_back(val);}}template<class T>vector<T>:: ~vector(){delete[] _start; //释放容器存储数据的空间_start = nullptr; //_start置空_finish = nullptr; //_finish置空_endofstorage = nullptr;}//========容量相关// 扩容template<class T>void vector<T>::reserve(size_t n){if (n > capacity()) //判断是否需要进行操作{size_t sz = size(); //记录当前容器当中有效数据的个数T* tmp = new T[n]; //开辟一块可以容纳n个数据的空间if (_start) //判断是否为空容器{for (size_t i = 0; i < sz; i++) //将容器当中的数据一个个拷贝到tmp当中{tmp[i] = _start[i];}delete[] _start; //将容器本身存储数据的空间释放}_start = tmp; //将tmp所维护的数据交给_start进行维护_finish = _start + sz; //容器有效数据的尾_endofstorage = _start + n; //整个容器的尾}}/*template<class T>void vector<T>::resize(size_t n, const T& val = T()){}*/template<class T>size_t vector<T>::capacity()const{return _endofstorage - _start;}template<class T>size_t vector<T>::size()const{return _finish - _start;}template<class T>bool vector<T>::empty()const{return size() == 0;}//========CRUDtemplate<class T>void vector<T>::push_back(const T& x){if (_finish == _endofstorage) //判断是否需要增容{size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity(); //将容量扩大为原来的两倍reserve(newcapacity); //增容}*_finish = x; //尾插数据_finish++; //_finish指针后移}template<class T>void vector<T>::pop_back(){assert(!empty()); //容器为空则断言_finish--; //_finish指针前移}template<class T>void vector<T>::insert(iterator pos, const T& x){assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage) //判断是否需要增容{size_t len = pos - _start; //记录pos与_start之间的间隔size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity(); //将容量扩大为原来的两倍reserve(newcapacity); //增容pos = _start + len; //通过len找到pos在增容后的容器当中的位置}//将pos位置及其之后的数据统一向后挪动一位,以留出pos位置进行插入iterator end = _finish-1;while (end >= pos){*(end+1) = *(end);end--;}*pos = x; //将数据插入到pos位置_finish++; //数据个数增加一个,_finish后移}//template<class T>//typename vector<T>::iterator vector<T>::erase(typename vector<T>::iterator pos)//{// assert(pos>= _start);// assert(pos < _finish);// iterator it = pos + 1;// while (it != _finish)// {// *(it - 1) = *it;// it++;// }// _finish--; //数据个数减少一个,_finish前移// return pos;//}template<class T>typename vector<T>::iterator vector<T>::erase(typename vector<T>::iterator pos){assert(pos>= _start);assert(pos < _finish);iterator it = pos + 1;while (it != _finish){*(it - 1) = *it;it++;}_finish--; //数据个数减少一个,_finish前移return pos;}//========重载template<class T>T& vector<T>::operator[](size_t i){assert(i < size()); //检测下标的合法性return _start[i]; //返回对应数据}template<class T>const T& vector<T>::operator[](size_t i)const{assert(i < size()); //检测下标的合法性return _start[i]; //返回对应数据}template<class T>vector<T>& vector<T>::operator=(vector<T> i){swap(i);return *this;}//========迭代器template<class T>typename vector<T>::iterator vector<T>::begin(){return _start;}template<class T>typename vector<T>::iterator vector<T>::end(){return _finish;}template<class T>typename vector<T>::const_iterator vector<T>::begin()const{return _start;}template<class T>typename vector<T>::const_iterator vector<T>::end()const{return _finish;}template<class T>T& vector<T>::front(){assert(size() > 0);return *_start;}template<class T>T& vector<T>::back(){assert(size() > 0);return *(_finish - 1);}class Solution {public:vector<vector<int>> generate(int numRows) {// resize()初始化 开几行vector<vector<int>> res;res.resize(numRows);// 每一行再次初始化开空间,i行开i+1个// 默认给0 但是初始化第一个和最后一个给1for (int i = 0; i < numRows; i++){res[i].resize(i + 1, 0);res[i].front() = res[i].back() = 1;}// 行列for (int i = 0; i < numRows; i++){for (int j = 0; j < res[i].size(); j++){if (res[i][j] == 0)res[i][j] = res[i - 1][j - 1] + res[i - 1][j];cout << "i = "<< i <<" , j = " << j <<" , res = " << res[i][j] << endl;}}return res;}};
}
测试文件
#include"l2vector_t.h"void test_v1()
{lz::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;
}void test_iteartor()
{lz::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;lz::vector<int>::iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}}void test_insert()
{lz::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;auto p = find(v.begin(), v.end(), 3);if (p != v.end()){v.insert(p, 30);}for (auto e : v){cout << e << " " << endl;}}void test_erase()
{lz::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);auto p = find(v.begin(), v.end(), 3);if(p!=v.end()){v.erase(p);}for (auto e : v){cout << e << " ";}cout << endl;}void test_copy()
{lz::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);lz::vector<int> v2(v);for (auto& e : v){e = 1;cout << e << " ";}cout << endl;for (auto e : v2){cout << e << " ";}}void test_vector_iterator()
{string s("hello world");lz::vector<char> v(s.begin(), s.end());for (auto e : v){cout << e << " ";}cout << endl;
}// 赋值重载的测试
void test_operator_equals()
{lz::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);lz::vector<int> v2;v2 = v;for (auto e : v){cout << e << " ";}
}
void test_resize()
{lz::vector<int> v;v.resize(10, 1);for (auto e : v){cout << e << " ";}cout << endl;v.resize(3);for (auto e : v){cout << e << " ";}
}void test_vectorerwei()
{lz::Solution().generate(5);
}
int main()
{//test_iteartor();//test_insert();//test_erase();//test_copy();//test_vector_iterator();//test_operator_equals();//test_resize();test_vectorerwei();return 0;
}