STL模拟实现—vector

news2024/12/23 10:23:57

引言:本篇文章主要是模拟实现vector,但不同于stl中vector的成员变量都是迭代器,这个自定义的vector是一个T* 的数据变量和一个int类型的size和int类型的capacity。(有时间再写三个迭代器的版本吧!)

首先来看一下vector的结构 :

start、finish、end_of_storage都是迭代器,也就对应vector类的三个成员变量

 本篇实现的自定义vector类实现了如下功能:

  • 类模板vector的定义:模板类vector拥有构造函数、析构函数、拷贝构造函数、赋值运算符重载,以及其他一些成员函数。
  • 构造函数:默认构造函数创建一个空的向量。带有两个参数的构造函数创建一个具有指定大小和初始值的向量。
  • 析构函数:释放内存并将大小和容量重置为0。
  • 成员函数size()和capacity():分别返回向量的大小和容量。
  • 成员函数push_back():将元素添加到向量的末尾。如果向量已满,则自动扩容。
  • 成员函数reserve():为向量分配指定的容量大小。
  • 重载运算符[]:通过下标访问向量中的元素。
  • 成员函数begin()和end():返回迭代器的起始和结束位置。
  • 成员函数insert()和erase():在指定位置插入或删除元素。
  • 成员函数find():在指定范围内查找元素,并返回迭代器。

 下面是代码:

 my_vector.h

#pragma once
#include <iostream>
#include <assert.h>

using std::cout;  
using std::endl;

template <class T> //模板类声明
class vector{
public:
  vector(); //默认构造函数

  ~vector(); //析构函数

  vector(const vector& v); //拷贝构造函数

  vector& operator=(vector v); //赋值运算符

  typedef T* iterator; //迭代器类型定义

  typedef const T* const_iterator; //常量迭代器类型定义

  vector(size_t n,const T& val); //构造函数,用n个val初始化

  size_t size()const; //返回大小

  size_t capacity()const; //返回容量

  void push_back(const T& val); //尾部插入元素

  void reserve(size_t n); //容量预留

  T& operator[](size_t n); //下标访问运算符

  const T& operator[](size_t n)const; //常量版本下标访问运算符

  iterator begin(); //返回开始迭代器

  iterator end(); //返回结束迭代器

  const_iterator begin()const; //返回常量开始迭代器

  const_iterator end()const; //返回常量结束迭代器

  iterator insert (iterator position, const T& val); //迭代器版本插入

  iterator insert (int position,const T& val); //整数位置版本插入  

  iterator erase (iterator position); //迭代器版本删除

  iterator erase (int position); //整数位置版本删除

  iterator find (iterator _begin,iterator _end,const T& val); //查找元素

private:
  
  T* _data; //内部存储空间指针

  size_t _size; //当前大小 

  size_t _capacity; //当前容量
};

 my_vector.cpp

#include "my_vector.h"
template<class T>
vector<T>::vector()
  :_data(nullptr)
  ,_size(0)
  ,_capacity(0)
{}
template <class T>
vector<T>::~vector(){
  delete [] _data;
  _size = 0;
  _capacity = 0;
}
//构造器—开辟n个val的数组
template<class T>
vector<T>::vector(size_t n,const T& val){
  _data = new T[n];
  for(size_t i = 0; i<n;i++){
    _data[i] = val;
  }
  _size = n;
  _capacity = n;
}

template<class T>
size_t vector<T>::size()const{
  return _size;
}

template<class T>
size_t vector<T>::capacity()const{
  return _capacity;
}

template <class T>
void vector<T>::reserve(size_t n){
    if(n==0)return;
    T* tmp = new T[n];
    for(size_t i =0;i<_size;i++){//深拷贝
      tmp[i] = _data[i];
    }
    delete []_data;
    _data = tmp;
    _capacity = n;
}

template<class T>
void vector<T>::push_back(const T& val){
  if(_size == _capacity){//扩容
    _capacity = _capacity == 0?2:(_capacity*1.5);
    reserve(_capacity);
    }
    _data[_size] = val;
    _size++;
  }

