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

news2025/4/15 11:28:42

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;
        }
    }
};

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

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

相关文章

软件运维服务方案(Word原件2024)

软件运维服务方案&#xff08;Word原件&#xff09; 1. 服务简述 我们提供全面的软件运维服务&#xff0c;确保软件系统的稳定运行。 1.1 服务内容 包括监控、维护、故障排查与优化。 1.2 服务方式 结合远程与现场服务&#xff0c;灵活响应客户需求。 1.3 服务要求 高效响应&am…

【Mac】adobe CameraRaw 16 for mac(ps插件RAW处理工具)软件介绍

软件介绍 Adobe Camera Raw是一款专为处理和编辑数字照片原始文件&#xff08;RAW文件&#xff09;而设计的插件&#xff0c;它提供了丰富的功能来调整和优化图像。以下是它的主要特点和功能&#xff1a; 支持广泛的RAW格式&#xff1a; Adobe Camera Raw 16 支持处理来自各…

设置单实例Apache HTTP服务器

配置仓库 [rootlocalhost ~]# cd /etc/yum.repos.d/ [rootlocalhost yum.repos.d]# vi rpm.repo仓库代码&#xff1a; [BaseOS] nameBaseOS baseurl/mnt/BaseOS enabled1 gpgcheck0[AppStream] nameAppStream baseurl/mnt/AppStream enabled1 gpgcheck0挂载 [rootlocalhost …

hdu物联网硬件实验1 小灯闪烁

