STL vector 手写--迭代器设计思想、空间配置器思想!两个面试题

STL空间配置器

空间配置器的核心功能就是把==对象的内存开辟和对象构造的过程分解开,对象析构和内存释放的过程分解开==,因此空间配置器主要提供了以下四个函数:

空间配置器的函数功能
allocate负责开辟内存
deallocate负责释放内存
construct负责在容器中构造对象
destroy负责析构对象

简单的空间配置器:

// 手写空间配置器
template<typename T>
class Alloc{
public:
    T *allocate(size_t size){
        return (T*)malloc(sizeof(T) * size);
    }
    void deallocate(T* ptr, size_t size){
        free(ptr);
        // free(ptr, sizeof(T) * size);
    }
    template<typename... Args>
    void construct(T *ptr,  Args&& ...args){
        new ((void*)ptr) T(std::forward<Args>(args)...);
    }
    void destroy(T *ptr){
        ptr->~T();
    }
};

这是一种设计思想:将分配空间和构造、释放空间和析构分离,这样在容器初始化时不需要构造,只用分配空间,push时才构造。

常规的new 和 delete 都包含两阶段操作:

  • 对于 new 来说,编译器会先调用 ::operator new 分配内存;然后调用 Obj::Obj() 构造对象内容。

  • 对于 delete 来说,编译器会先调用 Obj::~Obj() 析构对象;然后调用 ::operator delete 释放空间。

STL allocator 决定将这两个阶段操作区分开来,分工明确。

  • 对象构造由 ::construct() 负责;对象释放由 ::destroy() 负责。

  • 内存配置由 alloc::allocate() 负责;内存释放由 alloc::deallocate() 负责;

如果不这样做,会出现什么问题?(参考大秦坑王博客)

  • vector<A> v(10)构造容器时,不应该构造10个A对象,应该为其分配空间,A对象的构造应由用户调用。
  • v.pop_back()时,应同时析构A对象。
  • 出了v的作用域时,应析构v内的所有有效元素。

内存池设计思想以及两级空间配置器这里暂不讨论!

STL迭代器

traits编程技法 traits 编程技法

迭代器模式和STL中的迭代器的区别?

在这里插入图片描述

什么是萃取?

traits技法利⽤“内嵌型别“的编程技巧与编译器的template参数推导功能,增强C++未能提供的关于型别认证⽅⾯的能⼒。常⽤的有iterator_traitstype_traitsiterator_traits负责萃取迭代器的特性,type_traits负责萃取型别特性

为什么需要萃取?

在这里插入图片描述

本质就是通过模板的偏特化扩充value type的适用性(泛化性)

工作原理

在这里插入图片描述

这里提供一个简单版本的迭代器:

template<typename T>
class Iterator{
public:
    template<typename U, typename Alloc>
    friend class Vector;
    Iterator(T *ptr = nullptr):ptr_(ptr){}
    void operator++() { ++ptr_;}
    bool operator!= (const Iterator &it){
        return ptr_ != it.ptr_;
    }
    T& operator*(){ return *ptr_; }
    T* operator->() { return ptr_; }
private:
    T *ptr_;
};

如上所述,会存在上文提到的几个问题,但是面试中写出这个就够了!

面试题:push_back()以及emplace_back()区别?

我们来看源码,从源码分析是最直观的:

push_back:

/**
 * 向容器的末尾添加一个新元素。
 * 
 * 此函数通过移动语义将给定的值val添加到容器的末尾。如果容器已满,
 * 则调用resize函数来增加容器的容量。使用分配器的construct方法来
 * 在新位置构造元素,确保正确地管理资源。最后,更新元素数量的计数器。
 * 
 * @tparam Args 参数类型列表,支持完美转发。
 * @param val 要添加到容器末尾的值,可以是任意类型。
 */
void push_back(Args&& val)
{
    // 检查容器是否已满,如果满则扩大容器容量
    if(size_ == capacity_){
        makeSpace();
    }
    // 使用分配器在容器末尾构造新元素,通过转发语义保持值的原始性
    allocator_.construct(vec_ + size_, std::forward<Args>(val) );
    // 更新元素数量
    ++ size_;
}

