当前位置:   article > 正文

C++——vector类及其模拟实现_c++怎么在类中定义vector

c++怎么在类中定义vector

前言:前边我们进行的string类的方法及其模拟实现的讲解。这篇文章将继续进行C++的另一个常用类——vector。


一.什么是vector

vector和string一样,隶属于C++中STL标准模板库中的一个自定义数据类型,实际上就是线性表两者之间有着很多相似,甚至相同的方法

但是它们也有着很大的不同:因为vector是线性表,所以他可以存储任意类型的数据,甚至是自定义类型的数据,包括vector本身


二.vector基本框架

  1. #include<assert.h>
  2. namespace Myvector
  3. {
  4. template<class T>
  5. class vector
  6. {
  7. public:
  8. typedef T* iterator;
  9. typedef const T* const_iterator;
  10. //构造函数
  11. voctor()
  12. {}
  13. //析构函数
  14. ~voctor()
  15. {
  16. delete[] _start;
  17. _strat = _finish = _endofstorage = nullptr;
  18. }
  19. //迭代器
  20. iterator begin()
  21. {
  22. return _start;
  23. }
  24. iterator end()
  25. {
  26. return _finish;
  27. }
  28. const_iterator begin() const
  29. {
  30. return _start;
  31. }
  32. const_iterator end() const
  33. {
  34. return _finish;
  35. }
  36. //数据个数
  37. size_t size() const
  38. {
  39. return _finish - _start;
  40. }
  41. //空间大小
  42. size_t capacity() const
  43. {
  44. return _endofstorage - _start;
  45. }
  46. //[]运算符重载
  47. T& operator[](size_t pos)
  48. {
  49. assert(pos < size());
  50. return _start[pos];
  51. }
  52. const T& operator[](size_t pos) const
  53. {
  54. assert(pos < size());
  55. return _start[pos];
  56. }
  57. private:
  58. iterator _start = nullptr;
  59. iterator _finish = nullptr;
  60. iterator _endofstorage = nullptr;
  61. };
  62. }

因为vector允许定义不同类型的数据,所以我们要使用模版来定义类

值得注意的是,在voctor中我们不用传统的size和capacity来定义数据个数和总空间大小,而是都将它们定义为指针类型,通过指针间的相减来得到size和capacity。初始情况下三者均为空指针

同时我们将T*指针类型重定义为iterator,这是因为vector的本质就是数组,其迭代器就是指针


三.vector常用操作

1.扩容

扩容分为两种方式,只扩容不初始化的reserve,以及扩容并且初始化的resize调整

  1. //扩容
  2. void reserve(size_t n)
  3. {
  4. if (n > capacity())
  5. {
  6. size_t old_size = size();
  7. T* tmp = new T[n];
  8. //memcpy(tmp, _start, sizeof(T) * size());
  9. for (size_t i = 0; i < old_size; i++)
  10. {
  11. tmp[i] = _start[i];
  12. }
  13. delete[] _start;
  14. _start = tmp;
  15. _finish = old_size + tmp;
  16. _endofstorage = tmp + n;
  17. }
  18. }

扩容较为简单,值得注意的是这里不在使用strcpy,而是通过for循环的方式进行赋值。

  1. //调整
  2. void resize(size_t n,const T& val = T())
  3. {
  4. if (n > size())
  5. {
  6. reserve(n);
  7. while (_finish < _start + n)
  8. {
  9. *_finish = val;
  10. _finish++;
  11. }
  12. }
  13. else
  14. {
  15. _finish = _start + n;
  16. }
  17. }

对于resize,因为我们需要使用缺省参数,但是由于vector的对象类型不定,甚至是自定义类型,所以我们不能使用0作为缺省参数而应使用匿名对象作为参数,让匿名对象去自动识别类型并调用自己的构造函数形成初始值


2.插入

先来看尾插,与string类似,需要考虑扩容:

  1. //尾插
  2. void push_back(const T& val)
  3. {
  4. if (_finish == _endofstorage)
  5. {
  6. reserve(capacity() == 0 ? 4 : capacity() * 2);
  7. }
  8. *_finish = val;
  9. _finish++;
  10. }