物联网硬件基础实验报告 学院 班级 学号 姓名 日期 成绩 实验题目 配置环境小灯 实验目的 配置环境以及小灯闪烁 硬件原理 无 关键代码及注释 /* Blink The basic Energia example. Turns on an LED on for one second, then off for one sec…

一维前缀和的实现

这是C算法基础-基础算法专栏的第十一篇文章&#xff0c;专栏详情请见此处。 引入 我们用朴素做法求一维数组的区间和时&#xff0c;一般是从前向后循环累加&#xff0c;它的时间复杂度为&#xff0c;当求区间和的次数过多&#xff0c;则会有超时的可能&#xff0c;那有没有时间…

【吊打面试官系列-MyBatis面试题】MyBatis 实现一对一有几种方式?具体怎么操作的?

大家好&#xff0c;我是锋哥。今天分享关于 【MyBatis 实现一对一有几种方式?具体怎么操作的&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; MyBatis 实现一对一有几种方式?具体怎么操作的&#xff1f; 有联合查询和嵌套查询,联合查询是几个表联合查询,只查询…

Java跳出循环的四种方式

1、continue,break,return continue&#xff1a;跳出当前层循环的当前语句&#xff0c;执行当前层循环的下一条语句。   continue标签 break&#xff1a;跳出当前层循环。 break标签&#xff1a;多层循环时&#xff0c;跳到具体某层循环。 return&#xff1a;结束所有循环…

烟草企业如何在数字化转型中实现从“传统”到“智能”的跨越?

在数字化浪潮的席卷下&#xff0c;各行各业都在经历着深刻的变革。作为国民经济的重要组成部分&#xff0c;烟草行业正处于高质量发展的重要阶段&#xff0c;加快信息系统国产化升级&#xff0c;对于提升行业竞争力、强化信息安全保障具有重要战略意义。 达梦数据积极助力烟草行…

AIGC | 在机器学习工作站安装NVIDIA cuDNN 深度学习库

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] 0x03.初识与安装 cuDNN 深度学习库 什么是cuDNN? cuDNN&#xff08;CUDA Deep Neural Network library&#xff09;是由英伟达&#xff08;NVIDIA&#xff09;开发的深度学习库&#xff0c;专门用…

怀念旧的Windows声音?以下是如何在Windows 11中恢复它们

如果你渴望旧的Windows声音,希望能在Windows 11上再次听到,那你就很幸运了。我们将向你展示如何下载必要的声音包并创建复古的声音方案。 如何获取旧Windows声音的声音包 你需要做的第一件事是下载一个包含旧Windows版本声音的声音包。此外,请确保它包含的每个声音都是WAV…

【学习笔记】爱立信SPO 1400 CRAFT软件基础知识6——配置的备份与恢复的详细方法

一、前期准备 提示&#xff1a;下面所有学习内容都是基于以下条件完成的 条件1.已经正确安装并正常运行SPO 1400 CRAFT软件&#xff08;以下简称LCT&#xff09; 条件2.确认已正确使用爱立信SPO 1400 CRAFT软件通过网络登录设备&#xff08;以下简称NE&#xff09; 具体登录…

Python制作动态颜色变换:颜色渐变动效

文章目录 引言准备工作前置条件 代码实现与解析导入必要的库初始化Pygame颜色变换函数主循环 完整代码 引言 颜色渐变动画是一种视觉上非常吸引人的效果&#xff0c;常用于网页设计和图形应用中。在这篇博客中&#xff0c;我们将使用Python创建一个动态颜色变换的动画效果。通…

记录discuz修改用户的主题出售价格

大家好&#xff0c;我是网创有方的站长&#xff0c;今天遇到了需要修改discuz的主题出售价格。特此记录下 方法很简单&#xff1a; 进入用于组-》选择论坛-》批量修改

前端面试题(CSS篇三)

一、简单介绍使用图片 base64 编码的优点和缺点。 base64是一种图片处理格式&#xff0c;通过特定的算法将图片编码为一长串字符串&#xff0c;在页面显示的时候&#xff0c;可以使用该字符串来代替图片的url属性。 使用base64的优点: 减少一个图片的http请求 使用base64的缺点…

倒退型自闭症与轻度自闭症有什么区别?

作为星贝育园自闭症儿童康复中心的一名专业教师&#xff0c;我深知家长们在面对自闭症谱系障碍&#xff08;ASD&#xff09;时的种种疑问与挑战&#xff0c;尤其是关于倒退型自闭症与轻度自闭症之间的区别。今天&#xff0c;我将从专业视角出发&#xff0c;深入浅出地解析这两种…

Java项目:基于SSM框架实现的德云社票务管理系统【ssm+B/S架构+源码+数据库+开题报告+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的德云社票务管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、功…

在 Azure 云中开始使用适用于 Ubuntu 的 Grafana

介绍 Grafana 是一款开源工具&#xff0c;可用于可视化和分析数据。它特别适合跟踪计算机系统的运行情况。在构建微服务或其他类型的应用程序时&#xff0c;您可能需要分析日志数据、轻松可视化数据或设置特殊警报以接收有关系统中发生的某些事件的通知。 这就是为什么你可能…

【Python】已解决:(Python写入Excel表格报错)‘NoneType’ object has no attribute ‘write’

文章目录 一、分析问题背景二、可能出错的原因四、正确代码示例五、注意事项 已解决&#xff1a;&#xff08;Python写入Excel表格报错&#xff09;‘NoneType’ object has no attribute ‘write’ 一、分析问题背景 在处理Excel文件时&#xff0c;Python提供了多种库来方便…

【Python进阶】函数的扩展

函数 目录 函数 一、容器知识补充 1、字典遍历方法 2、遍历字典元素 keys()方法&#xff1a; values()方法&#xff1a; items()方法&#xff1a; 3、公共运算符 4、公共方法 二、函数介绍 1、函数的概念 2、引入函数 3、函数定义与调用 4、函数的参数 5、函数…

了解基于大模型的多模态风险内容识别技术研究

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 在 AIGC 多场景、多模态的应用中&#xff0c;平台用户输入的信息以及平台模型生成的内容中&#xff0c;可能存在大量涉及色情、敏感、暴力、违禁等风险元素。多模态风险内容识别是一种先进的内容安全分…