【简易版tinySTL】 vector容器

news2024/10/23 4:56:35

文章目录

    • 基本概念
    • 功能
    • 思路
    • 代码实现
      • vector.h
      • test.cpp
    • 代码详解
      • 变量
      • 构造函数
      • 析构函数
      • 拷贝构造
      • operator=
      • push_back
      • operator[]
      • insert
      • printElements
    • 本实现版本 和 C++ STL标准库实现版本的区别:

基本概念

  • vector数据结构和数组非常相似,也称为单端数组
  • vector与普通数组区别: 不同之处在于数组是静态空间,而vector可以动态扩展
  • 动态扩展: 并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝新空间,释放原空间

功能

设计一个 Vector 类,该类应具备以下功能和特性:

1、基础成员函数:

  • 构造函数:初始化 Vector 实例
  • 析构函数:清理资源,确保无内存泄露
  • 拷贝构造函数:允许通过现有的 MyVector 实例来创建一个新实例
  • 拷贝赋值操作符:实现 MyVector 实例之间的赋值功能

2、核心功能:

  • 添加元素到末尾:允许在 Vector 的末尾添加新元素
  • 获取元素个数:返回 Vector 当前包含的元素数量
  • 获取容量:返回 Vector 可以容纳的元素总数
  • 访问指定索引处的元素:通过索引访问特定位置的元素
  • 在指定位置插入元素:在 Vector 的特定位置插入一个新元素
  • 删除数组末尾元素:移除 Vector 末尾的元素
  • 清空数组:删除 Vector 中的所有元素,重置其状态

3、遍历:

  • 遍历并打印数组元素:提供一个函数,通过迭代器遍历并打印出所有元素

4、高级特性:

  • 容器扩容:当前容量不足以容纳更多元素时,自动扩展 Vector 的容量以存储更多元素

思路

内存分配

当容量不足以容纳新元素时,std::vector 会分配一块新的内存空间,将原有元素复制到新的内存中,然后释放原内存。这个过程确保了元素的连续存储。

动态扩容

当需要进行扩容时,std::vector 通常会将容量翻倍,以避免频繁的内存分配操作,从而减少系统开销。

涉及以下步骤:

  1. 分配一个更大的内存块,通常是当前大小的两倍(这个增长因子取决于实现)。
  2. 将当前所有元素移到新分配的内存中。
  3. 销毁旧元素,并释放旧内存块。
  4. 插入新元素。

image-20240521155911371

  • 在上图中, 有一个vector<int> v对象, 其成员变量存储在在了栈上, 包括size, capacity, data pointer,分别表示数组已经使用的大小、数组的容量、数组的首地址, 最左边表示初始时刻的堆栈状态,
  • 某时刻调用v.push_back(20), 检查发现此操作不会超出容量上限, 因此在中间的堆栈示意图中插入了20, 并更新控制结构中的size = 2
  • 下一时刻调用v.push_back(30), 此时检查发现此操作要求的容量不足, 因此需要重新在堆内存申请容量为4的内存空间, 如右边的示意图所示, 并且复制原来的内容到新的地址, 完成此操作后可以丢弃原来的堆内存空间, 并插入30

代码实现

vector.h

#include <iostream>
#include <algorithm>
#include <sstream>
#include <string>
#include <stdexcept>

namespace mystl{
template <typename T>
class Vector
{
private:
    T *elements;    
    size_t capacity;    
    size_t size;    
    
public:
    
    Vector():elements(nullptr), capacity(0), size(0){};
    
    ~Vector()
    {
        delete[] elements; 
    }
    
    Vector(const Vector& other):capacity(other.capacity),size(other.size)
    {
        
        elements = new T[capacity];
        std::copy(other.elements, other.elements+size, elements);
    }
    
