实现vector

  • 前言
  • 初步实现
    • 出现的问题
  • 解决问题
    • Allocator(空间配置器)
  • 最终结果
    • 对比系统自带的

前言

不知道大家有没有这种感觉,学习了一段时间后,总想着自己能够实现一些标准库里已经提供的东西,比如说STL容器vector、比如数据类型string
一方面是对这个知识可以有进一步的一个认识和了解,另一方面也可以检验自己的掌握程度。
基于此,我们今天实现自定义的vector

初步实现

通过包含头文件#include <vector>转到文档,我们可以查看系统自带vector的实现:

主要包含了三个指针:

  1. 指向数组起始位置的指针;
  2. 指向数组中最后一个有效元素位置的后继位置;
  3. 指向数组空间的后继位置。

了解了这些后,我们就可以自定义vector了:
代码如下:

template<typename T>
class vector
{public:vector(int size = 10){_first = new T[size];_last = _first;_end = _first + size;}~vector(){delete[]_first;_first = _end = _last = nullptr;}vector(const vector<T>& rhs){int size = rhs._end - rhs._first;_first = new T[size];int len = rhs._last - rhs._first;for (int i = 0; i < len; ++i){_first[i] = rhs._first[i];}_last = _first + len;_end = _first + size;}vector<T>& operator=(const vector<T>& rhs){if (this == &rhs)return *this;delete[]_first;int size = rhs._end - rhs._first;_first = new T[size];int len = rhs._last - rhs._first;for (int i = 0; i < len; ++i){_first[i] = rhs._first[i];}_last = _first + len;_end = _first + size;return *this;}void push_back(const T& val) // 向容器末尾添加元素{if (full())expand();*_last++ = val;  }void pop_back() // 从容器末尾删除元素{if (empty())return;--_last; }T back()const // 返回容器末尾的元素的值{return *(_last - 1);}bool full()const { return _last == _end; }bool empty()const { return _first == _last; }int size()const { return _last - _first; }
private:T* _first; // 指向数组起始的位置T* _last;  // 指向数组中有效元素的后继位置T* _end;   // 指向数组空间的后继位置void expand() // 容器的二倍扩容{int size = _end - _first;T *ptmp = new T[2 * size];for (int i = 0; i < size; ++i){ptmp[i] = _first[i];}delete[]_first;_first = ptmp;_last = _first + size;_end = _first + 2 * size;}
};

出现的问题

我们知道,对于一个容器来说,里面存放的数据类型可以是系统自带的默认类型,比如:intchar等等,也可以是自己定义的class XXX类型。
对于默认类型来说,我们上面的代码是没有问题的(可以自行验证),但是对于自定义类型来说,就会出现问题:
比如我们加上自己定义的简单类型,分别给构造函数、析构函数、拷贝构造函数都加上打印信息:

class Test
{public:Test() { cout << "Test()" << endl; }~Test() { cout << "~Test()" << endl; }Test(const Test&) { cout << "Test(const Test&)" << endl; }
};

接下来运行代码:

int main()
{vector<Test> vec;return 0;
}

结果如下:

我们会发现,我只是定义了一个空的容器,
但是它却自动给我添加了十个对象。
这,,于情于理都说不过去。

解决问题

Allocator(空间配置器)

分析完问题后,我们可以继续查看系统自带vector是如何做的?

可以看到,系统的实现,除了数据类型外,还有一个allocator。而这个,就是解决问题的办法了!
我们再来回过头看看问题,其实问题主要出在了构造函数的new上:

