【STL模版库】模拟实现vector类模版

news2024/9/30 13:17:03

一、成员变量

template<class T>
class Myvector{
    typedef T *iterator; //[1]
    typedef const T *const_iterator;
  private:
    iterator _start; //指向存存储空间的开头 //[2]
    iterator _finish; //指向实际存储元素的下一个位置
    iterator _end_of_storage; //指向存储空间结尾的下一个位置

  public:   
    size_t size()const{
      return _finish-_start;
    }
    size_t capacity()const{
      return _end_of_storage-_start;
    }
};

解释:

  • [1] 对于顺序表迭代器就是指向其内部元素的指针。
  • [2] 下面是其结构示意图:
    在这里插入图片描述

二、遍历访问

2.1 iterator

	iterator begin(){
      return _start;
    }

    iterator end(){
      return _finish;
    }

    const_iterator begin() const{
      return _start;
    }
    const_iterator end() const{
      return _finish;
    }

2.2 operator[ ]

    T& operator[](size_t pos){
      assert(pos < size());
      return _start[pos];
    }

    const T& operator[](size_t pos) const{
      assert(pos < size());
      return _start[pos];
    }

三、四个默认构造函数

3.1 构造

    Myvector()
      :_start(nullptr), //[1]
      _finish(nullptr),
      _end_of_storage(nullptr)
    {}

    Myvector(size_t n, const T &val = T()) //[2]
      :_start(nullptr),
      _finish(nullptr),
      _end_of_storage(nullptr)
    {
      resize(n, val);
    }
 	//上一个函数的重载函数
    Myvector(int n, const T &val = T()) //[3]
      :_start(nullptr),
      _finish(nullptr),
      _end_of_storage(nullptr)
    {
      resize(n, val);
    }

 

解释:

  • [1] 构造函数必须要先将3个迭代器置空,否则在reserve开空间时会出现访问野指针的问题。
  • [2] 第二个参数用T类型的匿名对象做缺省值,相当于去调默认构造。生成的匿名对象具有常性需用const引用。
  • [2] 由此可以看出,我们自己定义的类是一定要提供默认构造的,否则会出现一些问题。
  • [2] 由于模版的出现,C++对内置类型进行了升级,内置类型也具有默认构造。内置类型的默认构造会将其初始化为0
  • [3] 重载该函数的目的是为了解决和函数Myvector(input_iterator first, input_iterator last);的调用冲突问题。
  • [3] 例如:Myvector v1(10, 5);如果没有该重载函数,由于10(int)与size_t(unsigned int)类型不匹配,需要进行类型转换。所以编译器会优先匹配Myvector(input_iterator first, input_iterator last);,将10和5解释成迭代器,从而访问非法空间造成程序崩溃。

3.2 拷贝构造

//迭代器区间拷贝构造:
    template<class input_iterator> //[1]
     Myvector(input_iterator first, input_iterator last)
        :_start(nullptr),
        _finish(nullptr),
        _end_of_storage(nullptr)
      {
        reserve(last-first);
        while(first!=last)
        {
          *_finish = *first;
          ++_finish;
          ++first;
        }
      }

// 传统写法:
    Myvector(const Myvector<T> &v)
        :_start(nullptr),
        _finish(nullptr),
        _end_of_storage(nullptr)
    {
      reserve(v.size());
      for(size_t i = 0; i<v.size(); ++i)
      {
        _start[i] = v._start[i];
      }
      _finish = _start + v.size();
    }

// 复用写法:
    Myvector(const Myvector<T> &v)
        :_start(nullptr), //[3]
        _finish(nullptr),
        _end_of_storage(nullptr)
    {
      Myvector tmp(v.begin(), v.end()); //[2]
      swap(tmp); 
    }
    
//交换函数:
    void swap(Myvector<T> &v){
      std::swap(_start,v._start);
      std::swap(_finish,v._finish);
      std::swap(_end_of_storage, v._end_of_storage);
    }