    Vector& operator=(const Vector& other)  
    {
        if(this != &other)  
        {
            delete[] elements;  
            capacity = other.capacity;
            size = other.size;
            elements = new T[capacity];
            std::copy(other.elements, other.elements+size, elements);
        }
        return *this;  
    }
    void push_back(const T& value)
    {
       
        if(size == capacity)
        {
            reserve(capacity==0? 1:2*capacity);
        }
        elements[size++] = value;
    }
    size_t getSize() const
    {
        return size;
    }
    size_t getCapacity() const
    {
        return capacity;
    }
    T& operator[](size_t index)
    {
        if(index >= size)
        {
            throw std::out_of_range("Index out of range");
        }
        return elements[index];
    }
    void insert(size_t index, const T& value)
    {
        if(index > size)
        {
            throw std::out_of_range("Index out of range");
        }
        if(size == capacity)
        {
            reserve(capacity == 0? 1:capacity*2);
        }
        for(size_t i = size; i>index;--i)  // TODO:为什么不是i--?
        {
            elements[i] = elements[i-1];
        }
        elements[index] = value;
        ++size;
    }
    void pop_back()
    {
        if(size > 0)
        {
            --size;   
        }
    }
    void clear()
    {
        size = 0;
    }
    T* begin()
    {
        return elements;
    }
    T* end()
    {
        return elements+size;
    }
    const T* begin() const
    {
        return elements;
    }
    const T* end() const
    {
        return elements+size;
    }
    void printElements() const 
    {
        for(size_t i = 0; i<size; ++i)
        {
            std::cout << elements[i] << " ";
        }
        std::cout << std::endl;
    }
    
private:
    void reserve(size_t newCapacity)
    {
        if(newCapacity > capacity)
        {
            T* newElements = new T[newCapacity];
            std::copy(elements,elements+size,newElements);
            delete[] elements;
            elements = newElements;
            capacity = newCapacity;
        }
    }
    
};
}

test.cpp

#include "vector.h"
#include "list.h"
#include "deque.h"

void vectorTest()
{
    mystl::Vector<int> v1;
    v1.push_back(10);
    mystl::Vector<int> v2(v1);
    mystl::Vector<int> v3 = v2;
    for(int i = 0; i < 5; i++)
    {
        v3.push_back(i);
    }
    int size = v3.getSize();
    int cap = v3.getCapacity();
    int t = v3[3];
    std::cout << t << std::endl;
    v3.insert(2,88);
    v3.pop_back();
    int* ptr = v3.begin();
    v3.printElements();
    v3.clear();
}

int main()
{
    vectorTest();
    system("pause");
    return 0;
}

代码详解

变量

T *elements;    // 指向动态数组的指针
size_t capacity;    // 数组的容量, size_t = unsigned int
size_t size;    // 数组中元素的个数
  • elements: 指向动态数组的指针
  • capacity:数组的容量, size_t = unsigned int
  • size: 数组中元素的个数

构造函数

Vector():elements(nullptr), capacity(0), size(0){};

构造函数:初始化Vector对象,默认设置指针悬空,容量设置为0,当前元素的数量也为0

析构函数

  ~Vector()
  {
	delete[] elements; // elements是数组指针
  }

析构函数:销毁Vector对象,释放指针

拷贝构造

Vector(const Vector& other):capacity(other.capacity),size(other.size)
{
    // 找一块地方开辟地址空间
    elements = new T[capacity];
    std::copy(other.elements, other.elements+size, elements);
}

拷贝构造函数,cap、size都默认和副本相同

std::copy()

  • 作用:从一个容器复制元素到另一个容器。
  • std::copy需要三个参数:两个指定要复制的元素范围的迭代器(起始迭代器和结束迭代器),以及一个指向目标位置开始的迭代器。
    指针也是一种天然的迭代器

operator=

    // 拷贝复制+运算符重载
    Vector& operator=(const Vector& other) 
    {
        
        {
            delete[] elements; 
            // 获取副本的数据
            capacity = other.capacity;
            size = other.size;
            elements = new T[capacity];
            // 分配新内存,并复制所有元素
            std::copy(other.elements, other.elements+size, elements);
        }
        return *this; 
    }
  • if(this != &other):通过判断两个指针是否相同,检查是不是自赋值的情况
  • delete[] elements;:删除这一块地址的数据、指针
  • return *this;: 返回当前对象的引用

push_back

    // push_back函数
    void push_back(const T& value)
    {
        if(size == capacity)
        {
            reserve(capacity==0? 1:2*capacity);
        }
        elements[size++] = value;
    }
  • if(size == capacity):如果内存将要溢出,则开辟更大的空间
  • reserve(capacity==0? 1:2*capacity);:不能简单的将capacity容量翻倍, 需要考虑0的边界情况,如果是开始时刻,没有分配内存,则分配1块内存,否则内存空间加倍

operator[]

下标操作符重载,允许通过下标访问Vector中的元素

T& operator[](size_t index)
{
    // 如果指定的下标越界(大于或等于 size),则抛出 std::out_of_range 异常
    if(index >= size)
    {
    throw std::out_of_range("Index out of range");
    }
    return elements[index];
}

int arr[] = { 99, 15, 100, 888, 252 } 时,arr是指向数组首地址的指针。可以采用 arr[i] 的形式访问数组元素。如果 p 是指向数组 arr 的指针,那么也可以使用 p[i] 来访问数组元素,它等价于 arr[i]。因此,elements[index]等同于vector v[index]

insert


