飞机选做网站,ecto wordpress,win7可以做网站吗,网站服务器是干什么的✍作者#xff1a;阿润菜菜 #x1f4d6;专栏#xff1a;C vector的常用接口
首先贴上#xff1a;vector的文档介绍,以备查阅使用。 vector的基本框架#xff1a; vector的成员变量分别是空间首部分的_start指针和最后一个元素的下一个位置的_finish指针#xff0c;以… ✍作者阿润菜菜 专栏C vector的常用接口
首先贴上vector的文档介绍,以备查阅使用。 vector的基本框架 vector的成员变量分别是空间首部分的_start指针和最后一个元素的下一个位置的_finish指针以及指向可用空间末尾的下一个位置的_end_of_storage指针对于vector来说它的底层就是由顺序表实现的所以它的迭代器就是原生指针T*我们定义const和非const的迭代器便于const和非const对象的迭代器的调用。 vector的构造函数
vector提供四种构造函数来进行初始化 1.对于无参构造我们利用初始化列表来进行初始化用nullptr初始化
vector():_start(nullptr),_finish(nullptr),_end_of_storage(nullptr)
{}2.构造并初始化是用n个value值来初始化因为value既可能是内置类型也可能是自定义类型所以如果用引用作参数的话需要用const引用也就是常引用来作参数否则无法接收内置类型的实参。
//vectorint v(10,5)初始化vector(size_t n, const T val T()) //这里调用匿名对象的默认构造初始化{reserve(n);for (size_t i 0; i n; i){push_back(val); //进行深拷贝不要用memcpy浅拷贝会带来二次析构等问题}}注意匿名对象的生命周期只有其所在那一行const引用会延长匿名对象的生命周期到引用对象域结束因为后续调用就是匿名对象并自动销毁 在实现完n个value构造的构造函数之后如果我们此时用10个int类型的数字1来构造对象v1实际会报错报错的原因其实是由于函数的匹配优先级所导致的实参无法正确匹配相应的构造函数。而使用10个char类型的字符A却不会报错这是由于函数的匹配优先级决定的 对于这种问题STL源码的解决方式是利用了函数重载来解决了这个问题多重载了一个类型为int的构造函数 //int 不能向size_t 类型转换vector(int n, const T val T()){reserve(n);for (size_t i 0; i n; i){push_back(val);}}3.对于拷贝构造函数的实现有个一定需要注意的问题 深浅拷贝问题 — 对象嵌套深浅拷贝问题。 我们传统写法会是memcpy申请资源并按字节拷贝这种写法用在string类实现可以但在vector类实现就肯定不行了因为vector的模板实例化有int数组对象string对象、及vector的vector嵌套等等。所以对于自定义类型如果时浅拷贝会使两个对象指向同一块空间对象调用析构函数时会造成二次析构值拷贝很多时候是一种危险的行为。
我们采用现代写法也就是复用写法利用形参对象v的迭代器来构造临时对象tmp然后将对象tmp和* this进行交换这里交换函数需要我们自己提供复用库里的swap即可。同时为了防止对象tmp离开函数栈帧销毁时造成野指针的访问问题所以在调用构造函数时采用了初始化列表的方式将* this的三个成员都初始化为nullptr。 在实现拷贝构造后我们还需要实现赋值重载利用传值拷贝构造的临时对象即可然后调用swap类成员函数即可完成自定义类型的赋值工作。为了符合连续赋值含义我们利用引用来作为返回值。
vector swap(vectorT v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);return *this;
}vector(const vectorT v):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{//复用区间构造vectorT tmp(v.begin(), v.end());swap(tmp);//交换的时候就可以体现出拷贝构造函数初始化列表的好处了
}// v1 v2;
// v1 v1;
vectorT operator(vectorT v)//返回引用符合连续赋值的含义
{swap(v);return *this;
}不复用swap函数通过复用reserve函数实现要注意更新_finish和_endofstorge vector(const vectorT v): _start(nullptr), _finish(nullptr), _endOfStorage(nullptr){reserve(v.capacity());iterator it begin();const_iterator vit v.cbegin();while (vit ! v.cend()){*it *vit;}_finish _start v.size();_endOfStorage _start v.capacity();}小tips在拷贝构造函数可以不加模板参数 为了可读性我们推荐加上
4.迭代器区间构造 vector的迭代器区间初始化是个模板 可以传入任意类型并可以部分初始化但实际上vector很少用迭代器。 templateclass InputIteratorvector(InputIterator first, InputIterator last){reserve(last - first);while (first ! last){push_back(*first);first;}}vector iterator — 迭代器
接口说明begin end获取第一个数据位置的iterator/const_iterator 获取最后一个数据的下一个位置的iterator/const_iteratorrbegin rend获取最后一个数据位置的reverse_iterator获取第一个数据前一个位置的reverse_iteratorvector 空间函数 1.capacity的代码在vs和g下分别运行会发现vs下capacity是按1.5倍增长的g是按2倍增长的。 不要固化的认为vector增容都是2倍具体增长多少是根据具体的需求定义的。vs是PJ版本STLg是SGI版本STL 测试代码
// 测试vector的默认扩容机制
void TestVectorExpand()
{size_t sz;vectorint v;sz v.capacity();cout making v grow:\n;for (int i 0; i 100; i) {v.push_back(i);if (sz ! v.capacity()) {sz v.capacity();cout capacity changed: sz \n;}}
}2.reserve只负责开辟空间如果确定知道需要用多少空间reserve可以缓解vector增容的代价缺陷问题,提高效率。 resize在开空间的同时还会进行初始化影响size。
reserve函数 1.对于reserve以及resize函数来说官方并没有将其设定为能够实际缩容的功能明确规定这个函数在其他情况下例如预留空间要比当前小的情况下这个函数的调用是不会引起空间的重新分配的也就是说容器vector的实际开辟的capacity是不会被影响的。值得注意的是缩容表面看起来是降低了空间的使用率想要提高程序的效率但实际上并未提高效率缩容是需要异地缩容的需要重新开空间和拷贝数据代价不小所以不建议对空间进行缩容 2.shrink_to_fit就是缩容函数强制性的将capacity的大小降低到适配size大小的值它的设计理念就是以空间来换时间但日常人们所使用的手机或者PC空间实际上是足够的不够的是时间所以这种函数还是不要使用的为好除非说你后面肯定不会插入数据了不再进行任何modify操作那你可以试着将空间还给操作系统减少空间的使用率
//开辟空间void reserve(size_t n){if (n capacity()) //先检查避免缩容//如果大于原有空间则重新开辟并挪动数据否则就不做任何行为{size_t sz size();T* tmp new T[n]; //开辟失败会抛异常if (_start){memcpy(tmp, _start, , sizeof(T) * size());//拷贝旧数据delete[] _start;}//更新_start tmp;_finish _start sz;_end_of_storage _start n;}}resize函数
1.注意C是泛型编程的所以resize提供了参数 T val T () 用匿名对象默认构造来初始化 //调整空间和初始化void resize(size_t n; T val T()) //泛型编程 --- T val T() 即用匿名对象的默认构造来初始化{if (n size()) //若小于原空间数据量{//删除数据,但并不实际上缩小空间_finish _start n;}else{if (n capacity()){reserve(n);}while (_finish ! _start n){*_finish val;_finish;}}}vector 增删查改 1.这里push_back和pop_back都是对insert和erase的复用所以我们主要介绍三个函数的模拟实现。find函数我们无需提供直接调用算法模块库里的对于swap我们需要直接实现因为我们是一个自定义类型浅拷贝无法实现swap。同时在实现insert和erase函数是我们会遇到第一个经典的迭代器失效问题就是因为扩容操作造成了迭代器野指针问题。
insert函数
1.在insert函数模拟实现中因为插入数据很多时候我们要对空间扩容而且基本上是异地扩容在扩容后迭代器pos指针就会指向已释放的空间造成了野指针访问的问题解决就是更新pos迭代器指针 我们提供返回值 返回pos使用时需要pos 接受返回值更新pos迭代器指针 2.而且vector的插入操作如果导致底层空间重新开辟则迭代器就会失效。如果空间足够那么迭代也算失效了因为数据相对位置已经发生改变他已经不是指向之前的位置了。 3.那我们为什么不能用引用传参 注意所有的传值返回都是拷贝临时对象具有常性不能改变不能传给左值引用
iterator insert(iterator pos, const T val){assert(pos _start);assert(pos _finish);if (_finish _end_of_storage) //检查扩容{size_t len pos - _start; //记录len 后续pos更新reserve(capacity() 0 ? 4 : capacity() * 2);//扩容后更新pos解决迭代器pos失效问题pos _start len;}iterator end _finish - 1;while (end pos) //遍历挪动数据{*(end 1) *end;--end;}*pos val;_finish;return pos;}erase函数 1.erase删除pos位置元素后pos位置之后的元素会往前搬移没有导致底层空间的改变理论上讲迭代器不应该会失效但是如果pos刚好是最后一个元素删完之后pos刚好是end的位置而end位置是没有元素的那么pos就失效了。因此删除vector中任意位置上元素时vs就认为该位置迭代器失效了。 2. 在实现函数erase时erase 之后认为pos是否失效 我们认为失效不要访问因为行为结果不能确定的根据具体的编译器实现有关系 ---- 本质是不让pos跳过_finish(本该访问数据) 发生段错误或者结果不对 3. 在使用时vector 的erase 要配合find实现 vs编译器会进行强制检查erase以后的迭代器都不能继续访问
// 返回删除数据的下一个数据// 方便解决:一边遍历一边删除的迭代器失效问题iterator erase(Iterator pos){// 挪动数据进行删除iterator begin pos 1;while (begin ! _finish){*(begin - 1) *begin;begin;}--_finish;return pos;}operator[ ] 1.其实vector一般不常用迭代器。常用的stl只有 string和vector 会使用方括号其他的都会使用迭代器 2.我们要注意越界访问的检查机制直接断言assert(possize) //[]T operator [] (size_t pos){assert(pos size());return _start[pos];}const T operator [] (size_t pos) const{assert(pos size());return _start[pos];}附上vector模拟实现源码
vector深度剖析
使用memcpy拷贝问题
memcpy是内存的二进制格式按字节拷贝将一段内存空间中内容原封不动的拷贝到另外一段内存空间中。这是一种妥妥浅拷贝如果拷贝的是自定义类型的元素memcpy既高效又不会出错但如果拷贝的是自定义类型元素并且 自定义类型元素中涉及到资源管理时就会出错因为memcpy的拷贝实际是浅拷贝会造成对象和拷贝对象指向同一块空间在调用析构时会二次析构。 结论如果对象中涉及到资源管理时千万不能使用memcpy进行对象之间的拷贝因为memcpy是 浅拷贝否则可能会引起内存泄漏甚至程序崩溃。
迭代器失效问题
1.vector的迭代器是一个原生指针的typedef所以迭代器失效的本质就是指针失效换句话说就是野指针访问对指针指向的无效空间进行访问所导致的问题。 2.在使用insert时我们需要传某个位置的迭代器如果在insert中不发生扩容则这个迭代器在insert之后还是有效的但只要在insert中发生扩容则迭代器就会失效因为reserve进行的是异地扩容所以原有的迭代器就不能使用因为原有迭代器指向的是已经释放的旧空间的某个位置所以如果继续使用则势必会发生野指针的访问这正是我们所说的迭代器失效。 3.比如vector的迭代器就是原生态指针T* 。因此迭代器失效实际就是迭代器底层对应指针所指向的 空间被销毁了而使用一块已经被释放的空间造成的后果是程序崩溃(即如果继续使用已经失效的迭代器 程序可能会崩溃) 4.对于vector可能会导致其迭代器失效的操作有会引起其底层空间改变的操作都有可能是迭代器失效比如resize、reserve、insert、assign、push_back等。
迭代器失效解决办法在使用前对迭代器重新赋值即可
vector二维数组
构造一个vv动态二维数组vv中总共有n个元素每个元素都是vector类型的每行没有包含任何元素如果n为5时如下所示: vv中元素填充完成之后如下图所示 使用标准库中vector构建动态二维数组时与上图实际是一致的.