template <class T>
T& vector<T>::operator[](size_t n){
    assert(n<_size);//断言n要小于size
    return _data[n];
}
template<class T>
const T& vector<T>::operator[](size_t n)const{
  assert(n<_size);
  return _data[n];
} 
template <class T>
typename vector<T>::iterator vector<T>::begin(){
  //定义时需要加上 typename 告诉编译器这是一个类型,因为这个类型是我们typedef出来的 
  return _data;
}
template <class T>
typename vector<T>::iterator vector<T>::end(){
    return _data+_size;
}
template <class T>
typename vector<T>::const_iterator vector<T>::end()const{
    return _data+_size;
}

template <class T>
typename vector<T>::const_iterator vector<T>::begin()const{
    return _data+_size;
}
template <class T>
vector<T>::vector(const vector& v){
  if(this == &v)return;//如果拷贝构造自己返回
  _data = new T[v._size];
  for(int i = 0;i<v._size;i++){
    _data[i]=v[i];
  }
  _capacity = v._size;
  _size = v._size;
}
template <class T>
vector<T>& vector<T>::operator=(vector v){
//赋值现代写法,利用传过来的值,拿临时变量v拷贝构造
//再交换他们的值,这个临时变量又会出作用域销毁
  _data = v._data;
  v._data = nullptr;
  _size = v._size;
  _capacity = v._capacity;
  return *this;
}
template <class T>
typename vector<T>::iterator vector<T>::insert (vector<T>::iterator position, const T& val){
  assert(position<=end());
  int pos = position -begin();
  if(_capacity == _size){//需要扩容
    _capacity = _capacity==0?2:(_capacity*1.5);
    reserve(_capacity);
  }
  position = begin()+pos;
  for(vector<T>::iterator it = end()-1;it>=position;it--){
     *(it+1)=*it;
  }
  *position = val;
  _size++;
  return position;
}
template <class T>
typename vector <T>::iterator vector<T>::insert(int position,const T& val){
  return insert(begin()+position,val);
}
template <class T>
typename vector<T>::iterator vector<T>::erase(vector<T>::iterator position){
  assert(position >= begin() && position < end());//迭代器不能大于或等于end()
  for(vector<T>::iterator it = position+1;it<=end();it++){
    *(it-1)=*it;
  }
  _size--;
  return position;
}
template <class T>
typename vector<T>::iterator vector<T>::erase(int position){
  return erase(begin()+position);
}
template <class T>
typename vector<T>::iterator vector<T>::find(vector<T>::iterator _begin,vector<T>::iterator _end,const T& val){
 if(_begin>=end() || _end<begin()) return nullptr;
  if(_begin<begin()) _begin=begin();//条件限制一下
  if(_end>=end()) _end = end()-1;
  for(vector<T>::iterator it = _begin;it<=_end;it++){
    if(*it == val) return it;//找到返回迭代器
  }
  return nullptr;
}

 main.cpp

#include "my_vector.cpp"
void test01(){
vector<int> v1;
  v1.push_back(1);
  v1.push_back(2);
  v1.push_back(5);
  v1.push_back(3);
  v1.push_back(100);
  cout<<v1[1]<<endl;
  for(auto& e : v1){
    cout<<e<<' ';
  }
  cout<<endl;
  cout<<v1.capacity()<<endl;
  vector<int> v2;
  v2 = v1;
  v2.insert(v2.begin()+2,1230);
  for(auto& e:v2){
    cout<<e<<' ';
  }
  cout<<endl;
  v2.erase(v2.end()-1);
  for(auto& e:v2){
    cout<<e<<' ';
  }
  cout<<endl;
  cout<<*(v2.find(v2.begin(),v2.end(),5))<<endl;
}
int main(){
  test01();
}

注意事项:

 1. mian.cpp中,需要包含的文件是my_vector.cpp,不能像一般那样包含my_vector.h

因为如果模板的声明和定义分别单独的放在.h和.cpp文件中,当要实例化一个模板时,编译器必须看到模板确切的定义,而不仅仅是它的声明若在main()函数中包含.h文件,则编译器无法知道模板的确切定义,所以要在main()中包含.cpp文件,.cpp文件中又会包含.h文件,这样一来通过在main函数中包含.cpp文件就会将类的定义和声明都包含进来,编译器自然能找到模板的确切定义。