解释:

  • [1] 将此函数写成模版,是为了兼容各种容器的迭代器。使vector可以拷贝构造各种容器的数据。
  • [2] 复用迭代器区间拷贝构造,先构造出临时对象tmp,再与构造对象交换数据(浅交换)
  • [2] 之后tmp析构,而构造对象也得到了拷贝数据。
  • [3] 注意:交换前要先将构造对象的三个迭代器置空,否则析构tmp时会因释放野指针而崩溃。

3.3 赋值重载 & 析构

    Myvector<T>& operator=(const Myvector<T> &v){
      Myvector tmp(v); //[1]
      swap(tmp);
    }

    ~Myvector(){
      delete[] _start;
    }
  • [1] 复用拷贝构造实现赋值重载。

四、容量操作

4.1 reserve

    void reserve(size_t n){
      if(n > capacity())
      {
        iterator tmp = new T[n];
        size_t sz = size();
        if(_start!=nullptr)
        {
          //memcpy(tmp, _start, sz * sizeof(T)); //[1]
          for(size_t i = 0; i< sz; ++i)
          {
            tmp[i] = _start[i];
          }
          delete[] _start;
        }
        _start = tmp;
        _finish = _start + sz; //[2]
        _end_of_storage = _start + n;
      }
    }

解释:

  • [1] 此处不能使用memcpy拷贝数据,当模版类型T是自定义类型且涉及动态内存申请时(如string或list),memcpy只能进行浅拷贝。
  • [1] 应该逐元素调用赋值重载,以实现多层深拷贝。下文有二维动态数组的深拷贝问题的详解。
  • [2] 扩容后开辟新空间,释放旧空间。_finish和_end_of_storage也要根据长度计算新地址,否则就成了野指针。

4.2 resize

    void resize(size_t n, const T &val = T()){
      if(n > size())
      {
        reserve(n);
        for(size_t i = size(); i<n; ++i)
        {
          _start[i] = val; //[1]
        }
      }
      _finish = _start + n; //[2]
    }

解释:

  • [1] 对于自定义类型元素此处调用赋值重载。
  • [2] 当n > size(),_finish向后移动;当n<=size(),_finish向前移动;

五、增删查改

5.1 push_back & pop_back

    void push_back(const T &val){
      if(_finish == _end_of_storage)
      {
        size_t n = capacity() == 0? 5:capacity()*2;
        reserve(n);
      }
      *_finish = val;
      ++_finish;
    }
    
    void pop_back(){
      assert(_finish != _start);
      --_finish;
    }

5.2 insert

    iterator insert(iterator pos, const T &val){
      assert(pos >= _start);
      assert(pos <= _finish); //[1]
      if(_finish == _end_of_storage)
      {
        size_t n = capacity() == 0? 5:capacity()*2;
        size_t len = pos-_start;
        reserve(n);
        pos = _start+len; //[2]
      }
      iterator end = _finish - 1;
      while(end >= pos)
      {
        *(end+1) = *end;
        --end;
      }
      *pos = val;
      ++_finish;
      return pos; //[3]
    }

解释:

  • [1] 当pos == _finish时,表示尾插数据。
  • [2] 扩容重开空间后,pos仍指向旧空间的地址成了野指针(迭代器失效)。因此需要根据长度重新计算位置。
  • [3] insert插入数据pos迭代器可能失效,因此要返回重新计算后的pos迭代器(指向新插入的数据),并在函数调用处接收返回值,才可继续进行插入操作。

5.3 erase

    iterator erase(iterator pos){
      assert(pos >= _start);
      assert(pos < _finish);
      iterator tmp = pos+1; //[1]
      while(tmp < _finish){
        *(tmp-1) = *tmp;
        ++tmp;
      }
      --_finish;
	
	  //if(size() < capacity()/2) //[2]
	  //{
	  	//......
	  	//缩容--以时间换空间
	  //}

      return pos; //[3]
    }

解释:

  • [1] 这个过程是在向前挪动数据覆盖要删除的数据。
  • [2] 不排除个别版本的erase会进行缩容操作,此处重新开辟内存空间,pos迭代器也有可能失效,因此同样需要重新计算。
  • [3] 返回被删除元素的后一个元素。