emplace_back:

/**
 * 向向量末尾添加一个新元素,使用emplace_back直接在容器内就地构造对象。
 * 这样做的好处是避免了额外的对象拷贝或移动,从而可能提高性能。
 * 
 * @tparam Args 构造函数的参数类型列表,支持完美转发。
 * @param args 用于构造新元素的参数,通过解包传递给构造函数。
 */
void emplace_back(Args&&... args)
{
    // 如果当前容量不足,则增加容量以容纳新元素。
    if(size_ == capacity_){
        makeSpace();
    }
    // 使用分配器在向量的末尾位置构造一个新元素。
    // 这里使用了std::forward来保证参数能被正确地转发给新元素的构造函数。
    allocator_.construct(vec_ + size_, std::forward<Args>(args)...);
    // 更新元素数量。
    ++size_;
} 

几点区别:

  • push_backemplace_back在末尾插入一个已经存在的对象是没有丝毫区别的!

  • emplace的方便之处在于,在原地构造对象,少了一次拷贝构造或者移动构造的开销。

    并且可以接受多个参数(源码:可变参模板、万能引用+完美转发)

面试题:resize()以及reserve()区别?

size()函数返回的是已用空间大小,capacity()返回的是总空间大小,capacity()-size()则是剩余的可用空间大小

当size()和capacity()相等,说明vector目前的空间已被用完,如果再添加新元素,会引起vector空间的动态增长。

由于动态增长会引起重新分配内存空间、拷贝原空间、释放原空间,这些过程会降低程序效率。

因此,可以使用reserve(n)预先分配一块较大的指定大小的内存空间,这样当指定大小的内存空间未使用完时,是不会重新分配内存空间的,这样便提升了效率。

  • resize()函数使用

    1、实质:resize()函数实质是改变vector中的元素个数;

    2、参数:resize()含有两个参数,resize(n,m);参数n表示vector中元素个数n,参数 m表示初始化,参数m可省略。

    resize(n,m)使用有3种情况:

    • 1、参数n<v.size()时,结果是容器v的size减小到n,删除n之后的元素;
    • 2、v.size()<参数n<v.capacity(),结果是容器v的size增加到n,增加的元素值初始化为m
    • 3、v.capacity)<参数n,结果是先增大容量capacity至n,然后初始化值,此时v中的size与capacity均发生改变。

    resize()常⽤情形

    1、需要对容器中现有元素进⾏操作时;

    2、容器初始化之后,使⽤容器时。

  • **reserve()**函数使⽤

    1实质reserve()函数实质是改变vector的容量,即总空间的⼤小**;**

    2、参数:**reserve(n),**只含有⼀个参数,表⽰总空间的⼤小。

    reserve(n)使⽤有2种情况:

    • 1、参数n < v.capacity()时,size与capacity均不发⽣改变;
    • 2、参数**n > v.capacity()时,此时会重新分配⼀块空间,使得capacity扩容⾄n**,size不发⽣改变。

    **reserve()**常⽤情形

    • 常⽤来容器初始化,预留容器空间,以免之后多次动态改变容器空间。
    • 也可以在程序中间调⽤以扩⼤容器的空间.
    • 注意: reserve只能扩⼤容器的空间,并不能减小容器的空间

总结:

  • reserve操作的是capacity(), 扩大容器的空间,并不对size造成影响
  • resize操作的是size(),只有在参数大于capacity时才会扩大capacitysize造成影响!所以push_back()也可能引发扩容。
vector<A> v;
v.resize(2);
// v.reserve(2);
v.push_back(A());
v.push_back(A());
  • resize操作最后size是4,有四个A对象(两个默认构造的对象);而reserve操作最后是2,只有两个A对象,符合预期。性能上更优先选reserve

Vector实现完整代码

#include <bits/stdc++.h>