2. 深浅拷贝问题

 在上面的代码中,每次进行拷贝和移动时,都是经过for循环,一个个去拷到新数组中去的,并没有使用memset等函数,因为memset是以一个字节一个字节进行拷贝的,如果此时vector里面存放的是自定义类型的数据,就会出错。

3. 迭代器失效问题 

my_vector.cpp中,insert函数如果不注意就会发生迭代器失效

如图:传进来的迭代器,如果进行扩容了,那么这个迭代器也就失效了,因为其指向的是旧空间的,而旧空间已经释放。 

所以,需要提前记录下迭代器与begin()的距离,再到扩容后重新更新这个迭代器。

 总之:写模板类还是有点挑战的,需要多加练习!!

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

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

相关文章

Redis学习(第八章缓存策略)

目录 RdisExample 课程介绍 1.Redis介绍 2.Redis 安装 3. Redis的数据结构 4. Redis缓存特性 5. Redis使用场景 6. Redis客户端-Jedis 7. Jedis Pipeline 8. Redis缓存策略 学习资料 QA 相关问题 http, socket ,tcp的区别 RdisExample 项目代码地址&#xff1a;htt…

Leetcode—104.二叉树的最大深度【简单】

2023每日刷题&#xff08;六&#xff09; Leetcode—104.二叉树的最大深度 递归实现代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/int maxDepth(struct TreeNode* root){…

2017年高热度编程语言简介

世上语言千千万&#xff0c;我却独爱这一种!”这句话用来形容程序员和编程语言之间的爱恨情仇实在是再精准不过了。根据GitHub 2016年的开源报告&#xff0c;其上所有开源项目共包含了316种编程语言&#xff0c;这是一个什么概念呢?举个例子来说&#xff0c;世界上共有226个国…

高防CDN的发展趋势

随着互联网的迅速发展&#xff0c;网站和在线服务的安全性变得至关重要。网络攻击如DDoS攻击和恶意流量正在增加&#xff0c;因此高防CDN&#xff08;高防御内容分发网络&#xff09;成为网络安全的重要组成部分。本文将探讨高防CDN未来的发展趋势&#xff0c;并比较其与传统CD…

PyCharm改变代码背景图片的使用教程

一个好的集成环境是学习和使用一门编程语言的重中之重&#xff0c;这次我给大家分享如何改变PyCharm软件的代码背景图片。 说明&#xff1a;本教程使用的是汉化版PyCharm软件。 打开PyCharm软件。 点击软件最上方导航栏的文件&#xff0c;然后找到设置。 打开设置然后点击外观…

小米妙享无法正常启动,用非管理员权限启动

网上找到的其他方法大多数不太好 1.非管理员方式运行的方法 1.创建一个用户123&#xff0c;密码123 2.创建一个bat文件&#xff0c;复制粘贴以下内容 runas /savecred /user:123 “C:\Program Files\MI\AIoT\Launch.exe” 第一次点击运行&#xff0c;要输入密码&#xff0c;以…

面试官:说说webpack的热更新是如何做到的?

一、是什么 HMR 全称 Hot Module Replacement&#xff0c;可以理解为模块热替换&#xff0c;指在应用程序运行过程中&#xff0c;替换、添加、删除模块&#xff0c;而无需重新刷新整个应用 例如&#xff0c;我们在应用运行过程中修改了某个模块&#xff0c;通过自动刷新会导致…

靶机 DC_1

DC_1 信息搜集 存活检测 详细扫描 网页目录扫描 网页信息搜集 cms 为 Drupal 漏洞利用 使用 msf 搜索 drupal 的漏洞 启动 msfconsole搜索 search drupal尝试编号为 0 的漏洞 失败 利用编号为 1 的漏洞 use 1查看需要配置的选项 show options设置目标 ip set rhost 10…

Leetcode—1726.同积元组【中等】

2023每日刷题&#xff08;六&#xff09; Leetcode—1726.同积元组 哈希表解题思路 实现代码 class Solution { public:int tupleSameProduct(vector<int>& nums) {unordered_map<int, int>count;int n nums.size();int i, j;for(i 0; i < n - 1; i) {f…

C语言实现单链表(图解增删查改+代码)