void insert(size_t index, const T& value)
{
    if(index > size)
    {
    	throw std::out_of_range("Index out of range");
    }
    if(size == capacity)
    {
    	reserve(capacity == 0? 1:capacity*2);
    }
    // 后续元素后移
    for(size_t i = size; i>index;--i)
    {
    	elements[i] = elements[i-1];
    }
    elements[index] = value;
    ++size;
}
  • Vector 的指定位置 index 插入一个元素 value
  • 如果 index 大于 size,则抛出 std::out_of_range 异常;
  • 如果当前没有足够的容量来存储新元素,则通过 reserve 函数扩展数组的容量;
  • index 之后的所有元素向后移动一个位置,为新元素腾出空间;
  • 将新元素放置在 index 位置;
  • 增加 Vectorsize
for(size_t i = size; i>index;--i)
{
	elements[i] = elements[i-1];
}

for循环执行顺序如图:

image-20240521162919322

printElements

void printElements() const
{
    for(size_t i = 0; i<size; ++i)
    {
    	std::cout << elements[i] << " ";
    }
    std::cout << std::endl;
}

const 关键字在成员函数声明之后表示该函数不会修改类的任何成员变量

本实现版本 和 C++ STL标准库实现版本的区别:

内存分配策略不同、无范围检查和异常处理、只实现了一些基本的功能,例如插入、删除、访问元素等,而没有涵盖 std::vector 的所有功能和特性

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

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

相关文章

IP SSL证书使用率大幅度提升

IP SSL证书的使用人数在增长&#xff0c;这一趋势背后有几个推动因素&#xff1a; 1.网络安全意识提升&#xff1a;随着网络安全事件频发&#xff0c;用户和企业对数据保护的重视程度日益增加。IP SSL证书能为基于IP地址直接访问的网站或服务提供加密&#xff0c;有助于防止数据…

深入了解常用负载均衡软件

在构建高性能、高可用的分布式系统时&#xff0c;负载均衡技术扮演着至关重要的角色。它通过合理分发网络请求到后端服务器集群&#xff0c;从而有效提升系统吞吐量、减少响应延迟、并保障系统的稳定运行。本文将介绍几种常用的负载均衡软件&#xff0c;包括它们的优缺点、应用…

高效空气净化器大揭秘,不要再花冤枉钱了!对付“灰尘、毛絮”真好用

最近因为天气炎热的原因&#xff0c;基本上家家户户每天都在开窗通风透气&#xff0c;这就导致很多家庭一直饱受灰尘、毛絮等问题的困扰。 即使天天打扫&#xff0c;家里各处依旧能够找到灰尘、毛絮的踪迹&#xff0c;让很多人苦不堪言。甚至有一部分的灰尘毛絮又聚集在了一些…

C语言中字符串处理函数

目录 前言 1. strlen 测字符串长度函数 2.字符串拷贝函数 2.1strcpy 2.2 strncpy 3.strcat字符串追加函数 4. strcmp/strncmp 比较函数 5.字符查找函数 5.1 strchr 5.2 strrchr 6.atoi/atol/atof字符串转换数值 总结 前言 从0开始记录我的学习历程&#xff0c;我会尽…

Freemaker 模板

背景 发送邮件&#xff0c;正文利用freemaker完成 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId> </dependency>Autowired private Configuration configurer;GetMap…

04 - matlab m_map地学绘图工具基础函数 - 设置网格

04 - matlab m_map地学绘图工具基础函数 - 设置网格 0. 引言1. 关于m_grid2. 关于m_utmgrid3. 结语 0. 引言 本篇介绍下m_map中网格设置有关的函数m_grid和m_utmgrid&#xff0c;其中m_grid较为常用&#xff0c;m_utmgrid为设置UTM网格&#xff0c;仅支持在UTM投影下使用。 首先…

【数据库系统概论复习】关系数据库与关系代数笔记

文章目录 基本概念数据库基本概念关系数据结构完整性约束 关系代数关系代数练习课堂练习 语法树 基本概念 数据库基本概念 DB 数据库&#xff0c; 为了存用户的各种数据&#xff0c;我们要建很多关系&#xff08;二维表&#xff09;&#xff0c;所以把相关的关系&#xff08;二…

算法训练营day15--110.平衡二叉树+ 257. 二叉树的所有路径+ 404.左叶子之和+222.完全二叉树的节点个数

一、110.平衡二叉树 题目链接&#xff1a;https://leetcode.cn/problems/balanced-binary-tree/ 文章讲解&#xff1a;https://programmercarl.com/0110.%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91.html 视频讲解&#xff1a;https://www.bilibili.com/video/BV1Ug411S7m…