// 手写空间配置器
template<typename T>
class Alloc{
public:
    T *allocate(size_t size){
        return (T*)malloc(sizeof(T) * size);
    }
    void deallocate(T* ptr, size_t size){
        free(ptr);
        // free(ptr, sizeof(T) * size);
    }
    template<typename... Args>
    void construct(T *ptr,  Args&& ...args){
        new ((void*)ptr) T(std::forward<Args>(args)...);
    }
    void destroy(T *ptr){
        ptr->~T();
    }
};
template<typename T>
class Iterator{
public:
    template<typename U, typename Alloc>
    friend class Vector;
    Iterator(T *ptr = nullptr):ptr_(ptr){}
    void operator++() { ++ptr_;}
    bool operator!= (const Iterator &it){
        return ptr_ != it.ptr_;
    }
    T& operator*(){ return *ptr_; }
    T* operator->() { return ptr_; }
private:
    T *ptr_;
};
template<typename T, typename allocator = Alloc<T> >
class Vector{
public:
    Vector(int size = 0)
        :size_(0), capacity_(size)
    {
        vec_ = allocator_.allocate(size);
    }
    ~Vector()
    {
        for(int i = 0; i < size_; ++i){
            allocator_.destroy(vec_ + i);
        }
        allocator_.deallocate(vec_, capacity_);
        vec_ = nullptr;
    }
    Vector(const Vector<T> &src)
        :size_(src.size_)
        , capacity_(src.capacity_)
        , allocator_(src.allocator_)
    {

        vec_ = allocator_.allocate(capacity_);
        for(int i = 0; i < size_; ++i){
            allocator_.construct(vec_ + i, src.vec_[i]);
        }
    }
    void reserve(int size){
        vec_ = allocator_.allocate(size);
        capacity_ = size;
    }
    Vector<T> operator=(const Vector<T> &src)
    {
        if(this == &src){
            return *this;
        }
        for(int i = 0; i < size_; ++i){
            allocator_.destroy(vec_ + i);
        }
        allocator_.delocate(vec_, capacity_);
        size_ = src.size_;
        capacity_ = src.capacity_;
        vec_ = allocator_.allocae(capacity_);
        for(int i = 0; i < size_; ++i){
            allocator_.construct(vec_ + i, src.vec_[i]);
        }
        return *this;
    }
    template<typename Args>
    void push_back(Args&& val)
    {
        if(size_ == capacity_){
            makeSpace();
        }
        allocator_.construct(vec_ + size_, std::forward<Args>(val) );
        ++ size_;
    }
    void pop_back()
    {
        if(size_ == 0){
            return;
        }
        --size_;
        allocator_.destroy(vec_ + size_);
    }
    template<typename... Args>
    void emplace_back(Args&&... args)
    {
        if(size_ == capacity_){
            makeSpace();
        }
        allocator_.construct(vec_ + size_, std::forward<Args>(args)...);
        ++size_;
    } 
    int size(){ return size_; }