六、关于vector的两个问题

6.1 迭代器失效问题

在所有偶数前插入此数的10倍 :

//错误写法: 
void Test1(){    
  int arr[] = {1,2,3,4,5};    
  Myvector<int> v1(arr, arr+sizeof(arr)/sizeof(arr[0]));    
  Myvector<int>::iterator it = v1.begin();    
  while(it != v1.end())    
  {    
    if(*it % 2 == 0)    
    {    
      v1.insert(it, (*it) * 10); //返回值指向新插入的数据                                                                                                                                     
    }  
    ++it;
  }    
    
  for(int e : v1)    
  {    
    cout << e << " ";    
  }    
  cout << endl;    
}  

此时可能出现以下两种迭代器失效的情况,从而引起程序崩溃:
在这里插入图片描述

//正确写法:    
void Test1(){    
  int arr[] = {1,2,3,4,5};    
  Myvector<int> v1(arr, arr+sizeof(arr)/sizeof(arr[0]));    
  Myvector<int>::iterator it = v1.begin();    
  while(it != v1.end())    
  {    
    if(*it % 2 == 0)    
    {    
      it = v1.insert(it, (*it) * 10); //接收返回值,解决情况1                                                                                                                                
      it+=2; //从偶数的下一个数据开始重新判断,解决情况2    
    }  
    else
    {  
    	++it;
    }    
  }    
  //......   
}  

删除数列中所有的偶数:

//错误写法:                                                                                                                                                  
void Test2(){    
  int arr[] = {1,2,3,4,5};    
  Myvector<int> v1(arr, arr+sizeof(arr)/sizeof(arr[0]));    
  Myvector<int>::iterator it = v1.begin();    
  while(it != v1.end())
  {
    if(*it % 2 == 0)
    {
      v1.erase(it); //返回值指向被删除元素的后一个元素
    }
      ++it; 
  }

  for(int e : v1)
  {
    cout << e << " ";
  }
  cout << endl;  
}

运行结果:
在这里插入图片描述
解释SGI版结果:

  1. 第一种情况看似正常运行,实则是因为数据排列的偶然性。
  2. 第二种情况程序崩溃。是因为最后一个数是偶数,删除之后又++it错过了_finish,最终导致越界访问程序崩溃。
  3. 第三种情况程序可以运行但结果不对。是因为删除第一个2之后++it错过了第二个2。
//正确写法:                                                                                                                                                  
void Test2(){    
  int arr[] = {1,2,2,3,4,4,4};    
  Myvector<int> v1(arr, arr+sizeof(arr)/sizeof(arr[0]));    
  Myvector<int>::iterator it = v1.begin();    
  while(it != v1.end())
  {
    if(*it % 2 == 0)
    {
      it = v1.erase(it); //返回值指向被删除元素的后一个元素
    }
    else{
      ++it;
    }
  }
  //......  
}

结论:

  1. 使用insert/erase插入或删除pos位置数据之后。一定要接收返回值更新pos位置,如果直接访问可能因扩容而遇到野指针问题。
  2. 要明确insert/erase返回值指向的位置,并对返回更新后的pos进行适当调整以继续插入或删除操作。

6.2 二维动态数组的深拷贝问题

以上一节提到的题目杨辉三角(【STL模版库】vector的介绍及使用3.2)为例,如果我们要拷贝其计算得到的结果:

 void Test1(){
  Myvector<Myvector<int>> ret = Solution().generate(5); //拷贝构造    
  for(size_t i = 0; i<ret.size(); ++i)    
  {    
    for(size_t j = 0; j<ret[i].size(); ++j)    
    {    
      cout << ret[i][j] << " ";    
    }    
    cout << endl;    
  }
}

ret是Myvector<Myvector>类型的二维数组,那么多层深拷贝是如何进行的呢?
在这里插入图片描述
运行结果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/544987.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【5.19】三、白盒测试方法—逻辑覆盖法