测试如下:

 在来看指定位置的插入先判断pos是否合法,同样需要判断扩容

  1. //指定位置插入
  2. void insert(iterator pos, const T& val)
  3. {
  4. assert(pos >= _start);
  5. assert(pos < _finish);
  6. if (_finish == _endofstorage)
  7. {
  8. size_t len = pos - _start;
  9. reserve(capacity() == 0 ? 4 : capacity() * 2);
  10. //扩容要更新pos
  11. pos = _start + len;
  12. }
  13. iterator it = _finish - 1;
  14. while (it >= pos)
  15. {
  16. *(it + 1) = *it;
  17. --it;
  18. }
  19. *pos = val;
  20. _finish++;
  21. }

这个指定的位置pos用迭代器比用数字更加方便进行比较,所以建议直接使用迭代器进行插入

有一点值得注意的是,传入的pos是指向原数组的如果进行了扩容,那么原数组被销毁,所有的指针都指向新的数组,所以也需要对pos指针进行更新,否则pos就会成为野指针。 

 测试如下:


3.遍历

除了上边的for循环遍历外,我们还可以通过迭代器和范围for进行遍历

  1. vector<int>::iterator it = v.begin();
  2. while (it != v.end())
  3. {
  4. cout << *it << ' ';
  5. ++it;
  6. }
  7. cout << endl;
  8. for (auto e : v)
  9. {
  10. cout << e << ' ';
  11. }
  12. cout << endl;

测试如下:


4.删除

首先来看尾删,和顺序表一样,尾删就直接让_finish减1即可,还要注意判断是否为空

  1. //判空
  2. bool empty()
  3. {
  4. return _start == _finish;
  5. }
  6. //尾删
  7. void pop_back()
  8. {
  9. assert(!empty());
  10. --_finish;
  11. }

测试如下:

 再来看指定位置的删除:

  1. //指定位置删除
  2. void erase(iterator pos)
  3. {
  4. assert(pos >= _start);
  5. assert(pos < _finish);
  6. iterator it = pos + 1;
  7. while (it < _finish)
  8. {
  9. *(it - 1) = *it;
  10. it++;
  11. }
  12. _finish--;
  13. }

测试如下:


5.交换 

想要实现两个对象之间的交换,其底层实现还是要借助std库中的swap函数

  1. //交换
  2. void swap(vector<T>& v)
  3. {
  4. std::swap(_start, v._start);
  5. std::swap(_finish, v._finish);
  6. std::swap(_endofstorage, v._endofstorage);
  7. }

测试如下: 

 


6.赋值

使用容器的一种必不可少的方法就是相同类型的对象之间的赋值,这就关系到类中的拷贝构造函数了。

我们知道拷贝构造分为浅拷贝和深拷贝后者要关系到指针,显然,voctor类型对象的成员变量都是指针,所以我们必须写出深拷贝构造函数

  1. //拷贝构造函数
  2. vector(const vector<T>& v)
  3. {
  4. reserve(v.capacity());
  5. for (auto& e : v)
  6. {
  7. push_back(e);
  8. }
  9. }

这里我们采用了先开空间,在依次尾插的方式来实现拷贝构造函数,测试如下:


还可以使用“=”运算符重载的方式进行赋值:

  1. //=运算符重载
  2. vector<T>& operator=(vector<T> v)
  3. {
  4. swap(v);
  5. return *this;
  6. }

这个写法非常的高明,假设赋值为:v1 = v2,那么v将作为v2的拷贝与v1进行交换,然后返回v1,而v因为只是一个拷贝,出函数后就会被销毁,所以也不会影响到v2的值,测试如下:

 


 此外,还有一种方式,他可以选择某个对象的部分区间赋值给新的对象,叫做迭代器区间构造

  1. //迭代器区间构造
  2. template<class InputIterator>
  3. vector(InputIterator first, InputIterator last)
  4. {
  5. while (first != last)
  6. {
  7. push_back(*first);
  8. first++;
  9. }
  10. }

 这里要注意,该函数我们把它写成模版函数

类模板的成员函数可以是函数模版,那么写成这样的好处是什么呢?

能够看出,我们不仅能截取同为vector类型的对象,还能够截取不同的string类型的对象。 


总结

关于vector的分享就到这里啦。

喜欢本篇文章的小伙伴记得一键三连,我们下期再见!

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/959775
推荐阅读
  

闽ICP备14008679号