    using iterator = Iterator<T>;
    iterator begin(){ return iterator(vec_); }
    iterator end(){ return iterator(vec_ + size_); }

private:
    T *vec_;
    int size_;
    int capacity_;
    allocator allocator_;
    void makeSpace(){
        if(capacity_ == 0){
            vec_ = allocator_.allocate(sizeof(T));
            size_ = 0;
            capacity_ = 1;
        }
        else{
            T *ptr = allocator_.allocate(capacity_ * 2);
            for(int i = 0; i < size_; ++i){
                allocator_.construct(ptr + i, vec_[i]);
            }
            for(int i = 0; i < capacity_; ++i){
                allocator_.destroy(vec_ + i);
            }
            allocator_.deallocate(vec_, capacity_);
            vec_ = ptr;
            capacity_ *= 2;
        }
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一键直达:2024最新Win10系统安装包!快来下载!

对于想体验Win10系统最新功能的用户来说&#xff0c;寻找可靠的最新系统安装包是特别重要的。接下来系统之家小编就给大家带来2024年最新Win10系统安装包&#xff0c;有需要的小伙伴一键点击就能开始下载。该系统安装步骤简单易懂&#xff0c;无需担心任何装机经验。 推荐下载&…

快递物流运输中的RFID智能锁控应用方案

一、物流货运管理的痛点分析 1.1 货物安全与监控难题 物流货运过程中&#xff0c;货物安全是首要关注的问题。传统的锁控方式存在诸多不足&#xff0c;例如易被撬锁、监控盲点以及难以实时追踪货物状态。据统计&#xff0c;每年因货物丢失或损坏导致的经济损失高达数十亿美元…

景区智能厕所系统,打造智能化,人性化公共空间

在智慧旅游的大潮中&#xff0c;景区智能厕所系统正逐渐成为提升公共空间智能化、人性化水平的关键载体。作为智慧城市建设的重要组成部分&#xff0c;智能厕所系统不仅解决了传统公厕存在的诸多问题&#xff0c;更通过科技的力量&#xff0c;为游客创造了更加舒适、便捷的如厕…

中电金信:加快企业 AI 平台升级,构建金融智能业务新引擎

在当今数字化时代的浪潮下&#xff0c;人工智能&#xff08;AI&#xff09;技术的蓬勃发展正为各行业带来前所未有的变革与创新契机。尤其是在金融领域&#xff0c;AI 模型的广泛应用已然成为提升竞争力、优化业务流程以及实现智能化转型的关键驱动力。然而&#xff0c;企业在积…

Zabbix 配置 VMware 监控

Zabbix监控VMware 官方文档&#xff1a;https://www.zabbix.com/documentation/current/en/manual/vm_monitoring Zabbix 可以使用低级发现规则自动发现 VMware 虚拟机管理程序和虚拟机&#xff0c;并根据预定义的主机原型创建主机来监控它们。Zabbix 还包括用于监控 VMware …

缓存与数据库数据一致性问题

在用了redis缓存的系统中&#xff0c;正常情况下&#xff0c;一个读操作会先查缓存&#xff0c;如果在缓存中查到了&#xff0c;则直接返回&#xff0c;如果缓存中没有&#xff0c;则会查数据库&#xff0c;再将查到的数据写到redis中&#xff0c;然后返回。如下图&#xff1a;…

PDI-kettle工具连接本地虚拟机Ubuntu上的数据库

PDI 配置ubuntu数据库配置Kettle工具 PDI版本&#xff1a;9.4 Ubuntu2204&#xff1a;10.11.8-MariaDB-0ubuntu0.24.04.1 Ubuntu 24.04 配置ubuntu数据库 安装 apt install -y mariadb-server配置监听地址 cat > /etc/mysql/mariadb.conf.d/99-kettle.cnf << EOF …

国衍科技——梅雨季节文物保护专家

尊敬的文物保护者们 随着梅雨季节的脚步渐近&#xff0c;湿润的空气和连绵的雨水不仅为我们的生活带来了不便&#xff0c;更为文物保护工作带来了严峻的挑战。在这个季节&#xff0c;文物发霉的风险急剧上升&#xff0c;每一件珍贵的文化遗产都面临着被时间侵蚀的威胁。然而&am…

玩转内网穿透详细教程,收藏这一篇就够了

小朋友&#xff0c;你是否有过以下这些烦恼&#xff1f; 当你在外地&#xff0c;苦于无法拿到存储在家里的资料&#xff1b; 当你在玩游戏的时候&#xff0c;苦于无法和朋友直接联机&#xff1b; 当你在家里&#xff0c;苦于无法通过自己的电脑连上公司电脑远程办公&#xf…

WPF在.NET9中的重大更新:Windows 11 主题

在2023年的2月20日&#xff0c;在WPF的讨论区&#xff0c;WPF团队对路线的优先级发起了一次讨论。 对三个事项发起了投票。 第一个是Windows 11 主题 第二个是更新的控件 第三个是可空性注释 最终Windows 11 主题得票最高&#xff0c;WPF团队2023-2024的工作优先级就是Windows…

VUE3使用antd引入百度地图 实现位置查询,获取地址经纬度

实现效果: 1.index.html 中先引入下 <script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=自己申请的key"></script> 申请密钥key地址:登录百度账号 注册登录后创建应用,根据自己需求选择 2.新建bmp.js文件…

期权学习必看圣书:《3小时快学期权》要在哪里看?

今天带你了解期权学习必看圣书&#xff1a;《3小时快学期权》要在哪里看&#xff1f;《3小时快学期权》是一本关于股票期权基础知识的书籍。 它旨在通过简明、易懂的语言和实用的案例&#xff0c;让读者在短时间内掌握股票期权的基本概念、操作方法和投资策略。通过这本书&…

Hyper-V克隆虚拟机教程分享!

方法1. 使用导出导入功能克隆Hyper-V虚拟机 导出和导入是Hyper-V服务器备份和克隆的一种比较有效的方法。使用此功能&#xff0c;您可以创建Hyper-V虚拟机模板&#xff0c;其中包括软件、VM CPU、RAM和其他设备的配置&#xff0c;这有助于在Hyper-V中快速部署多个虚拟机。 在…

数字信号处理实验二(模拟信号采样与重构及频谱分析FFT)

模拟信号采样与重构及频谱分析FFT&#xff08;2学时&#xff09; 要求&#xff1a; 对一模拟信号进行采样&#xff1b;对该采样信号进行重构&#xff1b;分析它们的频谱特征。目的&#xff1a; 熟悉MATLAB命令和编辑、运行、调试环境&#xff1b;掌握采样定理及对信号的频谱分析…

无线领夹麦克风怎么挑选,无线领夹麦克风哪个品牌音质最好详解!

在数字化内容创作的浪潮中&#xff0c;无线领夹麦克风凭借其轻便的设计和卓越的录音性能&#xff0c;成为了许多创作者不可或缺的音频帮手。这种类型的麦克风能够轻松附着在衣物上&#xff0c;使得创作者在进行直播或录制视频时能够自由移动&#xff0c;而不受传统麦克风线缆的…

STM32智能家居安防系统教程

目录 引言环境准备智能家居安防系统基础代码实现&#xff1a;实现智能家居安防系统 4.1 数据采集模块 4.2 数据处理与分析 4.3 控制系统实现 4.4 用户界面与数据可视化应用场景&#xff1a;安防管理与优化问题解决方案与优化收尾与总结 1. 引言 智能家居安防系统利用STM32嵌…

前端从业者的历史难题Vue和React的抉择:难度不亚于丈母娘和媳妇

**前端从业者的历史难题&#xff1a;Vue和React的抉择——难度不亚于丈母娘和媳妇** Vue和React这两个框架无疑是当下最为流行的两个选择。它们各自拥有独特的优势和特点&#xff0c;吸引了大量的前端从业者。然而&#xff0c;对于许多从业者来说&#xff0c;如何在Vue和React…

D. Beauty of the mountains(cf955)

分析&#xff1a;有一个n*m的数组a&#xff0c;对应n*m的数组b&#xff0c;想让b0的下标的a的数组之和等于b1的下标的a的数组之和&#xff0c;你可以进行k*k的范围所有数字全部加任意数。 求出子矩阵里0和1的差值&#xff1b; #include<bits/stdc.h> using namespace st…

Android 四大组件

1. Activity 应用程序中&#xff0c;一个Activity通常是一个单独的屏幕&#xff0c;它上面可以显示一些控件&#xff0c;也可以监听并对用户的事件做出响应。 Activity之间通过Intent进行通信&#xff0c;在Intent 的描述结构中&#xff0c;有两个最重要的部分&#xff1a;动…

电子行业MES系统解决方案

工业4.0时代的工业自动化&#xff0c;将在原有自动化技术和架构下&#xff0c;实现集中式控制向分散式增强型控制的基本模式转变&#xff0c;让设备从传感器到因特网的通讯能够无缝对接&#xff0c;从而建立一个高度灵活的、个性化和数字化、融合了产品与服务的生产模式。在这种…