做网站需要交维护费么,地方门户网站推广方法有那些,昆明app开发哪家好,百度推广优化技巧文章目录 vector各函数接口总览vector当中的成员变量介绍默认成员函数构造函数1构造函数2构造函数3拷贝构造函数赋值运算符重载函数析构函数 迭代器相关函数begin和end 容量和大小相关函数size和capacityreserveresizeempty 修改容器内容相关函数push_backpop_backinserterases… 文章目录 vector各函数接口总览vector当中的成员变量介绍默认成员函数构造函数1构造函数2构造函数3拷贝构造函数赋值运算符重载函数析构函数 迭代器相关函数begin和end 容量和大小相关函数size和capacityreserveresizeempty 修改容器内容相关函数push_backpop_backinserteraseswap 访问容器相关函数operator[ ] vector各函数接口总览
namespace zpl
{templateclass Tclass vector{public:// Vector的迭代器是一个原生指针typedef T* iteratortypedef const T* const_iteratoriterator begin()iterator end()const_iterator cbegin()constconst_iterator cend() constvector()vector(int n, const T value T())templateclass InputIteratorvector(InputIterator first, InputIterator last)vector(const vectorT v)vectorT operator (vectorT v)~vector()// capacitysize_t size() const size_t capacity() constvoid reserve(size_t n)void resize(size_t n, const T value T())///access///T operator[](size_t pos)const T operator[](size_t pos)const///modify/void push_back(const T x)void pop_back()void swap(vectorT v)iterator insert(iterator pos, const T x)iterator erase(Iterator pos)private:iterator _start; // 指向数据块的开始iterator _finish; // 指向有效数据的尾iterator _endOfStorage; // 指向存储容量的尾};
}vector当中的成员变量介绍
在vector当中有三个成员变量_start、_finish _endofstorage。 _start指向容器的头_finish指向容器当中有效数据的尾_endofstorage指向整个容器的尾。
默认成员函数
构造函数1
编译器会自动支持一个无参的默认构造函数当我们重载了其它的构造函数编译器就不会提供了现在我们想要使用无参的构造函数就必须自己手动添加了。
//无参的构造函数
vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr)
{}构造函数2
ector还支持使用一段迭代器区间进行对象的构造。因为该迭代器区间可以是其他容器的迭代器区间也就是说该函数接收到的迭代器的类型是不确定的所以我们这里需要将该构造函数设计为一个函数模板。
//利用一段迭代区间构造
templateclass InputIterator
vector(InputIterator first, InputIterator last): _start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{reserve(last - first); //避免构造空容器的构造和频繁调用扩容//干脆直接扩容然后不停尾插就行了while (first ! last){push_back(*first);first;}构造函数3
vector容器还支持一种构造就是n个val值的填充 直接复用resize后面会实现
vector(size_t n, const T val T())
{resize(n, val);
}注意该构造函数还需要实现两个重载函数。
因为当使用这样构造时会运行崩溃。
vectorintv(5,3)因为当使用这样构造时编译器并不会调用构造函数3而会调用构造函数2而构造函数2中对int内置类型进行解引用会程序崩溃为了让编译器调用构造函数三必须重载如下两个版本
vector(int n, const T val T())
{resize(n, val);
}
vector(long n, const T val T())
{resize(n, val);
}拷贝构造函数
vector的构造函数涉及深拷贝问题这里提供两种深拷贝的写法 1.传统写法 拷贝构造的传统写法的思想是我们最容易想到的先开辟一块与该容器大小相同的空间然后将该容器当中的数据一个个拷贝过来即可最后更新_finish和_endofstorage的值即可。
vector(const vectorT v)
{_start new T[v.capacity()];//memcpy(_start, v._start, v.size() * sizeof(T));for (size_t i 0; i v.size(); i){_start[i] v[i];}_finish _startsize();_endofstorage _start v.capacity();
}**注意**将容器当中的数据一个个拷贝过来时不能使用memcpy函数当vector存储的数据是内置类型或无需进行深拷贝的自定义类型时使用memcpy函数是没什么问题的但当vector存储的数据是需要进行深拷贝的自定义类型时使用memcpy函数的弊端就体现出来了。例如当vector存储的数据是string类的时候。 每个sting对象都有自己指向的那一块空间。 如果此时我们使用的是memcpy函数进行拷贝构造的话那么拷贝构造出来的vector当中存储的每个string的成员变量的值将与被拷贝的vector当中存储的每个string的成员变量的值相同即两个vector当中的每个对应的string成员都指向同一个字符串空间。 这样析构时就会析构两次程序崩溃。
解决办法 看似是赋值操作其实两个string类型对象赋值时string会去调用它自己的赋值运算符重载完成深拷贝结果如下 总结 memcpy使用浅拷贝对于内置类型和不需要深拷贝的自定义类型来说是可以的但遇到像string这种自定义类型必须要深拷贝那么我们就不能用memcpy函数了要调用自定义类型自己的深拷贝。
写法二:现代写法 先调用reserve扩容到与v容量相同再利用范围for将每个元素push_back尾插过来即可 //现代写法vector(const vectorT v){reserve(v.capacity());for (cosnt auto e : v){push_back(e);}}注意 在使用范围for对容器v进行遍历的过程中变量e就是每一个数据的拷贝然后将e尾插到构造出来的容器当中。就算容器v当中存储的数据是string类在e拷贝时也会自动调用string的拷贝构造深拷贝所以也能够避免出现与使用memcpy时类似的问题。
赋值运算符重载函数
vector的赋值运算符重载当然也涉及深拷贝问题我们这里也提供两种深拷贝的写法 1.传统写法 首先判断是否是给自己赋值若是给自己赋值则无需进行操作。若不是给自己赋值则先开辟一块和容器v大小相同的空间然后将容器v当中的数据一个个拷贝过来最后更新_finish和_endofstorage的值即可。
vectorT operator(const vectorT v)
{if (this ! v){delete[] _start;_start new T[v.capacity()];for (size_t i 0; i v.size(); i){_start[i] v[i];}_finish _start v.size();_endofstorage _start v.capacity();}return *this; //支持连续赋值
}8注意 这里和拷贝构造函数的传统写法类似也不能使用memcpy函数进行拷贝。
2.现代写法: 赋值运算符重载的现代写法非常精辟首先在右值传参时并没有使用引用传参因为这样可以间接调用vector的拷贝构造函数然后将这个拷贝构造出来的容器v与左值进行交换此时就相当于完成了赋值操作而容器v会在该函数调用结束时自动析构。
//现代写法
vectorT operator(vectorT v)
{swap(v);return *this;
}注意 赋值运算符重载的现代写法也是进行的深拷贝只不过是调用的vector的拷贝构造函数进行的深拷贝在赋值运算符重载函数当中仅仅是将深拷贝出来的对象与左值进行了交换而已。
析构函数
对容器进行析构时首先判断该容器是否为空容器若为空容器则无需进行析构操作若不为空则先释放容器存储数据的空间然后将容器的各个成员变量设置为空指针即可。
~vector()
{if (_start){delete[] _start;_start _finish _endofstorage nullptr;}
}迭代器相关函数
vector当中的迭代器实际上就是容器当中所存储数据类型的指针。 typedef T* iterator; typedef const T* const_iterator; begin和end
vector当中的begin函数返回容器的首地址end函数返回容器当中最后一个有效数据的后面一个地址。
iterator begin()
{return _start;
}
iterator end()
{return _finish;
}我们还需要重载一对适用于const对象的begin和end函数使得const对象调用begin和end函数时所得到的迭代器只能对数据进行读操作而不能进行修改
const_iterator begin() const
{return _start;
}
const_iterator end() const
{return _finish;
}因此vector的迭代器遍历就出来了
vectorint v(10, 2);
vectorint::iterator it v.begin();
while (it ! v.end())
{cout *it ;it;
}
cout endl;支持迭代器就支持范围for
vectorint v(5, 3);
for (auto e : v)
{cout e ;
}
cout endl;容量和大小相关函数
size和capacity size_t size() const
{return _finish - _start; //有效数据个数
}
size_t capacity() const
{return _endofstorage - _start; //总容量大小
}reserve
reserve规则 1、当n大于对象当前的capacity时将capacity扩大到n或大于n。 2、当n小于对象当前的capacity时什么也不做。 实现reserve还是比较轻松的先判断要扩大到的容量n是否大于当前容量大于就需要扩容判断原容器是否为空容器为空直接指向新开辟的tmp指向的那块空间不为空就需要提前计算好原容器有多少个有效数据然后拷贝至新容器再释放旧空间指向新空间就可以了最后更新一下成员变量。
//一般不缩容只扩容
void reserve(size_t n)
{if (n capacity()){size_t sz size();T* tmp new T[n];if (_start){for (size_t i 0; i sz; i){tmp[i] _start[i];}delete[] _start;}_start tmp;_finish _start sz;_endofstorage _start n;}
}实现reserve函数需要注意两个细节: 细节一需要提前记录好有效数据的个数便于更新_finish 因为_start指向新空间后_start已经不指向原来那块空间的首地址了现在还是利用size()函数计算有效元素个数那就错了所以_finish_startsize()就更新错误结果了。 细节二:拷贝容器当中的数据时不能使用memcpy函数进行拷贝。 由于memcpy函数拷贝是浅拷贝那么当vector数据类型为string这种自定义类型时拷贝的新容器的元素对象指向的那块空间和拷贝对象指向的空间是同一块空间那么析构的时候就会析构两次导致程序崩溃。
所以说我们还是得用for循环将容器当中的string一个个赋值过来因为这样能够间接调用string的赋值运算符重载实现string的深拷贝。 这样析构就会各自释放自己对应的那块空间互不干扰。
resize
resize规则 1、当n大于当前的size时将size扩大到n扩大的数据为val若val未给出则默认为容器所存储类型的默认构造函数所构造出来的值。 2、当n小于当前的size时将size缩小到n。 注意如果容量不够得先扩容
void resize(size_t n, const T val T())
{if (n size()){_finish _start n;}else{if (n capacity()){reserve(n);}while (_finish _start n){*_finish val;_finish;}}
}注意 在C当中内置类型也可以看作是一个类它们也有自己的默认构造函数所以在给resize函数的参数val设置缺省值时设置为T( )即可。
empty
如果_finish与_start指向相同说明没有有效数据。 bool empty() const{return _start _finish;}修改容器内容相关函数
push_back
要尾插数据首先得判断容器是否已满若已满则需要先进行增容然后将数据尾插到_finish指向的位置再将_finish即可。
void push_back(const T val)
{if (_finish _endofstorage){size_t newcapacity capacity() 0 ? 4 : 2 * capacity();reserve(newcapacity);}*_finish val;_finish;
}pop_back
尾删数据之前也得先判断容器是否为空若为空则做断言处理若不为空则将_finish–即可。
void pop_back()
{assert(!empty());_finish--;
}insert
insert函数可以在所给迭代器pos位置插入数据在插入数据前先判断是否需要增容然后将pos位置及其之后的数据统一向后挪动一位以留出pos位置进行插入最后将数据插入到pos位置即可。
iterator insert(iterator pos, const T val)
{assert(pos _start pos _finish);if (_finish _endofstorage){size_t len pos - _start;size_t newcapacity capacity() 0 ? 4 : 2 * capacity();reserve(newcapacity);pos _start len;}iterator end _finish - 1;while (end pos){*(end 1) *end;--end;}*pos val;_finish;return pos;
}注意 若需要增容则需要在增容前记录pos与_start之间的间隔然后通过该间隔确定在增容后的容器当中pos的指向否则pos还指向原来被释放的空间。
erase
erase函数可以删除所给迭代器pos位置的数据判断pos位置是否合法删除数据时直接将pos位置之后的数据统一向前挪动一位将pos位置的数据覆盖即可。
iterator erase(iterator pos)
{assert(pos _start);assert(pos _finish);iterator it pos 1;while (it _finish){*(it - 1) *it;it;}--_finish;return pos;
}swap
swap函数用于交换两个容器的数据我们可以直接调用库当中的swap函数将两个容器当中的各个成员变量进行交换即可。
void swap(vectorT v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);
}注意 在此处调用库当中的swap需要在swap之前加上“::”作用域限定符告诉编译器这里优先在全局范围寻找swap函数否则编译器会认为你调用的就是你正在实现的swap函数就近原则。
访问容器相关函数
vector也支持我们使用“下标[ ]”的方式对容器当中的数据进行访问实现时直接返回对应位置的数据即可。
operator[ ]
注意 重载运算符[ ]时需要重载一个适用于const容器的因为const容器通过“下标[ ]”获取到的数据只允许进行读操作不能对数据进行修改。
T operator[](size_t pos)
{assert(pos size());return _start[pos];
}
const T operator[](size_t pos) const
{assert(pos size());return _start[pos];
}