【vector的模拟实现】
创始人
2024-05-23 14:44:30
0

目录

1 类的成员变量

2 常用成员函数和迭代器

3 增删查改

3.1 reverse

3.2 push_back

3.3 resize

3.4 insert && erase

4 默认成员函数

4.1 构造函数

4.2 拷贝构造

4.3 赋值运算符重载

4.4 析构函数


前面我们详细介绍了string类的使用,vector的使用与string相差不大,大家可以自己到官网上查询:vector的使用

(注意下面的实现是参考stl SGI版本3.0)

1 类的成员变量

namespace grm
{templateclass vector{public:typedef T* iterator;typedef const T* const_iterator;private:iterator _start;iterator _finish;iterator _endofstorage;};
}

 这里面与之前我们实现的顺序表有所不同的是里面的成员变量全是指针,_start是开始数据的地址,_finish是有效数据下一位的地址,_endofstorage是数据最大容量的下一位地址。


 2 常用成员函数和迭代器

        size_t size()const{return _finish - _start;}size_t capacity()const{return _endofstorage - _start;}iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin()const{return _start;}const_iterator end()const{return _finish;}const T& operator[](size_t i){return _finish[i];}T& operator[](size_t i)const{return _finish[i];}

这些都很简单,就不在多说了。


3 增删查改

3.1 reverse

大家看看下面这种写法有没有问题?

        void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * sz);delete[] _start;}_start = tmp;_finish = _start + size();_endofstorage = _start + n;}}

大家回顾一下size()我们是用_finish - _start实现的,这样的话使用size()的时候_finish还没有更新,而_start已经被更新了,那不就g了吗,正确的处理方式有两种,一种是先更新_start,另外一种是先保存size()。

方法1:

        void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * sz);delete[] _start;}//写法1:_finish = tmp + size();_start = tmp;_endofstorage = _start + n;}}

 写法2:

        void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];size_t sz = size();//先保存size()if (_start){memcpy(tmp, _start, sizeof(T) * sz);delete[] _start;}//写法2:_start = tmp;_finish = tmp + sz;_endofstorage = _start + n;}}

 这样无论先更新还是后更新都可。

大家仔细看看上面这种写法还有没有问题?

顺便问一句,这里能够用memcpy吗?

如果T是内置类型的话那还好没啥问题,那如果T是一个自定义类型就会出大问题,因为memcpy进行的是浅拷贝,那析构的话就又会将同一块空间析构两次而出错。