文章目录 写在前面1. 链表节点的定义2. 链表的创建3. 插入数据3.1 头插3.2 尾插3.3 在指定位置的前面插入数据 4. 删除数据4.1 头删4.2 尾删4.3 删除指定位置的数据 5. 查找数据5. 链表的销毁 写在前面 上面文章用C语言实现了顺序表的增删查改&#xff0c;本片文章继续用C语言…

web各个指标理解

QPS : 单位时间得请求次数 TPS &#xff1a;单位时间得事务数 并发 &#xff1a; QPS *单位响应时间 pv &#xff1a;进入一个网站&#xff0c;又单击打开该网站的其他页面&#xff0c;每打开一个页面就 增加一个PV,甚至在同一页面每刷新一次也多一个PV 二八定律&#xff1a;百…

nonaDlA 逻辑分析仪 使用记录

注意事项&#xff0c;很灵敏&#xff0c;不要用手碰&#xff0c;产生误触发 安装软件 github地址 官方提供的淘宝地址与使用说明 1.安装 1.安装程序 &#xff1a;下载githubDLA源码&#xff0c;打开 software\PulseView.exe安装 2.安装驱动&#xff1a;安装完第一步后&a…

【OpenVINO】行人摔倒检测 — 基于 OpenVINO C# API 部署PP-Human-下篇

行人摔倒检测 — 基于 OpenVINO C# API 部署PP-Human 4. 配置 PP-Human_Fall_Detection 项目4.1 环境配置4.2 创建 AlxBoard_deploy_yolov8 项目4.3 添加项目源码4.4 添加 OpenVINO C# API4.5 添加 OpenCvSharp 5. 测试 PP-Human_Fall_Detection 项目5.1 创建视频读取器5.2 行人…

Python南瓜头

系列文章 ​​​​​​​ 序号文章目录直达链接1浪漫520表白代码https://want595.blog.csdn.net/article/details/1306668812满屏表白代码https://want595.blog.csdn.net/article/details/1297945183跳动的爱心https://want595.blog.csdn.net/article/details/1295031234漂浮…

生成二维码

Qt本地生成二维码-第三方库Libqrencode Chapter1 Qt本地生成二维码-第三方库Libqrencode一、功能简介二、本地生成二维码三、在线生成二维码 Chapter2 Qt生成二维码图片方法QRCode二维码简介如何选定QR码版本&#xff1f;主要方法(1) 下载qrencode源码(2) 将qrencode源码移植到…

C++ STL六大组件

目录 前言 一、容器 1 向量 1.1 向量&#xff08;Vector&#xff09;和数组&#xff08;array&#xff09;之间的区别 1.2 语法 1.3 示例 1.3.1 创建 vector 对象 1.3.2 不能打印向量对象&#xff1b;不能打印空向量中的元素&#xff0c;因为空向量中无元素可打印 1.3…

又哭又笑,这份面试宝典要是早遇到就好了

01、算法原理 选择排序(Selection sort)是一种简单直观的排序算法。 第一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;存放在序列的起始位置&#xff0c;然后再从剩余的未排序元素中寻找到最小&#xff08;大&#xff09;元素&#…

filebeat(8.9.0)采集日志到logstash,由logstash发送的es

filebeat采集日志到logstash&#xff0c;由logstash发送的es 下载并配置filebeat下载配置logback.xml logstash配置 下载并配置filebeat 下载 参考 配置 filebeat.inputs: - type: filestreamenabled: truepaths:# 日志文件目录- D:\modellog\elkdemo\*\*.logparsers:# 多…

消息队列 RocketMQ 消息重复消费问题(原因及解决)

目录 1.出现重复消费的原因 2.解决 2.1 数据库插入法 2.2 使用布隆过滤器 2.2.1 添加hutool的依赖 2.2.2 测试生产者 2.2.2 测试消费者 1.出现重复消费的原因 BROADCASTING(广播) 模式下&#xff0c;所有注册的消费者都会消费&#xff0c;而这些消费者通常是集群部署的…

hdlbits系列verilog解答(内部wire)-09

文章目录 wire线网类型介绍一、问题描述二、verilog源码三、仿真结果wire线网类型介绍 wire线网类型是verilog的一种数据类型,它是一种单向的物理连线。它可以是输入也可以是输出,它与reg寄存器数据类型不同,它不能存储数据,只能用于组合逻辑建模。常用于assign连续赋值语…