odoo 出库发票三大凭证

销售一般有三张凭证 库存计价凭证 库存计价凭证&#xff0c;又称为存货计价凭证或库存评估凭证&#xff0c;在会计和企业资源规划&#xff08;ERP&#xff09;系统中&#xff0c;特别是如Odoo这样的系统中&#xff0c;是指记录库存商品成本变动的会计凭证。这些变动通常涉及到…

【牛客深信服总结】

1.反转链表 2.协议 交换机路由器 3.手写代码&#xff0c;一个二叉树&#xff0c;从根节点到叶子结点算一条路径&#xff0c;打印出所有路径。 4.一些数据结构相关的问题&#xff0c;包括栈和队列的应用&#xff0c;链表和数组的区别&#xff0c;最大堆和最小堆&#xff0c;动态…

leetCode-hot100-链表专题

leetCode-hot100-链表专题 链表简介单链表单链表的使用例题206.反转链表19.删除链表的倒数第N个结点24.两两交换链表中的节点25.K个一组翻转链表 双向链表双向链表的使用 循环链表61.旋转链表141.环形链表142.环形链表Ⅱ LinkedListLinkedList的使用 链表简介 参考博客&#x…

Z语言学习——基于通讯案例

目录 1数据类型 2初始状态 3 Alice的消息发送 4 Bob接收与发送消息 5 Alice接收消息 6消息的增删改查 6.1 删除消息 6.2查询消息 6.3修改/增加消息 7定理证明——重要目的 案例背景&#xff1a; (1)构建一个交互式的通讯方案&#xff1b; (2)攻击者控制了所有的通讯…

计算机编码以及URL转码

目录 一、计算机编码 1.ASCII编码 2. GB2312编码 3.GBK编码 4.UTF-8编码 二、URL转码 1.encodeURI和decodeURI 2.encodeURIComponent 和 decodeURIComponent 三、Base64 一、计算机编码 在计算机中&#xff0c;所有的数据在存储和运算时都要使用二进制数表示&#xf…

开源驰骋低代码-积极拥抱AI时代

开源驰骋AI低代码-积极拥抱AI时代 驰骋AI代码开发平台ccfast通过集成人工智能技术&#xff0c;为开发者提供了一系列强大的辅助功能&#xff0c;极大地提升了开发效率和流程体验。以下是针对您列出的功能的详细解释&#xff1a; 概要说明 驰骋低代码开发平台是一款基于云计算和…

【人工智能,机器学习,统计学习,科学表征】开源商用与研发合作

个体工户linjing-lab托管在Github&#xff0c;现公开招募商用与合作人员&#xff0c;目标人群分为以下几个方向&#xff1a; 数学、信息科学、计算机专业的大学高年级学生&#xff0c;熟悉C和面向对象模型&#xff0c;擅长Pybind11编译算子到Python环境。26岁以下的大学本科毕…

Java学习 (四) 面向对象--类与方法

关于面向对象的知识点 1、 java类 以及类成员 属性 方法 构造器 代码块 内部类 2、面向对象特征 封装 继承 多态 3、 其他关键字使用 this \ super \ package \ import \static \ final\ interface\ abstract …

北方高温来袭!动力煤却不涨反跌的原因分析

内容提要 北方高温而南方降雨偏多的格局或将继续&#xff0c;整体水力发电量增长可能继续明显增长&#xff0c;但火电增幅可能继续缩小。5月重点火电厂的发电量和耗煤量增速均呈现负增长&#xff0c;耗煤量月度同比下降7%&#xff0c;而重点水电同比大增近40%。我国电力行业绿…

Apple Phone Memory

Apple Phone Memory 苹果手机内存查询&#xff0c;哪些应用程序&#xff08;app&#xff09;占用内存&#xff1a; 设置 通用 iPhone储存空间 清理下QQ音乐&#xff1a;

灵感互娱U3D笔试题

文章目录 题目1解析 题目2解析 题目3解析 题目4数组链表 题目5解析 题目6解析 题目7解析题目8解析 后话 题目1 以下C#代码的输出顺序是什么 namespace ConsoleApp2 {internal class Program{class A{ public A(string text){Console.WriteLine(text);}}class B{static A a1 …

同城跑腿多合一系统源码小程序支持安卓+IOS+公众号+H5

&#x1f680; 同城跑腿多合一小程序&#xff1a;便捷生活新选择 &#x1f4a8; 一、引言&#xff1a;走进便捷新纪元 在这个快节奏的现代生活中&#xff0c;时间成了最宝贵的财富。而“同城跑腿多合一小程序”正是为了满足大家对于便捷、高效生活的追求而诞生的。它不仅是一款…