目录 3.1 逻辑覆盖法 3.1.1 语句覆盖 3.1.2 判定覆盖 3.1.3 条件覆盖 3.1.4 判定—条件覆盖 3.1.5 条件组合覆盖 3.1.6 实例&#xff1a;三角形逻辑覆盖问题 白盒测试又称为透明盒测试、结构测试&#xff0c;它基于程序内部结构进行测试&#xff0c;而不是测试应用程序…

Net跨平台UI框架Avalonia入门-资源和样式

Net跨平台UI框架Avalonia入门-资源和样式编写和使用 资源和样式编写和使用样式&#xff08;Styles&#xff09;和资源&#xff08;Resources&#xff09;样式&#xff08;Styles&#xff09;样式定义定义的位置:定义内容&#xff1a; 样式文件的定义和引用 资源&#xff08;Res…

微信小程序xr-frame后处理

前言&#xff1a;什么是后处理&#xff1f;&#xff08;详见&#xff1a;ThreeJS 后处理 - 掘金 (juejin.cn)&#xff09; 后处理就是对WebGLRenderer.render(scene, camera)的渲染2D图片进行处理。可以把多个后处理进行组合&#xff0c;按照顺序执行&#xff0c;每个处理过程…

新-git-gitee代码管理(管理)

git忽略文件失效 git rm -r --cached . //清除缓存 git add . //添加所有文件 git commit -m update .gitignore //提交更新.gitignoregit 提交的一些规范 开发git commit规范&#xff1a; git commit --fix我的问题feat&#xff1a;新功能 fix&#xff1a;BUG…

VMware16安装 CentOS7

目录 VM下载与安装 密钥 CentOS镜像下载 安装过程 问题 win11一点启动就蓝屏重启 系统安装 安装摘要 选择日期 软件选择-> 最小安装 安装位置 网络和主机名 开始安装 用户设置 完成 登录 xshell连接操作 登录成功 VM下载与安装 官网下载地址 下载 VMware Works…

恩智浦正式启动人工智能创新实践平台,为本地生态注入创新动能

中国天津——2023年5月19日——恩智浦半导体&#xff08;NXP Semiconductors N.V.&#xff0c;纳斯达克代码&#xff1a;NXPI&#xff09;今日宣布&#xff0c;设于天津的人工智能应用创新中心二期项目——人工智能创新实践平台&#xff08;以下称“创新实践平台”&#xff09;…

三、IOC容器(3)

一、IOC操作Bean管理&#xff08;外部属性文件&#xff09; 1.直接配置数据库信息 配置德鲁伊连接池引入德鲁伊连接池依赖jar包 <!--配置连接池--> <bean id"dataSource" class"com.alibaba.druid.pool.DruidDataSource"><property name&…

面了一位5年的测试,真的很失望......

最近看了很多简历&#xff0c;很多候选人年限不小&#xff0c;但是做的都是一些非常传统的项目&#xff0c;想着也不能通过简历就直接否定一个人&#xff0c;何况现在大环境越来 越难&#xff0c;大家找工作也不容易&#xff0c;于是就打算见一见。 在沟通中发现&#xff0c;由…

《HTTP权威指南 陈涓 赵振平》读书笔记

目录 第一章 HTTP概述 第二章 URL与资源 第三章 HTTP报文 第四章 连接管理 第一章 HTTP概述 1、POST和PUT的区别 POST&#xff1a;将客户端数据发送到一个服务器网关应用程序PUT&#xff1a;将来自客户端额数据存储到一个命名的的服务器资源中 2、HTTP报文&#xff1a;…

Windows下通过cwRsync备份到服务器服务器之间使用rsync备份传输

Windows下通过cwRsync备份到服务器&服务器之间使用rsync备份传输 Linux服务器配置Rsync服务端1、安装Rsync2、配置rsyncd.conf3、创建目录、密码文件并修改权限4、启动rsync服务 Windows配置cwRsync客户端1、下载并解压cwRsync客户端2、打开cmd&#xff0c;执行同步命令 Wi…

