【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

相关内容

热门资讯

常用商务英语口语   商务英语是以适应职场生活的语言要求为目的,内容涉及到商务活动的方方面面。下面是小编收集的常用商务...
六年级上册英语第一单元练习题   一、根据要求写单词。  1.dry(反义词)__________________  2.writ...
复活节英文怎么说 复活节英文怎么说?复活节的英语翻译是什么?复活节:Easter;"Easter,anniversar...
2008年北京奥运会主题曲 2008年北京奥运会(第29届夏季奥林匹克运动会),2008年8月8日到2008年8月24日在中华人...
英语道歉信 英语道歉信15篇  在日常生活中,道歉信的使用频率越来越高,通过道歉信,我们可以更好地解释事情发生的...
六年级英语专题训练(连词成句... 六年级英语专题训练(连词成句30题)  1. have,playhouse,many,I,toy,i...
上班迟到情况说明英语   每个人都或多或少的迟到过那么几次,因为各种原因,可能生病,可能因为交通堵车,可能是因为天气冷,有...
小学英语教学论文 小学英语教学论文范文  引导语:英语教育一直都是每个家长所器重的,那么有关小学英语教学论文要怎么写呢...
英语口语学习必看的方法技巧 英语口语学习必看的方法技巧如何才能说流利的英语? 说外语时,我们主要应做到四件事:理解、回答、提问、...
四级英语作文选:Birth ... 四级英语作文范文选:Birth controlSince the Chinese Governmen...
金融专业英语面试自我介绍 金融专业英语面试自我介绍3篇  金融专业的学生面试时,面试官要求用英语做自我介绍该怎么说。下面是小编...
我的李老师走了四年级英语日记... 我的李老师走了四年级英语日记带翻译  我上了五个学期的小学却换了六任老师,李老师是带我们班最长的语文...
小学三年级英语日记带翻译捡玉... 小学三年级英语日记带翻译捡玉米  今天,我和妈妈去外婆家,外婆家有刚剥的`玉米棒上带有玉米籽,好大的...
七年级英语优秀教学设计 七年级英语优秀教学设计  作为一位兢兢业业的人民教师,常常要写一份优秀的教学设计,教学设计是把教学原...
我的英语老师作文 我的英语老师作文(通用21篇)  在日常生活或是工作学习中,大家都有写作文的经历,对作文很是熟悉吧,...
英语老师教学经验总结 英语老师教学经验总结(通用19篇)  总结是指社会团体、企业单位和个人对某一阶段的学习、工作或其完成...
初一英语暑假作业答案 初一英语暑假作业答案  英语练习一(基础训练)第一题1.D2.H3.E4.F5.I6.A7.J8.C...
大学生的英语演讲稿 大学生的英语演讲稿范文(精选10篇)  使用正确的写作思路书写演讲稿会更加事半功倍。在现实社会中,越...
VOA美国之音英语学习网址 VOA美国之音英语学习推荐网址 美国之音网站已经成为语言学习最重要的资源站点,在互联网上还有若干网站...
商务英语期末试卷 Part I Term Translation (20%)Section A: Translate ...