对于new来说,它要做两件事情:

  1. 开辟空间;
  2. 数据初始化(对于自定义类型来说就是要调用构造函数

可问题是,我们既然选择把它作为容器,那么就只需要它提供一个场所(空间),至于这个场所里存放什么数据,是由程序员来决定的,不应该由容器来擅作主张。
那么,问题的关键就在于将开辟空间和构造对象分离开。
而这,也就是空间配置器做的工作;
于是我们添加容器的空间配置器:

// 定义容器的空间配置器,和C++标准库的allocator实现一样
template<typename T>
struct Allocator
{T* allocate(size_t size) // 负责内存开辟{return (T*)malloc(sizeof(T) * size);}void deallocate(void* p) // 负责内存释放{free(p);}void construct(T* p, const T& val) // 负责对象构造{new (p) T(val); // 定位new}void destroy(T* p) // 负责对象析构{p->~T(); // ~T()代表了T类型的析构函数}
};

可以看到,空间配置器主要有四个功能:

  1. 内存开辟 allocate(底层调用malloc);
  2. 内存释放 deallocate(底层调用free);
  3. 对象构造 construct(调用构造函数);
  4. 对象析构 destroy(调用析构函数)。

并且,修改自定义的vector

template<typename T, typename Alloc = Allocator<T>>
class vector
{public:vector(int size = 10){// 需要把内存开辟和对象构造分开处理_first = _allocator.allocate(size);_last = _first;_end = _first + size;}~vector(){// 析构容器有效的元素,然后释放_first指针指向的堆内存for (T* p = _first; p != _last; ++p){_allocator.destroy(p); // 把_first指针指向的数组的有效元素进行析构操作}_allocator.deallocate(_first); // 释放堆上的数组内存_first = _last = _end = nullptr;}vector(const vector<T>& rhs){int size = rhs._end - rhs._first;_first = _allocator.allocate(size);int len = rhs._last - rhs._first;for (int i = 0; i < len; ++i){_allocator.construct(_first + i, rhs._first[i]);}_last = _first + len;_end = _first + size;}vector<T>& operator=(const vector<T>& rhs){if (this == &rhs)return *this;for (T* p = _first; p != _last; ++p){_allocator.destroy(p); // 把_first指针指向的数组的有效元素进行析构操作}_allocator.deallocate(_first);int size = rhs._end - rhs._first;_first = _allocator.allocate(size);int len = rhs._last - rhs._first;for (int i = 0; i < len; ++i){_allocator.construct(_first + i, rhs._first[i]);}_last = _first + len;_end = _first + size;return *this;}void push_back(const T& val) // 向容器末尾添加元素{if (full())expand();_allocator.construct(_last, val);_last++;}void pop_back() // 从容器末尾删除元素{if (empty())return;// 不仅要把_last指针--,还需要析构删除的元素--_last;_allocator.destroy(_last);}T back()const // 返回容器末尾的元素的值{return *(_last - 1);}bool full()const { return _last == _end; }bool empty()const { return _first == _last; }int size()const { return _last - _first; }
private:T* _first; // 指向数组起始的位置T* _last;  // 指向数组中有效元素的后继位置T* _end;   // 指向数组空间的后继位置Alloc _allocator; // 定义容器的空间配置器对象void expand() // 容器的二倍扩容{int size = _end - _first;T* ptmp = _allocator.allocate(2 * size);for (int i = 0; i < size; ++i){_allocator.construct(ptmp + i, _first[i]);}for (T* p = _first; p != _last; ++p){_allocator.destroy(p);}_allocator.deallocate(_first);_first = ptmp;_last = _first + size;_end = _first + 2 * size;}
};

最终结果

修改完之后,我们再来运行如下代码:

int main()
{Test t1;cout << "-------------------" << endl;vector<Test> vec;vec.push_back(t1);cout << "-------------------" << endl;return 0;
}

想要实现的效果是:
用户定义了多少个对象,容器里面包含多少个对象;
容器不会自己定义多个对象出来。
运行结果如下:

可以看到,开辟空间和构造对象分离开了。

对比系统自带的

我们还可以使用系统自带的vector来对比:

#include <iostream>
#include <vector>
using namespace std;class Test
{public:Test() { cout << "Test()" << endl; }~Test() { cout << "~Test()" << endl; }Test(const Test&) { cout << "Test(const Test&)" << endl; }
};
int main()
{Test t1;cout << "-------------------" << endl;vector<Test> vec;vec.push_back(t1);cout << "-------------------" << endl;return 0;
}

运行结果:

可以看到,是一样的!

C++实现自定义vector以及allocator相关推荐

  1. Boost:自定义vector的测试程序

    Boost:自定义vector的测试程序 实现功能 C++实现代码 实现功能 Boost的container模块,自定义vector的测试程序 C++实现代码 #include <boost/c ...

  2. 利用std::allocator实现自定义的vector类

    std::allocator即空间配置器,用于内存分配.更多的细节建议大家研究相关源码. 这里仅是利用std::allocator来实现简单的自定义vector类,如有问题欢迎指正. 1 #inclu ...

  3. C++(19)--自定义Array,vector练习

    自定义Array,vector 1.自定义Array 2.自定义vector <老九学堂C++课程><C++ primer>学习笔记.<老九学堂C++课程>详情请到 ...

  4. 分配器allocator和new重载

    3. 分配器allocator和new重载 3 分配器allocator和new重载 3.3 分配器allocator详解 3.4 自定义allocator 3.5 未初始化内存复制分析 3 分配器a ...

  5. C++ STL: 容器vector源码分析

    文章目录 前言 vector的核心接口 vector push_back实现 vector 的 Allocator vector 的 push_back 总结 前言 vector 是我们C++STL中 ...

  6. STL源码剖析 序列式容器|Vector

    容器的概观和分类 array 数组 .list 链表.tree树 .stack堆栈.queue队列.hash table散列表.set集合.map映射表 根据数据在容器中的排列顺序,将上述数据结构分为 ...

  7. vector 内部方法大全 学习(初学者的参考资料)

    1    vector构造函数:也就是如何对一个vector对象进行初始化 代码//  explicit vector ( const Allocator& = Allocator() );  ...

  8. 神秘的 Allocator

    先看看 std::vector 的声明 template < class Type, class Allocator = allocator<Type> > class vec ...

  9. C++中std::allocator的使用

    标准库中包含一个名为allocator的类,允许我们将分配和初始化分离.使用allocator通常会提供更好的性能和更灵活的内存管理能力. new有一些灵活性上的局限,其中一方面表现在它将内存分配和对 ...

最新文章

  1. css选择器匹配没有属性x的元素[重复]
  2. Oracle中的date与timestamp
  3. iview 级联选择组件_vue组件递归渲染实例
  4. 多线程队列的算法优化
  5. 申请成功Azure帐号开始学习Azure云计算
  6. EMNLP 2022 和 COLING 2022,投哪个会议比较好?
  7. O_RDONLY/O_NOATIME undeclared (first use in this function
  8. 微信公众号分销商城(源码+数据库+文档)
  9. NCTF-Writeup
  10. linux中的本地化
  11. vue 实现 高德地图 api 掩模、定位、天气
  12. apache-tomcat-10.0.18配置
  13. 数字电路硬件设计系列(八)之LED电路设计
  14. 发布订阅模式vs观察者模式
  15. 浅谈Python的现状、发展前景以及Python的就业岗位!
  16. 贝加莱工控机维修主板维修5PC600.SX01-00常见故障排查
  17. 什么是智能安全帽,如何选购智能安全帽
  18. 常用电平标准——LVTTL、LVCMOS、LVDS等
  19. SAP S4 实施 会计科目表的实施方法论
  20. CentOS 7.7安装Erlang和Elixir

热门文章

  1. 数据库第一、二章速——数据库基础概念
  2. Spark MLlib (1)
  3. 整形二维数组中求最大值Max,行(row)和列(colum)。
  4. 对个位数和十位数进行四舍五入
  5. 闪闪发光的你,华泰张庆的交易哲学观
  6. Python(14)查找算法
  7. GSM/GPRS基础汇总
  8. 攻防世界 - MISC - 07 - 坚持60s
  9. 电脑系统修复,操作简单
  10. 三种JS截取字符串方法