好程序员:一篇文章看懂JavaScript 学习路线!前端自学!

如果你是一名编程初学者&#xff0c;刚刚学完HTML和CSS&#xff0c;那就不得不接触JavaScript。今天&#xff0c;好程序员给大家分享一篇2023最新版&#xff0c;JavaScript学习路线。 1. HTML and CSS 语法、结构、响应式设计、引导 2. JavaScript语言基础 语法、数据、类型、…

Cloud Studio 高阶玩家:强大的 YAML 模板

Cloud Studio 高阶玩家&#xff1a;强大的 YAML 模板 1. 功能简介 编程免不了要写配置文件&#xff0c;怎么写配置也是一门学问。YAML 是专门用来写配置文件的语言&#xff0c;非常简洁和强大。 了解到一些用户在Cloud Studio开发项目的时候&#xff0c;环境上需要依赖一些组…

Java设计模式-策略模式

简介 在软件开发中&#xff0c;设计模式是为了解决常见问题而提供的一套可重用的解决方案。策略模式&#xff08;Strategy Pattern&#xff09;是其中一种常见的设计模式&#xff0c;它属于行为型模式。该模式的核心思想是将不同的算法封装成独立的策略类&#xff0c;使得它们…

软件测试项目测试报告总结

测试计划概念&#xff1a;就在软件测试工作实施之前明确测试对象&#xff0c;并且通过资源、时间、风险、测试范围和预算等方面的综合分析和规划&#xff0c;保证有效的实施软件测试。 需求挖掘的6个方面&#xff1a; 1、输入方面 2、处理方面 3、结果输出方面 4、性能需求…

蓝牙耳机怎么挑选?工程师盘点目前最值得入手的蓝牙耳机

蓝牙耳机已经成为手机标配&#xff0c;各大品牌也陆续加入蓝牙耳机行业&#xff0c;市场十分繁荣。我身为从业人员对整个行业有着深入的了解&#xff0c;考虑到很多朋友还不知道蓝牙耳机怎么挑选&#xff0c;我整理了目前最值得入手的蓝牙耳机&#xff0c;分别是&#xff1a; 1…

保护你的 shell脚本

什么是shell&#xff1f; shell 是一种脚本语言 脚本&#xff1a;本质是一个文件&#xff0c;文件里面存放的是 特定格式的指令&#xff0c;系统可以使用脚本解析器 翻译或解析 指令 并执行&#xff08;它不需要编译&#xff09; shell 既是应用程序 又是一种脚本语言&#xff…

1. python学习环境准备

文章目录 前言本专栏文章旨在记录《Python编程从入门到实践》一书的学习笔记。 一、编程环境二、使用步骤1.修改默认python版本2.终端退出python解释器3.编写.py文件4.运行.py文件 三、Python帮助文档的使用总结 前言 本专栏文章旨在记录《Python编程从入门到实践》一书的学习…

N9305语音芯片在新能源车充电桩上的方案

语音芯片技术作为近年来蓬勃发展的人工智能领域的重要组成部分&#xff0c;正在被广泛运用于诸多领域&#xff0c;并为人类生活带来了很多便利和创新。其中&#xff0c;新能源充电桩的运用就是一个很好的例子。随着电动汽车的普及&#xff0c;充电桩的需求量不断增加。为了提高…

BGP路由选择实验

测试环境拓扑图 每一种规则测试完后记得恢复初始状态&#xff01;&#xff01; 各设备BGP Router_ID为loopback 0的地址。 AR1 配置 [V200R003C00] #sysname AR1 # interface GigabitEthernet0/0/0ip address 10.1.12.1 255.255.255.0 # interface LoopBack0ip address 1.1.…

远程桌面连接怎么使用?

远程桌面连接是一种远程控制计算机的技术&#xff0c;它允许用户通过Internet或局域网远程访问另一台计算机的桌面界面。使用远程桌面连接技术&#xff0c;可以帮助用户在家里或在外出时访问工作计算机&#xff0c;或者在不同的地方协作完成任务。在本文中&#xff0c;我们将介…