正确的做法是一个一个赋值拷贝:

        void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];size_t sz = size();if (_start){//memcpy(tmp, _start, sizeof(T) * sz);//浅拷贝,如果是自定义类型就会发生重复析构for (int i = 0; i < sz; i++){tmp[i] = _start[i];}delete[] _start;}//错误写法/*_start = tmp;_finish = _start + size();//使用size()时_finish还没有更新_endofstorage = _start + n;*///写法1:_finish = tmp + size();_start = tmp;_endofstorage = _start + n;//写法2:/*_start = tmp;_finish = tmp + sz;_endofstorage = _start + n;*/}}
结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

3.2 push_back

        void push_back(const T& val){if (_finish >= _endofstorage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = val;_finish++;}

3.3 resize

        void resize(size_t n, const T& x = T())//用T类型创造出来的匿名对象{if (n > capacity()){reserve(n);}while (_finish != _endofstorage){*_finish = x;_finish++;}}

3.4 insert && erase

        //iterator insert(iterator& pos, const T& val)iterator insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);if (_finish >= _endofstorage){//扩容了要更新possize_t gap = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + gap;}iterator end = _finish ;while (pos < end){*end = *(end - 1);--end;}*pos = val;++_finish;return pos;}iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator begin = pos+1;while (begin < _finish){*(begin-1) = *begin;++begin;}--_finish;return pos;}

大家想想为啥扩容了要更新pos?假如不更新pos,当扩容了后pos的值肯定会发生改变,那么我们就不能够使用原先的pos来访问数据了,否则就非法越界了。这也是迭代器失效的问题。大家想想string会迭代器失效吗?答案也是会的,不过由于string我们常用的是下标访问所以一般来说不会失效。

插入可以用返回值接受,为啥不用引用呢?

有些场景下可能不适用:

        //有些场景下不适用,像下面这里:v.insert(v.begin(), -1);//这里v.begin()是用一个具有常属性的临时变量保存的,不能够修改,与形参类型矛盾了。for (auto e : v){cout << e << " ";}

 大家再思考一下下面这段程序:

    void test5(){vector v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(2);v.push_back(5);//删除所有为2的数字vector::iterator it = find(v.begin(), v.end(), 2);while (it != v.end()){//vector::iterator ret = find(it, v.end(), 2);auto ret = find(it, v.end(), 2);if (ret != v.end()){it =ret;v.erase(it);}++it;}for (auto& e : v)    cout << e << " ";}

这种使用方法肯定是错误的,因为it在不断的往下面走,正确应该修改为:

            if (ret != v.end()){it =ret;v.erase(it);}else{++it;}


4 默认成员函数

4.1 构造函数

           vector():_start(nullptr), _finish(nullptr), _endofstorage(nullptr){}

4.2 拷贝构造

传统写法:

        vector(const vector& v){_start = new T[v.capacity()];memcpy(_start, v._start, sizeof(T) * v.size());_finish = _start + v.size();_endofstorage = _start + v.capacity();}

这里也是一样,正确的做法是一个一个赋值拷贝:

//传统写法vector(const vector& v){_start = new T[v.capacity()];//memcpy(_start, v._start, sizeof(T) * v.size());//这里也是浅拷贝for (int i = 0; i < v.size(); i++)_start[i] = v._start[i];_finish = _start + v.size();_endofstorage = _start + v.capacity();}

我们知道现代写法就是不用我们自己造轮子,通过构造函数来帮助我们实现,但是我们又发现是无法用我们刚才实现的构造函数来完成tmp对象的创建的,所以我们又得重新再写一个构造函数:

        template vector(InputIterator first, InputIterator last): _start(nullptr), _finish(nullptr), _endofstorage(nullptr){while (first != last){push_back(*first);first++;}}

这里的InputIterator取名C++是有默认规定的,迭代器分类:

input_iterator(只写)  output_iterator(只读)没有实际对应的类型
forward_iterator(单向)
bidirectional_iterator
randomaccess_iterator

有了这个构造函数后就能够用现代写法了:

        //现代写法//void swap(vector& v)//可以这么写,但不推荐,可读性不高void swap(vector& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}vector(const vector& v): _start(nullptr), _finish(nullptr), _endofstorage(nullptr){vector tmp(v.begin(),v.end());swap(tmp);}

4.3 赋值运算符重载

传统写法:

        //传统写法vector& operator=(const vector& v){if (this != &v){T* tmp = new T[v.capacity()];//memcpy(tmp, v._start, sizeof(T) * v.size());浅拷贝for (int i = 0; i < v.size(); i++)tmp[i] = v._start[i];delete[] _start;_start = tmp;_finish = _start + v.size();_endofstorage = _start + v.capacity();}return *this;}

现代写法:

        //现代写法1:vector& operator=(const vector& v){vector tmp(v);swap(tmp);return *this;}//现代写法2:vector& operator=(vector v){swap(v);return *this;}

这里与之前string的类似。

4.4 析构函数

        ~vector(){delete[] _start;_start = _finish = _endofstorage = nullptr;}

有需要的老铁可以在博主的码云中获取源码:https://gitee.com/monday-sky/text_cpp/commit/6521a7399bdf5ebccf028e5fa130cccbd57d4dcchttps://gitee.com/monday-sky/text_cpp/commit/6521a7399bdf5ebccf028e5fa130cccbd57d4dcc

相关内容

热门资讯

80岁生日宴会致辞 80岁生日宴会致辞(精选11篇)  在学习、工作或生活中,大家都不可避免地会接触到致辞吧,致辞具有有...
婚宴长辈证婚人致辞 婚宴长辈证婚人致辞  在平日的学习、工作和生活里,大家总少不了要接触或使用致辞吧,致辞受场合、事件的...
元旦舞会主持词 元旦舞会主持词(精选7篇)  主持词要尽量增加文化内涵、寓教于乐,不断提高观众的文化知识和素养。在人...
圣诞联欢会主持词 圣诞联欢会主持词  活动对象的不同,主持词的写作风格也会大不一样。时代不断在进步,主持成为很多活动不...
美好童年—庆“六一”大型活动... 美好童年—庆“六一”大型活动主持词  利用在中国拥有几千年文化的诗词能够有效提高主持词的感染力。随着...
毕业晚会主持词串词 毕业晚会主持词串词  毕业,是人生的一个转折点,愿你们能展开双翼,飞得更高、看得更远。下面是小编给大...
运动会致辞 运动会致辞(精选5篇)  无论在学习、工作或是生活中,大家或多或少都用到过致辞吧,致辞要求风格的雅、...
六一儿童节的主持稿 六一儿童节的主持稿(精选8篇)  随着社会一步步向前发展,我们都不可避免地要接触到主持稿,主持稿是主...
元旦文艺汇演主持稿 元旦文艺汇演主持稿范文(通用5篇)  在当下社会,很多情况下我们需要用到主持稿,主持稿起到承上启下的...
颁奖主持词 颁奖主持词三篇  主持人在一场活动中是十分重要的,一个好的主持人是一直带动着活动过程中的气氛,让大家...
婚宴答谢宴简短主持词 婚宴答谢宴简短主持词  主持词要根据活动对象的不同去设置不同的主持词。在人们积极参与各种活动的今天,...
汽车公司庆典主持词 汽车公司庆典主持词  利用在中国拥有几千年文化的诗词能够有效提高主持词的感染力。现今社会在不断向前发...
古筝音乐会主持词 古筝音乐会主持词6篇  主持词要把握好吸引观众、导入主题、创设情境等环节以吸引观众。在一步步向前发展...
小学元旦联欢会主持词开场白和... 小学元旦联欢会主持词开场白和结束词  根据活动对象的不同,需要设置不同的主持词。随着社会一步步向前发...
知识竞赛主持词 知识竞赛主持词(精选6篇)  主持词的写作需要将主题贯穿于所有节目之中。在人们越来越多的参与各种活动...
小学家长会学生欢迎词 小学家长会学生欢迎词小学家长会学生欢迎词文章标题:小学家长会学生欢迎词家长会欢迎辞亲爱的叔叔阿姨,爷...
消夏晚会主持词 2017消夏晚会主持词  漫漫暑假,天气越来越燥热,不妨在炎热的午后,参加一场纳凉晚会,欣赏社区带来...
周立波脱口秀台词 周立波脱口秀台词集锦四十岁之前喝酒是为了别人的一句~~厉害!醉了!!四十岁以后喝酒是为了自己的一句~...
圣诞节活动主持词节目串词 圣诞节活动主持词节目串词3篇  根据活动对象的不同,需要设置不同的主持词。在人们积极参与各种活动的今...
生日华诞主持词 生日华诞主持词范文各位领导,各位朋友,各位来宾,女士们,先生们:  中午好。  今天是个喜庆的日子,...